# 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](/ecosystem.md)). **License conflict.** demarkus-library is MIT ([ADR 0009](/adr/0009-license-split-core-agpl-plugins-mit.md)) 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](/plans/watch-agent-messaging.md)) 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. - `Append` with 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 module `github.com/latebit-io/demarkus-sdk/go`, package `mark`. 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/fetch` is deleted. - **Ordering with realtime:** the Go SDK lands before realtime PR 5, so `Watch` is 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. ```go 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](/plans/quic-first.md): 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 `mdoutline` and `graphstore` after 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?