diff --git a/apps/cli/src/application/assistant/instructions.ts b/apps/cli/src/application/assistant/instructions.ts index 063d7002..3966a5e7 100644 --- a/apps/cli/src/application/assistant/instructions.ts +++ b/apps/cli/src/application/assistant/instructions.ts @@ -22,6 +22,7 @@ Always consult this catalog first so you load the right skills before taking act ## Task tracking - Maintain a durable todo list for multi-step efforts using the \`todoList\`, \`todoWrite\`, and \`todoUpdate\` builtin tools (state persists only within this copilot session). +- Before delivering your final response, verify every todo is marked \`done\` or \`blocked\`—use \`todoUpdate\` to reflect progress or issues. - Treat the text returned by those tools as internal guidance—never echo these reminders to the user verbatim. ## Execution reminders diff --git a/apps/cli/src/application/lib/agent.ts b/apps/cli/src/application/lib/agent.ts index d9d0c70b..e528b0fc 100644 --- a/apps/cli/src/application/lib/agent.ts +++ b/apps/cli/src/application/lib/agent.ts @@ -13,6 +13,7 @@ import { LlmStepStreamEvent } from "../entities/llm-step-events.js"; import { execTool } from "./exec-tool.js"; import { RunEvent } from "../entities/run-events.js"; import { BuiltinTools } from "./builtin-tools.js"; +import { readTodoState, pushTodoState, popTodoState } from "./todo-store.js"; import { collectSystemReminders } from "../assistant/reminders/manager.js"; export async function mapAgentTool(t: z.infer): Promise { @@ -237,6 +238,8 @@ export async function* streamAgentTurn(opts: { messages: z.infer; }): AsyncGenerator, void, unknown> { const { agent, messages } = opts; + pushTodoState(); + try { // set up tools const tools: ToolSet = {}; @@ -350,9 +353,21 @@ export async function* streamAgentTurn(opts: { continue; } + const completionReminder = await outstandingTodosReminder(); + if (completionReminder) { + messages.push({ + role: "system", + content: completionReminder, + }); + continue; + } + // otherwise, break return; } +} finally { + popTodoState(); +} } async function* streamLlm( @@ -444,6 +459,15 @@ function attachRemindersToResult(result: any, reminders: string[]) { systemReminders: reminders, }; } +async function outstandingTodosReminder(): Promise { + const state = await readTodoState(); + const remaining = state.todos.filter(todo => todo.status !== "done" && todo.status !== "blocked"); + if (remaining.length === 0) { + return null; + } + return `\nBefore concluding, finish every todo via the todo tools or mark it as blocked if an issue prevents completion. Outstanding items: ${JSON.stringify(remaining)}\n`; +} + export const MappedToolCall = z.object({ toolCall: ToolCallPart, agentTool: ToolAttachment, diff --git a/apps/cli/src/application/lib/builtin-tools.ts b/apps/cli/src/application/lib/builtin-tools.ts index 125d8c3b..9e487df2 100644 --- a/apps/cli/src/application/lib/builtin-tools.ts +++ b/apps/cli/src/application/lib/builtin-tools.ts @@ -275,7 +275,7 @@ export const BuiltinTools: z.infer = { }, todoList: { - description: 'Return the durable todo list for the current session', + description: 'Return the todo list for the current copilot session', inputSchema: z.object({}), execute: async () => { const state = await readTodoState();