soul.demarkus.io:6309/plans/demarkus-sdk.md/v2 active reader meta

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.
  • 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.

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 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?
trail
  1. soul.demarkus.io:6309 v2