RecallWeave README

The project's README from github.com/Herricka/RecallWeave. As one markdown file: README.md.

🧵 RecallWeave

a local-first agent memory lifecycle engine

Threads of memory, laid one pass at a time, into a cloth you can inspect, mend, and fold — never a black box, never the cloud.

the memory loom


Every long-running agent accumulates a tangle of things it "knows": facts it was told, preferences it inferred, procedures it was taught, events it witnessed. Most memory tooling reaches straight for a vector database and calls the problem solved. But a vector index answers exactly one question — "what is similar to this?" — and stays silent on all the questions a memory actually has to answer over its lifetime:

RecallWeave is the loom, not the thread. It is the lifecycle layer: it gives every memory an identity, a provenance, a confidence, a time-to-live, and a place in an append-only, tamper-evident weave. It dedupes, detects conflicts, forgets on schedule, compacts, verifies its own integrity, and exports a portable memory-pack that a companion viewer turns into a woven tapestry you can read at a glance.

It is written in Rust with the standard library only — no serde, no clap, no crypto crates — so the entire format, from the JSON codec to the hash chain, is a few hundred lines you can audit in an afternoon. The companion tapestry viewer is TypeScript and consumes only the exported pack.

What it is not. RecallWeave does no semantic search. Matching is lexical. It is a lifecycle engine you run beside a vector store, not a replacement for one. The honest limitations section means every word of that.


Table of contents


🪡 The weaving metaphor

A loom holds a set of taut vertical threads — the warp — under tension. The weaver passes a shuttle back and forth, trailing a horizontal thread — the weft — over and under the warp. Row by row, the passes accumulate into cloth. Once a row is beaten into place it is fixed; you do not go back and re-thread it. To change the finished cloth you either weave more on top, or you cut a thread and let it fray.

RecallWeave takes that literally:

loom RecallWeave
a pass of the shuttle appending one event to the log
a weft thread a single memory
the colour of a thread the memory's kind
thread thickness / length confidence
a finished, beaten-in row an immutable log record
cutting a thread a tombstone
re-weaving over an old row a supersession
a thread that frays with time a TTL expiry
folding the cloth to store it compaction
checking the weave for slips integrity verification
rolling the cloth off the loom to show someone exporting a memory-pack

The two animated panels in this README (docs/assets/memory-loom.svg and docs/assets/forgetting-weave.svg) are hand-authored SVGs that show the shuttle weaving typed threads, and threads fading, being cut, and re-woven during forgetting and compaction. They are local files — no external assets, no tracking — so they render anywhere the SVG does.


🚀 Quick start

You need a Rust toolchain (1.74+). Node 18+ is only needed for the viewer.

# build the engine
cargo build --release

# point every command at a store directory with --dir (default: .RecallWeave)
alias rw='./target/release/RecallWeave --dir mymemory'

# weave in some memories
rw add --kind semantic   --content "prod region is eu-west-1" --tags infra,region
rw add --kind preference --content "prefers concise answers"  --tags style --confidence 0.8
rw add --kind procedural --content "rotate the API token via ops runbook step 3" --tags ops
rw add --kind episodic   --content "deploy failed at 14:03"    --tags infra --ttl 86400

# read the cloth
rw list
rw query --kind preference --contains concise
rw stats

# check the weave has not been tampered with
rw verify

# roll it off the loom into a portable pack
rw export --out memory-pack.json

Then turn the pack into a tapestry:

cd viewer
npm install
npm run build
node dist/cli.js ../memory-pack.json --svg tapestry.svg

🎨 The threads: memory kinds

RecallWeave is typed on purpose. A memory's kind changes how you should treat it, so the type is first-class rather than a free-text tag.

Every thread also carries the metadata that makes a memory trustworthy: provenance (source + detail — why do I believe this?), confidence in [0,1], tags, links to related memories, a fingerprint for dedupe, and lifecycle fields (ttl_secs, supersedes, superseded_by, tombstoned). The full schema lives in docs/MEMORY.md.


🧶 The loom: the append-only weave

The store is a directory (default .RecallWeave/) holding a single log.jsonl — one JSON event per line, in the order it happened. Three event types are ever written:

The current state is not stored anywhere. It is derived by replaying every event in order:

   log.jsonl (events)  ──replay──▶  materialized state (id → Memory)
         ▲                                   │
         │ append                            ▼
    assert / tombstone / supersede    query · verify · export · compact

Because state is a pure function of the log, the log is the single source of truth and nothing is silently lost. You can always ask "how did I come to believe this?" and replay to find out.

Tamper-evidence: the integrity chain

Each record commits to the one before it. It stores prev — the digest of the previous record — and digest, a 256-bit hash over its own canonical payload chained with prev. The first record's prev is 64 zeros (GENESIS). Alter any byte of any record and every digest downstream stops matching:

$ rw verify
integrity OK: 7 records, chain intact

# ... someone edits log.jsonl by hand ...
$ rw verify
integrity FAILED (7 records):
  - seq 3: digest mismatch (record contents were altered)

This is a checksum chain, not a signature. It catches accidental corruption and casual tampering. It does not stop a determined attacker who rewrites the whole file — for that, sign the exported pack out of band. (Said plainly again in the limitations.)


🧷 Tending the cloth: the lifecycle

Deterministic dedupe

Before fingerprinting, content is normalized: trimmed, internal whitespace collapsed, lowercased. So these two are the same memory:

rw add --kind semantic --content "prod region is eu-west-1"
rw add --kind semantic --content "PROD region   is  eu-west-1"   # → deduped

If a live memory of the same kind already has that fingerprint, no new record is written and the existing id comes back with deduped: true. Fully deterministic, fully explainable — and, honestly, only lexical: a paraphrase is a different thread.

Supersession — re-weaving a row

When a fact changes, you do not delete the old one; you weave a new row over it:

rw supersede --old mem_9f21b35efe51 --content "prod region is us-east-1"

The old memory gains a superseded_by pointer and drops out of the live set; the new one records what it supersedes. History is intact until you compact.

Conflict detection — spotting crossed threads

conflicts reports (and never mutates) two kinds of clash among live memories:

  1. duplicate-content-different-kind — the same normalized content stored as, say, both semantic and preference. Usually a modelling slip.
  2. preference-polarity — two live preferences that share a subject token but differ on a recognised antonym (dark/light, concise/verbose, …):
$ rw conflicts
1 conflict(s):
  [preference-polarity] mem_0572… <-> mem_881f…: preferences appear to conflict on 'dark' vs 'light'

The shared-subject guard means "prefers dark theme" vs "prefers light beer" does not trip it. This is a small, transparent heuristic — a nudge, not an oracle.


🍂 Forgetting, on purpose

the forgetting weave

Forgetting is a feature, not a failure. Three mechanisms let the cloth shed what it no longer needs:

$ rw compact
compacted 12 -> 6 records (dropped 3 tombstoned, 2 superseded, 1 expired)
$ rw verify
integrity OK: 6 records, chain intact

The forgetting-weave.svg panel animates exactly this: a TTL thread fading to dashed as its little clock drains, a tombstoned thread being cut with a marker dropped in the gap, and a compaction sweep tightening the remaining cloth.


🖼 The tapestry viewer

The engine and the viewer are decoupled by design: the viewer never reads the log or understands the hash chain. It consumes only the portable memory-pack (format: "recallweave-pack"), a self-describing JSON document with the live memories, per-kind and per-tag counts, detected conflicts, and the integrity head.

The TypeScript viewer (viewer/) renders two things from a pack:

cd viewer && npm install && npm run build
node dist/cli.js ../examples/memory-pack.json                     # text report
node dist/cli.js ../examples/memory-pack.json --svg tapestry.svg  # + SVG
node dist/cli.js ../examples/memory-pack.json --svg -             # SVG to stdout

The rendering is deterministic: the same pack always yields byte-identical SVG, and all user content is XML-escaped (there is a test for a <script> payload).


📖 Command reference

Every command takes --dir <path> (default .RecallWeave), an optional --now <epoch> to pin the clock for reproducible runs, and --json where a machine-readable form makes sense.

command what it weaves
add assert a memory (--kind --content [--confidence --ttl --tags --links --source --detail --supersedes])
supersede replace a memory (--old --content [--kind --confidence ...])
list [--all] list live memories (--all includes retired)
query filter (--kind --tag --contains --min-confidence --source --sort --limit [--all])
get <id> show one memory in detail
forget <id> [--reason] tombstone a memory
gc tombstone all TTL-expired memories
conflicts report conflicts among live memories
compact rewrite the log, dropping retired records
verify check the integrity chain (non-zero exit on failure)
export [--out file] write a portable memory-pack
stats summarize the store
help, version the usual

Run RecallWeave help for the full flag list.


🧪 Worked example: an agent's week

The repository ships a real, runnable demo (see examples/). It builds a store, records a spread of memories, changes its mind, lets an episodic note expire, and exports a pack — every step executed by the real binary, not mocked:

# from the project root, on any OS with a shell
sh examples/demo.sh          # bash / sh
# or
pwsh examples/demo.ps1        # PowerShell

That produces examples/memory-pack.json (committed) whose tapestry is rendered to docs/assets/tapestry-demo.svg. The demo store shows a superseded region fact, two preferences in genuine polarity conflict, a procedural runbook, and an episodic note with a one-day TTL — the exact scene both README panels illustrate.


