UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

119 lines (118 loc) • 9.22 kB
/** * 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;