The project store
A kapi project has three things, and it helps to keep them straight:
- the recipe (
kapi.yaml), your config: content, governance bindings, flows, defaults, languages; - the files, your deliverable: the source and target files on disk;
- the project store, your work: what kapi derives under
.kapi/work/while you build, plus the committed record of the reviews you make along the way.
This page is about the third one. The guiding rule:
Files and the committed context are the source of truth.
.kapi/work/store.dbis a derived index over them, plus the unit state you have staged but not yet committed.
What's in the store
| Layer | Lives in | Kind | Lose it? |
|---|---|---|---|
| committed context | .kapi/: the terms and memory bundles, the voice profile, and state/*.jsonl | authored: unit state, approved terms, memory pairs | committed; keep it in git |
| local store | .kapi/work/store.db | derived index over the committed context and your content, plus the working set of staged unit state | rebuilds, except unit state staged since the last kapi commit |
| cache | .kapi/work/cache/ | derived: parse cache, extractions, collections, sync cache | rebuilds on next run |
A project keeps one local database. .kapi/work/store.db holds every subsystem's
tables in a single SQLite file, on a single connection, each with its own
migration ledger: the block cache, the terms store, the content memory, the
voice store, the unit-state working set, and the property graph
(graph_nodes / graph_edges)
that links a term to the blocks that use it, a state record to the unit it blesses,
and both to their
context coordinates.
It sits at the top of .kapi/work/ rather than under cache/, and the
distinction matters: rm -rf .kapi/work/cache is completely free, while
store.db carries the working set. Between a review landing and kapi commit, that working set holds the only copy of the staged unit state. Delete
store.db and you lose at most what you staged since the last commit;
everything else is rebuilt from the committed context and your files.
The committed side is plain text, and it is what a person reads in a pull request:
.kapi/state/*.jsonl: the unit-state record, one JSON Lines shard per document (serialization);- the terms bundle the recipe binds with
defaults.terms_source(a.terms.json) and the content-memory bundle it binds withdefaults.memory_source(a.memory.json), conventionally.kapi/terms.jsonand.kapi/memory/memory.json, though both keys bind any path; - the voice profile,
.kapi/voice.yaml; - the content files themselves.
What to commit, and what to ignore
.kapi/ is committed. It is your project's context, and it belongs in review
like the rest of your source. The one thing kapi keeps out of git is
.kapi/work/, where everything it derives goes, which makes the ignore rule two
lines, with no exceptions to remember:
/.kapi/work/
/.kapi/filters.local.json
The second line is your personal filter overlay: your own reader settings, not the project's.
Deleting is worth being precise about:
rm -rf .kapi/work/cacheis always safe. Everything under it is rebuilt on the next run.rm -rf .kapi/workcosts two things. Any unit state you staged since the lastkapi commitlives only instore.db. And if you use redaction, the vault under.kapi/work/vault/holds the withheld originals, deliberately local-only, never committed and never synced, so no copy exists anywhere else. Merge the batches you have in flight before you clear it.
One local context graph
Because the graph tables live beside the block, terms and memory tables, a project answers graph questions without a server: which blocks use this term, in which collection, at which coordinate. A connected project answers the same query shapes server-side across more dimensions; locally they are fixed to a single value. See C-03: The Local Context Graph Store.
Two stages: working, then ship
The store also explains the two stages of a run:
- Working:
kapi run(with no-o, in a project) lands its translations as overlays in the block tables ofstore.db. The work is in the store and not yet in your files. - Ship:
kapi mergewrites those overlays out to the target files;kapi statusthen reads the files (plus the unit-state record) for the real ship state, the kapi loop's ladders and gates.
So coverage has two readings: "what's translated in the store" (working,
pre-merge) and "what's in the files and reviewed" (ship). In Kapi Desktop, the
project home's Content axis is the working reading; kapi status reports
the ship reading.
Unit state is staged, then committed
Approving a translation writes unit state, and publishing it is explicit, like a git
commit. When you approve a unit (see Review),
the state record lands in the working set inside store.db. kapi status reports
what is pending, and kapi commit materializes the working set into
.kapi/state/, where you commit it with your sources. The record is bound to
the content hash of the translation it blesses, so editing that translation
later drops the unit back below reviewed; an approval can't silently outlive
the text it approved.
kapi status --review # the worklist: units awaiting approval
kapi apply <<<'{"kind":"review","file":"src/nb.json","id":"save.label","locale":"nb","status":"reviewed"}'
kapi status # staged unit state, not yet committed
kapi commit # → .kapi/state/*.jsonl, ready for git
The same model maps to a server-backed project: with the server plugin
installed, instead of committing shards it pushes its unit state to a remote
(kapi push) and pulls it back (kapi pull), the same record with a
different sink.
An empty subsystem is an absent store
Presence is table-level. A store.db whose terms tables are empty behaves
exactly as though there were no terms store at all: on a fresh checkout the
terminology gate reads the committed .terms.json directly, kapi up --plan
never creates state, and kapi pack carries only the parts that hold
something. The file's presence conjures nothing into existence.
In the browser, the same model runs without SQLite: the memory and terms
backends are in-memory, blocks are keyed by path in memory, and the working set
persists to a JSON sidecar at .kapi/work/store.json.
What the store is not
- It is not the content memory. The
.memory.jsonbundle (defaults.memory_source) is a recycle corpus:kapi applywithkind:"memory"adds leverage for future translation and records no review. Review state lives in the unit-state record. See Memory & terms storage. - It is not authoritative for your translations; the files are. The store indexes and stages; the deliverable is the source of truth.
See also
- The kapi loop: the derived ladder, gates, and review queue.
- Projects: how a run's source and sink resolve.
- Project file reference:
defaults.terms_source,defaults.memory_source, and the other recipe fields.