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:46:44 +07:00
parent 6770e8d510
commit 746d413082
5 changed files with 41 additions and 20 deletions

View File

@@ -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"]);

View File

@@ -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"
);
}

View File

@@ -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"),

View File

@@ -2759,7 +2759,7 @@ function buildCodexMcpConfig(
developerInstructions = history;
}
}
const selfIdentificationInstructions = getSelfIdentificationInstructions();
const selfIdentificationInstructions = getSelfIdentificationInstructions({ cwd: config.cwd });
// Build MCP servers configuration
const mcpServers: Record<string, CodexMcpServerConfig> = {};

View File

@@ -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");
}