diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 44b472c2c..b11926ffa 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -1,7 +1,6 @@
# Changelog
## [Unreleased]
-
### Added
- Added `assignment` field to task progress and result objects to track the raw per-task assignment text separately from the full templated task
@@ -14,6 +13,8 @@
### Changed
+- Modified eager todo prelude to use hidden custom message type instead of visible developer message, preventing duplicate prompt text in session history
+- Updated eager todo prompt to remove dynamic user request injection, simplifying the template and preventing request repetition in displayed messages
- Modified eager todo enforcement to prepend the todo reminder to the first user turn instead of executing it as a separate synthetic turn, reducing unnecessary prompt calls
- Updated task rendering to display assignment text instead of full task template when available, reducing noise in progress and result displays
- Modified task section rendering to show trimmed assignment text without stripping context blocks, simplifying the display logic
diff --git a/packages/coding-agent/src/prompts/system/eager-todo.md b/packages/coding-agent/src/prompts/system/eager-todo.md
index 977369af4..350a30709 100644
--- a/packages/coding-agent/src/prompts/system/eager-todo.md
+++ b/packages/coding-agent/src/prompts/system/eager-todo.md
@@ -1,11 +1,6 @@
Before doing substantive work on the upcoming user request, create a comprehensive phased todo first.
-The todo **MUST** cover this request:
-
-{{userRequest}}
-
-
You **MUST** call `todo_write` first in this turn.
You **MUST** initialize the todo list with a single `replace` op.
You **MUST** cover the entire request from investigation through implementation and verification — not just the next immediate step.
@@ -14,4 +9,5 @@ You **MUST** keep task `content` to a short label (5-10 words). Put file paths,
You **MUST** keep exactly one task `in_progress` and all later tasks `pending`.
After the initial `todo_write` call succeeds, continue with the user's request in the same turn.
+Do not emit another `todo_write` call unless task state materially changed.
\ No newline at end of file
diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts
index 2138c8bb5..c5d120285 100644
--- a/packages/coding-agent/src/session/agent-session.ts
+++ b/packages/coding-agent/src/session/agent-session.ts
@@ -1959,7 +1959,7 @@ export class AgentSession {
return;
}
- const eagerTodoPrelude = !options?.synthetic ? this.#createEagerTodoPrelude(expandedText) : undefined;
+ const eagerTodoPrelude = !options?.synthetic ? this.#createEagerTodoPrelude() : undefined;
const userContent: (TextContent | ImageContent)[] = [{ type: "text", text: expandedText }];
if (options?.images) {
@@ -3515,7 +3515,7 @@ export class AgentSession {
}
}
- #createEagerTodoPrelude(userRequest: string): { message: AgentMessage; toolChoice: ToolChoice } | undefined {
+ #createEagerTodoPrelude(): { message: AgentMessage; toolChoice: ToolChoice } | undefined {
const eagerTodosEnabled = this.settings.get("todo.eager");
const todosEnabled = this.settings.get("todo.enabled");
if (!eagerTodosEnabled || !todosEnabled) {
@@ -3545,14 +3545,14 @@ export class AgentSession {
return undefined;
}
- const eagerTodoReminder = renderPromptTemplate(eagerTodoPrompt, {
- userRequest,
- });
+ const eagerTodoReminder = renderPromptTemplate(eagerTodoPrompt);
return {
message: {
- role: "developer",
- content: [{ type: "text", text: eagerTodoReminder }],
+ role: "custom",
+ customType: "eager-todo-prelude",
+ content: eagerTodoReminder,
+ display: false,
attribution: "agent",
timestamp: Date.now(),
},
diff --git a/packages/coding-agent/test/agent-session-eager-todo.test.ts b/packages/coding-agent/test/agent-session-eager-todo.test.ts
index efe6b0389..e9d7341ad 100644
--- a/packages/coding-agent/test/agent-session-eager-todo.test.ts
+++ b/packages/coding-agent/test/agent-session-eager-todo.test.ts
@@ -20,6 +20,7 @@ type ObservedPromptCall = {
toolChoice: string | undefined;
toolNames: string[];
messageRoles: AgentMessage["role"][];
+ messageTexts: string[];
lastMessageRole: AgentMessage["role"];
lastMessageText: string;
};
@@ -164,6 +165,7 @@ describe("AgentSession eager todo enforcement", () => {
toolChoice: getToolChoiceName(options?.toolChoice),
toolNames: (context.tools ?? []).map(tool => tool.name),
messageRoles: context.messages.map(message => message.role),
+ messageTexts: context.messages.map(message => getMessageText(message)),
lastMessageRole: lastMessage.role,
lastMessageText: getMessageText(lastMessage),
});
@@ -200,21 +202,24 @@ describe("AgentSession eager todo enforcement", () => {
tempDir.removeSync();
});
- it("prepends the eager todo reminder to the first real user turn", async () => {
+ it("prepends a hidden eager todo reminder without repeating the prompt text", async () => {
await session.prompt("list all work trees");
- const dumpText = session.formatSessionAsText();
-
expect(observedCalls).toHaveLength(1);
expect(observedCalls[0]).toEqual({
toolChoice: "todo_write",
toolNames: ["todo_write", "bash"],
- messageRoles: ["developer", "user"],
+ messageRoles: ["user", "user"],
+ messageTexts: [
+ expect.stringContaining("Before doing substantive work on the upcoming user request"),
+ "list all work trees",
+ ],
lastMessageRole: "user",
lastMessageText: "list all work trees",
});
- expect(dumpText).toContain("## Developer");
- expect(dumpText).toContain("Before doing substantive work on the upcoming user request");
+ expect(observedCalls[0]?.messageTexts.filter(text => text.includes("list all work trees"))).toHaveLength(1);
+ expect(observedCalls[0]?.messageTexts[0]).not.toContain("list all work trees");
+ expect(session.formatSessionAsText()).not.toContain("");
});
it("initializes todos once, then continues within the same user turn", async () => {
@@ -242,7 +247,11 @@ describe("AgentSession eager todo enforcement", () => {
expect(observedCalls[0]).toEqual({
toolChoice: "todo_write",
toolNames: ["todo_write", "bash"],
- messageRoles: ["developer", "user"],
+ messageRoles: ["user", "user"],
+ messageTexts: [
+ expect.stringContaining("Before doing substantive work on the upcoming user request"),
+ "list all work trees",
+ ],
lastMessageRole: "user",
lastMessageText: "list all work trees",
});