How we cut repo-wide symbol indexing for LLM agents from 30s to 98ms LiuHe, a code-operation toolchain designed for LLM agents, has cut repo-wide symbol indexing from tens of seconds to 98ms. The project, built by developer wulun811, uses a Rust tree-sitter parse daemon, SQLite symbol index, and transactional journal-backed writes to address the unique constraints of agents that lack hands, eyes, and memory. The toolkit includes features like atomic edits, undo journals, and executable error recovery actions to prevent silent data loss. If your coding agent has ever stalled for tens of seconds on "what's in this repo?" — or burned hundreds of tokens re-reading a file after a failed edit — this is the story of why that happens and how we fixed it. TL;DR — we rebuilt code tooling for agents that have no hands, no eyes, and no memory: repo map in kill -9 — no more silently lost work LiuHe https://github.com/wulun811/LiuHe https://github.com/wulun811/LiuHe is a code-operation toolchain designed for LLMs rather than humans: Node orchestration, a Rust tree-sitter parse daemon, a SQLite symbol index, and transactional journal-backed writes. MIT, v0.4.6, zero-build deploy no cargo, no npm install . This post is the architecture story: what was slow, what we changed, and the numbers we measured while doing it. Six families, all deterministic, all reproducible from benchmarks/ : read symbol version-anchored , symbol search , code search , repo map 98ms, paginated skeleton , reindex , dep graph , references impact analysis , call chain , trace symbol constant tracing , inspect , sweep dead code , config drift edit batch 4-level tolerant matching , edit transaction atomic + undo journal , rename symbol , git worktree , edit sandbox , edit collision guard , diff facts code review , security review , code quality , style sniffer , guard patterns , naming consistency , dependency gatekeeper , fix imports , mock sync — zero LLM calls test bridge , find tests , verify pipeline , debug runner , tsc check , patch parser , spec gen health self-healing , gc , feedback If you've watched an agent burn thousands of tokens re-reading a file because a sed didn't match, or lose work to a silently botched write — these are the failure modes this toolkit exists for. Everything below is about making those tools fast, safe, and cheap. Our first version answered "what symbols exist in this repo?" by walking the tree and parsing every file on every request. On a 347-file project that was tens of seconds . On a real Ansible repo of 1,482 files it was worse — and agents ask for repo maps constantly every tool-call needs file → symbol → reference context . The fix came in three layers: Result: full index of 1,482 files in 9.7s 153 files/s ; repo map afterwards: 98ms. The original tree-sitter binding inside Node had two failure modes: a parse exception killed the entire MCP process on average every 2–4 hours of use , and GC pauses + per-node JS↔C crossings made batch indexing stall. The Rust daemon fixes both: catch unwind PARSE PANIC error code; the MCP server keeps running. Users forgive slow, never dead. worker threads startup costs.Human tools assume you have hands, eyes, and memory. An LLM has none. Three compensations: edit transaction is all-or-nothing; every write produces an undo journal. We tested kill -9 mid-write: the half-written transaction rolls back, source files untouched. suggestion next action — an executable recovery call the model reissues verbatim instead of guessing. workspace dir ; writes are version-anchored optimistic concurrency , so even if the model forgets the version it read, the write On the "silent corruption" point: while building with a default agent tool stack, one overwrite write silently lost 400+ lines — surfaced ~40 turns later, by luck. We stopped betting on "models will get better" and moved the safety into the tool layer. Errors aren't a dead end — they're an interface. Every failure carries a stable code, a human-readable suggestion, and a next action that is executable, not advice: { "error": { "code": "VERSION CONFLICT", "message": "base version mismatch: FILE CHANGED", "suggestion": "Re-read the file and regenerate the batch.", "next action": { "tool": "read symbol", "params": { "locator": { "file path": "src/api.js" } } } } } The model doesn't parse the suggestion and decide what to do — it reissues next action verbatim and recovers. Successful calls carry a next step the same way. Errors become signposts with navigation instead of dead ends. edit batch : tolerant matching, paranoid writing LLMs generate old string anchors with mistakes humans rarely make: collapsed double spaces, truncated line ends, curly quotes where the code has straight ones. A bare no match sends the model off to re-read the whole file — thousands of tokens per failed match. Matching degrades in four stages: exact → trailing-whitespace-stripped → edit-distance candidates similarity ≥ 0.5 → diagnostics whitespace visualized, 17 Unicode confusable pairs listed . A typical failure reads: "candidate at line 42, similarity 0.87 — you used curly quotes, the code has straight ones." Usually one retry fixes it. The write side is paranoid: symlink guards, unique temp file names, TOCTOU check between match and commit, rename retries for Windows AV file locks, post-write syntax check node --check / py compile / JSON.parse . security review , code review , sweep dead code are pure regex/AST — zero LLM calls . Same input, same output; CI-safe and auditable. We state the boundary explicitly: these tools do not cover control flow, data flow, or cross-module semantics. Zero findings ≠ safe; a high score ≠ healthy. A deterministic pattern scanner that admits its scope beats a "comprehensive security" claim every time. 30+ rounds of "LiuHe reviews LiuHe" — every bug found becomes a regression test. Real fixes from those rounds: a scope filter scanning outside its target directory, dead-code false positives on registration patterns, constant tracing missing read sites, SQL parameterization cleanup. Assertions grew every round: 2,013 JS + 92 Rust , full chain green. LiuHe 六合, "six harmonies" names the six design constraints applied to every tool in the toolkit: The three compensations earlier hands / eyes / memory are the user-facing summary; these six are the per-tool checklist behind them. The AST layer that enforces them is called Malong. All under a real docker --memory=512m cgroup: | Metric | Value | |---|---| repo map | 98ms was tens of seconds | | Full index | 1,482 files in 9.7s | | Concurrency | 128 concurrent / 256 in-flight, zero OOM | | Peak RSS | 134MB ~26% of limit | | Throughput | ~588 calls/s 60–600× realistic agent demand | | Hot-file storm | 32-way read/write mix, zero torn writes, integrity check PASS, 95/95 conflicts rejected as FILE LOCKED | | Token savings | ↓65.3% 7,673 → 2,662 est. on same task | Honest boundary: throughput doesn't scale with concurrency — better-sqlite3 's synchronous queries serialize on the Node event loop. We evaluated worker threads and decided the risk wasn't worth the gain. 588 calls/s is already overkill. Where the token savings come from: tiered tool-description compression 44 tools ≈ 1.33k tokens — core tools keep full descriptions, low-frequency ones shrink to ≤70 chars, verbose ones ≤230, with the detail deferred to next step hints , incremental returns with explicit pagination instead of dumping everything, and batch endpoints read symbols , write symbols that cut round-trips. Same task: 7,673 → 2,662 estimated tokens ↓65.3% and 6 calls → 3 ↓50% . All benchmarks are reproducible from benchmarks/ and tests/ in the repo concurrency correctness: tests/test-mvp-concurrency.js . We also shipped first-day support for DeepSeek Harness dsh web — one line to register, all 44 tools exposed as malong with workspace dir auto-filled from the conversation's workspace: dsh plugin --profile web add @jieai/dsh-malong-bridge Full guide in the repo: malong/dsh/DSH-INTEGRATION.md . Zero-build deploy no cargo, no npm install : git clone https://github.com/wulun811/LiuHe liuhe && cd liuhe/malong mkdir -p ~/.local/bin tar -xzf ../releases/malong-liuhe-0.4.6-linux-x86 64.tar.gz cp malong-parse/target/release/malong-parse ~/.local/bin malong-parse & start the parse daemon node --max-old-space-size=512 --expose-gc mcp-server.js --workspace /path/to/project If better-sqlite3 is unavailable, it falls back to vendored sql.js WASM — no install, no compile, no network. Skepticism welcome — all numbers above are self-measured and reproducible. If this resonates with an agent failure you've had, try it, break it, and tell us where we're wrong — the repo is wulun811/LiuHe https://github.com/wulun811/LiuHe .