Skip to main content

Zikaron

PyPI CI Coverage Downloads Ruff Checked with mypy License: MIT

Zikaron (Hebrew/Yiddish זיכרון — "memory, remembrance") gives a coding agent the two kinds of knowledge a project holds that are not in its source code.

Tribal knowledge — memory. What nobody wrote down, because it was learned by living through the work: how to build and test it, which step fails silently, which env vars the integration tests need, which API is not safe to use yet and why, what was already tried and how it failed. Agents write this as they go and read it back in later sessions.

Institutional knowledge — knowledge bases. What the project did write down, indexed and made searchable: a docs tree, a directory of run books, design records, a vendored dependency's manual. The agent searches a named corpus and gets fragments back with real line ranges, quoted verbatim.

The two differ in where they come from and therefore in who maintains them. Memory accumulates without anybody curating it, and goes stale when the code moves under it — so the agent that a memory misleads is the one expected to correct it. A knowledge base is a view onto files somebody else owns, and it goes stale when those files change — so you refresh it, and a result from a file that has changed since it was indexed comes back marked as such.

The test for whether something belongs in memory: could you learn it by reading the code? If yes, it is out of scope. That store is for what cost somebody time to discover, and would otherwise be discovered again by the next agent, at full price. A knowledge base has no such test — it is whatever corpus you point it at.

It runs entirely on your machine: SQLite databases under .zikaron/, a local embedding model, and a small background service on a Unix socket. Zikaron reaches the network for exactly one thing — the embedding-model download described under Install, pinned to one revision and verified against a SHA256 set it ships. That download repeats only if the cache it lands in is cleared, or if a file in it fails verification. There is no account to create. What it retrieves still reaches your model, since that is the point: the gists a hook injects and the groups a consolidator is handed travel to it the way the rest of your session does.


How it works

The two halves share a substrate — the same store directory, the same embedding model, the same service, the same hybrid retrieval — and differ in everything above it. Memory is described first and at greater length because it is the half with a write path, a policy and a consolidation step; knowledge bases are the simpler half and are covered under Knowledge bases below.

Memories are short records — a one-line gist and a longer content body. The gist exists to let a future agent decide whether to read further; the content carries the detail. Records live in two tiers: new ones land in a journal, and consolidation later folds the journal into long-term records, merging what belongs together and retiring what does not.

Reading happens two ways. Before every message you send, a hook injects the few most relevant gists into the agent's context, so recall costs no tool call and no decision. When the agent wants more, it calls zikaron_memory_search for a wider look or zikaron_memory_fetch for a full record. Retrieval is hybrid: a vector search over a local embedding model and a full-text search over the same corpus, their rankings fused, so an exact identifier and a vague description both find their record.

Writing is the agent's own judgment. Nothing summarizes your session behind your back and no extra model runs on the write path. A policy injected when the agent starts tells it what is worth recording; it then calls zikaron_memory_remember, zikaron_memory_amend or zikaron_memory_retire itself. Near-duplicates are detected at write time and handed back to the agent to resolve rather than silently dropped, and nothing is ever hard-deleted — a retired record stops surfacing but stays auditable.

Consolidation is manual and runs as a separate agent. You ask for it; a subagent with the four consolidation verbs and no way to search or fetch is handed groups of related entries that code selected for it, and decides for each whether to merge, promote or discard. Keeping it separate means a fresh context, and it cannot write outside the group it was handed — every verb refuses a uuid that was not served to it. Under kiro those four verbs are its whole surface. Under Claude Code it is also granted a file-reading tool, because a group too large to return is handed to it as a file instead; that grant cannot be restricted to one path there, so what keeps it off everything else is its prompt.

No agent can put its own words into the text a knowledge base searches. The searchable content comes only from the files on disk: an indexer walks the tree you pointed it at, chunks them, and stores the text with its line ranges. What an agent does write is the corpus's definition — the name, description, root and filters it passes to zikaron_knowledge_add, of which zikaron_knowledge_rename changes the name alone, and the name and description come back on every search as the label on each group — plus a build's own metadata and the four search counters described under Files and logs. The agent reaches them through zikaron_knowledge_search and a handful of management verbs — every tool name carries its subsystem, zikaron_memory_* or zikaron_knowledge_*, so a model choosing between the two stores reads which one it is addressing before it reads a description. Consolidation never touches them.

The pieces

What it is
core The library: the store, retrieval, chunking, consolidation, and the knowledge index. No process concerns, no transport.
service A long-running process per project. Holds the embedding model in memory and the databases open, and answers requests over a Unix socket. Starts itself when needed and stops itself when idle.
MCP server Translates the agent's tool calls into requests to the service. One process per agent instance; loads no model.
hook A single-shot executable your agent runs on start, before each message, and — under Claude Code — when it spawns a subagent. Deliberately tiny; loads no model and never touches the databases directly.
indexer A detached process started per knowledge-base build — by the service when an agent asks, or by the zikaron.knowledge command when you do. It outlives the call that started it, loads a model, saturates a core for a minute or more, and exits when the build finishes. If you see one in ps, that is a corpus building, not a runaway.

