From 746d4130822f0cc4f1dcb912cdfb57cae666c6ad Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 22 Jan 2026 21:46:44 +0700 Subject: [PATCH] refactor(server): improve self-identification instructions - Made the timing clearer: "Immediately after your first message where you understand the task and decide to do multi-step work" - Added explicit "do not repeat" constraint to prevent over-calling - Made set_branch instruction dynamic based on whether cwd looks like a Paseo worktree (contains .paseo/worktrees) - When in a worktree, explicitly tell the agent to call set_branch - When not in a worktree, keep the cautious "only if certain" language --- .../src/server/agent/mcp-server.test.ts | 4 +- .../server/src/server/agent/mcp-server.ts | 16 ++++---- .../server/agent/providers/claude-agent.ts | 2 +- .../server/agent/providers/codex-mcp-agent.ts | 2 +- .../agent/self-identification-instructions.ts | 37 +++++++++++++++---- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index fd8421899..5a2ce5ff9 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -48,7 +48,7 @@ function createTestDeps(): TestDeps { describe("create_agent MCP tool", () => { const logger = createTestLogger(); - it("requires a concise title no longer than 40 characters", async () => { + it("requires a concise title no longer than 60 characters", async () => { const { agentManager, agentRegistry } = createTestDeps(); const server = await createAgentMcpServer({ agentManager, agentRegistry, logger }); const tool = (server as any)._registeredTools["create_agent"]; @@ -64,7 +64,7 @@ describe("create_agent MCP tool", () => { const tooLong = await tool.inputSchema.safeParseAsync({ cwd: "/tmp/repo", initialMode: "default", - title: "x".repeat(41), + title: "x".repeat(61), }); expect(tooLong.success).toBe(false); expect(tooLong.error.issues[0].path).toEqual(["title"]); diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 303bf8786..1b24358e7 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -281,9 +281,9 @@ export async function createAgentMcpServer( .string() .trim() .min(1, "Title is required") - .max(40, "Title must be 40 characters or fewer") + .max(60, "Title must be 60 characters or fewer") .describe( - "Short descriptive title (<= 40 chars) summarizing the agent's focus. Use a single concise sentence that fits on mobile." + "Short descriptive title (<= 60 chars) summarizing the agent's focus." ), agentType: AgentProviderEnum.optional().describe( "Optional agent implementation to spawn. Defaults to 'claude'." @@ -313,9 +313,9 @@ export async function createAgentMcpServer( .string() .trim() .min(1, "Title is required") - .max(40, "Title must be 40 characters or fewer") + .max(60, "Title must be 60 characters or fewer") .describe( - "Short descriptive title (<= 40 chars) summarizing the agent's focus. Use a single concise sentence that fits on mobile." + "Short descriptive title (<= 60 chars) summarizing the agent's focus." ), agentType: AgentProviderEnum.optional().describe( "Optional agent implementation to spawn. Defaults to 'claude'." @@ -906,8 +906,8 @@ export async function createAgentMcpServer( title: z .string() .min(1) - .max(40) - .describe("Short descriptive title (<= 40 chars)."), + .max(60) + .describe("Short descriptive title (<= 60 chars)."), }, outputSchema: { success: z.boolean(), @@ -931,10 +931,10 @@ export async function createAgentMcpServer( if (!normalizedTitle) { throw new AgentMcpToolError("NOT_ALLOWED", "Title cannot be empty"); } - if (normalizedTitle.length > 40) { + if (normalizedTitle.length > 60) { throw new AgentMcpToolError( "NOT_ALLOWED", - "Title must be 40 characters or fewer" + "Title must be 60 characters or fewer" ); } diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index c0986aeb5..9a36f6248 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -804,7 +804,7 @@ class ClaudeAgentSession implements AgentSession { preset: "claude_code", append: [ getOrchestratorModeInstructions(), - this.currentMode === "plan" ? "" : getSelfIdentificationInstructions(), + this.currentMode === "plan" ? "" : getSelfIdentificationInstructions({ cwd: this.config.cwd }), ] .filter(Boolean) .join("\n"), diff --git a/packages/server/src/server/agent/providers/codex-mcp-agent.ts b/packages/server/src/server/agent/providers/codex-mcp-agent.ts index 43e3488f2..9a8a70628 100644 --- a/packages/server/src/server/agent/providers/codex-mcp-agent.ts +++ b/packages/server/src/server/agent/providers/codex-mcp-agent.ts @@ -2759,7 +2759,7 @@ function buildCodexMcpConfig( developerInstructions = history; } } - const selfIdentificationInstructions = getSelfIdentificationInstructions(); + const selfIdentificationInstructions = getSelfIdentificationInstructions({ cwd: config.cwd }); // Build MCP servers configuration const mcpServers: Record = {}; diff --git a/packages/server/src/server/agent/self-identification-instructions.ts b/packages/server/src/server/agent/self-identification-instructions.ts index 046bb71f4..9c74b56c2 100644 --- a/packages/server/src/server/agent/self-identification-instructions.ts +++ b/packages/server/src/server/agent/self-identification-instructions.ts @@ -1,9 +1,30 @@ -export function getSelfIdentificationInstructions(): string { - return [ - "Once you understand the task and are about to execute multi-step work (e.g., edits, tests, or repo changes), call set_title and set_branch exactly once to self-identify.", - "Skip these calls in plan/read-only modes or when running a slash command.", - "Do not call set_title or set_branch for short, fixed, or single-response requests (e.g., “say X exactly”, yes/no, one-liners).", - "Only call set_branch when you are certain you are running inside a Paseo-owned worktree under .paseo/worktrees.", - "If set_branch fails due to permissions or not being in a Paseo worktree, do not retry.", - ].join("\n"); +export interface SelfIdentificationContext { + cwd?: string; +} + +function looksLikePaseoWorktree(cwd?: string): boolean { + if (!cwd) return false; + // Simple heuristic: if cwd contains .paseo/worktrees, it's likely a Paseo worktree + return cwd.includes(".paseo/worktrees") || cwd.includes(".paseo\\worktrees"); +} + +export function getSelfIdentificationInstructions( + context?: SelfIdentificationContext +): string { + const inWorktree = looksLikePaseoWorktree(context?.cwd); + + const lines = [ + "You are running under Paseo, an agent orchestration tool.", + "Immediately after your first message where you understand the task and decide to do multi-step work, call set_title to self-identify. Call it exactly once per task—do not repeat.", + "Skip set_title in plan/read-only modes or when running a slash command.", + "Do not call set_title for short, fixed, or single-response requests (e.g., \"say X exactly\", yes/no, one-liners).", + ]; + + if (inWorktree) { + lines.push( + "You are running inside a Paseo-owned worktree. Call set_branch once (alongside set_title) to name your branch." + ); + } + + return lines.join("\n"); }