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 workLiuHe (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 callstest_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 s + 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.