Skip to content

Releases: townsendmerino/ken

v1.3.1

Choose a tag to compare

@github-actions github-actions released this 27 Jul 03:06

v1.3.1 — Cold-start hardening

A stability and correctness release. v1.3.0 shipped the cold-start campaign
(snapshot persistence, serve-before-warm staging, embed cache); v1.3.1 is that
campaign hardened by five independent re-audit rounds, each one auditing the
previous round's fixes. 44 commits, all bug-fix / perf / test — no new
features, no breaking changes.
Every fix landed on main under full CI
(build · vet · gofmt · golangci-lint · go test -race · Windows smoke · DB
integration on Postgres/MySQL/MariaDB/SQLite), green across all five rounds.

Upgrading from v1.3.0 is a drop-in. Most fixes are in the long-lived ken-mcp
watch/staging paths, so servers that stay up across file changes and restarts
benefit most.

Highlights

  • No more staged-boot crash. A KEN_MCP_STAGED=1 server that loaded a
    snapshot on its second start could panic during startup (vectors/chunks fell
    out of alignment). Fixed, with a defensive path that degrades to BM25 instead
    of ever crashing on a misaligned corpus.
  • Search results are the source, not a synthetic line. A pre-existing
    .ken/index.bin built before v1.3.0's enrichment-label change could serve a
    fabricated # func: … first line to the agent as if it were real code. The
    on-disk format version is now bumped so those stale prebuilts are rejected and
    rebuilt.
  • Correct semantic ranking with a database configured. In the narrow
    staged-warmup window with KEN_DB_DSN set, find_related / semantic search
    could rank filesystem chunks by database vectors. Now detected and downgraded
    to BM25 until the real vectors land.
  • Snapshots actually re-persist. A staged server that reconciled a snapshot
    on boot never re-wrote it, so drift grew every restart until it forced the full
    rebuild snapshots exist to avoid. Fixed.
  • Crash-durable writes. Snapshot, prebuilt-index, rerank-cache, and
    downloaded-model writes now fsync the file before rename and the parent
    directory after — a power loss can no longer publish a torn file that passes
    its in-memory checks but fails to load.
  • Honest database errors and tool surface. reindex_db no longer appears on
    filesystem-only servers, and when a DB operation fails the full error is
    actually written to the server log the sanitized message tells you to check
    (credentials never leak to the model).

By area

Watch / incremental indexing

  • Fixed a debounce busy-spin that could peg a core during a slow flush.
  • A stray delete of a never-indexed file (an editor .swp, .orig, .DS_Store)
    no longer triggers a full rebuild + snapshot rewrite.
  • A directory renamed during an in-flight flush now correctly drops its stale
    chunks instead of orphaning them.
  • Recovery from a dropped-event (inotify overflow) storm now reconciles deletions
    against the authoritative in-memory corpus, catching files it previously missed.
  • Enrichment labels no longer double on each restart; the label now uses an
    unambiguous # ken: sentinel.

Cold start (v1.3.0 campaign)

  • Staged embedding, snapshot reconcile, and the embed cache all hardened per the
    fixes above; snapshot config keys now version and cover lazy/staged modes.

MCP server

  • reindex_db registered only when a database is actually configured.
  • Structural-index background builds are cancelled when their repo is evicted
    from the cache (no more parse goroutine walking a deleted temp clone).
  • Outline/symbols responses signal when results were truncated (overshot).

CLI / distribution

  • ken build-index output is world-readable again (0644), so a prebuilt
    .ken/index.bin baked into a container image loads under a non-root uid
    instead of silently falling back to a full cold build.

Performance

  • Index serialization holds ~2× less peak memory on large corpora.
  • Token hashing on the watch path is now zero-allocation.

Process note

The residual risk after five rounds isn't in the code — it's that the audit was
performed in a sandbox that couldn't compile the project. That gap is already
closed on main: CI builds and runs the full race-enabled suite (plus a live
database matrix) on every push, and it is green on this release.


Full commit changelog (auto-generated)

Changelog

