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_TOKEN="your-secret-token" ante serve --ws 127.0.0.1:8080
The --ws flag starts a WebSocket server on the given address. ante serve --ws requires a bearer token provided via the ANTE_SERVE_TOKEN environment variable and refuses to start without one. Clients must send Authorization: Bearer <token> in the upgrade request; missing or invalid tokens are rejected with HTTP 401, and handshakes time out after 10 seconds. Each client gets its own connection and session on the shared host. The server keeps accepting connections until the host receives SIGINT or SIGTERM; a client's Shutdown closes only that connection.
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. To access a daemon across machines, dial through an SSH tunnel or an authenticating reverse proxy with TLS (wss://).
Read the server transcript
In v0.2.8, --sock and --ws print a readable transcript to stdout: connections, session starts, messages, tool calls, and turn status. Entries carry a server or client label so concurrent connections can be distinguished. Redirect stdout to keep a local transcript:
NO_COLOR=1 ante serve --sock > serve-transcript.log
Styling is enabled only on a terminal and disabled by any nonempty NO_COLOR. The transcript is best-effort: a slow output pipe can drop transcript entries without holding up protocol traffic. Clients should consume the protocol for complete events. --stdio keeps stdout exclusively for JSONL and does not print this transcript.
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
- Answer
TurnPausewithApprovalResponsefor anApprovalreason orQuestionResponsefor aQuestionreason. Clients without an interactive UI should start sessions withunattended: true - Send
Shutdown(or disconnect) to close the connection gracefully. On stdio that also ends the one-connection process; socket and WebSocket hosts continue serving other clients until signalled
See the Protocol Reference for the complete message catalog and wire format.
Protocol input is read independently from event output, so Interrupt and Shutdown remain responsive even when a peer stops consuming events. Each event write has a 30-second deadline; a stalled or response-flooding peer is disconnected instead of blocking the host.
Example session
Save this as client.cjs and run node client.cjs with Ante on PATH and Anthropic credentials configured. The client keeps stdin open, waits for SessionStart before submitting its prompt, and sends Shutdown after the turn ends or an error occurs. unattended: true denies calls that require approval and skips structured questions so this non-interactive example can finish.
const { spawn } = require("node:child_process");
const { createInterface } = require("node:readline");
const ante = spawn("ante", ["serve", "--stdio"], {
stdio: ["pipe", "pipe", "inherit"]
});
const lines = createInterface({ input: ante.stdout, crlfDelay: Infinity });
let sequence = 0;
let stopping = false;
const send = (op) => ante.stdin.write(
JSON.stringify({ op, id: `op_${++sequence}` }) + "\n"
);
const fail = (error) => {
console.error(error.message);
process.exitCode = 1;
};
ante.on("error", fail);
ante.stdin.on("error", fail);
ante.on("exit", (code) => {
if (code) process.exitCode = code;
});
lines.on("line", (line) => {
if (!line.trim()) return;
const { event } = JSON.parse(line);
console.log("Event:", event);
if (stopping) return;
if (event.SessionStart) {
send({ UserInput: "explain what this project does" });
} else if (event.TurnEnd || Object.hasOwn(event, "Error")) {
if (event.TurnEnd?.status !== "Completed") process.exitCode = 1;
stopping = true;
send("Shutdown");
ante.stdin.end();
}
});
send({ StartSession: {
model: "claude-sonnet-5-5",
provider: "anthropic",
unattended: true
} });
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::{Evt, Op, SessionRequest}};
let mut client = connect("stdio".parse()?, ConnectOptions::default()).await?;
client.send(Op::StartSession(SessionRequest {
model: Some("claude-sonnet-5-5".into()),
provider: Some("anthropic".into()),
unattended: Some(true),
..Default::default()
})).await?;
client.send(Op::UserInput("explain what this project does".into())).await?;
while let Some(event) = client.next_event().await {
println!("{event:?}");
if matches!(event.event, Evt::TurnEnd { .. } | Evt::Error(_)) {
break;
}
}
client.close().await;
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>, wss://<addr> | Dials an ante serve --ws host, presenting ConnectOptions::token as its bearer | Someone else's; the host outlives the connection |
Plain ws:// is dialed only to loopback hosts; remote connections require wss:// (under the opt-in wss feature) so the bearer token never travels across the network in cleartext.
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 across transports: executable (defaults to ante on PATH), args appended after serve --stdio, cwd, extra env, and token (the bearer token sent when dialing ws:// or wss://). 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?;
For a complete client, examples/mini-tui is a one-file chat TUI: it streams MessageDeltas, answers TurnPause approvals with y/n, and sends Interrupt on Esc. Run it with cargo run from that directory.
In SDK 0.2.2, ante_sdk::claude and the legacy child-process stdio API were removed. Ante host stdio connections remain supported through connect(Endpoint::Stdio, options), as in the example above.
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 | Defaults to yolo; explicit strict/auto denies calls that would prompt | Client must respond to TurnPause events, or declare the session unattended to deny them |
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 [MODEL] | Load a local or verified model in the shared llama serve router and register local. Accepts a path, discovered filename, or verified model name/URL; omit the value for the default saved with ante offline use |
The --prompt flag cannot be used with ante serve. Use UserInput operations instead.