Protocol Reference
Ante uses a typed message-passing protocol between the client and daemon. In-process clients use a bounded operation channel and an unbounded event channel; external clients exchange the same messages over JSON Lines or WebSocket frames.
Wire format
External clients communicate with the daemon using JSON Lines (JSONL) — one JSON object per line over stdin/stdout.
- Client → Daemon: Send
OpMsgobjects as JSON lines to stdin - Daemon → Client: Receive
EventMsgobjects as JSON lines from stdout
OpMsg envelope
Every operation is wrapped in an OpMsg:
{
"op": { "StartSession": { "model": "claude-sonnet-5-5", "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}.
| Prefix | Usage |
|---|---|
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, replacing any session already running. A field supplied by the client is pinned; an omitted or null field resolves from the host's settings at this session boundary, or from Ante's built-in default when the setting is absent.
{
"op": {
"StartSession": {
"model": "claude-sonnet-5-5",
"provider": "anthropic",
"credential": null,
"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,
"short_prompt": null,
"no_skills": null,
"include_skills": null,
"exclude_skills": null,
"save_session": null,
"unattended": null,
"title": null
}
},
"id": "op_..."
}
SessionRequest fields:
| Field | Type | Description |
|---|---|---|
model | string? | Model name (e.g. "claude-sonnet-5-5"); null uses the host default |
provider | string? | Provider name (e.g. "anthropic", "openai", "gemini", "xai", "openrouter", "local"); null uses the host default |
credential | string? | Pin a credential reference, such as "oauth:anthropic/work" on anthropic-subscription, from the first request. The start is refused if the reference is invalid, unavailable, or not accepted by the provider. Omitted or null uses configured credential precedence. Available since v0.3.1 |
permission_mode | PermissionMode? | Tool approval mode: "strict", "auto", or "yolo"; null uses the host default |
system_prompt | string? | Override the default system prompt entirely |
append_system_prompt | string? | Append content to the default system prompt |
tools | string[]? | Exactly these tools, replacing the default tool set as the base. Also gates dynamically registered (MCP) tools |
include_tools | string[]? | Tools added on top of the base set (tools, or the default set) |
exclude_tools | string[]? | Tools removed from the session; wins over tools and include_tools |
cwd | string? | Working directory. Defaults to daemon's process directory |
effort | Effort? | Effort override: "min", "low", "medium", "high", "xhigh", or "max". null resolves from the catalog |
enable_auto_memory | bool? | Whether the agent records and recalls auto-memory. null leaves the daemon default (on for interactive sessions) |
short_prompt | bool? | Use shorter built-in tool descriptions; the system instructions stay the same. null leaves the settings default |
no_skills | bool? | When true, skip all skill discovery so no skills are advertised or invocable. null leaves the session default |
include_skills | string[]? | Exact skill names added to the session's base set. A supplied list replaces the host's include_skills setting and does not enable discovery when no_skills is true |
exclude_skills | string[]? | Exact skill names removed from the session. A supplied list replaces the host's exclude_skills setting and wins over include_skills |
save_session | bool? | Whether to write a transcript and resumable snapshot. null leaves the host's session-saving default |
unattended | bool? | Declare that no client can answer approval prompts. When true, a call that would require approval is denied instead of pausing. Omitted or null means false |
title | string? | A display name for the session, shown in place of the first message in resume pickers. Trimmed; empty after trimming means no title. null starts the session untitled |
SessionRequest replaced the former SessionOverrides name in v0.preview.92. The JSON field names stayed the same, but absence now has one explicit meaning for StartSession: resolve the host's default now, never “leave the previous session unchanged.” A client implementing /clear should retain and resend its own request, applying any model or permission-mode changes it made; there is no separate restart operation.
Use ante auth list --json to discover stored credential references and the providers that accept them. Credential pins apply to the current session only and are not saved with the conversation; send the reference again when resuming. A rejected pin does not fall back to another credential.
UpdateSession
Update the active session without restarting it (e.g. switch providers, 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 provider or model change applies on the next turn. A title change applies at once and is echoed back on the SessionUpdated event. Other session options must be set at StartSession time.
A provider change continues the conversation on the named catalog provider, keeping the session ID, messages, title, and permission state. model then names a model on that provider; when it is absent, the provider's default model is used. Naming the current provider is not a switch. The whole update is refused — answered with an Error instead of SessionUpdated — if the provider ID is unknown, or if a turn is in flight when a different provider is requested; the session is never re-routed to another provider. Interrupt the running turn first to switch away from a failing or rate-limited provider. Available since v0.2.5.
{
"op": {
"UpdateSession": {
"provider": "openai",
"model": { "id": "gpt-6.1-sol", "temperature": 0.2, "effort": "high" },
"permission_mode": "auto",
"title": "auth refactor"
}
},
"id": "op_..."
}
SessionUpdate fields:
| Field | Type | Description |
|---|---|---|
provider | string? | Catalog provider ID to switch to (e.g. "openai"), keeping the conversation. Refused with Error for an unknown ID or while a turn is in flight |
model | ModelSpec? | New model specification to use. Carries the whole request, effort included: a set effort overrides the catalog default; unset fields resolve from the catalog. Resolved on the session's provider — the one provider names when both are set |
permission_mode | PermissionMode? | New permission mode ("strict", "auto", or "yolo") |
title | string? | Rename the session. The text is trimmed; an empty or whitespace-only string clears the title |
credential | string? | Pin a credential reference such as "oauth:openai/work" on the resulting provider for this session. Refused if the provider does not accept it, it has no value, or a turn is running. Absent keeps the current provider's choice; a switch to another provider starts from its configured order. Available since v0.3.0 |
ResumeSession
Resume a previously persisted session by its ID. The daemon restores the saved dialog and pinned model, provider, and effort, resolves anything the snapshot did not pin from the host's current defaults, re-discovers extensions, and replays the complete persisted conversation so the client can rebuild its view.
Saved system prompts and prompt mode are restored as sent, including replacements, appended instructions, and short_prompt. Current launch prompt flags apply only to fresh sessions. A change in local date, OS, or shell is reported to the model once on its next turn; an unchanged environment adds no notice.
Permission mode always resolves from the host's current settings. Older snapshots still load, but their saved permission mode is ignored and omitted on the next save.
{ "op": { "ResumeSession": { "session_id": "ses_01ARZ...", "unattended": false } }, "id": "op_..." }
| Field | Type | Description |
|---|---|---|
session_id | Id | The session identifier to resume |
unattended | bool | Whether no client can answer approval prompts for this resumed incarnation. Defaults to false when absent; when true, would-be prompts are denied at admission |
tools | string[]? | Base tool set for this resume, replacing the default set and also gating MCP tools. Omitted or null uses current settings. Available since v0.3.1 |
include_tools | string[]? | Tools added to the base set for this resume. Omitted or null adds none. Available since v0.3.1 |
exclude_tools | string[]? | Tools removed for this resume; wins over tools and include_tools. Omitted or null excludes none. Available since v0.3.1 |
credential | string? | Credential reference to pin before the first resumed request, under the same validation rules as StartSession.credential. Omitted or null uses configured precedence. Available since v0.3.1 |
For example, resume a conversation saved on anthropic-subscription with the work account and a restricted tool set:
{
"op": {
"ResumeSession": {
"session_id": "ses_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"credential": "oauth:anthropic/work",
"tools": ["Read", "Glob", "Grep"]
}
},
"id": "op_01ARZ3NDEKTSV4RRFFQ69G5FAV"
}
The base tool set resolves from the current tools setting unless this request supplies tools; the saved conversation does not restore an old tool set. Additions and exclusions apply to this resume only.
On success the daemon emits SessionEnd for the current session (if any), then SessionStart and ExtensionRefreshed for the resumed session, followed by the complete persisted event log in order, excluding session lifecycle events and malformed lines. There is no 200-event cap. On failure it emits an Error event.
RewindSession
Restore the active session's conversation and visible history to just before input to. The ID is the TurnStart.turn_id for that input (for user input, the submitting operation's ID). The chosen input and everything after it are removed; no new turn starts and no file edits or other external effects are undone.
{ "op": { "RewindSession": { "to": "op_input_1" } }, "id": "op_rewind_1" }
The session must be saved, with no running or queued turn, active goal, or running background job. The input must still be reachable: saved input boundaries survive resume and are copied into forks, but a successful history-replacing compaction or provider switch invalidates earlier boundaries. Failed compaction leaves them intact. A refused request emits Error without changing the conversation. Success emits SessionRewound.
ForkSession
Save a new session from the active conversation. Set at to a reachable input ID to copy the conversation just before it, using the same cut as RewindSession; omit at (or pass null) to copy the whole conversation.
{ "op": { "ForkSession": {} }, "id": "op_fork_1" }
The source must be saved with no running or queued turn and no active goal. It stays unchanged. The new session is saved but not started: SessionForked returns its ID, and the client sends ResumeSession to switch to it. A refused request emits Error and creates no session. Forking copies conversation state, not files, a Git worktree, or running processes.
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_..." }
ShellInput
Run a shell command on the daemon side without starting a turn — the !command path in the TUI. The daemon answers with a ShellOutput event and queues the output into the agent's context for the next turn.
{ "op": { "ShellInput": "git status" }, "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_id": "tool_use_abc123", "decision": "Accept" },
{
"tool_use_id": "tool_use_def456",
"decision": "Deny",
"message": "use the read-only endpoint"
}
]
}
},
"id": "op_..."
}
ToolDecision fields:
| Field | Type | Description |
|---|---|---|
tool_use_id | string | Tool call being decided (matches ToolStart.id) |
decision | ReviewDecision | One of the values below |
message | string? | Feedback returned with a denial. Omit for every other decision |
ReviewDecision values:
| Decision | Description |
|---|---|
Accept | Allow this tool call |
Deny | Deny this tool call. The agent receives a failed tool result — Tool call denied by user: <message> when a message is present, otherwise Tool call denied by user and was not executed. |
AcceptForSession | Grant this call for the rest of the session (scoped — see Session grants) |
AcceptAlways | Grant for the session and persist an allow rule to settings.json ("always allow") |
QuestionResponse
Resolve a pending question pause (TurnPauseReason::Question). Sent after the user answers, skips, or requests a discussion on structured questions asked by AskUser.
{
"op": {
"QuestionResponse": {
"turn_id": "step_01ARZ...",
"tool_use_id": "toolu_1",
"reply": {
"Answered": [
{ "selected": ["JWT (Recommended)"], "note": null }
]
}
}
},
"id": "op_..."
}
| Field | Type | Description |
|---|---|---|
turn_id | Id | The paused turn's identifier |
tool_use_id | string | Tool call identifier matching the pause |
reply | QuestionReply | Answered(QuestionAnswer[]), Dismissed, or Discuss { message? } |
The Answered array corresponds to TurnPause.reason.Question.questions in question order. Each QuestionAnswer contains:
| Field | Type | Description |
|---|---|---|
selected | string[] | Exact labels of selected options, not indices; empty for a free-text-only answer |
note | string? | Additional text, or the answer itself when selected is empty; omit when no text was entered |
An empty selection with no note leaves that question unanswered. To skip the whole prompt, send "reply": "Dismissed". To discuss it, send "reply": { "Discuss": { "message": "Explain the options first" } }; Discuss.message is optional.
SlashCommand
Invoke a skill by name.
{ "op": { "SlashCommand": { "name": "commit", "args": "-m 'fix bug'" } }, "id": "op_..." }
Compact
Manually trigger conversation compaction on the active session (what /compact sends). Optional instructions steer what the replacement summary emphasizes or preserves; they are ignored when the reducer can reclaim enough context without writing a summary. The daemon emits CompactStart and CompactEnd around the operation.
{ "op": { "Compact": {} }, "id": "op_..." }
{ "op": { "Compact": { "instructions": "focus on the API changes" } }, "id": "op_..." }
Compact is a struct variant in v0.preview.90; clients must send the empty object form when they have no instructions rather than the older bare string form.
ContextReport
Request a per-category breakdown of the active session's context-window occupancy (what /context sends). Answered with a ContextReport event.
{ "op": "ContextReport", "id": "op_..." }
Goal
Set, clear, or report a goal-driven execution loop on the active session. A set goal keeps the session working — re-running turns and judging the condition after each one — until it is met, judged unreachable, or cleared.
{ "op": { "Goal": { "Set": "all tests pass" } }, "id": "op_..." }
{ "op": { "Goal": "Clear" }, "id": "op_..." }
{ "op": { "Goal": "Status" }, "id": "op_..." }
AmbientPhrase / AmbientSuggestion
Request an ad-hoc ambient prediction off the conversation's critical path on a cheap model: AmbientPhrase predicts a spinner "thinking phrase" for the in-progress draft, and AmbientSuggestion predicts a next-prompt suggestion from the last exchange. Both answer with an Ambient event; the monotonic req_id lets the client discard stale results.
{ "op": { "AmbientPhrase": { "draft": "refactor the parser to...", "req_id": 3 } }, "id": "op_..." }
{ "op": { "AmbientSuggestion": { "recent_user": "fix the bug", "recent_agent": "Fixed — the cause was...", "req_id": 7 } }, "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 endpoint (for example, Ante's managed llama serve router or an externally run llama-server) 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 endpoint is hosting.
{
"op": {
"RegisterLocalProvider": {
"port": 8080,
"model": null
}
},
"id": "op_..."
}
| Field | Type | Description |
|---|---|---|
port | u16 | Port the local inference server or router is listening on |
model | ModelSpec? | 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. It announces the session identity, mutable settings, and the skills and subagents equipped for the session. ExtensionRefreshed immediately repeats those capability lists alongside MCP state for clients that replace all extension state from one event.
{
"event": {
"SessionStart": {
"model": { "id": "claude-sonnet-5-5", "max_tokens": 8192 },
"provider": {
"id": "anthropic",
"display_name": "Anthropic",
"base_url": "https://api.anthropic.com/v1"
},
"session_id": "ses_01ARZ...",
"cwd": "/home/user/project",
"permission_mode": "strict",
"skills": [],
"subagents": []
}
}
}
SessionInfo fields:
| Field | Type | Description |
|---|---|---|
model | ModelSpec | Active model specification |
provider | ProviderSpec | Active provider (see below) |
session_id | Id | Unique session identifier |
cwd | string | Working directory path |
permission_mode | PermissionMode | Active permission mode ("strict", "auto", or "yolo") |
skills | SkillMetadata[] | Skills the user can invoke in this session. Defaults to an empty list when absent for backward compatibility |
subagents | SubagentMetadata[] | Subagents this session can delegate to. Defaults to an empty list when absent for backward compatibility |
title | string? | The session's title when one is set — chosen by the user or the client, never derived from the conversation. Omitted from the payload entirely when unset |
forked_from | Id? | Source session ID for a fork. Omitted for sessions that were not forked; distinct from subagent parentage |
The embedded ModelSpec uses the model fields documented in the Catalog Reference. Since v0.preview.92, its optional supported_efforts field carries a custom model's accepted effort ladder; it replaces the old output-only effort_options field.
ProviderSpec fields:
| Field | Type | Description |
|---|---|---|
id | string | Provider key, e.g. "anthropic" (accepts name as a legacy alias) |
display_name | string | Friendly name for display |
base_url | string | Endpoint actually in use for this session — environment overrides can move it away from the catalog default |
credential | string? | The session's explicit credential choice, as a reference such as "oauth:antix/work", never a token. Omitted when the provider uses its configured order. Available since v0.3.0 |
The payload type was named SessionInitialized before v0.preview.94 and is now SessionInfo. This is a Rust type name only — the event names (SessionStart, SessionUpdated) and the JSON are unchanged, so no client needs to adapt.
ProviderSpec deliberately carries only what a client cannot look up for itself. The provider's model list is not on the wire; it is identical for every session and is published by ante catalog. Unknown fields are ignored, so payloads from older daemons that still send preferred_models decode fine.
SessionRewound
Confirms that the active conversation and its persisted history now stand just before to. This acknowledgment is never included in resume replay. A client should remove the selected input and later entries from its displayed conversation after receiving it.
{ "event": { "SessionRewound": { "to": "op_input_1" } }, "parent": "op_rewind_1" }
SessionForked
Confirms that session_id was saved from forked_from, before input at when present or from the whole conversation when omitted. It does not announce a running session; resume the returned ID to start using it.
{ "event": { "SessionForked": { "forked_from": "ses_parent", "session_id": "ses_child" } }, "parent": "op_fork_1" }
SessionUpdated
Emitted when the active session is updated in place (e.g. model changed via UpdateSession). Carries the same fields as SessionStart.
{
"event": {
"SessionUpdated": {
"model": { "id": "gpt-6.1-sol" },
"provider": {
"id": "openai",
"display_name": "OpenAI",
"base_url": "https://api.openai.com/v1"
},
"session_id": "ses_01ARZ...",
"cwd": "/home/user/project",
"permission_mode": "strict",
"skills": [],
"subagents": []
}
}
}
SessionEnd
Emitted when the session span closes. Mirrors TurnEnd: it carries the session's identity, why it ended, and its final usage accounting.
{
"event": {
"SessionEnd": {
"session_id": "ses_01ARZ...",
"reason": "Shutdown",
"usage": { "input_tokens": 15000, "output_tokens": 3000 }
}
}
}
SessionEndReason variants:
| Variant | Description |
|---|---|
Replaced | The session was replaced by a new or resumed session |
Shutdown | The daemon is shutting down |
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:
| Variant | Fields | Description |
|---|---|---|
Approval | tools: ToolUse[], message: string | Waiting for tool approval |
Question | tool_use_id: string, questions: QuestionSpec[] | Waiting for user response to structured questions from AskUser |
QuestionSpec fields:
| Field | Type | Description |
|---|---|---|
header | string | Short label for the question |
question | string | Full question text |
multi_select | bool | Whether multiple options may be selected; defaults to false. The wire field is multi_select, while the tool input uses multiSelect |
options | QuestionOption[] | Offered choices; the client adds its own free-text "Other" input |
Each QuestionOption has label and description strings, plus an optional preview string for a code snippet, mockup, or config excerpt. Other tools in the batch may keep running during a question pause. Keep consuming events and clear any pending question on TurnResume or TurnEnd.
TurnResume
Emitted when the turn resumes after a TurnPause (e.g. the approval was answered or a steer arrived), so clients never have to infer resumption from the next tool event.
{ "event": { "TurnResume": { "turn_id": "step_01ARZ..." } } }
TurnEnd
Emitted when a turn completes. steps is the number of turn-loop steps attempted before the turn ended.
{ "event": { "TurnEnd": { "turn_id": "step_01ARZ...", "status": "Completed", "steps": 4 } } }
TurnEndStatus variants:
| Variant | Fields | Description |
|---|---|---|
Completed | — | Turn finished successfully |
Interrupted | reason?: string | Turn was interrupted |
Error | kind?: string, headline: string, details: string[] | Turn ended with an error. kind is a stable machine-readable LLM error kind when available — notably "oauth" means the provider sign-in is missing, expired, or revoked. headline is a one-line summary; details are expanded cause lines |
Message streaming events
AgentMessage
Complete agent text response, assembled from the stream. Clients displaying deltas should avoid rendering this text a second time.
{ "event": { "AgentMessage": "The project is a web server that..." } }
Thinking
Complete reasoning block, assembled from the stream. Clients displaying reasoning deltas should avoid rendering this text a second time.
{ "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..."
}
}
}
| Field | Type | Description |
|---|---|---|
tool_use_id | string | Tool call identifier (matches ToolStart.id) |
seq | u64 | Monotonically increasing sequence number |
message | string | Progress message |
ToolEnd
Emitted when a tool execution completes.
{
"event": {
"ToolEnd": {
"tool_use_id": "tool_use_abc123",
"tool_name": "Read",
"status": "Completed",
"result_json": { "content": "fn main() { ... }" }
}
}
}
ToolEndStatus variants:
| Variant | Description |
|---|---|
Completed | Tool ran successfully |
Cancelled | Tool execution was cancelled |
Denied | Tool was denied by the user |
Failed | Tool execution failed |
tool_name is populated even when execution never starts, so clients can render denied, cancelled, and malformed calls without reconstructing the name from a preceding event. Older events may deserialize it as an empty string. There is no separate is_error field: derive it from status (Completed is the only non-error status). Every accepted tool call gets a terminal ToolEnd, and concurrently running calls emit it independently as each one settles.
TaskEnd
Emitted when tracked work handed off by a tool call finishes, such as a Bash command that outlived its inline wait window. Available since v0.2.3. The earlier ToolEnd completes the tool call and returns the background handle; it does not mean the process has exited.
{ "event": { "TaskEnd": { "tool_use_id": "tool_use_abc123", "exit_code": 0 } } }
tool_use_id identifies the original call. exit_code is the process exit code (128 + signal for signal termination), or null if the exit could not be observed. Clients can use it to settle the original running-job display. The event is persisted and replayed when the session resumes.
Compaction events
CompactStart
Emitted when dialog compaction begins.
{ "event": "CompactStart" }
CompactEnd
Emitted when dialog compaction completes. A non-null summary is the text committed as the replacement for compacted history and carried forward as context; clients should invalidate earlier rewind and fork targets. null means no history replacement was committed, so those targets remain valid. Do not invalidate targets on CompactStart alone.
{ "event": { "CompactEnd": { "summary": "The user asked to..." } } }
Extension events
ExtensionRefreshed
Emitted when skills, subagents, or MCP servers are refreshed. It is sent once immediately after SessionStart with the same skills/subagents and mcp_servers: []. When MCP servers are configured, a second event follows after warm-up with the discovered servers and tools; with no MCP configuration, the initial event is the only one.
{
"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 describes a configured server and its discovered tools. Local servers have command and args; remote servers have url, an empty command, and empty args. The optional url field was added in v0.2.12 and is omitted for local servers. The tools list is empty for servers that failed to connect or exposed no tools, 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, when the model context limit is unknown, and on usage forwarded from a sub-agent. A client should add every usage value to its totals but retain the last root-session context snapshot when context is absent; delegated work must not replace the root indicator. A present context 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 }
}
}
}
ContextReport
Answer to the ContextReport operation: a per-category breakdown of the session's context-window occupancy at the time of the request.
{
"event": {
"ContextReport": {
"system_prompt_tokens": 3200,
"system_tools_tokens": 4100,
"mcp_tools_tokens": 900,
"memory_tokens": 600,
"skills_tokens": 450,
"messages_tokens": 12000,
"used_tokens": 21250,
"limit_tokens": 200000,
"compact_buffer_tokens": 20000
}
}
}
| Field | Type | Description |
|---|---|---|
system_prompt_tokens | u32 | System prompt, excluding the skills and memory sections counted separately |
system_tools_tokens | u32 | Built-in tool schemas |
mcp_tools_tokens | u32 | MCP tool schemas |
memory_tokens | u32 | Project/user instruction files and the auto-memory prompt |
skills_tokens | u32 | The available-skills listing |
messages_tokens | u32 | Conversation messages — everything not attributed to a category above |
used_tokens | u32 | Total context-window occupancy |
limit_tokens | u32? | Model context limit; null when unverified |
compact_buffer_tokens | u32 | Tokens reserved at the top of the window; auto-compaction triggers once occupancy grows into this reserve |
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. When loading is true the renderer animates a ./../... suffix on the header until the first InfoBlockAppend arrives.
{
"event": {
"InfoBlockStart": {
"id": "mcp-warmup-ses_01ARZ...",
"header": "Warming up 4 MCP servers in the background — tools will become available as they connect.",
"loading": true
}
}
}
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" } }
ShellOutput
Answer to a ShellInput operation: the command that ran and what it produced.
{
"event": {
"ShellOutput": {
"command": "git status",
"stdout": "On branch main...",
"stderr": "",
"exit_code": 0
}
}
}
Ambient
An ephemeral ambient hint produced off the main conversation on a cheap model — a predicted "thinking phrase" for the current draft, or a suggested next prompt shown as input ghost text. Requested with the AmbientPhrase / AmbientSuggestion operations; req_id lets clients drop superseded results. Never persisted to the event log.
{ "event": { "Ambient": { "kind": "PromptSuggestion", "req_id": 7, "text": "run the tests" } } }
Offline mode and the protocol
Local-model management (engine installation, model discovery, and local-router 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 the managed llama serve router (or connects to an external server), 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 Tokio mpsc channels:
| Channel | Direction | Capacity |
|---|---|---|
| Op channel | Client → Daemon | 256 messages |
| Event channel | Daemon → Client | Unbounded; Ante warns when the pending backlog reaches 4096 and reports when it drains |
Events are never dropped or blocked behind a slow in-process client. A client that stops reading accumulates a memory backlog, so consumers should keep draining the event receiver even while handling other work.
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::Goodbyereceived → Transport exits- JSON parse errors → An
Evt::Erroris sent back on stdout
Unix socket transport
For clients that dial a host someone else is running, ante serve --sock [PATH] listens on a Unix domain socket (default run/serve.sock in the Ante home) and exchanges the same JSONL protocol over the stream:
- Each client gets its own connection and at most one active session on the shared host
Op::Shutdownends only that connection; the host keeps accepting new ones- The host process exits on
SIGINTorSIGTERM - Ownership is an exclusive lock on a
.lockfile beside the socket, so a second host on the same path refuses to start rather than displacing the live one; a stale socket left by a crashed host holds no lock and is replaced
WebSocket transport
For networked or browser-based clients using ante serve --ws <ADDR>, WsTransport exchanges the same JSONL protocol over WebSocket frames:
- Each WebSocket client gets its own connection and session on the shared host
- Messages are the same
OpMsgandEventMsgJSON objects, sent as text frames - Client disconnect → That connection and session shut down; other clients and the host continue running
Op::Shutdownreceived → Only that connection exits- The host accepts clients concurrently until it receives
SIGINTorSIGTERM - Input remains readable while an event write is stalled; each write has a 30-second deadline, and reply-queue overflow disconnects only the offending peer
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 } │
│ │