Fixes

  • 1a3cd39: fix(db): bound startup introspection with KEN_DB_STARTUP_TIMEOUT (audit db/mcp §3) (@townsendmerino)
  • 7045bcd: fix(db): introspection determinism + cleanup (audit db/mcp §12, §15, §17, §25, §29) (@townsendmerino)
  • 1957fd0: fix(db): sampler robustness — pool churn, BLOBs, FK PKs, panics, timeouts (audit db/mcp §10,§13,§14,§26,§27,§28) (@townsendmerino)
  • 4b58a10: fix(ken-mcp): Tier-2 DB chunks survive default-repo LRU eviction (audit db/mcp §1) (@townsendmerino)
  • 9fc85c4: fix(lint): enable rowserrcheck + sqlclosecheck; fix the 6 leaks they caught (audit db/mcp §20) (@townsendmerino)
  • 1479661: fix(mcp): embedded find_related error mode, MCP rerank validation, per-clone byte cap (audit db/mcp §21, §22, §24) (@townsendmerino)
  • 656de7c: fix(mcp): single-pass outline + error survives bad output mode (audit db/mcp §16, §23) (@townsendmerino)
  • f295e4d: fix(mcp): strip Arm B enrichment label from search results (audit search §10) (@townsendmerino)
  • 034fadd: fix(mcp): structural build honors ctx, heartbeats, and retries on failure (audit db/mcp §8) (@townsendmerino)
  • f47a850: fix(mcp): tree-diff recently_changed + honor ctx; cap outline/symbols dumps (audit §5, §6) (@townsendmerino)
  • e8638e4: fix(pull-forward): db#4 allowlist, §21 vecs peak, §27 fsync, R14 comment (round-3 leftovers) (@townsendmerino)
  • f4e210c: fix(regressions): N1 staged panic, N2 orphaned dir-rename chunks, N8 debounce bypass; make the slow-flush test bite (round-3 re-audit) (@townsendmerino)
  • f9afc35: fix(regressions): N3 snapshot key/lock, N4 label sentinel (round-3 re-audit) (@townsendmerino)
  • 3bcffb9: fix(regressions): N5 zero-alloc token hash, N6 purge retry, N7 structural-build lifecycle (round-3 re-audit) (@townsendmerino)
  • 59c714b: fix(regressions): N9 overshoot signal in JSON responses (round-3 re-audit) (@townsendmerino)
  • 36f6945: fix(regressions): R1 busy-spin, R2 label doubling, R3 structural never-converges, R4 bogus row count, R5 label strip (2026-07-26 re-audit) (@townsendmerino)
  • 388d935: fix(regressions): R10 usage rotation recovery, R11 token-cache hash key (2026-07-26 re-audit) (@townsendmerino)
  • 7d35d3e: fix(regressions): R12-R15 + leftover watcher/credential holes (2026-07-26 re-audit) (@townsendmerino)
  • ca580ee: fix(regressions): R4-1 staged-boot panic, R4-3 no-op flush, R4-6 resync — round-4 re-audit (@townsendmerino)
  • 730d1d9: fix(regressions): R4-2 bump KEN1 serializeFormatVersion to 2 (round-4 re-audit) (@townsendmerino)
  • 08f3549: fix(regressions): R4-4 route LRU eviction through reapEntry (round-4 re-audit) (@townsendmerino)
  • 72384b5: fix(regressions): R4-5 reindex diagnostic logging, R4-6 remaining fsync paths (round-4 re-audit) (@townsendmerino)
  • a93168b: fix(regressions): R5-1 merged-extras vecs/chunks invariant + round-5 test guards (search) (@townsendmerino)
  • e9ea01e: fix(regressions): R5-2 guard typed-nil DBIntegration + log full reindex_db error (@townsendmerino)
  • 3e06867: fix(regressions): R5-3 gate reconcile-boot snapshot persist on wi.Warming() (@townsendmerino)
  • 4aed5e9: fix(regressions): R5-4 restore 0644 on ken build-index output (@townsendmerino)
  • d10a28d: fix(regressions): R6 staged-embed vecs misalign, R7 purge repopulates stale bundle (2026-07-26 re-audit) (@townsendmerino)
  • 4e49b7f: fix(regressions): R8 event loop no longer blocks on corpusMu, R9 gitignore-gate new dirs (2026-07-26 re-audit) (@townsendmerino)
  • ae2e3ee: fix(repo): gitignore ** respects path-component boundaries (audit search §16) (@townsendmerino)
  • 60e1370: fix(search): copy-on-write in reconcileCorpusLocked — kill the flush data race (audit §1) (@townsendmerino)
  • 9bdecab: fix(search): tombstone over-fetch, no-panic extras, synced lazy reads, misc Lows (audit §11,§23,§24,§25,§26,§29,§30) (@townsendmerino)
  • 9aca434: fix(search): unique temp name for rerank-cache atomic write (audit search §27) (@townsendmerino)
  • 17d09d2: fix(security): close three credential/topology leaks (audit db/mcp #2, #4, #7) (@townsendmerino)
  • 3aa62a2: fix(structural): case-insensitive extension lookup + correct determinism comment (audit search §18, §28) (@townsendmerino)
  • 38366ad: fix(usage): local-tz day boundary + cached append handle (audit db/mcp §9, §19) (@townsendmerino)
  • 71bd780: fix(watch): deterministic flush order, size re-check, debounce ceiling (audit search §13, §15, §17) (@townsendmerino)
  • 9cbacf3: fix(watch): index new/moved-in dirs, re-path renamed dirs (audit §2, §14) ...
Read more

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 03:02

ken v1.3.0 — cold-start campaign

ken-mcp no longer rebuilds the index from scratch on every launch.

Warm search in ken was always fast (tens of ms). The rough edge was cold
start — every IDE restart re-walked, re-chunked, re-parsed, and re-embedded the
whole repo before serving a single query. This release makes cold start a
one-time cost, not a per-launch tax, and adds opt-in modes that serve a first
query almost immediately on a true-cold index.

Motivated by #61.


The headline (on by default)

Persistent index snapshots + reconcile-on-boot. ken-mcp now persists the
built index to <repo>/.ken/ and, on restart, loads it and drift-scans
(config-key + per-file mtime/size) instead of rebuilding when the repo hasn't
changed. Edit a few files and restart? It reconciles only the changed files
rather than the whole tree.

Time to a servable first query — yii2 PHP corpus (~12k chunks), hybrid, M1 Pro, median of 3:

scenario before after speedup
restart, repo unchanged 2.39 s 573 ms 4.2×
restart after a 1-file edit 2.39 s 746 ms 3.2×

It's safe by construction: any missing / corrupt / config-mismatched / drifted
snapshot silently falls back to a normal live build, and both on-disk loaders
are fuzzed against hostile input. .ken/ is a plain cache — delete it anytime;
add it to your .gitignore. Turn the whole thing off with KEN_MCP_SNAPSHOT=0.
(This is separate from, and coexists with, the ADR-024 operator prebuilt
.ken/index.bin.)

Serve-before-warm (opt-in)

For the first-ever index of a repo — where there's no snapshot to load yet —
three opt-in knobs move the expensive work off the first query:

env var what it does
KEN_MCP_STAGED=1 Serve BM25 lexical results instantly on a cold hybrid build (~4.2× faster first query), then embed to full hybrid in the background. Responses carry "semantic":"warming" until the upgrade lands.
KEN_MCP_LAZY_ENRICH=1 Defer the structural-enrichment tree-sitter parse off the cold path (serve raw, enrich in the background).
KEN_MCP_EMBED_CACHE=1 Persistent sha256(chunk)→vector cache at <repo>/.ken/embed.db, so a full rebuild re-embeds only never-seen text.

Also new:

  • ken index --write-snapshot — build once and persist, so a later
    ken-mcp launch on that repo loads instead of rebuilds (CI prewarming).
  • Lazy structural indexken-mcp defers the symbol index for the
    definition/references/callers/outline/symbols tools to first use,
    removing a redundant full-corpus parse from every cold start.
  • KEN_ENRICH_FILE_BUDGET_MS — a per-file wall-clock parse budget that
    skips (and logs) a pathological template-like file the size cap misses.

Honest notes

  • All numbers above are M1 Pro (10-core / 16 GB). The speedups should port,
    but absolute times won't hold on a 4-core laptop — so the serve-before-warm
    modes ship off by default until we've measured them on that hardware
    class. If you run ken on a constrained machine, try KEN_MCP_STAGED=1 and let
    us know how it lands.
  • Why "take the parse off the cold path" rather than "make it faster": the
    gotreesitter 0.20.5 → 0.47.0 bump we shipped in 1.1.1 (a correctness fix)
    regressed the PHP tree-sitter parse ~2.7×, and that parse is ~50% of cold
    index time. Details + the upstream data point are in the repo docs.
  • Index throughput and warm-search latency are unchanged — the opt-in modes are
    off by default, and the default index path is untouched.

Install / upgrade

brew upgrade ken            # Homebrew
scoop update ken            # Scoop
go install github.com/townsendmerino/ken/cmd/ken@v1.3.0
go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.3.0

No config changes needed — snapshots turn on automatically. Full details in
CHANGELOG.md.

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 24 Jul 14:01

ken v1.2.1 — memory campaign + security & correctness hardening

The biggest release since 1.0. Three strands came together: a memory campaign that gives you real control over what ken indexes and how much RAM it holds, a full security & correctness review (two process-kill bugs and seven majors, each fixed with a regression test), and bounded, cancellable shutdown.

No breaking changes. Every public 1.0 signature is unchanged; new API and env vars are purely additive. Upgrading is a drop-in — no config edits required.

🔐 Security note: 1.2.1 is a same-day security fast-follow to 1.2.0. It bumps golang.org/x/text to v0.39.0 to fix GO-2026-5970 (an infinite loop on invalid input), which govulncheck flagged as reachable via the Postgres connect path and Unicode normalization. The advisory landed after 1.2.0 was tagged, so 1.2.0's binaries shipped the vulnerable version — use 1.2.1. Everything else below is the 1.2.0 feature set, unchanged.


🗂️ .kenignore — control what ken indexes

Your repo commits things you don't want searched: built JS/CSS bundles, generated migrations, vendored code. ken was gitignore-only, so on artifact-heavy monorepos it indexed roughly 2× the files it should have — inflating cold-start time and memory.

Drop a .kenignore (gitignore syntax, nested per-directory) to fix that:

web/assets/          # compiled bundles
vendor/              # third-party code
**/migrations/*.php  # generated migrations
*.min.js
  • Union with .gitignore — excluded if either matches; evaluated independently so a !negation in one can't re-include what the other dropped.
  • .sembleignore honored as a fallback — a drop-in for anyone migrating from semble.
  • Default-on and inert without the file — your existing .gitignore behavior is untouched.
  • Even without one, ken now auto-skips oversized files (KEN_MAX_FILE_BYTES) and minified/generated files (KEN_MAX_AVG_LINE_BYTES).

On a committed-artifact monorepo this is the single biggest lever on index size, cold start, and RAM.

🧠 Lower, more predictable memory

For the long-lived ken-mcp server: GOGC=50 by default, an opt-in KEN_MEMLIMIT soft cap, and freed pages returned to the OS after builds and watch flushes — so idle RSS settles near the live index size instead of the build high-water. KEN_MAX_FILES rejects a hostile "millions of tiny files" repo rather than letting it OOM. A new scripts/rss_bench.sh measures it the same way external benchmarks do.

🔒 Security & correctness review

Every finding fixed and regression-tested:

  • top_k could OOM-crash the whole server (Critical). An unbounded top_k reached a giant allocation (and a filter path could overflow into a panic). Now clamped.
  • One source file could fatal-crash the server (Critical). The structural indexer parsed files with none of the size/stop-reason guards its sibling path had, so a pathological file could uncatchably overflow the goroutine stack. Both paths now share one guarded parser.
  • DB passwords no longer leak in a malformed-DSN error (MySQL + Postgres), which also fixes passwords containing / @ ?.
  • Symlink-escape closed on the watch path — an out-of-root symlink can no longer be indexed.
  • Plus: shutdown cleanup on stdin-EOF, cache eviction moved off the hot lock, watch-path enrichment parity, a lock-free-reader race in the reranker, and a batch of determinism/robustness fixes.

🔐 Verified model downloads

ken download-model and the background auto-fetch now verify each file as it streams — SHA-256 against HuggingFace's git-lfs ETag, and byte count against Content-Length / X-Linked-Size — so a truncated or swapped download is rejected before it lands. Concurrent fetches use a per-process temp file.

🛑 Graceful shutdown that can't hang

In-flight tool calls drain within KEN_MCP_SHUTDOWN_GRACE (default 5s), a second Ctrl-C force-quits, and a large in-flight index build now honors cancellation instead of blocking shutdown until it finishes.


New environment variables

Variable Default Purpose
KEN_MAX_FILE_BYTES 2MiB Per-file size cap for indexing
KEN_MAX_AVG_LINE_BYTES 1000 Skip minified/generated files (0 disables)
KEN_MAX_FILES 1000000 Admission cap — reject huge/hostile repos (0 = unlimited)
KEN_MEMLIMIT (unset) Soft memory limit (overrides GOMEMLIMIT)
KEN_MCP_SHUTDOWN_GRACE 5s Bounded in-flight drain window on SIGINT/SIGTERM

Install / upgrade

# Homebrew (macOS/Linux)
brew upgrade --cask ken     # new: brew install --cask townsendmerino/tap/ken
# Windows (Scoop)
scoop update ken            # new: scoop bucket add townsendmerino https://github.com/townsendmerino/scoop-bucket; scoop install ken
# Or with Go
go install github.com/townsendmerino/ken/cmd/ken@v1.2.1
go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.2.1

Nothing to change on upgrade. If you're on an artifact-heavy repo, adding a .kenignore is the one thing worth doing.

Full changelog: CHANGELOG.md · compare v1.1.1...v1.2.1

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 24 Jul 13:55

⚠️ ken v1.2.0 — superseded by v1.2.1 (security)

Use v1.2.1 instead.

v1.2.0's binaries shipped with golang.org/x/text v0.37.0, which is affected by GO-2026-5970 — an infinite loop on invalid input, reachable via ken's Postgres-connect and Unicode-normalization paths. The advisory was published to the Go vulnerability database shortly after v1.2.0 was tagged.

v1.2.1 is an identical release with x/text bumped to v0.39.0 — same features, fixed dependency.

The full feature notes for this release (the .kenignore memory campaign, the security & correctness review, verified model downloads, and bounded shutdown) are on the v1.2.1 release page.

v1.1.1

Choose a tag to compare

@github-actions github-actions released this 16 Jul 14:57

ken v1.1.1 — crypto/tls CVE fix + dependency updates

A patch release. It closes a reachable standard-library TLS vulnerability and folds in routine dependency updates. No API, wire-format, or default changes from 1.1.0 — a drop-in upgrade.

🔒 Security

  • Toolchain go 1.26.4 → 1.26.5 fixes GO-2026-5856 — an Encrypted Client Hello privacy leak in the standard library's crypto/tls. ken's govulncheck CI gate (added in 1.1.0) flagged it as reachable, with real call paths through the MySQL introspection TLS handshake and the model-fetch HTTPS client. The fix is a single go-directive bump in go.mod, which propagates to every CI job (all read go-version-file: go.mod); govulncheck ./... now reports 0 affecting vulnerabilities. No source changes.

📦 Dependencies

No ken API changes; the full test suite and govulncheck are green on the combined tree.

  • github.com/townsendmerino/aikit v1.5.0 → v1.9.0 — the extracted algorithm packages (BM25 / embed / ann / encoder / chunk / fuse / topk).
  • modernc.org/sqlite v1.52.0 → v1.53.0 — pure-Go SQLite driver (Tier-2 DB indexing).
  • github.com/odvcencio/gotreesitter v0.20.2 → v0.20.5 — pure-Go tree-sitter (treesitter chunker + Arm B enrichment).
  • actions/checkout v6 → v7 (CI).

Install / upgrade

# Homebrew (macOS/Linux) — ken ships as a cask
brew upgrade --cask ken     # new install: brew install --cask townsendmerino/tap/ken
# Windows (Scoop)
scoop update ken            # new install: scoop bucket add townsendmerino https://github.com/townsendmerino/scoop-bucket; scoop install ken
# Or with Go
go install github.com/townsendmerino/ken/cmd/ken@v1.1.1
go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.1.1

No config changes required. Upgrading is recommended for the TLS CVE fix if you use ken-mcp's remote-clone or MySQL Tier-2 introspection paths.

Full changelog: CHANGELOG.md · compare v1.1.0...v1.1.1

ken v1.1.0 — security hardening + int8 reranker by default

Choose a tag to compare

@github-actions github-actions released this 12 Jun 01:34

A feature-and-hardening release. The int8 reranker becomes the default now that it's free, output: "json" reaches the last tool, the remote-clone path closes its two known SSRF gaps, and a govulncheck gate joins CI.

No breaking changes. mcp.Run / mcp.NewServer / the chunk.Chunker interface and the MCP wire format are unchanged from 1.0. The int8 default and the new KEN_MAX_CLONE_BYTES knob are both backward-compatible — existing configs keep working.

Highlights

⚡ int8 reranker is now the default

aikit v1.5.0 fixed the q8 path, so int8 now matches f32 reranker latency (50-doc cold: 7.35 s vs 7.75 s on arm64) at ~21× less runtime memory (18 MiB vs 379 MiB) and ¼ the weight footprint (~140 MB resident vs ~547 MB) — with cosine 0.997 vs f32, unchanged ranking quality. KEN_MCP_RERANK_QUANT and --rerank-quant now default to int8; pass f32 for the full-precision path. No re-download needed — LoadQ8 quantizes your existing CodeRankEmbed snapshot in-process.

This reverses the "int8 is slower on Apple Silicon" note from 1.0.1 — that was the pre-fix aikit path.

🧩 All nine MCP tools now speak JSON

recently_changed was the last markdown-only tool; it now accepts output: "json" and returns a typed RecentlyChangedResponse (considered-count + per-commit hash / short_hash / subject / author / RFC3339 timestamp / changed_files), built from the same rows as the markdown render so the two can't drift.

🔒 Security

  • Clone SSRF guard hardened + a byte cap. ken-mcp's remote-repo clone now re-validates the resolved IP at connect time and dials it literally (TLS still verifies the hostname via SNI) — closing the DNS-rebinding TOCTOU the pre-flight check couldn't catch, including redirects to internal hosts. It also byte-caps the clone stream (KEN_MAX_CLONE_BYTES, default 2 GiB) so a hostile server can't stream an unbounded/pathological pack. Both checks honor KEN_ALLOW_PRIVATE_CLONE_TARGETS=1.
  • govulncheck CI gate. Go's symbol-level vulnerability scanner now runs on every push. Bringing it green fixed 9 reachable CVEs (bumped golang.org/x/crypto to v0.52.0 and the toolchain to go 1.26.4). The gate blocks any reachable-CVE regression from landing.

🐛 Fixed

  • Data race in the definition-pattern cache. internal/search's defPatternCache (the compiled-regex memo behind the definition tool) was read and written unsynchronized; concurrent queries could race it. Now guarded by an RWMutex, with a race-detector regression test.

📚 Docs & tooling

New top-level CONTRIBUTING.md and SECURITY.md, a Makefile, a "Compared to other agent code-search tools" table in the README, and a "Personally-namespaced dependencies" section in DEVELOPERS.md documenting the gotreesitter dependency delta and the go.work local-aikit workflow.

Install / upgrade

# Homebrew (macOS/Linux) — ken ships as a cask
brew upgrade --cask ken     # new install: brew install --cask townsendmerino/tap/ken
# Windows (Scoop)
scoop update ken            # new install: scoop bucket add townsendmerino https://github.com/townsendmerino/scoop-bucket; scoop install ken
# Or with Go
go install github.com/townsendmerino/ken/cmd/ken@v1.1.0
go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.1.0

No config changes are required to upgrade. If you run the reranker (KEN_MCP_RERANK=on) and want to keep the full-precision path, set KEN_MCP_RERANK_QUANT=f32.

Full changelog: CHANGELOG.md · compare v1.0.1...v1.1.0

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 11 Jun 17:27

ken v1.0.1 — faster hybrid search

A patch release: same retrieval quality, ~3× faster hybrid search. No API changes — mcp.Run, mcp.NewServer, mcp.NewCache, FormatResults, the chunk.Chunker interface, and the MCP wire format are unchanged from 1.0.0, so this is a drop-in upgrade.

~3× faster hybrid search (measured)

The headline comes from a aikit bump (v1.0.0 → v1.4.0) that rewrote the semantic-arm cosine scan (ann.Flat.Query) from a scalar-float64 loop to a SIMD-float32 dot kernel with 8-vectors-per-pass streaming. Benchmarked before/after on the same machine:

  • Flat.Query 11.7× faster in isolation (2061 µs → 176 µs over 8 000×256).
  • End-to-end hybrid search p50 −66 % — 4.58 ms → 1.56 ms on a ~13 k-chunk corpus. The scan is O(N) in chunks, so the win grows with corpus size (it's invisible on tiny repos where search is already sub-millisecond, large on real ones).
  • Indexing time unchanged — the bump didn't touch the index path.

Quality is provably untouched: recall@10 was re-verified identical (0.967 NL / 0.995 symbol), and embedding parity holds (golden cosine ≥ 1 − 1e-5 vs the Python reference). The neural reranker is unchanged too (the encoder vectorization in this aikit range targets the int8 path; ken's default reranker runs f32).

Also in this release

  • Deserializer fuzzing. FuzzDeserializeIndex and FuzzDecodeRerankCache validate the hand-written adversarial-input defenses in the KEN1 / KNRC binary parsers — the index parser is real attack surface, since ken-mcp auto-loads <repo>/.ken/index.bin from shallow-cloned remote repos. 2.6M executions, zero crashers.
  • Model-download fix. ken download-model no longer mistakes a leftover Git-LFS / HF-hub pointer stub (or a broken symlink / truncated file) for a real model. The "already present" check now validates file size, so a stubbed model gets re-downloaded instead of silently failing to load later.
  • Robustness from the aikit bump on exactly the paths ken runs: a safetensors mmap-lifetime guardrail and two fuzz-fixed untrusted-tensor crashes in embed, plus bm25 / chunk indexing-pipeline fuzzing.
  • Docs accuracy sweep. A full pass aligned the documentation with the post-1.0 / post-extraction reality (package locations, the 10-tool MCP surface, 13 languages, the mcp.Run library example, regenerated third-party licenses, and a current-state ARCHITECTURE.md).

Upgrade

brew upgrade --cask ken      # or: scoop update ken

Or grab a binary below, or go install github.com/townsendmerino/ken/cmd/ken-mcp@v1.0.1. Full detail in CHANGELOG.md; benchmark methodology in docs/PERF-expectations.md.

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 06 Jun 23:58

ken v1.0.0 — code search for agents, in one binary

ken is a hybrid code-search engine for AI agents: BM25 lexical + Model2Vec semantic embeddings + reciprocal-rank fusion + a code-aware ranking pipeline, served over MCP. It is a faithful Go port of MinishLab/semble — the retrieval algorithm is ported verbatim and validated against the Python reference — packaged as a single static, pure-Go, no-cgo binary: no Python, no GPU, no vector DB, no API keys, air-gapped friendly.

1.0 means two things: the public API surface is frozen (mcp.Run, mcp.NewServer, mcp.NewCache, FormatResults, the chunk.Chunker interface, and the typed JSON-output structs), and the retrieval quality is measured, reproducible, and at its ceiling for this corpus class — every headline number below has an in-tree reproduction harness.

The numbers (all reproducible — see docs/BENCH.md)

  • ~97% recall@10 in the default (hybrid) mode — 0.967 NL / 0.995 symbol on semble's 1,251-query, 63-repo benchmark — at ~46× fewer agent tokens than grep + Read for NL queries. grep still wins exhaustive enumeration (~99.9%); ken's job is "find the chunk that answers this," at 1–2 orders of magnitude lower token cost.
  • NDCG@10 0.842 vs semble's published 0.854 — the verbatim-port validation, with the semantic-only arms matching within 0.003.
  • Opt-in neural reranker: +0.165 NDCG@10 on CoIR — a CodeRankEmbed transformer running entirely in pure Go (hand-written ARM64 NEON assembly, optional int8 quantization, disk-persisted embedding cache).
  • At 280K-file corpus scale, grep+Read needs ~16M tokens per NL query (past any context window); ken answers in ~1,300.

Highlights

Zero-setup onboarding — new installs land on the good path

ken-mcp now auto-fetches the embedding model (~60 MB) in the background on first run: it serves BM25 immediately, then transparently upgrades to hybrid (~0.83 → ~0.97 recall@10) when the model lands. No flags, no setup step, stderr-only progress — the JSON-RPC stream stays clean. (KEN_MCP_AUTO_FETCH=0 restores warn-and-downgrade.)

Structural navigation — nine MCP tools, 13 languages

Beyond ranked search (search, find_related), ken builds a structural index from its tree-sitter parse and answers structural questions exactly, by lookup: definition, references, callers, outline, symbols — plus status and recently_changed. Honest framing baked into the tool descriptions: name-resolved, not type-resolved (for type-level call hierarchy, use an LSP). The callers edge data ships with a published precision sample (100% on 400 edges across 8 languages). Languages: Python, Go, TypeScript, JavaScript, Java, Rust, C, C++, C#, PHP, Ruby, Kotlin, Dart.

Retrieval quality, measured the hard way

  • Structural enrichment (default-on): every chunk from a supported language is prefixed with an AST-derived label (# func: NAME | calls: A, B | raises: X) before indexing — +0.02 NDCG@10 on leak-free benchmarks, pure Go, no model, KEN_ENRICH=off to disable.
  • The recall numbers come from a decomposition harness (internal/search/recall_decomp_test.go) that separates candidate-generation loss from ranking loss — the same harness that caught our own docs misreporting the BM25-only fallback (~0.84) as ken's recall. The corrected, reproducible story is in docs/BENCH.md.

An index that keeps up

  • Watch mode by default: ken index stays alive and re-publishes an atomic snapshot ~2s after any file change; ken-mcp always watches, so agents see their own edits.
  • Pre-built indices: serialize an index into your binary (mcp.Run + //go:embed) for instant-start embedded corpora; ken-mcp auto-loads <repo>/.ken/index.bin.
  • Database schema indexing: point ken at Postgres/MySQL/MariaDB/SQLite and search your schema + migration history alongside code (reindex_db).

Drop-in for semble

Same search / find_related tool schemas, same markdown wire format. If your agent already uses semble, swap the command: path and nothing else changes.

Runs everywhere

macOS, Linux, Windows (amd64 + arm64). brew install --cask townsendmerino/tap/ken, scoop install ken, direct download, or go install. Single static binaries; slim builds ~23 MB (ken) / ~40 MB (ken-mcp).

Try it in 60 seconds

brew install --cask townsendmerino/tap/ken   # or scoop / download / go install
claude mcp add ken -s user -- $(which ken-mcp)
# ask your agent: "where do we handle retry backoff?" — ken auto-fetches the model on first run

Or try the flagship demo — the Go standard library, pre-indexed, model baked in, zero config: ken-demo-go-stdlib. Every Go developer knows this corpus by heart — verify the answers yourself, then reproduce against your own $GOROOT/src in 30 seconds (demos/go-stdlib/README.md). Supporting demos: kubernetes (90K chunks, scale) and postgres (treesitter on C).

Honest limits

  • Exhaustive enumeration belongs to grep. Refactors and pre-rename audits need 100% literal recall; ken optimizes relevance per token, not completeness.
  • Structural tools are tree-sitter-grade. Name resolution, ranked candidates — not a compiler. Same-spelled symbols collapse; type-level questions need an LSP.
  • The neural reranker is opt-in (--mode=hybrid-rerank / KEN_MCP_RERANK=on): it buys NDCG at real latency cost on first contact with a repo (the embedding cache persists to disk and is fast thereafter).

Provenance & thanks

ken stands on semble and the potion-code-16M model by MinishLab, and CodeRankEmbed by Nomic AI / CoRNStack for the reranker. Tree-sitter parsing via gotreesitter; algorithm packages live in aikit (pinned at its own 1.0). Everything runs in pure Go — including the transformer.

Full change history: CHANGELOG.md. Benchmarks and methodology: docs/BENCH.md.

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 06 Jun 22:58

Changelog

Features

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 06 Jun 15:49

v0.10.0 — C# · automatic model onboarding · Windows

Three headline additions — plus a measurement correction that's the most important thing in this release.

Highlights

  • 🪟 Windows binaries. ken and ken-mcp now ship for Windows (amd64 + arm64) alongside macOS and Linux — pure-Go single static binaries, .zip archives.
  • ⬇️ ken-mcp auto-fetches the embedding model on first run. A fresh ken-mcp with no model now downloads it in the background (~60 MB, pure-Go, no Python) and upgrades to hybrid automatically — new installs land on the high-recall path with zero setup. KEN_MCP_AUTO_FETCH=0 to opt out. (ADR-037)
  • #️⃣ C# is the 13th language. gotreesitter v0.20.2 bounded the namespace-recovery recursion that used to OOM ken's indexer on real C#, so .cs files now get full structural extraction + Arm B enrichment.

The measurement correction (read this one)

Auditing recall for 1.0, we found the widely-quoted "82–91% recall@10" was the BM25-only fallback — the mode ken runs in when no embedding model is installed. The shipped default (hybrid) mode measures ~97% recall@10 (0.967 NL / 0.995 symbol on semble's 1,251-query benchmark), at ~46× fewer agent tokens than grep+Read (4,120 vs 189,773 median on NL queries).

That reframes the product story: ken isn't "82% and hope" — it's near-grep recall at 1–2 orders of magnitude lower token cost, if you're on hybrid. Which is exactly why auto-fetch is in this release — the only users on the 82% path were the ones who hadn't installed the model, and now that's automatic. Every number is backed by a reproducible decomposition harness (internal/search/recall_decomp_test.go), and the README / benchmark docs were corrected throughout.

Also in this release

  • Phase 0 structural call-graph substrate — per-call-site records + span fields on every symbol; groundwork for resolved function-level callers / impact (no tool-surface change yet).
  • Crash fix — a guard against a gotreesitter stack overflow on oversized (64 KiB+) table-driven files that could take down ken index.
  • Docs overhaul — README trimmed 711 → 184 lines with the recall headline up front; working docs moved to docs/internal/; a new docs/db-indexing.md.

Install

go install github.com/townsendmerino/ken/cmd/ken@v0.10.0
go install github.com/townsendmerino/ken/cmd/ken-mcp@v0.10.0

Or grab a pre-built binary below — .tar.gz for macOS/Linux, .zip for Windows (amd64/arm64). Point your agent at ken-mcp; it fetches the embedding model on first run.

Full notes: CHANGELOG.md · compare v0.9.1...v0.10.0