The service exists for one measured reason: loading the embedding model costs about 780 ms, and retrieval sits on the path of every message you send. Keeping the model resident turns that into a ~7 ms embed and a ~9 ms round trip. (The indexer pays that same load cost once per build, in its own process, which is why it is detached rather than run inside the service.) The hook stays thin for the same reason — it runs once per message, and it costs about 50 ms end to end.

If the service is not running, the hook starts it and moves on. If anything fails, the hook writes one line to a log, tells the agent to mention it to you, and exits cleanly — it never blocks your message and never fails your turn.


Requirements

  • Linux, or macOS on Apple Silicon. The transport is a Unix domain socket and the paths assume a POSIX filesystem. Linux x86_64 and macOS arm64 are verified; Linux arm64 is supported but not verified — CI runs the full hermetic gate on both of the first two and nothing has ever run on the third. What is verified on macOS is that gate: no harness binary exists on a CI runner, so nothing there exercises a real session pushing or searching. Intel Macs and Windows are not supported: Intel Macs would mean pinning a year-stale onnxruntime on two of the three interpreters and a hard install failure on the third, and Windows needs a second RPC transport rather than a flag. design/distribution.md §1 has the detail.

  • Python 3.12 or newer. Tested versions: 3.12, 3.13, 3.14.

  • A supported harness installed: kiro-cli or Claude Code. Zikaron installs hook and MCP entries whose only reader is that harness's own binary, so the installer checks it is on your PATH and refuses if it is not — writing those files where nothing reads them would exit 0 and leave you with no Zikaron tools and no error to search for. Pass --print-only to see exactly what would be written without writing it, which is how to provision a machine before its harness.

    The installer itself is an ordinary program and does not need a harness running in order to execute. Run it from any shell — a terminal, an ssh session, a container build. It works out which harness a project is for from the project itself (a .claude/ or a .kiro/ directory), falls back to the CLAUDECODE marker when the project says nothing, and refuses — telling you to name the harness — when the project names both, or when neither the project nor the marker says. Under kiro it additionally validates the consolidator's model id against the binary, because kiro substitutes an unknown model silently; Claude Code refuses one itself, at spawn, so no such check is needed there.

Install

Two supported paths, and the first is recommended for a reason worth reading.

# Recommended. `--managed-python` is not optional; the paragraph below says why.
uv tool install --managed-python zikaron
# The same, from the repository rather than PyPI — for a version that has not been released.
uv tool install --managed-python git+https://github.com/nathan-shapiro/Zikaron.git
# Alternative — a source checkout on a Python you already have.
git clone https://github.com/nathan-shapiro/Zikaron.git
cd Zikaron
python3 -m venv .venv
.venv/bin/pip install -e .

Why uv is recommended. Zikaron's vector search is a loadable SQLite extension, and whether an interpreter can load one at all is decided when that interpreter was compiled — and it is reported off in some widely used builds, python.org's macOS installer and conda-forge among them. The interpreters uv fetches were measured here and have it; those two were not, so treat them as a reported risk rather than a verified failure. A host Python may well work; it is simply the path where "it installed fine and then retrieval does not work" is possible.

And that is why --managed-python is in the command. Without it, uv falls back to a host interpreter whenever it has not already downloaded one of its own — which is the case right after you install uv. Measured, running both forms against empty uv directories: with the flag, uv downloaded its own CPython and built the tool environment on it; without it, uv used /usr/bin/python3.12 and downloaded nothing. So the plain command hands you the host build on a fresh machine, which is the outcome the recommendation exists to avoid.

uv is used at install time only. Zikaron never invokes uv run or uvx when it runs: the hook executes once per message you type, and a resolver in that path costs about 20 ms every time. What gets installed is ordinary console scripts with a fixed interpreter.

First use downloads a 64 MB embedding model from Hugging Face, so the very first search is slow and needs the network. Everything after it is local, and the download survives a reboot: it is cached per user, under $XDG_CACHE_HOME/zikaron/models (or ~/Library/Caches/zikaron/models on macOS), and $FASTEMBED_CACHE_PATH overrides that if you already set it. It is outside your project either way, so no .gitignore entry helps.

The download is pinned and verified. Zikaron fetches one named revision of one Hugging Face repository and checks every file it downloads against a SHA256 it ships, so an upstream change or a damaged transfer is refused rather than used, and it re-fetches once before giving up. A file that goes bad on disk afterwards is zikaron doctor's to find rather than startup's — checking all 64 MB on every start costs more of the push hook's budget than it is worth, which is measured in design/distribution.md §"Model acquisition". zikaron doctor reports which state your cache is in.

Then install into the project you want Zikaron in:

cd /path/to/your/project
zikaron install --project .

Under kiro, name the config for the agent you actually work in:

zikaron install --project . --agent .kiro/agents/<your-agent>.json

