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
This commit is contained in:
Mohamed Boudra
2026-01-22 21:53:31 +07:00
parent 6770e8d510
commit 746d413082
5 changed files with 41 additions and 20 deletions
@@ -48,7 +48,7 @@ function createTestDeps(): TestDeps {
describe("create_agent MCP tool", () => { describe("create_agent MCP tool", () => {
const logger = createTestLogger(); 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 { agentManager, agentRegistry } = createTestDeps();
const server = await createAgentMcpServer({ agentManager, agentRegistry, logger }); const server = await createAgentMcpServer({ agentManager, agentRegistry, logger });
const tool = (server as any)._registeredTools["create_agent"]; const tool = (server as any)._registeredTools["create_agent"];
@@ -64,7 +64,7 @@ describe("create_agent MCP tool", () => {
const tooLong = await tool.inputSchema.safeParseAsync({ const tooLong = await tool.inputSchema.safeParseAsync({
cwd: "/tmp/repo", cwd: "/tmp/repo",
initialMode: "default", initialMode: "default",
title: "x".repeat(41), title: "x".repeat(61),
}); });
expect(tooLong.success).toBe(false); expect(tooLong.success).toBe(false);
expect(tooLong.error.issues[0].path).toEqual(["title"]); expect(tooLong.error.issues[0].path).toEqual(["title"]);
@@ -281,9 +281,9 @@ export async function createAgentMcpServer(
.string() .string()
.trim() .trim()
.min(1, "Title is required") .min(1, "Title is required")
.max(40, "Title must be 40 characters or fewer") .max(60, "Title must be 60 characters or fewer")
.describe( .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( agentType: AgentProviderEnum.optional().describe(
"Optional agent implementation to spawn. Defaults to 'claude'." "Optional agent implementation to spawn. Defaults to 'claude'."
@@ -313,9 +313,9 @@ export async function createAgentMcpServer(
.string() .string()
.trim() .trim()
.min(1, "Title is required") .min(1, "Title is required")
.max(40, "Title must be 40 characters or fewer") .max(60, "Title must be 60 characters or fewer")
.describe( .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( agentType: AgentProviderEnum.optional().describe(
"Optional agent implementation to spawn. Defaults to 'claude'." "Optional agent implementation to spawn. Defaults to 'claude'."
@@ -906,8 +906,8 @@ export async function createAgentMcpServer(
title: z title: z
.string() .string()
.min(1) .min(1)
.max(40) .max(60)
.describe("Short descriptive title (<= 40 chars)."), .describe("Short descriptive title (<= 60 chars)."),
}, },
outputSchema: { outputSchema: {
success: z.boolean(), success: z.boolean(),
@@ -931,10 +931,10 @@ export async function createAgentMcpServer(
if (!normalizedTitle) { if (!normalizedTitle) {
throw new AgentMcpToolError("NOT_ALLOWED", "Title cannot be empty"); throw new AgentMcpToolError("NOT_ALLOWED", "Title cannot be empty");
} }
if (normalizedTitle.length > 40) { if (normalizedTitle.length > 60) {
throw new AgentMcpToolError( throw new AgentMcpToolError(
"NOT_ALLOWED", "NOT_ALLOWED",
"Title must be 40 characters or fewer" "Title must be 60 characters or fewer"
); );
} }
@@ -804,7 +804,7 @@ class ClaudeAgentSession implements AgentSession {
preset: "claude_code", preset: "claude_code",
append: [ append: [
getOrchestratorModeInstructions(), getOrchestratorModeInstructions(),
this.currentMode === "plan" ? "" : getSelfIdentificationInstructions(), this.currentMode === "plan" ? "" : getSelfIdentificationInstructions({ cwd: this.config.cwd }),
] ]
.filter(Boolean) .filter(Boolean)
.join("\n"), .join("\n"),
@@ -2759,7 +2759,7 @@ function buildCodexMcpConfig(
developerInstructions = history; developerInstructions = history;
} }
} }
const selfIdentificationInstructions = getSelfIdentificationInstructions(); const selfIdentificationInstructions = getSelfIdentificationInstructions({ cwd: config.cwd });
// Build MCP servers configuration // Build MCP servers configuration
const mcpServers: Record<string, CodexMcpServerConfig> = {}; const mcpServers: Record<string, CodexMcpServerConfig> = {};
@@ -1,9 +1,30 @@
export function getSelfIdentificationInstructions(): string { export interface SelfIdentificationContext {
return [ cwd?: string;
"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).", function looksLikePaseoWorktree(cwd?: string): boolean {
"Only call set_branch when you are certain you are running inside a Paseo-owned worktree under .paseo/worktrees.", if (!cwd) return false;
"If set_branch fails due to permissions or not being in a Paseo worktree, do not retry.", // Simple heuristic: if cwd contains .paseo/worktrees, it's likely a Paseo worktree
].join("\n"); 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");
} }