Skip to content

System Architecture

Editors, agents, CLI clients, and library callers use the same analysis runtime. Transport stays at the outer edge; orchestration, language knowledge, semantic engines, and durable storage sit behind it.

Bifrost is layered from clients and protocol adapters through one runtime into query, policy, flow, and analysis engines backed by language front ends and a SQLite store.

The CLI, Rust library, Python client, MCP server, and LSP server are entry points. MCP is optimized for tool calls from agents; LSP is optimized for editor navigation. Shared analysis services decide whether a graph edge is proven and whether an empty result is complete.

The protocol-neutral runtime owns the shared code-intelligence operations and their serializable extension boundary. Protocol hosts map requests, cancellation, and errors onto that runtime. Canonical result data is produced below the transport boundary, so equivalent operations have the same semantics whether they arrive over JSON-RPC or MCP.

Rune Query Language (RQL) is the single authoring syntax for the canonical typed CodeQuery model. The RQL compiler produces a CodeQuery. Canonical JSON is generated from a decoded CodeQuery and remains its stable machine-facing serialization: MCP clients can supply fields inline, and query_file accepts saved .rql queries. Both paths reach the same typed logical and physical planner. Rust embedders can also construct the model directly. See Query Representations for the detailed trade-offs and execution surfaces.

Policies add versioned authoring, endpoint selection, finding construction, completion rules, and report formats. A structural endpoint match selects a source or sink. The dataflow executor establishes whether a path connects them.

The flow crate owns language-neutral value-flow, taint, typestate, type-flow, and reusable-summary clients. They reuse one bounded interprocedural call/return engine while retaining separate domains and result contracts. A taint class, typestate state, and inferred receiver class remain distinct types.

The analysis engine coordinates workspace snapshots, language analyzers, structural indexes, usage resolution, semantic lowering, CFG/ICFG construction, and semantic-model activation. The multi-analyzer routes each file to the appropriate language implementation while presenting shared analyzer capabilities to callers.

The core crate contains the dependency-free domain model: project files, languages, code units, structured names and identities, cancellation, compact IDs, canonical hashing, and cache-database foundations. Language crates depend on that model and implement language-specific knowledge such as imports, visibility, hierarchy, declaration extraction, call shapes, and usage resolution.

Language rules stay in language crates because Java overload applicability, Rust module routing, C++ macro-aware identity, and Ruby autoload visibility use different semantics. Typed interfaces let them produce common evidence shapes. The adapter and resolver boundary is defined in the front-end chapter.

The analyzer store is SQLite-backed. It persists content-addressed, queryable facts and projects them through workspace-generation views. It also stores the complete acyclic class-set dimension of reusable procedure summaries as normalized rows with exact dependency and read evidence. A serialized writer and bounded reader pool allow writes and snapshot reads to proceed concurrently. Other complete immutable derived values remain in memory within explicit byte budgets. The storage chapter’s analysis storage layers define the boundary between those durable, projected, and cached representations.

A typical request follows this sequence:

  1. The host binds the request to a workspace generation and cancellation token.
  2. The query layer validates the operation, limits, and requested capability.
  3. The analyzer selects durable facts and current source content by exact identity.
  4. If the request needs deeper semantics, language adapters materialize immutable per-file semantic artifacts and a bounded interprocedural view.
  5. A structural executor, usage resolver, or flow client computes evidence under the request budget.
  6. The runtime canonicalizes results together with execution status, proof, completeness, diagnostics, and provenance.
  7. The protocol adapter maps that result to its wire format and preserves its uncertainty and analysis boundaries.

The workspace generation associates each result with the exact source snapshot used to compute it. The revisioned workspace projections section describes the read-side isolation mechanics.

RepresentationTypical lifetimeWhy
Parsed declarations and structural rowsAcross processes and compatible worktreesThey are the reusable, queryable, content-addressed output of upfront workspace indexing.
File semantic artifactAcross requests while its full validity key matchesIt is immutable but more expensive and memory-heavy than structural rows.
Bounded ICFG snapshotOne solve or retained resultIts dense IDs and expanded contexts are run-local.
Complete procedure summaryAcross compatible solves within a bounded repositoryIts stable semantic identity can replace re-analysis, but only for inputs covered by its validity key.
Finding/reportOne exact policy, snapshot, and configuration identityPresentation must refer to the evidence actually solved.

Upfront indexing stops after publishing source-derived declarations and structural relations. File semantic artifacts, bounded ICFGs, and solver state are materialized on demand. Storage and Cache Strategy shows how those indexed facts are published and reused.

Dependencies flow upward through the crate structure:

  • core contains types that every language can share without importing the main analysis engine;
  • language crates contain their own semantic knowledge;
  • analysis composes core, languages, storage, and semantic lowering;
  • flow depends on the semantic-provider contracts from analysis;
  • RQL and policy build typed user-facing operations above analysis and flow;
  • runtime composes the protocol-neutral product surface; and
  • MCP and LSP remain hosts at the outer edge.