If zikaron is not on your PATH — a source checkout whose virtualenv you have not activated — every command below also works as python -m zikaron.<command>, run with that virtualenv's interpreter. zikaron doctor has no such form; it is new with the zikaron command.

Pass --harness claude-code or --harness kiro to override the detection described under Requirements. --agent is kiro-only, and passing it under Claude Code is refused rather than ignored — there its entries go into fixed project files instead. To see exactly what would be written without writing anything, pass --print-only on either harness.

Finally, if the project is a git repository, tell git to ignore the store — and, under Claude Code, the MCP config too:

echo '.zikaron/' >> .gitignore
echo '.mcp.json' >> .gitignore     # Claude Code only; see below

.mcp.json is the awkward one. Claude Code intends that file to be committed and shared — that is what "project-scoped" means — but the entries Zikaron writes into it name absolute paths inside your virtualenv, so a clone-mate gets a server that cannot start, plus an approval prompt for it. This is the same objection that keeps the hook entries out of the checked-in settings.json; the difference is that settings.local.json exists as an untracked sibling and .mcp.json has no equivalent. With no per-project, machine-local MCP scope to move it to, the choice is yours: ignore the file, or accept that each clone re-runs the installer. Re-running is safe — a differing Zikaron entry is refused loudly rather than merged over.

The installer does not edit .gitignore for you; appending to it is not a decision an installer should make silently. But it matters and is easy to forget: without it the memory database and its write-ahead log, every knowledge base's index and the service logs all show up as untracked, and a routine git add -A commits the lot. None of it is useful to anyone else, and none of it is reproducible from your repository.

What it writes under kiro, relative to the project:

Path What
.kiro/agents/zikaron-consolidator.json the consolidation subagent: its model, its four tools, its prompt
.kiro/skills/zikaron-consolidate/SKILL.md how to run a consolidation, and how to recover a stuck one
the agent config you named hooks for start and per-message, an mcpServers entry, @zikaron in tools and allowedTools, the consolidator in toolsSettings.crew, and the skill in resources

and under Claude Code:

Path What
.claude/agents/zikaron-consolidator.md the consolidation subagent, as frontmatter plus its prompt
.claude/skills/zikaron-consolidate/SKILL.md how to run a consolidation, and how to recover a stuck one
.claude/settings.local.json three hooks — session start, per-message, and per-subagent — plus both approval keys, enabledMcpjsonServers and permissions.allow
.mcp.json both servers: zikaron for your own tools, zikaron-consolidator for the consolidation verbs

settings.local.json, not settings.json, and it matters if you commit your settings: the hook entries name absolute paths inside your virtualenv, so they are meaningless in anyone else's clone. The third hook is the one with no kiro counterpart — it hands the write policy to each subagent you spawn, which kiro achieves by firing its ordinary hooks for subagent sessions instead.

It backs up any file it merges into (<file>.bak, and the first backup wins), and refreshes a shipped file whose contents are not what this version ships — after backing it up, and saying so. That is what makes upgrading work, and the cost is that a hand-edit to a shipped file is reverted on the next install rather than kept. Under kiro it also validates the consolidator's model id, because an unknown model would otherwise be silently replaced by the harness's default.

@zikaron has to be in tools or Zikaron's tools are simply absent: the mcpServers entry configures the server and tools is what selects from it. It goes into allowedTools too, so the agent can record without interrupting you. That is deliberate rather than lax — the whole design leans on the agent writing freely, and a permission prompt per write both suppresses that and trains you to click through prompts. What it trusts is still narrow, but it is not only rows. The memory tools read and write rows in a local database. The knowledge tools additionally read a directory tree you name, which may sit outside the project — a docs tree or a vendored dependency is a legitimate corpus, so only degenerate roots (the filesystem root, your home directory itself) are refused — and copy its text into an index, in a detached process that works a core for minutes. No network either way once the model is cached — the only fetch Zikaron makes is the embedder download described above, which a cleared cache directory can make the indexer pay again — and nothing is written into your project outside .zikaron/. A mistaken memory write is recoverable rather than undoable: zikaron_memory_retire withdraws a record from ordinary retrieval and leaves it auditable, while an amend overwrites prose that nothing restores. A mistaken zikaron_knowledge_remove destroys that corpus's index, which is rebuildable from the files it was built from but not instantly. Pass --no-trust-tools if you would rather approve each one.

That trust stops at the primary agent's tools. Spawning the consolidator is a separate grant — a subagent with its own model invocation, four mutation verbs and, under Claude Code, a file read — and the installer does not make it for you. Under kiro that grant is toolsSettings.crew.trustedAgents, which the installer only ever reads: starting a consolidation therefore asks your permission once, and adding zikaron-consolidator there yourself is what stops it. Claude Code has no such key, so there is nothing to add and nothing withheld.

