Skip to content

Commit 3490291

Browse files
chore(ci): ignore benchmark and working-note scratch dirs
2 parents 95d52c3 + c73e35a commit 3490291

4 files changed

Lines changed: 98 additions & 1 deletion

File tree

‎.gitignore‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ subagents/
3030

3131
# Working notes and design spikes — local only, never committed
3232
docs/plans/
33+
# Fleet/harness working notes — local only, never committed
34+
plans/
35+
36+
# Benchmark/eval scratch — local only, never committed
37+
tb-dataset/
38+
tb-rerun-50/
39+
terminal-bench/
40+
evals/**/__pycache__/
3341

3442
# Scratch repro tests — never committed
3543
__repro/

‎src/provider/inference-dependencies.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { GROK_RESPONSES_PROVIDER } from "./grok-responses.js";
1919
import { withAnthropicCacheBreakpoint } from "./anthropic-cache-breakpoint.js";
2020
import { withReplaySanitizer } from "./replay-sanitizer.js";
2121
import { isPollOnlyPendingBatch } from "../subagent/poll-exempt.js";
22+
import { isIdempotentTaskBatch } from "../subagent/idempotent-task-exempt.js";
2223
import { OPENCODE_GO_PROVIDER_ID } from "../../packages/opencode-go/src/index.js";
2324
import { BIFROST_PROVIDER } from "./bifrost-adapter.js";
2425
import { OPENAI_RESPONSES_PROVIDER } from "./openai-responses.js";
@@ -101,7 +102,13 @@ export function createInferenceDependencies(): Promise<Dependencies> {
101102
.then((deps) => ({
102103
...deps,
103104
fetch: withCodexContentTypeRepair(deps.fetch),
104-
isPollOnlyPendingBatch,
105+
// Compose the first-party doom-loop exemptions into the single liveness
106+
// hook the vendored reactor already consumes: still-pending polls and
107+
// idempotent task bookkeeping both reset the repeat streak instead of
108+
// counting toward the fail-run threshold.
109+
isPollOnlyPendingBatch: (calls, results) =>
110+
isPollOnlyPendingBatch(calls, results) ||
111+
isIdempotentTaskBatch(calls, results),
105112
}));
106113
}
107114
return cached;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, test } from "bun:test";
2+
import type { ToolCall, ToolResult } from "@intx/types/runtime";
3+
import { isIdempotentTaskBatch } from "./idempotent-task-exempt.js";
4+
5+
function call(name: string, id = "c1"): ToolCall {
6+
return { id, name, arguments: {} };
7+
}
8+
9+
function taskResult(): ToolResult {
10+
return { callId: "c1", content: "{}" };
11+
}
12+
13+
describe("isIdempotentTaskBatch", () => {
14+
test("manage_tasks batch is exempt", () => {
15+
expect(isIdempotentTaskBatch([call("manage_tasks")], [taskResult()])).toBe(
16+
true,
17+
);
18+
});
19+
20+
test("todowrite alias is exempt", () => {
21+
expect(isIdempotentTaskBatch([call("todowrite")], [taskResult()])).toBe(
22+
true,
23+
);
24+
});
25+
26+
test("update_plan hidden alias is exempt", () => {
27+
expect(isIdempotentTaskBatch([call("update_plan")], [taskResult()])).toBe(
28+
true,
29+
);
30+
});
31+
32+
test("repeated task-only batches stay exempt", () => {
33+
expect(
34+
isIdempotentTaskBatch(
35+
[call("todowrite", "c1"), call("todowrite", "c2")],
36+
[taskResult(), { callId: "c2", content: "{}" }],
37+
),
38+
).toBe(true);
39+
});
40+
41+
test("a real tool in the batch is never exempt", () => {
42+
expect(
43+
isIdempotentTaskBatch(
44+
[call("todowrite"), call("run_shell")],
45+
[taskResult(), { callId: "c2", content: "{}" }],
46+
),
47+
).toBe(false);
48+
});
49+
50+
test("non-task tools are never exempt", () => {
51+
expect(isIdempotentTaskBatch([call("run_shell")], [taskResult()])).toBe(
52+
false,
53+
);
54+
});
55+
56+
test("empty batches are never exempt", () => {
57+
expect(isIdempotentTaskBatch([], [])).toBe(false);
58+
});
59+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { isSameTool } from "../agent/canonical-tool-name.js";
2+
import type { ToolCall, ToolResult } from "@intx/types/runtime";
3+
4+
/**
5+
* Doom-loop exemption for idempotent task bookkeeping. A batch is exempt only
6+
* when it is non-empty and every executed call is a task-bookkeeping tool
7+
* (`manage_tasks` — `isSameTool` folds the `todowrite` and `update_plan`
8+
* aliases onto that engine id).
9+
*
10+
* Repeating an identical task update is legitimate bookkeeping, not a runaway
11+
* loop: "finalize checklist" three times changes nothing and should not
12+
* abort an otherwise-finished run under the `fail-run` policy. This resets
13+
* the doom-loop streak (same mechanism as the poll exemption) so consecutive
14+
* task-only batches never accumulate toward the threshold, while any real
15+
* tool in the batch counts normally and always preserves the guard.
16+
*/
17+
export function isIdempotentTaskBatch(
18+
calls: readonly ToolCall[],
19+
_results: readonly ToolResult[],
20+
): boolean {
21+
if (calls.length === 0) return false;
22+
return calls.every((call) => isSameTool(call.name, "manage_tasks"));
23+
}

0 commit comments

Comments
 (0)