soul.demarkus.io:6309/plans/memory-knowledge-layering/file-worlds.mdsoul.demarkus.io:6309/adr/0037-contract-is-the-protocol-two-doors.md draft reader metaADR 0037: The contract is the protocol; transports are bindings; two doors by credential kind
Status: proposed (2026-10-08), decision 6 amended 2026-10-19. Drafted from Plan: The engine, not the wire. Supersedes ADR 0033 and the "Transport is QUIC by design" section of Verb set and wire format. Follows ADR 0014, which stays the contract's home, and ADR 0032.
Context
Demarkus started as a wire protocol, mark:// over QUIC, with a dumb store behind it. The product it became is memory for agents and shared knowledge for organizations, reached through MCP plugins. Since ADR 0032 the MCP gateway runs in the knowledge server process and dispatches to local worlds through worldruntime.Runtime.Exchange: the same verbs, request types and grants, no stream and no bytes. Every person and every LLM host enters through MCP. The QUIC wire is left with machine edges: cross-cluster reads through the world pool, replica hints, the CLI, souls on demarkus-server, and the library.
The 2026-09-25 transport review already found that request and response traffic gains nothing from QUIC; only WATCH, federation and unbuilt 0-RTT justify it. ADR 0033 then built a second QUIC listener on UDP 443 that accepts identity bearers on the raw mark wire, for CLI, SDK and live-view clients. None of those clients exists. The door was built before the walker.
Meanwhile the word "protocol" has covered three things: the product, the contract (eight verbs, frontmatter metadata, immutable versions with a hash chain, the LOOKUP catalog, WATCH cursors, grants), and the transport (QUIC, ports, ALPN, SNI). The thought On the Protocol as the Permanent Layer named the permanent layer correctly but placed the transport inside it. Git's durability is its object model, not its wire; HTTP's is its semantics, not TCP.
Decision
-
The contract is the protocol. The permanent layer is the contract defined in the
protocolmodule and SPEC: the verb set, the request and response shape, frontmatter as a map, immutable versions and the hash chain, the catalog, WATCH cursors and terminal blocks, and grants. The durable-core rule ("would this still matter if every product on top were thrown away") applies to the contract. -
Transports are bindings. There are three bindings of the contract and each is named as such in the architecture docs:
- In-process:
Runtime.Exchange. Same gate, concurrency slot, rate limit and deadline as a stream. Used by the gateways and the in-process agent. - QUIC: raw
markon 6309, ALPNmark, SNI world routing. The machine wire. - MCP: JSON-RPC over HTTPS through the gateways, tool names mirroring the verbs. The driver for LLM hosts, browsers and SDK apps that want identity. No binding is "the protocol". A new binding needs an ADR; a new verb still needs its own.
- In-process:
-
Two doors, by credential kind. One credential per binding, not one per port:
- Raw
markover QUIC accepts capability tokens only. Its clients are machines: servers, replicas, souls, operators, the plugin binary. It is served inside the cluster, on loopback, or over a private network. - MCP over HTTPS accepts OAuth identity only: IdP id_tokens and broker signed tokens, verified by the broker's composite verifier, gated by the org predicate and the world's
allow. Its clients are people and agents. It is served on 443 at the load balancer. - HTTP/3 is a load balancer setting, not server code. The GKE load balancer negotiates h3 with clients and speaks h1 or h2 to the pod.
- WATCH reaches MCP clients as server-sent events over MCP's streamable HTTP. No WebTransport.
- Raw
-
The bearer listener is removed. The second QUIC listener of ADR 0033 is stripped from
feat/broker-in-processbefore merge. The SNI routing and verifier reuse stay where the gateways already use them. ADR 0033 is superseded. There is no identity on the rawmarkwire. -
Transport work needs a named consumer. No transport workstream starts without a consumer that ships in the same quarter. In Lean into QUIC: workstreams 2 (UDP 443 for raw mark), 7a (HTTP/3 on the broker) and 7b (WebTransport) are closed by this decision; 3 to 6, 8, 10 and 11 are parked; 1 (measure) stays only while the baseline is cheap; 9 is done by ADR 0032. The protocol freeze after WATCH and ALPN
mark/1stands. -
The local profile constraint, and the shape of the merge. A soul on a laptop outlives the hosted product. Whatever binary serves a local soul runs with no broker, no bucket, no account and no network, and keeps the plugin contract byte for byte: tool names and schemas,
mark://localhost:6309, ALPNmark, token discovery in~/.mark/tokens.d, the on-disk store format of ADR 0014, and a launcher that finds a binary nameddemarkus-server.Amended 2026-10-19: the merge is one runtime with two thin mains, not one binary.
worldruntime,handler,catalog,changefeed,quicserveandwritepolicyare already the one codepath both servers run; what the soul main lacks is a seat inknowledgeserver, and what the knowledge path lacks is a file-store backend. So:demarkus-serverstays, as alocalmain over the shared runtime: the file backend, one world, one token file, static TLS. It never importsbucketstore, the GCS client,auth,certsource,configwatch,snirouter,peerhint,managementor the broker; the layer guard in pre-commit enforces the boundary. No rename, so the launcher clause above needs no transition release.demarkus-knowledge-serveranddemarkus-knowledgecollapse into the one full binary; the file backend is a second opener there, so a self-hosted knowledge system runs without GCS.- A Go binary carries what its main imports. The local binary stays near today's size (8 MB stripped against 70 MB for the full binary on 2026-10-19) with no cloud, OAuth or telemetry
initin a laptop process. Build tags are reserved for the GCS opener only.
Superset, one way. After the merge the full binary with a local config serves a soul under the same contract, because it is the same runtime with the file backend included;
demarkus-servercannot serve a knowledge system, because the bucket, OAuth, broker and world registry are absent by construction. The relation does not hold today in either direction: the knowledge server has no file backend until the opener lands. And the superset is a property, not a deployment: the launcher looks fordemarkus-server, and the laptop constraint wants the cloud and OAuth packages absent rather than idle, so a soul runs the thin main by policy.The merge still waits for the file-store backend in
knowledgeserverand ships behind the plugin compatibility gate: the eval suite and/soul-statuspass against the old and the newdemarkus-serveron the same config and soul directory. -
Hosted souls are memory-broker tenants. A soul served over the public internet is a tenant world behind the memory gateway with OAuth, not a
demarkus-serverwith a token on UDP 6309.soul.demarkus.ioremains a personal instance or retires.
Consequences
- The architecture docs change wording, not structure: the core principle keeps the librarian and describes the bookshelf as an engine with three bindings and two doors; the "Transport is QUIC by design" section becomes "Bindings". README and the pages branch lead with memory and knowledge.
- The in-process dispatch of ADR 0032 is a binding, not a bypass. The "edge is a package boundary" sentence stands.
- The knowledge server's internet-facing surface is HTTPS only. UDP 6309 is fenced to the cluster, loopback, or private networks. The blast-radius note of ADR 0032 about an internet-facing UDP port is withdrawn.
- The CLI has no identity path. Accepted while it is an operator tool. If that changes, the CLI speaks MCP with the device flow like every other identity client; a broker-issued short-lived capability token is the fallback and would re-add a mint path ADR 0032 deleted.
- The SDK (ADR 0026) is a driver for capability-token clients: the library, operators, third-party servers. Identity clients use MCP. Its scope narrows; its license purpose does not change.
- Live views in a browser ride MCP streamable HTTP. Their priority is a product decision, recorded in the plan, not a transport one.
- The rejected HTTP verb mapping stays rejected. MCP is the HTTP-shaped door; the contract's wire shape is not mapped onto HTTP methods. A TLS on TCP binding for raw
markremains the recorded fallback if QUIC fails its revisit clause, and would be its own ADR. - Code removed: the bearer listener, its chart port and NetworkPolicy rule, its SPEC section draft. Code kept: everything else on
feat/broker-in-process. - Two release artifacts from the server module, as today. A user of
demarkus-serverupgrades across the merge and sees no change; the gate is a release condition, not a hope. - Open: whether a tenant world accepts a bulk import with version history intact, which decides how a hosted soul migrates.