UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

146 lines (145 loc) • 8.47 kB
/** * The pause registry — ONE table for the three faces of a durable pause (why it stopped / what it left * hanging / what brings it back), the three word types and the resume match DERIVED from it, and the * registry-bound `gate` × `pendingAction` pair with its checked constructor. A vocabulary module on the * floor: it types against `checkpoint-store.ts` (type-only, no runtime edge) and `checkpoint-store.ts` * reaches down for the pair type. */ import type { AssertAllKeysHandled } from "./ask-origin.js"; import type { Checkpoint, CheckpointGate, CheckpointRow, PendingAction, ResumeOutcome } from "./checkpoint-store.js"; /** * THE registry of pauses — one row per way a run can stop and wait, and the ONE place the three faces * of that pause are declared together: WHY it stopped (`CheckpointGate.kind`, the row key), WHAT it left * hanging (`pending` — the {@link PendingAction} kind the row carries), and WHAT BRINGS IT BACK (`resume` * — the {@link ResumeOutcome} gate word a caller must answer with). The three vocabularies used to be * three independent `as const` lists whose correspondence lived in a six-arm disjunction in the resume * entry and two FACET comments; adding a kind of pause meant three declarations, one disjunction arm and * two comments, and missing any of them was silent. Now the three word types, the resume match and the * facet are DERIVED from this table: adding a pause = adding a row, and the fences below red the build * until every declaration agrees with it. * * - `facet` — the deadline semantic a deployment's reaper applies (see {@link Checkpoint.deadline}): * `A` = approval gates, whose `deadline` is an SLA resolve-deny; `B` = unattended-TTL gates, whose * `deadline` is an abandonment TTL (`expire`/`reap` only — never a resolve-deny, so a sweep's word is not a * decider on these rows; these pauses produce no `tool_end`, and the one that records its decider — the plan * pause — files the settlement on the resolved row alone); `-` = the non-gate park (`task_done`). * - `taskStatus` — the `TaskResult.status` word a run assembles when it stops at this gate (approvals and * resource slices read `suspended`; the two review pauses read `needs_review`). * - `terminal` — `task_done` is the 1C handle / deployment-park convention, not a run pause core mints. */ export declare const PAUSE_REGISTRY: { readonly human: { readonly pending: "tool_approval"; readonly resume: "policy_ask"; readonly facet: "A"; readonly taskStatus: "suspended"; readonly terminal: false; }; readonly irreversible_ask: { readonly pending: "tool_approval"; readonly resume: "policy_ask"; readonly facet: "A"; readonly taskStatus: "suspended"; readonly terminal: false; }; readonly resource_limit: { readonly pending: "resource_limit"; readonly resume: "resource_limit"; readonly facet: "B"; readonly taskStatus: "suspended"; readonly terminal: false; }; readonly needs_review: { readonly pending: "review"; readonly resume: "dry_run_review"; readonly facet: "B"; readonly taskStatus: "needs_review"; readonly terminal: false; }; readonly plan_review: { readonly pending: "plan_review"; readonly resume: "plan_review"; readonly facet: "B"; readonly taskStatus: "needs_review"; readonly terminal: false; }; readonly task_done: { readonly pending: "task_done"; readonly resume: "task_done"; readonly facet: "-"; readonly taskStatus: "suspended"; readonly terminal: true; }; }; /** The pause kinds — the row keys of {@link PAUSE_REGISTRY}; `CheckpointGate.kind` is fenced to equal it. */ export type GateKind = keyof typeof PAUSE_REGISTRY; /** The pending-action kinds — the `pending` column; `PendingAction.kind` is fenced to equal it. */ export type PendingKind = (typeof PAUSE_REGISTRY)[GateKind]["pending"]; /** The resume gate words — the `resume` column plus `wake` (the un-park verb, which is not a gate decision * and so has no registry row); `ResumeOutcome.gate` is fenced to equal it. */ export type ResumeGate = (typeof PAUSE_REGISTRY)[GateKind]["resume"] | "wake"; /** Runtime twin of {@link GateKind}. */ export declare function isGateKind(v: unknown): v is GateKind; /** The deadline semantic of a parked row — the registry's `facet` column, read with the gate's card: a question parked * under an approval gate kind is not an approval, so its deadline voids it (`B`) instead of denying it (`A`). No gate * kind of its own (the once-only winner, the reaper deadline and the batch stay one family). Only the literal * `"question"` card marks a question. */ export declare function facetOf(gate: { readonly kind: GateKind; readonly approvalCard?: unknown; }): "A" | "B" | "-"; /** Is this parked row a question to the user: an approval-kind gate whose card is the reserved `"question"`. */ export declare function isQuestionGate(gate: { readonly kind: GateKind; readonly approvalCard?: unknown; }): boolean; /** The one resume-match predicate: does this outcome gate word answer a checkpoint of this kind? `wake` * answers any (it passed its own, stricter, non-gate validation upstream); every other word must be the * row's `resume` column. Replaces a six-arm hand-written disjunction. */ export declare function resumeGateMatches(gateKind: GateKind, outcomeGate: ResumeGate): boolean; /** * The one CONSTRUCTOR of a registry-bound pause pair from a gate and a pending action that arrive * separately typed (a mint site that chooses its gate in a ternary, a store deserializing a row, a test * fixture taking overrides). The generic binds the pending action's kind to the gate's registry row at * the call site; the runtime check is the belt for the wide call (`CheckpointGate` × `PendingAction`) and * for a deserialized row, where the type says nothing about the bytes. A pair the registry does not pair * is refused loudly here rather than persisted and discovered at resume. */ export declare function pauseOf<K extends GateKind>(gate: Extract<CheckpointGate, { kind: K; }>, pendingAction: Extract<PendingAction, { kind: (typeof PAUSE_REGISTRY)[K]["pending"]; }>): CheckpointPause<K>; /** The loose input shape of {@link checkpointFrom}: a row's fields with its gate and pending action typed * independently (as a deserializer or a fixture holds them). */ export type CheckpointFields = CheckpointRow & { gate: CheckpointGate; pendingAction: PendingAction; }; /** Build a {@link Checkpoint} from independently typed fields — {@link pauseOf} binds (and checks) the pair. */ export declare function checkpointFrom(fields: CheckpointFields): Checkpoint; /** Compile-time fences (each `never` while the declaration and the registry agree, in BOTH directions). */ export type GateDeclarationCoversEveryRegistryRow = AssertAllKeysHandled<Exclude<GateKind, CheckpointGate["kind"]>>; export type RegistryCoversEveryGateDeclaration = AssertAllKeysHandled<Exclude<CheckpointGate["kind"], GateKind>>; export type PendingDeclarationCoversEveryRegistryColumn = AssertAllKeysHandled<Exclude<PendingKind, PendingAction["kind"]>>; export type RegistryCoversEveryPendingDeclaration = AssertAllKeysHandled<Exclude<PendingAction["kind"], PendingKind>>; export type ResumeDeclarationCoversEveryRegistryColumn = AssertAllKeysHandled<Exclude<ResumeGate, ResumeOutcome["gate"]>>; export type RegistryCoversEveryResumeDeclaration = AssertAllKeysHandled<Exclude<ResumeOutcome["gate"], ResumeGate>>; /** * One row's `gate` × `pendingAction` pair, BOUND by {@link PAUSE_REGISTRY}: a `needs_review` gate carries a * `review` pending action and nothing else, an approval gate a `tool_approval`, and so on. Distributed over * the registry rows so that a row literal pairing a gate with another kind's pending action does not * type-check — the invariant that used to be a sentence in a comment is now the shape of the type. */ type PairOf<K extends GateKind> = { gate: Extract<CheckpointGate, { kind: K; }>; pendingAction: Extract<PendingAction, { kind: (typeof PAUSE_REGISTRY)[K]["pending"]; }>; }; export type CheckpointPause<K extends GateKind = GateKind> = { [P in K]: PairOf<P>; }[K]; export {};