Skip to main content

Protocol Reference

Ante uses a typed message-passing protocol between the client and daemon. Messages are exchanged over bounded async channels (in-process) or as JSON Lines over stdin/stdout (external clients).

Wire format

External clients communicate with the daemon using JSON Lines (JSONL) — one JSON object per line over stdin/stdout.

  • Client → Daemon: Send OpMsg objects as JSON lines to stdin
  • Daemon → Client: Receive EventMsg objects as JSON lines from stdout

OpMsg envelope

Every operation is wrapped in an OpMsg:

{
"op": { "StartSession": { "model": "claude-sonnet-4-6", "provider": "anthropic" } },
"id": "op_01ARZ3NDEKTSV4RRFFQ69G5FAV"
}

EventMsg envelope

Every event is wrapped in an EventMsg:

{
"timestamp": "2025-06-01T12:00:00Z",
"id": "evt_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"event": { "AgentMessage": "Here is the result..." },
"parent": "op_01ARZ3NDEKTSV4RRFFQ69G5FAV"
}

The parent field links an event back to the operation that triggered it. It is null when not applicable.

Message IDs

Every message has a typed Id consisting of a prefix (up to 4 bytes) and a ULID. The string format is {prefix}_{ulid}.

PrefixUsage
op_Operations (client → daemon)
evt_Events (daemon → client)
ses_Session identifiers
step_Step identifiers

Example: op_01J5A3B7C9D0E1F2G3H4J5K6M7

Operations (Client → Daemon)

StartSession

Initialize a new session with model, provider, and configuration.

{
"op": {
"StartSession": {
"model": "claude-sonnet-4-6",
"provider": "anthropic",
"permission_mode": null,
"system_prompt": null,
"append_system_prompt": null,
"tools": null,
"include_tools": null,
"exclude_tools": null,
"cwd": null,
"effort": null,
"enable_auto_memory": null
}
},
"id": "op_..."
}

SessionConfig fields:

FieldTypeDescription
modelstringModel name (e.g. "claude-sonnet-4-6")
providerstringProvider name (e.g. "anthropic", "openai", "gemini", "xai", "openrouter", "local")
permission_modePermissionMode?Tool approval mode: "strict", "auto", or "yolo". null uses strict
system_promptstring?Override the default system prompt entirely
append_system_promptstring?Append content to the default system prompt
toolsstring[]?Exactly these tools, replacing the default tool set as the base. Also gates dynamically registered (MCP) tools
include_toolsstring[]?Tools added on top of the base set (tools, or the default set)
exclude_toolsstring[]?Tools removed from the session; wins over tools and include_tools
cwdstring?Working directory. Defaults to daemon's process directory
effortEffort?Effort override: "min", "low", "medium", "high", "xhigh", or "max". null resolves from the catalog
enable_auto_memorybool?Whether the agent records and recalls auto-memory. null leaves the daemon default (on for interactive sessions)

UpdateSession

Update the active session without restarting it (e.g. switch models or permission mode mid-session). Each field is optional; an absent field is left unchanged. A permission-mode change — what Shift+Tab cycling sends from the TUI — takes effect immediately, applying to the turn already in flight as well as later ones; a model change applies on the next turn. Other session options must be set at StartSession time.

{
"op": {
"UpdateSession": {
"model": { "id": "gpt-5.4", "temperature": 0.2, "effort": "high" },
"permission_mode": "auto"
}
},
"id": "op_..."
}

SessionUpdate fields:

FieldTypeDescription
modelModelSpec?New model specification to use. Carries the whole request, effort included: a set effort overrides the catalog default; unset fields resolve from the catalog
permission_modePermissionMode?New permission mode ("strict", "auto", or "yolo")

ResumeSession

Resume a previously persisted session by its ID. The daemon restores the conversation history, re-discovers extensions, and replays recent events so the client can rebuild its view.

{ "op": { "ResumeSession": { "session_id": "ses_01ARZ..." } }, "id": "op_..." }
FieldTypeDescription
session_idIdThe session identifier to resume

