kronicle-lite
Provides git-aware document blocks via bind_git, allowing notes to be tied to Git-tracked sources and marked stale when the source changes (STALE_SOURCE).
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
kronicle-lite
A local-first MCP server that tells your agent when its notes went stale.
Agents write notes, runbooks, decisions. Then the world moves: a block gets rewritten, a decision gets reversed, a procedure gets retired. Nothing in a plain memory store tells the agent. It keeps reasoning from the thing it read three sessions ago, confidently.
kronicle-lite is a small SQLite-backed document store where:
every write is an event on a hash chain you can verify from a public genesis with
sha256sum;every
recall()hit comes with a signed receipt that later answers "is this still the current version?" —VERIFIED,SUPERSEDED(here is the new one),TOMBSTONED, orUNVERIFIED;get_system_stategives the agent an orient call: pass your last cursor, get exactly what changed since, a drift banner when you have been away too long, and a retraction list of things you may have relied on that are now wrong;every hit carries an honest label —
can_act,inspect_onlyornever_act, with the reason — and an empty answer says what was searched, what was not, and whether the index was fresh. Secrets are refused before they reach the append-only chain.
It is one process, one file, no accounts. It runs over stdio next to Claude Code, Cursor, Claude Desktop, Codex, GitHub Copilot or any MCP client, and over loopback Streamable HTTP for clients that cannot spawn a process.
See it in one command
npx -y kronicle-lite demoNo LLM, no network, a temp dir that is removed afterwards. A runbook goes
in; an agent orients and reads "Deploys go to the staging cluster
stg-west-2 first."; someone changes it to stg-east-1; the agent's next
orient says, before it acts:
- [behavioral] seq 9: You previously relied on "Deploys go to the staging cluster `stg-west-2` first."
(runbook › Deploy runbook › Deploy target); current truth is "Deploys go to the staging cluster
`stg-east-1` first." (seq 9). Reconcile before proceeding.
changed: Deploys go to the staging cluster [-`stg-west-2`-]{+`stg-east-1`+} first.Then someone edits the SQLite file by hand: recall withholds the edited
text, the old receipt verifies UNVERIFIED (content_tampered),
verify-chain exits 1, rebuild restores the text from the chain. A put
holding an API key is refused before anything is chained, an empty recall
answers absent_in_searched with the scopes it did not search, and an edit
to the chain itself is caught at its seq.
0.6.0 — honest labels. Every
recallhit carriesauthority: can_act | inspect_only | never_actwith a reason; only block-level evidence (the block's exact text found, now, in its recorded source file) reachescan_act; superseded, tombstoned, withheld and drifted text never does.absence {searched, not_searched, index_fresh, verdict}separates "searched, found nothing" from "not searched", and a stale or cold index never certifies "not found". Hits you already read this session rank lower (never hidden). Secrets (API keys, tokens, private keys, connection strings) are refused on put / import / watch, or stored redacted on request; served text is scrubbed again at read time.serve --coreserves six tools;recallcaps content with a "read the whole document" pointer. Rules:docs/HONESTY.md. All additive: no hash, receipt, cursor or event layout changed.
0.5.0 closes the gaps an adoption audit found: what the agent reads is now checked against the chain, not only the log (
recall,verify,verify-chain,doctor,rebuild); instruction files are keyed by project, so two repositories sharing a data dir no longer retract each other's rules; retractions carry the old and new words;tools/listis 71% smaller;serve --httpalso serves 2025-era clients such as Cursor. Details in CHANGELOG.
0.3.0 speaks MCP 2026-07-28 (TypeScript SDK v2). Every tool has an
outputSchemaand returnsstructuredContent;tools/listandserver/discovercarry cache hints; results carryresultType. The stdio server and (since 0.5.0)serve --httpare dual-era: a client that still opens with the 2025-erainitializehandshake (2025-11-25, 2025-06-18, 2025-03-26) is served too.
0.4.0 adds off-box anchoring (
checkpoint --push,verify --remote), chained instructions (watch, retractions of kindinstruction), git-aware blocks (bind_git,STALE_SOURCE) andkronicle-lite doctor. All additive: 0.3.0 chains verify byte-for-byte.
kronicle-lite is not a memory server. It sits beside one. Keep using whatever you use for knowledge graphs or semantic recall; point kronicle-lite at the notes whose staleness would hurt.
Related MCP server: Lians Agent Memory
Install
Requires Node ≥ 20.
npm install -g kronicle-lite # or run via npx, see below
kronicle-lite serve # stdio MCP server, data in ~/.kronicle-liteClaude Code
claude mcp add kronicle-lite -- npx -y kronicle-lite serveOptional: copy skills/orient/ to .claude/skills/orient/ and add the
wake contract from examples/.claude/CLAUDE.md to your CLAUDE.md (it
includes "pulse before every world-changing command"). Then typing orient
re-grounds the agent against the chain. To push changes that land while the
agent works, add a PostToolUse hook on kronicle-lite orient --cursor C --focus retractions --json (docs/PUSH.md).
To gate world-changing commands on changes the agent has not re-read yet,
add a PreToolUse hook on kronicle-lite pending --actor <agent> --json
and block while pending_count > 0 (docs/PUSH.md "Pending"). For a check
at the moment git push / terraform apply / kubectl apply actually
runs, in any client, see exec-time pulse shims.
Notes the agent writes after reading something are linked to what it read;
when a source changes, the note is served never_act / stale_derived
(docs/HONESTY.md §6).
Cursor
Cursor reads .cursor/mcp.json (project) or ~/.cursor/mcp.json
(global) and interpolates ${workspaceFolder}, ${userHome} and
${env:NAME} in command, args, env, url and headers:
{
"mcpServers": {
"kronicle-lite": {
"command": "npx",
"args": ["-y", "kronicle-lite", "serve", "--actor", "cursor"]
}
}
}This uses the default store (~/.kronicle-lite, shared with your other
clients; documents are scoped per project since 0.5.0), which is also the
one the exec-time shims check. To keep a store per
workspace instead, add "--data-dir", "${workspaceFolder}/.kronicle-lite":
the shims (0.8.1) find a project-local .kronicle-lite/ by walking up from
the repository a command acts on, but when ~/.kronicle-lite holds a store
too they cannot tell which one the agent uses and refuse world-changing
commands until you pin one (kronicle-lite shim install --data-dir DIR).
For the HTTP mode below use { "url": "http://127.0.0.1:7417/mcp", "headers": { "Authorization": "Bearer ${env:KRONICLE_LITE_BEARER}" } }
instead of command/args. Since 0.5.0 a 2025-era HTTP client such as
Cursor's is served (statelessly); before, it was refused.
Source:
Cursor Docs — Model Context Protocol.
Claude Desktop
claude_desktop_config.json:
{ "mcpServers": { "kronicle-lite": { "command": "npx", "args": ["-y", "kronicle-lite", "serve", "--actor", "claude-desktop"] } } }Codex
~/.codex/config.toml (or .codex/config.toml in a trusted project):
[mcp_servers.kronicle-lite]
command = "npx"
args = ["-y", "kronicle-lite", "serve", "--actor", "codex"]
# optional: default_tools_approval_mode = "writes" # prompt only for put/supersede/tombstoneCodex reads the server instructions at startup and uses the first 512
characters as server-wide guidance; kronicle-lite's instructions are the
orient loop and fit in that window. Per OpenAI's docs the same file also
takes enabled_tools / disabled_tools, startup_timeout_sec (default
10) and tool_timeout_sec (default 60). Source:
OpenAI — Model Context Protocol.
GitHub Copilot (coding agent + code review)
Repository Settings → Copilot → MCP servers (under "Code, planning, and automation"); the configuration is shared by the Copilot coding agent and Copilot code review:
{
"mcpServers": {
"kronicle-lite": {
"type": "local",
"command": "npx",
"args": ["-y", "kronicle-lite", "serve", "--data-dir", "./.kronicle-lite", "--actor", "copilot"],
"tools": ["get_system_state", "audit_assumptions", "recall", "verify", "verify_chain", "anchor_status"]
}
}
}The store lives in the checkout (./.kronicle-lite): exec-time shims
installed in the same environment find it by walking up from the
repository (0.8.1), or pin it with kronicle-lite shim install --data-dir "$PWD/.kronicle-lite".
tools is required; GitHub recommends allowlisting read-only tools because
the agent calls them without asking. The six above are the ones
kronicle-lite marks readOnlyHint: true, which is also the set Copilot code
review will use (it ignores tools without that annotation). Add
put_document, supersede_block, tombstone_block (or "*") if the
coding agent should write. Secrets must be named COPILOT_MCP_* and
referenced from env. Source:
GitHub Docs — Extending Copilot coding agent with MCP.
More in examples/.
Streamable HTTP: serve --http
For a client that cannot spawn a process (or several clients sharing one store on the same box):
kronicle-lite serve --http # http://127.0.0.1:7417/mcp
kronicle-lite serve --http --port 9000 # other port
kronicle-lite serve --http --bearer-file ./token # token from a file you manage
kronicle-lite serve --http --bearer "$TOKEN" # token from the environmentAuth. Every request must carry
Authorization: Bearer <token>. By default the token lives in<data dir>/http-bearer.token, generated on first run with mode0600; print it withcat ~/.kronicle-lite/http-bearer.token.Loopback. Binds
127.0.0.1;HostandOriginare validated against loopback (DNS-rebinding defence).--host 0.0.0.0 --allow-host myhost.lan --allow-origin myhost.lanopens it up; put TLS in front.Stateless, both eras. No session id,
GET/DELETEanswer405. A 2026-07-28 request carries its protocol version in_metaand theMCP-Protocol-Versionheader; a 2025-era client that opens withinitialize(Cursor, most clients today) is served by a fresh server instance per request (0.5.0).--modern-onlyrefuses those withUnsupportedProtocolVersionError(-32022), as 0.4.0 did.Trust domain. Single tenant: the token is the whole store. See
docs/THREAT-MODEL.md.
Sixty-second tour
Works as-is in an empty directory after npm install -g kronicle-lite
(data goes to ~/.kronicle-lite; add --data-dir DIR to keep it apart):
printf '# Deploy runbook\n\n## Deploy target\nDeploys go to the staging cluster `stg-west-2` first.\n' > runbook.md
# store it: split into blocks, one hash-chained event per change
kronicle-lite put runbook runbook.md --title Runbook
# orient as an agent would, and keep the cursor it hands back
C=$(kronicle-lite orient --actor me --json | node -pe 'JSON.parse(require("fs").readFileSync(0)).cursor_compact')
# change the runbook behind the agent's back
sed -i.bak 's/stg-west-2/stg-east-1/' runbook.md
kronicle-lite put runbook runbook.md --title Runbook
# orient again with the cursor: the retraction says what changed, in words
kronicle-lite orient --actor me --cursor "$C" --focus retractions
# recompute every hash and link, and re-hash every stored block; exit 1 on a mismatch
kronicle-lite verify-chain > /dev/null && echo "chain and store verify"
# sign a Merkle checkpoint; take the chain elsewhere and rebuild from it
kronicle-lite checkpoint > /dev/null
kronicle-lite export --out events.jsonl
kronicle-lite import --data-dir ./copy --in events.jsonlIn an agent session the loop is: get_system_state → work → recall (keep
the receipts) → verify before building on an old receipt → write with your
cursor → get_system_state again next turn with the new cursor.
Anchor off-box (0.4.0)
kronicle-lite is free and local. Anchoring adds one flag —
checkpoint --push — and a URL anyone can check. The local key proves the
rows still match what the box signed; it cannot prove anything against the
box owner, who holds the key. An anchor service countersigns your signed
Merkle root with its own key and a received_at time, so a later rewrite of
an anchored range is detectable even when it was re-signed locally.
# config: flags > env > <data-dir>/config.json > default
export KRONICLE_ANCHOR_KEY=KLA_… # or {"license_key": "KLA_…"} in config.json (0600)
# export KRONICLE_ANCHOR_URL=https://anchor.isokron.ai # the default
kronicle-lite checkpoint --push # sign a checkpoint, anchor it, store the countersigned receipt
kronicle-lite verify --remote # recompute every anchored range; exit 1 on
# "local chain diverges from anchor at [a,b]"
kronicle-lite verify --remote --offline # same, from stored receipts + the pinned service keys
kronicle-lite anchor status # last anchor, status, events since (no network)
kronicle-lite anchor keys # pinned anchor service keys (add PEM-FILE | remove KEY_ID | reset)What leaves the box: the root, range, signature and your public key. Never events or content. Details in FORMAT §9 and THREAT-MODEL.
MCP:
checkpoint_chain {push: true}returns the anchor outcome next to the local checkpoint;anchor_status(read-only) compares the local chain with the last anchor (match | diverged | beyond_head | gap | unverified),remote: truealso asks the service. Once something was anchored,get_system_stateadds one banner line:anchored N events ago (status); before that it says nothing about anchoring, anddoctoronly probes the service when a license key or URL is configured.The service. The default,
https://anchor.isokron.ai, is deployed with a Free tier. When it cannot be reached,--pushstores the checkpoint locally and exits 2 withanchor service unreachable at …;verify --remoteexits 2 the same way; every local command, the MCP server andorientbehave exactly as without anchoring.What the client trusts (0.8.1). Only countersigned receipts: every comparison uses the root and range inside a receipt whose countersignature verifies against a pinned service key and whose checkpoint signature verifies against your own chain key — never the unsigned fields of the service's listing or the local
anchors.rootcolumn. The first online push orverify --remotepins the service key it is served (<data-dir>/anchor-keys.pinned.json, 0600); later keys are accepted only with a successor signature from a pinned key or byanchor keys add. Receipts you hold are checked even when the service stops listing them.http://URLs are refused unless the host is loopback or you pass--insecure-http; preferKRONICLE_ANCHOR_KEY/config.jsonover--key(which warns: argv is visible to other users).Exit codes: 0 ok, 1 divergence, conflict or an untrusted receipt, 2 usage or I/O (incl. unreachable and a refused
http://URL).
Chained instructions (0.4.0)
Your agent's instructions are notes too, and they go stale the same way:
someone edits CLAUDE.md, a rule file is rewritten, Claude Code's auto
memory index changes. kronicle-lite can chain them, so a changed instruction
is a supersession and the next get_system_state lists it as a
retraction of kind instruction, in words (You previously relied on instruction "Use pnpm, not npm." (instructions/acme-api-1a2b3c4d/claude-md › Project); current truth is "Use bun, not npm." …). A deleted file
retires its blocks (severity safety).
Default set, relative to the git worktree root (or the working directory).
Since 0.5.0 every slug carries a project key, <repo dir name>-<8 hex of the root path's sha256>, so one data dir can serve many repositories:
syncing repo B's CLAUDE.md never touches repo A's, and an agent orienting
in A is not shown B's instruction changes (all_projects: true /
--all-projects shows them). KRONICLE_LITE_PROJECT=<name> fixes the key.
file | slug |
|
|
|
|
|
|
|
|
Claude Code auto memory |
|
Upgrading from 0.4.0: the first sync of a file whose un-keyed document
(instructions/claude-md) still holds exactly that file's text renames the
document in place (a document.renamed event): same blocks, receipts keep
verifying, no retraction. An un-keyed document holding some other text (in
a shared data dir it may be another project's) is left alone.
Auto memory lives at ~/.claude/projects/<project>/memory/MEMORY.md, where
<project> is the git repository's main worktree root (shared by all
worktrees and subdirectories; outside git, the working directory) with every
non-alphanumeric character replaced by -. CLAUDE_CONFIG_DIR,
CLAUDE_CODE_PROJECT_DIR_NAME and autoMemoryDirectory are honoured as
Claude Code documents them
(memory,
sessions).
kronicle-lite watch --list prints what it resolved.
kronicle-lite put --from-instructions # one shot: chain whatever changed
kronicle-lite watch # keep chaining while files change (Ctrl-C to stop)
kronicle-lite watch docs/ AGENTS.md --interval 5000
kronicle-lite watch --list [--json] # show the resolved set and slugsUnchanged files append nothing. watch uses fs.watch on the containing
directories with a debounce (150 ms; --debounce) plus a polling pass
(--interval, default 2000 ms) that also discovers new rule files;
--poll uses polling only.
A file moved inside the project with its content intact is a rename,
not a removal: the document follows it (document.renamed, same blocks and
receipts, no retraction), whichever of the two paths is synced first. Edits
that are format-only — the same raw text up to line endings, trailing
whitespace, reflow inside a paragraph and blank lines (not inside code,
quotes or link targets) — are chained like any other but reported under
format_only_edits, not as retractions. Every other change is a retraction
with its diff: a changed character, quote, date, case or hyphen, a
double space or tab inside a line, an emphasis or list marker (*↔_, 1)↔1., renumbering: agents copy raw
text into commands), a paragraph moved to another section, and blocks
reordered within a section. A typo-shaped one carries hint: likely_typo, a label only.
Inside Claude Code: hooks, not a watcher. Claude Code already watches
files: a SessionStart hook can return watchPaths, and a FileChanged
hook runs whenever one of them changes on disk, whoever wrote it
(hooks: FileChanged).
put --from-instructions --stdin-hook is both hooks: on SessionStart it
syncs the set and answers with its watchPaths; on FileChanged it re-puts
the one file and posts a one-line systemMessage when something changed. It
never fails the hook. This is the shape of cc-anchor's watchPaths /
FileChanged hook; with a cc-anchor that does not ship it yet, or without
cc-anchor, add it to .claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "kronicle-lite put --from-instructions --stdin-hook", "timeout": 30 }] }
],
"FileChanged": [
{ "hooks": [{ "type": "command", "command": "kronicle-lite put --from-instructions --stdin-hook", "timeout": 30 }] }
]
}
}The FileChanged group has no matcher on purpose: it then runs for every
path in the dynamic watch list. Install kronicle-lite globally for hooks
(npx adds seconds per call) and pass the same --data-dir your MCP server
uses. Everywhere else — Cursor, Codex, Copilot, a plain editor — run
kronicle-lite watch in a terminal or as a user service.
Git-aware blocks (0.4.0)
A document put from a file in a git repository can remember which blob it came from, so "the runbook says X" gets checked against the runbook file as it is now, not only against the store:
kronicle-lite put deploy docs/deploy.md --bind-git # records {repo_root, commit, path, blob_sha}Over MCP: put_document {slug, title, markdown, source_path, bind_git: true}.
When the working-tree file changes (or is deleted), verify on a receipt
for that document returns STALE_SOURCE and audit_assumptions grades
claims about it stale_source, each with a diff stat (+3 −1) and the
bound vs working blob; get_system_state lists the document under a
Source drift heading. Re-put with --bind-git to rebind. It only uses
git rev-parse / git hash-object / git cat-file; outside a repository
(or without git) the put succeeds with a warning and nothing is bound.
kronicle-lite doctor (0.4.0)
kronicle-lite doctor # human-readable; exit 1 when any check fails
kronicle-lite doctor --json # the full report
kronicle-lite doctor --no-network # skip the anchor reachability probe
kronicle-lite doctor --full # verify the whole chain, not the last 1000 events
kronicle-lite rebuild # re-derive documents and blocks from a chain that verifiesRead-only: it never creates the data dir or writes a key, config or schema.
Checks: Node version (≥ 20); data dir and key-file modes (0700 dir,
0600 keys — a group/world-readable signing key, cursor secret, bearer
token or license-bearing config.json fails); PRAGMA integrity_check;
chain links and hashes over the last 1000 events plus every checkpoint;
the stored documents, revisions and blocks replayed from the chain and
re-hashed (0.5.0; a mismatch fails, rebuild repairs it once the chain
verifies); FTS5 index vs blocks; the MCP catalog this build serves vs the
fingerprint pinned for this version; config.json sanity; the last anchor
vs the local root (fails on divergence); anchor service reachability
(warning only, and only when anchoring is configured); which MCP clients have kronicle-lite configured
(~/.claude.json user and local scopes, .mcp.json, .cursor/mcp.json,
~/.cursor/mcp.json, ~/.codex/config.toml — read, never written) and with
which --data-dir. On Windows it adds a note that the POSIX modes are
no-ops there, with an icacls line to lock the data dir down.
Exec-time pulse shims
A push hook only reaches clients that have hooks, and a check made before a
Bash call misses a runbook change that lands while build && deploy runs,
or a deploy hidden in make release, bash -c "$CMD" or a wrapper script.
Shims check at the moment the real binary is executed, for any client
(Claude Code, Cursor, Codex, Hermes, a human in a terminal):
kronicle-lite shim install # git terraform tofu kubectl helm npm pnpm yarn → ~/.kronicle-lite/shims
export PATH="$HOME/.kronicle-lite/shims:$PATH" # FIRST on PATH: shell profile and each agent's environment
kronicle-lite shim status # active on PATH? real binaries? anything blocking here?
kronicle-lite shim install --git-hook # git: also a pre-push hook in this repo (catches every push)Each shim is a small POSIX sh wrapper (plus .cmd/.ps1 on Windows). A
read-only invocation (git status, terraform plan, kubectl get) execs
the real binary at once with shell builtins only, ~1 ms overhead and no
node start. A world-changing one (git push, terraform apply,
kubectl apply|delete|patch|replace|rollout …, helm install|upgrade|uninstall,
npm|pnpm|yarn publish, npm run deploy, plus your own --world "make release,docker push")
first runs kronicle-lite pulse (~0.2 s): when a document changed since
the last check and whoever read it has not re-read the current version, it
is refused with the diff (exit 77):
$ terraform apply
kronicle-lite shim: did NOT run `terraform apply` — kronicle-lite has 1 change(s) you have not reconciled (head seq 7):
- runbook › Runbook › Deploy (seq 7, supersession/behavioral): relied on "Target: prod-a" → now "Target: prod-b"; diff: Target: [-prod-a-]{+prod-b+} [not yet re-read at the current version by: agent-a]
Re-read the pending item(s) above … reading the current version clears the gate by itself. Then run the command again.
Still to re-read the current version: agent-a (the invoking actor, agent-a).
If you cannot re-read them yourself, stop and ask the operator.The gate is version-bound, like pending: once the agent recalls the
runbook again (or reads kronicle://docs/runbook) and gets the current
version, the same command runs, no acknowledgement needed. Who has to
re-read: shim install --pending-actor <agent>, else the invoking actor
(KRONICLE_LITE_ACTOR in the agent's environment), else (or with
--all-readers) everyone who read the changed block. The project is the
one the command acts on (git -C, --git-dir, terraform -chdir=, …).
kronicle-lite shim ack --through SEQ is an operator override: it needs
a terminal or --operator, is logged, and the refusal never mentions it.
The PATH shim only sees commands found through PATH; shim install --git-hook adds a git pre-push hook that checks every push (absolute
paths, sudo, aliases, hooks, rebase -x; --no-verify aside).
--mode warn prints the same and runs the command. A store that fails its
integrity check refuses world-changing commands in both modes (fail
closed), and so does a store file that is damaged (0.8.1). The shims check
the store the server uses: shim install --data-dir DIR pins it; else
$KRONICLE_LITE_DATA_DIR or ~/.kronicle-lite, plus a project-local
.kronicle-lite/ above the repository (0.8.1). No store, or two different
ones, refuses world-changing commands in deny mode; a missing kronicle-lite
CLI or a crash lets them run (fail open, noted once on stderr, logged in
.kronicle-shim/shim.log). A check that times out
(KRONICLE_SHIM_TIMEOUT, default 10 s) prints kronicle-lite check TIMED OUT … was NOT checked on every occurrence and runs the command, or refuses
it with exit 77 under shim install --on-timeout deny. Every world-changing
call's check time and outcome go to shim.log. Matching rules,
recursion guard, reconciliation, per-project acknowledgement and client snippets:
docs/PUSH.md § Shims.
Tools
tool | what it does | writes chain |
| Orient. | no |
| Grade claims | no |
| FTS5 bm25 search over live blocks; each hit carries a receipt v2 bound to the chain head and (0.6.0) | no (stores receipts) |
| Re-derive a receipt (object or | no |
| Recompute every hash and link in a range; first mismatch; checkpoint results; over the whole chain also the projection (every stored document and block re-derived and re-hashed). | no |
| Sign a Merkle root over a seq range with the local key; | no (checkpoints, anchors tables) |
| Last off-box anchor, events since, and whether the local chain still matches it ( | no |
| Create or re-put a Markdown document by slug. Unchanged blocks keep their ids; changed ones are superseded; removed ones tombstoned. | yes |
| Replace one live block with new content and a reason (secret guard as for | yes |
| Retire one live block with a reason. | yes |
Every write tool accepts an optional cursor and returns the new chain head
plus a stale_write_advisory (advisory only; the stale-write check never
blocks; the secret guard does). The kronicle://docs/{slug} resource serves
a document's current revision.
serve --core (0.6.0) serves only get_system_state,
audit_assumptions, recall, verify, put_document and
supersede_block, for small context windows: tools/list is 10.5 KB
instead of 14.2 KB. Its catalog is pinned separately
(.mcp-catalog/kronicle-lite-core.json).
Honest labels (0.6.0)
you see | it means | do |
| this exact block text is in its recorded source file right now | act on it |
| no block-level evidence (no source, document-level provenance only, model-written memory, or the source could not be read) | reason with it; verify before acting |
| superseded, tombstoned, withheld for integrity, or the source no longer holds it / is gone | don't act on it; re-read the source or recall the successor |
| nothing matched in the scopes listed in | treat as absent there; |
| the index was stale/cold, a match was withheld, or nothing was searched | no conclusion; fix the store ( |
| you read it earlier this session (it ranks lower) | skip it unless you need it again |
| the content was cut to fit | read |
Full rule table and invariants: docs/HONESTY.md.
Secrets are refused, not tombstoned (0.6.0)
The chain is append-only and every byte of a put is hashed into it. A
tombstone hides a block from recall; it does not remove the secret from the
events table, from export, or from any backup, and deleting it would break
verification by design. So put_document, supersede_block,
tombstone_block, kronicle-lite put, put --from-instructions, watch
and import scan for common secret shapes (provider API keys and tokens,
private keys, JWTs, scheme://user:password@ connection strings, bearer
headers, …_PASSWORD= / …_TOKEN= assignments) before anything is
chained, and refuse with secret_detected naming the detector and line
(never the secret). Overrides: redact_secrets: true / --redact-secrets
chains a copy with [REDACTED:<detector>] in place of each match;
import --allow-secrets imports verbatim (chained bytes cannot be redacted
without breaking their hashes). An independent read-time ruleset masks
anything that got in anyway (pre-0.6.0 data, --allow-secrets) in recall,
orient, audit_assumptions and the document resource.
Every tool declares an outputSchema and returns the same JSON as
structuredContent and as a text block (get_system_state renders Markdown
in the text block by default; response_format: "json" makes the text the
JSON too). Since 0.5.0 tools/list advertises compact output schemas (every
top-level field with its type, enum and required; nested shapes as bare
types) and short descriptions: 12.4 KB instead of 43.2 KB (13.6 KB in 0.6.0 with the honest-label fields). Results are
still validated against the full schemas, which the
kronicle://schemas/tools resource serves; the orient skill carries the
longer guidance. The emitted catalog is pinned in
.mcp-catalog/kronicle-lite.json and CI
fails on byte drift (npm run catalog:check), because a changed description
or schema silently invalidates every client's prompt-cache prefix.
Full hash recipes and payload shapes: docs/FORMAT.md.
What the local signature does and does not prove:
docs/THREAT-MODEL.md.
How it compares
Four axes that matter for "agent reasons from stale notes". As documented by each project at time of writing; corrections welcome.
local-first | tamper-evident log | staleness detection | signed receipts | |
kronicle-lite | yes (SQLite file) | yes (SHA-256 chain, public genesis, signed Merkle checkpoints) | yes (orient delta, drift banner, retractions, receipt | yes (Ed25519, chain-bound) |
reference MCP memory server | yes (JSONL knowledge graph) | no | no | no |
basic-memory | yes (Markdown files + SQLite index) | no | no | no |
mem0 | partial (self-hostable; hosted platform is the default) | no | no (dedups/updates memories; does not tell the agent) | no |
InALign | yes by default (provenance of agent actions, optional cloud) | yes (SHA-256 chain) | no | not documented |
The honest summary: the first three are memory stores and do their job; none of them answer "has what I read changed?". InALign hash-chains what an agent did; kronicle-lite hash-chains what an agent knows and signs what it was told.
Guarantees, briefly
The chain verifies from a public genesis (
sha256("kronicle-lite-genesis-v1")). No secret is needed to check it.What the agent reads is checked against the chain, not just the log:
recall,verifyand the document resource re-hash the stored text (BLAKE3) against the event that created it and refuse a mismatch;verify-chainanddoctorre-derive the whole store from the chain.Receipts bind
(block_id, content_hash, doc_slug, revision)to(chain_seq, chain_head_hash). A store reset or rewrite under the agent makes old receiptsUNVERIFIED, not silentlyVERIFIED.The signing key is local. This proves integrity against accidents and unprivileged edits; it is not third-party notarization. Read the threat model before relying on it for anything adversarial.
Orienting never fails on a bad cursor; the server recovers position from the flight recorder and says so.
(0.6.0)
can_actcomes only from block-level evidence; an emptyrecallnever claims "not found" from a stale or cold index; a briefing that hid a scope says it is partial.
Development
npm install
npm run build # tsup → dist/
npm test # builds, then vitest (unit + MCP in-process, stdio, HTTP, legacy handshake);
# CI runs it on Ubuntu (Node 20, 22) and Windows (Node 22)
npm run lint
npm run catalog:check # tools/list bytes vs .mcp-catalog/ (re-snapshot with catalog:snapshot)See CONTRIBUTING.md (DCO sign-off required) and
ROADMAP.md for what is deliberately not here yet: inclusion
proofs against published anchor day roots, chain-backed tickets, semantic
recall.
The anchor acceptance tests run against an in-test stub of the service. To
run them against the real isokron-anchor Worker under Miniflare as well,
point KRONICLE_ANCHOR_WORKER_DIR at a checkout with its dependencies
installed: KRONICLE_ANCHOR_WORKER_DIR=../isokron-anchor npx vitest run test/anchor.test.ts.
Beyond one agent
kronicle-lite is one actor, one process, one trust domain, on purpose. IsoKron is the platform growing out of the same ideas — a typed graph shared by many agents, claim fencing so parallel agents don't collide, and per-tenant chains. It is in development and not self-serve; https://isokron.ai explains what exists today and what doesn't.
License
Apache-2.0. See LICENSE and NOTICE.
Built off-grid in Cochise County, AZ by a laid-off support engineer — https://theexiledentrepreneur.com
This server cannot be deployed
Maintenance
Related MCP Connectors
Governed local-first memory and continuity for AI agents — signed receipts, optional hosted lane.
Local-first long-term memory for AI agents, with byte-recomputable signed verification receipts.
Issue & verify signed (ed25519), hash-chained, timestamped provenance receipts for agent actions.
Provenance-first database for teams and agents: every value carries sources, rules and coverage.
Related MCP Servers
- AlicenseAqualityBmaintenanceLocal-first memory engine for AI-agent teams: private/team/project ACL, associative recall, and federated sync across nodes. One SQLite file, no LLM required.127Apache 2.0

Lians Agent Memoryofficial
AlicenseAqualityCmaintenanceLocal-first bitemporal memory for AI agents with deterministic supersession, point-in-time recall, erasure proofs, and tamper-evident audit history.917Apache 2.0- AlicenseNot gradedqualityAmaintenanceProvides AI agents an auditable memory layer with provable deletion via signed certificates, a tamper-evident hash-chained audit trail, and bi-temporal time-travel recall over SQLite or Postgres.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents and tools to share durable, approved knowledge, current decisions, task ownership, and handoffs from a local-first workspace kept outside any single AI product. Markdown holds human-readable rules, goals, and narratives while SQLite tracks agents, sessions, tasks, leases, and audit events behind one transport-independent core.2MIT