Persistent memory for AI agents. Set up once. Stays out of your way. Local SQLite, auditable, no GPU, no network.
Project description
aelfrice
Your AI stops forgetting. Set up once. Stays out of the way.
Local SQLite. Fully auditable. No GPU, no network.
You correct your agent. "Got it," it says. Next session, same mistake.
aelfrice runs in the background and stops the amnesia and context drift. You write a rule once and it gets attached to every prompt thereafter — no cross-references for the agent to skip, no markdown files to maintain, nothing to remember to do.
pipx install aelfrice # or: uv tool install aelfrice
aelf setup # wire the hook into your agent
aelf onboard . # scan the current project and ingest beliefs
Then add your first rule and restart your agent:
aelf lock "never push directly to main; use scripts/publish.sh"
That's it. Your next prompt that mentions "push" already has the rule attached. From here on out aelfrice is invisible — no command to remember to run, no file to keep updated.
The
aelf lockline above is an example — substitute your own rule. Skip it entirely if you'd rather start with onboarded beliefs only and add locks as you go.
What makes aelfrice different
- The agent can't skip the rule. The
UserPromptSubmithook injects matched beliefs into your prompt before the model sees it. Not "the agent will check a file" — the file is already in the prompt. - Bit-level determinism. No embeddings, no learned re-rankers, no LLM in the retrieval path. Same write log, same code → bit-for-bit identical results across runs and machines. (PHILOSOPHY.md)
- Every belief has a confidence and a confidence-in-its-confidence. A
(α, β)Beta-Bernoulli posterior gives both:α / (α+β)says which way the belief leans,α + βsays how sure we are of that lean. New beliefs sit at low evidence (high variance, retrievable but discounted); locked beliefs short-circuit decay and pin as ground truth. - One prompt, every layer responds. Four parallel lookups happen at once, each catching what the others miss: locked rules pin to the front, keyword match for literal word overlap, anchor-text match that weighs labels and references heavier than body prose, and a graph lane that finds beliefs by how they connect rather than what they say. You don't have to pick. The build cost is paid once when you onboard the project — every prompt thereafter gets every layer for free.
- Local-only. SQLite at
<git-common-dir>/aelfrice/memory.db(or~/.aelfrice/memory.dboutside git). No network calls, no telemetry, no accounts. One brain per project, all on your machine. (PRIVACY.md) - Auditable to the row. Every belief has an
origincolumn (user_stated,user_corrected,user_validated,agent_inferred,agent_remembered,document_recent,speculative,unknown) tying it to the action that wrote it. Open the DB in any SQLite browser; nothing hidden. Query it with the tools; full traceability. - Reversible.
aelf uninstall --archive backup.aencencrypts the DB and deletes the live copy.--purgewipes it.--keep-dbleaves data untouched. No vendor lock-in by construction. You're the boss of your memories. - No GPU, no network, no inference cost. Runtime deps are
numpy,scipy,snowballstemmer— all CPU, all offline. Retrieval is a sparse-matrix query, not an LLM call. Operations are fast and local.
What it does
When you submit a prompt in Claude Code, aelfrice's UserPromptSubmit hook fires before the model sees your message. It runs a two-layer search:
L0: locked beliefs -> rules you marked permanent (always returned)
L1: FTS5 keyword -> SQLite full-text search, BM25-ranked
(Since v3.0, two additional lanes run alongside L1 by default: an L2 graph walk over typed edges from L1 seeds, and an L2.5 structural HRR rerank against anchor text and structural markers. The two-layer phrasing above is the v1.0 baseline; the four-lane stack is the v3.0 default. See ARCHITECTURE § Retrieval.)
The matching beliefs come back as an <aelfrice-memory> block prepended to your prompt. The agent reads it as part of the prompt — it doesn't have to remember to check a file.
<aelfrice-memory>
[locked] never push directly to main; use scripts/publish.sh
[locked] commits must be SSH-signed with ~/.ssh/id_rrs
the publish script runs gitleaks before tagging
</aelfrice-memory>
push the release
Default budget is 2,400 tokens per prompt. Locked beliefs are the always-injected pool — every lock ships on every prompt, in full, regardless of relevance score. Lock count is your baseline-context budget knob: if you've locked 200 things, every session opens with all 200, by your design. The non-locked pool (FTS/L1) is BM25-ranked and truncated to fit.
Session-start enrichment vs per-turn retrieval
The first UserPromptSubmit of a new session carries extra context compared to subsequent prompts in the same session.
Per-turn retrieval (every prompt): BM25-ranked beliefs matching your current prompt, wrapped in <aelfrice-memory>.
Session-start enrichment (first prompt only): a <session-start> sub-block is embedded inside that same <aelfrice-memory> envelope. It contains two sections:
<locked>— all user-locked beliefs (the full L0 pool, same asaelf locked).<core>— load-bearing unlocked beliefs: those with corroboration count ≥ 2, or posterior mean ≥ ⅔ with sufficient feedback mass (α+β ≥ 4). Same selection asaelf core.
Detection is session-scoped. aelfrice records the last-seen session_id in <git-common-dir>/aelfrice/session_first_prompt.json. When the id changes (or the file is absent), the current call is treated as the first prompt of a new session; subsequent calls with the same id skip the sub-block.
Cost: one additional list_locked_beliefs() + one belief-id walk per session, not per prompt.
What it remembers
| You run | It stores |
|---|---|
aelf lock "never commit .env files" |
Permanent rule. Returned on every retrieval. |
aelf onboard . |
Walks the project — git log, README headings, code structure — and ingests structural facts. |
aelf feedback <id> used |
Bayesian feedback. Strengthens the belief's posterior. |
aelf feedback <id> harmful |
Weakens it. After enough independent harmfuls, locks auto-demote. |
| (passive — no command) | Default-on auto-capture: every prompt/response turn is logged to per-project JSONL and ingested into the belief graph at compaction; successful git commit events are ingested too. See INSTALL § hooks. Opt out with aelf setup --no-transcript-ingest --no-commit-ingest. |
Each belief carries a (α, β) Beta-Bernoulli posterior. α / (α+β) is the confidence. Locks short-circuit decay; everything else fades over time so stale beliefs eventually drop out of retrieval.
aelf stats
# beliefs: 142 locked: 8 threads: 67
# feedback: 31 avg_confidence: 0.71
Why files don't solve this
The standard workaround for "agent keeps forgetting" is more files: STATE.md, DECISIONS.md, a CLAUDE.md with cross-references to runbooks. Every cross-reference is a bet that the agent will read the file, find the right section, and follow what it says.
The failure modes are predictable. The agent reads the rule and runs git push anyway. Cross-references break silently after compaction. State files rot the moment you forget to update them. Each new failure mode begets another file.
aelfrice replaces the chain with a mechanism. The hook injects matched beliefs as part of your prompt, before the agent sees it. Nothing voluntary. Nothing the agent can skip.
| Manual approach | What breaks | aelfrice |
|---|---|---|
| Rules in CLAUDE.md | Agent reads them, doesn't follow them | Injected per-prompt, not per-session |
| Cross-references | Agent skips or reads the wrong section | Matched beliefs injected directly |
| Hand-maintained state files | One missed update breaks the chain | State is the SQLite DB; no manual sync |
Why this is a memory system, not a key-value store
Leonard Lin's review of agentic-memory implementations frames the bar bluntly:
The biggest differentiator is not "vector DB vs SQLite" — it's write correctness and governance: provenance / audit trail, write gates / confirmation, conflict handling, reversibility (inspect / edit / delete).
By that bar, "a vector store with a similarity query" is not a memory system — it is a search index. A memory system has to answer who wrote this, when, via what ingress, what supersedes it, and how do I take it back. Here is how aelfrice answers each.
| Lin's pillar | What it means | aelfrice mechanism |
|---|---|---|
| Provenance / audit trail | Every row traces back to the action that wrote it: who, when, via what ingress channel. | origin column on every belief — user_stated, user_corrected, user_validated, agent_inferred, agent_remembered, document_recent, speculative (src/aelfrice/models.py). Append-only ingest_log table records every raw input with its source kind, source path, and session id — tear the DB down and rebuild it from this log alone. content_hash binds each row to its content. belief_versions / edge_versions sidecar tables carry per-scope version vectors. Open the file in any SQLite browser; nothing is hidden. |
| Write gates / confirmation | Persistence is not unconditional. Some writes need explicit approval; external-origin claims cannot be laundered into ground truth. | Two-tier lock state: lock_level ∈ {none, user} with a locked_at timestamp and a demotion_pressure counter that blocks silent removal. aelf lock is the only path to L0 user-asserted ground truth — locked beliefs short-circuit decay and pin to every retrieval. aelf confirm only bumps a Beta-Bernoulli posterior — it cannot promote origin without an explicit aelf promote. The (α, β) posterior means feedback accumulates rather than overwrites; one harmful click does not erase a belief, it nudges the mean. |
| Conflict handling | Competing claims about the same thing are surfaced, not silently overwritten. | First-class edge types CONTRADICTS and SUPERSEDES in src/aelfrice/models.py — disagreement is a graph relation, not a vanished row. New facts about the same (entity, property) chain via SUPERSEDES rather than mutating in-place, leaving the prior claim queryable. Per-scope version vectors (#204 / #205) preserve causal ordering for concurrent edits across worktrees. |
| Reversibility (inspect / edit / delete) | Mutations remain auditable and partially undoable. The user is the boss of their memories. | aelf delete <id> writes an audit row before the cascade (cli.py:_cmd_delete). aelf unlock writes a lock:unlock audit row (promotion.py); aelf promote and its inverse leave promotion:revert_to_agent_inferred rows; aelf feedback lands rows in feedback_history. The ingest_log is append-only and replay-capable. At the top level: aelf uninstall --archive backup.aenc encrypts and removes the live DB, --purge wipes, --keep-db leaves data untouched. No vendor lock-in by construction. |
What you get for free
Running in the background. No action required after aelf setup.
- Passive capture. Default-on transcript-ingest, commit-ingest, and session-start hooks (since v2.1). Session activity flows into the belief graph without you typing
aelfat all; opt out per-hook viaaelf setup --no-transcript-ingest,--no-commit-ingest,--no-session-start. See INSTALL § default-on hooks. - Determinism. Stdlib + SQLite. No embeddings, no learned re-rankers, no LLM in the retrieval path. Every result traces to the action that wrote it.
- Local-only. SQLite at
<repo>/.git/aelfrice/memory.db. No telemetry, no network calls, no accounts. Per-project isolation by construction. See PRIVACY.md. - Removable.
aelf uninstall --archive backup.aencencrypts the DB to a file, then deletes it. Or--purgefor a full wipe.
Tradeoff: no fuzzy semantic recall. See PHILOSOPHY.md.
Day-to-day surface
After aelf setup you should rarely type aelf again. The day-to-day commands are six:
aelf onboard . # once per project — scan and ingest
aelf lock "never push to main" # add a permanent rule
aelf locked # see what rules are active
aelf search "push to main" # check what the agent will see
aelf status # quick health summary
aelf setup / aelf doctor # initial install + verification
Everything else (deeper diagnostics, archive/uninstall, migration tools, hook entry-points called by Claude Code itself) is callable but not something you reach for in normal use. aelf --help shows the everyday surface; aelf --help --advanced lists the rest. Full reference: COMMANDS.
The same operations are also available as MCP tools and /aelf:* slash commands — same library underneath. See MCP and SLASH_COMMANDS.
Reasoning surfaces (v3.0)
Two slash commands let the agent reach back into the belief graph mid-turn instead of relying only on the auto-injected retrieval block.
/aelf:reason <query> — walks the belief graph from BM25-seeded starting points and emits a structured reasoning trace: hops with edge-type breadcrumbs, a VERDICT (SUFFICIENT / INCOMPLETE / CONTRADICTED / IMPASSE), IMPASSES (typed gaps, ties, or constraint failures), and SUGGESTED UPDATES — (belief_id, direction, note) rows that map straight to aelf feedback so the conclusion closes the loop on the beliefs that fed it. Each impasse is dispatched to a role-tagged subagent (Verifier / Gap-filler / Fork-resolver) so a single /aelf:reason invocation produces both an answer and the work needed to resolve what's still uncertain. Peer-aware: hops in foreign scopes are annotated [scope:<name>]. Spec: COMMANDS § reason, v2_wonder_consolidation_R_final.md.
/aelf:wonder <topic> — the research surface. Pair to /aelf:reason: where reason walks the graph you already have, wonder grows the graph by going off and learning things. Given a topic, it runs gap analysis on what the store already knows, generates 2–6 orthogonal research axes (always-on domain_research + internal_gap_analysis; conditional contradiction_resolution / uncertainty_deep_dive / coverage_extension), fans out one subagent per axis to research and write up findings, and persists the merged research as new speculative beliefs via wonder_ingest. Those phantoms sit in the graph at low evidence — discoverable by retrieval and by the next /aelf:reason <topic> — until you promote them with aelf promote (or lock the statement, which auto-promotes a matching phantom). Agent-count shorthand (quick 2-agent, deep 4-agent) is recognised in the query string. The two-step rhythm is the point: /aelf:wonder to add fresh thinking to the graph, then /aelf:reason to draw conclusions across it. A no-arg /aelf:wonder (no topic) falls back to a graph-walk consolidation pass — useful for finding mergeable or contradicting belief clusters — but the headline behavior is research with a topic. Spec: COMMANDS § wonder, v2_wonder_consolidation_R_final.md.
Both surfaces are deterministic in the aelfrice layer (verdict classification, impasse derivation, axis generation, suggested-update mapping) — the only LLM call happens when the host agent dispatches a subagent for a /aelf:reason impasse or a /aelf:wonder axis, and those calls run under the host's own credentials, not aelfrice's.
Status
Latest stable: v3.0.0 (2026-05-13) — wonder lifecycle complete (#542), wonder/reason parity (#645) with VERDICT/IMPASSES + dispatch policy + suggested updates, HRR persistence default-ON with split-format save/load (#553), type-aware compression A2 bench gate (#434), eval-harness host-agent replay + LLM-judge stage + Cohen's-κ runner (#592, #600, #687), read-only federation with scope field + peer DB FTS5/BFS (#650/#655/#688/#690), query_strategy default flipped to stack-r1-r3 (#718), phantom-promotion Surface A + Surface B (#550), sentiment-feedback UPS hook (#606). Ratified design decisions: PHILOSOPHY stays deterministic (#605); multimodel deferred (#607). Milestone tracker: #608. Full entries: CHANGELOG.md § 3.0.0.
Per-version detail: docs/ROADMAP.md. Open issues / known limits: docs/LIMITATIONS.md.
Documentation
- Getting started: Install · Quickstart
- Reference: Commands · MCP · Slash commands · Config
- Background: Architecture · Philosophy · Privacy · Limitations
- Development: Releasing · Changelog · Contributing · Security
Citation
@software{aelfrice2026,
author = {robotrocketscience},
title = {aelfrice: deterministic Bayesian memory for AI coding agents},
year = {2026},
url = {https://github.com/robotrocketscience/aelfrice},
license = {MIT}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aelfrice-3.0.0.tar.gz.
File metadata
- Download URL: aelfrice-3.0.0.tar.gz
- Upload date:
- Size: 7.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f30a274c7e3224db7a22dd11e661bbab28c606802cae6351dfd5e411b45a27d
|
|
| MD5 |
160cedbe9253dc1e877de8598297262a
|
|
| BLAKE2b-256 |
ac38a9b88a4ab19ff1831290feaaeb5e1a154a5385853830299f7cc67770b6ef
|
Provenance
The following attestation bundles were made for aelfrice-3.0.0.tar.gz:
Publisher:
publish.yml on robotrocketscience/aelfrice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aelfrice-3.0.0.tar.gz -
Subject digest:
4f30a274c7e3224db7a22dd11e661bbab28c606802cae6351dfd5e411b45a27d - Sigstore transparency entry: 1525236576
- Sigstore integration time:
-
Permalink:
robotrocketscience/aelfrice@b3022b2e9b9bc944de8eccf01ea9fa007cc4ee33 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/robotrocketscience
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b3022b2e9b9bc944de8eccf01ea9fa007cc4ee33 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aelfrice-3.0.0-py3-none-any.whl.
File metadata
- Download URL: aelfrice-3.0.0-py3-none-any.whl
- Upload date:
- Size: 527.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
225796bf9ad15a97c7121ec23dd6f40e0ffe8462ad9d887534cd32b76e0540d4
|
|
| MD5 |
cc2d7de66cfeb9fd767771bf09b9fa9c
|
|
| BLAKE2b-256 |
dd6ccab2b338388918f350cd9ee3694d1841194eb4b8f2a55d55064309c2005f
|
Provenance
The following attestation bundles were made for aelfrice-3.0.0-py3-none-any.whl:
Publisher:
publish.yml on robotrocketscience/aelfrice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aelfrice-3.0.0-py3-none-any.whl -
Subject digest:
225796bf9ad15a97c7121ec23dd6f40e0ffe8462ad9d887534cd32b76e0540d4 - Sigstore transparency entry: 1525236599
- Sigstore integration time:
-
Permalink:
robotrocketscience/aelfrice@b3022b2e9b9bc944de8eccf01ea9fa007cc4ee33 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/robotrocketscience
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b3022b2e9b9bc944de8eccf01ea9fa007cc4ee33 -
Trigger Event:
push
-
Statement type: