What actually moved to Python — and the number I got wrong
An honest capstone on the ADR-020 brain migration: which semantic logic moved to a local Python sidecar, what stayed, and a correction — the audit's headline '~26 sites to convert' was over-counted. The real number is 4 live, ~4-7 worth doing.
The shape of it
Dalkkak's app is ~31k lines of Rust + ~88k of TypeScript. The "AI" that's actually ours — not the
BYO Claude that does generation — is a 272-line Python sidecar (apps/ai-brain,
sentence-transformers, localhost JSON). It does one thing: turn free text into a semantic decision
(rank / classify). That's it. The terminal, git, PTY, UI, MCP wiring — all stay Rust/TS, and should.
What moved, and where it is in the code:
- Search ranking —
lib.rs:951(graph_query) now calls the brain's/rankinstead ofgraph_query_score. The old keyword scorer is still there, as the fallback. - Work-item context —
lib.rs:1159(workitem_context), same pattern;context_scoreis the fallback, and the graph-edge boost stays as a structural signal. - Email intent —
SellArea→classifyFewShot;classifyByRules(the keyword rules) is the fallback atpack.ts:198. - Import KIND —
ImportCenter→proposeFromItemsFewShot;proposeFromItem(regex) is the fallback.
Nothing was deleted or commented out. The structure everywhere is brain first, old keyword path as the safety net — if the sidecar is down or unsure, the app behaves exactly as before. The brain auto-spawns on launch (a bug earlier: it was registered but never actually called — so it never ran; fixed).
The number I got wrong
Earlier I said, with confidence, that an audit had found 26 sites to move to Python, "1순위 11", cross-validated by two independent passes, "신뢰도 최상." I presented 26 as the migration target.
That was wrong, and I'll own it plainly.
Both audit passes asked "is there keyword/regex code here doing something semantic-looking?" and
counted yes-answers as "should be ML." But that test over-includes. inferTaskId("worker-3/x.json")
is a regex — and it's path parsing, not ML. A secret-redaction gate is keyword matching — and it must
stay deterministic, because a model that's 97% right leaks 3% of secrets. Two commandMatch /
scoreWorkflowMatch "classifiers" have no caller at all — dead code.
When I went site by site (the catalog: docs/product/adr-020-site-catalog.md), ~12 of the 26 were
format/path parsers or security gates that belong in Rust/TS, and several more were dead or structural.
The honest count: ~4-7 worth converting, of which 4 are done and live (the two rankers + the two
text-classification entry points). Not 26.
Two auditors agreeing didn't make it right — they made the same category error. The real test isn't "does semantic-looking string code exist," it's "would embeddings actually beat this, and is there even a consumer." I should have led with that.
What stays, on purpose
Security/redaction gates, approval gates, explicit enum→enum maps, RFC email threading, file-path/ID parsing, the cosine engine that's already vector-based, and closed-set intent vocabularies (a 14-word recency list). Moving any of these to a model makes them slower and less trustworthy. "Move the AI to Python" never meant "rewrite the app" — it meant move the handful of genuine semantic judgments, behind a clean boundary, and keep the deterministic spine.
The honest ledger
- Done + live: 4 sites (ranking ×2, classification ×2), each with a keyword fallback. Verified on the
installed build (
/classify_fewshotclassifying keyword-miss paraphrases the old rules would drop). - Corrected: the "26" claim, in this post and in the brain's own README/docstring.
- Still open: the English-only UI sweep (separate from this), and whatever the next real semantic need turns out to be — measured per site, not by a headline count.