You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Code indexing has become a crowded category, and the engines are converging on one shape while diverging on license. codeoid should plug into whichever one the user configures, and — the actual point — fuse its output into recall rather than dumping its tools into the model's lap.
MCP (axon_query/axon_context/axon_impact), FastAPI, CLI, web
Convergent: tree-sitter → structural knowledge graph in an embedded local DB, plus optional embeddings and hybrid BM25/vector, exposed over MCP + HTTP + CLI. All three are local-only with zero cloud dependency, which clears codeoid's self-hostable constraint.
Divergent, and decisive: the most popular engine in the category is PolyForm Noncommercial. We cannot bundle or depend on GitNexus commercially, but a user may absolutely want to use it. That is the argument for an interface rather than an adoption — and it is why #11 ("adopt a local code-graph MCP server") should become one implementation behind this seam rather than the plan itself.
What already works today — be honest about the baseline
Any of these can be mounted right now through the existing MCP registry as stdio {command,args,env} or http {url,headers}, and mcp/tool-source.ts surfaces it on every backend. So "plug in an index engine" is already possible at the crudest level.
What the MCP-mount route does not give you — this is the ticket
The model has to remember to call it. gortex exposes 100+ tools; GitNexus 17. Dumping that into the tool surface is precisely the tool-selection pressure problem tracked as open question fix: six confirmed audit findings (token lifecycle, msg-drop, recall scaling) #12 in conductor-design.md, and it degrades badly on a small open-weight backend. A retrieval source is consulted by the engine; a tool is consulted by the model's judgment. Only the first is reliable.
No fusion.RecallHit.components is {vector, fts, recency, pathOverlap} combined by a weighted sum in ranker.ts. A structural score cannot participate, so you end up with two separate retrieval systems instead of one better one.
No lifecycle. No freshness signal, no incremental update on file change, no status surface. IndexScheduler already does exactly this job for codeoid's own index.
No workspace-index contribution. codeoid injects a workspace memory index into every system prompt. An engine's architecture summary and cluster labels are exactly what belongs there, and a tool mount cannot contribute to it.
Why this is strategically ours, not catch-up
#282 established that codeoid's retrieval moat is the corpus — tool-call-granular episodes with file paths — because a text-ingesting memory layer never sees a tool call. A code index is the complementary axis: static structure (who calls what, what type is this, what is dead) against behavioral history (what agents actually did to these files, together, when).
The sharpest instance: axon computes change coupling from git history. #69 computes file co-occurrence from agent tool calls. Those are different couplings — what humans historically changed together, versus what agents touched together in sessions. Fusing static graph + git coupling + agent coupling produces a signal no engine in the table above can compute, because the third term requires the episode corpus. That is the differentiator, and it only exists if the index is a fused source rather than a mounted tool.
Proposed design
The interface — mirror Embedder/Reranker
Small, factory-constructed, registered like a provider with markUnavailable(hint) when the binary or service is unresolvable (the pi resolution pattern in providers/registry.ts is the model).
exportinterfaceCodeIndex{readonlyid: string;readonlydisplayName: string;init(): Promise<void>;/** Optional capabilities this engine actually supports — probe, never assume. */capabilities(): CodeIndexCapabilities;/** Freshness + coverage, so recall can decide whether to trust it. */status(workspaceId: string): Promise<CodeIndexStatus>;/** Ensure/refresh; incremental where the engine supports it. Driven by IndexScheduler. */sync(workspaceId: string,changed?: string[]): Promise<void>;/** The retrieval primitive that fuses into the ranker. */search(q: CodeQuery): Promise<CodeHit[]>;/** Graph primitives, guarded by capabilities(). */neighbors?(node: CodeRef,edge: CodeEdgeKind,depth?: number): Promise<CodeRef[]>;impact?(node: CodeRef): Promise<CodeRef[]>;close(): Promise<void>;}
Capability probing, not a fat interface. The engines differ wildly — 257 grammars with compiler-grade resolution versus three languages, and completely different graph vocabularies. A fat required surface would exclude nearly everything. Required: search + status + sync. Everything else optional behind capabilities(), with graceful degradation and never an error when unsupported.
Normalize into a canonical CodeHit/CodeRef, the same discipline providers/canonical.ts applies to provider events. That normalization is what makes fusion possible at all.
Two tiers, exactly like the backend strategy
COMPARISON.md already explains why codeoid runs native providers plus a cheap ACP adapter rather than a PTY tier. Same shape here:
Native adapter per engine — full fidelity, uses the engine's library or typed HTTP API.
Generic MCP-backed adapter — since every engine in the table ships MCP, one adapter plus a config mapping of tool names to interface methods covers the whole category cheaply. This is the "any engine the user configures" answer.
Three surfaces for the results
Fused ranker signal. Add a codeStructure component to RecallHit.components behind a weight that defaults to 0 when no index is configured, so existing behavior is bit-identical without one.
A small stable tool set of ours — roughly code_search / code_context / code_impact — normalized across engines, so a backend sees ~4 stable tools instead of 100 engine-specific ones. This is what fixes the tool-count problem rather than inheriting it.
Workspace-index contributor — architecture summary and cluster labels into the injected system-prompt index.
Registry with resolution + markUnavailable(hint), config under codeIndex.* (engine id, command/url, weight, sync cadence)
Generic MCP-backed adapter with a tool-name mapping
One native adapter as the reference — gortex (Apache-2.0) is the right first pick: permissive license, single static binary with zero external deps, widest language coverage, and a versioned /v1/* API
Ranker fusion behind a default-0 weight
IndexScheduler drives sync() out-of-band — gortex indexes the Linux kernel (70k files) in ~3 min, so this must never touch a turn's critical path
Normalized code_* tool set replacing raw engine tool exposure
Adapters record the engine's license so a commercial deployment cannot accidentally take a PolyForm-NC dependency
Open questions
Worktree interaction. Engines write .gitnexus/ / .axon/ into the repo, but codeoid anchors memory on git-common-dir precisely so worktrees share it. Per-worktree indexes would duplicate a multi-minute build. Policy needed.
Tenancy. The index is per-workspace on disk, not per-(account, project). Probably acceptable for local single-operator use; state the boundary rather than discover it.
Do we need the graph at all, or only search? A v1 that fuses search and ignores neighbors/impact is far cheaper and may capture most of the value. Worth measuring against the P0 fixture before building graph plumbing.
Code indexing has become a crowded category, and the engines are converging on one shape while diverging on license. codeoid should plug into whichever one the user configures, and — the actual point — fuse its output into recall rather than dumping its tools into the model's lap.
The landscape, surveyed
.gitnexus//v1/*+ SSE, CLI, web UI.axon/kuzu/axon_query/axon_context/axon_impact), FastAPI, CLI, webConvergent: tree-sitter → structural knowledge graph in an embedded local DB, plus optional embeddings and hybrid BM25/vector, exposed over MCP + HTTP + CLI. All three are local-only with zero cloud dependency, which clears codeoid's self-hostable constraint.
Divergent, and decisive: the most popular engine in the category is PolyForm Noncommercial. We cannot bundle or depend on GitNexus commercially, but a user may absolutely want to use it. That is the argument for an interface rather than an adoption — and it is why #11 ("adopt a local code-graph MCP server") should become one implementation behind this seam rather than the plan itself.
What already works today — be honest about the baseline
Any of these can be mounted right now through the existing MCP registry as stdio
{command,args,env}or http{url,headers}, andmcp/tool-source.tssurfaces it on every backend. So "plug in an index engine" is already possible at the crudest level.What the MCP-mount route does not give you — this is the ticket
RecallHit.componentsis{vector, fts, recency, pathOverlap}combined by a weighted sum inranker.ts. A structural score cannot participate, so you end up with two separate retrieval systems instead of one better one.IndexScheduleralready does exactly this job for codeoid's own index.Why this is strategically ours, not catch-up
#282 established that codeoid's retrieval moat is the corpus — tool-call-granular episodes with file paths — because a text-ingesting memory layer never sees a tool call. A code index is the complementary axis: static structure (who calls what, what type is this, what is dead) against behavioral history (what agents actually did to these files, together, when).
The sharpest instance: axon computes change coupling from git history. #69 computes file co-occurrence from agent tool calls. Those are different couplings — what humans historically changed together, versus what agents touched together in sessions. Fusing static graph + git coupling + agent coupling produces a signal no engine in the table above can compute, because the third term requires the episode corpus. That is the differentiator, and it only exists if the index is a fused source rather than a mounted tool.
Proposed design
The interface — mirror
Embedder/RerankerSmall, factory-constructed, registered like a provider with
markUnavailable(hint)when the binary or service is unresolvable (thepiresolution pattern inproviders/registry.tsis the model).Capability probing, not a fat interface. The engines differ wildly — 257 grammars with compiler-grade resolution versus three languages, and completely different graph vocabularies. A fat required surface would exclude nearly everything. Required:
search+status+sync. Everything else optional behindcapabilities(), with graceful degradation and never an error when unsupported.Normalize into a canonical
CodeHit/CodeRef, the same disciplineproviders/canonical.tsapplies to provider events. That normalization is what makes fusion possible at all.Two tiers, exactly like the backend strategy
COMPARISON.md already explains why codeoid runs native providers plus a cheap ACP adapter rather than a PTY tier. Same shape here:
Three surfaces for the results
codeStructurecomponent toRecallHit.componentsbehind a weight that defaults to 0 when no index is configured, so existing behavior is bit-identical without one.code_search/code_context/code_impact— normalized across engines, so a backend sees ~4 stable tools instead of 100 engine-specific ones. This is what fixes the tool-count problem rather than inheriting it.Scope
CodeIndexinterface +CodeIndexCapabilities+ canonicalCodeHit/CodeReftypesmarkUnavailable(hint), config undercodeIndex.*(engine id, command/url, weight, sync cadence)/v1/*APIIndexSchedulerdrivessync()out-of-band — gortex indexes the Linux kernel (70k files) in ~3 min, so this must never touch a turn's critical pathcode_*tool set replacing raw engine tool exposureOpen questions
.gitnexus//.axon/into the repo, but codeoid anchors memory ongit-common-dirprecisely so worktrees share it. Per-worktree indexes would duplicate a multi-minute build. Policy needed.(account, project). Probably acceptable for local single-operator use; state the boundary rather than discover it.search? A v1 that fusessearchand ignoresneighbors/impactis far cheaper and may capture most of the value. Worth measuring against the P0 fixture before building graph plumbing.Related