fix(goal): quiet waits and unify fleet NG ownership (#56, #57, #58)

This commit is contained in:
Dewey
2026-09-06 04:07:09 -05:00
parent 7345f330fc
commit 9a5fbdbda7
14 changed files with 892 additions and 26 deletions
+122 -1
View File
@@ -51,6 +51,7 @@ async function createHeadlessHarness(root: string, incarnation: string) {
const commands = new Map<string, any>();
const tools = new Map<string, any>();
const sent: SentMessage[] = [];
const observedChecks = new Set<string>();
const notifications: Array<{ message: string; level: string }> = [];
const widgets: unknown[] = [];
const statuses: string[] = [];
@@ -70,7 +71,18 @@ async function createHeadlessHarness(root: string, incarnation: string) {
},
registerShortcut(key: string, shortcut: any) { shortcuts.set(key, shortcut); },
registerTool(tool: any) {
tools.set(tool.name, tool);
// Reports execute inside a model turn. Deliver its queued check's start
// rather than bypassing before_agent_start as the old fixture did.
tools.set(tool.name, { ...tool, execute: async (...args: any[]) => {
const queued = sent.at(-1)?.content;
if (queued && !observedChecks.has(queued)) {
observedChecks.add(queued);
for (const handler of handlers.get("before_agent_start") ?? []) {
await handler({ prompt: queued, systemPrompt: "base" }, ctx);
}
}
return tool.execute(...args);
} });
},
sendUserMessage(content: string, options?: SentMessage["options"]) {
sent.push({ content, options });
@@ -113,6 +125,7 @@ async function createHeadlessHarness(root: string, incarnation: string) {
idle = value;
},
async emit(name: string, event: any = { type: name }) {
if (name === "before_agent_start") observedChecks.add(event.prompt);
for (const handler of handlers.get(name) ?? []) await handler(event, ctx);
},
state() {
@@ -324,6 +337,114 @@ const waitReport = {
nextAction: "Read input and finish output", nextCheck: "input file available" },
};
test("queued check latch deduplicates lifecycle events until the matching start", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-check-latch-"));
const harness = await createHeadlessHarness(dir, "check-latch");
try {
await harness.commands.get("goal").handler("ready work", harness.ctx);
const first = harness.sent[0].content;
for (let i = 0; i < 10; i++) {
await harness.emit("session_start");
await harness.emit("agent_settled");
}
assert.equal(harness.sent.length, 1);
await harness.emit("before_agent_start", { prompt: "unrelated incoming question", systemPrompt: "base" });
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 1, "unrelated input does not release a pending check");
await harness.emit("before_agent_start", { prompt: first, systemPrompt: "base" });
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2, "a completed check can continue ready work");
await harness.emit("before_agent_start", { prompt: first, systemPrompt: "base" });
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2, "duplicate old start cannot release a newer check");
await harness.commands.get("goal").handler("replacement", harness.ctx);
assert.equal(harness.sent.length, 3, "explicit replacement may schedule its new goal");
await harness.commands.get("goal").handler("stop", harness.ctx);
await harness.commands.get("goal").handler("resume", harness.ctx);
assert.equal(harness.sent.length, 4);
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("untimed wait: settle and duplicate reports never inject a heartbeat", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-"));
const harness = await createHeadlessHarness(dir, "untimed");
try {
await harness.commands.get("goal").handler("wait for coordinator", harness.ctx);
const report = harness.tools.get("goal_report");
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
const before = harness.sent.length;
for (let i = 0; i < 100; i++) {
await harness.emit("agent_settled");
await report.execute(`repeat-${i}`, waitReport, undefined, undefined, harness.ctx);
}
t.mock.timers.tick(7 * 86400000);
assert.equal(harness.sent.length, before, "accepted waits must not generate model turns");
assert.equal(harness.state().checks, 0);
assert.equal(harness.state().noProgressReports, 0);
assert.equal(harness.state().activeWait.deadlineAt, undefined);
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("untimed wait: reload, unrelated input and tool work preserve suspension", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-reload-"));
const harness = await createHeadlessHarness(dir, "untimed-reload");
try {
await harness.commands.get("goal").handler("wait for coordinator", harness.ctx);
const report = harness.tools.get("goal_report");
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
const wait = harness.state().activeWait;
const before = harness.sent.length;
await harness.emit("session_shutdown");
await harness.emit("session_start");
await harness.emit("session_start");
assert.equal(harness.sent.length, before, "startup must preserve even an untimed wait");
await harness.emit("before_agent_start", { prompt: "unrelated question", systemPrompt: "base" });
await harness.emit("tool_result", { toolName: "read", isError: false });
await harness.emit("agent_settled");
assert.deepEqual(harness.state().activeWait, wait);
assert.equal(harness.sent.length, before);
await harness.emit("before_agent_start", { prompt: "dependency result arrived", systemPrompt: "base" });
await report.execute("progress", { status: "in_progress", evidence: "dependency verified", progress: { ...waitReport.progress, kind: "measurement", lastMeasurement: "verified input" } }, undefined, undefined, harness.ctx);
assert.equal(harness.state().activeWait, undefined);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, before + 1, "verified progress allows continuation");
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("untimed wait: explicit resume reconciles once, paused waits remain owner-controlled", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-resume-"));
const harness = await createHeadlessHarness(dir, "untimed-resume");
try {
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("wait for coordinator", harness.ctx);
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
await goal.handler("resume", harness.ctx);
assert.equal(harness.sent.length, 2, "resume must work for an active waiting goal");
assert.equal(harness.state().activeWait, undefined);
await report.execute("still-waiting", waitReport, undefined, undefined, harness.ctx);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2);
await goal.handler("stop", harness.ctx);
await harness.emit("before_agent_start", { prompt: "dependency result", systemPrompt: "base" });
await harness.emit("agent_settled");
assert.equal(harness.state().status, "paused");
assert.equal(harness.sent.length, 2);
await goal.handler("resume", harness.ctx);
assert.equal(harness.sent.length, 3);
assert.equal(harness.state().activeWait, undefined);
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("quiet wait: no heartbeats, one deadline wake, unresolved dependency pauses", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-runtime-"));