Skip to content

Commit 4808b5f

Browse files
committed
Document plugin packaging and installation instructions
1 parent b5b5490 commit 4808b5f

11 files changed

Lines changed: 8110 additions & 0 deletions

‎.claude/Research/memory-alternatives.txt‎

Lines changed: 110 additions & 0 deletions
Large diffs are not rendered by default.

‎.firecrawl/glean-kg.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎.specs/09-scratchpad.toml‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Spec 09 — Scratchpad
2+
# Raw transcripts -> analysis -> memories. A TUI to inspect both ends.
3+
4+
[spec]
5+
id = "scratchpad"
6+
version = "0.2.0"
7+
status = "draft"
8+
date = "2026-08-16"
9+
10+
[thesis]
11+
flow = "raw chat transcripts -> analyzed for memories -> official memories written"
12+
why = """
13+
The session agent is the worst judge of its own session: it overvalues effort it
14+
spent and cannot see its own corrections as corrections, having already absorbed
15+
them. A separate reviewer reading the finished transcript sees what the
16+
participant structurally cannot. Running it out of band, over completed
17+
transcripts, means it can never collide with a live session.
18+
"""
19+
visible = """
20+
Both ends are inspectable, so the filter is tunable by looking at it rather than
21+
by guessing. Every memory traces back to the chat that produced it.
22+
"""
23+
24+
[raw]
25+
what = "Claude Code's own session transcripts — one JSONL per chat, already on disk"
26+
where = "${CLAUDE_CONFIG_DIR:-~/.claude}/projects/<project-slug>/"
27+
rule = "read-only view. We never write, move, or delete them. There is no capture step."
28+
boundary = "RAW sits outside the memory root; the scratchpad opens transcripts read-only and no other crate gets transcript access"
29+
30+
[memories]
31+
what = "ordinary memories per spec 01 — the judge writes nothing special-cased"
32+
storage = "topical namespaces, unchanged"
33+
grouping = """
34+
Per-chat grouping is a VIEW, not a layout. Memories are stored topically and
35+
rendered grouped by their `source` field. Storing them under the originating
36+
chat would key location by WHEN instead of WHAT, and spec 02's dedup probe
37+
(namespace-scoped) would never fire across sessions.
38+
"""
39+
source = "`session <uuid>`, matching the transcript filename — the join between the two tiers"
40+
41+
[judge]
42+
input = "the whole transcript, unmodified"
43+
context = """
44+
Requires a 1M-context model (Gemini Flash Lite and equivalents). At that size the
45+
raw transcript fits — this project's largest sessions are ~1.2 MB, roughly 300k
46+
tokens — so there is no stripping, chunking, or multi-pass logic. Send the file.
47+
"""
48+
transport = "OpenAI-compatible chat completions; model is a base-URL and name swap (Groq, Gemini, OpenAI, local Ollama)"
49+
placement = "upstream of memory_store, which keeps spec 02's invariants untouched. The judge decides whether to call it, never how it works."
50+
fail_open = "unreachable, timed out, or malformed = nothing is written and the session is unaffected. Never blocks, never delays."
51+
scheduling = "out of band, queued on Stop and PreCompact, over completed transcripts only"
52+
egress = """
53+
EXPLICIT OPT-IN, no default endpoint. Transcripts carry source, scrollback, and
54+
client work; today they reach Anthropic by the user's choice of Claude Code, and
55+
a third party is a new decision. A local model keeps zero egress.
56+
"""
57+
confinement = "all network code lives in memory-judge; memory-core does not depend on it, so the zero-network invariant stays enforceable by the dependency graph"
58+
ledger = """
59+
.index/reviewed.json records which sessions were analyzed and how many memories
60+
each produced. Needed because a chat that correctly yielded nothing is otherwise
61+
indistinguishable from one never read — and the zero-count entries are exactly
62+
what shows whether the rules are too strict.
63+
"""
64+
65+
[judge.rules]
66+
layers = "a global ruleset applies everywhere; an optional per-project file drills down. No project file is the normal case."
67+
global_path = "${XDG_CONFIG_HOME:-~/.config}/fff-memory/judge.toml"
68+
project_path = "<memory root>/judge.toml"
69+
global_owns = ["what counts as memory-worthy", "sentiment rules", "the never-store floor: secrets, credentials, routine activity", "alias quality per spec 01"]
70+
project_owns = ["extra events in this codebase's vocabulary", "suppression of named global rules", "path and topic exclusions", "default namespace"]
71+
merge = "project extends global, may suppress named rules and narrow scope, may not relax the never-store floor"
72+
73+
[judge.criteria]
74+
principle = """
75+
Ask for observation, not evaluation. 'Was this important?' is judgment, which
76+
small models do badly — they over-produce, the exact failure this design exists
77+
to prevent. 'Did the user correct the model?' is pattern recognition, which they
78+
do well.
79+
"""
80+
events = [
81+
"user corrected the model, especially twice on the same point -> feedback",
82+
"user stated a preference, constraint, or convention -> preference",
83+
"a decision was made and a reason given -> decision",
84+
"an approach was tried and abandoned, with the reason -> lesson",
85+
"a durable project fact not derivable from the repo -> fact",
86+
]
87+
sentiment = """
88+
Sentiment marks WHERE to look: correction, the same correction repeating,
89+
explicit confirmation, prohibitions ('never', 'always', 'don't'), and visible
90+
frustration. Repetition and prohibition weigh most; a lone confirmation is not
91+
enough to write anything. It marks the moment — the memory's content still comes
92+
from the substance of the exchange.
93+
"""
94+
95+
[judge.history]
96+
requirement = "the judge sees current memories, their update history, and what was archived or deleted"
97+
why = [
98+
"dedup — update an existing handle instead of re-deriving a stored fact",
99+
"no resurrection — deleting a memory is the user saying they do not want it; a judge blind to deletions undoes that decision every session",
100+
"reinforcement — a fact re-established in a later session is evidence the memory is right, not a reason to write a second copy",
101+
]
102+
sources = "memory root, .archive/, and the access log (spec 05)"
103+
104+
[tui]
105+
toolkit = "ratatui — terminal UI, no browser, no socket, no daemon"
106+
hierarchy = "PROJECTS -> { RAW | MEMORIES } -> per-chat"
107+
navigation = "only the two tier labels are real tabs; the chat level is a filterable, recency-ordered list"
108+
does = ["read a transcript", "read the memories it produced and jump between them", "see chats that produced nothing — the signal for tuning rules", "edit or delete a memory"]
109+
never = "RAW is not editable, and the TUI is not an approval gate — memories are live when written. Requiring a click reintroduces the manual curation this project rejected, because people forget."
110+
launch = "fff-memory scratchpad"
111+
112+
[crates]
113+
new = [
114+
"memory-judge (lib) — transcript reading, OpenAI-compatible client, memory emission. The only crate with network access.",
115+
"memory-tui (lib) — ratatui views over both tiers.",
116+
]
117+
wiring = "both wired into the existing binary; one binary preserved"
118+
119+
[supersedes]
120+
note = "where this conflicts with an earlier spec, this wins and the earlier text gets rewritten"
121+
changes = [
122+
"00: the no-server rule covers sockets and daemons; a local TUI is in scope",
123+
"01: `source` format fixed to `session <uuid>`",
124+
"04: CLI gains `scratchpad`; the command list is not closed (the five-tool MCP limit is untouched — subcommands are not tools)",
125+
"06: workspace gains crates/memory-judge and crates/memory-tui",
126+
"07: the Stop hook agent becomes an enqueue; PreCompact added as a second enqueue point",
127+
]
128+
129+
[testing]
130+
policy = [
131+
"memory-judge: parsing against checked-in JSONL fixtures; the LLM client behind a trait with a fake — no test needs network",
132+
"fail-open regressions: unreachable, timeout, and malformed response each leave the store byte-identical",
133+
"a test asserts memory-core's dependency graph excludes memory-judge",
134+
"a test asserts no code path opens a transcript for writing",
135+
]
136+
137+
[open_questions]
138+
oq_1 = """
139+
One session per analysis, or a batch? Per-session keeps a clean link from memory
140+
to chat. Batching is where the reviewer's edge is largest — the same correction
141+
recurring across five sessions is the strongest signal a rule is missing, and it
142+
is invisible one at a time. Decide by dogfooding.
143+
"""

