/** * skillSteps — the procedure grammar (9.18.0). ONE owner of everything * "steps as data": the types, the validation, the step pointer's shape and * re-key rule, the `skip_step` integrity tool, and EVERY sentence the model * reads about a procedure (the banner, the result suffixes, the skip * sentences, the nudge). Stages and slots call in here; none of them owns * a word of the grammar. * * WHAT A PROCEDURE IS. `defineSkill({ steps })` declares an ordered list of * `{ tool, note }` pairs — "run these tools, in this order, for these * reasons". The framework then OWNS sequence and scope at the protocol * level: while a stepped skill holds the tenure, the tools slot offers only * the current step's tool (plus every escape hatch — see below), so the * model cannot call step 5's tool from step 3 because step 5's schema was * never sent. No refusal machinery, no new gate: an unoffered tool is * uncallable by construction. * * WHAT THE MODEL STILL OWNS: judgment inside the step. It can run the tool, * skip it with a recorded reason (`skip_step`), work around it with an * escape hatch, or stop and say why. The escape hatches are load-bearing * and stay offered under narrowing: `read_skill`, `list_skills`, every * OTHER active skill's tools, the baseline `.tool()` registry, provider * tools. A procedure is a declared order, not a cage. * * THE POINTER. `AgentState.stepPointer` records where the procedure stands: * `{ skillId, step, total, skipped }`, 1-based, `step === total + 1` means * complete. It is strictly SUBORDINATE to the one skill-graph cursor: it * lives and dies inside one skill's tenure, is re-keyed at the same stage * the cursor truth lives (Evaluate), and never crosses runs. It can never * move the cursor. * * CARRIER SHAPE (load-bearing): the pointer travels through scope and the * mount mappers as a 0-or-1-element ARRAY (`StepPointerCarrier`), never as * a bare object. footprintjs's `applyOutputMapping` shallow-merges bare * object values field-by-field — and APPENDS nested arrays — so a re-keyed * pointer mapped as an object would inherit the previous tenure's `skipped` * entries. A top-level array under `arrayMerge: Replace` is set wholesale. * `[]` = the feature is on and no procedure is active; the key is absent * entirely on agents with no stepped skill (zero-cost-when-unused). */ import type { SkillToolDescriptor } from './hostContract.js'; import type { Injection } from './types.js'; /** One step of a declared procedure. */ export interface SkillStep { /** The tool this step runs. MUST be one of this skill's own `tools` — * refused at `defineSkill` otherwise. Repeats across steps are legal * ("look it up again after the change"). */ readonly tool: string; /** Why/what — the sentence the model sees ("look up the order before * touching money"). Required, non-empty: a step with no note is a * force-march instruction, and the note is the whole point. */ readonly note: string; /** * Artifact KINDS this step leaves behind (9.25.0) — a declaration, not * machinery: nothing at run time reads it, and a step without it is * byte-identical to today. It makes the step's data leg checkable at build * (`artifact-kind-unsatisfied`) and drawable by a lens. See * `skillVocabulary.ts` for the satisfiability rule and its boundaries. */ readonly produces?: readonly string[]; /** * Artifact KINDS this step needs to have arrived (9.25.0). Satisfiable * three ways — by an EARLIER step of the same skill, by the skill's own * `consumes` (it arrived from outside), or by the step's tool declaring * `wants` for the kind (the framework redeems that ref at dispatch, from a * store that outlives the turn). Anything else is a build-time WARNING. */ readonly consumes?: readonly string[]; } /** * What the framework does when the model skips a step with `skip_step`: * * - `'advance'` (default) — record the skip and move to the next step; * - `'hold'` — record the skip and keep the step current (its tool stays * the offer; the model may retry it, work around it with the escape * hatches, or finish and explain). */ export type OnSkipPolicy = 'advance' | 'hold'; /** * WHERE a declared procedure stands. 1-based; `step === total + 1` means * the procedure completed. `skipped` holds the indexes the model skipped * under the `'advance'` policy (a `'hold'` skip is recorded on the event * stream, not here — the pointer did not move). */ export interface StepPointer { /** The tenant this pointer belongs to — the stepped skill whose tenure * is current. A cursor move away from it resets the pointer. */ readonly skillId: string; /** Current step, 1-based. `total + 1` = procedure complete. */ readonly step: number; readonly total: number; /** Step indexes skipped-with-reason that the pointer moved past. */ readonly skipped: readonly number[]; } /** * How the pointer travels through scope and every mount mapper: a * 0-or-1-element array. See the module docstring for why a bare object * cannot cross a footprintjs outputMapper safely. */ export type StepPointerCarrier = readonly StepPointer[]; /** The build-time fold of one stepped skill — frozen at `Agent` build. */ export interface StepPlan { readonly skillId: string; readonly steps: readonly SkillStep[]; /** Every tool name the plan's steps name — the hold-out candidates. */ readonly toolNames: ReadonlySet; readonly onSkip: OnSkipPolicy; } /** The lookup the engine, the tools slot and the tool-calls stage share. * Undefined ⇒ no skill on this agent declares steps (the zero-cost gate). */ export type StepPlanFor = (skillId: string) => StepPlan | undefined; /** The integrity tool's reserved name. Auto-attached (dispatchable) when * ≥1 registered skill declares steps; OFFERED only while a stepped tenure * is active and unfinished. */ export declare const SKIP_STEP_TOOL_NAME = "skip_step"; /** * Validate a `steps` declaration against the skill's own tools. All the * data is in hand at `defineSkill`, so every refusal happens there — the * `body-unknown-tool` sibling. */ export declare function validateSkillSteps(skillId: string, opts: { readonly steps?: readonly SkillStep[]; readonly onSkip?: OnSkipPolicy; readonly toolNames: ReadonlySet; }): void; /** Read a skill injection's declared steps off its metadata bag (the * `autoActivate` precedent — the engine ignores unknown keys). */ export declare function stepsOf(injection: Injection): { readonly steps: readonly SkillStep[]; readonly onSkip: OnSkipPolicy; } | undefined; /** * Fold every stepped skill into one frozen plan map. Runs once at * `Agent` build; the map is threaded as closures — nothing rides * `projectActiveInjection`, so the projection allow-list is untouched. * Empty map ⇒ the feature is off everywhere (callers thread nothing). */ export declare function foldStepPlans(injections: readonly Injection[]): ReadonlyMap; /** Unwrap the carrier. `undefined` for an absent key, an empty carrier, * or a value that is not a carrier at all. */ export declare function pointerOf(carrier: unknown): StepPointer | undefined; /** True while the procedure has steps left (`step <= total`). A completed * pointer stays on scope as the record; nothing narrows or nudges on it. */ export declare function stepInProgress(ptr: StepPointer | undefined): ptr is StepPointer; /** The current step's declaration, when the procedure is in progress. */ export declare function currentStepOf(ptr: StepPointer, plan: StepPlan): SkillStep | undefined; /** * The tenure RE-KEY — Evaluate's half of the pointer discipline. * * `tenant` is who holds the tenure THIS iteration: the advanced cursor on * graph agents, else the tail of `activatedInjectionIds` (the shipped * `activeSkillId` notion). Tenant changed → a fresh pointer when the new * tenant declares steps, else cleared. Tenant unchanged → pass-through. * Always returns a carrier (possibly empty) — the caller writes it every * iteration the feature is on, so downstream readers never see a stale one. */ export declare function rekeyStepPointer(args: { readonly prior: StepPointerCarrier | undefined; readonly tenant: string | undefined; readonly stepPlanFor: StepPlanFor; }): StepPointerCarrier; /** * DESCRIBE the `skip_step` tool — name, schema, and the placeholder result. * * A description, not a tool: this module owns every sentence the model reads * about a procedure, and that includes this one, but building the framework's * tool object would mean importing the framework's tool factory into the * grammar. `skillTools.ts#buildSkipStepTool` does the wrapping (9.34.0). * * The tool is auto-attached to the dispatch registry when ≥1 registered skill * declares steps (the `read_skill` auto-attach seam) — always dispatchable, * but OFFERED (schema in the request) only while a stepped tenure is active * and unfinished. Its execute returns a placeholder; the tool-calls stage * overwrites the model-visible result with the authoritative sentence, * exactly as the skill-graph gate overwrites `read_skill` refusals — * bookkeeping lives where the batch order lives. */ export declare function skipStepDescriptor(): SkillToolDescriptor<{ reason: string; }, string>; /** `[Step 3 of 6 — ] ` — prefixed onto the current step's tool * description in the offer (a rebuilt schema copy, substituted by name). */ export declare function stepBannerPrefix(ptr: StepPointer, step: SkillStep): string; /** The per-iteration `skip_step` offer description: names the current * step, the remaining count, and the declared onSkip policy. */ export declare function skipStepDescription(ptr: StepPointer, plan: StepPlan): string; /** Suffix for the `read_skill` result that activated a stepped skill: * where the procedure starts. */ export declare function readSkillStepIntro(plan: StepPlan): string; /** Suffix for a completed step's tool result: done + what is next. */ export declare function stepAdvanceSuffix(next: StepPointer, plan: StepPlan): string; /** The authoritative `skip_step` result under the `'advance'` policy. */ export declare function skipAdvanceSentence(skippedIndex: number, reason: string, next: StepPointer, plan: StepPlan): string; /** The authoritative `skip_step` result under the `'hold'` policy. */ export declare function skipHoldSentence(ptr: StepPointer, reason: string, plan: StepPlan): string; /** Teaching result for `skip_step` with an empty reason — no event. */ export declare function skipNeedsReasonSentence(): string; /** Teaching result for `skip_step` outside a stepped tenure — no event. */ export declare function skipNothingActiveSentence(): string; /** The remaining (unrun) steps — the `steps_unfinished` payload's list and * the nudge's material. Under `'advance'` a skipped step is behind the * pointer and therefore never "remaining". */ export declare function remainingStepsOf(ptr: StepPointer, plan: StepPlan): ReadonlyArray<{ readonly index: number; readonly tool: string; readonly note: string; }>; /** * The one teaching nudge for a premature stop (at most once per turn). * * Written as an AUTHORED FRAME (9.86.0). `stepNudge` appends the result to * `scope.history` wearing `role: 'user'`, so two things follow. First, it has * to be recognisable as this library's own writing: it opens with * {@link STEP_NUDGE_FRAME_PREFIX}, imported from the authorship registry that * `isSaidByPerson` matches against, because the body lists a skill id and * every unrun step's TOOL NAME — exactly the text a routing rule scanning * history watches for, and before the opening was here such a rule fired on * the library's own nudge. * * Second, it is re-read on every later call of the turn, so it may not speak * in the present or issue a standing instruction: "Steps 2–3 have not run … * Finish them" is false the moment they run, and it was still sitting in * `history` when they did. Every clause now reports the call the nudge was * written for, in the past tense, and the ask is stated as what THAT call was * for. Pinned by `test/lib/injection-engine/userTurnProducers.test.ts`, which * runs the composed sentence through `unprovable`. */ export declare function nudgeTeachingMessage(ptr: StepPointer, plan: StepPlan): string;