@sema-agent/core
Version:
Stateless, task-oriented AI agent core
182 lines (181 loc) • 11.9 kB
TypeScript
/**
* 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>;