The layering keeps ownership explicit. A type belongs in core only if it needs no analyzer, store, grammar, or language module.

The shipped service model is local: analysis runs against a client-approved workspace root and reads local source. Unsaved or mid-refactor code remains available to the analyzer, and source custody stays within the local trust boundary. A remote multi-tenant service would require additional decisions about authentication, resource isolation, cancellation, cache tenancy, and version negotiation. That deployment model remains a research topic; the current architecture assumes a local service boundary.

Every subsystem is expected to preserve the same invariants:

  • exact identity is structured and source- or resolver-backed;
  • source coordinates support identity claims; durable identity requires the structured subject behind those coordinates;
  • partial positive evidence remains usable; absence claims require complete coverage;
  • cancellation and budget exhaustion are terminal properties of that request;
  • cache publication requires complete, current inputs;
  • run-local dense IDs never escape as durable identities.

The Evidence and Result Contract shows how these invariants appear at the client boundary.

The policy coordinator evaluates the requested policies against a workspace and, for --diff-base, a base revision. It applies the diff join, suppressions, scope, and reporting after evaluation. Reuse must produce the same canonical report and exit status as a fresh full evaluation. Execution timings, reuse measurements, and work counters describe the run and are excluded from that comparison; finding identities, ordering, evidence, diagnostics, completeness, and obligations are included.

Within one serial policy batch, assertion policies with identical subject queries can share a complete whole-workspace subject selection. Compatibility requires the full canonical query, including captures, branch order and execution controls, identical query limits, and the same immutable analyzer and pinned semantic-model context. This boundary always has an unrestricted AST selection. Each policy builds its own selected AST-id set and correlated row-family session afterward; those products are not shared across policies.

The batch retains only selections with multiple consumers and bounds their combined rows, evidence records and dependency keys by a retention weight. Incomplete, diagnostic-bearing, cancelled or unattributed selections are not reusable. Each consumer retains its original logical work charges and checks cancellation independently. Captured dependency reads are replayed into the active ledgers without transferring a producer’s ledger to another policy. The cache ends with the batch and does not change transactional diff-unit publication, policy ordering, or the canonical report.

A seed unit restricts a query’s initial file enumeration to one file. It does not restrict subsequent caller, import, dispatch, or other cross-file lookups. The recorded inputs of the execution determine whether the unit remains valid. The coordinator enumerates the current seeds on every run, so a newly matching file enters evaluation even when an earlier unit returned no rows.

Only exhaustive, diagnostic-free units with complete dependency evidence may replace full execution. The coordinator checks cumulative budgets after merging. A failed proof widens the query to full execution.

A full union visits branches in authored order and rows within each branch in that branch’s execution order. A repeated row key keeps its first value and appends later provenance traces, retaining the first sixteen traces and the truncation indication. These rules are observable in policy evidence and in relational assertions that choose a representative row.

Branch order cannot be exchanged with seed-file order. In particular, deduplicating all branches inside each file and then concatenating files can choose a different first writer. Sorting the final findings cannot repair that lost evidence.

Sliceable union trees therefore retain each leaf branch’s contribution in each file unit until the global merge. Each row carries its authored branch path in both its public and detailed provenance. The merge orders contributions by branch path, then seed-file order, then the leaf execution’s row order. Only then does it deduplicate keys. Item and detailed evidence move together.

The invariant is: for every row key, the merged value is the first value in the full branch-major execution, and its provenance is the same bounded prefix of that execution’s trace sequence, with the same truncation indication. Ordered union concatenation is associative, as is retaining a bounded prefix while propagating truncation. Flattening nested unions without intervening steps preserves this invariant, including when branches enumerate overlapping files or only one branch’s results change in an edited file.

The eligibility check requires union-only Set nodes with no pipeline suffix on those nodes, supported leaf plans, and a common seed-file comparator. A suffix after a Set would consume already-deduplicated rows and needs a separate proof; intersection and difference are not distributive over arbitrary seed files. These shapes remain Whole. Registration-dependent typestate, value-flow, taint, and witness steps, and decorator-binding projections, also remain Whole. Unions containing absent_member are also excluded: that operation merges caller-root evidence into the value itself, which cannot be reproduced by retaining a first value and appending provenance. Selector compilation that consumes live selected-site lists retains its existing whole-union path until it can use the same global composition.

Full union execution divides remaining scan and pipeline budgets among its branches and may retry a starved branch. Exhaustive file units alone do not prove that a full union would avoid starvation.

For a sliced union, cumulative scan and pipeline work must stay below the smallest branch share implied by the union tree. The bound uses the maximum product of sibling counts along a root-to-leaf path. This conservative test proves that the full execution cannot enter a fair-share retry. Semantic lanes are shared without this division and retain their ordinary cumulative checks. Reaching a bound requires full execution, preserving its completion and diagnostics rather than guessing which prefix it would return.