Three keys under kiro only, and Claude Code has none of them. The consolidator also has to be reachable by the subagent tool, so if your config already restricts which agents may be spawned (toolsSettings.crew.availableAgents), the installer adds zikaron-consolidator to that list. If you have no such restriction it leaves it alone — an empty list means every agent is available, and writing one entry into it would restrict you to just this one. The skill is also declared in the agent's resources unless something there already covers it. Skills normally arrive by inheritance, so that entry is usually redundant — but it is the only thing that makes the skill loadable if you have set chat.disableInheritingDefaultResources, and declaring a resource does not disable inheritance, so it can only help.

One thing the kiro install deliberately does not do: add the subagent tool itself, which the consolidation skill needs in order to spawn the consolidator. Its reach is much wider than Zikaron's own tools, so that grant stays yours. The installer says so if it is missing. Under Claude Code none of this arises — the shipped skill is an ordinary project file, and the consolidator is spawned through whatever subagent tool the harness already gives your agent.

Installing into a clone of the Zikaron repository itself, under kiro, finds .kiro/agents/zikaron-consolidator.json already tracked, carrying whichever virtualenv path the last committer had. The installer notices that the file names a different install, backs it up, and rewrites it for yours. Under Claude Code there is nothing tracked to collide with — the consolidator config is not in this repository — so the installer simply writes it.

What to know under Claude Code

There are three approval gates and the install answers two of them. enabledMcpjsonServers decides whether a project-scoped .mcp.json server loads at all; permissions.allow decides whether each tool call goes through without a prompt. Both are written into settings.local.json. The one it does not answer comes first: Claude Code's own folder-trust dialog, on first entry to a directory, which is yours to answer — and it reads the permissions.allow entries back at you as a warning that this folder pre-approves tool permissions. If Zikaron's tools are missing after a fresh install, check /mcp for a server pending approval before looking anywhere else — an unapproved server is simply absent, with nothing saying why. If they are present but every write interrupts you, it is permissions.allow that did not take.

A fourth prompt is deliberately left live. The first time a consolidation meets a group too large for the harness to deliver, the group is written to a file and Claude Code asks whether the consolidator may read from the runtime directory. Allow it for the session — and expect it again in the next one, because session scope is the only scope that grant has. It is not pre-answered on purpose: a file-reading tool cannot be restricted to one path in subagent config, so this prompt is the only point at which that grant is put to you as a question.

--no-trust-tools withholds both written keys for your tools. It does not withhold them for the consolidator's: a subagent has nobody to answer a permission prompt, so an unapproved MCP tool there does not ask, it fails at the moment consolidation needs it. The file read above is the exception — that one prompts.

Your own agent can see the four consolidation verbs, and that is not a misconfiguration. A server has to be registered for the whole session before any subagent can reach it, so registering the consolidator's server exposes it to you too. Under kiro the two tool sets are separated mechanically; here it is the prompt that keeps them apart. Nothing in the store is at risk from it — the never-lose guard, the receipts and the lease are untouched — but an unauthorized consolidation would spend tokens and could write a poorly-judged long-term record. permissions.deny is not the fix: it is global and unregisters the tool, after which the consolidator itself refuses to start.

Options

Flag Effect
--project <dir> the project to install into. Also where the store lives (default: the current directory)
--harness {auto,kiro,claude-code} which harness to install for. auto reads the project, then the CLAUDECODE marker; it refuses when the project has both dotdirs, and when neither source says
--agent <path> kiro only. Merge the hook and MCP entries into that config, after backing it up
--print-only print what would be written and write nothing at all
--model <id> the consolidator's model (default: claude-sonnet-5 under kiro, sonnet under Claude Code)
--format {object,array} kiro only. Which hook format to write when the target config has none yet
--no-trust-tools do not pre-approve Zikaron's own tools, so every Zikaron tool call asks permission
--force replace a symlink at a shipped path, and overwrite entries wired to a different Zikaron install that would otherwise be refused

Both hook formats kiro accepts are supported, and a config that already uses one keeps it: kiro rewrites a config in whichever format it read, so mixing them in one file has no defined meaning.

Verify

Start with zikaron doctor, which answers the questions that decide whether Zikaron can run on this machine at all — and names what to change for each one it answers badly, rather than leaving you a traceback:

$ zikaron doctor
ok   sqlite extension loading              available
ok   sqlite FTS5                           available
ok   sqlite-vec                            loads, and registers vec0
ok   model cache (BAAI/bge-small-en-v1.5)  present at 52398278842ec682c6f32300af41344b1c0b0bb2 under /home/you/.cache/zikaron/models, 5 files verified
ok   socket path length                    /run/user/1000/zikaron/2eb0b9c22f39e097ac31aa6adfe7fba7.sock fits 108 bytes
--   sqlite version                        3.45.1 linked by Python 3.12.3

That is real output with the home directory replaced; everything else is verbatim, including the full revision and socket path the command prints.

It exits non-zero if any check fails. A model cache that is not there yet is not a failure — there is no prefetch at install time, so a first run reports it as fetched on first use and exits 0. The last row reports rather than checks: the linked SQLite version varies between interpreter builds on one machine and there is no correct value to compare against, so stating it is the whole point. zikaron doctor --project <dir> checks another project's socket path.

