Flagship independent systems project
Satori
An agent-safe semantic code retrieval system for MCP coding agents, with indexing, hybrid search, deterministic navigation, and hardened lifecycle semantics.
Satori
Summary: Satori is my flagship independent systems project: an MCP server and retrieval engine that helps coding agents search real repositories, open exact code spans, inspect symbol outlines, and reason from fresher context before editing.
Why I built it
I kept running into the same failure mode with coding agents: they could edit quickly, but they often started from weak context. File-name guesses, stale indexes, noisy chunks, duplicated results, and broad context dumps made the agent spend extra turns just finding the right code.
Satori started as a direct response to that problem. The goal was not to build a general agent framework. The goal was narrower: make repository investigation more deterministic before an agent touches code.
What it does
Satori indexes a codebase, chunks source with AST-aware boundaries where possible, embeds code into Milvus or Zilliz, and exposes a small MCP surface for coding agents.
The public tool surface is intentionally constrained:
list_codebasesmanage_indexsearch_codebasefile_outlinecall_graphread_file
That shape is deliberate. Agents need fewer ambiguous tools, not more knobs. Search finds candidate areas, outline locks symbol spans, call graph adds local relationship context, and read returns bounded file evidence.
Workflow Schematic
[Host IDE / Agent] ─(Stdio JSON-RPC)─➔ [Satori MCP Server]
│
┌─────────────┴─────────────┐
▼ ▼
[Symbol Graph] [Vector Engine]
(Tree-sitter) (Milvus/Zilliz)
│ │
└─────────────┬─────────────┘
▼
[Bounded Context Chunk]
Terminal Session Output
$ satori search "symbol: getStaticPaths"
[INDEX] Loaded ~/.satori/index.db (142 files, 4,210 chunks)
[SEARCH] Running hybrid search (BM25 + Dense) for "symbol: getStaticPaths"...
[RESULTS] Found 2 matches in 45ms:
1. src/pages/projects/[slug].astro:L8-14 (Score: 0.94)
export async function getStaticPaths() {
const projects = await getCollection('projects');
return projects.map((project) => ({ ... }));
}
2. src/pages/posts/[slug].astro:L5-11 (Score: 0.82)
Tech stack
The project is a TypeScript monorepo with three runtime packages:
@zokizuan/satori-corefor indexing, AST chunking, embeddings, vector storage, and incremental sync.@zokizuan/satori-mcpfor the MCP server, tool schemas, state gates, and agent-facing contracts.@zokizuan/satori-clifor installing client config and first-party skills.
Under the hood it uses tree-sitter for AST-aware chunking, dense plus BM25 hybrid retrieval, optional VoyageAI reranking, Milvus/Zilliz vector storage, and local snapshot state under ~/.satori.
Key engineering decisions
I treated determinism as the product boundary. A coding agent should know when an index is stale, when a graph is unsupported, when a symbol is ambiguous, and when a reindex is required.
That led to several important constraints:
- A fixed six-tool MCP surface instead of an expanding tool bundle.
- Runtime-first search defaults that exclude docs/tests unless requested.
- Fingerprint gates for provider, model, dimension, vector store, and schema compatibility.
- Exact symbol and line-range reads rather than broad file dumps.
- Sync for normal file and ignore-rule changes, reindex only for rebuild recovery.
- No write-capable MCP tools; Satori helps agents investigate, while edits still happen through the host environment.
Problems I ran into
The hardest work was not the first search result. It was keeping lifecycle behavior honest after the happy path broke.
A few examples:
- Search quality improved only after handling noisy test/fixture/generated results and grouping repeated chunks.
- CLI work forced strict stdout discipline because MCP JSON-RPC over stdio cannot tolerate accidental logs.
- Index readiness needed completion-proof checks so a local snapshot alone was not treated as enough evidence.
- Milvus/Zilliz delete timeouts exposed a subtle lifecycle bug: local state must not be cleared unless remote deletion is actually verified.
Those bugs changed the architecture. They pushed Satori toward explicit state, retryable failures, deterministic hints, and tests around the edge cases that agents are likely to hit.
Engineering Notes & Lessons Learned
- Tooling as Systems Engineering: Agent search quality is secondary to contract predictability. A tool must expose stable boundaries, bounded reads, and lifecycle gates that gracefully degrade.
- Designing for Failures: Building Satori reinforced designing for edge cases. A coding agent needs explicit, machine-readable instructions on what is safe to do when indexes are stale, missing, or require reindexing.
Validation Notes
- Bounded Retrieval Validation: Compared AST-aware retrieval against naive character chunking during local agent runs. AST segmentation produced more symbol-relevant retrieval results and fewer irrelevant context blocks.
- Local Workspace Sync: Verified incremental indexing updates for local file changes and ignore-rule updates.
- Stdio Transport Stability: Tested JSON-RPC messaging continuity under standard input/output streams across multi-hour agent runs without transport-level protocol errors.
What I would improve next
I want to keep improving retrieval evaluation, language coverage for call graphs, local-first setup paths, and the public documentation around real workflows. The product boundary should stay small: index, search, navigate, read, and explain lifecycle state clearly.