@sema-agent/core
Version:
Stateless, task-oriented AI agent core
82 lines (76 loc) • 5.73 kB
JavaScript
import { Type } from "typebox";
import { delimitUntrusted } from "./untrusted-text.js";
import { toolFace } from "./tool-catalog-entries.js";
export const PRESENT_PLAN_TOOL_NAME = "ExitPlanMode";
export const ENTER_PLAN_MODE_TOOL_NAME = "EnterPlanMode";
const ENTER_PLAN_MODE_DESCRIPTION = `Enter PLAN MODE (read-only): stop making changes and switch to exploration/planning. For the rest of this run, every write/edit/mutating tool call ASKS the user first ("Cannot write to <path> while in plan mode."; where nobody can answer, it is refused) while read-only tools (Read, Grep, Glob, etc.) still work. When your plan is ready, call ${PRESENT_PLAN_TOOL_NAME} with it — the run PAUSES for a human to approve it, then continues (read-only lifted) so you can execute.
Use this tool proactively when you're about to start a non-trivial implementation task. If you would use AskUserQuestion to clarify the approach, use EnterPlanMode instead — getting alignment on the plan itself is the point.
## When to Use This Tool
1. Non-trivial implementation work: the change involves real design decisions worth aligning on before code is written
- Example: "Add a delete button to the user profile" — placement, confirmation UX, what the deletion cascades to
- Example: "Update the error handling in the API" — which layers change, error shapes, retry semantics
2. Significant architectural ambiguity: multiple reasonable approaches exist and the choice meaningfully affects the codebase
- Example: "Add caching to the API" — Redis vs in-memory vs file-based
- Example: "Add real-time updates" — WebSockets vs SSE vs polling
3. Unclear requirements: you need to explore and clarify before you can make progress
- Example: "Make the app faster" — need to profile and identify bottlenecks first
4. High-impact or destructive restructuring: the work significantly restructures, migrates, or deletes existing code, and buy-in first reduces risk
- Example: "Redesign the authentication system"
## When NOT to Use This Tool
Skip plan mode only when there is genuinely nothing to align on:
- Trivial fixes (e.g. fixing a typo, removing a stray console.log)
- Pure research/exploration tasks — just read; no plan approval is needed
If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work.
## Examples
GOOD: "Add user authentication to the app" (genuinely ambiguous: session vs JWT, token storage, middleware). "Add a delete button to the user profile" (small but has real design decisions — align first). "Update the error handling in the API" (touches multiple layers; agree on the shape before rewriting). "Redesign the data pipeline" (major restructuring where the wrong approach wastes significant effort).
BAD: "Fix the typo in the README" (trivial; nothing to align on). "Remove the leftover console.log in utils.js" (trivial cleanup). "How does the caching layer work?" (pure research — read and answer; no plan needed).`;
export function createEnterPlanModeTool(enterPlanMode) {
return {
name: ENTER_PLAN_MODE_TOOL_NAME,
...toolFace("enter-plan-mode"),
description: ENTER_PLAN_MODE_DESCRIPTION,
parameters: Type.Object({}),
executionMode: "sequential",
execute: async () => {
await enterPlanMode();
const message = "Entered plan mode (read-only). Every write/edit/mutating tool call now asks the user first for the rest of this run; " +
`research with read-only tools, then call ${PRESENT_PLAN_TOOL_NAME} with your plan to get it approved before acting.`;
return {
content: message,
details: { type: "enter-plan-mode", message },
};
},
};
}
const PRESENT_PLAN_DESCRIPTION = [
"Present your proposed PLAN for human approval BEFORE taking any action (CC plan-mode parity).",
"Use this when you are in plan mode (read-only research): once you have explored enough and formed a concrete plan,",
"provide the full plan here. The run PAUSES for a human to approve, edit, or reject it.",
"IMPORTANT: only use this when the task requires planning the implementation steps of work that changes things;",
"for research tasks (gathering information, reading files, understanding the codebase) do NOT use this tool.",
"This tool inherently requests approval of your plan — do not separately ask \"Is this plan okay?\".",
"Do NOT attempt edits / writes / mutating commands before the plan is approved — present the plan first.",
"If the plan is approved, the run continues (and any read-only restriction is lifted) so you can execute it;",
"if it is rejected or edited, follow the reviewer's feedback instead of the original plan.",
].join(" ");
export function createPresentPlanTool(requestReview) {
return {
name: PRESENT_PLAN_TOOL_NAME,
...toolFace("exit-plan-mode"),
description: PRESENT_PLAN_DESCRIPTION,
parameters: Type.Object({
plan: Type.String({
description: "Your full proposed plan, in markdown. The human reviewer approves / edits / rejects it before you act.",
}),
}),
execute: async (args) => {
const { plan } = args;
requestReview({ reason: "ExitPlanMode" });
return {
content: `Plan presented for human review:\n\n${delimitUntrusted("plan", plan)}\n\n` +
"(The run is pausing for approval — you will continue once the plan is approved, edited, or rejected.)",
details: { type: "exit-plan-mode", plan, isAgent: false },
};
},
};
}