@sema-agent/core
Version:
Stateless, task-oriented AI agent core
337 lines (336 loc) • 13.9 kB
JavaScript
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" };
}