Then start a session with the agent you installed into. On start you should see nothing unusual — the write policy goes into the model's context, not to your terminal. Then, from the project directory:

# a service should be running for this project after your first message
pgrep -af zikaron.service.main

# one line per hook failure, write-policy-override note, or session/environment mismatch; absent means none happened
cat .zikaron/hook.log

Ask the agent to remember something, then start a fresh session and ask about it. If the memory comes back, the whole loop works: write through MCP, injection through the hook, retrieval in between.

Using it

Writing. Say "remember that" when you want something kept, or leave it to the agent — the injected policy tells it what is worth recording. Its bias is toward recording, because the common failure is an agent that records nothing.

Reading. The relevant gists arrive before every message you send. The agent fetches full records when it wants the detail, and can search when the injected few are not enough.

Consolidating. Ask the agent to consolidate project memory once the journal has built up — after a stretch of real work, or at the end of a task. It loads the shipped skill and spawns the consolidator. If a run ever seems stuck, ask again: a second invocation takes the abandoned run over and replans.

Knowledge bases: searching what the project wrote down

Memory holds the tribal knowledge agents learned by working here. A knowledge base is the institutional half: a named, indexed corpus of text files the project already has — a docs tree, a directory of run books, a vendored dependency's documentation. The agent searches it and gets back fragments with line ranges, quoted verbatim, so it can read further or quote them as they stand.

Ask the agent to create one and it will, without leaving the session: "index the docs directory as a knowledge base called design docs". It has tools to list, create, rename, refresh, inspect and remove them. The same verbs are available at a shell, for when no agent is running:

zikaron knowledge list
zikaron knowledge add "design docs" --path ./design \
    --description "Architecture and design records"
zikaron knowledge refresh            # every corpus; name one to narrow it
zikaron knowledge status "design docs"

Three things are worth knowing before you point one at a directory.

Building takes minutes and runs in the background. add and refresh start a build and return. A first build answers nothing at all while it runs: the corpus reports reindex_required for its whole duration, and only becomes searchable when the build completes. A later refresh keeps answering from what is already indexed while it works — as does a --full one — with the exception of a rebuild forced by a changed embedding model, which empties the corpus before it starts and so answers nothing until it finishes. status says how far it has got.

Nothing updates an index on its own. There is no watcher and no schedule: a corpus drifts from its files until somebody refreshes it. A search says so when it can — a result whose file has changed since it was indexed comes back marked stale.

An index holds the text of every file in it. Do not point one at a directory holding credentials. The databases are 0600, the same as memory.db, and they are under .zikaron/, so the .gitignore line above already covers them — but what is in one is searchable by every agent working in this project.


Configuration

Entirely optional — every key has a working default. Two TOML files, later wins per key:

~/.config/zikaron/config.toml     applies to every project
<project>/.zikaron/config.toml    this project only

An unknown key or a value of the wrong type is fatal rather than ignored, so a typo cannot silently leave you on a default. Values are range-checked when the service starts.

[retrieval]
fusion_depth = 50          # how many candidates each arm contributes before fusion
rrf_k = 60                 # the rank-fusion constant; larger flattens the ranking

[consolidation]
group_max = 12             # members per group before it is split
run_lease = 1800           # seconds a consolidation run holds the store

[service]
idle_timeout = 1800        # seconds of inactivity before the service stops itself

Everything that is tweakable

Section Key Default Range What it does
embedding embed_model BAAI/bge-small-en-v1.5 any model your embedder can load which embedding model to use. Store-coupled (hard)
embedding embed_dim 384 ≥ 1 that model's vector width. Store-coupled (hard)
embedding embed_prefix_query a retrieval instruction free text the prefix prepended to a query before embedding
indexing chunk_max_tokens 450 64–8192 how large a chunk may get before a record is split. Store-coupled (soft)
indexing gist_max_tokens 64 8–256 the longest gist a write may carry
indexing knowledge_max_file_bytes 1048576 1–67108864 knowledge bases: the largest file an indexer will read; bigger ones are skipped
indexing knowledge_embed_batch 32 1–256 knowledge bases: how many chunks an indexer embeds per batch
retrieval chunk_overfetch 8 1–64 how many extra chunks the vector arm reads to cover its candidates
retrieval fusion_depth 50 1–500 how deep each arm goes before the two are fused
retrieval rrf_k 60 ≥ 1 the rank-fusion constant
retrieval supersession_penalty 0.5 >0.0–1.0 how far a replaced record is demoted rather than hidden — the score is multiplied by it, so zero is refused because it would hide the record rather than demote it
retrieval retired_penalty 0.5 >0.0–1.0 the same, for a retired record when one is asked for
retrieval supersession_max_depth 32 1–1024 how far a chain of replacements is followed
retrieval fts_query_max_terms 64 1–512 the largest full-text query built from one prompt
retrieval knowledge_max_chunks_per_file 2 1–20 knowledge bases: how many fragments one file may contribute to a result set
retrieval knowledge_snippet_max_chars 1200 80–24000 knowledge bases: how much text a single returned fragment carries, counted in code points
dedup dedup_threshold 0.8 0.0–1.0 how similar a new record must be to be offered back as a near-duplicate
dedup dedup_max 3 0–20 how many near-duplicates are handed back at once
consolidation mutual_k 5 2–50 how many neighbours each entry considers when grouping
consolidation orphan_edge_cutoff 0.65 0.0–1.0 how close two entries must be to group without a shared anchor
consolidation anchor_cutoff 0.65 0.0–1.0 how close an entry must be to a long-term record to be anchored to it
consolidation group_max 12 2–64 members per group before it is split into shards
consolidation max_group_serves 3 1–16 how many times one group may be re-served before it is abandoned
consolidation run_lease 1800 60–86400 seconds a consolidation run holds the store
consolidation spill_threshold 27000 4096–1048576 Claude Code only: bytes past which a consolidator tool result is written beside the socket instead of returned inline. Nothing spills under kiro, where the key has no effect
service idle_timeout 1800 60–86400 seconds of inactivity before the service stops itself
signals signal_horizon_days 30 1–3650 the window the write-policy instrumentation reports over

