Repository navigation
Releases
kash v2.2.2
What's Changed
Fixed
- Comparative questions dropped one of the books they named. Asked what
Sun Tzu and Machiavelli say about deception, an agent built from both books
answered that The Prince says nothing on it. The names in the question pick
the books, but every retrieval route counted them as relevance, and in a
book the author is named only on the title page and in footnotes. Those
chunks took The Prince's slots, and its chapter on deceit and keeping faith
never reached the context. When a question's named entities point to two or
more works, each work is now guaranteed a share of the top-k. Chunks that
match only through an author's name are demoted. A name counts as the
author's when it is on the work's title page and in at most a tenth of the
work. A chunk that also matches the question's topic keeps its score, so a
contract party or a biography's subject is never demoted. Single-work
questions are unchanged. No rebuild needed.
Installation
Linux (amd64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.2/kash_linux_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Linux (arm64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.2/kash_linux_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Apple Silicon)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.2/kash_darwin_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Intel)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.2/kash_darwin_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Windows (amd64)
Download kash_windows_amd64.zip, extract, and add kash.exe to a directory on your PATH.
For full usage instructions, configuration options, and examples, see the README.
kash v2.2.1
What's Changed
Fixed
- Chat answers ignored the corpus behind a Gemini gateway.
/v1/chat/completionssent the agent prompt and the retrieved context as two
separate system messages, and Gemini behind an OpenAI-compatible gateway
keeps only the first. The context was dropped without an error, so answers
came from the model's own priors even when retrieval had found the right
passage: asked what the Trishul represents, an agent built from a book that
answers it in one sentence returned generic lore and no citation. The prompt
and the context now travel in a single system message. No rebuild needed.
Installation
Linux (amd64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.1/kash_linux_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Linux (arm64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.1/kash_linux_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Apple Silicon)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.1/kash_darwin_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Intel)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.1/kash_darwin_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Windows (amd64)
Download kash_windows_amd64.zip, extract, and add kash.exe to a directory on your PATH.
For full usage instructions, configuration options, and examples, see the README.
kash v2.2.0
What's Changed
Fixed
-
A build stopped after its last document never finished, and no later build
noticed. The lexical index, entity descriptions and MCP tool description
are produced after the per-document loop, but the "corpus up to date" check
looked only at documents. A build interrupted between the two left all three
missing, and every laterkash buildreturned before reaching them — while
kash serveran on vector search alone. The manifest now carries
pending_finalize, set before any data is touched and cleared only when the
final steps finish; a manifest written before the flag existed is recognised
by documents recorded at version 0. The nextkash buildcompletes such a
corpus without re-embedding anything, and also builds a missing lexical index
for a corpus that predates it. -
kash servesaid nothing when the lexical index was missing. A missing
index loads as an empty one, so keyword and exact-reference search were off
with no sign at startup. It now warns whenever a corpus holds chunks but has
nolexical.idx. -
kash versionsaid "dev / none / unknown" for any binary not built by
makeor the release workflow. Only those two pass the version through
-ldflags, so a plaingo buildproduced a binary that could not be placed —
and wrotedevas thekash_versionof every manifest and profile it built.
The version and commit now fall back to the git revision Go embeds in every
binary built inside a checkout (dev-1a984b7-dirty), alongside the commit
time and the binary's file time. The published base image reporteddevtoo:
its Dockerfile passed no version at all, and now takesVERSION,COMMITand
BUILD_DATEbuild arguments from the release workflow. On Windows without
make,scripts/build.ps1builds a binary stamped with the release tag.
Installation
Linux (amd64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.0/kash_linux_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Linux (arm64)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.0/kash_linux_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Apple Silicon)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.0/kash_darwin_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Intel)
curl -L https://github.com/akashicode/kash/releases/download/v2.2.0/kash_darwin_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Windows (amd64)
Download kash_windows_amd64.zip, extract, and add kash.exe to a directory on your PATH.
For full usage instructions, configuration options, and examples, see the README.
kash v2.1.0
What's Changed
Fixed
-
The generic reference words missed the commonest division of all. The
built-in list covered section, clause, article and part, so a query naming a
chapter took no reference route at all — nor did paragraph, rule, schedule,
annex or appendix, which any policy, statute or standard uses. Added, along
with the section symbol, which never worked: a word boundary before a symbol
needs a word character in front of it, so§ 9at the start of a query could
not match. Deliberately still excluded are "item" and "step", which occur
constantly in ordinary prose. Measured on a corpus that uses none of these
words, the wider list added one tag across 895 chunks. -
A scheme strongly used by one work was outvoted by a passing mention in
another. Sequence scoring averaged across documents, so a scripture
numbering 101 of its own verses (0.84) and a commentary citing nine of them
(0.49) produced 0.66, describing neither. The strongest document now decides:
a numbering scheme belongs to the work that uses it, and another work quoting
it in passing is not evidence against it. -
Numbering that did not start at 1 was scored as though it started
nowhere. A scheme was rewarded only when its lowest value was 1, 2 or 3,
which assumes every work is quoted whole. An anthology quoting ślokas 7 to 102
begins at the beginning of what it quotes; that penalty alone sank a scheme
carrying 41 headings and 40 distinct numbers, leaving its book with 8% of its
chunks addressable against a companion volume's 50%. The test is now relative
to the scheme's own range, so a run starting a short way in still counts while
page numbers starting halfway through do not. -
A numbering scheme used by only one document was discarded. Detection
required a scheme to appear in at least two documents before accepting it.
That was safe on the 61-document corpus it was calibrated against, where any
real scheme appeared in several, and wrong on a small mixed-genre one, where
each work brings its own convention. A scripture numbering 45 distinct
passages as97)— 51 occurrences, monotonicity 0.77, comfortably above the
acceptance score — was rejected because the two commentaries beside it did not
use that form, leaving 46 of its 112 numbered passages unreachable by number.
Document spread still counts as a term in the score; it no longer vetoes. -
One numbered heading suppressed every other section's numbering in the same
chunk. A section whose heading already numbers it should not also take
numbers from its own body, or an ordinary list becomes verse numbering. That
rule was applied across the whole chunk rather than per section, so a listing
of31)and32)packed beside aVerse 33heading lost both of its
numbers. It was invisible while a bare listing wrote a different metadata key
from the heading, and became a real loss as soon as both named the same key. -
A reference could be indexed and still unreachable. The exact-reference
lookup has two sides that name the key independently: the chunker names it
from the pattern that matched the document, the query router from the pattern
that matched the query. Those are different texts, so the names need not agree
even when they mean the same passage — and nothing enforced that they would.
A work numbering its passages both asVerse 51in headings and97)in a
listing files the first underverseand the second undersection; on the
corpus this was found on, 51 of 112 numbered passages were reachable only as
a section, and asking for the verse fell through to pure similarity, which
returned a back cover ahead of the passage. The route now falls back to
matching the number under any reference key when the named key holds nothing.
It can only add hits where there were none, so it never displaces a correct
match. Measured against an already-built index, with no rebuild: 61 of 112
reachable becomes 112 of 112. -
Reference patterns kept punctuation they captured by accident. They end in
(\d[\d.]*)so they can capture4.2, which means they also capture the full
stop ending "…in accordance with clause 22." Stored as22., that reference
matched no query for 22. Values are normalised when written and when compared,
so an index built before this resolves too. -
The one numbering scheme that needed naming was the one that could not be
named. Every scheme is named by the word beside its number, and the model is
asked to supply a metadata key for each. The bare48)form has no word — its
pattern is punctuation and a digit class — so the match that assigns the
model's answer, a substring test against the pattern, could never fire for it,
and it stayed under the genericsectionkey. That is why a verse could be
cited asSection 96and why the graph namedSection Nentities. The match
is now by scheme identity, and the model is told what the wordless label
means so it can answer with the corpus's own word —clausein a contract,
versehere. -
Structural references away from the start of a chunk were never indexed.
Reference patterns are matched against whole multi-line chunk bodies, but were
compiled without(?m), so a leading^meant start-of-body rather than
start-of-line. A pattern like^\s*(\d{1,4})\)therefore tagged a marker only
when the chunk happened to begin with it, and every later marker in the same
chunk was dropped — present in the text, and unaddressable by number. On the
corpus this was found on, 22 of 112 numbered passages were missing their
reference; queries naming them took the exact-reference route to an empty
result and fell back to similarity, returning the book's introduction instead
of the passage asked for. Coverage on that corpus goes from 90/112 to 112/112.This affected any corpus whose markers sit mid-chunk —
Section 4.2in a
spec,Clause 7in a contract,48)in a numbered list — not only verse
numbering. -
The chunker and the retrieval layer compiled the same patterns
differently. Queries are single-line, so the missing flag was harmless
there and destructive at build time; the two sides could disagree about what
a pattern matches. Both now compile throughchunker.CompileRefPattern. -
Headings now take precedence over the body, per reference key. A heading
names what a chunk is, so an ordinary numbered list in the body no longer
contributes bogus reference numbers to a chunk a heading already numbered.
The body is still scanned for every key no heading answered, which is what
keeps a run of numbered passages addressable by each of them. -
Chunks repeated their own overlap text. Consecutive pieces of a section
share an overlap tail so a passage split across a chunk boundary reads whole
in both. When two such pieces were packed into the same chunk there was no
boundary to bridge, and they were joined verbatim — so a sentence appearing
once in the source appeared twice in the retrieved passage. A window that
consisted of nothing but the carried tail was also emitted as a piece of its
own, though the function had always claimed in a comment to drop it. Affected
chunks across the two books measured: 37 → 4. -
Chunks ran past the size they were budgeted against. The body was packed
to the full chunk size and the citation header was prefixed to it afterwards,
so every chunk that filled its buffer overshot by the length of its header.
Packing now uses the same content budget the pieces were cut to. -
A citation header could be longer than the passage it located. A heading
is whatever follows the hashes, so a document that numbers its passages by
putting the passage in the heading produced a breadcrumb segment hundreds of
characters long — unquotable, and prefixed to every chunk of that section.
Segments are now capped for display; chunk metadata keeps the full heading.
The ratio that was meant to bound this clamped the deduction rather than the
header, which inverted the guarantee it was written for. -
Entity summaries were shown however weakly they matched. The entity query
is top-K with no cutoff, so it returned its full quota whenever the corpus held
that many entities. On a question the corpus could not answer, the context
block opened by orienting the reader around whatever was nearest in embedding
space. The relevance floor already applied to graph seeding and provenance now
applies to what is shown, and is a named constant rather than a literal
repeated at three call sites.
Added
kash buildreports reference keys that tagged nothing. Profiles outlive
the corpus they were derived from, so a profile carries patterns for
structures the current documents may not contain. Such a key is dead weight —
queries naming it silently fall back to similarity — and the build now says
so instead of leaving it invisible.
Known limitation
A chunk can still exceed the configured size when a single source line is longer
than the budget: splitting cuts on line boundaries, so a paragraph written as one
unbroken line is indivisible and is emitted whole. On the corpus measured this
accounts for nearly all remaining oversized chunks — 422 of 434 — and is
unchanged by this release.
Requires rebuild
Run both flags on an existing corpus:
kash build --rebuild --refresh-profileBoth are needed, and the build enforces it. Detection now proposes different
reference patterns, which changes the domain signature, so kash build refuses
an already-indexed corpus until it is re-chunked. --rebuild alone is not
enough: it leaves data/domain.profile.json in place, and the profile is where
detection's output lives, so without --refresh-profile the old patterns
survive and nothing this release fixes in detection takes effect.
What ...
kash v2.0.0
What's Changed
Added
- Corpus profile.
kash buildnow measures your documents and writes
data/domain.profile.json: the diacritic mode, structural numbering patterns,
title stopwords, stem-vowel folding, and — via one model call — the extraction
vocabulary, priorities and honorifics. Configuration is layered
defaults < profile < agent.yaml, soagent.yamlis an override you may
never need to open. kash verifyaudits the provenance chain. It walks the graph, fetches
each fact’s chunk from the vector store, and reports how much of the graph
can be shown to a reader in the passage it came from — with examples of
what cannot. On a real corpus the answer is a number, not a hope.kash profileshows what was measured, the evidence behind each decision,
and which fieldsagent.yamloverrides.--dry-runre-derives without
writing,--no-llmmeasures only,--refreshregenerates.- Hybrid retrieval. A pure-Go BM25 index (
data/lexical.idx) is fused with
vector search by Reciprocal Rank Fusion, alongside an exact-reference route
for queries naming a section or verse number. - Structure-aware chunking. Documents are split on their heading structure
with citation breadcrumbs, and chunks carry structural metadata (book,
heading path, reference numbers, content type). - Semantic graph retrieval. Entity and relationship descriptions are
embedded into their own collections, so the knowledge graph can be reached by
meaning rather than only by exact token overlap. Entity hits seed graph
traversal. - Chunk-level provenance, end to end. Every graph fact records the chunk it
was extracted from, and entities carry the passages their facts came from.
At query time all three graph routes — traversal, the entity vector store
and the relationship vector store — return chunk ids, and those passages are
fetched and fused with vector hits. A semantic match on a generated
description therefore arrives with the source text behind it, rather than a
synthesis the reader cannot check. - Query decomposition splits a question into specific entities and broad
concepts to seed graph search, with a short-query bypass and an LRU cache so
most queries cost no extra model call. - Version stamping. Local builds now report a real version, commit and build
date; the manifest and profile record the binary that produced them. - Reasoning effort. Reasoning models can be driven at
low,mediumor
highthroughLLM_REASONING_EFFORT,llm.reasoning_effortin
~/.kash/config.yaml, orruntime.llm.reasoning_effortinagent.yaml
— last wins. It applies to build-time extraction, entity adjudication and
profiling as well as to serving, and a chat request may override it per call.
Unset means the parameter is not sent at all, so non-reasoning models are
unaffected. - Relationship weight as a corpus-time quality signal. Each canonical triple
now accumulates a co-occurrence count — the number of distinct chunks the same
(subject, predicate, object) triple was extracted from. The count is stored in
the relationship vector-store metadata at build time (weightfield on
RelationshipDoc) and used at retrieval time to apply alog1p(weight)boost
to graph fact scores inrankFactsByContext, so facts attested in many chapters
surface above those extracted from a single passage. Corpora built before this
change degrade gracefully: a missingweightkey is treated asw = 1(uniform
factor, relative order unchanged). A rebuild is not required but will stamp
weights on an existing corpus. The boost reads the weight of every candidate
fact from the graph index, not only of the handful the relationship vector
store returns, so it reaches the whole result set rather than its first rows. - Gleaning (iterative extraction). Extraction now performs iterative follow-up
passes per passage batch to recover facts the model missed due to token limits
or lost attention on dense passages. Each gleaning pass appends the model's
previous extraction as an assistant message and sends a continuation prompt
requesting any explicitly stated facts not yet captured, deduplicating newly
returned triples against existing extractions. Configured viaextraction.glean_rounds
(default: 1 inDomainConfig, layered viadefaults < profile < agent.yaml),
with early exit whenever a pass yields no new unique triples or an empty array.
Settingglean_rounds: 0disables gleaning for single-pass extraction.
Budget for it: a round that finds something costs a second model call for that
batch, so a dense corpus approaches double the extraction calls of a single
pass. The early exit keeps that cost proportional to what is actually
recovered.
Fixed
- BM25 and entity resolution were dead in every Docker deployment. The
scaffolded Dockerfile copied three paths whilekash servereads five, so
lexical.idxandentity_aliases.jsonnever reached the image and hybrid
retrieval silently degraded to vector-only. It now copiesdata/wholesale,
and the build warns when an existing project's Dockerfile is affected. retrieval.top_knever applied. Every caller passed a hardcoded default,
so the configured value was unreachable on the chat, A2A and dashboard paths.kash builddeleted every comment inagent.yamlby round-tripping it
through a map to update the MCP description. Nothing writesagent.yamlnow.- Corrupt PDF text was indexed silently. A PDF whose font subsets carry no
usableToUnicodeCMap yields glyph indices — valid UTF-8 that decodes as a
substitution cipher. A text-quality gate now rejects it instead of embedding
it. - Truncated extraction responses looked like "no facts". A batch that
returned unusable output was checkpointed as complete and never revisited. - A reference pattern without a capture group crashed the build or a query
with an index-out-of-range panic. Patterns are now validated on compile. - Build artifacts were walked as source documents, so every build reported
its own manifest and embedded-store files as "not indexed". - The lexical index did not record its diacritic mode. A corpus built with
one mode and served with another tokenised queries differently from its own
index and returned nothing, with no error. The index now pins its own mode. - Graph term matching measured length in bytes, discarding short Latin terms
like "om" while admitting any single Devanagari character. - Derived honorifics lost their trailing space, which is what separates a
title from the start of a word. Honorifics are stripped with a plain prefix
cut, so a mined"śrī "echoed back as"śrī"would have eaten the front of
every entity beginning with those letters. Values filtered against a supplied
list now come back in the list's own spelling rather than the model's. - Release notes never came from the CHANGELOG. The release workflow built a
dynamic regex as"^## \[" ver "\]", but awk resolves\[inside a
string literal to a plain[, leaving the character class^## [1.0.0]—
which matches no heading. An empty section falls back to the commit list by
design, so a release would have shipped commit subjects while appearing to
honour the CHANGELOG. The heading is now matched as a plain string. - Reranker responses are bounds-checked; a provider returning an out-of-range
index previously panicked the request./healthnow reports reranker status
from the real gate rather than a partial one. - A build could hang forever on one provider call. Every HTTP client was a
zero-valuehttp.Clientand the build passed a context with no deadline, so
a provider that accepted a request and then stalled — or an intermediary that
dropped the connection without a RST — blocked the process indefinitely. The
retry loop was no defence: a call that never returns never returns an error
either. Provider calls are now bounded by a response-header timeout that
scales with reasoning effort, and extraction prints a live batch counter so a
stall no longer looks identical to slow progress. - Every rerank request carried the whole candidate pool. The pool is sized
for fusion, which is local and free, so a defaulttop_kshipped 200 chunks
to a paid API billed per hundred, and a configuredtop_k: 50would have
shipped 1000 and likely been rejected — silently, since a failed rerank falls
back to cosine order. The reranker now sees the first 100 candidates and the
rest keep their similarity order behind them. - Facts with unknown provenance borrowed the first chunk in their batch.
When no passage in a batch showed evidence for a fact, attribution fell back
to the first one, which fabricated a citation: the fact printed as
"[passage 1]" and took the chunk-level ranking boost, both on text that does
not support it. Unknown provenance now stays unknown, and the fact keeps its
document citation. - A fact’s passage citation was never checked. The extractor reports which
passage it used and that report became the chunk id unconditionally, so a
misreported index printed a passage citation on text that does not support
the fact and took the chunk-level ranking boost with it. An evidence check
existed but ran only when the model declined to answer, which is backwards.
The claim is now evidence rather than authority: preferred when the passage
it names mentions the fact, and the batch searched otherwise. Matching folds
the way the corpus does, so a transliterated name still matches its source. - Generated entity summaries were indistinguishable from quoted source.
Descriptions written by a model at build time appeared under the same
heading style as retrieved passages, so an answer could rest on a summary
and cite a document that never contains those ...
kash v1.0.0
What's Changed
- Initial release
Installation
Linux (amd64)
curl -L https://github.com/akashicode/kash/releases/download/v1.0.0/kash_linux_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Linux (arm64)
curl -L https://github.com/akashicode/kash/releases/download/v1.0.0/kash_linux_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Apple Silicon)
curl -L https://github.com/akashicode/kash/releases/download/v1.0.0/kash_darwin_arm64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/macOS (Intel)
curl -L https://github.com/akashicode/kash/releases/download/v1.0.0/kash_darwin_amd64.tar.gz | tar -xz
sudo mv kash /usr/local/bin/Windows (amd64)
Download kash_windows_amd64.zip, extract, and add kash.exe to a directory on your PATH.
For full usage instructions, configuration options, and examples, see the README.