On success the daemon emits SessionEnd for the current session (if any), then SessionStart and ExtensionRefreshed for the resumed session, followed by up to 200 replayed historical events. On failure it emits an Error event.

Steer

Provide additional guidance to the agent during an active turn without starting a new one.

{ "op": { "Steer": "focus on the auth module first" }, "id": "op_..." }

UserInput

Submit user text input to the agent.

{ "op": { "UserInput": "explain what this project does" }, "id": "op_..." }

ApprovalResponse

Respond to a tool approval request (sent after receiving a TurnPause event with Approval reason).

{
"op": {
"ApprovalResponse": {
"turn_id": "step_01ARZ...",
"responses": [
["tool_use_abc123", "Accept"],
["tool_use_def456", "Skip"]
]
}
},
"id": "op_..."
}

ReviewDecision values:

DecisionDescription
AcceptAllow this tool call
SkipSkip this tool call
AcceptForSessionGrant this call for the rest of the session (scoped — see Session grants)
AcceptAlwaysGrant for the session and persist an allow rule to settings.json ("always allow")
AbortAbort the current task

SlashCommand

Invoke a skill by name.

{ "op": { "SlashCommand": { "name": "commit", "args": "-m 'fix bug'" } }, "id": "op_..." }

Interrupt

Abort whatever is currently running.

{ "op": "Interrupt", "id": "op_..." }

Shutdown

Request a graceful shutdown.

{ "op": "Shutdown", "id": "op_..." }

RegisterLocalProvider

Register a running local inference server (e.g. an offline llama-server started by the client) as the local provider for this daemon. The model is optional — if provided, it pins the local provider to a specific ModelSpec; otherwise the local provider serves whatever model the server is hosting.

{
"op": {
"RegisterLocalProvider": {
"port": 8080,
"model": null
}
},
"id": "op_..."
}
FieldTypeDescription
portu16Port the local llama-server is listening on
modelModelSpec?Optional model spec to pin the provider to

RestoreLocalProvider

Restore the previously registered local provider configuration after a session-level provider switch. Useful when the daemon needs to revert to a previously registered local server without re-supplying its port and model.

{ "op": "RestoreLocalProvider", "id": "op_..." }

Events (Daemon → Client)

Session events

SessionStart

Emitted when a session is initialized. Contains metadata about the active model, provider, session ID, and working directory. Skills and subagents are delivered separately via ExtensionRefreshed.

{
"event": {
"SessionStart": {
"model": { "id": "claude-sonnet-4-6", "max_tokens": 8192 },
"provider": "anthropic",
"session_id": "ses_01ARZ...",
"cwd": "/home/user/project",
"permission_mode": "strict"
}
}
}

SessionInitialized fields:

FieldTypeDescription
modelModelSpecActive model specification
providerstringActive provider name (e.g. "anthropic")
session_idIdUnique session identifier
cwdstringWorking directory path
permission_modePermissionModeActive permission mode ("strict", "auto", or "yolo")

SessionUpdated

Emitted when the active session is updated in place (e.g. model changed via UpdateSession).

{
"event": {
"SessionUpdated": {
"model": { "id": "gpt-5.4" },
"provider": "openai",
"session_id": "ses_01ARZ...",
"cwd": "/home/user/project"
}
}
}

SessionEnd

Emitted when the session terminates.

{ "event": "SessionEnd" }

Turn lifecycle events

TurnStart

Emitted when a new turn begins processing.

{ "event": { "TurnStart": { "turn_id": "step_01ARZ..." } } }

TurnPause

Emitted when a turn is paused waiting for user input (e.g. tool approval).

{
"event": {
"TurnPause": {
"turn_id": "step_01ARZ...",
"reason": {
"Approval": {
"tools": [
{ "id": "tool_use_abc123", "name": "Bash", "input": { "command": "ls -la" } }
],
"message": "Allow running shell command?"
}
}
}
}
}

TurnPauseReason variants:

VariantFieldsDescription
Approvaltools: ToolUse[], message: stringWaiting for tool approval

