UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

191 lines (190 loc) • 12.7 kB
import { strict as assert } from "node:assert"; import { summarizeWorkflowRun } from "./workflow-run-store.js"; export function createWorkflowRun(over = {}) { const createdAt = over.createdAt ?? 1_000; return { id: over.id ?? "run-1", scope: "tenant-a", status: "completed", phases: [{ title: "build", status: "completed", startedAt: createdAt, endedAt: createdAt + 5 }], agents: [ { label: "agent-1", callKey: "0:abc", phase: "build", status: "completed", taskStatus: "completed", queuedAt: createdAt, startedAt: createdAt, endedAt: createdAt + 4, stats: { tokens: 100, turns: 1, costMicroUsd: 10 } }, ], groups: [], stats: { tokens: 100, turns: 1, costMicroUsd: 10, nested: { tokens: 50, turns: 1, tasks: 1, costMicroUsd: 5 } }, startedAt: createdAt, endedAt: createdAt + 10, createdAt, ...over, }; } export async function workflowRunStoreContract(make, runAssertion = defaultSequentialRunner) { const pending = []; const run = (name, fn) => { const result = runAssertion(name, fn); if (result instanceof Promise) pending.push(result); }; run("put create-once → already_exists", async () => { const store = make(); const r = createWorkflowRun(); await store.put(r.id, r); await assert.rejects(store.put(r.id, r), (e) => e.code === "workflow_run.already_exists"); }); run("get returns a deep copy (mutating the result must not corrupt the store)", async () => { const store = make(); const r = createWorkflowRun(); await store.put(r.id, r); const got = await store.get(r.id); assert.ok(got); got.agents.push({ label: "x", callKey: "0:x", status: "running", queuedAt: 0, startedAt: 0 }); const again = await store.get(r.id); assert.equal(again.agents.length, 1); assert.equal(await store.get("nope"), null); }); run("update CAS: bumps rev, scope-mismatch loses, stale-rev loses, current-rev wins", async () => { const store = make(); const r = createWorkflowRun({ status: "running" }); await store.put(r.id, r); assert.equal(await store.update(r.id, r.scope, { ...r, status: "running", stats: { ...r.stats, tokens: 200 } }), true); assert.equal((await store.get(r.id)).rev, 1); assert.equal((await store.get(r.id)).stats.tokens, 200); assert.equal(await store.update(r.id, "tenant-b", { ...r, status: "completed" }), false); assert.equal(await store.update(r.id, r.scope, { ...r, status: "completed" }, { rev: 0 }), false); assert.equal(await store.update(r.id, r.scope, { ...r, status: "completed" }, { rev: 1 }), true); assert.equal((await store.get(r.id)).rev, 2); assert.equal((await store.get(r.id)).status, "completed"); assert.equal(await store.update("ghost", r.scope, createWorkflowRun({ id: "ghost" })), false); }); run("the key id is authoritative — a payload with a mismatched id never poisons the projection", async () => { const store = make(); const r = createWorkflowRun({ id: "real", status: "running" }); await store.put("real", { ...r, id: "spoofed" }); assert.equal((await store.get("real")).id, "real"); assert.equal(await store.get("spoofed"), null); await store.update("real", r.scope, { ...r, id: "spoofed-again", status: "completed" }); assert.equal((await store.get("real")).id, "real"); const listed = await store.listByScope(r.scope); assert.ok(listed.every((s) => s.id === "real")); }); run("listByScope: history included (completed + failed + running), createdAt DESC, status filter + limit", async () => { const store = make(); assert.deepEqual(await store.listByScope("tenant-a"), []); const running = createWorkflowRun({ id: "r-run", status: "running", createdAt: 3_000, endedAt: undefined }); const done = createWorkflowRun({ id: "r-done", status: "completed", createdAt: 2_000 }); const failed = createWorkflowRun({ id: "r-fail", status: "failed", createdAt: 1_000 }); const other = createWorkflowRun({ id: "r-other", scope: "tenant-b", createdAt: 9_000 }); for (const r of [running, done, failed, other]) await store.put(r.id, r); const all = await store.listByScope("tenant-a"); assert.deepEqual(all.map((s) => s.id), ["r-run", "r-done", "r-fail"]); assert.deepEqual(all.map((s) => s.status), ["running", "completed", "failed"]); assert.deepEqual((await store.listByScope("tenant-a", { status: "completed" })).map((s) => s.id), ["r-done"]); assert.deepEqual((await store.listByScope("tenant-a", { status: "failed" })).map((s) => s.id), ["r-fail"]); assert.deepEqual((await store.listByScope("tenant-a", { limit: 2 })).map((s) => s.id), ["r-run", "r-done"]); assert.deepEqual(all.find((s) => s.id === "r-done"), summarizeWorkflowRun(done)); assert.equal(all.find((s) => s.id === "r-done").tokens, done.stats.tokens + done.stats.nested.tokens); const doneSummary = all.find((s) => s.id === "r-done"); assert.equal(doneSummary.currentPhase, "build"); assert.equal(doneSummary.name, undefined); assert.equal(doneSummary.description, undefined); const named = createWorkflowRun({ id: "r-named", name: "find-flaky", description: "find flaky tests", createdAt: 4_000, phases: [ { title: "scan", status: "completed", startedAt: 4_000, endedAt: 4_001 }, { title: "fix", status: "running", startedAt: 4_001 }, ], }); await store.put(named.id, named); const namedSummary = (await store.listByScope("tenant-a")).find((s) => s.id === "r-named"); assert.equal(namedSummary.name, "find-flaky"); assert.equal(namedSummary.description, "find flaky tests"); assert.equal(namedSummary.currentPhase, "fix"); }); run("the ADDITIVE terminal disclosures reach the LIST row: agentFailures + budgetOvershoot project, and a clean run keeps both keys ABSENT", async () => { const store = make(); const over = createWorkflowRun({ id: "d-over", createdAt: 5_000, agentFailures: 2, budgetOvershoot: { budgetTokens: 150, spentTokens: 1_200 } }); const unsettled = createWorkflowRun({ id: "d-unsettled", createdAt: 6_000, budgetOvershoot: { budgetTokens: 100, spentTokens: 0, unsettledTokens: 500 } }); const clean = createWorkflowRun({ id: "d-clean", createdAt: 7_000 }); for (const r of [over, unsettled, clean]) await store.put(r.id, r); const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.id, s])); assert.equal(byId.get("d-over").agentFailures, 2); assert.deepEqual(byId.get("d-over").budgetOvershoot, { budgetTokens: 150, spentTokens: 1_200 }); assert.deepEqual(byId.get("d-unsettled").budgetOvershoot, { budgetTokens: 100, spentTokens: 0, unsettledTokens: 500 }); assert.equal("agentFailures" in byId.get("d-clean"), false, "no failures ⇒ the key is OMITTED, not 0/null"); assert.equal("budgetOvershoot" in byId.get("d-clean"), false, "within (or without) a ceiling ⇒ the key is OMITTED, not null/zeroed"); assert.equal(byId.get("d-over").tokens, over.stats.tokens + over.stats.nested.tokens); byId.get("d-over").budgetOvershoot.spentTokens = 7; assert.equal((await store.get("d-over")).budgetOvershoot.spentTokens, 1_200); }); run("the failure CODE reaches the LIST row verbatim, and a run without one keeps the key ABSENT", async () => { const store = make(); const coded = createWorkflowRun({ id: "f-coded", status: "failed", createdAt: 5_000, error: "the body gave up", errorCode: "workflow.agent_blocked" }); const codeless = createWorkflowRun({ id: "f-codeless", status: "failed", createdAt: 6_000, error: "the body gave up" }); const ok = createWorkflowRun({ id: "f-ok", status: "completed", createdAt: 7_000 }); for (const r of [coded, codeless, ok]) await store.put(r.id, r); const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.id, s])); assert.equal(byId.get("f-coded").errorCode, "workflow.agent_blocked"); assert.equal("errorCode" in byId.get("f-codeless"), false, "a failed run with no code ⇒ the key is OMITTED, not an `unknown` stand-in"); assert.equal("errorCode" in byId.get("f-ok"), false, "a completed run never carries one ⇒ the key is OMITTED"); assert.deepEqual(byId.get("f-coded"), summarizeWorkflowRun(coded)); }); run("the TOTAL-TIMEOUT disclosure reaches the LIST row: timeoutInterruption projects (de-aliased), and a run without it keeps the key ABSENT", async () => { const store = make(); const seat = { timeoutMs: 600_000, agentsCompleted: 3, agentsFailed: 1, agentsInFlight: 2, approvalWaitedMs: 540_000 }; const cut = createWorkflowRun({ id: "d-timeout", createdAt: 8_000, status: "failed", error: "workflow total timeout", timeoutInterruption: seat }); const noWait = { timeoutMs: 600_000, agentsCompleted: 1, agentsFailed: 0, agentsInFlight: 1 }; const slow = createWorkflowRun({ id: "d-slow", createdAt: 8_050, status: "failed", error: "workflow total timeout", timeoutInterruption: noWait }); const threw = createWorkflowRun({ id: "d-threw", createdAt: 8_100, status: "failed", error: "script blew up" }); for (const r of [cut, slow, threw]) await store.put(r.id, r); const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.id, s])); assert.deepEqual(byId.get("d-timeout").timeoutInterruption, seat); assert.deepEqual(byId.get("d-slow").timeoutInterruption, noWait); assert.equal("approvalWaitedMs" in byId.get("d-slow").timeoutInterruption, false, "a run nobody reported a wait for ⇒ the member is OMITTED, never stored as 0 (absence is 'not supplied', not 'nobody waited')"); assert.equal("timeoutInterruption" in byId.get("d-threw"), false, "a run its own error ended ⇒ the key is OMITTED, not null/zeroed"); byId.get("d-timeout").timeoutInterruption.agentsCompleted = 7; assert.equal((await store.get("d-timeout")).timeoutInterruption.agentsCompleted, 3); }); run("reap: only terminal runs, NEVER running; maxAgeMs + keep retention; no policy → no-op", async () => { const store = make(); const running = createWorkflowRun({ id: "k-run", status: "running", createdAt: 100, endedAt: undefined }); const oldDone = createWorkflowRun({ id: "k-old", status: "completed", createdAt: 100, endedAt: 100 }); const newDone = createWorkflowRun({ id: "k-new", status: "completed", createdAt: 9_000, endedAt: 9_000 }); const oldFail = createWorkflowRun({ id: "k-fail", status: "failed", createdAt: 200, endedAt: 200 }); for (const r of [running, oldDone, newDone, oldFail]) await store.put(r.id, r); assert.equal(await store.reap("tenant-a", 1_000_000), 0); assert.equal(store2size(store) >= -1, true); const reaped = await store.reap("tenant-a", 10_000, { maxAgeMs: 5_000 }); assert.equal(reaped, 2); assert.equal(await store.get("k-old"), null); assert.equal(await store.get("k-fail"), null); assert.ok(await store.get("k-new")); assert.ok(await store.get("k-run")); }); run("reap keep-newest-N: deletes all but the newest N terminal runs; running excluded from the count", async () => { const store = make(); const running = createWorkflowRun({ id: "p-run", status: "running", createdAt: 50, endedAt: undefined }); const t1 = createWorkflowRun({ id: "p-1", status: "completed", createdAt: 1_000 }); const t2 = createWorkflowRun({ id: "p-2", status: "completed", createdAt: 2_000 }); const t3 = createWorkflowRun({ id: "p-3", status: "failed", createdAt: 3_000 }); for (const r of [running, t1, t2, t3]) await store.put(r.id, r); assert.equal(await store.reap("tenant-a", 99_999, { keep: 1 }), 2); assert.equal(await store.get("p-1"), null); assert.equal(await store.get("p-2"), null); assert.ok(await store.get("p-3")); assert.ok(await store.get("p-run")); }); await Promise.all(pending); } function defaultSequentialRunner(_name, fn) { return fn(); } function store2size(store) { const s = store.size; return typeof s === "number" ? s : -1; }