UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

337 lines (336 loc) • 13.9 kB
import { createHash } from "node:crypto"; import { canonicalize, UNCANONICALIZABLE } from "./canonical-json.js"; import { CHECKPOINT_FEATURES, checkpointVersionOf, encodeAtFidelity } from "./checkpoint-store.js"; export const REQUIREMENT_WORDS = Object.freeze([ "resource", "binding", "token", "orgAdmission", "f012", "realApproval", "face", "requirements_digest", "org_admission_inherited", "org_write_grant", "org_governed", "delegation_provenance", "skill_grants", "skill_card_binding", "hands_coordinates", "hands_home", "path_rule_anchors", "path_rule_installations", "permission_mode", "plan_mode_restore", "persisted_rule_mandate", "deployment_authored", "call_names", "write_face", "denial_limit_fallback", "leg_scope", "tool_ceiling", "tool_lists_judged", "own_tool_lists", ]); export function isRequirementWord(word) { return typeof word === "string" && REQUIREMENT_WORDS.includes(word); } const DECISION_READ = { digest: "decision-read" }; const STATE_MACHINE = { mutable: "store state-machine member; integrity = CAS contract + kit, see #1580" }; const FRAME = { ignorable: "the format stamp itself" }; const SHOWN = { ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts" }; const RESOURCE_LEDGER_REQUIREMENTS = { totalBudgetMicroUsd: DECISION_READ, totalTokens: { word: "token" }, spentMicroUsd: DECISION_READ, spentTokens: DECISION_READ, spentTurns: SHOWN, spentWalltimeMs: SHOWN, sliceCount: DECISION_READ, }; export const INHERITED_GATE_REQUIREMENTS = { ancestorRules: DECISION_READ, shellGate: DECISION_READ, permissionMode: { word: "permission_mode" }, prePlanMode: { word: "plan_mode_restore" }, admittedOrgScopes: { word: "org_admission_inherited" }, ownAdmittedOrgScopes: { word: "orgAdmission" }, ownAdmittedOrgWriteScope: { word: "org_write_grant" }, orgAdmissionGoverned: { word: "org_governed" }, pathRuleAnchors: { word: "path_rule_anchors" }, pathRuleInstallations: { word: "path_rule_installations" }, requiresParentConstraint: DECISION_READ, parentConstraintCount: DECISION_READ, constraintChain: { word: "f012" }, constraintDigest: { word: "f012" }, }; export const CHECKPOINT_STATE_REQUIREMENTS = { activeTools: DECISION_READ, outputRef: DECISION_READ, nestedStats: DECISION_READ, readFileState: DECISION_READ, readFace: { word: "face" }, writeFace: { word: "write_face" }, skillGrants: { word: "skill_grants" }, reminderMark: DECISION_READ, repairBundle: DECISION_READ, workspaceHandle: DECISION_READ, pendingSteerQueue: STATE_MACHINE, runningBackgroundTasks: DECISION_READ, handsCwd: DECISION_READ, handsRoot: { word: "hands_coordinates" }, handsHome: { word: "hands_home" }, activeWorktree: DECISION_READ, inheritedGate: { members: INHERITED_GATE_REQUIREMENTS }, announcedListings: SHOWN, gitAnnouncement: SHOWN, delegationProvenance: { word: "delegation_provenance" }, isDelegatedChild: DECISION_READ, placementRootSessionId: DECISION_READ, externalContentTarget: DECISION_READ, legScope: { word: "leg_scope" }, toolCeiling: { word: "tool_ceiling" }, toolListsJudged: { word: "tool_lists_judged" }, ownToolLists: { word: "own_tool_lists" }, }; export const PENDING_ACTION_REQUIREMENTS = { tool_approval: { kind: { word: "binding" }, toolCallId: DECISION_READ, toolName: DECISION_READ, args: DECISION_READ, preview: SHOWN, previewWithheld: SHOWN, ruleOffers: SHOWN, ruleOffersAbsence: SHOWN, execCwd: DECISION_READ, requiresRealApproval: DECISION_READ, mandated: { word: "persisted_rule_mandate" }, authoredBy: { word: "deployment_authored" }, mandate: DECISION_READ, denialLimitFallback: { word: "denial_limit_fallback" }, classifierNoRuling: DECISION_READ, origin: DECISION_READ, askClass: DECISION_READ, classes: DECISION_READ, boundInputHash: DECISION_READ, boundGrantDigest: { word: "skill_card_binding" }, boundGrants: SHOWN, boundGrantAbsence: SHOWN, capabilityId: DECISION_READ, contractId: DECISION_READ, shapeDigest: DECISION_READ, implementationRevision: DECISION_READ, rosterDigest: SHOWN, family: DECISION_READ, pathTarget: DECISION_READ, names: { word: "call_names" }, hasBidiControls: SHOWN, batchToolCallIds: DECISION_READ, completedCallIds: SHOWN, }, resource_limit: { kind: DECISION_READ, reason: DECISION_READ }, review: { kind: DECISION_READ }, plan_review: { kind: { word: "binding" } }, task_done: { kind: DECISION_READ }, }; export const CHECKPOINT_GATE_REQUIREMENTS = { human: { kind: DECISION_READ, reason: DECISION_READ, toolName: DECISION_READ, approvalCard: DECISION_READ, riskDescriptor: SHOWN }, irreversible_ask: { kind: DECISION_READ, reason: DECISION_READ, toolName: DECISION_READ, approvalCard: DECISION_READ, safetyAxis: SHOWN, realApproval: { word: "realApproval" }, riskDescriptor: SHOWN }, resource_limit: { kind: { word: "resource" }, reason: DECISION_READ, resumeAfterMs: DECISION_READ }, needs_review: { kind: DECISION_READ, reason: DECISION_READ }, plan_review: { kind: DECISION_READ, reason: DECISION_READ }, task_done: { kind: DECISION_READ }, }; export const CHECKPOINT_REQUIREMENTS = { token: DECISION_READ, checkpointId: SHOWN, version: FRAME, requires: FRAME, requirementsDigest: { word: "requirements_digest" }, scope: DECISION_READ, sessionId: DECISION_READ, leafId: DECISION_READ, state: { members: CHECKPOINT_STATE_REQUIREMENTS }, resourceLedger: { members: RESOURCE_LEDGER_REQUIREMENTS }, deadline: DECISION_READ, status: STATE_MACHINE, createdAt: DECISION_READ, suspendCount: DECISION_READ, suspendedAt: SHOWN, humanReview: DECISION_READ, resolvedOutcome: STATE_MACHINE, reopenReason: STATE_MACHINE, rev: STATE_MACHINE, sourceTaskId: DECISION_READ, principal: DECISION_READ, durableApproval: DECISION_READ, executionOutcome: STATE_MACHINE, executionAtMs: { mutable: "the store's time stamp on the execution record: it changes after the mint and decides nothing" }, gate: { variants: CHECKPOINT_GATE_REQUIREMENTS }, pendingAction: { variants: PENDING_ACTION_REQUIREMENTS }, }; function digestedMembers(record, table, path, out) { if (typeof record !== "object" || record === null) return; for (const [key, cls] of Object.entries(table)) { if ("ignorable" in cls || "mutable" in cls) continue; const value = Reflect.get(record, key); if (value === undefined) continue; const at = path === "" ? key : `${path}.${key}`; if ("word" in cls) out.push({ path: at, word: cls.word, value }); else if ("digest" in cls) out.push({ path: at, value }); else if ("members" in cls) digestedMembers(value, cls.members, at, out); else if ("variants" in cls) { const kind = typeof value === "object" && value !== null ? Reflect.get(value, "kind") : undefined; const variant = typeof kind === "string" ? Object.entries(cls.variants).find(([k]) => k === kind)?.[1] : undefined; if (variant !== undefined) digestedMembers(value, variant, at, out); } } } const membersOf = (cp) => { const out = []; digestedMembers(cp, CHECKPOINT_REQUIREMENTS, "", out); return out; }; export function checkpointRequirementsFor(cp) { return [...new Set(membersOf(cp).flatMap((m) => (m.word !== undefined ? [m.word] : [])))].sort(); } class RowNotEncodable extends Error { at; constructor(at) { super(`${at.path} holds a value the row digest cannot encode (${at.kind})`); this.at = at; } } const DIGEST_DEPTH_CAP = 128; function digestProjection(value, path, depth, open) { if (typeof value === "function") throw new RowNotEncodable({ path, kind: "function" }); if (typeof value === "symbol") throw new RowNotEncodable({ path, kind: "symbol" }); if (typeof value !== "object" || value === null) return value; if (depth >= DIGEST_DEPTH_CAP) throw new RowNotEncodable({ path, kind: "depth" }); if (open.has(value)) throw new RowNotEncodable({ path, kind: "cycle" }); if (ArrayBuffer.isView(value)) return value; open.add(value); try { if (Array.isArray(value)) return value.map((el, i) => digestProjection(el, `${path}[${i}]`, depth + 1, open)); if (value instanceof Map) for (const [k, v] of value) (digestProjection(k, `${path}<key>`, depth + 1, open), digestProjection(v, `${path}<value>`, depth + 1, open)); else if (value instanceof Set) for (const v of value) digestProjection(v, `${path}<member>`, depth + 1, open); const plain = Object.prototype.toString.call(value) === "[object Object]"; const out = Object.create(null); for (const key of Object.keys(value)) { const v = value[key]; const projected = digestProjection(v, `${path}.${key}`, depth + 1, open); if (plain) out[key] = projected; } return plain ? out : value; } catch (e) { if (e instanceof RowNotEncodable) throw e; throw new RowNotEncodable({ path, kind: "unreadable" }); } finally { open.delete(value); } } export function unencodableIn(value, path) { try { digestProjection(value, path, 1, new Set()); return undefined; } catch (e) { if (e instanceof RowNotEncodable) return e.at; throw e; } } export function requirementsDigestOf(cp) { const held = Object.create(null); const open = new Set(); for (const m of membersOf(cp)) if (m.path !== "requirementsDigest") held[m.path] = digestProjection(m.value, m.path, 1, open); const canonical = canonicalize(held); if (canonical === UNCANONICALIZABLE) throw new RowNotEncodable({ path: "(row)", kind: "unreadable" }); return createHash("sha256").update(canonical, "utf8").digest("hex"); } function digestOrUnencodable(cp) { try { return { digest: requirementsDigestOf(cp) }; } catch (e) { if (e instanceof RowNotEncodable) return { at: e.at }; throw e; } } const unencodableWhy = (at) => `a member a decision reads cannot be encoded: ${at.path} (${at.kind})`; export function stampCheckpointRequirements(row, fidelity) { const { requirementsDigest: _stale, ...framed } = { ...row, version: CHECKPOINT_FEATURES.requirements.version }; const stored = encodeAtFidelity(fidelity, framed); const held = (stored.ok ? stored.value : framed); const got = stored.ok ? digestOrUnencodable(held) : { at: { path: "(row)", kind: "codec" } }; const digested = "digest" in got ? { ...held, requirementsDigest: got.digest } : held; return { ...digested, requires: checkpointRequirementsFor(digested) }; } export function judgeAsStored(cp, fidelity) { const stored = encodeAtFidelity(fidelity, cp); return stored.ok ? judgeCheckpointRequirements(stored.value) : { kind: "damaged", why: unencodableWhy({ path: "(row)", kind: "codec" }) }; } export function judgeCheckpointRequirements(cp) { const version = checkpointVersionOf(cp); const declared = Reflect.get(cp, "requires"); const digest = Reflect.get(cp, "requirementsDigest"); if (version < CHECKPOINT_FEATURES.requirements.version) return declared === undefined && digest === undefined ? { kind: "ok" } : { kind: "damaged", why: `a v${version} row carries a requirement list or digest no release of that version wrote` }; if (!Array.isArray(declared)) return { kind: "damaged", why: declared === undefined ? "the v10 row carries no requirement list" : "the requirement list is not a list" }; const said = []; const items = declared; for (const word of items) { if (typeof word !== "string") return { kind: "damaged", why: "the requirement list holds a value that is not a word" }; said.push(word); } const lacking = [...new Set(said.filter((w) => !isRequirementWord(w)))].sort(); if (lacking.length > 0) return { kind: "lacking", words: lacking }; const declaredSet = new Set(said); if (!declaredSet.has("requirements_digest")) { const got = digestOrUnencodable(cp); return { kind: "damaged", why: "at" in got ? unencodableWhy(got.at) : "the v10 row declares no content digest" }; } const carried = checkpointRequirementsFor(cp); const undeclared = carried.filter((w) => !declaredSet.has(w)); const unbacked = [...declaredSet].filter((w) => !carried.includes(w)).sort(); if (undeclared.length > 0 || unbacked.length > 0) { return { kind: "damaged", why: [unbacked.length > 0 ? `declared but not on the row: ${unbacked.join(", ")}` : "", undeclared.length > 0 ? `on the row but not declared: ${undeclared.join(", ")}` : ""].filter((s) => s !== "").join("; "), }; } const got = digestOrUnencodable(cp); if ("at" in got) return { kind: "damaged", why: unencodableWhy(got.at) }; return digest === got.digest ? { kind: "ok" } : { kind: "damaged", why: "the members a decision reads do not match the row's content digest" }; }