Future: WATCH streams and agent-to-agent communication
A future plan, not active work: a WATCH verb that holds a QUIC stream open and pushes change events, so agents coordinate through durable, versioned documents instead of an ephemeral message bus. Audience: demarkus maintainers deciding whether and when to reopen the verb set. Drafted 2026-09-15 from a session weighing QUIC's value against an HTTP binding.
Why this is the QUIC-native feature
Transport stays QUIC by design (protocol). WATCH is where that choice pays: one stream per subscription, many subscriptions on one connection with no head-of-line blocking, cancel by stream reset, and connection migration keeps a laptop's subscriptions alive across network changes. The client opens the stream, so subscribers need no public address. That is the flaw in the WebSub-style SUBSCRIBE with a callback URL sketched in docs/DESIGN.md: agents are not servers, and the backlog records that design as removed. SPEC §13 still lists Subscriptions as a planned extension.
The agent-to-agent idea
Agents talk by writing documents and watching prefixes; demarkus is the blackboard. Every message is versioned, hash-chained, attributed, and readable later, so the conversation is the record. Everything except WATCH already exists:
| Need | Existing mechanism |
|---|---|
| Send | PUBLISH /agents/<id>/inbox/<ulid>.md |
| Reply in a thread | APPEND to the message |
| Claim a task exactly once | PUBLISH with expected version; the loser gets a version conflict |
| Acknowledge or close | ARCHIVE |
| Discover work | LOOKUP by tag |
| Wake on arrival | WATCH (new) |
LLM agents are turn-based and do not sit on a socket. Push helps only when a harness wakes the agent, so the first consumer is demarkus watch printing one line per event, run as a long-lived background command an agent harness monitors.
Gates before starting
- An ADR in
docs/adr/(next number) supersedes "Verb Set: Complete", mirrored to/adr/. - Durable core fortification 1, the protocol version on the wire, has landed so WATCH ships as an additive extension after the 1.0 freeze (fortifications).
- A concrete multi-agent workflow where polling latency measurably hurts. Otherwise long-poll on a change feed (option C) is enough and needs no new verb.
- The client's
removeConnevicts a shared connection without closing it; fix before any long-lived stream rides on it. - The feature freeze in ecosystem optimization is lifted, or a tool budget is carved out for any MCP surface.
Wire shape
Request: WATCH /prefix/ with request metadata since: <cursor> (optional) and the usual token. An exact document path is allowed.
Response: one frontmatter block with status: ok and the current cursor, then one frontmatter block per event, no body: cursor, path, version, hash, op (publish, append, archive). Events are hints; the client fetches by hash for content. Terminal blocks end the stream: status: resync (cursor unknown or too old), status: unauthorized (token revoked or expired), status: closing (server drain).
Cursor is opaque to clients: <epoch>:<seq>. Delivery is at least once and ordered within an epoch. On resync the client rebuilds with LIST and VERSIONS of the prefix and resumes from the returned cursor.
Server design
- Change hub (
server/internal/changefeed, no store deps): publish, subscribe by prefix and cursor, an in-memory ring buffer for resume, a bounded queue per subscriber. A full queue ends that subscription withresync; the write path never blocks. - File store: emit after a committed WriteVersion, Append, or Archive. The epoch is random per process start, so a restart forces resync.
- Bucket store: the head sequence is already durable and monotonic, so the epoch is the world ID and a cursor survives restarts. Replicas learn of other replicas' commits only when they read the head, so each world runs a head poller while it has subscribers, diffs the old and new snapshot per path, and emits in sequence order. Several commits between polls coalesce to the latest version per path. A replica emits its own commits immediately; the hub drops any sequence it has already emitted.
- Runtime (
worldruntime.serveStream): a WATCH currently holds a concurrency slot and a read deadline for its whole life. Release the slot once subscribed, exempt the stream from the request timeout, add per-world and per-connection watch limits separate frommaxConcurrentRequests, and sendclosingon drain so the active wait group finishes. - Auth: subscribing requires
readon the prefix. Each event is checked against the token's paths, since a prefix can be wider than a token's scope and existence must not leak. A token reload re-checks live subscriptions.
Client and edge
fetch.Client.Watch(ctx, host, prefix, token, since)returns an event iterator, reconnects with the last cursor, and surfacesresyncto the caller rather than hiding it.demarkus watch mark://host/prefix/prints one tab-separated line per event.- Broker relay over SSE or MCP resource notifications only on demand. The broker would hold subscriptions per MCP session, a scaling cost the dumb-server principle pushes to the edge on purpose.
- Agent mailbox conventions ship as a plugin skill, not in core.
Phases
- Protocol. ADR, SPEC section, verb constant, event codec, status values. Proof: codec round trip; a server without WATCH rejects it with an error status, never a hang.
- Single server. Change hub, file store emit, runtime changes, client, CLI. Proof: two sessions on one local soul, one publishes to an inbox, the other's
demarkus watchprints the event within 100 ms; kill and reconnect resumes without gaps. - Knowledge server. Head poller and snapshot diff. Proof: two replicas, a write through replica A reaches a watcher on replica B within one poll interval; a cursor older than the ring gets
resync. - Agent conventions. Mailbox layout, claim by expected version, ack by archive, as a skill. Proof: two agents split a task list with no double claims.
- Broker relay. Only after a named consumer asks for it.
Risks
- Dumb-server drift. The server now holds subscriptions. Kept small because events are hints, subscribers keep no durable server state, and overload degrades to resync.
- Store as a message bus. Every message is a stored version. Chatty agents bloat history; guidance says documents are for durable coordination, and high-frequency chatter belongs elsewhere.
- Bucket poll cost. Object HEAD requests per world per interval, paid only while subscribers exist.
- Silent misses. A bug that drops an event looks like quiet. The cursor makes gaps detectable; tests assert contiguous sequences.
Open questions
- Should a subscription accept LOOKUP filter predicates (
tag=inbox) so agents watch a subject rather than a path? - Should events carry changed section anchors, so a watcher fetches one section instead of the whole document?
- Should the file store persist its sequence so cursors survive restarts, making the epoch stable?
- Is
demarkus watchplus a harness monitor enough to leave the broker relay out permanently?