@sema-agent/core
Version:
Stateless, task-oriented AI agent core
132 lines (131 loc) • 7.58 kB
TypeScript
/**
* 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; these pauses produce
* no `tool_end` and therefore no settlement); `-` = 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 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 {};