Local code navigation for AI agents

Give your coding agent a map.

An illustrative black hole with a large circular dark center, a tight gold ring, and a narrow band of orbiting particles.

Explore large C# and mixed C#/F# repos through MCP or the CLI. Focused answers. Clear confidence labels.

MCP + CLI

Connect your agent or call a tool from your terminal. Same executable. Same local engine.

Explore Phoenix

Less searching. More understanding.

Find the next
useful fact.

01 / FIND

Where is it defined?

Find a symbol and jump to its owning declaration, with the source you need.

search_symbol · definition
02 / FOLLOW

What connects to it?

Follow C# references, callers, and project dependencies across the repository.

references · callers · project_graph
03 / UNDERSTAND

What could this change affect?

Gather compact context, dependency impact, and likely test leads before you edit.

context_pack · impact · related_tests

Two ways in. One engine.

Your agent.
Your terminal.

Same navigation tools, same JSON answers, one shared workspace daemon.

MCP

Connect your coding agent.

Attach Phoenix to Claude Code, Codex, or another MCP client. Ask a question; your agent chooses the tools.

“Where is IndexManager defined, and who calls it?”
CLI

Call the tools directly.

Use the same executable from a terminal, script, or shell-only agent. No MCP client configuration required.

PowerShell · Phoenix on PATH
PhoenixCodeNav.Mcp.exe search_symbol `
  --workspace-root . `
  --query IndexManager --limit 5

One installation for both. Install Phoenix from the README ↗

Know what to trust

Useful answers.
Honest boundaries.

Exact

Compiler-backed evidence in the selected context.

Indexed

Facts from the index, with freshness and coverage disclosed.

Heuristic

A useful lead to verify, not a compiler fact.

Phoenix runs locally and writes its index under .codenav/. It does not edit tracked source files or Git refs. Your attached AI client may send returned context under its own data policy.

A few useful details

Which languages are supported?

C# is the primary language. Bounded F# support includes search, indexed declarations, outlines, position lookups, definitions through a supported F# project-reference closure, implementations, callers and callees, compiler-bound references across proven workspace dependents, and single-target netstandard2.0/2.1 semantics. F# type hierarchy, semantic navigation through C# project references, compatibility fallback from multi-target children, and netstandard1.x compile inputs remain unsupported. Markdown and SQL support text navigation. Partial coverage is disclosed. Read the current language boundaries ↗

Can several agents use one repository?

Yes. They share one Phoenix daemon per workspace, including CLI callers: one index writer, one watcher, and shared warm semantic state. If startup cannot succeed, Phoenix reports a typed, actionable cause instead of silently returning empty answers.

Do I need to configure MCP for the CLI?

No. The CLI is included in the same executable. Run tools, help <tool>, or schema <tool> to discover it offline. Tool calls then start or join the workspace daemon and return JSON.

Want to look under the hood?A worked Roslyn example, operational signals, and the local trust boundary.

From syntax to meaning

How an agent turns
var into a fact.

