UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

241 lines (240 loc) • 12.4 kB
import { createHash } from "node:crypto"; import { CYBER_RISK, DEFAULT_SYSTEM_PROMPT, URL_SAFETY, HARNESS_SECTION_ANCHOR } from "../prompts/default.js"; import { compose, layoutSystemBlocks } from "./composer.js"; import { SEMA_DEFAULT_PACK } from "./packs/sema-default.js"; const REPLACE_ALL_EXCLUDED = new Set([ "core/mode.supervisor", "core/mode.orchestration", "core/mode.awareness", "core/mode.worktree", "core/mode.goal", "core/role.append", "core/simple.communicating", "core/simple.pronouns", "core/simple.action-caution", "core/simple.task-continuity", "core/simple.tool-param-json", "core/simple.investigate-first", "core/simple.act-dont-rederive", "core/simple.autonomy", "core/simple.delivering-work", "core/simple.corrections", "core/sema.verify-fresh", "core/sema.evidence-audit", ]); const OPAQUE_KEPT = new Set([ "core/role.base", "core/discovery.mcp-instructions", "core/environment.context", "core/memory.tail", "core/behavior.model-guidance", "core/mode.subagent-consent", "core/mode.subagent-notes", ]); function subsetPack(base, opts) { return { ...base, sections: base.sections .filter((s) => (opts.keep ? opts.keep.has(s.id) : true) && !(opts.exclude?.has(s.id) ?? false)) .map((s) => (s.id === "core/role.base" && opts.roleBaseBlockId ? { ...s, blockId: opts.roleBaseBlockId } : s)), }; } function verifyDeclaredContentHash(d, onWarn) { if (d.contentHash === undefined) return undefined; const expected = `sha256:${createHash("sha256").update(d.text).digest("hex")}`; if (d.contentHash === expected) return d.contentHash; onWarn?.(`prompt-provider stableBlocks: contentHash on "${d.id}" does not match sha256 of its text (expected shape sha256:<64 lowercase hex>) — dropped from the manifest`, "prompt-constitution"); return undefined; } function mountDeclarations(basePack, decls, onWarn) { const SLOT_BASE = { identity: 100, scenario: 350, behavior: 460 }; const counters = {}; const extra = []; let replacesRoleBase = false; let replacedRoleBaseBy; for (const d of decls) { if (!d.id || d.id.startsWith("core/") || d.id.length > 128 || !/^[a-z0-9][a-z0-9/._-]*$/.test(d.id)) { throw new Error(`prompt-provider stableBlocks: invalid section id ${JSON.stringify(d.id).slice(0, 140)} (must match ^[a-z0-9][a-z0-9/._-]*$, ≤128 chars, non-core/)`); } const base = SLOT_BASE[d.slot]; if (base === undefined) throw new Error(`prompt-provider stableBlocks: unknown slot "${String(d.slot)}" ("${d.id}")`); if (d.slot === "identity") { replacesRoleBase = true; replacedRoleBaseBy ??= d.id; } const n = (counters[d.slot] = (counters[d.slot] ?? 0) + 1); const declaredContentHash = verifyDeclaredContentHash(d, onWarn); extra.push({ id: d.id, slot: d.slot, rank: base + n * 0.001, owner: "deployment", trust: "operator", mutability: "replaceable", cadence: "epoch", cacheClass: "prefix-stable", carrier: "system.block", content: () => d.text, blockId: d.id, ...(declaredContentHash !== undefined ? { declaredContentHash } : {}), origin: "provider", }); } return { pack: { ...basePack, sections: [...basePack.sections.filter((s) => !(replacesRoleBase && s.id === "core/role.base")), ...extra], }, ...(replacedRoleBaseBy !== undefined && basePack.sections.some((s) => s.id === "core/role.base") ? { replacedRoleBaseBy } : {}), }; } function warnRoleBaseEviction(replacedRoleBaseBy, origin, onWarn) { if (replacedRoleBaseBy === undefined || origin === "pack-default") return; const what = origin === "task-system-prompt" ? "the caller's per-task systemPrompt" : "the PromptProvider's own role text"; onWarn?.(`prompt-constitution: identity declaration "${replacedRoleBaseBy}" supplants the role base, so ${what} does NOT compose into this prompt (identity sections replace the role layer by design). Fold that text into the declaration, or use a scenario/behavior slot to keep the role base.`, "prompt-constitution"); } export function assemblePrompt(inputs) { const { provider, facts } = inputs; const onWarn = inputs.onWarn ? (message, phase) => { try { inputs.onWarn(message, phase); } catch { } } : undefined; const stableCtx = { userSystemPrompt: inputs.userSystemPrompt, userAppendSystemPrompt: inputs.userAppendSystemPrompt, tools: inputs.tools, ...facts, }; let pack = SEMA_DEFAULT_PACK; let roleBase = inputs.userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT; let roleBaseOrigin = inputs.userSystemPrompt !== undefined ? "task-system-prompt" : "pack-default"; let constitution = "core"; let mountedCenter = []; if (!inputs.isDefaultProvider && provider.stableBlocks) { const decls = provider.stableBlocks(stableCtx).map((d) => ({ id: d.id, slot: d.slot, text: d.text, ...(d.contentHash !== undefined ? { contentHash: d.contentHash } : {}), })); const assembledIdentity = decls.find((d) => d.slot === "identity" && d.text.includes(CYBER_RISK) && d.text.includes(URL_SAFETY) && d.text.includes(HARNESS_SECTION_ANCHOR)); if (assembledIdentity) { onWarn?.("prompt-constitution: a stableBlocks identity declaration carries an already-assembled prompt (constitution anchors found). Passed through un-doubled — provide ONLY the role layer in identity blocks.", "prompt-constitution"); if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0) { onWarn?.("prompt-constitution: a center prompt artifact is adopted but the provider's assembled-identity pass-through owns the prompt — the center sections cannot mount on this migration path.", "prompt-constitution"); } const droppedDecls = decls.filter((d) => d !== assembledIdentity); if (droppedDecls.length > 0) { onWarn?.(`prompt-provider stableBlocks: the assembled-identity pass-through owns the prompt — ${droppedDecls.length} other declaration(s) do NOT compose this run: ${droppedDecls.map((d) => `"${d.id}" (${d.slot})`).join(", ")}. Declare ONLY the role layer in the identity block to let them mount.`, "prompt-constitution"); } const renderInputsPT = (date) => ({ roleBase: assembledIdentity.text, facts, mcpInstructionsBlock: inputs.mcpInstructionsBlock, environmentBlock: inputs.buildEnvBlock(date), memoryBlock: inputs.memoryBlock, modelGuidance: inputs.modelGuidance, }); if (assembledIdentity.contentHash !== undefined) { verifyDeclaredContentHash(assembledIdentity, onWarn); onWarn?.(`prompt-provider stableBlocks: contentHash on "${assembledIdentity.id}" is ignored on the pass-through path (the text is emitted as core/role.base — a digest there would carry the wrong identity). Declare ONLY the role layer to keep the reconciliation anchor.`, "prompt-constitution"); } const ptPack0 = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseBlockId: "provider.assembled" }); const ptPack = { ...ptPack0, sections: ptPack0.sections.map((s) => (s.id === "core/role.base" ? { ...s, origin: "provider" } : s)) }; let lastPT = compose(ptPack, renderInputsPT(inputs.date)); return { constitution: "provider-assembled", centerMounted: false, renderWithDate: (date) => { lastPT = compose(ptPack, renderInputsPT(date)); return lastPT.text; }, get systemPrompt() { return lastPT.text; }, get blocks() { return lastPT.blocks; }, get sections() { return lastPT.sections; }, get systemBlocks() { return layoutSystemBlocks(lastPT); }, }; } const providerIds = new Set(decls.map((d) => d.id)); const centerDecls = (inputs.centerDeclarations ?? []).filter((c) => { if (providerIds.has(c.id)) { onWarn?.(`prompt-constitution: center section "${c.id}" collides with a provider stableBlocks declaration of the same id — the provider declaration wins (deployment-local SHADOW); the center section is not mounted this run.`, "prompt-constitution"); return false; } return true; }); mountedCenter = centerDecls; const mountedSB = mountDeclarations(SEMA_DEFAULT_PACK, [...centerDecls, ...decls], onWarn); pack = mountedSB.pack; warnRoleBaseEviction(mountedSB.replacedRoleBaseBy, roleBaseOrigin, onWarn); } else if (!inputs.isDefaultProvider && provider.stableSystem) { const roleOut = provider.stableSystem(stableCtx); if (provider.replaceAll) { constitution = "replaced"; roleBase = roleOut; roleBaseOrigin = "provider"; pack = subsetPack(SEMA_DEFAULT_PACK, { exclude: REPLACE_ALL_EXCLUDED, roleBaseBlockId: "provider.replace_all" }); } else if (roleOut.includes(CYBER_RISK) && roleOut.includes(URL_SAFETY) && roleOut.includes(HARNESS_SECTION_ANCHOR)) { constitution = "provider-assembled"; roleBase = roleOut; roleBaseOrigin = "provider"; pack = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseBlockId: "provider.assembled" }); onWarn?.("prompt-constitution: this PromptProvider returns an already-assembled prompt (constitution anchor found). Core now appends the constitution structurally — return ONLY the role base from stableSystem (or set replaceAll:true to own the whole base). Passed through un-doubled.", "prompt-constitution"); } else { roleBase = roleOut; roleBaseOrigin = "provider"; } } if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0 && !(!inputs.isDefaultProvider && provider.stableBlocks)) { if (constitution === "provider-assembled") { onWarn?.("prompt-constitution: a center prompt artifact is adopted but this PromptProvider returned an already-assembled prompt — the pass-through owns the prompt and the center sections cannot mount on this migration path (an identity section would evict the provider text that carries the constitution). Return ONLY the role base from stableSystem to let center sections compose.", "prompt-constitution"); } else { const composedCenter = inputs.centerDeclarations; const mountedC = mountDeclarations(pack, composedCenter, onWarn); pack = mountedC.pack; warnRoleBaseEviction(mountedC.replacedRoleBaseBy, roleBaseOrigin, onWarn); mountedCenter = composedCenter; } } const renderInputs = (date) => ({ roleBase, roleAppend: inputs.userAppendSystemPrompt, facts, mcpInstructionsBlock: inputs.mcpInstructionsBlock, environmentBlock: inputs.buildEnvBlock(date), memoryBlock: inputs.memoryBlock, modelGuidance: inputs.modelGuidance, }); let last = compose(pack, renderInputs(inputs.date)); const renderWithDate = (date) => { last = compose(pack, renderInputs(date)); return last.text; }; return { constitution, centerMounted: mountedCenter.length > 0, renderWithDate, get systemPrompt() { return last.text; }, get blocks() { return last.blocks; }, get sections() { return last.sections; }, get systemBlocks() { return layoutSystemBlocks(last); }, }; }