feat(core,server,docs): default max_turns to -1 (unlimited) to avoid unexpected run cutoffs (#232)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4a899a265f
commit
6e1c7cc28e
12 files changed
+80
-22
No files matched your search
@@ -479,7 +479,7 @@ describe("ContextEngine ReAct loop (mock LLM, approve callback)", () => {
|
||||
expect(deniedMsg).toBeDefined();
|
||||
});
|
||||
|
||||
it("max_turns default is 100", () => {
|
||||
it("engine maxTurns fallback is -1 (unlimited) when the option is omitted (direct SDK construction)", () => {
|
||||
const engine = new ContextEngine({
|
||||
llm: new FakeLLM(),
|
||||
environment: new Environment({
|
||||
@@ -487,8 +487,10 @@ describe("ContextEngine ReAct loop (mock LLM, approve callback)", () => {
|
||||
toolConfig: execCommandToolConfig(),
|
||||
}),
|
||||
});
|
||||
// Reads the default via a private field (white-box, only testing the default).
|
||||
expect((engine as unknown as { maxTurns: number }).maxTurns).toBe(100);
|
||||
// Reads the fallback via a private field (white-box, only testing the fallback). The
|
||||
// SDK-construction fallback for an omitted option now matches the agent-config default
|
||||
// (defaultSystemConfig().max_turns, asserted in state.test.ts): -1 = no turn cap.
|
||||
expect((engine as unknown as { maxTurns: number }).maxTurns).toBe(-1);
|
||||
});
|
||||
|
||||
it("streams the max-turns stop note before the complete text (no extra leading newline)", async () => {
|
||||
|
||||
@@ -297,6 +297,20 @@ describe("runGoalLoop", () => {
|
||||
expect(await readGoalStatus(file)).toBe("active");
|
||||
});
|
||||
|
||||
it("an explicit maxRounds of -1 disables the backstop: the goal runs past 100 rounds", async () => {
|
||||
// The default backstop stays 100; -1 is the explicit opt-out. Round 103 claims
|
||||
// complete, which the default cap would have ended as `aborted` at round 100.
|
||||
const behaviors = Array.from({ length: 103 }, (_, i) =>
|
||||
i === 102 ? { then: () => setStatus("complete") } : {},
|
||||
);
|
||||
const session = fakeSession(behaviors);
|
||||
const { outcome } = await drain(
|
||||
runGoalLoop(session, { text: "o", goalFilePath: file, maxRounds: -1 }),
|
||||
);
|
||||
expect(outcome).toEqual({ outcome: "complete", rounds: 103, tokensUsed: 0 });
|
||||
expect(session.prompts).toHaveLength(103);
|
||||
});
|
||||
|
||||
it("an abort landing between rounds stops the loop without a phantom round", async () => {
|
||||
const ac = new AbortController();
|
||||
// The signal aborts AFTER round 1's stream ends — no abort event ever hits the stream,
|
||||
|
||||
@@ -130,6 +130,10 @@ describe("loadOrInitAgentState", () => {
|
||||
expect(state.systemConfig.system_prompt).toContain("Caller agent");
|
||||
// The default AGENTS.md is empty: it carries no preset guidance.
|
||||
expect(state.agentsMd).toBe("");
|
||||
// The default turn cap is the -1 sentinel: a new agent has no per-Task turn limit, so
|
||||
// long runs are never cut off unless the user configures a positive cap. Existing
|
||||
// agents keep their stored max_turns verbatim (config is never auto-merged).
|
||||
expect(state.systemConfig.max_turns).toBe(-1);
|
||||
expect(state.systemConfig.system_prompt).toContain(AGENTS_MD_PLACEHOLDER);
|
||||
expect(state.systemConfig.system_prompt).toContain(SESSION_ID_PLACEHOLDER);
|
||||
expect(state.systemConfig.system_prompt).toContain(CWD_PLACEHOLDER);
|
||||
|
||||
Reference in new issue
Block a user