slivingdoc

Write in parallel. Persist to S3.

People and agents pull and commit shared notes through MCP or the CLI. slivingdoc merges non-conflicting concurrent changes, reports conflicts, and stores the notebook in your S3-compatible bucket.

Two operations, always in reach:
mcp:notes_pull()→ OKcli:slivingdoc pull→ OK
mcp:notes_commit(message)→ OKcli:slivingdoc commit -m "note"→ OK
npx -y slivingdoc serve

Shared by deployed agents, short-lived runs, and people.

Several agents commit concurrent changes to one shared S3-backed notebook

Use cases

  • One context for every coding agent

    Claude Code, Codex, and any other MCP host connect to the same server. Context written in one session is there in the next, whichever harness runs it.

    Connect an MCP host →
  • Shared state for production agents

    Agents in production keep dynamic context in the notebook: what a customer needs, how far an investigation got, what was already tried. Context too loose for a schema stays plain text.

    How it works →
  • Fleets that report back

    Spawn a large fleet and confine each agent to its own directory. Every agent commits its report, and a human pulls the notebook to read them all in one place.

    Writable paths →
  • Prompts you change at runtime

    Serve instructions to agents from read-only paths. Edit them from the command line, and every agent reads the new version on its next pull, with no redeploy.

    Read-only paths →
  • Memory for short-lived runners

    CI jobs, scheduled agents, and sandboxes lose their disk after every run. The notebook lives in the bucket, so the next run starts where the last one stopped.

    From scripts and cron →
  • Notes between people and machines

    Colleagues, and your own computers, pull and commit the same notebook from the command line and edit it with any editor.

    Share a directory →

How it works

One notebook, many agents — and their humans. Two operations.

pull

Pull the notebook into a directory

pullmcp:notes_pull()→ OKcli:slivingdoc pull→ OK reads the accepted state from the bucket — one small manifest called current, plus the immutable packs it references — and writes the notebook into your directory as ordinary UTF-8 text files.

No path needed: every server owns one directory and names it in each result. That directory is a checkout, not a place inside the notebook — it mirrors the whole notebook and remembers its own baseline, what it last saw. Unpublished local edits are merged with the accepted state, never overwritten.

The caller never sees Git object IDs, pack names, or S3 keys. The directory is the whole interface.

Read more: How it works →
notes_pull asks S3 for the manifest, streams packs from remote, merges, and writes files to local
GET current → packs from remote → merge → files to local

edit

Edit with ordinary file tools

Between calls there is no protocol at all. Agents — as many as you like — read and write the files with the tools they already have: a text editor, sed, an LLM's file tools.

The notebook is directories and UTF-8 text files — binary files are rejected, never mangled. Bytes and line endings are preserved. slivingdoc does nothing until the next call.

Humans can work in the same directory: open the notebook in your editor and write alongside your agents. It is perfectly possible to use slivingdoc as an agent-free distributed notebook too.

Read more: Share a directory with humans →
An agent and a human type in the local notes at the same time; nothing syncs until the next call
no daemon · no hidden sync — files move only on pull and commit

commit

Commit publishes your changes

commitmcp:notes_commit(message)→ OKcli:slivingdoc commit -m "note"→ OK merges your files with the latest accepted state, packs the result, and uploads the pack. Packs are immutable: uploading one publishes nothing.

Your change is the delta since the checkout's baseline — so a directory that was never pulled is refused with INVALID_REQUEST before any network work. Pull once first: commit publishes intent, not a guess.

Publication is a single conditional replace of the small current manifest — If-Match on the ETag the writer observed. The bucket is the durability boundary; everything local is a rebuildable cache.

Read more: CLI reference →
Commit reads the accepted state, merges, uploads a proposal pack, and the conditional swap accepts it
a pack is only a proposal until the conditional swap

race

Concurrent writers cannot clobber each other

Two agents commit at once. Both observed current at the same ETag; S3 accepts exactly one replacement for it. The loser gets a precondition failure — expected contention, not an error.

The losing writer downloads what it missed, merges against the new head, and retries with a fresh proposal. No lock object, no lease, no clock. Accepted state is never silently overwritten.

Read more: Storage model →
Two slivingdoc writers race on If-Match; the loser merges and retries, and overlapping lines return to the caller
optimistic publication: merge and retry, never overwrite

conflict

Conflicts are just text

When your change and an accepted remote change touch the same lines, the call returns CONTENT_CONFLICT and writes standard conflict markers into the affected files. The structured error names every file and marker line range.

Resolve with the same file tools you edit with: keep the text you want, delete the marker lines, call notes_commit again. A complete marker block is never accepted into the notebook.

Read more: Resolve conflicts →
A conflict appears as markers in the file and is resolved by editing
markers in, edits out — no Git commands involved

