Cook
DocsAgent mode (ACP) and IDE integration

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:

  1. Initialize -- client sends initialize with capabilities
  2. Create session -- client sends session/new with working directory
  3. Send prompts -- client sends session/prompt with user messages
  4. Receive updates -- agent sends session/update notifications with streamed content
  5. 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;
  }
}

Resources