Skip to content

Commit 4970013

Browse files
committed
compaction: extract buildIdentityReinforcement helper
* Extract inline system-reminder ternary into a named, exported buildIdentityReinforcement() function that constructs identity reinforcement XML from agent name and definition * Include agent description (or "a specialized agent" fallback) and behavioral constraints in the reminder to strengthen post-compaction role anchoring beyond the previous generic message * Add 7 unit tests covering Properties 1-5 and 10: custom agent with description, fallback for missing/empty description, native agent exclusion, undefined source safety, no-prompt agent, and return type verification * Bead opencode-x10.1 — passed all 3 judges on first review cycle
1 parent f765733 commit 4970013

2 files changed

Lines changed: 69 additions & 7 deletions

File tree

packages/opencode/src/session/compaction.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ export interface Interface {
5656

5757
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
5858

59+
export function buildIdentityReinforcement(agentName: string, source: Agent.Info | undefined): string | undefined {
60+
if (!source?.prompt || source?.native) return undefined
61+
const description = source?.description || "a specialized agent"
62+
return `<system-reminder>\nYou are the "${agentName}" agent.\nRole: ${description}\nYour role and constraints from your system prompt still apply after this compaction.\nContinue performing your designated role. Do not switch to code implementation or deviate from your assigned responsibilities.\n</system-reminder>`
63+
}
64+
5965
export const layer: Layer.Layer<
6066
Service,
6167
never,
@@ -222,8 +228,7 @@ When constructing the summary, try to stick to this template:
222228
const system: string[] = []
223229
if (source?.prompt) {
224230
const max = 4000
225-
const truncated =
226-
source.prompt.length > max ? source.prompt.slice(0, max) + "\n[...truncated]" : source.prompt
231+
const truncated = source.prompt.length > max ? source.prompt.slice(0, max) + "\n[...truncated]" : source.prompt
227232
system.push(truncated)
228233
}
229234
const msgs = structuredClone(messages)
@@ -314,10 +319,7 @@ When constructing the summary, try to stick to this template:
314319

315320
if (result === "continue" && input.auto) {
316321
// Inject post-compaction agent identity reminder for specialized agents
317-
const reminder =
318-
source?.prompt && !source?.native
319-
? `<system-reminder>You are the "${userMessage.agent}" agent. Your role and constraints from your system prompt still apply after this compaction. Do not deviate from your assigned role.</system-reminder>`
320-
: undefined
322+
const reminder = buildIdentityReinforcement(userMessage.agent, source)
321323
if (replay) {
322324
const original = replay.info
323325
const replayMsg = yield* session.updateMessage({

packages/opencode/test/session/compaction.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Bus } from "../../src/bus"
77
import { Config } from "../../src/config"
88
import { Agent } from "../../src/agent/agent"
99
import { LLM } from "../../src/session/llm"
10-
import { SessionCompaction } from "../../src/session/compaction"
10+
import { SessionCompaction, buildIdentityReinforcement } from "../../src/session/compaction"
1111
import { Token } from "../../src/util"
1212
import { Instance } from "../../src/project/instance"
1313
import { Log } from "../../src/util"
@@ -1828,3 +1828,63 @@ describe("session.compaction.agentAware", () => {
18281828
})
18291829
})
18301830
})
1831+
1832+
describe("buildIdentityReinforcement", () => {
1833+
function makeAgent(overrides: Partial<Agent.Info> = {}): Agent.Info {
1834+
return {
1835+
name: "test-agent",
1836+
mode: "primary",
1837+
permission: [],
1838+
options: {},
1839+
...overrides,
1840+
}
1841+
}
1842+
1843+
test("custom agent with description returns reinforcement containing name and description", () => {
1844+
const source = makeAgent({ prompt: "You are a reviewer", description: "a code review specialist", native: false })
1845+
const result = buildIdentityReinforcement("reviewer", source)
1846+
expect(result).toBeDefined()
1847+
expect(result).toContain("reviewer")
1848+
expect(result).toContain("a code review specialist")
1849+
expect(result).toContain("<system-reminder>")
1850+
expect(result).toContain("</system-reminder>")
1851+
})
1852+
1853+
test("custom agent without description falls back to generic description", () => {
1854+
const source = makeAgent({ prompt: "You are a reviewer", native: false })
1855+
const result = buildIdentityReinforcement("reviewer", source)
1856+
expect(result).toBeDefined()
1857+
expect(result).toContain("reviewer")
1858+
expect(result).toContain("a specialized agent")
1859+
})
1860+
1861+
test("custom agent with empty description falls back to generic description", () => {
1862+
const source = makeAgent({ prompt: "You are a reviewer", description: "", native: false })
1863+
const result = buildIdentityReinforcement("reviewer", source)
1864+
expect(result).toBeDefined()
1865+
expect(result).toContain("a specialized agent")
1866+
})
1867+
1868+
test("native agent returns undefined", () => {
1869+
const source = makeAgent({ prompt: "Built-in prompt", native: true })
1870+
const result = buildIdentityReinforcement("build", source)
1871+
expect(result).toBeUndefined()
1872+
})
1873+
1874+
test("undefined source returns undefined without throwing", () => {
1875+
const result = buildIdentityReinforcement("ghost", undefined)
1876+
expect(result).toBeUndefined()
1877+
})
1878+
1879+
test("agent without prompt returns undefined", () => {
1880+
const source = makeAgent({ native: false })
1881+
const result = buildIdentityReinforcement("reviewer", source)
1882+
expect(result).toBeUndefined()
1883+
})
1884+
1885+
test("returns plain string, not modifying system prompt inputs", () => {
1886+
const source = makeAgent({ prompt: "You are a reviewer", description: "reviewer role", native: false })
1887+
const result = buildIdentityReinforcement("reviewer", source)
1888+
expect(typeof result).toBe("string")
1889+
})
1890+
})

0 commit comments

Comments
 (0)