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.
Layer map
Section titled “Layer map”Client and protocol layer
Section titled “Client and protocol layer”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.
Query and policy layer
Section titled “Query and policy layer”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.
Flow layer
Section titled “Flow layer”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.
Analysis layer
Section titled “Analysis layer”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.
Core and language-knowledge layer
Section titled “Core and language-knowledge layer”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.
Storage layer
Section titled “Storage layer”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.
Request lifecycle
Section titled “Request lifecycle”A typical request follows this sequence:
- The host binds the request to a workspace generation and cancellation token.
- The query layer validates the operation, limits, and requested capability.
- The analyzer selects durable facts and current source content by exact identity.
- If the request needs deeper semantics, language adapters materialize immutable per-file semantic artifacts and a bounded interprocedural view.
- A structural executor, usage resolver, or flow client computes evidence under the request budget.
- The runtime canonicalizes results together with execution status, proof, completeness, diagnostics, and provenance.
- 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.
Representation lifetimes
Section titled “Representation lifetimes”| Representation | Typical lifetime | Why |
|---|---|---|
| Parsed declarations and structural rows | Across processes and compatible worktrees | They are the reusable, queryable, content-addressed output of upfront workspace indexing. |
| File semantic artifact | Across requests while its full validity key matches | It is immutable but more expensive and memory-heavy than structural rows. |
| Bounded ICFG snapshot | One solve or retained result | Its dense IDs and expanded contexts are run-local. |
| Complete procedure summary | Across compatible solves within a bounded repository | Its stable semantic identity can replace re-analysis, but only for inputs covered by its validity key. |
| Finding/report | One exact policy, snapshot, and configuration identity | Presentation 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.
Dependency boundaries
Section titled “Dependency boundaries”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.
Local-first service boundary
Section titled “Local-first service boundary”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.
Cross-cutting invariants
Section titled “Cross-cutting invariants”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.
Policy coordinator: diff-base unit reuse
Section titled “Policy coordinator: diff-base unit reuse”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.
Repeated assertion subjects
Section titled “Repeated assertion subjects”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.
Seed units and dependencies
Section titled “Seed units and dependencies”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.
Ordered union composition
Section titled “Ordered union composition”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.
Budgets are part of the proof
Section titled “Budgets are part of the proof”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.