UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

263 lines (262 loc) • 12.9 kB
import { boundInputHashOf, canonicalize } from "./canonical-json.js"; import { MAX_SUPPORTED_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, resolveCheckpointStore } from "./checkpoint-store.js"; import { resolveDeclaredDurability } from "./wiring-manifest.js"; export const PARK_SELFCHECK_SCOPE_PREFIX = "sema:park-selfcheck:"; export const PARK_SELFCHECK_STEP_TIMEOUT_MS = 10_000; export const PARK_SELFCHECK_ROW_TTL_MS = 60_000; async function within(op, step) { let timer; try { return await Promise.race([ Promise.resolve() .then(op) .then((ok) => ({ ok })) .catch((threw) => ({ threw })), new Promise((resolve) => { timer = setTimeout(() => resolve({ timedOut: `${step} did not answer within ${PARK_SELFCHECK_STEP_TIMEOUT_MS}ms — a store that neither answers nor errors is the failure this probe exists to surface, not one it may wait on` }), PARK_SELFCHECK_STEP_TIMEOUT_MS); }), ]); } finally { if (timer !== undefined) clearTimeout(timer); } } function declaresDurable(store, name) { try { return resolveDeclaredDurability(store, name) === "durable"; } catch { return false; } } export function durableParkGapOf(halves) { if (!halves.checkpointWired) return undefined; if (halves.checkpointDurable && halves.sessionDurable) return undefined; if (halves.checkpointDurable && !halves.sessionDurable) { return ("this deployment wires a checkpoint store that declares durability but a session store that does not " + "(RunnerDeps.sessionStore — absent means the built-in in-memory store): a durable park needs BOTH halves, " + "so a parked approval survives the restart while the conversation it resumes into does not, and a resume " + "arriving afterwards finds the checkpoint and not the session"); } if (!halves.checkpointDurable && halves.sessionDurable) { return ("this deployment wires a session store that declares durability but a checkpoint store that does not " + "(RunnerDeps.checkpointStore.durability): a durable park needs BOTH halves, so the conversation survives " + "the restart while the parked approval that was to resume it does not"); } return undefined; } export function durableParkGapFor(deps, spec = {}) { const checkpointStore = resolveCheckpointStore(spec, deps); return durableParkGapOf({ checkpointWired: checkpointStore !== undefined, checkpointDurable: declaresDurable(checkpointStore, "checkpointStore"), sessionDurable: declaresDurable(deps.sessionStore, "sessionStore"), }); } function syntheticCheckpoint(scope) { return { token: mintCheckpointToken(), checkpointId: mintCheckpointId(), scope, sessionId: `${scope}:session`, leafId: `${scope}:leaf`, gate: { kind: "human", reason: "park wiring self-check (design/252 G-6) — no person is being asked", toolName: "SelfCheck" }, pendingAction: { kind: "tool_approval", toolCallId: `${scope}:call`, toolName: "SelfCheck", args: { probe: true, nested: { depth: 2, list: [1, 2, 3] } }, boundInputHash: boundInputHashOf({ probe: true, nested: { depth: 2, list: [1, 2, 3] } }), hasBidiControls: true, preview: { probe: true, note: "park wiring self-check display stub" }, previewWithheld: "oversize", requiresRealApproval: true, mandated: true, denialLimitFallback: { consecutive: 3, total: 3, limit: "consecutive", autoDenyAfterMs: 0 }, origin: "denial_limit_fallback", askClass: "denial_limit_fallback", classes: ["denial_limit_fallback"], batchToolCallIds: [`${scope}:call`], completedCallIds: [], }, state: { activeTools: ["SelfCheck"], nestedStats: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false } }, status: "pending", createdAt: Date.now(), version: MAX_SUPPORTED_CHECKPOINT_VERSION, suspendCount: 1, suspendedAt: Date.now(), resourceLedger: { spentMicroUsd: 0, spentTokens: 0, spentTurns: 0, sliceCount: 1 }, humanReview: { count: 1, totalWaitMs: 0, gates: [{ kind: "human", waitMs: 0, decision: "approve", toolName: "SelfCheck", toolCallId: `${scope}:selfcheck-call`, toolArg: "park self-check probe" }] }, sourceTaskId: `${scope}:task`, principal: `${scope}:principal`, durableApproval: { scope, ttlMs: PARK_SELFCHECK_ROW_TTL_MS }, deadline: Date.now() + PARK_SELFCHECK_ROW_TTL_MS, }; } function faithfulnessDelta(sent, got) { const project = (cp) => ({ token: cp.token, checkpointId: cp.checkpointId, scope: cp.scope, sessionId: cp.sessionId, leafId: cp.leafId, gate: cp.gate, pendingAction: cp.pendingAction, state: cp.state, deadline: cp.deadline, createdAt: cp.createdAt, version: cp.version, suspendCount: cp.suspendCount, suspendedAt: cp.suspendedAt, resourceLedger: cp.resourceLedger, humanReview: cp.humanReview, sourceTaskId: cp.sourceTaskId, principal: cp.principal, durableApproval: cp.durableApproval, }); const a = canonicalize(project(sent)); const b = canonicalize(project(got)); return a === b ? undefined : `the row came back different from the row that was filed (sent ${a}; read ${b})`; } export async function probeParkRoundTrip(deps, spec = {}) { const gap = durableParkGapFor(deps, spec); const gapCell = gap !== undefined ? { durableTopologyGap: gap } : {}; const store = resolveCheckpointStore(spec, deps); if (store === undefined) { return { verdict: "not_probed", findings: [], summary: "no checkpoint store is wired on this assembly — there is no park lane to probe (the manifest reports the same fact as parkLane.capable: false)", ...gapCell, }; } const scope = `${PARK_SELFCHECK_SCOPE_PREFIX}${mintCheckpointToken()}`; const cp = syntheticCheckpoint(scope); const said = (err) => { try { const m = err instanceof Error ? err.message : undefined; return typeof m === "string" ? m : `a ${typeof err} the store threw`; } catch { return "a value the store threw that could not be described"; } }; const shown = (v) => { try { return typeof v === "string" ? JSON.stringify(v) : `a ${typeof v}`; } catch { return "an undescribable value"; } }; const fenceQuietly = async () => { await within(async () => { try { await store.expire(cp.token, scope); } catch { } }, "the cleanup fence"); }; const failed = async (code, detail) => { await fenceQuietly(); return { verdict: "failed", findings: [{ code, detail }], scope, summary: `the park lane failed its round-trip self-check at "${code}": ${detail}`, ...gapCell }; }; let putStarted; const put = await within(() => { const p = Promise.resolve().then(() => store.put(cp.token, cp)); putStarted = p; return p; }, "put"); if ("timedOut" in put) { void putStarted?.catch(() => { }).then(() => store.expire(cp.token, scope)).catch(() => { }); return await failed("put_failed", put.timedOut); } if ("threw" in put) return await failed("put_failed", `the checkpoint store refused to file a synthetic park: ${said(put.threw)}`); const first = await within(() => store.get(cp.token), "get"); if ("timedOut" in first) return await failed("row_not_readable", first.timedOut); if ("threw" in first) return await failed("row_not_readable", `the checkpoint store threw reading back the row it had just accepted: ${said(first.threw)}`); const read = first.ok; if (read === null) return await failed("row_not_readable", "the checkpoint store accepted the row and then answered null for its own token — a park filed here would be unresumable"); if (typeof read !== "object") return await failed("row_not_readable", `the checkpoint store answered ${read === undefined ? "undefined" : typeof read} for its own token — neither a checkpoint nor the absence of one`); let delta; let status; try { delta = faithfulnessDelta(cp, read); status = read.status; } catch (err) { return await failed("row_not_faithful", `the row the store returned could not be read for comparison: ${said(err)}`); } if (delta !== undefined) return await failed("row_not_faithful", delta); if (status !== "pending") return await failed("row_not_pending", `the filed row came back with status ${shown(status)} — a park in any other status is one nobody can resolve`); const enumerable = store.listByScope !== undefined; if (store.listByScope !== undefined) { const listByScope = store.listByScope.bind(store); const listed = await within(() => listByScope(scope), "listByScope"); if ("timedOut" in listed) return await failed("row_not_enumerable", listed.timedOut); if ("threw" in listed) return await failed("row_not_enumerable", `listByScope threw for the probe's own scope: ${said(listed.threw)}`); let present; try { present = Array.isArray(listed.ok) && listed.ok.some((row) => row?.token === cp.token); } catch (err) { return await failed("row_not_enumerable", `the listByScope result could not be read: ${said(err)}`); } if (!present) return await failed("row_not_enumerable", `listByScope("${scope}") did not include the pending row it holds — an inbox enumerating this scope would show no approvals`); } const fence = await within(() => store.expire(cp.token, scope), "expire"); if ("timedOut" in fence) return await failed("fence_failed", fence.timedOut); if ("threw" in fence) return await failed("fence_failed", `expire threw on the probe's own pending row: ${said(fence.threw)}`); const fenced = fence.ok === true; const second = await within(() => store.get(cp.token), "get (after the fence)"); if ("timedOut" in second) return await failed("fence_not_durable", second.timedOut); if ("threw" in second) return await failed("fence_not_durable", `the store threw re-reading the fenced row: ${said(second.threw)}`); const after = second.ok; let afterStatus; if (after !== null) { if (typeof after !== "object") return await failed("fence_not_durable", `the store answered ${after === undefined ? "undefined" : typeof after} re-reading the fenced row — neither a checkpoint nor the absence of one`); try { afterStatus = after.status; } catch (err) { return await failed("fence_not_durable", `the fenced row's status could not be read: ${said(err)}`); } if (afterStatus !== "expired") { return await failed(fenced ? "fence_not_durable" : "fence_failed", fenced ? `expire reported a win and the row reads ${shown(afterStatus)} — the contract's transition is pending → expired, so the CAS did not land` : `expire lost an UNCONTENDED CAS and the row reads ${shown(afterStatus)} — the fence that keeps a checkpoint from being both reaped and resumed does not close here`); } } const steps = `filed, read back${enumerable ? ", enumerated" : ""} and fenced`; const notExercised = enumerable ? "" : " (this store implements no listByScope, so the inbox enumeration was NOT exercised)"; const casNote = fenced ? "" : " (the expire CAS reported a loss while the row closed — consistent with store housekeeping, but a backend that mis-reports CAS wins is not distinguished here)"; return { verdict: "round_trip_ok", findings: [], ...(fenced ? {} : { casLossObserved: true }), scope, summary: after === null ? `the park lane ${steps} a synthetic checkpoint${notExercised} — the row is no longer present (a deleting backend or its own housekeeping removed it), so nothing resumable remains${casNote}` : `the park lane ${steps} a synthetic checkpoint${notExercised} — residual expired row in scope "${scope}"${casNote}`, ...gapCell, }; }