Handmade Claude Code

Campaign · 7 parts Public

Build a coding agent from nothing. Not a prompt pasted into a chat window — a program that runs the loop: sends a conversation to a model, executes the tools the model asks for, feeds the results back, and keeps going until the job is done. Then give it hands (files, shell, permissions), a memory (AGENTS.md, sessions, compaction), a way to learn (skills, hooks, subagents), a way to plug in the world (MCP), and finally a face: a terminal UI you would actually sit in.

Seven sessions, one codebase. You start each part in the folder where the last one ended (or in an empty one, and your previous work is fetched for you), and every part opens by re-checking the contract the previous part earned — so a campaign carried honestly pays from its first check.

The shape, frozen in part one

You declare one command; the platform keeps it in session memory:

agent: <command>     started as:  <command> -C <dir> [--yes] -p "<prompt>"

Without -p the same command opens the terminal UI (part six). The model behind the harness is a pluggable provider: every model call runs the command in AGENT_MODEL_CMD, writes the request to its stdin as JSON and reads the reply from its stdout. The checks plug in a scripted model, which is how a harness gets graded exactly; wiring a real API behind the same interface is your job — and your reward, because from part two on the thing codes for you.

The parts

  1. The Loop — the headless agent: one prompt in, one answer out, the model protocol, a tool round trip, many turns, a provider registry with switchable models, retries, turn caps and the bill.
  2. The Tools — read, write, edit, glob, grep and bash with real semantics; errors that come back as results; timeouts; a permission engine with allow/deny rules; parallel tool calls.
  3. Context and Sessions — AGENTS.md up the tree with imports, @file mentions, environment in the system prompt, resumable sessions, and compaction when the context runs out.
  4. Skills, Hooks and Agents — skills discovered and invoked, slash commands, pre/post tool hooks that can block, prompt and stop hooks, layered settings, and subagents that run their own loop.
  5. MCP — stdio servers from .mcp.json: the handshake, tools advertised to the model and called through the server, errors, a server that will not start, prompts as commands, resources as mentions, environment and shutdown.
  6. The TUI — the interactive face: a prompt, streamed answers, tool calls rendered, permission questions, slash commands, a status line with the bill, and an interrupt key that actually interrupts.
  7. ACP — the agent inside an editor: the Agent Client Protocol over stdio, so any ACP client (Zed, Neovim, JetBrains…) can drive the same loop — sessions, streamed chunks, tool calls reported, permissions asked, cancellation, the editor's unsaved files, the client's MCP servers.

How it is scored

Every rung is verified by deterministic checks against your running agent — they start it with a scripted model, watch what it sends and does, and compare exact results. The review panel does not wait for the end: each judge sits on the rungs where its subject is decided and reads the repository as that rung lands — architecture where a seam appears, performance where a loop or a process is paid for, code quality and tests where the edge cases live, technical governance where a decision should be written down, and developer experience — what the tool prints, how it fails, how it explains itself — where the output is the point, judged from terminal recordings and raw output you deliver on request. A rung you never reach is a verdict you never get.

Wrapping claude, codex, gemini, aider or any other coding agent, or building on an agent SDK that owns the loop for you, is not building one. The loop, the tools, the permission engine, the MCP client and the TUI are yours. HTTP clients, JSON parsers and terminal-drawing libraries are tools, not the wheel.

Parts

7

Visibility

Public

Category

Reinvent the Wheel

Slug

handmade-claude-code

Total playing time

3 h 15 min across 7 parts

Tags
  • ai-agent
  • harness
  • tui
  • mcp
  • campaign

Handmade Claude Code 1/7 — The Loop

Sign in to see how far you have got — parts unlock one after another as you finish them.

