Repository navigation
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. -
DaemonService.CreateWebSession(API revision 12): binds the web listener if needed and mints a session; ADMIN. Five reasons:WEB_UI_DISABLED,WEB_UI_NOT_BUILT,WEB_UI_UNAVAILABLE,WEB_SESSION_INVALID,WEB_ACCESS_DENIED. -
[ui]in the daemon'sconfig.toml:enabled(true) andlisten("127.0.0.1:0"; a loopback address only). -
Released binaries embed the web UI. A build from source embeds a placeholder page instead, which
reef uirefuses withWEB_UI_NOT_BUILT, unlessjust web-build(Node 24) ran beforecargo build. -
The web UI's smartlog, at
/repos/<id>/smartlogand linked from the overview: a revset's changes as a live graph, by default Reef's log set with each stack's fork point (trunk() | working_copies() | ancestors(mutable(), 2)), stacks hanging off a trunk spine with left-out ranges dashed. Stacks keep their places as agents snapshot, and a refresh follows the operation stream, one read at a time. Selecting a change shows its description, parents, files and who wrote it;?at=<operation>shows the graph at that operation, frozen. The page prints thereef logcommand that lists the same entries. -
A browser smoke test of the web UI (
just web-e2e, CI'sweb e2ejob): a daemon built with the app, on a scratch state directory, in Chromium, Firefox and WebKit, checking the session hand-off, one operation stream and one presence stream shared by two tabs, live updates of the overview, of a smartlog row in place, and of the workspaces page (lease health, an agent's activity, a write, a release), and the no-session page after a restart. -
The web UI's workspaces page, at
/repos/<id>/workspacesand linked from the overview's agent count and the smartlog: an Agents table (each agent with its workspaces, current change, what it is doing and its lease health) and a Workspaces table (owner, agents, change, changed files, disk beyond the base, lease, last snapshot), live from the shared streams. Lease health comes from the daemon's heartbeat entries, never the browser's clock:heartbeating,not heartbeating(never renewed),missed heartbeats(a renewing holder stopped), orwaiting for a heartbeatjust after a daemon start. Disk is measured one workspace at a time, re-measured only where an operation touched it, or on "Measure again". Workspaces that end and agents that leave keep their rows until hidden. A detail pane shows a selected workspace or agent;?at=<operation>shows the page at that operation, frozen, without presence or disk. A nav links the smartlog and the workspaces page. -
The web UI's activity feed, at
/repos/<id>/activity: the operation log as a live feed, one agent's snapshots and edits in a workspace (or one user'sjjcommands) coalesced into one row while no more than 5 minutes apart, with every count exact across reconnects, reloads and garbage collection, new rows held behind a count while you read, thereef op logcommand that lists the same operations, and a pane with an operation's details and effects. It holds up to 10,000 operations, 1,000 more per "Load older". -
The scrubber, in a header shared by the smartlog, workspaces and activity pages: one slider over the loaded operations that moves the open page to
?at=<operation>, by arrow keys (a burst is one navigation), by dragging on a time axis of operation counts, or by a time (GetOperationby time beyond what is loaded). -
Operation ids in the web UI link to the activity feed: the overview's latest operation, the smartlog's and the workspace pane's causes and snapshots, and the "at operation" banner.
-
Developer mode in the web UI: a switch in Settings, at the top right, off by default and remembered per daemon, that shows ids, the CLI commands behind each page, the smartlog's revset, exact times and the API's words, as the pages showed them before the design pass.
-
An incremental status index (ADR 0048, API revision 13):
StatusWalkReasonandWorkspaceStatus.walk_reasonsay why a status walked the tree;sourceisindexorwalk.[status_index]in the daemon'sconfig.toml:enabled(true) andsync_timeout("1s"). JSON fromreef status -o jsonand the MCPget_statusgainswalk_reasonwhen the source iswalk. -
A remote listener (ADR 0049, API revision 14): a daemon serves the API, gRPC-web and the web UI to other machines on one TLS port, to callers holding a token. Off by default;
[remote] enabled = true, or--remoteonreef-daemon(reef daemon start --serve-remote), turns it on. TLS is Reef's own, on rustls and ring: a generated key clients pin by its fingerprint, or[remote] tls_certandtls_key, reloaded when they change. ADMIN RPCs are never served remotely. -
RemoteService(ADMIN):CreateRemoteToken,ListRemoteTokens,RotateRemoteToken,RevokeRemoteToken. Tokens are named,readorwrite, expire (30 days by default), rotate with an overlap, and revoke at once, open streams included.GetDaemonStatusResponse.remote,Operation.remoteandWorkspace.remote_token_id; operations a remote call causes carryreef.remote-tokenandreef.remote-token-name. Eight reasons:REMOTE_TOKEN_INVALID,REMOTE_ACCESS_DENIED,REMOTE_WORKSPACE_NOT_OWNED,REMOTE_BUSY,REMOTE_TOKEN_NOT_FOUND,REMOTE_TOKEN_EXISTS,TOO_MANY_REMOTE_TOKENS,INVALID_REMOTE_TOKEN_NAME. The database moves to schema version 6. -
reef remote add|list|remove|showandreef remote token create|list|rotate|revoke;reef --remote <name>(orREEF_REMOTE,REEF_REMOTE_TOKEN,REEF_REMOTE_FINGERPRINT) runs a command on a remote daemon.reef daemon statusshows the remote listener. -
[remote]in the daemon'sconfig.toml:enabled(false),listen("0.0.0.0:7470"),names(the host name),tls_certandtls_key. -
reef schemaprints the CLI's commands (each one's input schema, theargvthat turns an input into words, and its output with-o json,-o briefand on a pipe), the MCP tools with output schemas, and the API's services and messages as canonical proto3 JSON, in one JSON Schema 2020-12 document;reef schema cli <words>,mcp <tool>andapi <name>print one, andreef schema --descriptor-setprints the API's protobuf descriptor set (ADR 0050). It runs offline. The MCP server'stools/listis unchanged.
Changed
-
reef --helpis a short, task-first help (ADR 0051): the four commands agents need first (ws list,here,ws run,history), then every other command by name, in 509 bytes instead of 3,547.--help-all, on the root and every command, prints the full help. The global options but-o(--state-dir,--socket,--config,--no-autostart,--no-register,--timeout,-q,-v,--agent,--remote) are shown only there; completions andreef schemastill list them. Every command's--helpis about half as long. -
The R2.17 token benchmark's live gate adds the
mcparm's fixed cost, as its scripted half always did. -
reef init --userwrites the user-level files of the agents it selects, recognizing each by its directory in the home directory (~/.claude,$CODEX_HOMEor~/.codex,$XDG_CONFIG_HOME/opencodeor~/.config/opencode), rather than always writing Claude Code's skill.--agents none --usernow writes nothing outside the repository. -
The web UI's design pass: an app bar with the Reef mark, the repository's name and tabs for Smartlog, Workspaces and Activity, with the activity scrubber as a strip under it; pages use the full width; repositories and agents are cards; smartlog rows lead with the description; times read "4 min ago" with the exact time on hover; identities have colored initials; operations read as sentences ("described a change") and lease health as healthy, idle, waiting or unresponsive; light and dark brand colors. Ids, commands, revsets and exact times move behind developer mode.
-
The web UI loads one chunk per page, so a page loads only its own code, and the JavaScript budget (500 KiB) applies to what each page loads. A tab older than the daemon that opens a page it had not loaded says to reload. The repository pages' nav moves into a header they share, with Activity added.
-
The web UI counts an agent once: the overview's "Active agents" and the smartlog's agent labels no longer count the daemon's lease and editing entries under the workspace's owner as a second agent beside the name the agent reports under. The web UI's JavaScript budget is 500 KiB (was 450 KiB).
-
A workspace's owner may not contain control characters (
CreateWorkspacerefuses one): an owner stands in for the agent on operations, which the remote listener's caller identity rides on (R2.08). -
Reads that snapshot a workspace first (
GetStatus,ListChanges,GetChange,GetDiff,ReadFile,ListFiles,ListSymbols,GetWorkspacewithsnapshot_first) answer from the incremental status index: no snapshot when nothing changed since the last scan, and a snapshot of the changed paths alone otherwise, instead of a walk of every tracked file. Each read first syncs with the watcher through a cookie in a directory outside the workspace root, so a write made just before it is seen. -
A watched workspace is now fully scanned at least every
watcher.watched_fallback_interval(10 minutes), busy or idle; before, a workspace with events was never fully scanned until its release. A full scan that finds a change no event named logs "status index missed changes". -
On Linux a close after writing (
IN_CLOSE_WRITE) counts as a change, so a write through a memory mapping is snapshotted. -
Piped output is brief JSON (ADR 0045): compact, a list one element per line, and for
reef ws list,statusanddiffonly the keys an agent acts on, with zero values left out. This breaks scripts that read pipedreef ws list,statusordiffwithout-o json;-o jsonorREEF_OUTPUT=jsonrestores the full JSON. On R2.17'sstate-repoquestion,reef ws listalone is 215 bytes, against 2,893 for git. -
The MCP server's
list_workspaces,get_statusandget_diffreturn the same brief objects, withoutnext_page_tokenon a last page. -
reef ws run --workspaceon a released or reclaimed workspace now says where its work is and names thereef ws create --changethat starts again on it. -
Reads at an operation share the head's commit index when it covers the operation's view: after GC they no longer rebuild and save a full index for each older operation read, and the daemon keeps 64 of them loaded instead of 8. A commit created after the operation, named by its full id in a read at it, now resolves (hidden) instead of not found; change offsets and short prefixes still resolve as of the operation.
-
ListSymbolsby name or symbol reads only the matching rows of the index, not each matching file's whole entry, and (R2.16) reads them in path order with its filters and cursor in SQL, stopping at the end of the page.
Fixed
- A command run while the daemon was shutting down, or still starting, started a second daemon, which lost the instance lock and exited, and the command reported "reef-daemon exited … before … answered". Since tonic 0.14.7 a daemon refuses connections while it drains. A command now waits, up to 15 s, while the daemon's lock is held and its socket refuses, and starts one only once the lock is free (#162).
- A panic on the daemon's filesystem-watcher threads no longer ends a workspace's watch silently. Since notify 8.2.1 such a panic unwound out of the macOS
FSEventsthread, and a later unwatch could then spin forever. A panic in Reef's tap is logged and marks every notify workspace's journalevents-lost, so the next reads walk; any other panic on notify's or the debouncer's threads aborts the daemon, as notify did before 8.2.1 on macOS, and the next command starts one that rescans (#161). reef initsays when the.mcp.jsonit writes duplicates the MCP server of Reef's Claude Code plugin, installed on this machine, for this user or this repository, and how to turn the project'sreefserver off on this machine; it still writes the file, which is for everyone working in the repository (#153). Claude Code's user directory followsCLAUDE_CONFIG_DIR, for the user-level skill too.reef mcp --help's example no longer names one harness's command (#118).- On macOS, creating or releasing a workspace no longer drops other workspaces' filesystem events: each notify workspace has its own
FSEventsstream. - A read at a GC-kept copy of an operation from before the last GC failed: jj could not build an index for it once GC had removed its ancestors. It answers through the head's index now (R2.12).
- The MCP server reads path arguments as the CLI does under
--workspace:./,..and a trailing/resolve, and.is the root where a tool takes one (get_diff,list_files).get_diff,read_fileandlist_fileshad refusedsrc/withINVALID_PATH, andquery_changesmatched nothing for./src. Anedit_filestarget with a trailing/is still refused (#117). - The MCP server's
get_diffdescribedmax_lines_per_fileas "[default: no cut]"; the default is 1,000. - Timestamps print with exactly three fraction digits (
2026-10-30T17:04:34.000Z,…34.100Z), in the CLI's JSON and text, MCP results and backup manifests, so they sort as strings.time's RFC 3339 formatting had trimmed trailing zeros and dropped a zero fraction, so a time on a whole second sorted after a later one in the same second.