Skip to content

RFC 106: Compiler-backed agent context graph

  • Status: In Progress
  • Created: 2026-05-26
  • Author(s): Danny Meijer (@dannymeijer)
  • Related:
    • RFC 015 (project lifecycle and CLI tooling)
    • RFC 031 (library system phase 1)
    • RFC 048 (checked contract metadata, Incan emit, and interrogation tooling)
    • RFC 077 (workspace and multi-package projects)
    • RFC 078 (tool execution and typed workflow actions)
    • RFC 080 (AI assets, models, prompts, evals, and agent metadata)
    • RFC 082 (checked API documentation generation)
    • RFC 096 (declaration metadata blocks)
    • RFC 105 (architect rule engine)
    • RFC 117 (loaf.toml and Oven's language-neutral project model)
    • RFC 119 (Oven-native Rust build facets and Cargo interoperation)
    • RFC 120 (canonical source symbol identity)
    • RFC 124 (Oven store unit identity and cross-plan artifact sharing)
  • Issue: #573
  • RFC PR: #766
  • Written against: v0.3
  • Shipped in: —

Summary

This RFC defines an Incan-owned agent context graph: a deterministic, compiler-backed graph of package, workspace, module, declaration, import, API, body-level, reference, diagnostic, process-risk, and architecture-advice facts that agents and developer tools can query directly instead of rediscovering source structure through repeated grep/read loops. The graph is not a runtime std.graph value and is not a dependency on any single external codegraph product; it is a stable Incan tooling contract with checked provenance, tolerant work-in-progress export, compact context packing, LSP integration, and MCP/CLI surfaces designed for agentic development.

Core model

  1. The compiler owns facts: Incan graph facts should come from the parser, module resolver, typechecker, RFC 048 checked API metadata, manifests, and package/workspace metadata rather than from a generic syntax scraper.
  2. Incan specialization is a feature: the graph optimizes for Incan source, stdlib, manifests, diagnostics, and checked metadata rather than cross-language breadth. Generic adapters may consume or export the graph, but the native model may include Incan-specific facts such as match dispatch, call sites, reference sites, declaration metadata blocks, and checked API contracts.
  3. Graph consumers are separate: storage engines, embeddings, MCP daemons, visualizers, hosted search, LSP features, architecture-advice tooling, and external CodeGraph-style tools consume the graph; they do not define Incan source semantics.
  4. Provenance is first-class: every fact must identify whether it is checked, unchecked, syntax-derived, manifest-derived, generated, external, derived-advisory, or runtime-observed so agents can reason about trust.
  5. Broken code remains navigable: the graph must support a tolerant source-graph mode that emits available facts from parseable modules even when semantic checking fails.
  6. Risk is evidence, not vibes: process signals such as churn, ownership concentration, co-change, coverage gaps, stale decisions, and recent volatility may influence agent context and architecture advice, but they must be deterministic records with cited evidence and visible limitations.
  7. Task context is a product surface: the primary agent workflow is not raw graph browsing; it is task-ranked, budgeted context packing that returns the most useful symbols, relationships, diagnostics, risk signals, architecture signals, and source anchors for a concrete change.
  8. LSP and CLI share one fact model: live editor features, command-line graph export, MCP context tools, and architecture-advice tools should reuse the same source-analysis substrate instead of building parallel extractors with different semantics.
  9. Identity is durable: nodes, edges, modules, files, and context packs should have stable content-addressed identities so stale data, cache keys, feedback expiry, graph diffs, and audit output are mechanically checkable.
  10. Compact output matters: graph output intended for agents must be token-aware and deterministic, with a compact textual format in addition to JSON/JSONL.
  11. Evaluation is part of the feature: Incan should define a small benchmark harness for agent-context quality so claims are measured on Incan packages and workspaces rather than inherited from generic tool marketing.

Motivation

Agents working in an Incan package currently reconstruct codebase shape by searching file names, grepping symbols, reading source, following imports by hand, and retrying when the first path misses. This wastes tokens and makes behavior sensitive to the model's exploration choices. It is also worst exactly when the project is large, modular, generated in part, or mid-refactor.

Incan already has information that generic code-indexing tools do not: parsed modules, checked declarations, resolved imports, package manifests, stdlib manifests, Rust interop manifests, RFC 048 checked API metadata, declaration metadata, diagnostics, and generated artifact metadata. Letting external tools rediscover that structure from .incn text would be slower and less trustworthy than exposing it directly.

Recent code-context tools show that the useful layer is moving beyond keyword search. Aider's repository map summarizes definitions and uses graph ranking to fit important symbols into a token budget. CodeGraph and similar MCP tools pre-index symbol relationships so agents can query structure rather than scan files. Codebase-memory and GitNexus expose persistent knowledge graphs through MCP with impact analysis, architecture summaries, and graph queries. Knowing adds content-addressed graph identity, task-ranked context packing, feedback expiry, and Merkle-style integrity claims. Sourcegraph's SCIP demonstrates the value of language-agnostic code-intelligence interchange for definitions and references. LSP and LSIF demonstrate the editor/code-navigation lineage. Repomix demonstrates the opposite baseline: full-repo packing is useful for portability but too blunt as the only agent context mechanism.

Those systems are useful prior art, but Incan should not copy them wholesale. Generic tree-sitter systems optimize for broad language coverage and approximate relationships. Incan can provide narrower but higher-trust facts for Incan source, including checked public API structure and source-compatible diagnostics. The best Incan solution should therefore combine compiler authority with agent-native retrieval and packaging.

The same graph also benefits non-agent developer workflows. The language server already has live source positions, diagnostics, and semantic navigation needs; architecture-advice tooling needs stable evidence for repeated dispatch, call patterns, references, and public API pressure. If those tools each reimplement source discovery, they will drift. A shared graph contract lets editor features, agent context, and architecture review use the same facts with different latency and presentation requirements.

Repowise adds a useful adjacent lesson for architecture tooling: structural source metrics are not the whole story. Its code-health layer combines AST-derived complexity, duplication, coverage, git ownership, churn, hidden coupling, and decision records, and its public benchmark reports statistically significant relationships between health scores and future bug-fix touches. The benchmark also reports that controlling for file size weakens the relationship, which is exactly the kind of caveat Incan should preserve. Incan should use deterministic risk signals as explainable evidence for prioritization, not as opaque bug prophecy.

Goals

  • Define a stable graph fact model for Incan packages and workspaces.
  • Expose compiler-backed source, module, declaration, import, public API, reference, call, diagnostic, manifest, and artifact facts where available.
  • Expose Incan-specific body facts such as match dispatch, call sites, reference sites, metadata annotations, and derived architecture signals where available.
  • Expose generated-behavior and resolution facts such as derive applications, decorator applications, vocabulary desugarings, trait adoptions, associated type bindings, type-directed overload selections, ownership and clone planning, and generated artifact relationships where available.
  • Expose optional process-risk facts such as churn, ownership, co-change, coverage, decision staleness, and trend signals when the required local inputs are available.
  • Include provenance, confidence, source span, package identity, module path, and graph-schema version on graph facts.
  • Support strict checked export and tolerant work-in-progress export.
  • Define CLI and MCP surfaces for raw graph exploration and task-ranked context packing.
  • Make the same graph facts available to LSP/editor features and architecture-advice tooling without requiring separate extraction logic.
  • Define a compact, deterministic agent context format alongside JSON and JSONL.
  • Define content-addressed identities for graph objects and graph snapshots.
  • Allow optional feedback and task-memory signals while requiring automatic staleness/expiry when the underlying graph changes.
  • Support stdlib, package, workspace, and generated-artifact exploration.
  • Keep external storage, embeddings, hosted services, visualization, and product-specific agent integrations outside the compiler boundary.
  • Define evaluation expectations for retrieval quality, token efficiency, freshness, determinism, and failure modes on Incan code.

Non-Goals

  • Adding a runtime std.graph data structure. Runtime graph collections are a separate concern from tooling graph facts.
  • Making Incan depend on CodeGraph, Knowing, GitNexus, codebase-memory, Sourcegraph, SurrealDB, SQLite, Neo4j, KuzuDB, or any single storage/query engine.
  • Requiring embeddings, vector search, hosted indexing, or remote inference.
  • Guaranteeing that an agent will make correct edits after receiving graph context.
  • Claiming defect prediction or risk prioritization without Incan-specific evaluation, visible controls, and caveats.
  • Standardizing every possible edge kind in the first release.
  • Replacing LSP. The agent context graph complements editor interactions rather than replacing live language-server diagnostics and completions.
  • Matching generic cross-language indexers on language count. Incan may support adapters, but the native value is language-specific precision.
  • Freezing future MCP tool names, storage backend choices, or service command names beyond the v0.4 incan inspect codegraph export.
  • Indexing arbitrary non-Incan languages. Mixed-language workspaces may expose foreign artifacts through future adapters. Rust is not one of those languages: Incan compiles through it, interoperates with it, and is written in it, so Rust facts are the same facts under a language attribute rather than a foreign-language adapter. See One graph across Incan and Rust.

Guide-level explanation

Exporting a graph

A developer can export graph facts for a source file, package, workspace, or directory:

incan inspect codegraph src --format jsonl

By default, export is checked. If the project contains semantic errors, the command fails with diagnostics rather than pretending the graph is fully trusted.

During active development, the same developer can request a tolerant graph:

incan inspect codegraph src --format jsonl --allow-errors

In tolerant mode, parseable modules still produce package, file, module, declaration, import, and source-span facts. Facts that require successful semantic checking are omitted or marked with lower provenance. Diagnostics become graph facts so an agent can see why the graph is partial.

The v0.4 implementation is this checked/tolerant JSONL export only. It does not include MCP serving, task-ranked context packing, process-risk scoring, architecture findings, or first-class Rust records yet; those are consumers or follow-up graph layers that build on the same schema contract.

Exploring graph structure

A human or agent can ask direct graph questions:

incan tools agent-graph summary
incan tools agent-graph find "Base32"
incan tools agent-graph module "encoding"
incan tools agent-graph neighbors "encoding::base32::decode"

These commands are useful for debugging the graph and for deterministic low-level exploration, but they are not the main agent path.

Asking for task context

The main workflow is task-ranked context:

incan tools agent-context pack --task "add strict padding validation to base32 decoding" --budget 8000 --format compact

The output should include a ranked, token-budgeted context pack: relevant declarations, modules, imports, call/reference neighborhoods, checked signatures, diagnostics, tests when known, and source anchors. The pack should be deterministic for the same graph snapshot, task string, budget, and ranking configuration.

An MCP client sees the same capability through tools such as:

context_for_task(task, budget, project_id)
context_for_files(files, budget, project_id)
explain_context(task, node_id, project_id)
graph_neighbors(node_id, project_id)
graph_summary(project_id)

Using graph-backed IDE and architecture tools

Editor and architecture-advice features can use the same graph facts without changing the export format. An LSP feature may ask for the current graph neighborhood around a cursor position to power richer hover, references, impact hints, stale-index warnings, or code actions. Architecture-advice tooling may consume body-level facts such as match dispatch and call sites, then emit architecture_finding records that cite the exact graph facts and source spans that caused them.

Those derived findings should not pretend to be compiler errors. They are advisory records with evidence links back to checked or unchecked graph facts. That distinction matters for agents: an agent can treat a checked signature differently from an architecture smell, and it can inspect the evidence trail before editing code.

Risk-aware tools can layer risk_signal records onto the same evidence model. A risk signal is prioritization evidence, not a recommendation by itself. For example, a finding may say that a dispatcher is broad, frequently edited, weakly covered, and co-changes with a module that has no import edge. That is more useful than a generic “complex file” label because the agent can see exactly which structural, git, and coverage facts contributed to the recommendation.

The architecture-advice split is therefore explicit:

  • architecture_finding: a specific advisory claim such as repeated dispatch, duplicated registry data, wrong-layer policy, public API drift, or hidden initialization behavior. It answers what design pressure is visible and cites graph evidence.
  • risk_signal: deterministic context such as churn, ownership spread, coverage gaps, co-change, decision staleness, or volatility. It answers where attention may be most valuable and exposes raw measures and caveats.

An architecture_finding may reference nearby risk_signal records to explain priority, but risk must not replace the finding. Conversely, a high-risk file with no architectural evidence should remain a risk observation rather than an architecture recommendation.

Using feedback

An agent may report that a symbol was useful for a task. That feedback can influence later ranking for similar tasks, but it must be tied to the graph snapshot or affected package root. If the module, package, or relevant graph neighborhood changes, stale feedback must stop applying automatically.

External tools

External tools may ingest the exported graph. For example, an external CodeGraph-style importer can map Incan JSONL into its own node and edge tables. That is an integration path, not the source of truth. Incan remains responsible for the stable wire schema and provenance semantics.

Reference-level explanation

Graph document

An Incan agent graph document must declare:

  • schema_version
  • package or workspace identity when available
  • languages represented by the export
  • source root and path normalization mode
  • graph generation mode: checked or allow-errors
  • toolchain version
  • graph snapshot identity when available
  • records containing nodes, edges, diagnostics, and metadata

The document must be deterministic for equivalent inputs under the same toolchain version, ignoring timestamps unless explicitly requested. Every source-backed graph fact record should carry an explicit language, provenance tier, source identity where applicable, and degraded-state flag.

Node kinds

The graph must support at least these node kinds:

  • package
  • workspace
  • file
  • module
  • namespace
  • declaration
  • api_member
  • import
  • external
  • diagnostic

The graph should support these node kinds as the compiler exposes enough information:

  • callable
  • method
  • field
  • enum_variant
  • trait_requirement
  • trait_adoption
  • associated_type_binding
  • derive_application
  • decorator_application
  • vocab_desugaring
  • extension_property
  • type_directed_mapping
  • ownership_plan
  • reference_site
  • call_site
  • match_dispatch
  • dispatch_pattern
  • test
  • artifact
  • generated_source
  • stdlib_item
  • architecture_finding
  • risk_signal
  • coverage_report
  • decision_record

Unknown node kinds must remain visible to consumers as opaque node records rather than causing a parse failure.

Namespaces and module subgraphs

A module path names where a declaration is. A namespace names where a consumer can reach it. The graph must distinguish them, because a consumer asking "did the surface change?" gets the wrong answer from a structure that mirrors the file tree.

A module whose name marks it internal is not a namespace of its own; it is a detail of the nearest enclosing namespace. A module path segment marks a module internal when it begins with a single underscore. A leading double underscore does not: __init__ and its relatives are structural names the compiler owns rather than authors' private modules. A module path's namespace is the path truncated at its first internal segment — a public module nested inside an internal one is no more reachable than its parent, so both belong to the same enclosing namespace.

Every module record must therefore carry:

  • namespace_path: the enclosing namespace's path segments, equal to module_path when the module is itself a namespace;
  • internal: whether the module is a detail of namespace_path rather than a namespace a consumer can reach.

A namespace node is the graph's top-level unit of reachable surface, and contains edges relate it to the modules beneath it. Grouping modules by namespace_path yields the same partition, so a consumer that only needs the grouping need not wait for namespace nodes to be emitted.

The point of the distinction is that moving a declaration between two internal modules of one namespace changes its module path and not its namespace. A consumer keying on namespace sees no change, which is correct: no consumer could observe one. A consumer keying on module path sees a change that is not there.

Flattening instead — dropping location from identity entirely — does not survive contact with a real standard library. Measured across 1,252 standard-library declarations, a flat root produces 47 collision classes over 167 declarations, and the worst are deliberate: compress and decompress across eight codec modules, encode and decode across five base-N modules, and the read capability across clock, env, and fs all carry identical signatures, because presenting one interface is the point. Nothing left in the identity separates them. Every one of those classes is cross-namespace, so scoping costs none of it; a collision within one namespace would be a duplicate definition, visible to the author where they wrote it.

Edge kinds

The graph must support at least these edge kinds:

  • contains
  • defines
  • imports
  • references
  • documents
  • diagnoses

The graph should support these edge kinds when available:

  • calls
  • implements
  • requires
  • exports
  • aliases
  • overrides
  • tests
  • explains
  • resolves_to
  • lowers_to
  • selects_overload
  • binds_associated_type
  • applies_derive
  • applies_decorator
  • desugars_to
  • borrows_as
  • clones_for
  • moves_to
  • generated_from
  • materializes
  • uses_stdlib_item
  • dispatches_on
  • matches_pattern
  • evidences
  • co_changes_with
  • owned_by
  • covered_by
  • governed_by

Unknown edge kinds must remain visible to consumers as opaque edge records rather than causing a parse failure.

Provenance tiers

Every node and edge must carry a provenance tier. The minimum tiers are:

  • compiler_checked: fact derived from successfully checked Incan source or checked metadata.
  • compiler_unchecked: fact derived from parseable source in tolerant mode where semantic checking failed or was intentionally skipped.
  • syntax_inferred: fact derived from syntax shape without full semantic confirmation.
  • manifest_declared: fact derived from manifests, library manifests, lockfiles, or workspace descriptors.
  • generated: fact derived from generated source or compiler materialization metadata.
  • external: fact names a target outside the current graph.
  • derived_advisory: fact derived from graph analysis rather than directly from the compiler, such as an architecture recommendation or impact finding.
  • process_observed: fact derived from local process artifacts such as git history, coverage reports, issue/commit metadata, or explicit decision records.

Future implementations may add tiers such as runtime_observed, lsp_resolved, or scip_imported, but they must not collapse into compiler_checked unless the Incan compiler itself validated the fact.

Identity

Identity and content digest are different fields and must not be conflated. Identity answers is this the same declaration; a digest answers has its meaning changed. One content-addressed field cannot answer both: if identity changes when content changes, a consumer cannot distinguish an edited declaration from a deletion plus an addition, and nothing can be keyed on it across edits.

A node identity must include enough logical information to distinguish package, module path, stable declaration anchor, node kind, and relevant source identity. An edge identity must include source node identity, target node identity, edge kind, and provenance tier. A diagnostic identity must include diagnostic code, affected source span, module path, and message-stable details — a diagnostic is anchored to an occurrence, so a span belongs in its identity and not in a declaration's.

A declaration identity must be stable across edits. It must not include a source span, a byte offset, or any value derived from traversal order. Three consequences follow, and none is hypothetical: each was found by measuring the current compiler against one standard library.

  • A span is not an identity input. RFC 120's CanonicalSymbolId carries declaration_span inside its equality. That is correct within its own scope — it states plainly that identity is stable across the stages of one compilation, not across edits — but it makes the value unusable as a graph identity. A graph declaration identity must be derived from it with the span removed.
  • Removing the span alone is not sufficient, because of overloads. Two module-level declarations sharing namespace, origin, name, and kind become indistinguishable once the span is gone. A declaration identity must therefore include the declaration's signature. Measured over the declarations one standard library actually declares — 1,252 of them, since imports, aliases, and re-exports share their target's identity by design and so cannot collide — overloads are the only collision class that arises, and it arises twice. A corpus is what establishes this: the class was found by measuring, not by predicting it.
  • A traversal counter is a span by another name. A scope discriminant assigned as an index into a module-wide table is positional: inserting or moving any declaration renumbers every declaration traversed after it, so an untouched sibling appears to change. Only the presence of a discriminant may enter an identity, never its value. Where a value is needed to separate sibling scopes, it must be renumbered densely within its own declaration.

Two consequences follow from that, and both are visible to a consumer.

Changing a signature replaces an identity rather than moving a digest. Because the signature is an identity input, an edit to it produces a declaration that did not exist before and removes one that did. For an invalidation consumer that is exactly right — both are "changed" — but a consumer tracing one declaration across versions sees it disappear, and must treat a removal paired with an addition at the same origin, name and kind as a possible signature change rather than as a deletion. This is the price of separating overloads, and it is the right trade: overloads are the only collision class a corpus produces, while a signature edit is at least a contract change.

Two declarations sharing a digest is not a collision. Identity is the key and the digest is the value: a consumer looks a declaration up by identity, then compares digests. A shared digest says only that two declarations mean the same thing, which happens legitimately — the same constant declared in several modules, for instance. A digest must therefore not be treated as an identity, and an implementation must not report shared digests as a defect. (What they are useful for is duplicate detection, which is a consumer of the graph rather than a property of it.)

Physical file movement must not destroy identity for declarations that retain the same package, module, and stable anchor. The layering rule below is what makes that achievable rather than aspirational.

Checked and tolerant export

Strict export must fail when the graph would otherwise imply checked semantic facts from an invalid package. Tolerant export may continue after parseable module errors, but it must mark unchecked facts clearly and must not emit checked API members, checked signatures, resolved call edges, or resolved reference edges for modules that failed the required semantic phase.

Diagnostics produced during tolerant export should be emitted as graph records so tools can explain why the graph is partial.

Generated behavior and resolution facts

The graph should represent user-visible compiler-selected behavior as graph facts when the owning compiler or tooling subsystem can expose them. These records are especially important for agents because they replace guesswork around hidden behavior with source-anchored facts.

Generated behavior records should cover at least these families over time:

  • derive applications and the generated protocol behavior they provide;
  • decorator applications, preserved callable identity, and decorator-produced metadata;
  • vocabulary block desugarings and import-scoped extension-property resolution;
  • trait adoption records, generic adoption-family records, and associated type bindings;
  • type-token overload selection and type-directed return mapping;
  • ownership, borrow, move, clone, and materialization plans at checked value-use boundaries where the compiler exposes that decision;
  • generated Rust or future backend artifacts linked back to source declarations, metadata, and diagnostics.

Each generated behavior record must carry provenance and source anchors. It should distinguish source-authored declarations from compiler-provided behavior, package-provided vocabulary descriptors, trait-owned capability families, and backend-generated artifacts. A behavior record may be partial or unsupported in early exports, but it must not masquerade as compiler_checked unless the compiler actually validated the relationship.

Explanation-oriented edges such as explains, resolves_to, lowers_to, selects_overload, binds_associated_type, desugars_to, borrows_as, clones_for, and moves_to let a context pack answer why a call, match, derive, trait method, property read, or generated artifact behaves the way it does. These edges are not required for every source construct in the first release, but the schema should reserve the concept so later consumers do not invent parallel explanation formats.

LSP and architecture consumers

The graph schema must be suitable for live editor and advisory consumers. Nodes and edges that point into source must carry enough range data for an LSP client to map a graph fact back to a document location. A graph consumer may operate against an in-memory live snapshot, a persisted snapshot, or an exported JSONL document, but fact meaning and provenance must be the same across those modes.

LSP features should consume graph facts through shared analysis services rather than defining a separate graph extractor. The language server may use live incremental state to avoid full export on every edit, but any graph records it exposes to external tools must still include schema version, provenance, source identity, and staleness information.

Architecture-advice tools may create architecture_finding records from graph facts. An architecture_finding should cite evidence nodes or edges with evidences relationships and should preserve the provenance of the underlying facts. For example, a repeated match-dispatch finding can cite the match-dispatch nodes, dispatch patterns, call sites, and source spans that caused the finding. If the underlying body facts are unchecked because the package is in tolerant mode, the derived finding must also be treated as unchecked advisory output.

Process-risk facts should be represented as risk_signal records. They may be attached to files, modules, declarations, tests, or architecture findings. They must identify their input source, such as git history, coverage reports, decision records, lockfiles, or local tool output. They should expose the raw contributing measures when practical rather than only a composite score. Composite scores may be useful for sorting, but they must not hide the evidence or normalization rules that produced them.

architecture_finding and risk_signal records serve different jobs. The former carries an advisory claim that can be accepted, rejected, or fixed; the latter carries deterministic evidence that can rank context, explain priority, or sharpen a finding. A consumer must be able to filter and inspect them independently.

Context packing

Task context packing must use the graph as its primary retrieval surface. A conforming implementation should:

  • extract exact identifiers and natural-language keywords from the task
  • seed candidate nodes from name, module, doc, diagnostic, and metadata matches
  • expand candidates through graph relationships with edge-aware weights
  • rank nodes using graph proximity, provenance, public API relevance, diagnostics, architecture signals, process-risk signals, recency when available, and optional feedback
  • pack results into a token budget using deterministic ordering and stable tie-breakers
  • include enough source anchors for an agent to open or inspect original files
  • explain why a node was included when requested

Embedding-based re-ranking may be offered, but it must be optional and local/remote execution must be policy-visible under RFC 080 when models are involved.

Investigation consumers

RFC 126 proposes an Oven-owned investigation service that connects compiler context to native execution and retained captures. Its observations must reference the exact source snapshot and executable through verified mappings; they must not be promoted into timeless checked source facts. Durable declaration identity, compilation-specific location, executable identity, and capture/stop identity remain distinct. Missing mappings remain unavailable rather than being reconstructed from matching names or lines.

The graph service remains the semantic-context producer. Read-only task-context operations do not implicitly grant launch, resume, expression-evaluation, rerun, or mutation authority. Runtime evidence may be linked through its owning record contract without merging the graph and investigation lifecycles. The public mapping and provenance contract must preserve these identity and authority boundaries when compiler context is consumed by RFC 126 investigations.

Formats

Graph export must support JSONL for streaming ingestion and should support pretty JSON for debugging. Agent context packing should support a compact text format designed for LLM consumption. The compact format should avoid repeated fully qualified names, preserve local IDs, expose edge direction, include provenance, and be deterministic.

The compact format is not required to be stable in the same way as the graph JSONL schema until explicitly versioned.

MCP surface

The MCP surface should expose task-level tools before raw graph tools. Raw graph tools are still required for debugging, but agents should be able to make one high-level call for task context instead of a sequence of low-level searches.

MCP resources should expose read-only orientation such as graph summary, schema, indexed packages, stale status, and current snapshot. MCP tools that mutate index state, record feedback, or trigger reindexing must be explicit and policy-visible.

Evaluation

The feature should include an Incan-specific evaluation harness before being considered complete. The harness should measure at least:

  • task-context precision at a fixed budget
  • recall of known relevant declarations
  • token cost compared with file-by-file exploration
  • determinism across repeated runs
  • freshness after adding or editing source
  • behavior on broken work-in-progress packages
  • query latency on stdlib-sized and workspace-sized projects
  • risk-signal usefulness for architecture review tasks, including at least one control for file or module size when making predictive claims

Evaluation tasks should be hand-labeled where possible and should not derive ground truth from the graph output being evaluated.

Design details

Prior art and lessons

Aider's repository map demonstrates that a compact cross-repository summary can help agents work without reading every file. Its public docs describe a map containing key classes/functions and signatures, then shrinking that map to fit a token budget through graph ranking over file dependencies. The lesson for Incan is that token-budgeted context should be a first-class output, but file-level PageRank is too coarse for compiler-backed Incan declarations.

CodeGraph demonstrates the practical value of a local pre-indexed code graph for reducing agent exploration calls. Its README frames the problem as agents scanning with grep/glob/read and proposes instant graph queries over symbol relationships, call graphs, and structure. The lesson for Incan is that local agent integration matters, but Incan should provide authoritative facts rather than requiring an external parser to guess .incn semantics.

Knowing demonstrates the strongest architectural pattern in this space: content-addressed graph identity, task-ranked context, compact wire formats, feedback expiry, and graph snapshots. Its docs describe a context pipeline that seeds from task text, performs graph-aware expansion such as random walk with restart, ranks results, packs into a budget, and emits compact formats. The lesson for Incan is that the graph export alone is not enough; task-context packing is the agent-facing product.

Codebase-memory demonstrates a different local-first product shape: a persistent knowledge graph served through MCP, broad tree-sitter language coverage, structural queries, impact analysis, architecture summaries, file watchers, agent installer integration, and optional graph visualization. The lesson for Incan is to keep operation local and ergonomic while avoiding the false precision of generic grammar coverage where compiler facts exist.

GitNexus demonstrates deep MCP/editor workflow integration: graph query tools, context/impact tools, generated agent guidance, and hooks that remind agents to use the graph or detect stale indexes. The lesson for Incan is that MCP alone is not enough; agent guidance and stale-index signals shape behavior.

Gortex demonstrates the appeal and risk of broad multi-language graph indexing. Its public site emphasizes a single static binary, in-memory graph, language-server integration, many language extractors, communities, and precomputed blast-radius indexes. The lesson for Incan is to learn from graph algorithms and operational UX, but not to dilute the Incan RFC into a generic polyglot indexer.

Repowise demonstrates a broader codebase-intelligence product shape: graph intelligence, git intelligence, generated documentation, decision records, code-health scoring, MCP tools, and automatic sync. Its code-health docs describe deterministic biomarkers over AST, git, duplication, coverage, ownership, and trends; its public benchmark reports a time-window experiment over FastAPI, Django, and Pydantic with significant health/defect correlations while also documenting limitations such as file-size confounds and commit-message defect-label heuristics. The lesson for Incan is that architecture advice should combine structural compiler facts with process facts, but any predictive or prioritization claim must expose its evidence and controls.

Sourcegraph SCIP and LSIF demonstrate durable code-intelligence interchange for definitions, references, and implementations. SCIP is language-agnostic and supports code navigation; LSIF was designed to dump language-server knowledge for rich navigation without a local source checkout. The lesson for Incan is that a stable interchange schema can be more valuable than one database, but Incan's schema must also carry agent-specific provenance, diagnostics, and context-packing semantics.

LSP demonstrates the live editor model: diagnostics, go-to-definition, references, hover, and code actions. The lesson for Incan is that live interactivity and durable graph indexing are complementary. The agent context graph should not make every agent simulate an editor session.

Repomix and related repo-pack tools demonstrate portability and simplicity: pack the repository into an AI-friendly file. The lesson for Incan is that full-project packing can be an escape hatch, but graph-ranked packs should be the default because they preserve token budget and structural relevance.

Incan-specific differentiators

Incan can distinguish checked facts from approximate facts. That is the core differentiator. A generic indexer can detect that text looks like a declaration or call; the Incan compiler can know whether a declaration is public, what its checked signature is, what metadata it carries, whether an alias target resolved, whether a stdlib import exists, and which diagnostic invalidated a module.

Incan can also expose body-level facts that are meaningful to the language rather than merely textual. Match dispatch, call sites, reference sites, pattern families, metadata blocks, checked public API members, and stdlib/import relationships are all examples of facts that can be represented directly instead of inferred by a generic code-search system. These facts are especially useful for architecture-advice tooling because they let a rule cite structured evidence rather than a bag of matching lines.

Incan can expose generated behavior without treating generated output as the source of truth. Derives, decorators, vocabulary desugarings, trait resolution, type-directed overload selection, and ownership planning all affect what code means, but users should be able to inspect those decisions through compiler-backed graph facts rather than by reverse-engineering generated Rust.

Incan can also export graph facts for generated or materialized structures. RFC 048 metadata and future artifact metadata allow packages to expose public contracts even when source is generated, embedded, or inspected from a built artifact.

Finally, Incan can make graph context part of the project lifecycle. Workspace roots, lockfiles, manifests, stdlib sources, package artifacts, and future incan.pub metadata can all contribute to graph identity and staleness detection.

The language server is another differentiator. A generic graph system usually indexes a repository out of band and then tries to stay fresh. Incan can share one source-analysis substrate between CLI export, MCP context, architecture advice, and live editor features. That does not make LSP the graph protocol; it means the language server can become a low-latency consumer and producer of the same schema where appropriate.

Incan can also tie process signals to language facts more precisely than a file-only health score. A generic tool can say a file is high churn; Incan can say that a specific public API, dispatch family, declaration metadata block, generated artifact, or package boundary is high churn, poorly covered, or governed by a stale decision. That gives architecture advice better targets and gives agents a more defensible reason to inspect a region before editing it.

Graph boundaries

The compiler-facing graph schema should remain narrow enough to be stable. Higher-level storage may add community detection, embeddings, visualization coordinates, feedback scores, or query caches as metadata, but those must not change the meaning of core compiler facts.

Derived advisory records occupy a middle ground. They are not compiler facts and must not be required for baseline graph export, but the schema should allow them because they are useful for agents and architecture tools. The key rule is evidence: derived records must point back to source graph facts so a consumer can audit why the recommendation exists.

Process-risk records follow the same boundary. Git history, coverage reports, and explicit decision records are local project facts, but they are not compiler validation. They should be optional, provenance-tagged, and separable from checked source facts. A missing coverage report should produce missing coverage facts, not a misleading zero-coverage claim.

Security and policy

Graph export reads source code and may expose private structure to agents. Local export is the default. Remote indexing, remote embedding, hosted storage, or sharing graph artifacts must be explicit and policy-visible. MCP tools that can record feedback or mutate index state should be separated from read-only resources.

One graph across Incan and Rust

Oven already treats the two languages as one substrate. RFC 117 defines a project model whose package, target, lock, cache, and receipt model is language-neutral, and names Incan and Rust as its two built-in authored source facets; RFC 119 gives Rust an Oven-owned crate graph and direct-rustc plan on those same terms. A unit that Oven bakes has an identity, a content digest, dependencies, and a compilation that produces it, and which surface syntax it was authored in is a property of that unit rather than a different kind of unit.

The graph should describe the same world. To Oven a declaration is a declaration; Incan is Rust and Rust is Incan at this layer, and a graph that partitions by language would be modelling a boundary the build system does not have.

This is not an aspiration; it is what the schema already asserts. CodegraphDeclarationRecord and CodegraphFileRecord each carry a language field. Language is an attribute of a fact, and every relationship — containment, import, export, reference, call — is defined over facts rather than over languages.

Consequence: rust_item and uses_rust_item are retired

Those two reserved kinds are withdrawn. They encode the distinction the language attribute exists to dissolve, and adding them would leave the graph with two ways to say the same thing and no rule for which to believe.

A Rust declaration is a Declaration with language: rust. A reference to it is a Reference. A call into it is a Call. An Incan function calling a Rust one and an Incan function calling an Incan one are the same edge, and a consumer that wants only one of them filters on the endpoints' language rather than reaching for a different edge kind.

The practical test: a query that answers "what does this declaration depend on" must not need to know, in advance, which language the answer is in.

What one graph makes answerable

These follow from the unification rather than motivating it.

  • Whether a change to a compiler declaration can reach a standard-library component's compilation — the same reachability question as any other, with endpoints that happen to be Rust.
  • Whether an Incan declaration reaches the Rust declaration it names, which is a broken edge rather than an emission mystery when it fails.
  • Whether a module respects an architectural boundary, expressed as reachability between rings rather than as a text search for import statements.
  • Whether a workaround is still needed, because the defect it works around is a node with a state.

Reachability is a lower bound

One property does not survive the unification unqualified. Incan declarations resolve exactly, because the compiler checked them. Rust declarations do not always: calls through function pointers, trait objects with no statically known implementor, and macro-generated code can leave an edge the graph cannot resolve.

The graph therefore reports reachability as a lower bound on what is reachable, never an upper bound on what is not. Three rules follow, and they bind consumers rather than producers:

  • Reachable means affected.
  • Unresolved means affected. An edge the graph could not resolve is not an edge that does not exist.
  • Downstream of affected means affected.

A consumer that skips work on the strength of a reachability answer must err toward doing the work. Missing an affected node yields a wrong build; a spurious one costs time.

Building it

The compiler already sees both languages. rust_inspect loads a Cargo project through rust-analyzer — ra_ap_hir, ra_ap_ide_db, ra_ap_syntax and ra_ap_project_model are workspace dependencies — and extracts typed Rust metadata that today serves interop checking. The Rust half of this graph is mostly a question of emitting what that already resolves, in the record shapes the schema already defines.

That single-observer position is the substantive advantage over the alternative. Polyglot indexers merge independently-derived per-language indexes and must reconcile their symbol identities afterwards. One compiler observing both languages has nothing to reconcile: the identity an Incan declaration uses to name a Rust one is the identity the Rust side was recorded under.

SCIP remains the reference for symbol naming and for separating definitions from reference occurrences, and remains an export adapter rather than the internal model, consistent with native JSONL being the source of truth. Following its conventions where they fit keeps a future exporter a projection rather than a translation.

Semantic digests

Identity says which declaration a fact is about. A semantic digest says whether that declaration's meaning has changed. Together they let a consumer answer what changed rather than what was touched, which is the difference between a build system that reuses work and one that rebuilds on any edit.

RFC 124 is the first such consumer: it names the semantic digest as the source input to a compiled unit's identity, in place of a hash of the unit's source bytes. It deliberately defines no digest of its own, so this section is the normative source for both.

Logical and physical layering

Every graph fact belongs to one of two layers, and only one of them carries identity.

Layer Facts Carries identity Enters a digest
Logical module, declaration, signature, visibility, reference, call yes yes
Physical file, byte span, line, on-disk path no — provenance only no

This is a rule about anchoring, not a second graph. The schema already separates the layers — CodegraphFileRecord and CodegraphModuleRecord are distinct node kinds — and a view over the graph is a projection, never a parallel structure. Splitting into multiple graphs would reintroduce the identity-reconciliation problem that one compiler observing every language exists to avoid.

The rule has a consequence the current compiler does not satisfy: a logical module identity must not be derived from a file path. Deriving it that way is ordinary language design, and it is how a reader finds a declaration, but it makes a pure refactor — splitting one module into two files with an unchanged public surface — look like the deletion of every declaration in it. Where a logical module identity cannot yet be established independently of layout, an implementation must report the affected declarations as changed rather than silently reuse them.

The semantic digest

A declaration's semantic digest must be a digest over its checked meaning: the declaration as the compiler resolved it, after type checking, before code generation.

It must be computed from checked values, not by normalising rendered text. Rendered forms embed positional data inside composite strings — an identity spelling that carries its own declaration span, for example — and scrubbing those after the fact reliably misses cases that omitting the field cannot. A digest built by text substitution over a debug rendering is not conformant even where it happens to produce the same answer.

The digest must exclude everything the compiler cannot observe in its output: comments, documentation, formatting, declaration order, and physical location. It must include everything it can: signatures, types, visibility, control flow, ownership and borrowing facts, and resolved call and reference targets.

The digest must be language-neutral. An Incan declaration and a Rust declaration contribute the same kind of fact and are compared the same way; a consumer asking whether a declaration changed must not need to know which language it is written in. A digest that covers only one language is not partial but unsound, because a consumer folding it will report a hit for a change it cannot see.

Where an implementation cannot compute a declaration's semantic digest, it must report the declaration as changed. Erring toward changed costs redundant work; erring the other way yields a wrong build.

The exported identity does not satisfy this yet

The identity rules above govern what a consumer may key on. The exported schema does not meet them today: the identity carried on declaration, reference, call, import-binding and export records mirrors the compiler's own, span and scope discriminant included. That is correct as provenance — it names where a declaration sat in the compilation that produced the record — and wrong as a key.

Closing the gap needs a second, span-free identity exported alongside it rather than a change to the existing one, since both jobs are real. It also needs a signature, which a compiler identity does not carry and which comes from the declaration's lowered body; an exported identity without one cannot separate overloads, and publishing it would push the collision this section exists to prevent out to every consumer at once.

Until that lands, a consumer must treat the exported identity as provenance and must not cache against it across compilations.

The documentation digest

Documentation is output for some consumers and invisible to others. A declaration must therefore carry a separate doc_digest covering its documentation, excluded from the semantic digest.

A consumer gating a compiled artifact uses the semantic digest alone, because documentation cannot change compiled output. A consumer gating published reference documentation or a manifest surface uses both. One digest per consumer, rather than one digest with a policy attached.

Composition

A declaration's own digest answers a narrow question. Consumers ask a wider one: has anything this declaration depends on changed. A closure digest must therefore be defined as a fold over the graph's own dependency edges:

closure_digest(n) is a digest over semantic_digest(n) followed by the closure digests of n's dependencies, ordered by dependency identity.

Ordering by identity rather than by traversal makes the fold order-free, which is required: a fold sensitive to traversal order reintroduces exactly the positional dependency the identity rules remove. A cycle folds over its strongly connected component as a unit.

Dependencies are the graph's outgoing references, calls, imports, contains, and resolves_to edges. Those edges are the graph's own and must come from it: an implementation must not derive them from a lowered body, which records the callee spelling a call site used and defers resolving which declaration it binds. Resolution is what makes an edge foldable, and it is the graph that holds it. Unresolved edges are covered by the reachability rules above: an edge the graph could not resolve is not an edge that does not exist, so a closure containing one must be reported as changed.

External identity and the resilience boundary

A change confined to a declaration's internals must not force its dependents to rebuild. Expressing that requires a second closure, and the boundary is not the pub keyword.

A consumer does not merely link against public signatures; it instantiates parts of what it depends on. Generic bodies are monomorphised in the consumer, inlinable bodies are code-generated there, and compile-time-evaluated bodies are evaluated there. A private declaration reachable from any of those is externally observable although no signature names it.

The external closure must therefore be rooted at public declarations and extended by reachability: public signatures, plus the bodies of declarations a consumer can instantiate or inline, plus everything reachable from those bodies whatever its visibility. Visibility marks the roots; reachability decides membership. A private declaration a public generic calls is an instance of the rule, not an exception to it.

An implementation may follow every edge out of every public declaration instead. That is sound and looser: because most private declarations are reachable from some public one, it places nearly everything in the external closure and the distinction stops paying. How far the closure follows is a tightness decision, not a correctness one.

Invariants

An implementation must satisfy all of the following. Each is stated at declaration granularity deliberately: an implementation that leaks positional data still moves the digest of the declaration actually edited, so an assertion at module or package granularity passes it.

Change Edited declaration Every other declaration
add or remove a comment unchanged unchanged
add or remove documentation unchanged unchanged
reformat without changing code unchanged unchanged
reorder declarations within a module unchanged unchanged
move a declaration between files, module unchanged unchanged unchanged
add a declaration — unchanged
remove a declaration — unchanged
change a declaration's body changed unchanged
change a public signature changed unchanged

Two conditions are load-bearing when testing these, and an implementation that omits them is untested rather than passing. A fixture must retain a declaration positioned after the edit, because positional leakage only contaminates declarations traversed later. That trailing declaration must own a nested scope, because a declaration with no scope discriminant cannot detect traversal-order renumbering. A body-change fixture must additionally introduce or remove a scope, since an edit that leaves the scope count unchanged shifts no later index and cannot detect the leak at all.

Separately, a distinct declaration identity must exist for every declaration a compilation admits, including overloads. Collisions must be measured over a corpus rather than reasoned about.

Schema versioning

The digest projection is a compatibility surface. Every exported digest must carry a digest_schema identifying the projection that produced it.

A change to the projection must not invalidate every stored digest. An implementation must retain the previous projection and, on a mismatch under the current one, compare under the previous; a match there is a hit, reported with a notice that the digest is stale and should be refreshed. Without this, a normalisation improvement is indistinguishable from a change to every declaration in existence.

Prior art

insta, already a development dependency, solves the same problems for snapshots and its internals are the reference for three of these rules.

Its MetaData::trim_for_persistence strips the assertion line before writing a snapshot, retaining it only for display — volatile positional data is kept for humans and never persisted. That is the layering rule: keep spans in what a maintainer reads, keep them out of what is compared.

Its redactions apply to a structured value tree and only then serialise, rather than rewriting rendered output. That is the checked-values rule, and the reason for it: normalising text after rendering misses positional data embedded inside composite strings.

Its snapshot comparison keeps the previous normalisation alongside the current one and accepts a match under either, warning rather than failing when only the older rule matches. That is the schema-versioning rule.

What does not transfer is the library itself. Digests are computed and compared in memory as cache keys, not written as reviewable files. The one artifact worth keeping is a committed golden of normalised digests, which turns a change to the projection into a visible diff in review rather than a silently cold cache.

Alternatives considered

Depend directly on CodeGraph

Rejected. CodeGraph-style tools are useful consumers, but making one external tool the Incan source of truth would give away the compiler's semantic advantage and tie the language roadmap to another project's schema and release cycle.

Use Knowing directly as the Incan graph system

Rejected as the default, but useful as an integration experiment. Knowing has strong architecture, but it does not currently understand Incan source. The right path is to learn from its content-addressed graph and context-packing model while keeping Incan facts compiler-owned. A bridge importer may still be valuable.

Emit only SCIP

Rejected as the only format. SCIP is good prior art for code navigation interchange, but the Incan agent graph needs package/workspace facts, diagnostics, checked API metadata, tolerant provenance, compact context packs, and task-context semantics that go beyond ordinary go-to-definition/reference indexing. A future SCIP exporter may still be useful.

Use only LSP

Rejected. LSP is optimized for live editor interactions, not durable agent context, snapshot identity, task-context packing, feedback expiry, or offline graph interchange.

Adopt Repowise-style health scoring directly

Rejected as the default. Repowise is strong prior art for deterministic risk signals, but its current implementation is broad, tree-sitter-centered, and file-score oriented. Incan should borrow the evidence model, benchmark discipline, and process metrics while tying signals to compiler-backed declarations, dispatches, packages, and architecture findings. Any Incan health or risk scoring must be validated on Incan projects before being described as predictive.

Build a generic tree-sitter Incan extractor

Rejected as the primary approach. A tree-sitter extractor may be useful for editor tooling or external tools, but it cannot replace compiler-backed graph facts without duplicating semantic logic and producing lower-trust results.

Start with embeddings-first retrieval

Rejected. Embeddings may improve ranking within a candidate set, but the graph should first use compiler structure, names, docs, diagnostics, and metadata. Embeddings should be optional and policy-visible.

Pack the whole repository

Rejected as the default. Full-repository packing is easy to understand but scales poorly and provides no structural ranking. It can remain a fallback export mode for small packages or archival review.

Drawbacks

This feature adds another public machine-readable contract to maintain. Once external tools depend on the graph schema, schema evolution must be careful.

Compiler-backed facts are only as complete as the compiler exposes. Early versions may have excellent module/declaration/import/API facts but limited call/reference edges.

Tolerant export is easy to misuse. Agents may over-trust unchecked facts unless provenance is visible and compact formats preserve it.

Task-ranking quality is hard to prove. The RFC therefore requires an Incan-specific evaluation harness, but even that will not guarantee performance on every future package.

Risk scoring can be misleading if presented without caveats. Churn, ownership, coverage, and size are correlated with each other; composite scores must remain explainable and should report controls when used for predictive claims.

Content-addressed identity can become brittle if fields are chosen poorly. The identity model must balance stability across harmless movement with accurate invalidation when semantics change.

MCP integration increases the security surface. Read-only graph tools are low risk, but indexing, feedback, hooks, and remote model re-ranking require explicit policy controls.

Implementation architecture

This section is non-normative.

The recommended shape is four layers. The first layer is a small schema crate that owns graph record types, versioning, serialization, provenance vocabulary, and compact-format helpers. The second layer is the compiler/tooling exporter that converts parser, resolver, typechecker, manifest, stdlib, artifact, and diagnostic facts into schema records. The third layer is a derived-analysis layer for architecture findings, process-risk findings, impact hints, and other advisory records that cite graph evidence. The fourth layer is an optional agent-context service that stores records, computes graph neighborhoods, ranks context, serves MCP, records feedback, and integrates with external stores or importers.

The compiler layer should remain storage-agnostic. It may emit JSONL to stdout or write a graph artifact, but it should not require a database server. The agent-context layer may use SQLite, SurrealDB, an embedded graph engine, or another storage backend as an implementation detail.

The LSP should share the same source-analysis services with the exporter where possible. Live editor state may require incremental caches and partial snapshots, but those caches should materialize the same graph records instead of defining parallel fact shapes.

The task-context ranker should start simple: exact identifiers, module/name/doc search, graph expansion over containment/import/reference/call/dispatch/evidence edges, provenance weights, public API boosts, diagnostic and architecture-signal proximity, and deterministic budget packing. Embedding re-ranking, community detection, feedback, and cross-package memory can come later without changing the core graph schema.

Layers affected

  • Parser / AST: parseable module structure, source spans, match dispatch, call sites, reference sites, and metadata syntax provide syntax-backed graph nodes even in tolerant mode.
  • Typechecker / Symbol resolution: checked declarations, imports, aliases, references, calls, public API signatures, dispatch targets where known, and diagnostics provide high-provenance graph facts.
  • IR Lowering: lowering does not define the graph, but generated or materialized structures may need source/metadata anchors so tools can connect emitted behavior back to source.
  • Emission: generated Rust is not the graph source of truth, but emission may expose artifact metadata that can be linked to source graph identities.
  • Stdlib / Runtime (incan_stdlib): stdlib source and manifests should be graphable as ordinary Incan packages, and stdlib builtins should appear as external or stdlib nodes when referenced.
  • Generated behavior explanation: derives, decorators, vocabularies, trait resolution, type-directed mappings, ownership plans, and generated artifacts should contribute source-anchored explanation records when available.
  • Formatter: no syntax changes are required by this RFC, but compact graph/context output should have deterministic formatting.
  • LSP / Tooling: CLI, MCP, editor integrations, graph export, graph stale checks, live graph snapshots, context packing, architecture-advice tooling, risk scoring, and checked metadata/documentation tooling are directly affected.
  • Packaging / Workspaces: package identity, workspace roots, lockfiles, generated artifacts, and future registry metadata contribute to graph identity and staleness.
  • VCS / Coverage inputs: git history, ownership signals, co-change data, coverage reports, and explicit decision records may contribute optional process-risk facts, but they must remain provenance-tagged and absent when inputs are unavailable.

Implementation Plan

Phase 1: Compiler-backed graph export

  • Export file, module, declaration, import, export, reference, call, diagnostic, span and provenance records from the compiler's own frontend, with strict checked and tolerant work-in-progress modes.
  • Give every declaration a reversible, span-free canonical identity and every record a graph-schema version.

Phase 2: One graph across Incan and Rust

  • Add namespace nodes over the module graph so a package's modules and the Rust items a module reaches are one subgraph.
  • Retire rust_item and uses_rust_item in favour of reference and call records whose canonical_owner names the Rust declaration, with reachability reported as a lower bound.

Phase 3: Semantic digests

  • Digest each declaration's checked meaning separately from its documentation, so a consumer can tell "changed" from "re-documented".
  • Fold closure digests over the graph's edges so a component's digest moves when anything it reaches moves, and back Rust bodies with MIR-derived digests.

Phase 4: Context surfaces

  • Define the CLI and MCP surfaces for raw graph exploration and task-ranked context packing, the compact deterministic agent context format, snapshot identities, staleness checks, and the optional process-risk and feedback facts.
  • Share the exporter's analysis services with the LSP so editor features read the same records.

Progress Checklist

Graph export

  • Compiler-backed incan inspect codegraph --format jsonl with files, modules, declarations, imports, exports, references, calls, diagnostics, spans, provenance and degraded-state records.
  • Canonical declaration identities that survive edits (incan-v1, span-free).
  • Graph-schema version on every export (schema 7).
  • Strict checked export and tolerant export.

Cross-language graph progress

  • Namespace nodes over the module graph (#1513).
  • Rust items a module reaches, recorded as reference and call records with canonical_owner (#1524).
  • Reference language derived from resolved identity rather than syntax.
  • rust_item and uses_rust_item retired.

Semantic digest progress

  • Per-declaration semantic digest excluding position, formatting, comments and documentation; documentation digest kept apart.
  • Closure digests over codegraph edges and MIR-backed Rust body digests (#1504).
  • Corpus and invariant tests: every standard-library declaration digests; a comment, a docstring, an insertion and a reorder move only what they should.
  • The exported identity carries the digest as its key (see "The exported identity does not satisfy this yet").

Context surfaces

  • MCP surface for graph exploration and task-ranked context packing.
  • Compact deterministic agent context format beside JSON and JSONL.
  • Content-addressed snapshot identities and staleness checks.
  • Optional process-risk facts (churn, ownership, co-change, coverage) with provenance and absence when inputs are unavailable.
  • Feedback and task-memory signals with automatic expiry.
  • LSP features reading the same graph records.

Docs

  • incan inspect codegraph documented in the CLI reference.
  • Agent context format and MCP surface documented.

Design Decisions

  • The v0.4 public CLI surface is incan inspect codegraph. Higher-level graph service commands, MCP tools, and task-packing commands remain follow-up work and may use a context, agent, or other namespace once those consumers are designed.
  • The v0.4 stable JSONL contract is the compiler-backed graph export shape implemented for source-backed Incan records: header schema/toolchain/mode/root/language metadata, deterministic record IDs, record kind, language: "incan" on source-backed records, provenance, degraded state, source spans where available, containment/import/export/reference/call-syntax facts where available, and diagnostics. Future fields must be additive or guarded by a schema version.
  • Native Incan JSONL is the source of truth for this RFC. A SCIP exporter may be added later as an adapter for external code-intelligence ecosystems, but SCIP does not replace the native graph schema.
  • The first release does not require Merkle snapshot roots. Deterministic IDs, schema versions, compiler versions, source roots, and degraded-state markers are sufficient for the v0.4 baseline; content-addressed graph snapshots, context-pack identity, and Merkle-style integrity are follow-up work.
  • The v0.4 edge and body-fact baseline is intentionally smaller than the north-star model: containment, imports, public exports, declaration structure, syntax-level reference and call-site records, spans, and diagnostics. Resolved reference targets, resolved call targets, aliases, implements, test edges, generated-from edges, match-dispatch families, metadata body facts, and richer pattern facts are follow-up graph layers.
  • Derived architecture findings and process-risk signals are separate record classes with different jobs. Architecture findings may be emitted as records or reports, but they must cite graph evidence; risk signals may explain priority, but they must not replace architectural evidence.
  • Process-risk records are not part of the v0.4 baseline beyond schema/provenance space for later consumers. Churn, ownership, co-change, coverage, decision staleness, trend snapshots, and predictive evaluation require Incan-specific validation before any prioritization claim is treated as more than evidence.
  • Live LSP graph snapshots should materialize the same fact model as persisted or exported graph snapshots. Dirty editor buffers require partial/stale markers and must not be silently mixed with checked persisted facts.
  • Feedback and learned usefulness signals belong in the agent-context or MCP layer rather than the core compiler export. The compiler may expose stable graph identities that make feedback expiry possible, but it should not own agent memory policy.
  • Compact task-context format is a consumer contract layered on top of JSONL. The JSONL graph export is the compatibility-stable contract for Planned status; compact context packing should become stable only when the MCP/task-context layer is implemented and evaluated.
  • Identity and digest are separate fields. Identity answers which declaration a fact is about and must survive edits; a semantic digest answers whether its meaning changed. A single content-addressed field cannot do both, and the graph is the normative source of the digest RFC 124 folds into unit identity.
  • One graph spans both languages. A declaration is a Declaration whatever its surface syntax, and language is an attribute of the fact rather than a partition of the schema. The reserved rust_item node kind and uses_rust_item edge kind are retired: they encode the distinction language exists to dissolve, and would leave two ways to state one relationship.
  • Reachability over Rust is a lower bound, not an exact answer. Unresolved edges count as reachable and affectedness propagates downstream. This binds consumers: anything skipping work on a reachability answer must err toward doing the work, because a missed node is a wrong build while a spurious one is only slow.
  • Rust-side facts should come from what rust_inspect already resolves through rust-analyzer before any independent analysis is written. Re-deriving name resolution would reproduce the low-trust rediscovery this RFC exists to avoid.
  • SCIP remains an export adapter rather than the internal model, but its symbol naming and its separation of definitions from reference occurrences should be followed where they fit, so an exporter stays a projection rather than a translation.
  • Planned status does not require proving all retrieval and risk-quality claims. The minimum bar for Planned is a settled architecture, a v0.4 baseline export, and explicit follow-up issues for resolved targets, LSP sharing, MCP/task packing, Architect integration, process-risk evaluation, external importer experiments, and first-class Rust records.