UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

182 lines (181 loc) • 11.9 kB
/** * PrepareTask's CONTEXT-LANE phase, verbatim from the driver: the edit/guard budgets, the * microCompact machine state with the trigger-window clamp's one loud outlet, the model's chars-per-token coordinate, * and the `context` handler registered on the harness — heal → ledger replay → aggregate tool-result and media caps → * frontier clear → the guard chain (arms A/B/C) → the git-frame guard → the lossy-projection flag → the projection * seat — and the harness's sink for the provider boundary's tool-turn repairs (`engine/llm/tool-turns.ts`, the one * law every request builder reads): the run's `context.tool_turn_repaired` seat, and the lossy flag's second half — * a request the law left recorded content out of is lossy too. The four module-level helpers only this lane called * moved with it (the machine-state builder, the frontier-clear recorder, the observation-buffer flush, the guard chain * with its two types). The interface is the dependency list the segment had implicitly. * * SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the handler it registers is async and * runs per provider request), so the phase adds no yield between the gate stations before it and the turn wiring after * it. Read-stability of the host handles: {@link RunInternals} (`@contract prepare.deps-read-stable`). * * REGISTRATION SIDE EFFECT, OWNED: this phase registers the ONE `context` handler on the harness and sets its * `onToolTurnRepaired` sink; the harness owns both for the run's life (never unregistered — the harness is per * prepare). No other station registers on that hook or sets that sink. */ import { type AgentHarness, type AgentMessage } from "../../internal/harness.js"; import { type Model } from "../../internal/llm.js"; import { type ClearedProjectionLedger, type MicroCompactMachine, type OccurrenceFingerprint, type OccurrenceIndex } from "../context-edit.js"; import type { ContextWindowOverlay } from "../context-settings.js"; import { type ToolResultStore } from "../tool-result-store.js"; import { type RunnerDeps } from "../types.js"; import type { Prepared, PreparedMicroCompact } from "./contracts.js"; import { type GitStatusLaneRef } from "./git-status-frame.js"; import type { ReadFileState } from "../../tools/fs/safety.js"; import { type ToolRosterDeltaSeat } from "../tool-roster.js"; export interface PrepareContextLaneInput { /** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */ model: Model; /** borrowed-readonly — the deployment seats, whole: `onNotice` (the clamp outlet, the offload-loss announcement, the * tool-turn repair seat), `tracer` (the cap / clear / trim frames), `mediaByteCapBytes` and `onMediaStripped` (the media * cap), `onError` (the git-frame degrade) — the guard chain and the machine-state builder read through the whole record. */ deps: RunnerDeps; /** borrowed-readonly — the acquired session id (the offload persist key; every disclosure's attribution). */ sessionId: string; /** borrowed-readonly — the run's invocation id (the clamp notice's per-run claim). */ runId: string; /** borrowed-readonly — the per-task identity (the trace frames' taskId). */ hostTaskId: string; /** borrowed-readonly — the run's offload store, or undefined (the caps' and the machine's persist seat). */ offloadStore: ToolResultStore | undefined; /** borrowed-readonly — the leg's live roster: a recorded tool name is judged as the wire name it resolves to. */ toolRosterDeltas: ToolRosterDeltaSeat; /** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */ microCompactKnob: { machine: MicroCompactMachine; clearOnRejection: boolean; }; /** borrowed-mutable — the built harness. Writers here: `on("context", …)` — the ONE registration of the request-build * handler (the harness owns the listener; nothing unregisters it) — and `onToolTurnRepaired`, the provider boundary's * repair sink. */ harness: AgentHarness; /** borrowed-mutable — the turn's halt fact (`Prepared.batchHaltRef`). Writer here: the handler CLEARS it at every new * provider request (a new request means the halted boundary was legitimately passed). The judgment site sets it. */ batchHaltRef: Prepared["batchHaltRef"]; /** borrowed-mutable — the git-status lane ref. Writers here: the git-frame guard's degrade latch (`overBudgetShrunk`) inside * the handler; the prompt-inputs phase seeds it. */ gitStatusRef: GitStatusLaneRef; /** borrowed-mutable — the posture directive's newest engine carrier (the first-turn mint and each post-compaction restatement write it); the clip guard reads it. */ postureFrameRef: { protectedText?: string; unwritten?: string; }; /** borrowed-mutable — the per-request lossy-projection flag. Writers here: the handler re-derives it at every request * build, and the repair sink raises it when the provider boundary's law left recorded content out of the same request * (the boundary runs after the handler, before the recording shim); the shim reads it immediately after. */ requestLossyRef: { current: boolean; }; /** borrowed-mutable — the hands' read-state map, or undefined without hands. Writer here: a landed stale-result * clear RETIRES the entries whose transmission it just replaced with a marker — the same retirement the compaction * lanes run through the SAME function (`applyContextShrinkToReadFileState`), because a Read body taken off the wire * is off the wire whichever mechanism took it. Without it a re-read of an unchanged file answers "content omitted" * pointing at a cleared marker. */ readFileState?: ReadFileState; /** borrowed-mutable — the trim-pressure flag (`Prepared.trimPressureRef`). Writer here: the guard chain's trim arm sets * `droppedMessages`; the run loop's boundary reads and clears it. */ trimPressureRef: { droppedMessages: boolean; }; /** borrowed-readonly — a CALL-TIME getter for the deployment's per-model window overlay in force for this * leg ({@link RunnerDeps.contextSettings} keyed by the leg's catalog word). A getter and not a value: this * is the one deployment seat an in-flight leg re-reads, and the request-layer frontiers below are asked * per request. Absent (a standalone prepare exercise, no Runner behind it) ⇒ no overlay, ever. */ contextOverlay?: () => ContextWindowOverlay | undefined; } export interface PrepareContextLaneResult { /** owned — the per-run microCompact machine state (`Prepared.microCompact`): machine, MC-R flag, ledger, the projection * and in-turn-compaction seats the handler and the runtask arms fill. */ microCompact: PreparedMicroCompact; /** owned — the PREPARE-time model's structural coefficient (`charsPerToken ?? DEFAULT`), the coordinate the * prompt-overhead term downstream is computed in. The per-REQUEST defenses read the CURRENT model's * instead (`requestGeometry`), beside the window they compare against — one snapshot, one coordinate. */ charsPerToken: number; } /** The M21 phase body — prepareTask's context-lane stretch, verbatim (see the module header). */ export declare function prepareContextLane(input: PrepareContextLaneInput): PrepareContextLaneResult; /** Record a cc-machine pass's clears into the ledger + emit the G11 * frame (one frame per firing pass; the replay of these markers on later requests emits nothing — * cardinality is per CLEAR, not per request). `trigger` names the arm: `"frontier"` = the * proactive request-build pass, `"blocking"` = the slice-3 arm-A pre-guard pass (the machine's * one shot when the frontier hook is off). Marker bytes are read back off the edited array * (the machine composed them, ref/media notes included) so the ledger replays EXACTLY what went * on the wire; an occurrence the index cannot key (pathological same-id-same-timestamp ambiguity) * is simply not recorded — its clear stays request-local, the safe (full-text-returns) direction. * `at.fp` is the occurrence's fingerprint in the PRE-CAP replayed view (the same bytes the next * session rebuild presents) — the replay-side mis-paint guard's comparand. */ /** * The ONE landing of a set of stale-result clears: the ledger entry that makes the marker survive every * later rebuild, and the retirement of the read-state entries those transmissions carried. The pair is * inseparable — a ledger entry WITHOUT the retirement leaves a dedup stub pointing at a marker — which * is why it is a function rather than two statements repeated at each of the two clear sites (the * frontier/blocking pass and the provider-rejection recovery arm, which used to write the ledger * independently, so only one of them retired anything). Posture: a ledger entry means the marker is * repainted on every later request build, so the occurrence is cleared for good even when the * projection that produced it is discarded — which is why the retirement belongs here and not in the * per-request observation buffer. */ export declare function landStaleResultClears(args: { ledger: ClearedProjectionLedger; cleared: ReadonlyArray<{ key: string; marker: string; groupCount: number; fp: OccurrenceFingerprint; toolCallId?: string; }>; readFileState?: ReadFileState; }): void; /** See {@link runGuardChain}. */ export interface GuardChainArgs { /** The post-frontier view the chain starts from. */ edited: AgentMessage[]; /** The pre-cap counterpart view (arm A's clearSource — durable composition seat). */ replayed: AgentMessage[]; index: OccurrenceIndex; microCompact: PreparedMicroCompact; guardAt: number; charsPerToken: number; offloadStore: import("../tool-result-store.js").ToolResultStore | undefined; sessionId: string; taskId: string; toolNameOf?: (recorded: string) => string; deps: RunnerDeps; trimPressureRef: { droppedMessages: boolean; }; recheck: boolean | undefined; signal: AbortSignal | undefined; /** The r7 per-request observation buffer — the chain DROPS it on its early returns (a discarded * provisional projection must not narrate itself). */ pendingProjectionObservations: Array<() => void>; /** The hands' read-state map, or undefined without hands — arm A's clear retires the entries its * cleared transmissions carried (see `PrepareContextLaneInput.readFileState`). */ readFileState?: ReadFileState; } export type GuardChainOutcome = { earlyReturn: { messages: AgentMessage[]; adoptSessionRebuild?: boolean; }; } | { working: AgentMessage[]; trimmed: AgentMessage[]; trimDroppedMessages: boolean; }; /** * The GUARD CHAIN over one request build (D-7 case-ii transfer table), * the chain reads the anchored * coordinate re-estimated on the EDITED array (pre-anchor clears don't lower it — deliberately; * trimToBudget doc). The opt-out machine = the pre-374 backstop order byte-identical * (trim as the ordinary second line); otherwise arm A (blocking machine run, only when the * frontier pass is off) → arm B (in-turn forced compaction behind the adopt seam) → arm C (trim, * the disaster-only last resort and this chain's sole `context.trim` emitter). Returns either the * post-chain view for the pipeline tail, or an EARLY hook result the context handler must return * verbatim (arm-B adoption / the r8 dead-turn stand-down). */ export declare function runGuardChain(args: GuardChainArgs): Promise<GuardChainOutcome>;