demarkus-sdk: client SDK
An active plan for demarkus-sdk: a permissively licensed client SDK that lets any application talk to a demarkus server the way it talks to a database through its driver. Go first. Audience: demarkus maintainers. Drafted 2026-09-25; narrowed the same day to a client SDK only. No code yet.
Why
There is no client library today. demarkus/client/fetch is the de facto Go client, but it is internal, sits in a module with 30+ packages including CLI, TUI, and MCP code, has no stability promise, and is AGPL. The only other client, Caztor's Java DemarkusConnection, is FETCH only and embedded in a browser (ecosystem).
License conflict. demarkus-library is MIT (ADR 0009) but imports AGPL client/fetch, protocol, client/mdoutline, and client/graphstore, so its binary carries AGPL obligations. Any third-party Go application would hit the same wall.
Realtime makes it urgent. WATCH (realtime plan) is only useful if applications can subscribe from their own code.
What the SDK is
A client SDK and nothing else: connect to a server, speak the protocol, return typed results.
In scope:
- Open a handle to a
mark://host with a token, pooled QUIC connections, close. - The seven verbs plus WATCH as typed calls:
Fetch,List,Versions,Lookup,Publish,Append,Archive,Watch. - Typed results parsed from the wire: document metadata and body, LIST pages as an iterator, VERSIONS entries, LOOKUP rows, WATCH events with cursor resume.
- Typed errors: not found, conflict with current version, unauthorized, rate limited, outcome unknown for writes, resync for watches.
- Connection vs stream error classification and retry of reads; writes never resent after bytes left the client.
Appendwith expected version 0 resolves the current version, the documented protocol pattern.
Out of scope, staying in core on top of the SDK: response cache, recursive LIST walk, markdown outline, link extraction, three-way merge, graph and graphstore, token file discovery (~/.mark/tokens.d), MCP tools and formatting, federation crawl, client/live.
Package mapping
| Moves into the SDK | Stays in core, imports the SDK |
|---|---|
client/fetch, client/listing, client/lookuptable, the write rules from client/docwrite, client-side wire codec from protocol/ |
Everything else in client/: marktools, mcpbind, mcpfmt, metaguard, lookupexpand, fetchdedup, listwalk, mdoutline, links, merge, graph, graphstore, index, generation, joinurl, internal/*, fetchtest |
About 3,000 lines move. Git history shows one author for every moved file (checked 2026-09-25), so relicensing them to MIT is possible; the ADR lists the exact files.
Decisions
- Repo:
latebit-io/demarkus-sdk, Go modulegithub.com/latebit-io/demarkus-sdk/go, packagemark. Other languages get directories in the same repo later, only when a named consumer exists. - License: MIT, matching ADR 0009 satellites. Wire fixtures CC0, like the spec.
- The server never imports the SDK. The SDK carries its own client-side codec (encode requests, decode responses and WATCH blocks); the server keeps the server-side codec in
protocol/(decode requests, encode responses). The two directions share little code. Agreement is enforced by golden wire fixtures both sides test against, the producer-consumer contract pattern. - Core client surfaces use the SDK. CLI, TUI, MCP, broker, and agent import
mark;client/fetchis deleted. - Ordering with realtime: the Go SDK lands before realtime PR 5, so
Watchis written once, in the SDK. Realtime PR 1 (error classification) lands directly in the SDK if extraction is done by then. - Versioning: v0 until WATCH ships, then v1 with semantic versioning, so the API does not freeze before its biggest addition.
Go API shape
Modeled on database/sql: open a handle, pass contexts, get typed results.
w, err := mark.Open("mark://soul.demarkus.io", mark.Options{Token: tok})
defer w.Close()
doc, err := w.Fetch(ctx, "/index.md", mark.FetchOptions{})
res, err := w.Publish(ctx, "/notes/a.md", body, mark.PublishOptions{ExpectedVersion: doc.Version})
for ev, err := range w.Watch(ctx, "/agents/me/inbox/", mark.WatchOptions{Since: cursor}) { ... }
Phases
Each phase is done when its proof exists and bash pre-commit.sh passes in every repo it touches.
| Phase | Scope | Proof |
|---|---|---|
| 0 | ADR in core (docs/adr/0026): client SDK only, MIT and CC0, the server never imports it, the exact file list relicensed. Mirror to /adr/. |
ADR accepted; file list matches the authorship check. |
| 1 | Repo, fixtures, codec. Create demarkus-sdk; golden request and response transcripts for every verb and status in fixtures/; the client-side codec with fuzz targets. Core protocol/ tests consume the same fixtures. |
SDK codec and core server codec both pass every fixture. |
| 2 | Client. Move fetch, listing, lookuptable, and the write rules into package mark with the API above. |
SDK unit tests plus an integration test against a real demarkus-server. |
| 3 | Core switches over. One surface per PR, mechanical moves with no behavior change: CLI, TUI, MCP, broker, agent. Delete client/fetch. |
No client/fetch import remains; all core tests pass. |
| 4 | demarkus-library switches over for fetch and protocol. | Library builds against the SDK; its remaining AGPL imports are only mdoutline and graphstore. |
| 5 | Docs and examples. Package docs, runnable examples per verb and a WATCH mailbox once realtime lands. | A new app uses the SDK from its README alone. |
Other languages follow the QUIC library matrix in Lean into QUIC: Rust (quinn) and Python (aioquic) are straightforward, TypeScript needs WebTransport or the broker. None is scheduled.
Risks
- Codec drift between SDK and server. Two codecs for two directions; fixtures are the contract, and a wire change without a fixture change is a review finding.
- Relicensing scope. Only listed files change license; anything with another author stays in core.
- Core churn. Phase 3 touches every client surface; one surface per PR, no behavior changes mixed in.
Open questions
- MIT or Apache-2.0? Apache adds a patent grant database-style adopters often ask for; MIT matches ADR 0009.
- demarkus-library still imports AGPL
mdoutlineandgraphstoreafter phase 4: move that code into the library, accept AGPL for the library, or publish those two separately under MIT? - Does token file discovery belong in the SDK after all, so every app resolves tokens like the CLI does?