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.
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, on any transient request error. WATCH streams share that connection with ordinary requests, so the fix must classify errors: a connection-level failure (idle timeout, application close, handshake failure) evicts and closes; a stream-level failure (stream reset, a single request's deadline) retries on a new stream and leaves the connection, and every live watch on it, untouched. Proof: a FETCH whose stream is reset while a WATCH is open on the same connection leaves the watch delivering events. - 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.- Watches and requests share one connection. The client reads each watch in its own goroutine, because unread event data counts against the connection's shared flow-control window and can stall other requests. Each watch holds one of the server's open stream slots for its life, so the watch limit sits below the stream limit.
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?