🎯 Design principles

  1. Lifecycle over lookup. The hard part of agent memory is not finding a string; it is knowing what to trust, what changed, and what to forget.
  2. The log is the truth. State is always a pure replay of an append-only log, so history is inspectable and forgetting is a deliberate act.
  3. Deterministic and explainable. Same inputs, same ids, same fingerprints, same SVG. Every dedupe and every conflict has a reason you can print.
  4. Auditable to the bottom. Standard library only; the JSON codec, hash chain, and CLI parser are all short enough to read.
  5. Local-first. Everything is a file on your disk. No network, no service, no telemetry. Portability is an explicit export, not an accident.
  6. Honest about scope. It says loudly what it does not do.

⚠️ Honest limitations

Read this. It is the difference between using RecallWeave well and being surprised by it.

These are deliberate trade-offs for a small, auditable, local-first tool. See docs/MEMORY.md for the reasoning behind each.


🛠 Building, testing, and project layout

make build     # cargo build --release
make test      # cargo test  +  viewer tests
make demo      # run the demo and regenerate the tapestry SVG
make verify    # build, test, clippy, fmt-check, viewer build+test
make clean     # remove build artifacts
RecallWeave/
├── src/                    # Rust engine (stdlib only)
│   ├── json.rs             #   hand-written JSON codec
│   ├── hash.rs             #   FNV fingerprints + 256-bit digest chain
│   ├── model.rs            #   Memory, Event, LogRecord + (de)serialization
│   ├── store.rs            #   append-only log, dedupe, conflicts, compaction…
│   ├── query.rs            #   deterministic filtering
│   ├── lib.rs              #   library crate
│   └── main.rs             #   the CLI
├── tests/                  # integration + black-box CLI tests
├── viewer/                 # TypeScript tapestry viewer
│   └── src/                #   pack parser, text report, SVG renderer, tests
├── examples/               # runnable demo + committed memory-pack
├── docs/
│   ├── MEMORY.md           # the data model & lifecycle reference
│   └── assets/             # hand-authored animated SVGs
├── Cargo.toml  Makefile  LICENSE  CHANGELOG.md
└── .github/workflows/ci.yml

📜 License

MIT — see LICENSE. Weave freely.

RecallWeave · a loom for what your agent remembers · local-first, dependency-light, honestly scoped

RecallWeave (Herricka/RecallWeave)

Searching Database…

这是什么东西?RecallWeave 是一个本地优先的智能体记忆生命周期引擎:它把带类型的记忆存进一条只能追加、带哈希链的日志里,并负责去重、冲突检测、遗忘、压缩和可移植导出。
这是向量数据库吗,还是别的什么?它明确不是语义搜索或向量存储;只做词法匹配,设计上是配合向量数据库一起用的,作为生命周期层来追踪来源、冲突和 TTL。
我怎么把它跑起来?用 1.74+ 工具链构建 Rust 二进制文件,使用 CLI 命令(add、list、query、forget、gc、compact、verify、export)来管理 .RecallWeave/ 目录里的记忆,然后可以选择导出一个 memory-pack,并用配套的 TypeScript 查看器来展示。
我需要装 Node.js 或额外的 crate 吗?核心引擎只需要一个 Rust 工具链,完全只用标准库(不用 serde、clap 或任何加密 crate);只有当你想运行 TypeScript 的 tapestry 查看器时才需要 Node 18+。
哈希链能挡住存心搞破坏的人改动日志吗?不能——它只是校验和链,不是加密签名,所以能发现意外损坏或随手的小改动,但挡不住把整个文件重写的人;真要防篡改,你得另外对导出的 pack 做签名。
这些数据会离开我的电脑吗?不会——所有东西都以文件形式存在你的磁盘上,没有网络请求、没有服务、也没有遥测;数据只能通过显式导出 memory-pack 才会分享出去。
它追踪的记忆有哪几种类型?四种带类型的记忆:semantic(持久事实)、preference(可能互相冲突的偏好)、procedural(操作方法类知识)和 episodic(带时间戳的事件,通常带 TTL)。
它是怎么发现相互冲突的记忆的?它会对现存记忆运行 conflicts 检查,标记出内容相同但类型不同的重复项,以及偏好极性冲突——也就是两条偏好主题相同,但其中 dark/light 这类被识别出的反义词相互矛盾。
遗忘具体是怎么实现的?记忆可以通过 TTL 过期(用 rw gc 清理)、用 rw forget 手动加墓碑标记,或通过 rw compact 永久删除——compact 会重写日志、去掉已退役的记录,同时保留现存记忆的 id 和时间戳不变。
tapestry 查看器能让我看到什么?这是一个独立的 TypeScript 工具,只读取导出的 memory-pack JSON,渲染一份文本报告和一张确定性的 SVG:每条记忆是一根线,按类型着色、按置信度决定粗细,冲突则画成打叉的红色线。
Creator
Herricka
Size
0.6 MB
Stars
175
Age
11y
Commits · 90 Days