Orbit Graph — Decisions
Mirrored from
docs/design/orbit-graph/4_decisions.md. Edit the source document in the repository, not this generated page.
ADR-style log of non-obvious orbit-graph decisions. Each entry names the pressure, the choice, and the tradeoff. Entries are append-only and keyed by global ADR ID; superseded entries are marked, not deleted.
Format for each entry: Status · Date · Task(s), then Context → Decision → Consequences. Cost lines are mandatory.
ADR-0184 — Graph is a derived index, not a versioned store
Section titled “ADR-0184 — Graph is a derived index, not a versioned store”Status: Superseded by ADR-0195 · 2026-06-13 · [ORB-00294] [ORB-00377]
Context. orbit-knowledge was built as a git-like history layer: content-addressed objects, mutable refs, atomic swaps, lock protocols. In practice the graph is consumed as “fresh queryable index of the current code” — none of the version-store affordances are used by agents.
Decision. Reframe the graph as a derived index, regenerable from (file_contents, extractor_version). Delete object storage, mutable refs, and atomic-swap locking. Single SQLite file per worktree is the only durable state.
Consequences.
- Deletes ~3k LOC of object-store, lock, and ref-management code.
- Removes the lock protocol’s structural inability to coordinate same-branch worktrees (see knowledge-graph ADR-002 cost line).
- Cost: no history. “What did the graph look like at commit X?” is no longer a query the graph can answer. Use git for that.
ADR-0185 — Per-worktree DB filename embeds extractor version
Section titled “ADR-0185 — Per-worktree DB filename embeds extractor version”Status: Proposed · 2026-05 · [ORB-00294]
Context. When extractor logic changes (new language, fixed parse bug, schema tweak), the on-disk DB becomes incompatible. The traditional fix is schema migration code; the V1 ethos is to keep complexity out of the storage layer.
Decision. DB filename is <branch>.<extractor_version>.db. Bumping EXTRACTOR_VERSION makes old DBs invisible; they’re deleted on next sync. No migration code.
Consequences.
- Extractor version bumps are zero-friction; agents never see migration failures.
- Multiple extractor versions can coexist on disk temporarily during rollback testing.
- Cost: cold rebuild after every extractor bump. For a 200k LOC repo that’s ~3s, acceptable per the perf budget. For a much larger repo it could become noticeable; revisit if a user complains.
ADR-0186 — Symbol IDs are ephemeral; resolution by qualified name
Section titled “ADR-0186 — Symbol IDs are ephemeral; resolution by qualified name”Status: Proposed · 2026-05 · [ORB-00294]
Context. With incremental sync, a file’s symbol rows are deleted and re-inserted on change. If cross-file refs FK to symbols.id, every incremental rebuild orphans inbound refs from other files. The current orbit-knowledge schema has an identity_key column trying to paper over this; it doesn’t fully work and adds complexity.
Decision. No foreign key on symbols.id from any table. Refs and relations resolve by target_qualified (string lookup). A target_symbol_hint INTEGER column exists as a build-time cache but is non-authoritative.
Consequences.
- Incremental sync is correct by construction: dropping a file’s symbols doesn’t dangle anything.
- No
identity_keycolumn or cross-build lineage tracking machinery. - Cost: string lookups instead of integer FK joins. SQLite’s B-tree on
target_qualifiedkeeps this fast (low single-ms even on 100k symbols), but it’s a real cost compared to the natural FK design. Rename tracking is a separate feature on top of git, not a graph affordance.
ADR-0187 — Two ref tables, not one
Section titled “ADR-0187 — Two ref tables, not one”Status: Proposed · 2026-05 · [ORB-00294]
Context. The original draft put all cross-symbol edges in one refs table with a kind column covering call | type | impl | use | trait_bound. Calls and type uses are anchored to (file, span). Impl relations are anchored to (concrete_symbol, trait_symbol) with no useful span. Mixing them forces meaningless columns on the impl side.
Decision. Split into refs (textual, from_file + from_span_start/end) and relations (symbol-to-symbol, from_qualified + to_qualified). CLI --kind impl is a routing alias to relations.
Consequences.
- “What implements X?” is a single
relationsindex lookup, fast enough to be a hot path. - The two tables are independently extensible (e.g. adding
relations.kind = "annotates"for TypeScript decorators) without inflating therefsshape. - Cost: two indexes to maintain instead of one. Schema is wider; the
refscommand needs to union two underlying queries. Acceptable for the correctness and ergonomics gain.
ADR-0188 — Sync policy is a property of the Graph handle
Section titled “ADR-0188 — Sync policy is a property of the Graph handle”Status: Proposed · 2026-05 · [ORB-00294]
Context. The original draft hardcoded “10ms stat budget at 5000 files; cache window 500ms” inside the query layer. The budget doesn’t scale, and the policy mixes product decisions into the library.
Decision. Graph::open(root, policy: SyncPolicy) where SyncPolicy is Manual | OnRead | Windowed { window: Duration }. CLI default: Manual. MCP server default: Windowed { window: 500ms }.
Consequences.
- Tests use
Manualfor determinism; long-lived processes useWindowed; one-shot scripts can useOnReadfor paranoia. - The library no longer carries an implicit perf contract that breaks silently at scale.
- Cost: callers must choose. No “just works” default beyond per-entry-point conventions. The conventions are documented but the choice is exposed.
ADR-0189 — Performance gate is against committed baseline, not last run
Section titled “ADR-0189 — Performance gate is against committed baseline, not last run”Status: Proposed · 2026-05 · [ORB-00294]
Context. A perf regression gate that compares “this run vs previous merged run” ratchets up to whatever the latest measurement happened to be — slow degradation goes undetected.
Decision. Baseline lives at bench/baselines.json, committed to the repo. Regression gate fires when a run is >20% slower than the committed baseline. Bumping the baseline requires a labeled PR and a one-line justification.
Consequences.
- Slow erosion is caught; cumulative drift requires an explicit acknowledgment.
- Performance wins are realized by intentional baseline bumps, not silent improvements that immediately become the new floor.
- Cost: baseline updates are friction. Every routine improvement requires a labeled PR. Acceptable — the friction is intentional and the alternative (no friction, no guarantee) is worse.
ADR-0190 — Use per-commit DB files for detached HEAD
Section titled “ADR-0190 — Use per-commit DB files for detached HEAD”Status: Accepted · 2026-05-25 · [ORB-00331]
Context. ORB-00326 (78e26efa) fixed detached-HEAD meta recording, but the filename still used HEAD.<version>.db, so detached checkouts on different commits churned the same cache. ORB-00331 compared keeping one HEAD DB with warnings against giving each detached commit its own DB file.
Decision. Detached HEAD uses detached-<short-sha>.<extractor_version>.db. Branch-attached checkouts keep the existing <branch>.<extractor_version>.db layout, and detached meta still records branch = "HEAD" plus the full commit SHA.
Consequences.
- Detached checkouts on different commits no longer invalidate each other through the same
HEADdatabase. - The stale-DB sweep removes detached DBs whose commits are no longer reachable from any local ref, while preserving the active DB family.
- Cost: more files during commit-hopping workflows. Bisects and cherry-picks can create O(N) detached DBs until reachability cleanup prunes the unreachable ones.
ADR-0192 — Roll back orbit-graph tool cutover to orbit-knowledge
Section titled “ADR-0192 — Roll back orbit-graph tool cutover to orbit-knowledge”Status: Superseded · 2026-05-25 · [ORB-00344] · Supersedes ADR-0191 · Amended by ADR-0197 (equivalence-harness retention reversed) · Superseded by ADR-0198 (ORB-00391 re-cutover + orbit-knowledge decommission)
Context. ORB-00338 cut the active graph query tools over from orbit-knowledge to orbit-graph, but audit data and post-cutover testing found unacceptable steady-state regressions: 13.5x p50 search slowdown, a roughly 9s cold-call floor, deleted high-use tools, incomplete plugin MCP exposure, byte-array show output, empty trace results for real enum-dispatch commands, and direction-confused impact output.
Decision. Restore the legacy orbit-knowledge-backed orbit.graph.search, show, refs, callers, pack, overview, implementors, and deps surface as the active backend. Keep the orbit-graph crate and equivalence harness in tree, but gate any future cutover on the rollback learnings captured in the global ADR.
Consequences.
- Future cutover work must use
SyncPolicy::Manualas the query-tool default unless a measured long-lived process explicitly opts into another policy. - Pre-cutover audit-log analysis, plugin MCP exposure equivalence, UTF-8 text response boundaries, trace/impact correctness gates, and cold-call latency measurements are required before another backend swap.
- Lost for now: cutover-only
callees,impact,trace, the changedsyncshape, and the extended graph-equiv corpus. - Cost: cutover pauses. The
orbit-graphbackend remains available for development, but agents lose the new cutover-only APIs until the root causes are fixed and a new cutover passes the gates.
ADR-0195 — Watcher-backed graph reads
Section titled “ADR-0195 — Watcher-backed graph reads”Status: Accepted · 2026-06-13 · [ORB-00377] · Supersedes ADR-0188
Context. ORB-00377 found that the MCP orbit.graph.* read path was effectively poll-on-read: the 500ms Windowed policy elapsed between most agent calls, so each query paid for a full worktree diff before running the SQLite lookup. Lengthening the window would reduce frequency but would keep query latency coupled to repository size.
Decision. Long-lived MCP graph handles use a watcher-backed policy: Graph::open performs one initial auto sync, starts a notify watcher scoped to the worktree, coalesces relevant filesystem events behind a debounce, and runs sync in the background. Query methods do not run inline sync for this policy; they read from a cached SQLite connection. The freshness contract is eventual: after a same-process file edit, graph reads may remain stale until the watcher observes and syncs the event, normally within the debounce plus sync duration; callers needing a hard read-after-write barrier must call Graph::sync/orbit.graph.sync before querying.
Consequences.
- Repeated graph reads with no intervening edits are pure SQLite lookups and do not initiate scanner walks.
- Watcher overflow or watcher errors request a coalesced auto sync, preserving the conservative fallback path.
Windowedremains available as an explicit fallback policy, but it is no longer the MCP default.- Cost: the MCP process now depends on platform filesystem watcher behavior and may serve stale graph data during the documented debounce-plus-sync window.
ADR-0197 — Remove the orbit-graph equivalence and benchmark harness
Section titled “ADR-0197 — Remove the orbit-graph equivalence and benchmark harness”Status: Accepted · 2026-06-13 · [ORB-00385] · Amends ADR-0192
Context. ADR-0192 kept the orbit-graph crate and the equivalence harness in tree to gate a future v2 cutover. The harness — the tools/graph-equiv workspace binary (plus its frozen corpus) and the bench/ baseline scripts — dual-ran both backends over a frozen corpus and failed CI (make ci-equiv, the graph-equiv CI job) on diffs outside documented tolerances. With the cutover paused indefinitely and none scheduled, that gate carried standing cost (a workspace member, a CI job, a Makefile target, a dependency-direction guardrail entry) for an inactive migration step.
Decision. Remove tools/graph-equiv/ and bench/ and unwire them from the build — the Cargo workspace member, the make ci-equiv target, the graph-equiv CI job, and the check-dependency-direction.sh allowlist entry. This amends ADR-0192’s “keep the equivalence harness in tree” consequence only; ADR-0192’s rollback decision and pre-cutover gates remain in force, and the orbit-graph crate is kept. The equivalence relation in GRAPH_SPEC.md still defines what a future cutover must satisfy; the harness is reintroduced fresh when a cutover is rescheduled rather than carried as an inactive scaffold.
Consequences.
- The workspace drops one crate and the
graph-equivCI job;make ci-equivno longer exists. - The documented v1↔v2 equivalence relation is now plan-only — no in-tree binary enforces it until a cutover is rescheduled.
- ADR-0192’s harness-retention consequence is amended here; its rollback decision is unchanged.
- Cost: a future cutover must rebuild the equivalence + benchmark harness (binary, frozen corpus, baselines, CI wiring) from scratch; the existing corpus and baseline history are lost.
ADR-0198 — Cut over to orbit-graph (v2) and decommission orbit-knowledge
Section titled “ADR-0198 — Cut over to orbit-graph (v2) and decommission orbit-knowledge”Status: Accepted · 2026-06-14 · [ORB-00391] · Supersedes ADR-0192
Context. ADR-0192 rolled back the ORB-00338 v2 cutover and restored orbit-knowledge (v1) as the active graph backend, gating any future cutover on a set of pre-cutover correctness/latency learnings. ORB-00386/00387/00389/00390 closed those gates against the watcher-backed orbit-graph (v2): UTF-8 show boundaries, trace/impact correctness, the overview/implementors/deps navigation queries (restoring v1 parity), and the refs fuzzy fallback. The automated equivalence/effectiveness harness that ADR-0192/§16 envisioned was removed by ADR-0197 and never rebuilt; with the gates closed, rebuilding it as a precondition carried no proportionate value.
Decision. Cut the agent graph surface over to orbit-graph (v2) and remove orbit-knowledge entirely. The v2 tools (sync, search, show, refs, callees, impact, trace, overview, implementors, deps) are served by the in-process GraphToolRegistry in orbit-mcp, which activates whenever the host exposes no orbit.graph.* schema. The v1 builtins, the orbit graph CLI command, the init-time graph build, and the v1 metrics pipeline are removed; the knowledge-stats computation moves to orbit_core::metrics. The measurement bar for this cutover is manual QA plus a v1-vs-v2 spot-check, accepted in place of the never-rebuilt automated harness. The pack tool is dropped rather than ported (ORB-00388 rejected); callers is subsumed by refs.
Consequences.
- orbit-graph is the sole graph surface;
orbit-knowledgeis deleted from the workspace (~24k LOC, plus the v1 builtins and theorbit graphCLI command). - Agents reach the graph only through
orbit-mcp; there is noorbit graphsubcommand. The standaloneorbit-graph-clibinary remains for direct CLI use. (Amended by ADR-0199:orbit graphis reintroduced as a thin wrapper over theorbit-graph-clilibrary for human/script use; the agent surface stays MCP-only.) - The dashboard knowledge-stats panel keeps working via
orbit_core::metrics::aggregate; pack-compression fields degrade to defaults (pack is gone), matching the pre-existing pack-less code path. - ADR-0192’s rollback decision is reversed; its
SyncPolicy::Manualdefault note is superseded by the watcher-backed policy (ADR-0195) for long-lived MCP handles. - Cost: the v1 content-addressed store, working-graph write surface, and graph-history attribution are gone; there is no automated equivalence gate guarding regressions against the (now-removed) v1 backend.
ADR-0199 — Reintroduce orbit graph as a thin wrapper over orbit-graph-cli
Section titled “ADR-0199 — Reintroduce orbit graph as a thin wrapper over orbit-graph-cli”Status: Accepted · 2026-06-16 · [ORB-00396] · Amends ADR-0198
Context. ADR-0198 cut the agent graph surface to orbit-graph (v2) and, in doing so, removed the orbit graph CLI command — agents reach the graph in-process over MCP, and direct CLI users were pointed at the standalone orbit-graph-cli binary. In practice that binary is not always on PATH (the agent shell documented in plugin/agents/orbit-code-reader.md notes “orbit-graph-cli is not on PATH in this environment”), leaving a shell user who holds only the orbit binary with no command-line path to the graph. Every other Orbit capability is reachable from the single orbit binary; the graph was the lone exception.
Decision. Reintroduce orbit graph as a thin wrapper over the orbit-graph-cli command layer. orbit-graph-cli is lib-ified (lib + bin): its Command subcommand enum and Command::run dispatch move into a library surface that both the standalone binary and orbit-cli consume, so there is exactly one command layer and no duplication. orbit-cli embeds that enum under an orbit graph parent and prints the same JSON the standalone binary emits, mapping the graph CLI error into OrbitError. The graph subcommands stay worktree-scoped (the DB is discovered from the current git worktree) and do not route through OrbitRuntime. This amends only ADR-0198’s “there is no orbit graph subcommand” consequence; the v2 cutover, the MCP adapter as the agent surface, and the removal of orbit-knowledge are unchanged.
Consequences.
orbit graph {sync, search, show, refs, callees, impact, trace, overview, implementors, deps, version, db-path, clean}is available from the singleorbitbinary; output matches the standaloneorbit-graph-cli(same library, same compact JSON).- New crate edge
orbit-cli → orbit-graph-cli(recorded inARCHITECTURE.md).orbit-graph-clinow publishes a minimal library surface (Command,Command::run,CliError); the per-subcommand arg structs are madepubto keep the public enum’s interface clean under-D warnings. - The agent-facing graph surface is unchanged: agents still use the in-process MCP adapter, not
orbit graph. The new subcommand is for humans/scripts holding theorbitbinary. - Cost: a second consumer of the orbit-graph-cli command layer means a subcommand change now ripples to two front ends’ help/output expectations. The duplication-free lib split confines the implementation to one edit site, but the orbit-cli parse tests and any
orbit graphdoc references must track the surface.
Task References
Section titled “Task References”- [ORB-00294] allocated the six initial orbit-graph ADR IDs (ADR-0184 through ADR-0189).
- [ORB-00331] allocated ADR-0190 and shipped the detached-HEAD per-commit DB layout.
- [ORB-00344] allocated ADR-0192 and restored
orbit-knowledgeas the primary graph tool backend. - [ORB-00377] allocated ADR-0195, superseded ADR-0188, and moved long-lived MCP graph reads to a watcher-backed sync policy.
- [ORB-00385] allocated ADR-0197 and removed the orbit-graph equivalence + benchmark harness, amending ADR-0192.
- [ORB-00391] allocated ADR-0198, cut the agent graph surface over to orbit-graph (v2), and decommissioned
orbit-knowledge. - [ORB-00396] allocated ADR-0199, lib-ified
orbit-graph-cli, and reintroducedorbit graphas a thin CLI wrapper over it.
Resolve any task above with orbit task show <ID> or git log --grep=<ID>.