refactor: replace maxRounds with supervisor check interval

Removes maxRounds as a hard stop limit from the entire stack. The supervisor
(already configured via workflow.yaml supervisorInterval) is now the sole
termination authority.

Changes across 27 files in 11 packages:
- workflow-protocol: StartStep.meta is now empty, StartNodePayload drops maxRounds
- workflow-cas: isStartPayload no longer checks maxRounds
- workflow-execute: engine, worker, fork-thread all stripped of maxRounds plumbing
- cli-workflow: --max-rounds flag removed from CLI and HTTP API
- workflow-runtime: build-context and create-workflow no longer reference maxRounds
- workflow-dashboard: UI no longer sends maxRounds
- workflow-template-develop/solve-issue: moderator no longer checks rounds remaining
- All tests updated

Fixes #185
This commit is contained in:
2026-05-11 08:51:35 +00:00
parent 0021596ff0
commit 2b587612d5
32 changed files with 84 additions and 204 deletions
@@ -27,7 +27,7 @@ describe("buildThreadContext", () => {
const bundleHash = "BHAAAAAAAAAAA";
const startHash = await putStartNode(
cas,
{ name: "demo", hash: bundleHash, maxRounds: 99, depth: 2 },
{ name: "demo", hash: bundleHash, depth: 2 },
promptHash,
);
@@ -59,7 +59,6 @@ describe("buildThreadContext", () => {
expect(ctx.depth).toBe(2);
expect(ctx.start.role).toBe(START);
expect(ctx.start.content).toBe("hello-task");
expect(ctx.start.meta.maxRounds).toBe(99);
expect(ctx.steps.map((s) => s.role)).toEqual(["planner", "coder"]);
expect(ctx.steps[0]?.refs).toEqual([art]);
expect(ctx.steps[1]?.refs).toEqual([]);
@@ -72,7 +71,7 @@ describe("buildThreadContext", () => {
const promptHash = await cas.put("only-prompt");
const startHash = await putStartNode(
cas,
{ name: "solo", hash: "BHBBBBBBBBBBB", maxRounds: 3, depth: 1 },
{ name: "solo", hash: "BHBBBBBBBBBBB", depth: 1 },
promptHash,
);
@@ -80,7 +79,6 @@ describe("buildThreadContext", () => {
expect(ctx.steps).toEqual([]);
expect(ctx.start.content).toBe("only-prompt");
expect(ctx.depth).toBe(1);
expect(ctx.start.meta.maxRounds).toBe(3);
});
test("omits __end__ states from steps", async () => {
@@ -89,7 +87,7 @@ describe("buildThreadContext", () => {
const bundleHash = "BHCCCCCCCCCCC";
const startHash = await putStartNode(
cas,
{ name: "demo", hash: bundleHash, maxRounds: 10, depth: 0 },
{ name: "demo", hash: bundleHash, depth: 0 },
promptHash,
);
@@ -57,7 +57,7 @@ async function threadFromStartHead<M extends RoleMeta>(
start: {
role: START,
content: prompt,
meta: { maxRounds: p.maxRounds },
meta: {},
timestamp: 0,
},
steps: [],
@@ -116,7 +116,7 @@ async function threadFromStateHead<M extends RoleMeta>(
start: {
role: START,
content: prompt,
meta: { maxRounds: sp.maxRounds },
meta: {},
timestamp: firstTs,
},
steps,
@@ -101,9 +101,10 @@ async function advanceOneRound<M extends RoleMeta>(
);
const artifactRefs = mergeUniqueHashes(extracted.refs, refsFromMeta);
const contentHash = artifactRefs.length === 0
? agentContentHash
: await putContentNodeWithRefs(runtime.cas, extracted.contentPayload, artifactRefs);
const contentHash =
artifactRefs.length === 0
? agentContentHash
: await putContentNodeWithRefs(runtime.cas, extracted.contentPayload, artifactRefs);
const refs = artifactRefs.includes(contentHash) ? artifactRefs : [...artifactRefs, contentHash];
const step = {
@@ -144,17 +145,9 @@ export function createWorkflow<M extends RoleMeta>(
if (thread.start.role !== START) {
throw new Error(`workflow loop expected start role to be ${START}`);
}
const maxRounds = thread.start.meta.maxRounds;
let currentThread = thread as ModeratorContext<M>;
while (true) {
if (currentThread.steps.length >= maxRounds) {
return {
returnCode: 0,
summary: `completed: reached maxRounds (${maxRounds})`,
};
}
const outcome = await advanceOneRound(def, binding, {
thread: currentThread,
runtime,