TurnEnd

Emitted when a turn completes.

{ "event": { "TurnEnd": { "turn_id": "step_01ARZ...", "status": "Completed" } } }

TurnEndStatus variants:

VariantFieldsDescription
CompletedTurn finished successfully
Interruptedreason?: stringTurn was interrupted
Errormessage: stringTurn ended with an error

Message streaming events

AgentMessage

Complete agent text response (non-streaming).

{ "event": { "AgentMessage": "The project is a web server that..." } }

Thinking

Complete chain-of-thought block (non-streaming).

{ "event": { "Thinking": "Let me analyze the codebase structure..." } }

MessageDelta

Streaming chunk of the agent's message. Concatenate all deltas to build the full message.

{ "event": { "MessageDelta": "The project" } }

ThinkingDelta

Streaming chunk of the agent's thinking. Concatenate all deltas to build the full thinking block.

{ "event": { "ThinkingDelta": "Let me" } }

Tool events

ToolStart

Emitted when a tool invocation begins.

{
"event": {
"ToolStart": {
"id": "tool_use_abc123",
"name": "Read",
"input": { "file_path": "/src/main.rs" }
}
}
}

ToolUpdate

Progress update during tool execution.

{
"event": {
"ToolUpdate": {
"tool_use_id": "tool_use_abc123",
"seq": 0,
"message": "Reading file..."
}
}
}
FieldTypeDescription
tool_use_idstringTool call identifier (matches ToolStart.id)
sequ64Monotonically increasing sequence number
messagestringProgress message

ToolEnd

Emitted when a tool execution completes.

{
"event": {
"ToolEnd": {
"tool_use_id": "tool_use_abc123",
"status": "Completed",
"result_json": { "content": "fn main() { ... }" },
"is_error": false
}
}
}

ToolEndStatus variants:

VariantDescription
CompletedTool ran successfully
CancelledTool execution was cancelled
DeniedTool was denied by the user
FailedTool execution failed

Compaction events

CompactStart

Emitted when dialog compaction begins.

{ "event": "CompactStart" }

CompactEnd

Emitted when dialog compaction completes.

{ "event": "CompactEnd" }

Extension events

ExtensionRefreshed

Emitted when skills, subagents, or MCP servers are refreshed. Sent at least twice during a normal session: once right after SessionStart with the initial skills/subagents (and mcp_servers: []), and again after MCP warm-up completes in the background with the discovered server and tool list.

{
"event": {
"ExtensionRefreshed": {
"session_id": "ses_01ARZ...",
"skills": [
{ "name": "commit", "description": "Create a git commit", "scope": "user", "argument_hint": "-m 'message'" }
],
"subagents": [
{ "name": "explore", "description": "Explore the codebase", "scope": "project" }
],
"mcp_servers": [
{
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"tools": [
{
"name": "read_text_file",
"qualified_name": "mcp__filesystem__read_text_file",
"description": "Read the contents of a text file",
"parameters": [
{ "name": "path", "param_type": "string", "required": true, "description": "Path to the file" }
]
}
]
}
]
}
}
}

Each entry in mcp_servers reflects what was actually launched and discovered: the tools list is empty for servers that failed to connect, and qualified_name is the mcp__<server>__<tool> form the agent uses to call the tool.

Informational events

UsageUpdate

Token usage statistics for the session. context is omitted before the first response or when the model context limit is unknown. It carries raw measurements only — used_tokens (cache-inclusive window occupancy) and limit_tokens (the raw model context limit); any percentage is derived by the client. usage may also include cache_read_tokens and cache_creation_tokens when the provider reports them.

{
"event": {
"UsageUpdate": {
"usage": { "input_tokens": 1500, "output_tokens": 300 },
"context": { "used_tokens": 1800, "limit_tokens": 200000 }
}
}
}

Info

General informational message.

{ "event": { "Info": "Compacting conversation history..." } }

InfoBlockStart