‎CLAUDE.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ wiring (`.mcp.json`, `hooks/`, `skills/`, `commands/`).
5252
└── fff-memory/ # the binary: serve | store | search | read | forget | stats | doctor | reindex
5353
```
5454

55+
## Plugin Packaging
56+
57+
The repository is also the Claude Code plugin: `.claude-plugin/marketplace.json` declares one
58+
plugin, `fff-memory`, with `"source": "./"`, so the plugin root and the repo root are the same
59+
directory. `.claude-plugin/plugin.json` is **metadata-only** — it must never gain `skills`,
60+
`commands`, `hooks`, or `mcpServers` keys, because their absence is what lets Claude Code
61+
auto-discover `skills/`, `commands/`, `hooks/hooks.json`, and `.mcp.json` from the plugin root.
62+
`crates/fff-memory/tests/plugin_package.rs` asserts exactly that, along with the byte contents
63+
of all eight wiring files, so `cargo test --workspace` is the gate on plugin correctness.
64+
65+
Installing it locally (required for dogfooding — the tools, hooks, and skill are inert until
66+
the plugin is both registered and enabled):
67+
68+
```bash
69+
claude plugin marketplace add /Users/fakebizprez/Developer/projects/libsql-memory
70+
# then enable fff-memory@linehaul-ai-fff-memory via /plugin
71+
```
72+
73+
Tool names are plugin-scoped once installed, e.g.
74+
`mcp__plugin_fff-memory_fff-memory__memory_store`. Pre-approve that one permission or the Stop
75+
hook silently skips its write.
76+
5577
## Build & Development Commands
5678

5779
Run from the repo root:

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ One Rust binary, both an MCP stdio server and a CLI, embedding the `fff-search`
5555
Thin Claude Code hooks (session start, prompt submit, session end) plus a `memory-usage` skill
5656
that teaches the alias discipline good recall depends on.
5757

58+
## Install
59+
60+
The repository doubles as the Claude Code plugin — the plugin root and the repo root are the
61+
same directory, so there is nothing to build or package separately:
62+
63+
```bash
64+
git clone https://github.com/linehaul-ai/libsql-memory.git
65+
claude plugin marketplace add ./libsql-memory
66+
```
67+
68+
Then enable `fff-memory` from `/plugin`. That registers the MCP server, the two slash commands
69+
(`/memory-status`, `/memory-doctor`), the three hooks, and the `memory-usage` skill in one step.
70+
The server compiles on first launch via `cargo run`, so a Rust toolchain is the only
71+
prerequisite.
72+
73+
Pre-approve the `mcp__plugin_fff-memory_fff-memory__memory_store` permission if you want the
74+
session-end hook to record memories without prompting; without it the hook skips rather than
75+
blocking.
76+
5877
## Workspace layout
5978

6079
```

0 commit comments

Comments
 (0)