Give your website or app an AI agent backend in one command
octomind server turns an agent into a WebSocket service: one session per user, real tools, and a cost frame closing every turn. Your backend keeps the login and talks to it server-to-server.
curl -fsSL https://octomind.run/install.sh | bash OCTOMIND_SERVER_TOKEN="$(cat .token)" octomind server developer:general --port 0 --allow-origin http://localhost:3000 🚀 WebSocket server started on ws://127.0.0.1:62580
Connections must authenticate (OCTOMIND_SERVER_TOKEN is set)
Allowed browser origins: http://localhost:3000
Press Ctrl+C to stop the server
How it works
Run the AI agent backend with a token
Start the server on loopback with a random shared secret. Every handshake must present it as a Bearer header, so only your backend can connect. Pick the agent with a tag; each session can override it.
export OCTOMIND_SERVER_TOKEN="$(openssl rand -hex 16)" octomind server developer:general --port 8080Connect from your backend, one session per user
Your server opens the socket with the token, binds the user to their own session_id and working directory, sends the prompt, and closes when the cost frame echoes its request_id. This is the 21-line Node client the proof below ran.
// Runs on YOUR backend, after your own auth has decided who the user is. import WebSocket from 'ws'; const [session_id, prompt] = process.argv.slice(2); // session_id = one per user, your mapping const ws = new WebSocket(process.env.OCTOMIND_URL, { headers: { Authorization: `Bearer ${process.env.OCTOMIND_SERVER_TOKEN}` } }); const send = (frame) => ws.send(JSON.stringify(frame)); ws.on('open', () => send({ type: 'session', session_id, cwd: process.env.SITE_DIR, request_id: 'bind-1' }) ); ws.on('message', (raw) => { const frame = JSON.parse(raw); console.log(JSON.stringify(frame)); if (frame.type === 'status' && frame.session_id === session_id && !frame.data) { send({ type: 'message', session_id, content: prompt, request_id: 'ask-1' }); } if (frame.type === 'cost' && frame.request_id === 'ask-1') ws.close(); if (frame.type === 'error') ws.close(); });Check that nobody else gets in
A handshake without the token gets 401, an unlisted browser origin gets 403, and only the Bearer header upgrades to a socket. These are the real answers from the server started above.
H='-H Connection:Upgrade -H Upgrade:websocket -H Sec-WebSocket-Version:13 -H Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==' echo "== no token"; curl -s -i $H http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin" echo "== unlisted origin"; curl -s -i $H -H "Origin: https://evil.example" http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin" echo "== allowlisted origin, no token"; curl -s -i $H -H "Origin: http://localhost:3000" http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin" echo "== token"; curl -s -i $H -H "Authorization: Bearer $(cat .token)" http://127.0.0.1:62580/ | grep -E "^HTTP"
What you get
Sessions that outlive the connection
Send a session_id and the server resumes that session from memory or disk, or creates it under that name. The session stays after the socket closes, so a user who reloads the page picks up the same conversation from a new connection.
src/websocket/server.rs:1043Per-session working directory and role
Each bind can carry an absolute cwd and an agent tag. AGENTS.md, .agents/ guardrails, skills and tools, and the MCP project scope all follow that directory, so one server can host a different project per customer.
src/websocket/protocol.rs:44request_id correlation on every turn
Any client frame can carry a request_id of up to 256 bytes. The server echoes it in the ack and on the cost frame that closes that turn, so your backend matches answers to requests without counting frames.
src/websocket/server.rs:1835Cost split between the main model and the helpers
The cost frame carries running totals for tokens, cache reads and writes, reasoning and USD. Supervisor and compression calls are split out as aux_input_tokens, aux_output_tokens and aux_cost, so you can bill a user for exactly what their session spent.
src/websocket/protocol.rs:449Cancel from any connection
A cancel frame stops the session’s running turn wherever it runs, even from a different socket. The turn ends with its usual cost frame marked cancelled. A client that disconnects mid-turn cancels its turn too.
src/websocket/server.rs:592
One real turn, frame by frame
The Node client from step 2, run against a server started with the hero command, with a fresh session and a small site directory as cwd. The agent listed the directory with a real tool call, answered in one sentence as its AGENTS.md asked, and the cost frame closed request ask-1: $0.0137 for the session, of which $0.0015 was supervisor work. The handshake checks ran against the hero server on port 62580.
OCTOMIND_URL=ws://127.0.0.1:62777 OCTOMIND_SERVER_TOKEN="$(< .token)" SITE_DIR=/private/tmp/claude-usecases/agent-backend/work/site node agent2.mjs "usecase-demo-8ceed2" "Which files are in this site directory? Check, then answer." {"type":"status","message":"Connected to Octomind WebSocket server (role: developer:general)"}
{"type":"ack","request_id":"bind-1","message_type":"session","session_id":"usecase-demo-8ceed2","status":"received","capabilities":["message_attachments_v1","session_control_v1","run_events_v1"]}
{"type":"status","message":"Session created: usecase-demo-8ceed2","session_id":"usecase-demo-8ceed2"}
{"type":"ack","request_id":"ask-1","message_type":"message","session_id":"usecase-demo-8ceed2","status":"received"}
…
{"type":"tool_use","tool":"view","tool_id":"call_-7144401294843704817","server":"octofs","params":{"path":"."},"session_id":"usecase-demo-8ceed2"}
{"type":"tool_result","tool":"view","tool_id":"call_-7144401294843704817","server":"octofs","content":"AGENTS.md\t3L\t~32t","success":true,"session_id":"usecase-demo-8ceed2"}
…
{"type":"assistant","content":"The site directory contains a single file: `AGENTS.md`.","session_id":"usecase-demo-8ceed2"}
{"type":"cost","session_tokens":37850,"session_cost":0.013651174000000002,"input_tokens":1128,"output_tokens":50,"cache_read_tokens":36480,"cache_write_tokens":0,"reasoning_tokens":192,"aux_input_tokens":12833,"aux_output_tokens":914,"aux_cost":0.0015223740000000004,"supervisor_accounting":{
…
},"session_id":"usecase-demo-8ceed2","request_id":"ask-1"}
H='-H Connection:Upgrade -H Upgrade:websocket -H Sec-WebSocket-Version:13 -H Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ=='
echo "== no token"; curl -s -i $H http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin"
echo "== unlisted origin"; curl -s -i $H -H "Origin: https://evil.example" http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin"
echo "== allowlisted origin, no token"; curl -s -i $H -H "Origin: http://localhost:3000" http://127.0.0.1:62580/ | grep -E "^HTTP|^Missing|^Origin"
echo "== token"; curl -s -i $H -H "Authorization: Bearer $(cat .token)" http://127.0.0.1:62580/ | grep -E "^HTTP" == no token
HTTP/1.1 401 Unauthorized
Missing or invalid token. Send `Authorization: Bearer <OCTOMIND_SERVER_TOKEN>`.
== unlisted origin
HTTP/1.1 403 Forbidden
Origin not allowed. Start the server with --allow-origin <ORIGIN> to permit it.
== allowlisted origin, no token
HTTP/1.1 401 Unauthorized
Missing or invalid token. Send `Authorization: Bearer <OCTOMIND_SERVER_TOKEN>`.
== token
HTTP/1.1 101 Switching Protocols
What it doesn't do
No TLS on the socket
The server speaks plain ws:// and binds 127.0.0.1 by default. Terminate TLS at a reverse proxy such as nginx or Caddy, and keep the octomind port off the public internet. Docs →
Browsers cannot connect with the token
The token is an Authorization: Bearer header, and the browser WebSocket API cannot set headers. Put your own backend in the middle: the browser talks to your app, and your server talks to octomind. Docs →
Replies arrive whole, not token by token
The assistant frame carries the full reply once the model finishes. Show progress from the thinking, tool_use and tool_result frames that stream in while the turn runs.
No users, per-session authorization or connection cap
session_id is a name, not a secret: any connection with the token can resume or cancel any session, and the server accepts unlimited connections. Your backend decides which user maps to which session and how many sockets it opens. For hard tenant separation, run one process per tenant with its own OCTOMIND_DATA_DIR. Docs →
One turn at a time per session
A second message to a busy session gets an error saying it is busy processing another request. Queue messages per session in your backend, or wait for the cost frame before sending the next one.
Questions
What is an AI agent backend?
It is the server process that runs the agent loop, its tools and its sessions, while your app only sends messages and renders replies. With octomind, that process is octomind server, and your app talks to it over a WebSocket.
Can my frontend connect to octomind server directly?
Not with a token, because browsers cannot send the Authorization header the handshake requires. Connect from your backend, which already knows who the user is, and relay frames to the browser over your own channel.
Does the WebSocket stream tokens as the model writes?
No. Each assistant reply arrives as one frame. Thinking, tool_use and tool_result frames arrive live during the turn, so you can show what the agent is doing while it works.
How do I serve many users from one agent server?
Give each user their own session_id, and optionally their own cwd and role, from your backend. All sessions in one process share its config and credentials, so run one process per tenant when they must not share.
How do I know what each request cost?
Every turn ends with a cost frame that echoes your request_id and carries running session totals in tokens and USD. Supervisor and compression spend is reported separately in the aux_* fields.
How do I host it in production?
Run it on a private port with OCTOMIND_SERVER_TOKEN set, put a TLS-terminating reverse proxy in front if anything outside the host connects, and keep user authentication in your own backend.
Other ways to run octomind
- Self-hosted agent An agent on your own box that takes work from webhooks, timers and your shell.
- Agentic workflows Multi-step agent pipelines in one TOML file: parallel, loop until it passes, fan-out.
- MCP client Plug any stdio or HTTP MCP server into a terminal agent, and add more mid-session.
- Agent orchestration A lead agent hands work to specialists at runtime. You get one bill and one verdict.
- Self-improving agent Correct the agent once. It saves the fix as a file and recalls it next session.
Start with one command
Apache-2.0 and any model. Install it, then run step one above.
curl -fsSL https://octomind.run/install.sh | bash