@sema-agent/core
Version:
Stateless, task-oriented AI agent core
243 lines (242 loc) • 16.4 kB
JavaScript
import { strict as assert } from "node:assert";
import { STALE_RUNNING_REAP_ATTRIBUTION, } from "../background-agent-store.js";
import { beginContract } from "./contract-harness.js";
export const BACKGROUND_AGENT_CONTRACT_SCOPE = "tenant-A";
function record(over = {}) {
return {
handle: "a0000000000000001",
scope: BACKGROUND_AGENT_CONTRACT_SCOPE,
owner: "task-1",
sessionScoped: false,
parentSessionId: "sess-1",
writerId: "writer-1",
spawnedAt: 1_000,
updatedAt: 1_000,
status: "running",
rev: 0,
...over,
};
}
async function withStores(mk, fn) {
const live = [];
const make = () => {
const s = mk();
live.push(s);
return s;
};
try {
await fn(make);
}
finally {
for (const s of live.splice(0))
s.close?.();
}
}
async function held(store, handle, scope = BACKGROUND_AGENT_CONTRACT_SCOPE) {
const row = await store.get(handle, scope);
assert.notEqual(row, null, `row ${handle} should exist in scope ${scope}`);
return row;
}
export async function backgroundAgentStoreContract(mk, runAssertion) {
const { run: runRaw, settle } = beginContract(runAssertion);
const run = (name, fn) => runRaw(name, () => withStores(mk, fn));
const S = BACKGROUND_AGENT_CONTRACT_SCOPE;
run("put is create-once and scope-partitioned; get round-trips the row and is null cross-scope", async (make) => {
const store = make();
await store.put(record({ handle: "a1", finalOutput: "result-text" }));
await assert.rejects(store.put(record({ handle: "a1" })), (e) => e.code === "agent_record.already_exists", "a second put under the same (handle, scope) must throw the typed already_exists error");
await store.put(record({ handle: "a1", scope: "tenant-B" }));
const row = await held(store, "a1");
assert.equal(row.finalOutput, "result-text");
assert.equal(row.owner, "task-1");
assert.equal(await store.get("a1", "tenant-C"), null, "an unrelated scope must not see the row");
assert.equal(await store.get("a-missing", S), null);
});
run("the org-admission fields round-trip, and ABSENT stays absent (a backend must not invent a verdict)", async (make) => {
const store = make();
await store.put(record({ handle: "a1", admittedOrgScopes: ["org:acme", "org:beta"], admittedOrgWriteScope: "org:acme" }));
const withVerdict = await held(store, "a1");
assert.deepEqual(withVerdict.admittedOrgScopes, ["org:acme", "org:beta"]);
assert.equal(withVerdict.admittedOrgWriteScope, "org:acme");
await store.put(record({ handle: "a2", admittedOrgScopes: [], admittedOrgWriteScope: null }));
const readOnly = await held(store, "a2");
assert.deepEqual(readOnly.admittedOrgScopes, []);
assert.equal(readOnly.admittedOrgWriteScope, null);
await store.put(record({ handle: "a3" }));
const untouched = await held(store, "a3");
assert.equal(untouched.admittedOrgScopes, undefined);
assert.equal(untouched.admittedOrgWriteScope, undefined);
await store.update("a3", S, { ...untouched, admittedOrgScopes: ["org:acme"], admittedOrgWriteScope: null }, { rev: untouched.rev });
const afterCas = await held(store, "a3");
assert.deepEqual(afterCas.admittedOrgScopes, ["org:acme"]);
assert.equal(afterCas.admittedOrgWriteScope, null);
});
run("put refuses a scopeless row (a row with no tenant partition would be world-readable)", async (make) => {
const store = make();
await assert.rejects(store.put(record({ scope: "" })), "an empty scope must be refused loudly, not stored");
});
run("a row carrying a key this build does not declare round-trips: no refusal, every declared field intact", async (make) => {
const store = make();
await store.put({ ...record({ handle: "a-extra", name: "worker", agentType: "reader" }), teamName: "retired-column" });
const row = await held(store, "a-extra");
assert.equal(row.name, "worker");
assert.equal(row.agentType, "reader");
assert.equal(row.status, "running");
await store.update("a-extra", S, { ...row, status: "completed" }, { rev: row.rev });
assert.equal((await held(store, "a-extra")).status, "completed", "a CAS update over such a row still lands");
});
run("get returns DETACHED copies: mutating a read never reaches the stored row", async (make) => {
const store = make();
await store.put(record({ handle: "a1", usage: { totalTokens: 5 } }));
const first = await held(store, "a1");
first.status = "failed";
first.usage.totalTokens = 999;
const second = await held(store, "a1");
assert.equal(second.status, "running");
assert.equal(second.usage?.totalTokens, 5);
});
run("update is a rev-CAS: stale rev loses, wrong scope loses, the winner bumps rev by EXACTLY 1", async (make) => {
const store = make();
await store.put(record({ handle: "a1" }));
const row = await held(store, "a1");
assert.equal(await store.update("a1", "tenant-B", { ...row, status: "completed" }, { rev: row.rev }), false, "wrong scope");
assert.equal(await store.update("a1", S, { ...row, status: "completed" }, { rev: row.rev + 5 }), false, "rev ahead");
assert.equal(await store.update("a-missing", S, { ...row, status: "completed" }, { rev: row.rev }), false, "missing row");
assert.equal(await store.update("a1", S, { ...row, status: "completed" }, { rev: row.rev }), true);
const after = await held(store, "a1");
assert.equal(after.status, "completed");
assert.equal(after.rev, row.rev + 1);
assert.equal(await store.update("a1", S, { ...row, status: "failed" }, { rev: row.rev }), false);
assert.equal((await held(store, "a1")).status, "completed");
});
run("update keeps the KEY authoritative: a payload naming another handle/scope cannot repartition the row", async (make) => {
const store = make();
await store.put(record({ handle: "a1" }));
const row = await held(store, "a1");
assert.equal(await store.update("a1", S, { ...row, handle: "a-other", scope: "tenant-B", status: "failed" }, { rev: row.rev }), true);
const after = await held(store, "a1");
assert.equal(after.handle, "a1", "the key's handle wins over the payload's");
assert.equal(after.scope, S, "scope is the partition key and an update must never rewrite it");
assert.equal(await store.get("a-other", "tenant-B"), null, "no second row may appear");
});
run("updateIf keeps the rev guard AND adds the field guards (status / checkpoint token / claim id)", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "parked", parkedCheckpointToken: "cp-1" }));
const row = await held(store, "a1");
assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev + 3 }), false, "stale rev");
assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, status: "running" }), false, "status mismatch");
assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, status: "parked", parkedCheckpointToken: "cp-OTHER" }), false, "checkpoint token mismatch");
assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, parkedCheckpointToken: null }), false, "null must mean 'this field is absent', and it is present");
assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, parkClaimId: "claim-1" }), false, "claim id expected but absent");
const next = { ...row, status: "running", parkClaimId: "claim-1" };
assert.equal(await store.updateIf("a1", S, next, { rev: row.rev, status: "parked", parkedCheckpointToken: "cp-1", parkClaimId: null }), true, "matching guards (incl. null-as-absent on the claim id) must win");
const after = await held(store, "a1");
assert.equal(after.status, "running");
assert.equal(after.parkClaimId, "claim-1");
assert.equal(after.rev, row.rev + 1);
});
run("listBySession anchors on (sessionScoped ? owner : parentSessionId) OR rootSessionId, newest-first", async (make) => {
const store = make();
await store.put(record({ handle: "a1", spawnedAt: 1_000, finalOutput: "SECRET", summary: "s", error: "e", recentSteps: [] }));
await store.put(record({ handle: "a2", owner: "sess-1", sessionScoped: true, parentSessionId: undefined, spawnedAt: 2_000 }));
await store.put(record({ handle: "a3", parentSessionId: "sess-mid", rootSessionId: "sess-1", spawnedAt: 3_000 }));
await store.put(record({ handle: "a4", parentSessionId: "sess-other", spawnedAt: 4_000 }));
const rows = await store.listBySession(S, "sess-1");
assert.deepEqual(rows.map((r) => r.handle), ["a3", "a2", "a1"], "newest-first by spawnedAt");
for (const row of rows) {
for (const field of ["finalOutput", "summary", "error", "recentSteps", "editedFiles"]) {
assert.equal(Object.hasOwn(row, field), false, `list projections must not carry ${field}`);
}
}
assert.deepEqual((await store.listBySession(S, "sess-1", { limit: 2 })).map((r) => r.handle), ["a3", "a2"], "limit keeps the NEWEST n");
assert.deepEqual(await store.listBySession("tenant-B", "sess-1"), [], "another tenant sees nothing");
assert.deepEqual((await store.listBySession(S, "sess-mid")).map((r) => r.handle), ["a3"], "the intermediate anchor still lists its direct child");
});
run("listBySession/listByScope filter one status; listByScope enumerates the whole tenant", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "running", spawnedAt: 1_000 }));
await store.put(record({ handle: "a2", status: "completed", spawnedAt: 2_000 }));
await store.put(record({ handle: "a3", status: "completed", parentSessionId: "sess-other", spawnedAt: 3_000 }));
await store.put(record({ handle: "a4", status: "completed", scope: "tenant-B", spawnedAt: 4_000 }));
assert.deepEqual((await store.listByScope(S)).map((r) => r.handle), ["a3", "a2", "a1"], "scope-wide, newest-first");
assert.deepEqual((await store.listByScope(S, { status: "completed" })).map((r) => r.handle), ["a3", "a2"]);
assert.deepEqual((await store.listByScope(S, { status: "completed", limit: 1 })).map((r) => r.handle), ["a3"]);
assert.deepEqual((await store.listBySession(S, "sess-1", { status: "completed" })).map((r) => r.handle), ["a2"]);
assert.deepEqual((await store.listByScope("tenant-B")).map((r) => r.handle), ["a4"], "tenants are separate ledgers");
});
run("delete is row-precise and CONDITIONAL when a rev is supplied (a concurrent revival must win)", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "completed" }));
await store.put(record({ handle: "a2", status: "completed" }));
const row = await held(store, "a1");
assert.equal(await store.delete("a1", S, { rev: row.rev + 1 }), false, "a stale rev must not delete");
assert.notEqual(await store.get("a1", S), null);
assert.equal(await store.delete("a1", "tenant-B"), false, "cross-scope delete is a miss");
assert.equal(await store.delete("a1", S, { rev: row.rev }), true);
assert.equal(await store.get("a1", S), null);
assert.equal(await store.delete("a1", S), false, "deleting a gone row reports false, not true");
assert.equal(await store.delete("a2", S), true, "an unconditional delete still works");
});
run("reap with NO policy is a no-op (retention is always explicit)", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "completed", settledAt: 1_000 }));
assert.equal(await store.reap(S, 100_000), 0);
assert.equal(await store.reap(S, 100_000, {}), 0);
assert.notEqual(await store.get("a1", S), null);
});
run("reap deletes terminal rows by age and by keep-bound; running and parked rows survive BOTH", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "completed", spawnedAt: 1_000, settledAt: 1_000 }));
await store.put(record({ handle: "a2", status: "running", spawnedAt: 1_000, updatedAt: 1_000 }));
await store.put(record({ handle: "a3", status: "parked", spawnedAt: 1_000, updatedAt: 1_000, parkedCheckpointToken: "cp-1" }));
assert.equal(await store.reap(S, 100_000, { maxAgeMs: 10 }), 1, "only the terminal row ages out");
assert.equal(await store.get("a1", S), null);
assert.equal((await held(store, "a2")).status, "running");
assert.equal((await held(store, "a3")).status, "parked");
for (const i of [1, 2, 3])
await store.put(record({ handle: `b${i}`, status: "completed", spawnedAt: 1_000 + i, settledAt: 2_000 + i }));
assert.equal(await store.reap(S, 100_000, { keep: 2 }), 1);
assert.deepEqual((await store.listByScope(S, { status: "completed" })).map((r) => r.handle), ["b3", "b2"]);
assert.notEqual(await store.get("a2", S), null);
assert.notEqual(await store.get("a3", S), null);
});
run("reap's stale-running arm flips to a terminal `failed` WITH the shared attribution, and never overwrites real failure text", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "running", updatedAt: 1_000 }));
await store.put(record({ handle: "a2", status: "running", updatedAt: 1_000, error: "the child reported a real failure" }));
assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000 }), 2);
const flipped = await held(store, "a1");
assert.equal(flipped.status, "failed", "an interrupted host's row settles as a terminal, not a perpetual running");
assert.equal(flipped.stoppedBy, "system");
assert.equal(flipped.settledAt, 100_000);
assert.equal(flipped.updatedAt, 100_000);
assert.equal(flipped.error, STALE_RUNNING_REAP_ATTRIBUTION);
assert.equal(flipped.summary, STALE_RUNNING_REAP_ATTRIBUTION);
assert.equal((await held(store, "a2")).error, "the child reported a real failure");
await store.put(record({ handle: "a3", status: "running", updatedAt: 99_000 }));
assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000 }), 0);
assert.equal((await held(store, "a3")).status, "running");
});
run("reap counts each ROW once even when the same sweep flips it AND removes it", async (make) => {
const store = make();
await store.put(record({ handle: "a1", status: "running", spawnedAt: 1_000, updatedAt: 1_000 }));
assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000, keep: 0 }), 1, "one row changed, so the count is 1");
assert.equal(await store.get("a1", S), null, "and it really is gone (flipped, then trimmed)");
});
await settle();
}
export async function backgroundAgentStoreScopesContract(mk, runAssertion) {
const { run: runRaw, settle } = beginContract(runAssertion);
const run = (name, fn) => runRaw(name, () => withStores(mk, fn));
run("listScopes enumerates the scopes that currently HOLD rows, deduplicated and sorted", async (make) => {
const store = make();
assert.equal(typeof store.listScopes, "function", "this layer is only for backends that implement listScopes");
await store.put(record({ handle: "a1", scope: "tenant-B" }));
await store.put(record({ handle: "a2", scope: "tenant-A" }));
await store.put(record({ handle: "a3", scope: "tenant-A" }));
assert.deepEqual(await store.listScopes(), ["tenant-A", "tenant-B"], "deduplicated and sorted — a retention loop drives itself off this");
await store.delete("a1", "tenant-B");
assert.deepEqual(await store.listScopes(), ["tenant-A"]);
});
await settle();
}