Three keys are coupled to what is already stored, at two severities. The file says what you want; the store records what was actually done, and a file that disagrees is requesting a change rather than making one.

  • Hard — embed_model, embed_dim. They describe the vectors already on disk, so changing either means reindexing. The two stores say so differently. The memory store refuses: the service will not start rather than serve a store whose configuration disagrees with its contents. Every knowledge base seeds the same two keys into its own metadata when it is created and degrades instead of refusing — a corpus whose recorded encoder no longer matches reports reindex_required, answers searches with no results, and is rebuilt whole by the next refresh. A corpus's own database is opened per call and never at startup, so one in this state does not keep the service down.
  • Soft — chunk_max_tokens, and only in the memory store. There the file governs new writes: nothing refuses, nothing needs rebuilding, and the store simply ends up holding chunks cut at more than one size. A knowledge base seeds this key at creation like the other two and then ignores later changes to it, so changing it affects only corpora you add afterwards — an existing corpus is re-chunked at the value it was created with, even by a full refresh.

Three further keys are frozen into a knowledge base alone, by the same mechanism and with no equivalent in the memory store: rrf_k, fusion_depth and knowledge_max_file_bytes are recorded in a corpus when it is created and read from there afterwards — the first two on every search, the third on every build — so changing them in the file affects only corpora you add afterwards. knowledge_max_file_bytes can also be set per corpus when you add it. They carry no marker in the table above because the severities are a property of the memory store, which these do not touch.

The write policy can be overridden per project. Put your own text at .zikaron/write-policy.md and the session-start hook prints that instead of the shipped policy. An absent override is the ordinary case and is silent. If one is there but is unreadable, empty, a symlink, not a regular file, or sits in a store directory that is not yours alone — owned by another user, or carrying any group or other permission at all — the shipped text is used and one line lands in hook.log saying which. An override too large for the harness's injection budget is printed anyway, with a line in hook.log — the harness truncates in silence, and a policy the model received part of is worse than one you were told about. The policy print is the one path that is never allowed to fail.


Files and logs

Almost everything Zikaron writes for a project while it runs lives in one directory, and all of it is 0600 or 0700. The installer also writes harness config — the files tabled under Install, plus a .bak beside anything it replaces.

<project>/.zikaron/
  memory.db          the store itself — never commit this
  memory.db-wal      SQLite's write-ahead log; part of the store
  memory.db-shm      SQLite's shared-memory index; part of the store
  knowledge/         one database per knowledge base, named by a generated id.
                     Absent until you create one
  config.toml        your per-project overrides, if you wrote any
  service.log        the background service: startup, resolved config, errors
  hook.log           one line per hook failure, write-policy-override note, or session/environment
                     mismatch. Absent means none happened. Not every line is a failure: an override
                     that is used but exceeds the injection budget is printed anyway and logged
  warmup.log         the small helper that warms the service when a session starts
  write-policy.md    your policy override, if you wrote one

Two things the running system writes live elsewhere, and both have to.

The first is the embedding model, which is per user rather than per project: one 64 MB artefact shared by every project you use Zikaron in, under $XDG_CACHE_HOME/zikaron/models — ~/Library/Caches/zikaron/models on macOS, or wherever $FASTEMBED_CACHE_PATH points if you set it. A copy inside each project would duplicate it for bytes that are identical by construction, since the version fetched is pinned. Removing a project does not remove it; Uninstall says how.

