Token-efficient codebase indexing and querying for LLM agents.
curl -fsSL https://raw.githubusercontent.com/vu1n/canopy/main/install.sh | shThis installs canopy + canopy-mcp to ~/.local/bin/ and configures Claude Code automatically.
Options:
--no-claude-setup: skip Claude Code MCP configuration.--prefix /usr/local: install to a custom location.
Build from source
git clone https://github.com/vu1n/canopy.git
cd canopy
make install # builds + installs canopy + canopy-mcp
make setup-claude # configures Claude CodeClaude Code now has access to canopy tools. Ask questions like:
- "Find the AuthController class" ->
canopy_query(symbol="AuthController") - "How does authentication work?" ->
canopy_query(pattern="auth") - "List all API endpoints" ->
canopy_query(symbol="Controller")plus selective expands
Typical agent flow:
- Index relevant paths based on query intent (predictive lazy indexing).
- Return compact handles + previews instead of full files.
- Expand only handles needed for the final answer.
| Scenario | Why |
|---|---|
| Large codebases (>1000 files) | Predictive indexing avoids blocking on full upfront index |
| Symbol discovery | Finds function/class definitions with file:line anchors |
| Cross-file tracing | Follows execution paths across files |
| Subsystem analysis | Scopes indexing/query to relevant areas |
| Parallel agents | Shared index behavior is better for concurrent exploration |
| Scenario | Use Instead |
|---|---|
| Known file path | Read tool directly |
| Literal text pattern | Grep tool |
| File by name | Glob / fd |
| Small repos (<500 files) | Native tools are usually enough |
Canopy treats repository understanding as a budgeted retrieval loop: broad cheap retrieval first, then selective expansion under explicit token and turn constraints.
- Handle-first retrieval
canopy_queryreturns compact handles and previews first; full content is fetched withcanopy_expand. - Predictive lazy indexing Query intent predicts likely globs so indexing is targeted before search.
- Feedback-reranked retrieval
Query/expand feedback in
.canopy/feedback.dbreranks future retrieval:- glob ranking (
glob_hit_rate_at_k) - node-type priors (
handle_expand_accept_rate)
- glob ranking (
- Retrieve -> local overlay -> merge (service mode) Service results are merged with local dirty-file overlays to keep answers fresh without full reindex.
- Guidance-driven evidence packs
canopy_evidence_packreturns ranked handles plusguidancesignals to stop querying and switch to expansion/synthesis. - Low-confidence recursive planning (service mode) Service-side planning is enabled by default only when evidence confidence is low; otherwise it stays single-step.
- Expand churn suppression
Recently expanded handles are de-prioritized in future
expand_suggestionlists to avoid repeated expansions. - Worst-case budget policy
Retrieval is constrained by
expand_budget,limit, and turn budget (MAX_TURNSin swarm tests).
Informal objective:
- maximize answer utility (grounding, coverage, correctness)
- minimize token cost and tail-risk of runaway context growth
Mental model for ranking:
score(handle) =
w_text * lexical_relevance +
w_type * node_type_prior +
w_feedback * historical_expand_acceptance
The ranker optimizes expected utility per token, not just raw relevance.
User Question
|
v
Predictive Scope Selection
(keywords -> likely globs / symbols)
|
v
Initial Query (cheap)
-> candidate handles + previews + token estimates
|
v
Handle Ranking
(text relevance + type priors + feedback priors)
|
v
Budget Gate
- expand top-k within budget
- keep strict limit / expand_budget
|
+--> unresolved + budget left? -- yes --> re-query / re-rank / expand
| (iterate)
|
no
|
v
Synthesize Answer
(grounded in expanded evidence)
+------------------------------+
| canopy-service |
| pre-indexed repo snapshots |
+---------------+--------------+
|
v
User Question -> canopy-mcp -> Service Query/Expand (generation-tagged handles)
|
v
Service Candidate Set
|
v
Dirty-File Detector (local working tree)
|
+---------------+---------------+
| |
clean | | dirty
v v
use service result local incremental index
directly on dirty subset only
| |
+---------------+---------------+
|
v
Local/Service Result Merge
(dedupe + freshness preference)
|
v
Grounded Final Answer
Query Issued
|
v
Handles Returned
|
v
Which handles were expanded?
|
v
Write events to .canopy/feedback.db
- query_events
- query_handles
- expand_events
|
v
Compute derived signals
- glob_hit_rate_at_k
- handle_expand_accept_rate
- node-type priors
|
v
Apply priors during future ranking
|
v
Better next-query ordering under same token budget
Traditional approach:
Agent reads file1.ts (500 tokens)
Agent reads file2.ts (800 tokens)
Agent reads file3.ts (600 tokens)
Total: 1900 tokens
Canopy approach:
Agent queries "auth" -> 10 handles with previews (200 tokens)
Agent expands 2 relevant handles (400 tokens)
Total: 600 tokens
Use canopy feedback-stats to inspect local retrieval feedback metrics.
Once configured, Claude Code has these tools:
Search indexed content. Returns handles with previews and token counts.
canopy_query(pattern="authentication")
canopy_query(symbol="AuthController", glob="src/**/*.ts")
canopy_query(patterns=["TODO", "FIXME"], match="any")
| Param | Type | Description |
|---|---|---|
pattern |
string | Text pattern to search |
patterns |
array | Multiple patterns |
symbol |
string | Code symbol (function, class, struct, method) |
section |
string | Markdown section heading |
glob |
string | Filter by file glob |
match |
any | all |
Multi-pattern mode |
limit |
integer | Max results (default: 16) |
expand_budget |
integer | Deprecated auto-expand toggle (default: 0, disabled) |
Build a compact ranked evidence set (no snippets) to minimize context bloat before expanding.
canopy_evidence_pack(pattern="authentication", max_handles=8, max_per_file=2)
Response includes guidance.stop_querying, guidance.recommended_action, and guidance.next_step
so agents can transition from retrieval to synthesis without custom prompt rules.
Expand handles to full content.
canopy_expand(handle_ids=["h1a2b3c...", "h5d6e7f..."])
Get index statistics.
Force reindex of files.
Return usage guidance for agents/tool callers.
This guidance is canopy-first: prefer canopy retrieval over ad-hoc find/grep/rg
for discovery, then expand selectively for synthesis.
# Initialize canopy in a repository
canopy init
# Index files (MCP server auto-indexes on query; CLI requires explicit index)
canopy index
# Query the codebase
canopy query --pattern "authentication"
canopy query --symbol "AuthController"
# Expand handles to full content
canopy expand <handle_id>
# Check index status
canopy status
# Local feedback metrics
canopy feedback-statsCanopy supports two modes through the shared canopy-client runtime.
Default when no service URL is configured.
canopy query --pattern "auth"Best for: solo developers, small/medium repos, quick setup.
For multi-agent and team workflows with shared indexing.
# Install and start service
curl -fsSL https://raw.githubusercontent.com/vu1n/canopy/main/install-service.sh | sh
canopy-service --port 3000
# Register and reindex repo
curl -X POST localhost:3000/repos/add -H 'Content-Type: application/json' \
-d '{"path": "/path/to/repo", "name": "my-repo"}'
curl -X POST localhost:3000/reindex -H 'Content-Type: application/json' \
-d '{"repo": "<repo-id>"}'
# Query via service
CANOPY_SERVICE_URL=http://localhost:3000 canopy query --symbol "Config"Service mode features:
- Generation tracking for stale-handle safety.
- Dirty-file local overlay merge for freshness.
- Handle metadata (
source,commit_sha,generation).
Create .canopy/config.toml in your repo:
[core]
default_result_limit = 20
[indexing]
default_glob = "**/*.{ts,tsx,js,jsx,py,rs,go}"
preview_bytes = 100
ttl = "24h"
[ignore]
patterns = ["node_modules", ".git", "dist", "build", "__pycache__"]+-----------------+
| canopy-service | HTTP service for multi-repo indexing
+-----------------+
| canopy-mcp | MCP server for Claude Code
| canopy-cli | Command-line interface
+-----------------+
| canopy-client | Shared runtime (service client, dirty overlay, merge, predict)
+-----------------+
| canopy-core | Indexing and query engine
| - index/ | SQLite FTS5 + symbol cache + pipeline + file discovery
| - parse.rs | Tree-sitter parsing
| - query.rs | Query DSL, execution, evidence packs
| - handle.rs | HandleSource + generation metadata
| - error.rs | CanopyError + ErrorEnvelope (shared boundary contract)
+-----------------+
No canonical benchmark claims are published yet.
For local evaluation:
# Multi-agent comparison
MODE=compare COMPARE_MODES="baseline canopy canopy-service" \
AGENTS=4 MAX_TURNS=5 INDEX_TIMEOUT=1200 \
./benchmark/run-swarm-test.sh /path/to/repo
# Single-agent A/B
./benchmark/run-ab-test.sh /path/to/repoDetailed protocol, metric definitions, and troubleshooting live in docs/benchmarking.md.
Token metric interpretation:
- Reported tokens: tokens billed in the run summary.
- Effective tokens: reported tokens plus cache-read tokens (proxy for total context consumed by the agent loop).
- Cache-read tokens can dominate long loops, so reported-only views may hide retrieval churn.
Design principles, anti-drift guardrails, and divergence logging live in docs/design-anchors.md.
All crates in this workspace are versioned together and released as a unit.
Cross-crate contracts (e.g., ErrorEnvelope, QueryParams, Handle) live in canopy-core and are re-exported by downstream crates.
| Component pair | Contract | Breakage signal |
|---|---|---|
| canopy-service ↔ canopy-client | JSON over HTTP; ErrorEnvelope, QueryResult, EvidencePack schemas |
Service integration tests (canopy-client/tests/service_integration.rs) |
| canopy-mcp ↔ canopy-client | ClientRuntime public API |
MCP build + unit tests |
| canopy-cli ↔ canopy-client | ClientRuntime public API |
CLI build + unit tests |
| canopy-client ↔ canopy-core | RepoIndex, QueryParams, Handle, EvidencePack |
Client unit tests |
Service mode requires matching canopy-service and canopy-client versions.
Running mismatched versions may produce stale_generation or schema errors.
| Behavior | Gate | Location |
|---|---|---|
| Core indexing + query | 59 unit tests | canopy-core |
| Client runtime, dirty overlay, merge | 32 unit tests | canopy-client |
| Service routes, evidence, state | 20 unit tests | canopy-service |
| End-to-end service lifecycle | 5 integration tests | canopy-client/tests/, canopy-service/tests/ |
| Symbol cache consistency | test_symbol_cache_by_file_consistency |
canopy-core |
| Pipeline vs sequential indexing | test_pipeline_path_indexes_large_batch, test_sequential_path_indexes_small_batch |
canopy-core |
| Dirty-file merge correctness | test_dirty_merge_* |
canopy-client |
Run cargo test to execute all gates. No benchmark quality gates are published yet; see docs/benchmarking.md for evaluation methodology.
MIT