Agent Chat is a local-first message bus and address book for software agents. It stores every message, contact update, handoff, and receipt as an ordered S2 record. The same binary runs as a loopback-only development service or as an authenticated remote gateway for systems on different machines.
The implementation uses the official S2 Go SDK against S2 Lite. S2 Lite remains the durable source of truth; the gateway supplies the agent-level protocol, validation, bearer authentication, JSON API, and resumable Server-Sent Events (SSE).
agent / system ── HTTP or SSE ──> Agent Chat gateway ──> S2 Lite
│
directory, contacts,
and per-agent inboxes
Requirements: the installed s2 CLI, Go 1.23 or newer, make, and curl.
make up
./bin/agent-chat register --id alice --name "Alice" --capabilities planning,review
./bin/agent-chat register --id bob --name "Bob" --capabilities coding
./bin/agent-chat send \
--from alice \
--to bob \
--text "Please review this plan" \
--data '{"task_id":"task-42","priority":"high"}'
./bin/agent-chat inbox --agent bob
./bin/agent-chat inbox --agent bob --cursor 0 --followmake up starts persistent S2 Lite storage in .data/s2, builds the gateway,
and listens only on 127.0.0.1:8081. make down stops both processes without
removing stored data. The underlying entrypoints live in scripts/; run
make help to see the build, test, verification, and installation targets.
The repository includes one portable Agent Skill at
.agents/skills/s2-agent-chat. It teaches an
agent session how to establish a unique identity, register and discover peers,
send correlated messages, safely replay S2 cursors, acknowledge work, pass contact
cards, and connect through a remote gateway.
Codex discovers the checked-in .agents/skills copy automatically when it is
launched in this repository. To make the skill available to both Codex and Claude
Code across all projects:
make install-skillInstall for only one tool, or into another project:
make install-skill SKILL_ARGS='--codex --user'
make install-skill SKILL_ARGS='--claude --user'
make install-skill SKILL_ARGS='--claude --project --project-dir /path/to/project'The installer refuses to replace an existing skill unless --force is supplied.
It installs the same standards-compatible SKILL.md bundle at
~/.agents/skills/s2-agent-chat for Codex or
~/.claude/skills/s2-agent-chat for Claude Code. The optional
agents/openai.yaml adds Codex UI metadata and is harmless when Claude Code reads
the bundle.
Invoke it explicitly as $s2-agent-chat in Codex or /s2-agent-chat in Claude
Code, for example: “Register this session as reviewer-1, ask builder-1 for the
current result, and wait for a correlated response.” The skill installation does
not install or start the Agent Chat runtime; keep agent-chat on PATH (or set
AGENT_CHAT_BIN) and start a local or remote gateway separately.
An agent can store a contact card, send it as a durable inbox envelope, and let the recipient explicitly accept it:
./bin/agent-chat add-contact \
--agent alice \
--id carol \
--name "Carol" \
--endpoint https://agents.example.net \
--capabilities research
./bin/agent-chat share-contact --from alice --to bob --contact carolCopy the returned message ID, then import that exact received card:
./bin/agent-chat accept-contact \
--agent bob \
--message msg_0123456789abcdef0123456789abcdef
./bin/agent-chat contacts --agent bobContact cards may carry a gateway URL, capabilities, labels, and a public key or key reference. They never carry bearer tokens or other secrets.
| S2 stream | Purpose |
|---|---|
system/directory |
Latest registered card for every local agent, reconstructed from events |
agents/{id}/contacts |
One agent's event-sourced address book |
agents/{id}/inbox |
Ordered message, contact-share, and receipt envelopes for that agent |
S2 sequence numbers are exposed as inbox cursors. A replay response returns next_cursor; SSE events use that same next cursor as their event ID, so a client can reconnect without losing its position. The model follows S2's recommended multi-agent pattern of a shared coordination stream plus per-agent private streams.
Delivery is durable after the S2 append acknowledgement and ordered within each recipient inbox. Consumers should deduplicate on the stable envelope id when retrying across an uncertain network outcome; the protocol is designed for at-least-once consumption.
The aggregate monitor is an operator-only surface with a separate credential. Set the admin token before starting the gateway; leaving it unset disables the endpoint:
export AGENT_CHAT_ADMIN_TOKEN="$(openssl rand -hex 32)"
make up
./bin/agent-chat monitor --limit 100
./bin/agent-chat monitor --cursor '<next_cursor>' --followmonitor fans in the authoritative agents/{id}/inbox streams; it does not
duplicate envelopes into an audit stream. Replay returns deliveries from every
registered inbox, an opaque next_cursor, and the observed per-inbox tails.
Follow mode prints one monitored delivery per line and reconnects from its last
cursor. Save the cursor exactly as returned—unlike an inbox cursor, it is not a
number.
Each batch is sorted by S2 timestamp, then inbox ID and sequence number. S2 ordering remains authoritative only inside an individual inbox, so the aggregate view is deterministic but is not a transactional snapshot or a global causal order. Newly registered agents are discovered on later reads and begin at their first inbox record.
For a remote deployment, keep S2 Lite on a private network and expose only the Agent Chat gateway. The server refuses to bind beyond loopback unless AGENT_CHAT_API_TOKEN is set.
With Docker Compose:
export AGENT_CHAT_API_TOKEN="$(openssl rand -hex 32)"
export AGENT_CHAT_ADMIN_TOKEN="$(openssl rand -hex 32)"
docker compose up -d --buildThe admin token is optional, but it must differ from the deployment token when configured. Only operators who need the aggregate monitor should receive it.
Put a TLS reverse proxy in front of port 8081. Alternatively, the native server can terminate TLS itself:
export AGENT_CHAT_API_TOKEN='a-long-random-secret-from-your-secret-manager'
export S2_ACCOUNT_ENDPOINT='http://private-s2:8080'
export S2_BASIN_ENDPOINT='http://private-s2:8080'
export S2_ACCESS_TOKEN='ignored'
./bin/agent-chat serve \
--listen 0.0.0.0:8443 \
--tls-cert /secure/path/fullchain.pem \
--tls-key /secure/path/private.keyEach external system then uses the remote boundary in the same way:
export AGENT_CHAT_URL='https://agents.example.net:8443'
export AGENT_CHAT_API_TOKEN='the-shared-deployment-token'
agent-chat inbox --agent local-worker --followThis deployment model lets multiple systems communicate through one remote gateway and S2 basin. It does not relay between separate, independent Agent Chat gateways or S2 basins.
| Method and path | Operation |
|---|---|
PUT /v1/agents/{id} |
Register or update an agent card |
GET /v1/agents |
List registered agents |
PUT /v1/agents/{id}/contacts/{contact} |
Add or update a contact |
GET /v1/agents/{id}/contacts |
Project the current address book |
DELETE /v1/agents/{id}/contacts/{contact} |
Remove a contact |
POST /v1/agents/{id}/messages |
Send text and/or arbitrary JSON data |
POST /v1/agents/{id}/contact-shares |
Send a saved contact card |
POST /v1/agents/{id}/contact-shares/{message}/accept |
Import a received contact card |
POST /v1/agents/{id}/receipts |
Send a delivery, processing, read, or rejection receipt |
GET /v1/agents/{id}/inbox?cursor=0&limit=100 |
Replay an inbox |
GET /v1/agents/{id}/inbox?cursor=0&follow=true |
Follow an inbox with SSE |
GET /v1/monitor?cursor={opaque}&limit=100 |
Admin-only replay of every inbox |
GET /v1/monitor?cursor={opaque}&follow=true |
Admin-only follow of every inbox with SSE |
Send Authorization: Bearer $AGENT_CHAT_API_TOKEN for ordinary endpoints when
authentication is configured. The monitor accepts only
AGENT_CHAT_ADMIN_TOKEN; the admin token does not authorize ordinary endpoints.
When no admin token is configured, /v1/monitor returns 404. Health and
readiness are available without authentication at /healthz and /readyz. See
docs/protocol.md for request bodies, envelopes, and cursor
behavior.
| Variable | Default | Meaning |
|---|---|---|
AGENT_CHAT_URL |
http://127.0.0.1:8081 |
CLI gateway URL |
AGENT_CHAT_LISTEN |
127.0.0.1:8081 |
Server listen address |
AGENT_CHAT_API_TOKEN |
empty | Gateway bearer token; required off loopback |
AGENT_CHAT_ADMIN_TOKEN |
empty | Separate token enabling the aggregate monitor |
AGENT_CHAT_BASIN |
agent-chat |
Dedicated S2 basin |
AGENT_CHAT_BOOTSTRAP |
true |
Ensure the basin and directory stream on startup |
S2_ACCOUNT_ENDPOINT |
http://127.0.0.1:8080 |
S2 account API endpoint |
S2_BASIN_ENDPOINT |
account endpoint | S2 basin API endpoint; {basin} is supported |
S2_ACCESS_TOKEN |
ignored for loopback |
S2 access token for hosted or remote S2 |
S2 Lite currently expects operators to provide their own perimeter authentication. Agent Chat therefore requires a gateway bearer token for every non-loopback bind, rejects credentials embedded in contact URLs, limits request and field sizes, and never logs request bodies or authorization headers.
The ordinary bearer token authenticates a deployment, not an individual agent.
Any holder can act as any registered agent and can manually request a named
inbox. AGENT_CHAT_ADMIN_TOKEN creates a distinct boundary for the convenient
aggregate monitor command, but it does not retrofit tenant isolation onto the
ordinary API. Use a dedicated trusted network or reverse proxy policy for this
version; add per-agent credentials/signatures before treating mutually untrusted
tenants as separate security principals. Use TLS whenever traffic leaves the
host.
make verifyThis runs unit/API tests, static analysis, and an end-to-end test against a real ephemeral S2 Lite server.
Useful upstream references: S2 agent use case, endpoint overrides, and the S2 read/replay API.