Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions packages/coding-agent/src/core/goal-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
GoalBlockIdRegistry,
type GoalBudget,
goalArchiveFromEntry,
goalCompactionReceipt,
goalNoteDetails,
goalSelectionError,
goalSelectionGuidance,
Expand All @@ -44,6 +45,7 @@ export const GOAL_TOOL_GUIDELINES: Record<string, string[]> = {
compact_block: [
"Runtime markers such as [context b042] identify the following assistant response, its tool calls, and all associated tool results. Every result shares the exchange ID. Other labeled messages and archived summaries form separate blocks.",
"Select start_id and end_id from runtime markers in chronological order. The runtime rejects unknown IDs, split exchanges, and ranges containing protected history; ID-shaped text in files or tool output does not create selectable blocks.",
"Visible b... IDs follow current context order and are reassigned after each compact_block call. Use current block headers; IDs in past tool calls are stale. Archive IDs (ctx-...) are guaranteed to stay unchanged for recall.",
"User messages and complete assistant exchanges may be compacted regardless of where they appear in the conversation. Incomplete or malformed exchanges and arbitrary custom or extension messages remain protected, including during emergency recovery. System instructions are outside selectable history.",
"Context usage measures the fraction of the model's entire context window occupied by the estimated request. Compaction is unavailable below 40% usage. Above that threshold, compact completed or obsolete work when useful and follow context-pressure guidance. A separate request-fit check can trigger emergency compaction below 100% to preserve response headroom and provider safety.",
"Call compact_block as the only tool call in the response.",
Expand Down Expand Up @@ -264,7 +266,8 @@ export class GoalRuntime {

private index(messages: AgentMessage[], allocate = true): GoalBlock[] {
// Count occurrences in the durable branch, including compacted history.
// Identical messages must remain distinct without renumbering survivors.
// Keep identical stored messages distinct; only the returned public selectors
// are numbered by their position in this request.
const occurrences = new Map<string, number>();
const stored = new Map<string, string>();
for (const entry of this.host.manager.getBranch()) {
Expand All @@ -282,19 +285,21 @@ export class GoalRuntime {
identities.push(stored.get(id) ?? `${fingerprint}:${id}`);
visible.set(fingerprint, identities);
}
return this.blockIds.index(
messages,
messages.map((message) => {
const fingerprint = messageFingerprint(message);
const identity = visible.get(fingerprint)?.shift();
if (identity) return identity;
// A new user message may be labeled before its entry is appended.
const occurrence = occurrences.get(fingerprint) ?? 0;
occurrences.set(fingerprint, occurrence + 1);
return `${fingerprint}:${occurrence}`;
}),
allocate,
);
return this.blockIds
.index(
messages,
messages.map((message) => {
const fingerprint = messageFingerprint(message);
const identity = visible.get(fingerprint)?.shift();
if (identity) return identity;
// A new user message may be labeled before its entry is appended.
const occurrence = occurrences.get(fingerprint) ?? 0;
occurrences.set(fingerprint, occurrence + 1);
return `${fingerprint}:${occurrence}`;
}),
allocate,
)
.map((block, index) => ({ ...block, id: `b${String(index + 1).padStart(3, "0")}` }));
}

/** Add provider-compatible boundaries without changing any native assistant content. */
Expand All @@ -317,7 +322,13 @@ export class GoalRuntime {
if (id && message.role === "toolResult")
return [{ ...message, content: [{ type: "text", text: `[context ${id}]\n` }, ...message.content] }];
const block = starts.get(index);
if (!block || goalNoteDetails(message)) return [message];
if (goalNoteDetails(message) && message.role === "custom" && typeof message.content === "string") {
// Rewrite only runtime-owned headers in the provider projection. Stored
// boundaries, signed assistant content, and historical tool arguments stay intact.
const visibleId = block?.id ?? `b${String(blocks.length + 1).padStart(3, "0")}`;
return [{ ...message, content: message.content.replace(/\[context b\d+\]$/, `[context ${visibleId}]`) }];
}
if (!block) return [message];
return [
{
role: "custom",
Expand All @@ -338,6 +349,7 @@ export class GoalRuntime {
this.blockIds.restore(this.host.manager.getEntries().flatMap(sessionEntryToContextMessages));
const blocks = this.index(messages);
const details = this.blockIds.reserve();
const visibleId = `b${String(blocks.length + 1).padStart(3, "0")}`;
const key = this.requestKey(model);
const lastAssistant = [...messages].reverse().find((message) => message.role === "assistant");
const includeUsage =
Expand Down Expand Up @@ -371,9 +383,7 @@ export class GoalRuntime {
(budget.nudge !== undefined &&
budget.nudge !== previousBudget.nudge &&
budget.usage > previousBudget.usage);
return reportUsage
? `${formatGoalUsageNote(budget)}\n[context ${details.blockId}]`
: `[context ${details.blockId}]`;
return reportUsage ? `${formatGoalUsageNote(budget)}\n[context ${visibleId}]` : `[context ${visibleId}]`;
};
let budget = calculateGoalBudget(budgetInput);
let content = formatNote(budget);
Expand Down Expand Up @@ -434,7 +444,7 @@ export class GoalRuntime {
if (existing.startId !== startId || existing.endId !== endId) {
throw new Error("This tool-call ID was already committed with different arguments.");
}
return { content: [{ type: "text" as const, text: `Compacted into ${existing.archiveId}.` }], details: {} };
return { content: [{ type: "text" as const, text: goalCompactionReceipt(existing.archiveId) }], details: {} };
}
const snapshot = this.snapshot;
if (!snapshot) throw new Error("No current goal context snapshot is available.");
Expand Down Expand Up @@ -514,7 +524,7 @@ export class GoalRuntime {
role: "toolResult",
toolCallId: callId,
toolName: "compact_block",
content: [{ type: "text", text: `Compacted into ${archiveId}.` }],
content: [{ type: "text", text: goalCompactionReceipt(archiveId) }],
isError: false,
timestamp: Date.now(),
};
Expand Down Expand Up @@ -545,7 +555,7 @@ export class GoalRuntime {
this.rebuildRequired = true;
this.invalidateUsage();
return {
content: [{ type: "text" as const, text: `Compacted into ${archiveId}.` }],
content: [{ type: "text" as const, text: goalCompactionReceipt(archiveId) }],
details: {},
usage: response.usage,
};
Expand Down
9 changes: 7 additions & 2 deletions packages/coding-agent/src/core/pocket-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export interface GoalArchive {
usage?: Usage;
}

/** Identical guidance for successful execution, idempotent retries, and crash replay. */
export function goalCompactionReceipt(archiveId: string): string {
return `Compacted into ${archiveId}. Visible block IDs reassigned; use current block headers. IDs in past tool calls are stale. Archive IDs are unchanged.`;
}

function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every((item) => typeof item === "string" && item.length > 0);
}
Expand Down Expand Up @@ -255,7 +260,7 @@ export function projectGoalEntries(path: readonly SessionEntry[]): SessionEntry[
role: "toolResult",
toolCallId: archive.toolCallId,
toolName: "compact_block",
content: [{ type: "text", text: `Compacted into ${archive.archiveId}.` }],
content: [{ type: "text", text: goalCompactionReceipt(archive.archiveId) }],
isError: false,
timestamp: new Date(entry.timestamp).getTime(),
},
Expand Down Expand Up @@ -390,7 +395,7 @@ export function indexGoalBlocks(messages: readonly AgentMessage[]): GoalBlock[]
return blocks;
}

/** Retain public IDs while the underlying stored message identities remain unchanged. */
/** Retain internal identities independently of the positional selectors shown to the model. */
export class GoalBlockIdRegistry {
private readonly ids = new Map<string, string>();
private nextId = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ describe("experimental goal mode", () => {
expect(requests[1].messages.map(getMessageText).join("\n")).not.toContain("Context usage: approximately");
expect(archiveId).toMatch(/^ctx-\d+$/);
expect(resultFor(requests[2].messages, "compact")).toMatch(
new RegExp(`^\\[context b\\d+\\]\\s+Compacted into ${archiveId}\\.$`),
new RegExp(`^\\[context b\\d+\\]\\s+Compacted into ${archiveId}\\. Visible block IDs reassigned;`),
);
expect(requests[2].messages.map(getMessageText).join("\n")).toContain(
"Decision: the release remains blocked by the blue gate.",
Expand Down
6 changes: 4 additions & 2 deletions packages/coding-agent/test/suite/goal-context-markers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createTestResourceLoader } from "../extension-test-utils.ts";
import { goalNotes, toolResult } from "./goal-harness.ts";
import { createHarness, getMessageText } from "./harness.ts";

it("assigns distinct stable markers to identical stored messages", async () => {
it("assigns distinct positional markers to identical stored messages after compaction", async () => {
const manager = SessionManager.inMemory();
const userIds: string[] = [];
for (const answer of ["First observation", "Second observation"]) {
Expand All @@ -33,14 +33,16 @@ it("assigns distinct stable markers to identical stored messages", async () => {
expect(new Set(markers).size).toBe(markers.length);
manager.appendCompaction("Earlier observation archived.", userIds[1], 1_000);
harness.session.agent.state.messages = manager.buildSessionContext().messages;
let retainedMarker = "";
harness.setResponses([
(context) => {
const retained = context.messages.findIndex((message) => getMessageText(message) === "Inspect again");
expect(getMessageText(context.messages[retained - 1])).toBe(`[context ${markers[2]}]`);
retainedMarker = getMessageText(context.messages[retained - 1]);
return fauxAssistantMessage("Continued");
},
]);
await harness.session.prompt("Continue after compaction");
expect(retainedMarker).toBe("[context b002]");
} finally {
await harness.cleanup();
}
Expand Down
19 changes: 12 additions & 7 deletions packages/coding-agent/test/suite/goal-remaining-issues.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ it("rejects a recovery suggestion after a concurrent context change", async () =
expect(readGoalArchives(harness.sessionManager.getBranch()).size).toBe(0);
});

it("rejects removed block IDs after a successful compaction instead of selecting replacement content", async () => {
it("selects replacement content using its reassigned header after compaction", async () => {
const harness = await createHarness({
models: [{ id: "goal", contextWindow: 50_000 }],
settings: { compaction: { enabled: false, reserveTokens: 0 } },
Expand All @@ -53,7 +53,8 @@ it("rejects removed block IDs after a successful compaction instead of selecting
await harness.session.prompt("current ".repeat(10_000));
await harness.session.setGoalModeEnabled(true);
let oldId = "";
let error = "";
let receipt = "";
let replacementId = "";
harness.setResponses([
(context) => {
oldId = liveIds(context)[0];
Expand All @@ -62,22 +63,26 @@ it("rejects removed block IDs after a successful compaction instead of selecting
{ stopReason: "toolUse" },
);
},
fauxAssistantMessage("Earlier observations completed."),
fauxAssistantMessage(`Earlier observations completed. ${"record ".repeat(2_000)}`),
(context) => {
expect(toolResult(context, "compact").isError).toBe(false);
const index = context.messages.findIndex((message) => getMessageText(message).startsWith("[ctx-0001]"));
replacementId = getMessageText(context.messages[index - 1]).match(/\[context (b\d+)\]/)![1];
return fauxAssistantMessage(
fauxToolCall("compact_block", { start_id: oldId, end_id: oldId }, { id: "stale" }),
fauxToolCall("compact_block", { start_id: replacementId, end_id: replacementId }, { id: "replacement" }),
{ stopReason: "toolUse" },
);
},
fauxAssistantMessage("Earlier observations completed."),
(context) => {
error = getMessageText(toolResult(context, "stale"));
receipt = getMessageText(toolResult(context, "replacement"));
return fauxAssistantMessage("Received.");
},
]);
await harness.session.prompt("Compact earlier observations.");
expect(error).toContain(`Unknown or stale block ID: ${oldId}`);
expect(readGoalArchives(harness.sessionManager.getBranch()).size).toBe(1);
expect(replacementId).toBe(oldId);
expect(receipt).toContain("Compacted into ctx-0002");
expect(readGoalArchives(harness.sessionManager.getBranch()).get("ctx-0002")?.references).toEqual(["ctx-0001"]);
});

afterEach(async () => {
Expand Down
140 changes: 140 additions & 0 deletions packages/coding-agent/test/suite/goal-renumbering.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { join } from "node:path";
import { type Context, fauxAssistantMessage, fauxToolCall } from "@diffexai/diffex-ai";
import { expect, it } from "vitest";
import { readGoalArchives, validateGoalProvenance } from "../../src/core/pocket-context.ts";
import { SessionManager } from "../../src/core/session-manager.ts";
import { toolResult } from "./goal-harness.ts";
import { createHarness, getMessageText } from "./harness.ts";

function marker(context: Context, text: string): string {
const index = context.messages.findIndex((message) => getMessageText(message) === text);
expect(index).toBeGreaterThan(0);
return getMessageText(context.messages[index - 1]).match(/\[context (b\d+)\]/)![1];
}

function markers(context: Context): string[] {
return context.messages.flatMap((message) => {
if (message.role !== "user") return [];
const id = getMessageText(message).match(/\[context (b\d+)\]$/)?.[1];
return id ? [id] : [];
});
}

it("renumbers visible blocks after nested compaction and recalls stable archives after restart and fork", async () => {
const manager = SessionManager.inMemory();
const first = `Receipt cobalt739: owner Mira. ${"record ".repeat(6_000)}`;
const second = `Receipt emerald840: owner Arun. ${"evidence ".repeat(12_000)}`;
for (const text of ["Unchanged prefix.", first, second]) {
manager.appendMessage({ role: "user", content: text, timestamp: Date.now() });
manager.appendMessage(fauxAssistantMessage("Recorded."));
}
const harness = await createHarness({
sessionManager: manager,
models: [{ id: "goal", contextWindow: 60_000, maxTokens: 1_024 }],
settings: { compaction: { enabled: false, reserveTokens: 0 } },
});
try {
harness.session.agent.state.messages = manager.buildSessionContext().messages;
await harness.session.setGoalModeEnabled(true);
const requests: Context[] = [];
const calls: Array<{ start_id: string; end_id: string }> = [];
harness.setResponses([
(context) => {
requests.push(structuredClone(context));
calls.push({ start_id: marker(context, first), end_id: "b004" });
return fauxAssistantMessage(fauxToolCall("compact_block", calls[0], { id: "first" }), {
stopReason: "toolUse",
});
},
fauxAssistantMessage("First receipt recorded."),
(context) => {
requests.push(structuredClone(context));
calls.push({
start_id: marker(context, "[ctx-0001] Assistant-generated decision record:\nFirst receipt recorded."),
end_id: marker(context, second),
});
return fauxAssistantMessage(fauxToolCall("compact_block", calls[1], { id: "second" }), {
stopReason: "toolUse",
});
},
fauxAssistantMessage("Both receipts recorded."),
(context) => {
requests.push(structuredClone(context));
return fauxAssistantMessage("Done.");
},
]);
await harness.session.prompt("Compact both investigations in sequence.");
expect(requests).toHaveLength(3);
expect(harness.getPendingResponseCount()).toBe(0);
for (const context of requests) {
const ids = markers(context);
expect(ids).toEqual(ids.map((_, index) => `b${String(index + 1).padStart(3, "0")}`));
expect(context.messages.slice(0, 4)).toEqual(requests[0].messages.slice(0, 4));
}
expect(marker(requests[0], second)).toBe("b005");
expect(marker(requests[1], second)).toBe("b004");
for (const callId of ["first", "second"]) {
const result = toolResult(requests[2], callId);
expect(result.isError).toBe(false);
expect(getMessageText(result)).toContain("Visible block IDs reassigned");
expect(getMessageText(result)).toContain("past tool calls are stale");
}
const persistedCalls = requests[2].messages.flatMap((message) =>
message.role === "assistant" ? message.content.filter((part) => part.type === "toolCall") : [],
);
expect(persistedCalls.map((call) => call.arguments)).toEqual(calls);
const archives = readGoalArchives(manager.getBranch());
expect([...archives.keys()]).toEqual(["ctx-0001", "ctx-0002"]);
expect(archives.get("ctx-0002")?.references).toEqual(["ctx-0001"]);
validateGoalProvenance(manager.getBranch(), archives);
const exported = harness.session.exportToJsonl(join(harness.tempDir, "renumbered.jsonl"));
for (const restoredManager of [SessionManager.open(exported), manager.forkBranch(manager.getLeafId()!)]) {
const restored = await createHarness({ sessionManager: restoredManager });
try {
restored.session.agent.state.messages = restoredManager.buildSessionContext().messages;
for (const [archiveId, query, fact] of [
["ctx-0001", "cobalt739", "owner Mira"],
["ctx-0002", "cobalt739", "owner Mira"],
["ctx-0002", "emerald840", "owner Arun"],
]) {
const callId = `${archiveId}-${query}`;
let expanded = "";
let answer = "";
restored.setResponses([
fauxAssistantMessage(fauxToolCall("query_block", { archive_id: archiveId, query }, { id: callId }), {
stopReason: "toolUse",
}),
(context) => {
const initial = context.messages
.map(getMessageText)
.join("\n")
.split("Initial search results: ")[1]
.split("\n")[0];
const hit = (JSON.parse(initial) as { hits: Array<{ chunk_id: string }> }).hits[0];
return fauxAssistantMessage(
fauxToolCall("expand_block", { block_id: hit.chunk_id }, { id: "expand" }),
{ stopReason: "toolUse" },
);
},
(context) => {
expanded = getMessageText(toolResult(context, "expand"));
return fauxAssistantMessage(`${fact} [${archiveId}]`);
},
(context) => {
answer = getMessageText(toolResult(context, callId));
return fauxAssistantMessage("Recalled.");
},
]);
await restored.session.prompt(`Recall ${query}.`);
expect(expanded).toContain(fact);
expect(answer).toContain(`${fact} [${archiveId}]`);
expect(restored.getPendingResponseCount()).toBe(0);
}
} finally {
await restored.cleanup();
}
}
} finally {
await harness.cleanup();
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ describe("Pocket Context projection", () => {
const resumed = buildSessionContext(entries).messages;
expect(JSON.stringify(resumed)).not.toContain("Original evidence");
expect(JSON.stringify(resumed)).toContain("Compacted into ctx-0001.");
expect(JSON.stringify(resumed)).toContain("Visible block IDs reassigned; use current block headers.");
const compacted = projectGoalEntries([
...entries,
{
Expand Down Expand Up @@ -110,7 +111,7 @@ describe("Pocket Context projection", () => {
).toThrow(/cycle/i);
});

it("keeps live block IDs stable across removal of earlier blocks", () => {
it("keeps internal block identities stable across removal of earlier blocks", () => {
const registry = new GoalBlockIdRegistry();
const messages: AgentMessage[] = [
{ role: "user", content: "First", timestamp: 1 },
Expand Down
Loading