@sema-agent/core
Version:
Stateless, task-oriented AI agent core
119 lines (118 loc) • 9.22 kB
TypeScript
/**
* PrepareTask's RUN-REFS phase, verbatim from the driver: the run's own edited-files
* ledger, the harness ref, the per-task skill grant table, the display-forwarding wrapper, the three run-local refs
* (review request / stop-after-turn / plan mode) with the ctx primitives that write them, the subagent-retain
* ledger and the worktree-isolation helper. Nothing here has behavior of its own: the phase MINTS SEATS —
* mutable cells and the closures bound to them — that later phases, the tool ctx and the run loop read and
* write. The interface is the dependency list the segment had implicitly.
*
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between the
* file-history phase before it and the inherited-gate fold after it (a stretch with no
* await is extracted as a sync function — an `async` wrapper would open a microtask window).
*
* Every cell that leaves here as `borrowed-mutable` carries its writer table on the Result member:
* the closures minted here, the driver's harness build, the project-context phase's skill-grant
* seeds, the skill tool, and the run loop's two writes — nothing else writes them. Read-stability of the host
* handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
*/
import type { AgentHarness, ExecutionEnv } from "../../internal/harness.js";
import type { SubagentWorktreeIsolation } from "../../agents/subagent.js";
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
import type { TaskEvent, TaskSpec } from "../types.js";
import { SkillGrantTable } from "../skill-grant.js";
import type { Prepared, RunInternals, ToolProgressLane } from "./contracts.js";
import type { PermissionModeController } from "../mode-policy.js";
import { type EditedFilesLedger } from "./edited-files-ledger.js";
import { type ToolOccurrenceTable } from "./tool-occurrence.js";
export interface PrepareRunRefsInput {
/** borrowed-readonly — the REBOUND spec. Read: `forwardSubagentEvents` (widens the forwarding filter),
* `retainSubagentSessions` (the ledger opt-in and its config), `handsReadOnly` (a read-only run mints no
* worktree helper). Never mutated. */
spec: Pick<TaskSpec, "forwardSubagentEvents" | "retainSubagentSessions" | "handsReadOnly">;
/** borrowed-readonly — the trusted spawn-side channel; only `onForwardEvent` (the deployment's display
* sink) is read, to decide whether a forwarding wrapper exists at all, and `forwardScope` (the run's
* forwarding containment, read by the door). The OBJECT is what the wrapper hands the door rather than
* the pulled-out function: the sink keeps its receiver and the run keeps one failure count. */
internals: Pick<RunInternals, "onForwardEvent" | "forwardScope"> | undefined;
/** borrowed-readonly — the run's resolved env; the worktree helper closes over it (lazy: no I/O until a
* delegation actually mints a worktree). */
executionEnv: ExecutionEnv;
/** borrowed-readonly — the SETTLED task root (`taskRootFinal`): the worktree helper's base repo root. */
taskRootFinal: string;
}
export interface PrepareRunRefsResult {
/** owned — the ledger's writer (`FileEditedHook`): handed to the hands/read-face phase as its post-write
* seat; nothing else calls it. */
noteFileEdited: EditedFilesLedger["note"];
/** owned — the ledger's LIVE read face (`Prepared.editedFilesSnapshot`, read at result assembly). */
editedFilesSnapshot: EditedFilesLedger["snapshot"];
/** borrowed-mutable — the harness seat: holds the harness once built, so a tool's execute can read the model
* the parent is CURRENTLY on (the `ToolExecuteContext.model` snapshot a delegation tool uses
* for `inherit`), and breaks the harness↔tool_search build cycle (rematerialize). Writers (R-3):
* the driver, ONCE, when the harness is built (`harnessRef.current = harness`) — before any run. Readers:
* `requestStopAfterTurn` here (mirrors the stop onto the harness's one-way flag), the tool ctx's model
* snapshot, the rebuild seam and the delegation mounts. Identity is the contract: every closure minted
* before the harness exists reads this exact cell. */
harnessRef: {
current?: AgentHarness;
};
/** borrowed-mutable — the tool-progress lane's seat (`Prepared.toolProgressRef`), the same late-bound
* shape and reason as `harnessRef`: the band is built HERE while the lane needs the run's content pusher
* and identity mint, which exist only after prepare returns. Writer (R-3): the driver, ONCE, right after
* `pushContent` exists. Readers: the band's progress seat and the harness-handler lane. Identity is the
* contract. Unbound is LEGITIMATE — a band outside a Runner has no run to tick on, and emissions no-op. */
toolProgressRef: {
current?: ToolProgressLane;
};
/** borrowed-mutable — the leg's occurrence table (`Prepared.toolOccurrences`), minted before the band that writes it. */
toolOccurrences: ToolOccurrenceTable;
/** borrowed-mutable — the per-task skill grant table (its rows live for this run). Writers: the project-context phase (the
* parent's captured rows, the person's invoked skills) and the `Skill` tool minted over it (a successful activation).
* Readers: the tool ctx's `activeSkillGrants()` capture and the gate's skill lane. */
skillGrants: SkillGrantTable;
/** owned — the FILTERED display-forwarding wrapper, or undefined when the deployment wired no sink (the
* tool ctx then carries no `forwardEvent` key). */
forwardEvent: ((e: TaskEvent) => void) | undefined;
/** borrowed-mutable — the review-request cell (`Prepared.reviewRequestRef`). Writers: `requestReview` here
* (the first request wins) and the run loop's turn-boundary consumer, which clears `pending` once the
* `plan_review` checkpoint is minted. */
reviewRequestRef: Prepared["reviewRequestRef"];
/** owned — the ctx primitive that arms `reviewRequestRef`. */
requestReview: (opts?: {
reason?: string;
}) => void;
/** borrowed-mutable — the stop-after-turn latch (`Prepared.stopRequestedRef`). Writers: `requestStopAfterTurn`
* here, and nothing else; the run loop reads it at a turn boundary. */
stopRequestedRef: Prepared["stopRequestedRef"];
/** owned — the ctx primitive: flips the latch and mirrors it onto the harness's one-way flag. */
requestStopAfterTurn: () => void;
/** owned — binds the leg's permission-mode seat once the inherited-gate phase minted it; `enterPlanMode` steers it
* (the run loop's resume ladder steers the same seat when a REJECTED `plan_review` resumes into a re-plan). */
bindPermissionModeSeat: (seat: PermissionModeController) => void;
/** owned — the ctx primitive (mounted on the ctx only under `enablePlanMode`): a steer of the bound seat into `plan`. */
enterPlanMode: () => Promise<void>;
/** ownership-ref — the parent-run subagent-retain ledger, or undefined when the spec did not opt in. Its
* lifecycle protocol belongs to the Runner (disposed in the task's terminal finally); a
* prepare that throws has nothing to release here — the ledger is empty until a delegation retains. */
subagentRetain: SubagentRetainLedger | undefined;
/** owned — the Agent tool's worktree-isolation helper (lazy; write-capable runs only), or undefined. */
worktreeIsolation: SubagentWorktreeIsolation | undefined;
}
/** The M3a phase body — prepareTask's run-refs stretch, verbatim (see the module header). */
/**
* A run that opted into `forwardSubagentEvents` may have BACKGROUND children whose frames are forwarded to the
* deployment's sink from the child's own run. Those forwards are scheduled on later microtasks than the parent's
* turn, so "the parent stream ended" and "the child's already-emitted frames reached the sink" are two different
* moments — the gap is one microtask ladder deep and moves whenever the gate's station structure changes
* (the three-layer gate added three async frames to a child's gated call, and a consumer that read the parent
* to `done` then inspected the sink lost the child's `tool_end`). The rule is not a tick count: **before the parent
* says `done`, every frame a child had already handed to the forward channel is delivered.** One macrotask turn is
* the smallest boundary that strictly orders after all pending microtasks; it is taken only on the opted-in run,
* only once, right before `done`.
*/
export declare function forwardsSubagentEvents(spec: Pick<TaskSpec, "forwardSubagentEvents">): boolean;
/** The drain itself — one macrotask turn. Callers gate it with {@link forwardsSubagentEvents} SYNCHRONOUSLY so a
* non-forwarding run's terminal push is not one microtask later than it was (an `await` of an early-returning
* async function still yields once; the black-box round measured that tick). */
export declare function drainForwardedFramesBeforeDone(): Promise<void>;
export declare function prepareRunRefs(input: PrepareRunRefsInput): PrepareRunRefsResult;