checkpoint

Checkpoints keep cold starts fast

Every commit uploads one small incremental pack. Left alone, that chain would make a fresh server download a growing number of objects. So after a configurable count, slivingdoc compacts the stable prefix into one complete-state checkpoint — see Configuration for the exact default.

A new reader needs one checkpoint plus the short tail after it. Checkpoints never block writers, and a failed checkpoint never touches accepted state.

Read more: Storage model →
Increments stream in at a constant tempo while the stable prefix compacts into a new checkpoint generation; the previous generation is retained and the oldest is cleaned
compaction is async — the increment stream never pauses

Get started

An S3-compatible bucket and a supported Node.js runtime — see Installation. Works in any MCP host, or with none at all: the same binary is a CLI.

  1. Point it at a bucket

    That is the whole configuration. AWS S3, Tigris, MinIO, or anything S3-compatible that supports conditional writes. Credentials come from the normal AWS chain.

  2. Add it to your MCP host

    The npm launcher downloads the right native binary for your platform, verifies its checksum, and runs it. No Git, no toolchain.

  3. Let agents pull and commit

    Two tools, no arguments to coordinate, plain files, OK on success. Conflicts come back as editable text, never as a stuck state.

Raw MCP configurationfor any compatible host
{
  "mcpServers": {
    "slivingdoc": {
      "command": "npx",
      "args": ["-y", "slivingdoc", "serve"],
      "env": {
        "SLIVINGDOC_BUCKET": "my-notes",
        "AWS_ACCESS_KEY_ID": "<your-access-key-id>",
        "AWS_SECRET_ACCESS_KEY": "<your-secret-access-key>",
        "AWS_REGION": "us-east-1"
      }
    }
  }
}

Each server takes its own scratch directory and discards it on exit — the notebook lives in the bucket. Pass --workspace-root to pin a fixed directory that humans and agents share instead. PreferAWS_PROFILE over pasted keys when ~/.aws is configured, and for a non-AWS store add AWS_ENDPOINT_URL_S3.

claude mcp add slivingdoc \
  --env SLIVINGDOC_BUCKET=my-notes \
  --env AWS_ACCESS_KEY_ID=<your-access-key-id> \
  --env AWS_SECRET_ACCESS_KEY=<your-secret-access-key> \
  -- npx -y slivingdoc serve

Defaults to local scope: this project only, in ~/.claude.json. Add --scope project for a shared .mcp.json, or --scope user for every project. See the MCP hosts guide.

codex mcp add slivingdoc \
  --env SLIVINGDOC_BUCKET=my-notes \
  --env AWS_ACCESS_KEY_ID=<your-access-key-id> \
  --env AWS_SECRET_ACCESS_KEY=<your-secret-access-key> \
  -- npx -y slivingdoc serve

Stored in ~/.codex/config.toml under [mcp_servers.slivingdoc]. Verify with /mcp. See the MCP hosts guide.

gemini mcp add -s user \
  -e SLIVINGDOC_BUCKET=my-notes \
  -e AWS_ACCESS_KEY_ID=<your-access-key-id> \
  -e AWS_SECRET_ACCESS_KEY=<your-secret-access-key> \
  slivingdoc npx -- -y slivingdoc serve

Written to ~/.gemini/settings.json; drop -s user for a per-project .gemini/settings.json. See the MCP hosts guide.

{
  "mcp": {
    "slivingdoc": {
      "type": "local",
      "command": ["npx", "-y", "slivingdoc", "serve"],
      "enabled": true,
      "environment": {
        "SLIVINGDOC_BUCKET": "my-notes",
        "AWS_ACCESS_KEY_ID": "<your-access-key-id>",
        "AWS_SECRET_ACCESS_KEY": "<your-secret-access-key>"
      }
    }
  }
}

Goes in opencode.json at the project root, or globally in ~/.config/opencode/opencode.json. See the MCP hosts guide.

pi install npm:pi-mcp-adapter

pi has no built-in MCP. The adapter reads the standard .mcp.json — paste the configuration below into your project's .mcp.json and restart pi. See the MCP hosts guide.

export SLIVINGDOC_BUCKET=my-notes
npx -y slivingdoc pull notes
# edit UTF-8 text files under notes/
npx -y slivingdoc commit notes -m "meeting summary"

The same two operations, driven by hand — humans and cron jobs share the notebook without an MCP host. Same flags and environment as serve. See the MCP hosts guide.

No bucket yet? Tigris is plug-and-play: a free tier, one global endpoint, and the conditional writes slivingdoc's startup probe requires. Set AWS_ENDPOINT_URL_S3 to https://t3.storage.dev and AWS_REGION to auto. For offline evaluation, the SeaweedFS example runs a local bucket in one docker compose up.

Type to search the documentation.