Agent mode (ACP) and IDE integration
Agent mode runs Cook as a long-lived server that clients talk to over ACP (JSON-RPC). Use it from IDEs, SDKs, eval harnesses, and custom apps. For a one-shot prompt that prints and exits, use cook -p instead (headless mode).
Automation and SDKs
For scripts, CI, evals, and agent servers, start with always-approve so tools run without interactive permission prompts. Deny rules and hooks still apply.
# stdio (local process / many SDKs)
cook agent --always-approve stdio
# WebSocket server
cook agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>
You can also set always-approve per session on session/new:
{
"cwd": "/path/to/project",
"mcpServers": [],
"_meta": { "yoloMode": true }
}
Interactive TUI users typically leave the default ask mode (or use auto). See Permissions and safety.
What is ACP?
The Agent Client Protocol (ACP) defines how clients talk to coding agents over JSON-RPC. With Cook it covers:
- Sessions (create, load, resume)
- Prompts and streamed replies
- Tool call updates
- Reasoning / thought streams
- Permission prompts when the session is not always-approve
stdio transport
stdio is the common local integration path. The agent speaks JSON-RPC on stdin and stdout:
cook agent --always-approve stdio
Typical clients: IDE extensions (Zed, Neovim, Emacs), custom tools, and ACP SDKs.
Options
Agent options apply to every transport (stdio, serve, headless, leader). They go after agent and before the mode name. Mode-specific flags go after the mode (for example serve --bind).
cook agent --always-approve --model grok-build stdio
cook agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>
| Flag | Description |
|---|---|
-m, --model <MODEL> |
Model ID (for example grok-4.6). |
--always-approve |
Run without interactive tool-permission prompts. Alias: --yolo. |
--reauth |
Authenticate before the agent starts. |
--agent-profile <PATH> |
Load an agent profile from a file. |
--leader / --no-leader |
Connect to a shared leader process, or force a local agent. When a non-off sandbox profile is requested, leader mode is refused so tools stay in-process (see Sandbox Mode). |
Server mode
cook agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>
Clients connect over WebSocket and authenticate with the secret token. If you omit --secret, the agent prints a generated token at startup, or set GROK_AGENT_SECRET. The process keeps state across client reconnects. Permissions match other entry points; see Permissions and safety.
This is a server you run yourself — Cook's hosted cloud sandboxes do not run cook agent serve.
WebSocket relay
To reach the agent over the internet, connect the agent to a relay and point browsers at the same relay:
cook agent --always-approve headless --grok-ws-url wss://your-relay.example.com/ws
ACP protocol basics
Communication follows the JSON-RPC 2.0 format. A typical session lifecycle:
- Initialize -- client sends
initializewith capabilities - Create session -- client sends
session/newwith working directory - Send prompts -- client sends
session/promptwith user messages - Receive updates -- agent sends
session/updatenotifications with streamed content - Handle permissions -- agent may request tool execution approval (or allow or deny based on permission mode)
Architecture
+------------------------------------------+
| ACP Client |
| (IDE, Editor, Custom Application) |
+-------------------+----------------------+
| JSON-RPC over stdio
+-------------------v----------------------+
| cook agent stdio |
| |
| +---------+ +---------+ +---------+ |
| | Session | | Tools | | MCP | |
| | Manager | | Registry| | Servers | |
| +---------+ +---------+ +---------+ |
+------------------------------------------+
Streaming updates
ACP streams structured events. Each session/update notification carries a sessionUpdate field that identifies the update type:
sessionUpdate value |
Description |
|---|---|
agent_message_chunk |
A chunk of the agent's response text. |
agent_thought_chunk |
A chunk of the agent's internal reasoning. |
tool_call |
A new tool invocation (title, kind, status, input). |
tool_call_update |
A status or result update for an in-flight tool call. |
plan |
The agent's execution plan. |
Each update names its type, so a client can render distinct panels for reasoning, tool calls, and response text.
Extension methods
Beyond the base ACP protocol, Cook defines extension methods under the x.ai/ prefix for upstream provider-specific functionality. These cover:
| Category | Prefix | Examples |
|---|---|---|
| Filesystem | x.ai/fs/* |
list, exists, read_file, write_file |
| Git | x.ai/git/* |
status, stage, commit, diffs, discard |
| Git Worktree | x.ai/git/worktree/* |
create, remove, apply, list, gc |
| Search | x.ai/search/* |
fuzzy/open, fuzzy/change, content |
| Terminal | x.ai/terminal/* |
create, kill, output, wait_for_exit |
| Session Management | x.ai/session/* |
fork, resolve_local_for_worktree_resume |
| Conversation & History | x.ai/* |
prompt_history, rewind/*, compact_conversation |
| Authentication | x.ai/auth/* |
get_url, submit_code |
| Feedback & Telemetry | x.ai/* |
feedback, telemetry/* |
The tables here show representative methods in each category. The x.ai/* set is upstream provider-specific and may expand across releases, so treat it as non-exhaustive and discover the available methods from the agent's initialize response.
Notifications (agent to client)
The agent sends push notifications to clients for real-time updates:
| Notification | Description |
|---|---|
x.ai/search/fuzzy/status |
Fuzzy search results update |
x.ai/git/worktree/status |
Worktree creation progress |
x.ai/fs_notify |
Filesystem change notification |
x.ai/fs/index |
Full file index update |
x.ai/fs/index/delta |
Incremental file index update |
x.ai/session_notification |
Session-specific updates (diff review, retry state, auto-compact) |
x.ai/session/update |
Session update (tool calls, content) |
Session config options
session/new and session/load responses include a typed configOptions list (standard ACP, not an x.ai/ extension). Change a live option with session/set_config_option.
configId |
Category | Effect |
|---|---|---|
model |
model |
Switches the session model (allowed_models, chat gateway routing). Value must be a string id. |
reasoning_effort |
thought_level |
Applies effort to the current model without changing the model (no prompt rewrite, no allowed_models gate). Value must be a string id (minimal, low, medium, high, xhigh). Dropped with a warning when the model does not advertise supportsReasoningEffort. |
{
"sessionId": "…",
"configId": "reasoning_effort",
"value": { "value": "high" }
}
The response is the complete, updated option list. A config_option_update session notification mirrors it to every subscribed client. In leader mode the proxy snoops configId: model so each client's default_model stays in sync. Boolean values are rejected; exposing boolean options is not implemented yet.
Session `_meta` options
Optional fields on session/new:
| Field | Description |
|---|---|
rules |
Extra rules appended to the system prompt. |
systemPromptOverride |
Replacement system prompt. |
agentProfile |
Agent profile name or JSON object. |
yoloMode |
When true, always-approve for this session. |
autoMode |
When true, auto permission mode for this session. Superseded when always-approve is already on. |
{
"cwd": "/path/to/project",
"mcpServers": [],
"_meta": { "yoloMode": true }
}
ACP SDKs
Official SDK libraries are available for multiple languages:
| Language | Package |
|---|---|
| TypeScript | `@agentclientprotocol/sdk` |
| Rust | `agent-client-protocol` |
| Python | `agent-client-protocol-python` |
| Go | `acp-go-sdk` |
| Kotlin | `acp` |
Compatible clients
| Client | Status |
|---|---|
| Zed | Supported |
| Neovim (CodeCompanion, avante.nvim) | Supported |
| Emacs | Supported |
| marimo notebook | Supported |
| JetBrains | Coming soon |
Integration example: a TypeScript ACP client
import { spawn, ChildProcess } from "child_process";
import * as readline from "readline";
class GrokACPChat {
private proc!: ChildProcess;
private sessionId!: string;
private rl!: readline.Interface;
constructor(private cwd = ".") {}
async init() {
this.proc = spawn("cook", ["agent", "--always-approve", "stdio"]);
this.rl = readline.createInterface({ input: this.proc.stdout! });
await this.request("initialize", {
protocolVersion: 1,
clientCapabilities: {
fs: { readTextFile: true, writeTextFile: true },
terminal: true,
},
});
const { sessionId } = await this.request("session/new", {
cwd: this.cwd,
mcpServers: [],
_meta: { yoloMode: true },
});
this.sessionId = sessionId;
return this;
}
private async request(method: string, params: any): Promise<any> {
return new Promise((resolve) => {
const msg = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params });
this.proc.stdin!.write(msg + "\n");
this.rl.once("line", (line) => {
resolve(JSON.parse(line).result || {});
});
});
}
async *streamPrompt(text: string) {
const msg = JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "session/prompt",
params: {
sessionId: this.sessionId,
prompt: [{ type: "text", text }],
},
});
this.proc.stdin!.write(msg + "\n");
for await (const line of this.rl) {
const data = JSON.parse(line);
if (data.method === "session/update") {
const update = data.params.update;
yield update; // { sessionUpdate, content, title, ... }
} else if (data.result) {
break; // Final response
}
}
}
}
// Usage
const client = await new GrokACPChat(".").init();
for await (const update of client.streamPrompt("List the files in this project")) {
switch (update.sessionUpdate) {
case "agent_message_chunk":
process.stdout.write(update.content?.text || "");
break;
case "agent_thought_chunk":
console.log(`\n[Thinking: ${update.content?.text}]`);
break;
case "tool_call":
console.log(`\n[Tool: ${update.title}]`);
break;
}
}