The second is the service's Unix socket, which with its lock file sits in $XDG_RUNTIME_DIR/zikaron/ — or /tmp/zikaron-<uid>/ where that variable is unset, which is every Mac — named by a hash of the store's path, 0600 inside a 0700 directory. A socket belongs on a runtime filesystem rather than in your project. Under Claude Code, an over-large consolidator result also spills to a file beside them, carrying that group's record prose verbatim and named by the same store hash; it is deleted when the consolidator asks for its next group — or, if that process is killed first, by the next one that starts. It is the one copy of memory text that lives outside .zikaron/, which is why the erasure procedure under Secrets removes it by hand. Nothing spills under kiro.

Two notes on reading the rest. memory.db can look implausibly small while memory.db-wal is large — that is normal for a database held open by the service, since recent writes live in the log until a checkpoint; the files together are the store. And hook.log records only a fixed failure label and an error code, never your prompt or a memory's text, so it cannot hold a leaked secret.

There is one more log that is not a file: an event table inside memory.db. It records what happened on every memory read and write — used to measure whether the write policy is working, and kept in the database because those rows must commit alongside the change they describe. A knowledge base has no event log: it keeps four search counters in its own metadata — searches, empty searches, results returned, results stale — which zikaron_knowledge_status reports beside the last build's own file, byte and skip-reason counts.


When something is wrong

Zikaron is built to fail quietly rather than get in your way. A hook that cannot reach the service prints a short note asking the agent to tell you, writes one line to hook.log, and exits cleanly. It never blocks your message, and it never reads the store directly.

Run zikaron doctor first for anything that smells environmental — no search results at all, a service that will not start, an MCP server that dies on contact. It checks the things about this machine that can stop Zikaron working and names what to change for each. The table below is for symptoms it cannot see.

Symptom Where to look
nothing searchable works at all zikaron doctor — extension loading, FTS5 and sqlite-vec are the three that fail this way, and all three are properties of the interpreter rather than of your project
the MCP server starts and immediately dies zikaron doctor — an over-long $XDG_RUNTIME_DIR makes the socket path exceed what the platform allows, and this is the only channel that says so
no memories are being injected .zikaron/hook.log, then pgrep -af zikaron.service.main
the agent has no zikaron_* tools kiro: /tools, and check @zikaron is in the agent's tools. Claude Code: /mcp, and check neither server is pending approval
every Zikaron tool call asks permission kiro: add @zikaron to the agent's allowedTools. Claude Code: it is permissions.allow in settings.local.json that did not take
a knowledge search returns nothing, or too little zikaron_knowledge_status (or zikaron knowledge status <name>) — a corpus refreshing answers from what is already indexed, but one building for the first time answers nothing until it finishes. reindex_required means it has no usable index right now: never built, or a rebuild emptied it and was interrupted before it finished, or its database is gone, or the configured embedding model changed since it was built. refresh rebuilds it
knowledge results look wrong for the file on disk the index has drifted; nothing refreshes it on a schedule. A result whose file changed since indexing is marked stale — run refresh
"Agents not available for crew stages: zikaron-consolidator" add it to toolsSettings.crew.availableAgents, or re-run the installer
consolidation seems stuck ask to consolidate again — that takes the run over
the service will not start .zikaron/service.log; a change to a hard store-coupled key (embed_model, embed_dim) is the usual cause. A soft one never refuses over what is already stored — but any key set outside its range refuses at startup, whatever its coupling
a hook command "not found" the config names a different virtualenv than the one you installed from; re-run the installer with --force
a memory looks half-written when injected an over-long gist; see the note below

One known limit, now narrow. kiro truncates a hook's output past a byte cap and says nothing when it does. Gist length is bounded in tokens, and tokens bound neither characters nor bytes — a single unbroken 4000-character string counts as one token — so a pathologically long gist could once push the injected block past that cap and lose the tail of it silently. New writes can no longer do this: a gist over 1,024 characters is rejected outright, which keeps a five-row block comfortably inside every supported harness's budget. (Emoji and other characters outside the common range count as two each, so a gist made mostly of them is capped nearer 512 — the matching count rather than a conservative one, because the injection budget was measured to count them the same way.) Only records written before that bound existed can still be over-long. If you see a truncated-looking block, find that gist with zikaron_memory_search and amend it; the amend is rejected until the gist is shortened.

Secrets

Both stores are plaintext on disk, and retiring a memory does not erase it. The policy tells agents never to record secrets, but nothing enforces that — treat the store as readable by anything that can read your home directory.

A knowledge base is the larger plaintext surface, and it works differently. A corpus holds the verbatim text of every file indexed into it. A secret that reaches Zikaron inside an indexed file was never recorded by an agent and is not in the memory store, so neither remedy below applies: zikaron_knowledge_remove destroys that corpus's index, and the file itself is yours to fix.

