Skip to content

Releases: reef-vcs/reef

Reef 0.4.0

Choose a tag to compare

@github-actions github-actions released this 04 Nov 04:59
aa27f40

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, then import reef. Every API call is a typed method on reef.connect()'s client and on reef.aio's, generated from the schema, with page iterators and streams. The daemon's errors arrive as exceptions by kind, with reason, hint and next. 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 as reef ws run does 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, then import { connect } from "reef-vcs". ESM for Node 22.15 and later, loadable by require(). Every API call is a typed async method, generated from the schema, with page iterators and streams; messages are in reef-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 as await using and as withWorkspace(...). Published to npm with provenance by each release.

Fixed

  • reef ws create, reef ws run and the MCP create_workspace tool 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. --timeout still 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 init adds Reef's MCP server to an existing .mcp.json or opencode.json without 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 init skips a .mcp.json or opencode.json with comments or trailing commas with a reason that quotes the entry to add by hand, as it does for opencode.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 status and reef diff read at an operation (--at-op, --at) keep at_operation, the full id of the operation read, so brief JSON says when its answer is not about now; so does the MCP get_diff tool when called with at_operation. Their text read at an operation starts with At operation <id>, with the operation's time for status. Reads at head print what they did (#126).
  • reef history -o text names 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

Choose a tag to compare

@github-actions github-actions released this 02 Nov 22:19
776e3c8

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 codex and --agents opencode, also detected by .codex/ and by opencode.json, opencode.jsonc or .opencode/ (and opencode by OPENCODE=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) or opencode.json; with --user, in the home directory.

  • reef presence hook edit and end: 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, holding reef and reef-daemon, and the server listed on the MCP registry as io.github.reef-vcs/reef. Releases also carry reef 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=POINTER mapping the program's own event; each command prints one object (contract: 1), or the path alone with -o text. A name makes create return the same workspace while it lives, and a create from inside a workspace starts on its change. --hold-while PID|parent keeps the lease alive while that process runs, through a detached reef 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 here reports held and no longer tells an agent to release a workspace that a provider's holder holds.

  • The reef-workspaces Claude Code plugin, opt-in beside reef in the same marketplace: claude plugin install reef-workspaces --marketplace reef-vcs/reef makes Claude Code's worktrees (--worktree, subagents with isolation: worktree) Reef workspaces, through its WorktreeCreate and WorktreeRemove hooks; a resumed session's first prompt holds its workspace again (UserPromptSubmit).

  • Symbol-level diffs (ADR 0043, API revision 8). GetDiff with symbols reports 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_symbols returns only the named symbols' lines. max_call_sites and symbol_source tune it. Symbols are computed when at most 1,000 files differ.

  • reef diff --symbols lists each file's symbol changes under it, nested, with up to three call sites each and the totals; --stat adds the line counts, --call-sites N sets how many are listed, and --source picks tags or SCIP. reef diff --expand SYMBOL shows only that symbol's lines. JSON gains symbol_summary and each file's symbols only with these flags.

  • The MCP server's get_diff takes format: "symbols" and expand. tools/list is 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 on trunk(), 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 into repos/<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-by and --limit.

  • The MCP server's query_changes tool. tools/list is held to 21,250 bytes.

  • Replay (ADR 0046, API revision 11): GetStatus, ListWorkspaces, GetWorkspace and ListOperations take at_operation and 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. GetOperation finds an operation by time with ended_at_or_before. Two reasons: OPERATION_NOT_RETAINED, TOO_FAR_BACK; STATUS_SOURCE_OPERATION.

  • --at-op on reef status, ws list, ws inspect and op log, and --at WHEN (an RFC 3339 time or a duration ago) on every command that takes --at-op.

  • ListWorkspacesRequest.changed_file_counts fills Workspace.changed_file_count, how many files each active workspace's change changes as of its last snapshot (API revision 10). reef ws list asks for it, in JSON and in a new FILES column, and so does the MCP server's list_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 first reef ui binds the listener, which stays until the daemon stops; a daemon whose user never runs reef ui opens no port.

  • reef ui opens the web UI in a browser on a new session, through a launch file, so the session's token is on no command line. --print prints the session URL instead; --open opens a browser even when stdout is not a terminal, where it otherwise opens nothing.

  • `Dae...

Read more

Reef 0.2.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 20:50
b5741e8

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): SymbolService with IndexSymbols, UploadScipDocuments and ListSymbols (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_QUERY and INVALID_UPLOAD. GC collects the index as its fifth step.
  • reef symbols index, upload and list (alias sym): 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] in config.toml: enabled, background, workers, max_file_size and max_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; 0s turns it off), over every repository, 10 minutes after it starts at the earliest and a full interval after the last full run, timed or reef 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 gc prints 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.yml runs it on both runners.
  • reef ws run reports 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 mcp reports, 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 --agent or REEF_AGENT, else the workspace's owner.
  • reef presence report reports what you are doing: TEXT for an activity, --path for files you are editing, --phase and --done for progress, in the current workspace or repository; it prints the entry's id (-o text) or the entry. --id replaces an entry, and --id-file keeps 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 clear ends entries by id or --id-file.
  • reef ws renew renews a workspace's lease, the one you are in by default: what ws run does for its command, for a workspace held outside ws run. reef here suggests it for a lease in its grace period.
  • reef_client::presence: the Reporter that ws run and reef mcp use, the reporting contract that ReportPresence's comment now states, and PresenceApi.
  • Presence (ADR 0037): ReportPresence, ClearPresence and SubscribePresence. 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_LARGE and TOO_MANY_PRESENCE_ENTRIES. Client::report_presence, clear_presence and subscribe_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 watch prints presence as it changes, one JSON line per event when piped, filtered by --repo, --workspace, --by and --kind; --for 0 prints 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_reason and PresenceEndReason.
  • SubscribeOperations streams the operation log (ADR 0036): frames of operations as they are recorded, filtered like ListOperations, from now, after an operation, or from a frame's cursor, with nothing dropped. Operations Reef records arrive within 100 ms, other jj processes' within about 200 ms. Four error reasons: INVALID_CURSOR, TOO_FAR_BEHIND, OPERATION_LOG_REWRITTEN and TOO_MANY_SUBSCRIPTIONS. Client::subscribe_operations returns the stream.
  • reef op watch prints operations as they are recorded, one JSON line each when piped, with op log's filters, --after to 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_operations and the rest), returning the JSON reef -o json prints, with errors as tool results carrying the daemon's hint, next and metadata. No ADMIN RPC is a tool. Workspaces the server creates are heartbeated while it runs. wait_for_operations waits for operations matching a filter, reading SubscribeOperations.
  • reef init writes Reef's entry in .mcp.json with Claude Code's files, and with --agents mcp; it owns only mcpServers.reef and leaves the rest of the file as it was.
  • json::Edited, edit_files' result, in reef-client.
  • Commands over the VCS services (ADR 0034): reef status, log, show, diff, describe, abandon, undo, op log, op show, ws snapshot, file read and file list, from a workspace or a checkout, or with --workspace or --repo. Paths given are relative to the current directory; paths printed are repository-relative. Each prints JSON when piped (file read prints the file's bytes unless -o json), and errors from the API carry the CLI's own hint and a next built 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 metadata when there is any, such as AMBIGUOUS_REVISION's commit_ids.
  • An error reason, SNAPSHOT_FAILED (metadata workspace_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), ListOperations and GetOperation. Revisions are jj revsets with Reef's trunk(), immutable_heads(), immutable() and mutable(); 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): DescribeChange sets a commit's description; AbandonChange abandons a commit, and on one commit of a divergent change resolves the divergence, handing its descendants, bookmarks and working copies to the surviving commit; UndoOperation reverts 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 optional expected_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...
Read more

Reef 0.1.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 10:45
d156d1e

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. Any reef command 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 reef command 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 run as the agent entry point.
  • reef init, reef guide, and reef here: AGENTS.md, CLAUDE.md, and a Claude Code skill that start agents in a workspace on their own, and errors that carry a hint and the next command to run. reef status and similar guesses at "where am I" point at reef here.
  • reef.toml workspace spec: lease default, snapshot excludes, and shared dependency stores built once per lockfile and mounted read-only (reef store build|list).
  • reef backup and reef 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 binstall metadata.
  • 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 0700 and its database 0600; registration refuses a repository another user owns ([repo] allow_foreign_owner); reef store build prints the command it runs, and [store] allow_builds = false turns 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, master or trunk. In a repository with none of these they start on the checkout's own working-copy change, and a jj command 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 .git nor .jj, so git and jj commands inside it fail: agents work on files, and you run git and jj in your checkout.
  • reef.toml is 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 create returns, and reef ws inspect shows notify (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's cacheDir setting 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 build runs the repository's declared command as you, with network access.
  • The daemon's state directory and socket directory must be your own: a REEF_SOCKET directly in /tmp, or a state directory another user created, is refused at start.
  • Reef is built on jj-lib 0.46. A newer jj may upgrade the repository's on-disk formats when you run it there.
  • Tools that do not read .git/info/exclude see the .jj/ directory.
  • The binaries are not signed or notarized. Windows is not supported.