Opens a grouped Info entry with a header. Subsequent InfoBlockAppend events sharing the same id render as tree-indented child lines under it — used for multi-step background notifications (e.g. MCP warm-up) that should visually cluster.

{
"event": {
"InfoBlockStart": {
"id": "mcp-warmup-ses_01ARZ...",
"header": "Warming up 4 MCP servers in the background — tools will become available as they connect."
}
}
}

InfoBlockAppend

Appends a child detail line to the InfoBlockStart with the same id. Drops silently if the matching block isn't present.

{
"event": {
"InfoBlockAppend": {
"id": "mcp-warmup-ses_01ARZ...",
"detail": "MCP ready: 4/4 servers connected, 60 tools registered."
}
}
}

Error

Error message.

{ "event": { "Error": "Authentication failed: invalid API key" } }

Goodbye

Final message before the daemon disconnects. After receiving this, no more events will be sent.

{ "event": "Goodbye" }

UserInput

User input recorded for session replay. This variant is written to the persisted event log but is not emitted during live sessions — you will only see it in events replayed by ResumeSession.

{ "event": { "UserInput": "explain what this project does" } }

Offline mode and the protocol

Local-model management (engine installation, model discovery, llama-server lifecycle) is client-side. It is driven through the in-process OfflineSubsystem API, not through the daemon protocol. The only daemon-facing surface for offline mode is RegisterLocalProvider / RestoreLocalProvider — the client starts a llama-server itself, then registers its port (and an optional ModelSpec) with the daemon.

See the Offline Mode page for the user-facing flows and the --offline-model CLI flag.

Transport

In-process channels

When the client and daemon run in the same process (TUI or headless mode), they communicate via bounded Tokio mpsc channels:

ChannelDirectionBuffer size
Op channelClient → Daemon256 messages
Event channelDaemon → Client4096 messages

Stdio transport (JSONL)

For external clients using ante serve (or ante serve --stdio), StdioTransport bridges JSON Lines over stdin/stdout to the internal channel pair:

  • stdin → Parse each line as OpMsg → forward to daemon
  • daemon events → Serialize as JSON → write to stdout (one line per event)
  • EOF on stdin → Automatically sends Op::Shutdown
  • Evt::Goodbye received → Transport exits
  • JSON parse errors → An Evt::Error is sent back on stdout

WebSocket transport

For networked or browser-based clients using ante serve --ws <ADDR>, WsTransport exchanges the same JSONL protocol over WebSocket frames:

  • Each WebSocket connection gets its own daemon instance
  • Messages are the same OpMsg and EventMsg JSON objects, sent as text frames
  • Client disconnect → Daemon instance shuts down, server accepts next connection
  • Op::Shutdown received → Connection and server both exit
  • The server loops accepting new connections until a Shutdown is received

Complete flow example

A full session lifecycle from start to shutdown:

Client Daemon
│ │
│─── OpMsg { StartSession(...) } ──────▶│
│◀── EventMsg { SessionStart(...) } ────│
│ │
│─── OpMsg { UserInput("fix bug") } ───▶│
│◀── EventMsg { TurnStart { turn_id } } │
│◀── EventMsg { ThinkingDelta("...") } │
│◀── EventMsg { ThinkingDelta("...") } │
│◀── EventMsg { Thinking("...") } │
│◀── EventMsg { MessageDelta("...") } │
│◀── EventMsg { ToolStart(ToolUse) } │
│◀── EventMsg { TurnPause(Approval) } │
│ │
│─── OpMsg { ApprovalResponse(...) } ──▶│
│◀── EventMsg { ToolUpdate(...) } │
│◀── EventMsg { ToolEnd(...) } │
│◀── EventMsg { MessageDelta("...") } │
│◀── EventMsg { AgentMessage("...") } │
│◀── EventMsg { UsageUpdate(...) } │
│◀── EventMsg { TurnEnd(Completed) } │
│ │
│─── OpMsg { Shutdown } ───────────────▶│
│◀── EventMsg { SessionEnd } │
│◀── EventMsg { Goodbye } │
│ │