If a secret does get recorded in a memory, do not just delete the row. The full-text index does not hold its own copy of the text, so deleting a content row on its own leaves that row's terms behind: the index then returns hits for a row that no longer exists, and the next write against it can corrupt the database. Two safe options, in order of preference:

  1. Stop the service, delete the whole store, and delete the spill files, which are not in it: pkill -f zikaron.service.main; rm -rf .zikaron; rm -f "${XDG_RUNTIME_DIR:-/tmp}"/zikaron*/*.json. You lose every memory and every knowledge base; the store rebuilds itself empty on the next session. The separator is ; rather than && deliberately: the service stops itself after service.idle_timeout, so it is usually not running, and pkill exits 1 when nothing matched — which with && skips the deletion silently, leaving the store you believe you just erased. The third command is the one that is easy to miss. A consolidator group too large to return is written to the runtime directory rather than into your project, with the record prose in it verbatim, so rm -rf .zikaron does not reach it and a record erased from the store can survive there until the next reboot. That glob takes every store's spill files rather than only this one's, which is recoverable rather than free: a consolidation still in flight fails its next read loudly and is re-served the group with a fresh copy. It is a Claude Code path — nothing spills under kiro.
  2. Follow the ordered procedure in design/write-policy.md §"The emergency erasure procedure, exactly", which removes the index terms and vectors before the content row.

Either way the secret is probably still live in its own original home — rotate it rather than only deleting the copy.

Uninstall

From the project directory, first the part that is the same on both harnesses:

pkill -f zikaron.service.main    # or just let it idle out
rm -rf .zikaron                  # the memory store, every knowledge base, and the logs
rm -f "${XDG_RUNTIME_DIR:-/tmp}"/zikaron*/*.json   # Claude Code only: spilled consolidator groups

The third line is there for the reason Secrets gives: spill files sit outside the project, so rm -rf .zikaron does not reach them.

Then the harness's own artefacts. Under kiro:

rm -rf .kiro/skills/zikaron-consolidate
rm .kiro/agents/zikaron-consolidator.json
mv .kiro/agents/<your-agent>.json.bak .kiro/agents/<your-agent>.json

The last line restores the backup the installer made. If you would rather keep later edits to that config, remove the hooks, mcpServers and @zikaron entries by hand instead.

Under Claude Code the artefacts are project files rather than one merged config:

rm .claude/agents/zikaron-consolidator.md
rm -rf .claude/skills/zikaron-consolidate

and then remove Zikaron's hooks, enabledMcpjsonServers and permissions entries from .claude/settings.local.json, and its two servers from .mcp.json. Both files are edited rather than deleted, because both hold settings that are not Zikaron's. All three settings keys, because the install writes three — leaving enabledMcpjsonServers behind names two servers that no longer exist, which loads nothing and breaks nothing but is not an uninstall.

Note that rm -rf .zikaron takes the knowledge bases with it. They are rebuildable — an index is a view onto files you still have — but rebuilding one takes minutes per corpus.

The embedding model is not in your project and survives all of the above. It is per user, not per project, so it is deliberately left alone by a single project's uninstall — another project using Zikaron still needs it. Remove it only when you are done with Zikaron everywhere:

rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}"/zikaron          # Linux
rm -rf ~/Library/Caches/zikaron                           # macOS

That is the 64 MB the first search downloaded. If you set $FASTEMBED_CACHE_PATH yourself, it is there instead. Finally, uv tool uninstall zikaron — or delete the virtualenv, if you installed from a source checkout.

Design and development

The full design record is in design/ — start with design/overview.md, which carries the decision table and the reasoning behind every choice above. design/coding-standards.md is binding for contributions. ./check.sh is the per-edit gate and the definition of done for a change — formatter, linter, mypy --strict, and the hermetic test suite under a coverage floor, with three marker tiers (manual, integration_kiro, integration_claude) deselected by default and run by name. ./check-matrix.sh, which runs all of that once per tested Python version, is additionally required before a milestone lands.

CI is not a third thing to run. .github/workflows/check.yml asserts the matrix's claim — every tested version green on one tree — against a commit, by running check.sh once per version. So a pull request needs nothing you would not already run locally. The macOS job is required on the same terms as the Linux ones. design/distribution.md explains what it can and cannot prove — the short version being that a CI runner has no harness binary, so a green macOS run is a statement about the hermetic gate rather than about a live install.

Metadata

Release files for zikaron 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zikaron 0.1.0
File Size Uploaded
zikaron-0.1.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for zikaron 0.1.0
File Interpreter ABI Platform
zikaron-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.8 MB

Release files / zikaron-0.1.0.tar.gz

Download URL zikaron-0.1.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
4b35dc7a0ee09b81227f3bc04172aac29e436b783494e0b36d3f3d71cd08f63b
BLAKE2b-256 checksum
How to use checksums
0c0507f5c80c8796f5ccb61862a22690a7378d679094337b058326233eac9af3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / zikaron-0.1.0-py3-none-any.whl

Download URL zikaron-0.1.0-py3-none-any.whl
Size 629.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d5d12f5adf5fb30ae16498efd2a12759e45518baa73e28a55df98fb844763c3
BLAKE2b-256 checksum
How to use checksums
10d9aa41ee4f3ce7a3a99fe0c1118bafcc72fc79bf57d7ea8cd790056e882b82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.1

2 release files

0.3.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page