Pull the notebook into a directory #
pull
mcp:
notes_pull(path)
→ OK
cli:
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.
The path picks a checkout, not a place inside the notebook: any directory under the workspace root works, each one mirrors the full notebook, and each 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.
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.
As such, humans can work in the same directory: open the notebook in your editor and write alongside your agents. Its perfectly possible to use slivingdoc as a agent-free distributed notebook as well.
Commit publishes your changes #
commit
mcp:
notes_commit(path, message)
→ OK
cli:
slivingdoc commit -m "note"
→ OK merges your files with the latest accepted state, packs the result, and uploads the pack. Packs are immutable: up one publishes nothing.
Your change is the delta since the checkout's baseline — so a path
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.
Concurrent writers cannot clobber each other #
Two agents commit at once. Both observed current
at
ETag e1
; 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.
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.
Checkpoints keep cold starts fast #
Every commit uploads one small incremental pack. Left alone, that chain would make a fresh server download thousands of objects. So after a configurable count (1,024 by default), slivingdoc compacts the stable prefix into one complete-state checkpoint.
A new reader needs one checkpoint plus the short tail after it. Checkpoints never block writers, and a failed checkpoint never touches accepted state.