UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

180 lines (179 loc) • 13.2 kB
/** * 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;