Skip to content

Commit d9be39a

Browse files
committed
fix(agent): single-source grok residual, drop duplicate promptResidual seam
1 parent 2350730 commit d9be39a

3 files changed

Lines changed: 48 additions & 35 deletions

File tree

‎src/agent/grok-residual.test.ts‎

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { describe, expect, it } from "bun:test";
2-
import { promptResidual } from "./model-family-policy.js";
2+
import { GROK_PROMPT_RESIDUAL } from "./model-family-policy.js";
33
import {
44
buildGrokLeafAntiThrashNote,
55
buildSubAgentSystemPrompt,
66
} from "./prompts.js";
7+
import { shouldApplyGrokAntiThrash } from "../subagent/provider-family.js";
78

89
// The three Grok ceremony lines (CL-7768 Design, merged by CL-8296): no git,
910
// no pre-plan, verify once.
@@ -19,28 +20,47 @@ function countOccurrences(haystack: string, needle: string): number {
1920

2021
describe("grok ceremony merge (CL-8296)", () => {
2122
it("exposes a single grok residual with each ceremony line exactly once", () => {
22-
const residual = promptResidual("grok");
23-
expect(residual).toContain("Finish bias (xAI / Grok worker):");
23+
expect(GROK_PROMPT_RESIDUAL).toContain("Finish bias (xAI / Grok worker):");
2424
for (const line of CEREMONY_LINES) {
25-
expect(countOccurrences(residual, line)).toBe(1);
25+
expect(countOccurrences(GROK_PROMPT_RESIDUAL, line)).toBe(1);
2626
}
2727
});
2828

2929
it("keeps the don't re-read line exactly once — no duplicate", () => {
30-
const residual = promptResidual("grok");
31-
expect(countOccurrences(residual, "re-open paths you already read")).toBe(
32-
1,
33-
);
30+
expect(
31+
countOccurrences(GROK_PROMPT_RESIDUAL, "re-open paths you already read"),
32+
).toBe(1);
3433
});
3534

36-
it("is grok-only: every other family resolves to an empty residual", () => {
37-
expect(promptResidual("default")).toBe("");
38-
expect(promptResidual("kimi")).toBe("");
39-
expect(promptResidual("muse")).toBe("");
35+
it("is grok-only: the finish-bias gate fires for grok leaves alone", () => {
36+
expect(
37+
shouldApplyGrokAntiThrash({
38+
providerName: "xai/default",
39+
model: "grok-4.6",
40+
orchestrator: false,
41+
}),
42+
).toBe(true);
43+
for (const input of [
44+
{ providerName: "anthropic", model: "claude-sonnet-4" },
45+
{ providerName: "moonshot", model: "kimi-k2" },
46+
{ providerName: "opencode-go", model: "muse-spark-1.3-contributor" },
47+
{ providerName: "openai", model: "gpt-4.1" },
48+
] as const) {
49+
expect(shouldApplyGrokAntiThrash({ ...input, orchestrator: false })).toBe(
50+
false,
51+
);
52+
}
53+
expect(
54+
shouldApplyGrokAntiThrash({
55+
providerName: "xai/default",
56+
model: "grok-4.6",
57+
orchestrator: true,
58+
}),
59+
).toBe(false);
4060
});
4161

4262
it("buildGrokLeafAntiThrashNote is the same single residual (one source of truth)", () => {
43-
expect(buildGrokLeafAntiThrashNote()).toBe(promptResidual("grok"));
63+
expect(buildGrokLeafAntiThrashNote()).toBe(GROK_PROMPT_RESIDUAL);
4464
});
4565

4666
it("the assembled grok worker prompt carries the merged residual exactly once", () => {

‎src/agent/model-family-policy.ts‎

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,17 @@ const MUSE_POLICY: Omit<ModelFamilyPolicy, "family"> = {
129129
toolDisciplineRules: MUSE_TOOL_DISCIPLINE_RULES,
130130
};
131131

132-
// Single grok prompt residual (CL-8296): the finish-bias bullets plus the
133-
// three ceremony lines from the CL-7768 design (no git, no pre-plan, verify
134-
// once), merged into one block with no line twice. The don't re-read idea
135-
// appears exactly once (the "re-open paths" bullet) — it is not repeated.
136-
// Grok-only: detectModelFamily has no glm family, so per the <30min rule no
137-
// GLM row ships here. The text lives here once; buildGrokLeafAntiThrashNote
138-
// (prompts.ts) returns it verbatim, so the prompt carries exactly one grok
139-
// residual and the sibling CL-8297 hook resolves to the same block.
140-
const GROK_PROMPT_RESIDUAL = [
132+
// Single grok finish-bias + ceremony residual (CL-8296): the finish-bias
133+
// bullets plus the three ceremony lines from the CL-7768 design (no git, no
134+
// pre-plan, verify once), merged into one block with no line twice. The don't
135+
// re-read idea appears exactly once (the "re-open paths" bullet) — it is not
136+
// repeated. Grok-only: detectModelFamily has no glm family, so per the <30min
137+
// rule no GLM row ships here. The text lives here once; exported for
138+
// buildGrokLeafAntiThrashNote (prompts.ts), which returns it verbatim, so the
139+
// prompt carries exactly one copy. This is a different block from the CL-8297
140+
// tool-budget hook (GROK_TOOL_BUDGET_RESIDUAL, surfaced via the
141+
// ModelFamilyPolicy.promptResidual field): grok leaves carry both, each once.
142+
export const GROK_PROMPT_RESIDUAL = [
141143
"Finish bias (xAI / Grok worker):",
142144
"- Once you can answer the dispatch brief, prefer the structured report over another speculative tool call.",
143145
"- If the next call would only re-open paths you already read, write the report instead.",
@@ -148,15 +150,6 @@ const GROK_PROMPT_RESIDUAL = [
148150
"- Verify with the test command once at the end, not after every edit.",
149151
].join("\n");
150152

151-
/**
152-
* Minimal per-family prompt residual hook. The sibling CL-8297 owns the
153-
* canonical hook; this local copy keeps the branch self-contained so tests
154-
* pass standalone — the two reconcile when CL-8297 lands.
155-
*/
156-
export function promptResidual(family: ModelFamily): string {
157-
return family === "grok" ? GROK_PROMPT_RESIDUAL : "";
158-
}
159-
160153
export function resolveModelFamilyPolicy(input: {
161154
providerName: string;
162155
model?: string;

‎src/agent/prompts.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
buildWorkerContract,
1212
buildWorkerToolNames,
1313
} from "./worker-contract.js";
14-
import { promptResidual } from "./model-family-policy.js";
14+
import { GROK_PROMPT_RESIDUAL } from "./model-family-policy.js";
1515

1616
// Advertise every gated core tool when the caller has no session-start facts
1717
// (tests, ad-hoc prompt previews) — except wait_agents, which is mount-gated:
@@ -512,12 +512,12 @@ export function buildSubAgentReportContract(
512512
// Tiny residual for Grok/xAI workers: mining showed higher tools-only thrash
513513
// than Codex on the same harness. Shared thrash harness + spawn contracts do
514514
// the structural work; this is only a finish-bias nudge, not a full rewrite.
515-
// Single source of truth is the promptResidual("grok") block in
515+
// Single source of truth is the GROK_PROMPT_RESIDUAL block in
516516
// model-family-policy.ts (CL-8296 merged the three ceremony lines into it);
517-
// this composes that block verbatim so the prompt carries one grok residual
517+
// this returns that block verbatim so the prompt carries one grok residual
518518
// with no line twice.
519519
export function buildGrokLeafAntiThrashNote(): string {
520-
return promptResidual("grok");
520+
return GROK_PROMPT_RESIDUAL;
521521
}
522522

523523
export function buildSubAgentSystemPrompt(

0 commit comments

Comments
 (0)