/** * Composing one chain step's task from the previous step's output — ADR-0033. * * **The hazard this module exists for.** A chain makes step N's task the output of step N−1 — a *governed child*, * not the operator or the orchestrator. At the `build → review` edge of a real pipeline, `build` reads a * repository file, faithfully summarises what it read, and that text becomes `review`'s task. A task is the * highest-authority text a child receives after its own `SKILL.md` body, and `review` may hold `tool:bash`. * * Nothing here is a capability escalation: `--tools` still binds every child to its own ceiling. It is * **instruction-level influence that no grant expresses**, and ADR-0012 puts prompt injection explicitly inside * this project's threat model. * * **What this module actually buys, stated honestly, because ADR-0033 does too.** The label is *framing*: it * persuades a well-behaved model and a determined injection can argue with it. Option B in that ADR — quarantining * the output to a file the next step must `read` — is the version with real containment, and it was deferred rather * than refused. If a chained step is ever shown to have followed injected instructions, that is the prepared * answer. * * **The nonce is the one part that is not framing.** It is minted *after* the producing child has finished, so that * child never saw it and cannot emit a matching closing delimiter to escape its own fence. A fixed delimiter would * be guessable from the format alone. * * Pure: no pi, no filesystem, no session. The only impurity is `randomBytes`, which is the point. */ /** * How much of a step's output crosses to the next step. * * A child may return up to `DEFAULT_MAX_OUTPUT_BYTES` (1 MiB); pasting that into a task would spend most of the * next child's context on its predecessor's transcript. 32 KiB is a generous summary and a poor transcript, which * is the right side of that line for a handoff. */ export declare const HANDOFF_MAX_BYTES: number; /** * What actually bounds a composed task: Linux's per-argv-element limit, `MAX_ARG_STRLEN` = 32 pages = 131,072 bytes. * * **Measured, and it is why `HANDOFF_MAX_BYTES` is 32 KiB rather than 64.** At 64 KiB a template using `{previous}` * **twice** produced a 131,502-byte argv element and the spawn failed with `E2BIG` — loudly, so rule 8 was * satisfied, but the cap had been sized against the child's 1 MiB *output* limit with no reference to the limit that * really applies. A predecessor could trip it deliberately. At 32 KiB even four placeholders fit. * * The herdr executor is unaffected — the task travels via `agent prompt`, not argv — but the bound has to hold for * the executor that is *not* the default too. */ export declare const MAX_ARG_STRLEN = 131072; /** Where a step's template asks for its predecessor's output. Familiar from the `subagent` extension it replaces. */ export declare const PLACEHOLDER = "{previous}"; /** * Wrap a prior step's output so it reads as data. * * The truncation notice goes **inside** the fence deliberately: above it, the notice would read as the * orchestrator's own instruction, and the next child would have no way to tell which lines were ours and which * were its predecessor's. Inside, it is unambiguously part of what the previous agent's output turned out to be. * * The tail is kept rather than the head, for `readPane`'s reason — a summary's conclusion is at its end. */ export declare function fenceHandoff(output: string): string; /** * Build one step's task from its template and its predecessor's output. * * **A template that omits the placeholder still receives the handoff, appended.** ADR-0033 chose that over * refusing, because a chain that breaks when an operator writes a natural instruction is a chain nobody uses — and * because dropping the output silently would make every step start from nothing while the chain *looked* like it * worked. That is the failure indistinguishable from success, which is what most of this project's risk register is * about. * * **An empty predecessor output is still fenced**, and says so. A step that produced nothing is a fact the next * step should be told, not an absence it should infer — treating `""` as "no handoff" would make a silent step * indistinguishable from being first in the chain. * * `replaceAll`, not `replace`: a template mentioning the placeholder twice would otherwise keep a literal * `{previous}`, which reads to a child as an unfilled template and is the sort of thing a model remarks on rather * than works around. */ export declare function composeStepTask(template: string, previous: string | undefined): string; /** One chain step as `runOneDelegation` takes it: the operator's spec with its task composed. */ export interface ChainStep { task: string; agent?: string; tools?: string[]; model?: string; /** Requested Pi thinking level; validated by the same schema as delegate/delegate_all. */ thinking?: string; /** ADR-0078: what of the parent's session this step receives. Capped by the step's own definition, like a tool. */ context?: unknown; correlation?: import("./correlation.ts").CorrelationMetadata; workspace?: { workspace_id: string; access: import("./workspace.ts").WorkspaceAccess; }; } /** * Build the spec for one step — the operator's step plus the composed task. * * **Extracted so the composition is reachable by a test.** It was inline in the run loop, and a reviewer deleted it * (`task: step.task`) with **all 489 tests still green**: the chain's entire reason for existing could be removed * without anything noticing, because the test that claimed to cover it only pinned the `taskFrom` ledger field. * * This does not make the *binding* untestable-to-testable by itself — see the note in * `test/delegate-chain-wiring.test.ts` about what remains uncovered and why — but it does put the composition under * a real unit test instead of under a title. */ export declare function chainStepSpec(step: ChainStep, previous: string | undefined): ChainStep; //# sourceMappingURL=chain.d.ts.map