@sema-agent/core
Version:
Stateless, task-oriented AI agent core
111 lines (110 loc) • 8.01 kB
TypeScript
/**
* The run body's LEG (R13), verbatim from `Runner.runLocked`: the one `harness.prompt` of the leg and
* everything that decides whether it is issued. Two legs behind one try / catch / finally: the RESUME leg (the walltime
* and governance-window entrance claims, the gated decision's application, the exhaustion arms, the continuation prompt's
* assembly — orphaned background tasks, the git frame, the parked steer queue and the wake message each under its own trust
* framing and redelivery screen — and the prompt itself) and the OBJECTIVE leg (the UserPromptSubmit screen, the git
* frame's primary delivery, the first-frame listings, the pre-call budget / token / entry-governance gates, the image
* conversion, the objective's human-input frame and the prompt itself). The resumed decision's application
* (`applyResumeDecision`) and the gated call's resolver (`resolvePendingCall` — the post-CAS belts, the execution record)
* are the lane's own module-private functions since S7, verbatim from the Runner's private methods: the resume
* leg is their one caller, and a sibling file would be a lane-to-lane reach (the S6 legs' reading). The tail captures the abort facts; the catch
* re-throws the resume-equivalent env failures after a bounded teardown and adopts everything else as the leg's throw;
* the finally releases the timer, the diagnostics, the signal, the content-ask bindings, flips the notification lane
* and releases the four subscriptions in their order.
*
* Async (twenty first-level awaits, the same awaits at the same positions). It ends by entering the terminal adoption
* (`next`) in its LAST continuation — the adoption's first read sits on the tick the finally ran on, as it did in the
* one-function body — and returns what it returns. The two legs stay ONE function on purpose: splitting them into two
* async functions (the design's "internal split") costs a microtask on the leg → tail edge, and the tail's first reads
* there — `abortController.signal.aborted`, `loopLatch.userInterrupted` — are host-mutable attribution facts a late read
* would mis-sign (the adversarial r2 HIGH-1 finding recorded on them); the forwarded-frame depth ratchet measured the
* extra hop (58 → 59) when the split was tried.
*/
import type { AssistantMessage } from "../../internal/llm.js";
import { type ActorAssertion } from "../checkpoint-store.js";
import type { PushQueue } from "../push-queue.js";
import { type TaskEvent, type TaskSpec } from "../types.js";
import { type WriteFamily } from "./turn-attachments.js";
import type { NotificationLaneBindings, Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js";
export interface RunLegInput<R> {
/** borrowed-readonly — the task spec: the objective, the images, the hooks slot, the principal, the task id. */
spec: TaskSpec;
/** borrowed-mutable — the run's event queue: the human-input frames, the git echo frames, the listing echo frames. */
queue: PushQueue<TaskEvent>;
/** borrowed-mutable — the leg's prepared seat: the harness is prompted, the blocked / git / listing refs written, the abort controller aborted and the signal released in the finally. */
prepared: Prepared;
/** borrowed-readonly — the resume plan when this leg resumes a checkpoint: chooses the resume leg; its delivery bits are written. */
resume: ResumeRun | undefined;
/** borrowed-mutable — the run's mutable state: the limit flags and the platform terminal are written by the entrance gates. */
rs: RunState;
/** borrowed-readonly — the trusted spawn-side fact the rejection boundary reads (a delegated child's bare refusal halts no batch). */
isDelegatedChild: boolean;
/** borrowed-readonly — the API-boundary attribution snapshot of the objective's actor (the objective leg's speaker envelope). */
entryActor: ActorAssertion | undefined;
/** borrowed-readonly — the effective walltime window (undefined = unbounded): `<= 0` refuses the first turn. */
effectiveTimeoutMs: number | undefined;
/** borrowed-mutable — the hard walltime timer: claimed (`fired`) and cleared by the exhausted arms and the finally. */
timeout: {
fired: boolean;
clear: () => void;
};
/** borrowed-readonly — the content push (the resumed batch's frames ride it with the leg's identity). */
pushContent: (e: TaskEvent) => void;
/** borrowed-readonly — the run's event-identity mint. */
ident: () => {
eventId: string;
parentToolCallId?: string;
sourceTaskId?: string;
};
/** borrowed-readonly — the `message_committed` mint (the resumed batch's re-applied tool results). */
emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
/** borrowed-readonly — the write-family resolver (the resume-executed gated write's attachment anchor). */
writeFamilyOf: (name: string) => WriteFamily | undefined;
/** borrowed-mutable — the observed tool-start ids: the resume-executed gated call registers itself here. */
startedToolCallIds: Set<string>;
/** borrowed-readonly — the harness-handlers lane's workspace-move announcer (the approved `cd`'s frame). */
announceWorkspaceMove: () => void;
/** borrowed-readonly — the brain-sinks composition the leg's prompt runs under. */
withBrainSinks: <T>(fn: () => Promise<T>) => Promise<T>;
/** borrowed-readonly — the git lane's parked-mirror flush, settled after the prompt (single-turn legs reach no boundary). */
flushGitMirror: () => Promise<void>;
/** borrowed-mutable — the loop-liveness latch: `ended` flips when the prompt settles (or a no-prompt / throw arm is taken); `userInterrupted` is snapshotted. */
loopLatch: {
ended: boolean;
readonly userInterrupted: boolean;
};
/** borrowed-mutable — the notification lane's bindings seat: `live` flips false in the finally (every later notification parks). */
notificationLane: NotificationLaneBindings;
/** borrowed-readonly — the notification lane's subscription release, called in the finally right after the flip. */
unsubscribeTaskNotifications: () => void;
/** borrowed-readonly — the git lane's owed-frame retry release, called in the finally first of the three. */
unsubGitRetry: () => void;
/** borrowed-readonly — the git lane's boundary-handler release, called second. */
unsubBoundary: () => void;
/** borrowed-readonly — the git lane's harness-event release, called third. */
unsub: () => void;
/** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`; the resumed
* decision's resolver files its execution record's word through `onNotice` too); the one home of why it is a seat and not a
* captured object is {@link RunnerDepsSeat}. */
runner: RunnerDepsSeat;
/** borrowed-readonly — the terminal adoption, entered in this lane's last continuation with the leg's facts. */
next: (leg: RunLegResult) => Promise<R>;
}
/** The leg's facts — what the terminal adoption and the settle read. */
export interface RunLegResult {
/** The final assistant message; undefined on every no-prompt arm (blocked, refused, exhausted). */
final: AssistantMessage | undefined;
/** What the leg threw (undefined on a clean end); the terminal adoption may lift a typed cause over it. */
threw: unknown;
/** The live-abort flag, captured before the finally's unconditional release-abort. */
abortedLive: boolean;
/** The snapshot of `loopLatch.userInterrupted`, taken with `abortedLive`. */
userInterruptedLive: boolean;
/** The answered-but-never-collected deliveries the finally settled. */
strandedHumanAnswers: ReadonlyArray<{
deliveryId: string;
toolCallId: string;
}>;
}
export declare function runLeg<R>(input: RunLegInput<R>): Promise<R>;