Follow a real example from Roslyn: an agent resolves an exact method call, reads bounded live source context, and combines that evidence with the C# rule for await. The result is an explanation of var newCompilation with a traceable reason for every step.

  1. 01

    The question in the code

    var hides the answer from plain text.

    The call site says what happens, but not which overload the compiler chose or what newCompilation means. A text search for GetCompilationAsync returns declarations and calls across the repository; the agent still needs the identity attached to this exact token.

    Roslyn · real source, shortened
    39 | public RegularCompilationTracker UnderlyingTracker { get; }
       |
    123| var newCompilation = await
       |     UnderlyingTracker.GetCompilationAsync(
       |         compilationState, cancellationToken)
       |     .ConfigureAwait(false);
       |
    136| newCompilation = newCompilation.ReplaceSyntaxTree(
       |     existingSyntaxTree, replacementSyntaxTree);
  2. 02

    Address the token, not the spelling

    The agent asks about one call in one snapshot.

    definition accepts the real workspace-relative path, 1-based line, and 1-based column. The optional name is an additional guard on a line that also declares a local variable. mode: "semantic" forbids a silent indexed guess, while includeBody returns the live declaration and its written return type.

    MCP · compiler-semantic definition request
    definition(
        name: "GetCompilationAsync",
        path: "src/Workspaces/Core/Portable/Workspace/Solution/" +
              "SolutionCompilationState.WithFrozenSourceGeneratedDocumentsCompilationTracker.cs",
        line: 123,
        column: 58,
        mode: "semantic",
        includeBody: true,
        timeoutMs: 30000
    )
    Exact positionCompiler binding for one source token
  3. 03

    The evidence chain

    Small facts compose into code understanding.

    Compile ownership comes from the index; a bounded live source read supplies the declared receiver type. Roslyn then binds the call to one symbol: RegularCompilationTracker.GetCompilationAsync. Its declaration returns Task<Compilation>; C# await unwraps that task, so the agent can state that newCompilation is a Compilation and explain why.

    Agent reasoning · every arrow has evidence
    UnderlyingTracker
    └─ declared type → RegularCompilationTracker
       └─ exact call → GetCompilationAsync(...)
          └─ return type → Task<Compilation>
             └─ await unwraps → Compilation
                └─ therefore → newCompilation : Compilation
    
    Follow-on check:
    newCompilation.ReplaceSyntaxTree(...)
    └─ receiver remains → Compilation
  4. 04

    The compiler-backed result

    The response carries identity, source, and provenance.

    The response identifies the compiler-bound symbol and its declaration, then labels the confidence, navigation layer, and freshness of each source read. This representative envelope shows those production fields together: exact semantic identity and live source are distinct pieces of evidence.

    Explore the full responseSymbol identity · source · confidence · freshness
    definition · representative response
    {
      "name": "GetCompilationAsync",
      "symbol": {
        "display": "Microsoft.CodeAnalysis.SolutionCompilationState.RegularCompilationTracker.GetCompilationAsync(Microsoft.CodeAnalysis.SolutionCompilationState, CancellationToken)",
        "kind": "method",
        "containingType": "RegularCompilationTracker",
        "ns": "Microsoft.CodeAnalysis",
        "assembly": "Microsoft.CodeAnalysis.Workspaces"
      },
      "declarations": [
        {
          "path": "src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs",
          "startLine": 216,
          "endLine": 233
        }
      ],
      "body": {
        "path": "src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs",
        "startLine": 216,
        "endLine": 233,
        "source": "  216| public async Task<Compilation> GetCompilationAsync(...)",
        "truncated": false,
        "freshness": "live"
      },
      "timing": {
        "deadlineMs": 30000,
        "elapsedMs": 54
      },
      "meta": {
        "indexStatus": "ready",
        "indexVersion": "7f4c2a9d18e64b3a92c01d5f6e8a7b40",
        "pendingChanges": 0,
        "confidence": "exact",
        "navigationLayer": "semantic",
        "build": "0.12.55+7b0b8112028b",
        "indexSchema": "28",
        "indexMode": "daemon"
      }
    }
    Compiler identityNot name matching dressed up as certainty
  5. 05

    The honest boundary

    Understanding is a proof trail, not a magic label.

    Phoenix does not claim that definition directly performs full expression inference for arbitrary C#. The agent composes a compiler-bound member identity, a separately read live declaration body, receiver context from another bounded live read, and a language rule. If the project cannot be loaded or the token cannot be resolved, the tool returns explicit semantic_unavailable or partial evidence instead of inventing the type.

    Agent conclusion · explainable and bounded
    What I can say:
      newCompilation is Compilation.
    
    Why:
      receiver declaration  → RegularCompilationTracker
      compiler-bound method → Task<Compilation>
      C# await rule         → Compilation
    
    What I cannot say:
      “Phoenix inferred every arbitrary expression type.”
    
    Evidence:
      indexed ownership
      text · live receiver context
      exact · semantic call binding
      separately read · live declaration body
    The useful answerA conclusion with receipts

Fast feedback without frozen promises

Progress you can see.
Confidence you can use.

Repository shape and hardware decide timings. Phoenix reports live progress, freshness, and bounded coverage instead of pretending one benchmark fits every workspace.

Cold index progress LIVE

phase · files · throughput

Measured on your machine, for your repository
Incremental updates DELTA

edits, additions, and deletes update the index

Freshness metadata HEAD

compare indexed state with the repository commit

Lazy semantics LAZY

compile only the project clusters an agent needs

Response budgets BOUND

compact, line-addressable context stays focused

Shared daemon ONE

every agent on a workspace joins one shared runtime — one index writer, one watcher, one warm semantic estate — transparently, with nothing to configure

Operations portal LOCAL

early-access loopback view of observed instances across up to eight configured workspaces, with index health and available live build progress — setup guide ↗

Local server, honest boundary

A local server.
An honest boundary.

PhoenixCodeNav runs locally; there is no hosted Phoenix service. Every agent you attach converges on one shared Phoenix daemon per workspace — elected and rejoined automatically, never configured. It reads your C# workspace, serves bounded results to each MCP client, and leaves tracked source files and Git refs unchanged. It does write local indexes under .codenav/.

Like other Git tooling, repository configuration still matters: Git may invoke configured helpers or fetch missing objects in a partial clone.

Important: your attached AI client may transmit returned context under its own data policy. Phoenix cannot control that boundary.