Skip to main content

ante serve

Server mode runs Ante as a long-lived daemon that external clients can drive through a structured message protocol. This is ideal for building editor integrations, web UIs, or any programmatic workflow that needs to control Ante over time.

Basic usage

ante serve

This starts the daemon and bridges the JSONL protocol over stdin/stdout. The process stays alive until the client sends a Shutdown operation or closes stdin.

Stdio transport (default)

ante serve --stdio

The --stdio flag is optional — stdio is the default transport. The daemon reads OpMsg JSON objects from stdin and writes EventMsg JSON objects to stdout, one per line.

Unix socket transport

ante serve --sock # ~/.ante/run/serve.sock
ante serve --sock /tmp/ante.sock

--sock hosts the protocol on a Unix domain socket, so clients that outlive any single connection can dial the host instead of spawning one each time. With no path, the socket is run/serve.sock inside the Ante home.

Each connection drives its own session. A client's Shutdown ends only that client's connection; the host keeps listening. The process exits on SIGINT or SIGTERM.

note

One host owns a socket path at a time. Ownership is an exclusive lock on a .lock file beside the socket: a second ante serve --sock on the same path refuses to start rather than stealing the path, so a live host's socket is never pulled out from under its clients. A socket file left behind by a crashed host holds no lock and is replaced on the next start, so a stale file needs no manual cleanup.

WebSocket transport

ante serve --ws 127.0.0.1:8080

The --ws flag starts a WebSocket server on the given address. Each client gets its own connection and session on the shared host. The server loops accepting new connections until a client sends a Shutdown operation.

note

The transport flags are mutually exclusive — pick one of --stdio, --sock, or --ws.

caution

The address must be a loopback address (127.0.0.0/8 or ::1). Ante refuses to bind anything else — 0.0.0.0:8080 exits with an error rather than starting. The protocol has no authentication, so a listener on a routable interface would give any client that reaches it full tool execution on your machine. Reach a remote daemon through an SSH tunnel or an authenticating reverse proxy instead.

How it works

  1. The host starts and waits for operations on the selected transport
  2. Send a StartSession operation to initialize a session with your chosen model and provider
  3. Send UserInput operations to submit prompts
  4. Receive streaming events — thinking deltas, message deltas, tool calls, approvals — on the same connection
  5. Respond to TurnPause events with ApprovalResponse to approve or deny tool calls
  6. Send Shutdown (or disconnect) to close the connection gracefully; on stdio and WebSocket, Shutdown also ends the serving process

See the Protocol Reference for the complete message catalog and wire format.

Example session

# Start the daemon, sending JSONL to its stdin via a named pipe
mkfifo /tmp/ante-pipe
ante serve < /tmp/ante-pipe &
echo '{"op":{"StartSession":{"model":"claude-sonnet-5","provider":"anthropic"}},"id":"op_001"}' > /tmp/ante-pipe
echo '{"op":{"UserInput":"what is 2+2"},"id":"op_002"}' > /tmp/ante-pipe

In practice, you'd spawn the process and write to its stdin directly. Here's a minimal Node.js example:

const { spawn } = require("child_process");

const ante = spawn("ante", ["serve"]);
let buffer = "";

ante.stdout.on("data", (chunk) => {
buffer += chunk.toString();
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (!line.trim()) continue;
const event = JSON.parse(line);
console.log("Event:", event.event);
}
});

ante.stdin.write(JSON.stringify({
op: { StartSession: { model: "claude-sonnet-5", provider: "anthropic" } },
id: "op_001"
}) + "\n");

ante.stdin.write(JSON.stringify({
op: { UserInput: "explain what this project does" },
id: "op_002"
}) + "\n");

Rust SDK

ante-sdk is the Ante client for external programs, so a Rust integration does not have to frame JSONL or manage the host process itself. connect returns a Client — an op sender and an event receiver — over any transport:

use ante_sdk::{ConnectOptions, connect, protocol::{Op, SessionRequest}};

let mut client = connect("stdio".parse()?, ConnectOptions::default()).await?;

client.send(Op::StartSession(SessionRequest {
model: Some("claude-sonnet-5".into()),
provider: Some("anthropic".into()),
..Default::default()
})).await?;

client.send(Op::UserInput("explain what this project does".into())).await?;

while let Some(event) = client.next_event().await {
println!("{event:?}");
}

An Endpoint names where a host is reachable — never a session. Which session a connection drives is decided by the ops sent over it (StartSession, ResumeSession):

EndpointWhat the client doesHost lifetime
stdioSpawns ante serve --stdio as its own childEnds with the connection — dropping the op sender closes the child's stdin
unix:<path>Dials the socket file of an ante serve --sock hostSomeone else's; the host outlives the connection
ws://<addr>Names a WebSocket host, but is not connectable yetSomeone else's

Every path yields the same Client, and a process that hosts sessions itself gets that same type from its host directly — the in-process channel carries the wire types the remote codecs serialize, so nothing a client can observe differs between them.

ConnectOptions is all optional and applies to the spawned host on the stdio path: executable (defaults to ante on PATH), args appended after serve --stdio, cwd, and extra env. Connecting succeeds at the transport level — the pipe opened, or the socket dialed. There is no greeting, so the first op's reply is the liveness check.

// Dial a host someone else is running
let client = connect("unix:/tmp/ante.sock".parse()?, ConnectOptions::default()).await?;

The crate's claude module is unrelated to the Ante protocol: it drives Claude Code as a child process.

Differences from headless mode

HeadlessServer
LifetimeSingle prompt, then exitsLong-lived, multiple interactions
InputCLI argument or stdin pipeStructured JSONL operations
OutputFormatted text (minimal/human/json)Raw protocol events (JSONL)
TransportN/AStdio (default), Unix socket (--sock), or WebSocket (--ws)
SessionsImplicit — one per invocationExplicit — client sends StartSession
Tool approvalAuto-approved (yolo implied)Client must respond to TurnPause events

CLI reference

FlagDescription
--stdioUse JSONL over stdin/stdout (default)
--sock [PATH]Serve the protocol over a Unix domain socket (default: run/serve.sock in the Ante home)
--ws <ADDR>Serve the protocol over WebSocket on a loopback address
--offline-model <PATH>Boot a local llama-server with this GGUF model and register it as the local provider for every connecting client
note

The --prompt flag cannot be used with ante serve. Use UserInput operations instead.