@sema-agent/core
Version:
Stateless, task-oriented AI agent core
180 lines (179 loc) • 13.2 kB
TypeScript
/**
* PrepareTask's DURABLE SETTLEMENT machinery, verbatim from the driver's
* gate-machinery block: the in-flight spend and repair-bundle folds, the §4.bis correctness-state serializer, the shared
* paused-VM compensation bound to this task's failure ledger, the put-commit + put-failure saga (the pre-put cut
* checkpoint, the screening-park disclosure, the rejection read-back with its three answers), the suspend-loop cap, the
* env capability split (suspendable / park-only / neither) and the park-only handle. The module-scope helpers only this
* segment called moved with it (the compensation's bound and body, the auto-mode latch reading, the placement stamp).
* The interface is the dependency list the segment had implicitly — every seat below used to be
* captured off the driver's scope.
*
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (every await lives inside the closures it
* builds, which run at park time), so the phase adds no yield between the ask lane before it and the boundary parks
* after it. Read-stability of the host handles for the call: {@link RunInternals}
* (`@contract prepare.deps-read-stable`).
*
* The saga exists exactly when the wiring-manifest phase's `gateMachineryActive` holds (the ask lane's presence law) —
* the boundary parks and the park closure key off `saga`'s presence, so the orchestrator grows no branch.
*/
import type { AgentHarness, ExecutionEnv } from "../../internal/harness.js";
import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
import type { RemoteExecutionEnv, SnapshotId } from "../remote-env.js";
import type { CheckpointStore, SerializedCheckpointState } from "../checkpoint-store.js";
import type { RemoteEnvFailureNote, RunnerDeps } from "../types.js";
import type { CwdRef } from "../../tools/fs/fs-shared.js";
import type { ReadFileState } from "../../tools/fs/safety.js";
import type { OutputRef } from "./synthetic-tools.js";
import type { HookInvocationIdentity } from "../hooks.js";
import type { InheritedGate, Prepared, PrepareResume, RunInternals, SuspendSaga } from "./contracts.js";
import type { PermissionModeController } from "../mode-policy.js";
/**
* The ONE compensation for a paused-but-unparked VM, shared by the fence's
* post-pause checkpoints (③ in the park closure, ④ in the saga) and the saga's put-failure absent
* arm (previously inline there: same two hops, one implementation now, so the three sites cannot
* drift). Restores the workspace (bounded transient retry) then re-establishes consistency
* (`postResumeInit`), both hops under one INDEPENDENT `AbortSignal.timeout(io.boundMs)` —
* deliberately NOT the run/cut signal: the remote contract answers an already-aborted signal
* `{ok:false,"aborted"}`, so gating the restore on the very signal whose firing caused the
* compensation made it die instantly and strand the paused VM on exactly the run-abort arm that
* needs it most. The adapter signal is best-effort (a deaf adapter ignores it), so the caller's
* await is ALSO raced against the same bound — bounded decision, three outcomes:
* · settled ok ⇒ the VM is running again ({ok:true});
* · settled not-ok / threw ⇒ recorded + disclosed, {ok:false} — the caller takes its fatal
* fail-closed arm (abort the run; never continue on a paused VM);
* · bound fires with the adapter still in flight ⇒ {ok:false} NOW, and the in-flight call
* continues DETACHED + swallow-guarded with the late-settlement split:
* – late SUCCESS: a running VM now exists with no run and no committed row to own it — the
* detached continuation compensates the compensation with `destroy()` (the adapter's own
* best-effort contract, swallow-guarded, disclosed);
* – late FAILURE: disclosed; the VM is most likely still paused — provider-side TTL/GC
* territory, and the disclosure is the end of this engine's obligation (a deaf adapter's
* stranded VM is that adapter's own defect surface).
* Decide-then-disclose throughout: `io.disclose`/`io.noteFailure` must be swallow-guarded by the
* caller's binding, and nothing they do can change the returned verdict. Never throws. Exported for
* direct unit pinning (the bounded/deaf/late arms need a small bound; production binds the
* `PARK_COMPENSATION_TIMEOUT_MS` constant at the one call-site closure).
*/
export declare function compensateUnparkedPause(remoteEnv: RemoteExecutionEnv, snapshotId: SnapshotId, io: {
boundMs: number;
noteFailure: (note: RemoteEnvFailureNote) => void;
disclose: (err: unknown) => void;
}): Promise<{
ok: true;
} | {
ok: false;
reason: string;
}>;
export interface PrepareSuspendSagaInput {
/** borrowed-readonly — the wiring-manifest phase's ONE activation predicate; false ⇒ this phase builds nothing and
* returns an absent saga. */
gateMachineryActive: boolean;
/** borrowed-mutable — prepare's abort seat. Writer here: the fail-closed arms (`abort()` when a paused VM cannot be
* restored, when the put outcome is unknowable, when the loop cap trips); `.signal` is read by the mint sites. */
abortController: {
readonly signal: AbortSignal;
abort(): void;
};
/** borrowed-mutable — the built harness. Writer here: `abort()` on the same fail-closed arms (release the gate,
* then stop the loop — the two-step every stop in this file takes). */
harness: Pick<AgentHarness, "abort">;
/** borrowed-readonly — the leg's checkpoint store, or undefined (then the saga answers `absent` and no park commits). */
checkpointStore: CheckpointStore | undefined;
/** borrowed-readonly — the deployment's error face, as a Pick over the SAME `deps` object (receiver preserved for
* `deps.onError?.()`): the put-failure disclosures, the compensation's disclose binding, the cap line. */
deps: Pick<RunnerDeps, "onError">;
/** borrowed-readonly — the acquired session id (every disclosure's tag, the running-tasks owner triple, the row's
* session). */
sessionId: string;
/** borrowed-readonly — the per-task identity (the running-tasks owner triple). */
hostTaskId: string;
/** borrowed-readonly — the registry scope (the running-tasks owner triple). */
taskScope: string;
/** borrowed-readonly — the loop's live spend seat (`Prepared.liveSpendRef`); `.get` is read at mint time. Not written
* here (runtask sets `.get`). */
liveSpendRef: Prepared["liveSpendRef"];
/** borrowed-readonly — the resume leg, or undefined: `seed.nestedStats` (the spend delta's base), `seed.repairBundle`
* (the pass-through bundle). */
resume: Pick<PrepareResume, "seed"> | undefined;
/** borrowed-readonly — the cumulative nested-usage accumulator (`Prepared.nestedStats`); read at mint time, copied
* onto the row. */
nestedStats: Prepared["nestedStats"];
/** borrowed-readonly — the trusted spawn-side channel: `repairBundle` (the live bundle) and `delegationProvenance`
* (the aggregate copied onto the row). */
internals: Pick<RunInternals, "repairBundle" | "delegationProvenance"> | undefined;
/** borrowed-readonly — the ToolSearch activation set (`Prepared.activeTools`); copied onto the row at mint time. */
activeTools: Prepared["activeTools"];
/** borrowed-readonly — the structured-output seat; `value`/`set` are copied onto the row at mint time. */
outputRef: OutputRef;
/** borrowed-readonly — the hand's read-file state for the checkpoint, or undefined (hands-less); its entries are
* copied onto the row at mint time. */
readFileStateForCheckpoint: ReadFileState | undefined;
/** borrowed-readonly — the inherited-gate phase's read-face section builder (the row's `readFace`). */
faceCheckpointSection: () => SerializedCheckpointState["readFace"];
/** borrowed-readonly — the inherited-gate phase's WRITE-face section builder (the row's `writeFace`). */
writeFaceCheckpointSection: () => SerializedCheckpointState["writeFace"];
/** borrowed-readonly — the inherited-gate phase's SKILL-GRANT section builder (the row's `skillGrants`). */
skillGrantCheckpointSection: () => SerializedCheckpointState["skillGrants"];
/** borrowed-readonly — the leg's reminder provenance mark (travels with the row). */
reminderMark: string;
/** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at mint time. Not written here. */
handsCwdRef: CwdRef | undefined;
/** borrowed-readonly — the active EnterWorktree session seat, or undefined; `current` is read at mint time. */
worktreeSessionRef: Prepared["worktreeSessionRef"];
/** borrowed-readonly — the resumed leg's not-yet-delivered continuation content; its orphan obituaries fold into this
* snapshot at mint time (a re-park before delivery must not lose them). */
undeliveredContinuationRef: Prepared["undeliveredContinuationRef"];
/** borrowed-readonly — the LIVE inherited parent constraints, or undefined (the row's `requiresParentConstraint`,
* count, frozen chain and the screening-park disclosure's predicate). */
inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined;
/** borrowed-readonly — the resume seed's inherited gate, or undefined (the carried-forward half of the same fields). */
seedInheritedGate: PrepareResume["seed"]["inheritedGate"];
/** borrowed-readonly — the ancestor rules AS THIS LEG NOW ENFORCES THEM, read at MINT time (a getter, not
* a value: the prepare-time list is not what a resumed leg must inherit — an ancestor rule written
* mid-run and enforced here has to survive suspend exactly as it survives a spawn, and the same
* projection serves both). Cloned onto the row. */
ancestorRulesNow: () => InheritedGate["ancestorRules"];
/** borrowed-readonly — the inherited shell floor, persisted verbatim. */
inheritedShellGate: InheritedGate["shellGate"];
/** borrowed-readonly — the leg's live permission-mode seat; the DECLARED mode (request clamped under the chain's ceiling,
* before the deployment's fold) is what the row records — the resume leg re-judges the fold for its own deployment. */
permissionMode: PermissionModeController;
/** borrowed-readonly — the inherited admitted org scopes, or undefined (copied onto the row). */
inheritedAdmittedOrgScopes: readonly string[] | undefined;
/** borrowed-readonly — this leg's OWN org verdict seat; `current` is read at mint time. Not written here. */
ownOrgVerdictRef: {
current: OwnOrgAdmissionVerdict | undefined;
};
/** borrowed-readonly — the monotonic org-governed provenance bit. */
orgGovernedProvenance: boolean;
/** borrowed-readonly — the listing frames' announced name-sets mirror (`Prepared.announcedListingsRef`); serialized
* onto the row at mint time. */
announcedListingsRef: Prepared["announcedListingsRef"];
/** borrowed-readonly — the git frame's lane ref (`Prepared.gitStatusRef`); `announced` is copied at mint time. */
gitStatusRef: Prepared["gitStatusRef"];
/** borrowed-readonly — the leg's identity envelope; only `isDelegatedChild` is read (the row's presence-coded bit). */
hookIdentity: Pick<HookInvocationIdentity, "isDelegatedChild">;
/** borrowed-readonly — the frozen prepare-time placement resolution (the row's placement stamp). */
placementRootResolved: string;
/** borrowed-readonly — the external-content-target fold (the row's monotonic bit). */
externalContentTargetActive: boolean;
/** borrowed-mutable — the run-scoped remote-lifecycle failure log (`Prepared.remoteEnvFailures`). Writer here: the
* compensation's note binding (`push`). */
remoteEnvFailures: Prepared["remoteEnvFailures"];
/** borrowed-readonly — the memory-engine session, or undefined; `harvest("checkpoint")` is called at the commit door. */
memoryEngineSession: Prepared["memoryEngineSession"];
/** borrowed-mutable — the loop-cap latch (`Prepared.suspendLoopRef`). Writer here: `hit = true` when the cap trips. */
suspendLoopRef: Prepared["suspendLoopRef"];
/** borrowed-readonly — the per-task owned env when a factory minted one, or undefined (the capability split). Never
* destroyed here. */
ownedEnv: ExecutionEnv | undefined;
/** borrowed-readonly — the restore-incomplete adapter's missing members, or undefined (excluded from both targets). */
incompleteSuspendAdapter: readonly ("resumeVM" | "postResumeInit")[] | undefined;
}
export interface PrepareSuspendSagaResult {
/** owned — the saga, or undefined exactly when `gateMachineryActive` is false (the boundary parks and the park closure
* key off this seat). */
saga: SuspendSaga | undefined;
}
/** The M19 suspend-saga phase body — prepareTask's durable settlement stretch, verbatim (see the module header). */
export declare function prepareSuspendSaga(input: PrepareSuspendSagaInput): PrepareSuspendSagaResult;