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.
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.
The transport flags are mutually exclusive — pick one of --stdio, --sock, or --ws.
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
- The host starts and waits for operations on the selected transport
- Send a
StartSessionoperation to initialize a session with your chosen model and provider - Send
UserInputoperations to submit prompts - Receive streaming events — thinking deltas, message deltas, tool calls, approvals — on the same connection
- Respond to
TurnPauseevents withApprovalResponseto approve or deny tool calls - Send
Shutdown(or disconnect) to close the connection gracefully; on stdio and WebSocket,Shutdownalso 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):
| Endpoint | What the client does | Host lifetime |
|---|---|---|
stdio | Spawns ante serve --stdio as its own child | Ends with the connection — dropping the op sender closes the child's stdin |
unix:<path> | Dials the socket file of an ante serve --sock host | Someone else's; the host outlives the connection |
ws://<addr> | Names a WebSocket host, but is not connectable yet | Someone 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
| Headless | Server | |
|---|---|---|
| Lifetime | Single prompt, then exits | Long-lived, multiple interactions |
| Input | CLI argument or stdin pipe | Structured JSONL operations |
| Output | Formatted text (minimal/human/json) | Raw protocol events (JSONL) |
| Transport | N/A | Stdio (default), Unix socket (--sock), or WebSocket (--ws) |
| Sessions | Implicit — one per invocation | Explicit — client sends StartSession |
| Tool approval | Auto-approved (yolo implied) | Client must respond to TurnPause events |
CLI reference
| Flag | Description |
|---|---|
--stdio | Use 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 |
The --prompt flag cannot be used with ante serve. Use UserInput operations instead.