Repository navigation
Releases: reef-vcs/reef
Release list
Reef 0.4.0
Phase 2's last rows: the Python and TypeScript SDKs, both named reef-vcs and published to PyPI and npm by this release's workflow. Brief reef status and reef diff read at an operation now say which operation they read, reef history -o text names the authors of changes no Reef agent wrote, and the remote listener answers errors in a form every gRPC client reads. Two fixes come from trying 0.3 on a large monorepo: a workspace create waits up to 5 minutes instead of failing at 30 s, and reef init adds Reef's MCP entry without reformatting .mcp.json. Phase 2 is complete.
Upgrading from 0.3
Nothing to migrate. The API revision (14) and the database schema (6) are those of 0.3, so a 0.3 daemon serves the 0.4 CLI. Restart it anyway, once reef ws list shows no active workspaces (reef daemon stop; the next command starts 0.4's), for the remote listener's fix below, which the SDKs need when they reach a daemon remotely.
Added
- The Python SDK (ADR 0054,
docs/python-sdk.md):pip install reef-vcs, thenimport reef. Every API call is a typed method onreef.connect()'s client and onreef.aio's, generated from the schema, with page iterators and streams. The daemon's errors arrive as exceptions by kind, withreason,hintandnext. The SDK finds the daemon as the CLI does and reaches a remote one with its key pinned before the token is sent.client.workspace(...)holds a workspace for a block of code asreef ws rundoes for a command: lease heartbeats, a presence entry, and a release with a final snapshot. Published to PyPI by each release. - The TypeScript SDK (ADR 0054,
docs/typescript-sdk.md):npm install reef-vcs, thenimport { connect } from "reef-vcs". ESM for Node 22.15 and later, loadable byrequire(). Every API call is a typed async method, generated from the schema, with page iterators and streams; messages are inreef-vcs/api/v1. The same error classes, discovery, remote pinning (checked in every TLS handshake) and workspace block as the Python SDK, the block both asawait usingand aswithWorkspace(...). Published to npm with provenance by each release.
Fixed
reef ws create,reef ws runand the MCPcreate_workspacetool wait up to 5 minutes for a workspace create, not 30 s, so a create in a large repository that checks the whole tree out into the base cache (the first, and on the overlay or copy backend each new tree) no longer fails partway.--timeoutstill sets the deadline. A create that runs past 5 s says why on stderr, and one that reaches its deadline says that the checkout goes on and that running the command again waits for it (#188).reef initadds Reef's MCP server to an existing.mcp.jsonoropencode.jsonwithout reformatting it: the rest of the file keeps its indentation (tabs or spaces), line endings, one-line arrays, number and string spellings and final newline, and only Reef's lines are added (#190).reef initskips a.mcp.jsonoropencode.jsonwith comments or trailing commas with a reason that quotes the entry to add by hand, as it does foropencode.jsonc, instead of calling it "not a JSON object"; a file that already has Reef's entry is skipped with a reason that asks to check that Claude Code (or opencode) reads a file with comments (#190).- Piped
reef statusandreef diffread at an operation (--at-op,--at) keepat_operation, the full id of the operation read, so brief JSON says when its answer is not about now; so does the MCPget_difftool when called withat_operation. Their text read at an operation starts withAt operation <id>, with the operation's time forstatus. Reads at head print what they did (#126). reef history -o textnames a change's author, as<email>, when no Reef agent wrote it (committed or fetched outside Reef, or written by another jj client), and no longer lists-beside the agents of a change that has some (#115).- The remote listener sends a service's error as a trailers-only answer, its HEADERS frame ending the stream. It used to send the headers and then an empty DATA frame, which tonic accepted and other gRPC clients (grpcio) refused as "Stream removed".
Reef 0.3.0
The rest of Phase 2's agent surface and its live views. Agents get brief JSON by default, a task-first reef --help, symbol-level diffs and a queryable history (reef history, the MCP query_changes tool), reef schema for generating tool definitions, and one-line installs for Claude Code, Codex and opencode, with the MCP server published to the MCP registry and as an MCPB bundle per target. Humans get a web UI (reef ui) with a live smartlog, a workspaces and fleet page, and an activity feed with a replay scrubber. Status answers from an incremental index instead of a tree walk, a daemon can be reached remotely over TLS with a token, and harnesses that isolate sessions in git worktrees can ask Reef for workspaces instead (the workspace provider contract). Every Phase 2 exit criterion is met; the Python and TypeScript SDKs (R2.06, R2.07) follow in a later release.
Upgrading from 0.2
Piped output is now brief JSON: compact, one list element per line, and for ws list, status and diff only the keys an agent acts on, with zero values (false, 0, "", []) left out, so a check such as .divergent == false no longer matches. A script that reads piped reef output and needs the full objects passes -o json, or sets REEF_OUTPUT=json once. The MCP server's list_workspaces, get_status and get_diff return the brief objects too.
Restart the daemon after upgrading: reef daemon stop once reef ws list shows no active workspaces, and the next command starts 0.3's. A 0.2 daemon keeps running after the binaries are replaced, and it answers commands that use new options of calls it already has as if the option were absent: --at-op and --at read the current state, diff --symbols returns no symbols, and ws list shows no file counts. A command whose first call the 0.2 daemon lacks restarts it when it has no active workspaces (not under --no-autostart or --remote); otherwise it fails with exit code 9 and next set to reef daemon stop.
The new daemon migrates its database (schema 6, for remote tokens) when it starts, and a 0.2 daemon then refuses that database: there is no going back to 0.2 on the same state directory. Its first start also backfills each repository's history index (repos/<id>/history.sqlite3, up to 100,000 operations and 512 MiB, [history]) in the background, and the state directory gains ui/ and remote/.
reef init --user now writes the user-level files only of the agents it detects by their home directories, so a machine without ~/.claude (or CLAUDE_CONFIG_DIR) no longer gets the Claude Code skill; --agents names them instead.
Added
-
Harness packages (ADR 0052): one line sets up an agent for every repository. Claude Code:
claude plugin install reef --marketplace reef-vcs/reef(the skill, Reef's MCP server, and hooks that declare the files it edits). Codex:codex plugin marketplace add reef-vcs/reef && codex plugin add reef@reef(the skill and the server, from a plugin in the vendor-neutral Agent Plugins format). opencode:reef init --agents opencode --user. This repository is both marketplaces, pinned to each release's tag. -
reef init --agents codexand--agents opencode, also detected by.codex/and byopencode.json,opencode.jsoncor.opencode/(and opencode byOPENCODE=1): the skill in.agents/skills/reef/SKILL.md, which both read, and Reef's server in.codex/config.toml(merged keeping the file's comments) oropencode.json; with--user, in the home directory. -
reef presence hook editandend: a harness's hook reports the files the agent is about to edit as an editing entry, from the hook event on stdin and JSON Pointers into it. One entry per session, replaced by its next edit, lasting 5 minutes; it always exits 0, prints nothing, and never starts a daemon. -
An MCPB bundle per target on each release,
reef-<version>-<target>.mcpb, holdingreefandreef-daemon, and the server listed on the MCP registry asio.github.reef-vcs/reef. Releases also carryreef schema's document and descriptor set, attested with the rest. -
The onboarding benchmark's scenario E: after the Claude Code plugin.
-
The workspace provider contract (ADR 0053,
docs/workspace-provider.md):reef ws provider create | attach | release, for a program that isolates its sessions in directories and asks Reef for them instead of making git worktrees. Fields come as flags, or as one JSON object on stdin with--at FIELD=POINTERmapping the program's own event; each command prints one object (contract: 1), or the path alone with-o text. A name makescreatereturn the same workspace while it lives, and a create from inside a workspace starts on its change.--hold-while PID|parentkeeps the lease alive while that process runs, through a detachedreef ws renew --follow; nothing is released when it exits, so the daemon reclaims the workspace with a final snapshot once the lease runs out. Two reasons:PROVIDER_INPUT_INVALID,HOLDER_NOT_RUNNING. -
reef ws renew --follow [--while-pid PID]renews a lease until it ends, the process exits, or a signal. -
reef herereportsheldand no longer tells an agent to release a workspace that a provider's holder holds. -
The
reef-workspacesClaude Code plugin, opt-in besidereefin the same marketplace:claude plugin install reef-workspaces --marketplace reef-vcs/reefmakes Claude Code's worktrees (--worktree, subagents withisolation: worktree) Reef workspaces, through itsWorktreeCreateandWorktreeRemovehooks; a resumed session's first prompt holds its workspace again (UserPromptSubmit). -
Symbol-level diffs (ADR 0043, API revision 8).
GetDiffwithsymbolsreports each file's added, removed, changed and moved definitions, matched across the whole diff (renamed files and moves included), with each file's coverage, and the call sites in the new tree of the changed, removed and moved ones: exact through uploaded SCIP, by name through tags, with how many definitions share the name.expand_symbolsreturns only the named symbols' lines.max_call_sitesandsymbol_sourcetune it. Symbols are computed when at most 1,000 files differ. -
reef diff --symbolslists each file's symbol changes under it, nested, with up to three call sites each and the totals;--statadds the line counts,--call-sites Nsets how many are listed, and--sourcepicks tags or SCIP.reef diff --expand SYMBOLshows only that symbol's lines. JSON gainssymbol_summaryand each file'ssymbolsonly with these flags. -
The MCP server's
get_difftakesformat: "symbols"andexpand.tools/listis held to 20,250 bytes. -
A history index and
HistoryService.QueryChanges(ADR 0044, API revision 9): which changes touched files or symbols, who wrote them (activity per agent and workspace), when, and what became of them (open, landed ontrunk(), abandoned, undone), filtered by symbol, path, agent, workspace, change, outcome and time window, paged or counted by agent, outcome or workspace, with the history each answer covers. The daemon follows the operation log intorepos/<id>/history.sqlite3, backfills it, and keeps it through GC.[history]configures it. Two reasons:HISTORY_DISABLED,INVALID_HISTORY_QUERY. -
reef history [PATH]...with--symbol,--change-kind,--by,--workspace,--change,--outcome,--since/--until(RFC 3339 or a duration ago),--include-empty,--group-byand--limit. -
The MCP server's
query_changestool.tools/listis held to 21,250 bytes. -
Replay (ADR 0046, API revision 11):
GetStatus,ListWorkspaces,GetWorkspaceandListOperationstakeat_operationand answer as of that operation: the workspaces whose working copy its view holds, each on its change then, with what the log does not record (presence, attention, stores, the watch, lease liveness) left unset.GetOperationfinds an operation by time withended_at_or_before. Two reasons:OPERATION_NOT_RETAINED,TOO_FAR_BACK;STATUS_SOURCE_OPERATION. -
--at-oponreef status,ws list,ws inspectandop log, and--at WHEN(an RFC 3339 time or a duration ago) on every command that takes--at-op. -
ListWorkspacesRequest.changed_file_countsfillsWorkspace.changed_file_count, how many files each active workspace's change changes as of its last snapshot (API revision 10).reef ws listasks for it, in JSON and in a newFILEScolumn, and so does the MCP server'slist_workspaces. -
-o brief: compact JSON with only the keys an agent acts on (ADR 0045). -
A web UI (ADR 0047): a React and TypeScript app the daemon serves on a loopback listener, read-only, over gRPC-web. It lists every registered repository with its latest operation and its active agents, live from the operation and presence streams, and has a reference page for the state colors (
/states). The firstreef uibinds the listener, which stays until the daemon stops; a daemon whose user never runsreef uiopens no port. -
reef uiopens the web UI in a browser on a new session, through a launch file, so the session's token is on no command line.--printprints the session URL instead;--openopens a browser even when stdout is not a terminal, where it otherwise opens nothing. -
`Dae...
Reef 0.2.0
The first release of Phase 2: a typed API, reef.api.v1, with the CLI as a thin client of it, so reef now needs reef-daemon beside it or on PATH; commands that look at and revert what agents did without jj; an MCP server; live operation and presence streams; a symbol index; and garbage collection on a timer. Coming from 0.1, read Upgrading from 0.1 first.
Added
- The symbol index (ADR 0042):
SymbolServicewithIndexSymbols,UploadScipDocumentsandListSymbols(API revision 7). The daemon extracts tree-sitter tags for Python, TypeScript, TSX, JavaScript, Go and Rust itself, in the background for new snapshots and edits and on demand, and stores definitions and references with their ranges and enclosing definitions in a per-repository cache (repos/<id>/symbols.sqlite3), keyed by path and file content so unchanged files share entries across revisions. SCIP documents an indexer wrote are uploaded and checked against the file they describe. Four error reasons:SYMBOLS_DISABLED,SYMBOLS_NOT_INDEXED,INVALID_SYMBOL_QUERYandINVALID_UPLOAD. GC collects the index as its fifth step. reef symbols index,uploadandlist(aliassym): index a revision, upload a SCIP index, and list a file's or directory's symbols or a name's occurrences, with JSON when piped.[symbols]inconfig.toml:enabled,background,workers,max_file_sizeandmax_size.reef-symbols, the extraction crate: Reef's tag queries, SCIP's symbol grammar and SCIP normalization, with no jj-lib, gRPC or SQLite dependency.- The daemon runs garbage collection by itself every
[gc] interval(default a day;0sturns it off), over every repository, 10 minutes after it starts at the earliest and a full interval after the last full run, timed orreef ws gc(#95, ADR 0041). Its reports go to the daemon's log. - API revision 6:
GcReport.base_bytes_unshared, what removing the base cache entries freed on disk, counting blocks shared between incremental bases once (measured on APFS).reef ws gcprints it beside the allocated size. reef-broker, the subscription broker (ADR 0038): bounded in-memory rings between a log and the streams that follow it, generic over topic, position and item, with no jj-lib or gRPC dependency.- A subscription load test (
crates/reef-daemon/tests/subscription_load.rs, docs/load-tests.md): 500 operation-log and 500 presence subscribers at 1,000 operations a minute, every delivery checked, gated on p99 within 100 ms.subscription-load.ymlruns it on both runners. reef ws runreports the command it runs as a presence entry (running <command line>), refreshed every 10 seconds and cleared when the command exits, before the release.reef mcpreports, per repository or workspace its calls name, the last tool called there, for a minute after the call, and clears its entries when it exits. Both name the agent from--agentorREEF_AGENT, else the workspace's owner.reef presence reportreports what you are doing:TEXTfor an activity,--pathfor files you are editing,--phaseand--donefor progress, in the current workspace or repository; it prints the entry's id (-o text) or the entry.--idreplaces an entry, and--id-filekeeps the id in a file for callers that are separate processes, such as a harness's hooks; an entry that had ended is reported anew.reef presence clearends entries by id or--id-file.reef ws renewrenews a workspace's lease, the one you are in by default: whatws rundoes for its command, for a workspace held outsidews run.reef heresuggests it for a lease in its grace period.reef_client::presence: theReporterthatws runandreef mcpuse, the reporting contract thatReportPresence's comment now states, andPresenceApi.- Presence (ADR 0037):
ReportPresence,ClearPresenceandSubscribePresence. Entries say what an agent is doing (activity), the paths it is editing, a lease heartbeat, or a long operation's progress; each has a TTL (30 seconds by default, 1 second to 10 minutes) and lives in the daemon's memory only. A subscription receives the current set, then changes, the latest state of each entry, at least 50 ms apart. At most 4,096 client entries, 32 per agent. Six error reasons:PRESENCE_NOT_FOUND,INVALID_PRESENCE_ID,PRESENCE_MISMATCH,DAEMON_PRESENCE,PRESENCE_TOO_LARGEandTOO_MANY_PRESENCE_ENTRIES.Client::report_presence,clear_presenceandsubscribe_presence. - The daemon reports presence itself: a lease entry per workspace on every heartbeat, lapsing after two missed ones; the paths each snapshot changes; and the progress of workspace creates and store builds that run longer than 250 ms.
reef presence watchprints presence as it changes, one JSON line per event when piped, filtered by--repo,--workspace,--byand--kind;--for 0prints who is doing what now. It stops after 30 seconds unless--follow, and stays a correct stream of changes across a daemon restart.- API revision 5:
Presence.lease,Presence.by_daemon,PresenceEvent.end_reasonandPresenceEndReason. SubscribeOperationsstreams the operation log (ADR 0036): frames of operations as they are recorded, filtered likeListOperations, from now, after an operation, or from a frame's cursor, with nothing dropped. Operations Reef records arrive within 100 ms, otherjjprocesses' within about 200 ms. Four error reasons:INVALID_CURSOR,TOO_FAR_BEHIND,OPERATION_LOG_REWRITTENandTOO_MANY_SUBSCRIPTIONS.Client::subscribe_operationsreturns the stream.reef op watchprints operations as they are recorded, one JSON line each when piped, withop log's filters,--afterto start from an operation, and--count; it stops after 30 seconds (--for) unless--follow, and says on stderr how to continue.- API revision 4:
SubscribeOperationsResponse.head_operation_ids. reef mcp, an MCP server over stdio (ADR 0035): twenty tools named after the API's RPCs (get_status,list_changes,get_diff,read_file,edit_files,describe_change,undo_operation,create_workspace,wait_for_operationsand the rest), returning the JSONreef -o jsonprints, with errors as tool results carrying the daemon'shint,nextandmetadata. No ADMIN RPC is a tool. Workspaces the server creates are heartbeated while it runs.wait_for_operationswaits for operations matching a filter, readingSubscribeOperations.reef initwrites Reef's entry in.mcp.jsonwith Claude Code's files, and with--agents mcp; it owns onlymcpServers.reefand leaves the rest of the file as it was.json::Edited,edit_files' result, inreef-client.- Commands over the VCS services (ADR 0034):
reef status,log,show,diff,describe,abandon,undo,op log,op show,ws snapshot,file readandfile list, from a workspace or a checkout, or with--workspaceor--repo. Paths given are relative to the current directory; paths printed are repository-relative. Each prints JSON when piped (file readprints the file's bytes unless-o json), and errors from the API carry the CLI's ownhintand anextbuilt from the command and the error. Writes in a workspace return it afterwards and note on stderr when it is divergent, naming how to resolve it. - The JSON error object carries the daemon's
metadatawhen there is any, such asAMBIGUOUS_REVISION'scommit_ids. - An error reason,
SNAPSHOT_FAILED(metadataworkspace_id), on a snapshot that fails before a read or a write; its kind is the snapshot error's. - The read side of the API (ADR 0030):
GetStatus(the repository's trunk, active workspaces, mutable heads, conflicted and divergent changes, and a workspace's changed and conflicted files),ListChanges(the log, with graph edges, over jj's revset language),GetChange,GetDiff(summary, line counts, or unified hunks, with renames and copies),ListOperationsandGetOperation. Revisions are jj revsets with Reef'strunk(),immutable_heads(),immutable()andmutable(); reads can be made at any operation, and reads of a workspace's@snapshot it first. - Every operation a response reports carries its effects: changes created, rewritten, hidden or abandoned, bookmarks moved, working copies moved. Operations Reef did not just write are classified by kind.
- The write side of the API (ADR 0031):
DescribeChangesets a commit's description;AbandonChangeabandons a commit, and on one commit of a divergent change resolves the divergence, handing its descendants, bookmarks and working copies to the surviving commit;UndoOperationreverts exactly one operation, refusing what it cannot revert exactly (workspace creation, release and moves, git ref imports, operations later ones changed again). Each takes an optionalexpected_commit_id(describe, abandon) and returns the operation it recorded, with effects. Workspaces a write moves are held still and, for abandon and undo, snapshotted first, so a write never overwrites unsnapshotted edits; t...
Reef 0.1.0
Phase 1: leased copy-on-write workspaces. Start with the quickstart.
Added
reef-daemon, a per-user service over a Unix socket (gRPC,reef.daemon.v0), with a SQLite state database, structured logs, and a graceful restart that keeps leases and mounts. Anyreefcommand starts it.- Workspaces on existing git and jj repositories:
reef ws create|list|inspect|release|move|gc, each a jj workspace materialized copy-on-write (APFS clones, kernel overlayfs or fuse-overlayfs, reflinks on btrfs and XFS, or a plain copy). - No setup step: the first
reefcommand in a git checkout registers it and initializes jj alongside git (.jj/and one line in.git/info/exclude). - Continuous snapshots driven by a filesystem watcher (notify; Watchman for large repositories when installed), with staleness and divergence handling that never discards an agent's edits.
- Leases with heartbeats, expiry, and reclaim with a final snapshot;
reef ws runas the agent entry point. reef init,reef guide, andreef here:AGENTS.md,CLAUDE.md, and a Claude Code skill that start agents in a workspace on their own, and errors that carry ahintand thenextcommand to run.reef statusand similar guesses at "where am I" point atreef here.reef.tomlworkspace spec: lease default, snapshot excludes, and shared dependency stores built once per lockfile and mounted read-only (reef store build|list).reef backupandreef restore, and a quiesce RPC for filesystem snapshots.- Prebuilt binaries for macOS and Linux on arm64 and x86_64, a one-line installer, a Homebrew tap, and
cargo binstallmetadata. - Benchmarks for workspace creation and a load test at 200 workspaces (docs/benchmarks.md, docs/load-tests.md).
- An agent onboarding benchmark: what a Claude Code agent new to Reef spends to reach a working
reef ws run, in four scenarios, run in CI on demand (docs/onboarding-benchmark.md). - A threat model (docs/threat-model.md) and the fixes from its review: the daemon refuses a state directory another user owns and keeps its state
0700and its database0600; registration refuses a repository another user owns ([repo] allow_foreign_owner);reef store buildprints the command it runs, and[store] allow_builds = falseturns builds off; a root daemon warns at start; a commit with a tree entry jj cannot read is refused instead of crashing the repository; the release workflow attests each tarball's build provenance (gh attestation verify), first exercised by 0.1.0's release.
Known limitations
- Watcher snapshots are local: they stay in the repository on this machine and are never pushed or exported to git.
- Two workspaces editing one change make it divergent. Nothing is lost; resolving it is a jj operation for now.
- New workspaces start on
main,origin/HEAD's branch,masterortrunk. In a repository with none of these they start on the checkout's own working-copy change, and ajjcommand in the checkout rebases them; pass--new-on <branch>. - In a git checkout, Reef imports branches but does not export: an agent's change is a jj change, not a git branch, until you make it one.
- A workspace directory has neither
.gitnor.jj, sogitandjjcommands inside it fail: agents work on files, and you rungitandjjin your checkout. reef.tomlis read from the commit a workspace starts on, not from disk: commit it before it takes effect.- On Linux a new workspace's watch attaches in the background after
reef ws createreturns, andreef ws inspectshowsnotify (attaching)meanwhile; writes made before it is up reach the store with its first snapshot. - A shared store is read-only, so a tool that writes a cache into it fails (Vitest's
node_modules/.vitest-cache; Vite'scacheDirsetting moves it). - On macOS, and on Linux without
CAP_SYS_ADMIN, a store is protected by file permissions only: a process running as the same user could make it writable again. - A workspace is not a sandbox: an agent runs as you and can do anything you can, including through the daemon's socket, and a harness sandbox that lets it reach the socket does not confine what the daemon does for it.
reef store buildruns the repository's declared command as you, with network access.- The daemon's state directory and socket directory must be your own: a
REEF_SOCKETdirectly in/tmp, or a state directory another user created, is refused at start. - Reef is built on jj-lib 0.46. A newer
jjmay upgrade the repository's on-disk formats when you run it there. - Tools that do not read
.git/info/excludesee the.jj/directory. - The binaries are not signed or notarized. Windows is not supported.