Handmade Claude Code 1/7 — The Loop 25 min Ready to play Handmade Claude Code 1/7 — The Loop Reinvent the Wheel· 9 tasks· ~11 reviews· 1 played Part one of the Handmade Claude Code campaign: the agent becomes a loop. Not a script that sends one prompt and prints one reply — a process that holds a conversation, runs the tools the model asks for, feeds the results back, survives a model that hiccups, and stops when told to. Everything after this part is built on the shape frozen here, so the shape comes first and the tool set stays deliberately thin: bash only. Part two gives it real hands. The command You declare it, and it is captured into session memory — any language, any entry point: agent: <command> started as: <command> -C <dir> [--yes] -p "<prompt>" -C <dir> — the working directory. Everything relative (files, shell commands, later AGENTS.md discovery) is relative to it. Default: the current directory. -p "<prompt>" — headless mode: run the loop to completion, print the final answer to stdout, exit 0. Nothing else goes to stdout — logs and progress belong on stderr. Without -p the same command opens the terminal UI (part six). --yes — skip permission checks; every tool call is allowed. Part two adds the permission engine; until then the checks always pass --yes. --output-format json — instead of the plain answer, print one JSON object: {"result": "<answer>", "session_id": "<id>", "turns": <model calls>, "usage": {"input_tokens": <sum>, "output_tokens": <sum>}}. --max-turns <n> — the most model calls one run may make (default 50). The model protocol The harness never knows which model it is talking to. Every model call is one run of the command in the environment variable AGENT_MODEL_CMD (started with sh -c): the request goes to its stdin as one JSON document, the reply comes back on its stdout, the process exits 0. Once per call — the model is stateless, the whole conversation travels every time, exactly like a real API. The request: { "system": "<the system prompt — never empty>", "messages": [ {"role": "user", "content": [{"type": "text", "text": "<prompt>"}]}, {"role": "assistant", "content": [ {"type": "text", "text": "…"}, {"type": "tool_use", "id": "t1", "name": "bash", "input": {"command": "ls"}} ]}, {"role": "user", "content": [ {"type": "tool_result", "tool_use_id": "t1", "content": "<output>", "is_error": false} ]} ], "tools": [{"name": "bash", "description": "…", "input_schema": {…}}] } The reply: one JSON object per line. Any number of streamed fragments {"type": "text_delta", "text": "…"}, then exactly one final line: {"type": "message", "content": [<blocks>], "stop_reason": "end_turn", "usage": {"input_tokens": 123, "output_tokens": 45}} stop_reason: "tool_use" means the content carries tool_use blocks: run every one of them, append one user message carrying a tool_result per call in the same order, and call the model again. end_turn ends the loop; the text blocks of the final message, joined with newlines, are the answer. The deltas are for showing progress — they are never printed as the answer a second time. A model command that exits non-zero, or whose last line is not a valid message, has failed. Call it again — three attempts in total, no more than a second or two apart. After the third failure print a line starting ERROR: to stderr and exit non-zero. --max-turns is enforced the same way: one call past the cap is an ERROR: and a non-zero exit. Providers and models AGENT_MODEL_CMD is the shortcut the checks use; the mechanism behind it is a provider registry in .agent/settings.json of the working directory: {"providers": {"scripted": {"type": "command", "command": "sh model.sh"}, "claude": {"type": "anthropic", "api_key_env": "ANTHROPIC_API_KEY"}, "local": {"type": "openai", "base_url": "http://localhost:11434/v1"}}, "model": "scripted/default"} model names the default as <provider>/<model>; --model <provider>/<model> overrides it for one run. Every request carries the model name: "model": "<model>" next to system, messages and tools. AGENT_MODEL_CMD, when set and non-empty and no --model is given, is a command provider that wins over the settings. A --model naming a provider the settings do not declare is an ERROR: and a non-zero exit. The command type is the one the checks exercise. At least one HTTP type belongs in the same registry so the agent can be used for real — the Anthropic Messages API, or an OpenAI-compatible chat completions endpoint (which also covers Ollama, OpenRouter and most local servers) — behind the same interface, so the loop never knows which one it is talking to. The review panel looks for it; part six adds /model to switch at runtime. The bash tool {"name": "bash", "input": {"command": "<shell>"}} Runs the command with sh -c in the working directory. The result content is its stdout and stderr, in order. A non-zero exit appends a line exit code: <n> to the content and sets is_error to true. The loop does not stop on a failed tool — the model is told, and decides. Wrapping claude, codex, gemini, aider or any other coding agent, or building on an agent SDK that owns the loop, is not building one. The loop is yours; HTTP clients, JSON parsers and terminal libraries are tools. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up: declare agent: and test: (10) One prompt, one answer (10) The request is a conversation (10) A tool round trip (30) Many turns (30) Bash tells the truth (20) Providers and models (30) A flaky model gets retried (20) Turns are capped, the bill is printed (20) ai-agentharnesscli +2 Open Handmade Claude Code 2/7 — The Tools 25 min Locked Handmade Claude Code 2/7 — The Tools Reinvent the Wheel· 9 tasks· ~12 reviews Part two of the Handmade Claude Code campaign: the loop gets hands. A model that can only run bash is a model doing everything through a shell; a coding agent reads files, writes them, edits them in place, finds them and searches them — with results the model can trust and errors it can act on. And because the hands are real, something has to decide what they may touch: a permission engine with rules. This session continues the agent you built in part one, in the same repository. Nothing about how it is started changes; what changes is what it can do. The command and the protocol, unchanged agent: <command> started as: <command> -C <dir> [--yes] -p "<prompt>" The model protocol is frozen in part one and not restated: request in on stdin, one JSON line per reply fragment, tool_use blocks run and answered with one tool_result each, in order, in one user message. The tools Six tools, advertised in every request with a name, a description and an input_schema. Paths are relative to the working directory unless absolute. A tool that cannot do what it was asked — a file that does not exist, an old_string found twice or not at all, a command that runs past its timeout — returns a result with is_error: true and a content that says why, in plain words. The loop goes on; the model decides. A timed-out command is killed and its result says timed out. When one reply carries several tool_use blocks, every one of them runs and the next request carries one user message with their results in the same order. Permissions Without --yes, every tool call is checked against rules before it runs: .agent/settings.json {"permissions": {"allow": ["bash(echo )", "write"], "deny": ["bash(rm )"]}} A rule is a tool name (write — the whole tool) or a tool name with a glob in parentheses (bash(echo *) — matched against the command; for read, write and edit, against the path). deny wins over allow; allow wins over the defaults; the defaults are: read, glob and grep may run, bash, write and edit may not. A call that is not permitted does not run: its result is is_error: true with a content that starts with permission denied. --yes allows everything not explicitly denied. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry part one forward (10) Read (10) Write (20) Edit (30) Glob (20) Grep (20) Bash has a clock (20) Permissions (40) Parallel tool calls (30) ai-agentharnesstools +2 Open Handmade Claude Code 3/7 — Context and Sessions 25 min Locked Handmade Claude Code 3/7 — Context and Sessions Reinvent the Wheel· 9 tasks· ~13 reviews Part three of the Handmade Claude Code campaign: the agent learns where it is and remembers what happened. A loop with tools is a capable stranger; what makes it a colleague is context — the project's own instructions, the files the user points at, the environment it runs in — and memory: a conversation that can be picked up again, and one that survives running out of room. This session continues the agent you built in parts one and two, in the same repository. Nothing about how it is started changes; what changes is what it knows before the first model call, and what it keeps after the last. The command and the protocol, unchanged agent: <command> started as: <command> -C <dir> [--yes] -p "<prompt>" Project instructions Before the first model call, the agent walks from the working directory up to the filesystem root and collects, in every directory, the file AGENTS.md — or CLAUDE.md when there is no AGENTS.md beside it. Their contents go into the system prompt, outermost directory first, innermost last. Inside such a file a line that is exactly @<path> imports another file: its contents are inlined in place, the path relative to the importing file. Imports nest (at least two levels deep); a file already imported is not imported twice. Mentions A token @<path> in the prompt attaches that file: the user message carries the prompt as typed and the file's contents (a second text block, or the same block — your call). A path that does not exist is left as typed. Where and when The system prompt says where and when the agent is: the absolute path of the working directory, today's date as YYYY-MM-DD, and — when the working directory is inside a git repository — the current branch name. Sessions Every headless run is a session, named by the session_id the JSON output carries. Where and how you store it is yours (a file per session is the usual answer); what it must allow is: --resume <session_id> -p "<prompt>" — the new prompt is appended to that session's conversation and the whole of it goes to the model. --continue -p "<prompt>" — the same, with the most recent session of this working directory. Compaction Contexts fill up. .agent/settings.json may set a budget: {"compaction": {"input_tokens": 100000}} When the input_tokens the last reply reported exceeds the budget, the next model call is not the next turn — it is a compaction call: the conversation so far plus one user message asking the model to summarise it. The reply's text becomes the new conversation — one user message carrying the summary (worded however you like) — and only then does the turn the user asked for go to the model, on top of the summary. The old messages are gone from every request after that. The default budget is yours; the checks set a small one. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry parts one and two forward (10) AGENTS.md is the system prompt (20) Up the tree (20) Imports (20) Mentions (20) Where and when (10) Resume a session (30) Continue the last session (20) Compaction (40) ai-agentharnesscontext +2 Open Handmade Claude Code 4/7 — Skills, Hooks and Agents 30 min Locked Handmade Claude Code 4/7 — Skills, Hooks and Agents Reinvent the Wheel· 9 tasks· ~12 reviews Part four of the Handmade Claude Code campaign: the agent becomes extensible without being recompiled. Skills the model can pull in when it needs them, commands the user can type, hooks that run around every tool call and can veto it, settings that layer, and subagents — whole loops the model can delegate to. This session continues the agent you built in parts one to three, in the same repository. Everything lives under .agent/ in the working directory. Skills .agent/skills/<name>/SKILL.md --- name: <name> description: <one line> --- <the instructions> Every skill is listed in the system prompt by name and description, so the model knows what it could ask for. A tool skill with input {"name": "<name>"} returns the skill's body (the frontmatter stripped) and the absolute path of the skill's directory, so the model can read the files beside it. An unknown name is an error result. Slash commands A prompt that starts with / is a command. /<name> <args> looks for .agent/commands/<name>.md and sends its contents as the user message, with $ARGUMENTS replaced by <args>; when there is no such file but a skill of that name, the skill's body is sent the same way. Hooks .agent/settings.json {"hooks": {"PreToolUse": [{"matcher": "bash", "command": "sh .agent/hooks/pre.sh"}]}} Events: PreToolUse, PostToolUse, UserPromptSubmit, Stop. A hook is a shell command run with sh -c in the working directory, with one JSON object on stdin: hook_event_name, and for tool events tool_name and tool_input (plus tool_response — the result content — after the tool ran), for UserPromptSubmit the prompt. matcher is a glob over the tool name; absent, the hook runs for every tool. PreToolUse — exit 2 blocks the call: the tool does not run and the result is is_error: true with the hook's stderr as content. PostToolUse — whatever it prints on stdout is appended to the tool result the model sees. UserPromptSubmit — whatever it prints is appended to the user message. Stop — runs once the final answer is ready, before the agent exits. Settings layering .agent/settings.local.json sits on top of .agent/settings.json: lists (allow, deny, each hook event) concatenate, scalars from the local file win. Subagents .agent/agents/<name>.md --- name: <name> description: <one line> --- <that agent's system prompt> Every agent is listed in the system prompt by name and description. A tool task with input {"agent": "<name>", "prompt": "<text>"} runs a fresh loop: its own conversation starting with just that prompt, the agent's body as its system prompt (plus your usual environment), the same model command, the same tools. Its final text is the tool result. The parent conversation never sees the child's turns. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry parts one to three forward (10) Skills are listed (20) A skill is a tool (30) Slash commands (20) A hook can say no (30) A hook can add a note (20) Prompt and stop hooks (20) Settings layer (20) Subagents (40) ai-agentharnessskills +2 Open Handmade Claude Code 5/7 — MCP 30 min Locked Handmade Claude Code 5/7 — MCP Reinvent the Wheel· 9 tasks· ~12 reviews Part five of the Handmade Claude Code campaign: the agent plugs into the world. The Model Context Protocol is how tools that live in other processes — a database, a browser, a ticket tracker — offer themselves to any agent that speaks it. This part makes yours speak it: start the servers, shake hands, put their tools in front of the model, call them, survive their failures, use their prompts and resources, and shut them down cleanly. This session continues the agent you built in parts one to four, in the same repository. The MCP client is yours too — JSON-RPC over stdio is small enough to write, and writing it is the point. An MCP SDK is out of contract. Servers .mcp.json (in the working directory) {"mcpServers": {"<name>": {"command": "sh", "args": ["srv.sh"], "env": {"K": "V"}}}} At launch, before the first model call, every server is started as a child process — command with args, the working directory as its directory, env added to its environment — and spoken to over its stdin and stdout in JSON-RPC 2.0, one JSON object per line. The handshake: an initialize request (with protocolVersion, capabilities and clientInfo), then the notifications/initialized notification, then tools/list. A server that fails to start, or fails the handshake, is reported on stderr by name and skipped; the run goes on without it. Tools Every tool a server lists is advertised to the model as mcp__<server>__<tool>, with the server's description and its inputSchema as the input_schema. A call becomes tools/call with name (the server's tool name) and arguments (the model's input). The result's content text blocks, joined, are the tool result content; a result with isError: true, or a JSON-RPC error reply, is an error result with the message as content. The loop goes on. Prompts A server that lists prompts (prompts/list) contributes slash commands: /mcp__<server>__<prompt> <args> calls prompts/get with the arguments mapped positionally onto the prompt's declared arguments, and the messages it returns become the user message (their text, joined). Resources A token @<server>:<uri> in the prompt reads a resource: resources/read with that uri, and the text of the returned contents travels with the prompt like a mentioned file. Shutdown When the run ends, every server's stdin is closed; a server still alive a moment later is killed. No orphans. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry parts one to four forward (10) The handshake (20) Tools are advertised (30) A tool call goes through (30) Errors come back as results (20) A server that will not start (20) Prompts are commands (20) Resources are mentions (20) Environment and shutdown (20) ai-agentharnessmcp +2 Open Handmade Claude Code 6/7 — The TUI 30 min Locked Handmade Claude Code 6/7 — The TUI Reinvent the Wheel· 9 tasks· ~14 reviews Part six of the Handmade Claude Code campaign: the agent gets a face. Everything so far ran headless — a prompt in, an answer out. A coding agent you would sit in is a conversation: you type, it streams, it shows you the tools it is running and asks before the dangerous ones, it tells you what the session has cost, and when it goes wrong you press a key and it stops. This session continues the agent you built in parts one to five, in the same repository. The same command without -p opens the terminal UI in the working directory: <command> -C <dir> [--yes] The contract The checks drive your TUI inside tmux (a 120×32 pane), typing keys and reading the screen — so tmux must be installed on your machine, and the screen must say what happened in plain text: An input line that begins with > (whitespace before it is fine). Enter submits; Enter on an empty line does nothing. The answer is shown as it arrives: streamed fragments appear on screen before the final message does. A tool call is shown with the tool's name and what it targets — for bash the command, for read, write, edit, glob and grep the path or pattern, for skill the skill's name, for task the agent's name and the delegated prompt — and, once it finishes, its output (or the head of it). A call that failed is marked as such (error or failed on its line). Several calls in one reply show as several lines. Skills work as they do headless: /<skill> <args> at the prompt sends the skill's body, and the typed line stays in the transcript. Subagents are visible: everything the child does — its tool calls, its answer — is shown marked with the agent's name, so the user can tell the child's activity from the parent's. Without --yes, a call the rules do not permit becomes a question on screen, showing the tool name and its input. y allows it, n denies it (the model gets the usual permission denied result). Slash commands: /help lists the commands, /clear forgets the conversation (the next request carries only the new prompt), /model <provider>/<model> switches to another provider from the settings, /quit exits (so does Ctrl-D on an empty line). A status line shows the session's total input and output tokens, as plain integers. Esc interrupts the running turn: a running tool is killed, no further model call is made for that turn, and the input line is back. The scripted model is the same one the headless checks use, so what streams, what asks and what gets interrupted is entirely under the check's control — and yours. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry parts one to five forward (10) It starts and it quits (20) Ask and answer: typed, streamed, billed (30) Tool calls on screen (40) The question (40) Slash commands (20) Skills on screen (30) Subagents on screen (40) Esc means stop (30) ai-agentharnesstui +2 Open Handmade Claude Code 7/7 — ACP 30 min Locked Handmade Claude Code 7/7 — ACP Reinvent the Wheel· 9 tasks· ~13 reviews Part seven of the Handmade Claude Code campaign: the agent moves into the editor. The Agent Client Protocol is how editors — Zed, Neovim, JetBrains, anything that speaks it — drive a coding agent: they start it, open sessions, send prompts, watch the answer stream in, see every tool call, answer its permission questions, cancel it, and lend it their own unsaved files and MCP servers. This part makes yours a citizen of that world — the same loop, one more front end. This session continues the agent you built in parts one to six, in the same repository. The protocol client is yours to write, like the MCP client was: JSON-RPC 2.0 over stdio, one object per line, in both directions. Starting <command> --acp reads JSON-RPC from stdin and writes it to stdout — nothing else goes to stdout — until stdin closes. --yes applies here too. The working directory comes from the session, not from -C. The methods Client → agent, requests: initialize {protocolVersion: 1, clientCapabilities: {fs: {readTextFile, writeTextFile}}, clientInfo} → {protocolVersion: 1, agentCapabilities: {...}, agentInfo, authMethods: []}. session/new {cwd, mcpServers: [{name, command, args, env: [{name, value}]}]} → {sessionId}. cwd is the working directory of that session — instructions, tools and paths resolve against it, exactly as -C did. The mcpServers are started for the session like .mcp.json entries. session/prompt {sessionId, prompt: [{type: "text", text}]} → runs a turn; the response {stopReason} comes only when the turn is over: end_turn, or cancelled after a session/cancel. Client → agent, notification: session/cancel {sessionId} — stop the turn: kill a running tool, make no further model call, answer the pending prompt with stopReason: "cancelled". Agent → client, notifications session/update {sessionId, update}, where update.sessionUpdate is: agent_message_chunk {content: {type: "text", text}} — the answer, as it streams (every text_delta, or the final text when the model did not stream), before the prompt response. tool_call {toolCallId, title, kind, status: "pending" | "in_progress", rawInput} — when a tool starts; for bash the title carries the command and kind is execute. tool_call_update {toolCallId, status: "completed" | "failed", content: [{type: "content", content: {type: "text", text}}], rawOutput} — when it ends, with its output. Agent → client, requests: session/request_permission {sessionId, toolCall: {toolCallId, title, kind, rawInput}, options: [{optionId: "allow", name, kind: "allow_once"}, {optionId: "reject", name, kind: "reject_once"}]} — for a call the rules do not permit, instead of the TUI's question. The reply {outcome: {outcome: "selected", optionId}} decides; {outcome: {outcome: "cancelled"}} denies. fs/read_text_file {sessionId, path, line?, limit?} → {content} and fs/write_text_file {sessionId, path, content} → null — only when the client declared the capability: then the read and write tools go through the editor (which has the unsaved buffers) instead of the disk. Paths are absolute. Ids: the client picks ids for its requests and the agent for its own; a reply carries the id of the request it answers. The checks drive the protocol from a shell script, so line order in the output is what they read: a chunk is streamed when it is written before the response. The judges The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get. The ladder Set up and carry parts one to six forward (10) Initialize (20) A session has a working directory (20) The answer streams as chunks (30) Tool calls are reported (30) Permission is asked (40) Cancel (30) The editor's files (20) The client's MCP servers (20) ai-agentharnessacp +2 Open