@jeffober/thatch 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -10
- package/artifacts/skills/thatch-code-review.md +9 -7
- package/artifacts/skills/thatch-fact-extractor.md +2 -2
- package/artifacts/skills/thatch-review-economy.md +97 -0
- package/artifacts/skills/thatch-review-followup.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +15 -5
- package/bin/qa-run +38 -0
- package/bin/thatch +13 -7
- package/docs/dev/README.md +76 -22
- package/docs/dev/features/README.md +33 -0
- package/docs/dev/features/behavior-engine.md +271 -0
- package/docs/dev/features/cicd.md +89 -0
- package/docs/dev/features/cli.md +81 -0
- package/docs/dev/features/compaction-recovery.md +48 -0
- package/docs/dev/features/database.md +244 -0
- package/docs/dev/features/deduplication.md +102 -0
- package/docs/dev/features/extraction.md +253 -0
- package/docs/dev/features/hygiene.md +81 -0
- package/docs/dev/features/memory-store.md +154 -0
- package/docs/dev/features/multi-host.md +228 -0
- package/docs/dev/features/nudge-pipeline.md +282 -0
- package/docs/dev/features/prediction-engine.md +253 -0
- package/docs/dev/features/qa-system.md +205 -0
- package/docs/dev/features/session-lifecycle.md +110 -0
- package/docs/dev/features/setup.md +199 -0
- package/docs/dev/features/sideband.md +104 -0
- package/docs/dev/gotchas.md +3 -3
- package/docs/dev/mcp-parity.md +7 -5
- package/docs/dev/setup-and-hooks.md +4 -4
- package/docs/dev/skills.md +10 -8
- package/docs/plans/README.md +2 -2
- package/docs/user/README.md +58 -20
- package/docs/user/behavior-engine.md +85 -0
- package/docs/user/cli.md +98 -0
- package/docs/user/code-review.md +73 -0
- package/docs/user/deduplication.md +70 -0
- package/docs/user/default-behaviors.md +75 -0
- package/docs/user/extraction.md +74 -0
- package/docs/user/hygiene.md +76 -0
- package/docs/user/memory.md +81 -0
- package/docs/user/prediction-engine.md +87 -0
- package/docs/user/setup.md +110 -0
- package/docs/user/skills.md +97 -0
- package/package.json +2 -2
- package/src/behavior.ts +120 -0
- package/src/db.ts +160 -417
- package/src/embeddings.ts +0 -41
- package/src/extraction.ts +2 -2
- package/src/git.ts +3 -1
- package/src/index.ts +66 -14
- package/src/mcp.ts +59 -2
- package/src/prediction.ts +127 -0
- package/src/prompts.ts +155 -200
- package/src/scoring-engine.ts +374 -0
- package/src/seed-behaviors.ts +339 -0
- package/src/setup.ts +2 -6
- package/src/sideband.ts +36 -8
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +235 -9
- package/src/tools.ts +11 -1
- package/src/vector-math.ts +30 -0
- package/docs/qa/README.md +0 -175
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# CLI (bin/thatch)
|
|
2
|
+
|
|
3
|
+
The thatch CLI is a Bun script at `bin/thatch`. It provides memory inspection, MCP server startup, hook commands for MCP hosts, and the setup installer.
|
|
4
|
+
|
|
5
|
+
## Subcommands
|
|
6
|
+
|
|
7
|
+
15 subcommands:
|
|
8
|
+
|
|
9
|
+
| Command | Args | Flags | Stdin | Purpose |
|
|
10
|
+
|---------|------|-------|-------|---------|
|
|
11
|
+
| `stores` | none | none | none | List all store names |
|
|
12
|
+
| `list [store]` | optional store | none | none | List memory labels in a store |
|
|
13
|
+
| `show <label> [store]` | label, optional store | none | none | Display one memory by label |
|
|
14
|
+
| `forget <label> [store]` | label, optional store | none | none | Delete one memory by label |
|
|
15
|
+
| `search <query> [store]` | query, optional store | none | none | Semantic cosine search (limit 10; "all" searches project + global) |
|
|
16
|
+
| `mcp` | none | none | none | Start the stdio MCP server (for Claude Code, Cursor) |
|
|
17
|
+
| `reminder [--json]` | none | `--json` | none | Print session-start reminder + hygiene report |
|
|
18
|
+
| `hygiene` | none | none | none | Print the hygiene report standalone |
|
|
19
|
+
| `prime` | none | none | none | Run thatch-project-primer skill via opencode/agent/claude CLI |
|
|
20
|
+
| `buffer-batch` | none | none | JSON | Append PostToolBatch payload to queue (Claude Code hook) |
|
|
21
|
+
| `buffer-tool` | none | none | JSON | Append single postToolUse interaction to queue (Cursor hook) |
|
|
22
|
+
| `flush-tools [--json]` | none | `--json` | JSON | Peek queue + extraction/recall/prediction/behavior/write nudge |
|
|
23
|
+
| `flush-predictions [--json]` | none | `--json` | JSON | Standalone prediction-only nudge |
|
|
24
|
+
| `setup --claude [--cursor] [--global]` | none | `--claude`, `--cursor`, `--global` | none | Install config + instructions + hooks + skills |
|
|
25
|
+
| (unknown) | none | none | none | Print usage, exit 1 |
|
|
26
|
+
|
|
27
|
+
## Global behavior
|
|
28
|
+
|
|
29
|
+
- No `--version`, `--help`, `-h`, or short flags. No subcommand aliases.
|
|
30
|
+
- Unknown command or missing required arg calls `usage()` and exits 1.
|
|
31
|
+
- Args are positional, parsed by hand from `process.argv.slice(2)`. No arg-parsing library.
|
|
32
|
+
- DB opened once at startup, closed at end (except `mcp` which closes early).
|
|
33
|
+
- Stores default to git remote detected by `detectRepo()`; "unknown" on failure. "global" is the shared store. "all" (search only) means project + global.
|
|
34
|
+
|
|
35
|
+
## prime
|
|
36
|
+
|
|
37
|
+
Runs the thatch-project-primer skill via an external CLI. Searches `PATH` in order: `opencode`, `agent` (Cursor CLI), `claude`. Uses the first found:
|
|
38
|
+
- opencode: `opencode run "<primer prompt>"`
|
|
39
|
+
- agent: `agent -p "<primer prompt>" --approve-mcps`
|
|
40
|
+
- claude: `claude "<primer prompt>"`
|
|
41
|
+
|
|
42
|
+
Inherits stdio, exits with the child's exit code. Errors and exits 1 if none found.
|
|
43
|
+
|
|
44
|
+
## Environment variables
|
|
45
|
+
|
|
46
|
+
| Variable | Purpose | Default |
|
|
47
|
+
|----------|---------|---------|
|
|
48
|
+
| `XDG_CONFIG_HOME` | Config home base | `~/.config` |
|
|
49
|
+
| `THATCH_DB_PATH` | SQLite DB file location | `$XDG_CONFIG_HOME/thatch/thatch.db` |
|
|
50
|
+
| `THATCH_MODEL` | Embedding model override | `Xenova/bge-small-en-v1.5` |
|
|
51
|
+
| `THATCH_RECALL_THRESHOLD` | Cosine cutoff for recall nudge | `0.55` |
|
|
52
|
+
| `THATCH_PREDICTION_THRESHOLD` | Cutoff for prediction auto-fire | `0.60` |
|
|
53
|
+
| `THATCH_BEHAVIOR_THRESHOLD` | Cutoff for behavior auto-fire | `0.60` |
|
|
54
|
+
| `CLAUDE_PROJECT_DIR` | Claude Code project dir | `process.cwd()` |
|
|
55
|
+
| `CURSOR_PROJECT_DIR` | Cursor project dir | falls through |
|
|
56
|
+
| `CLAUDE_CONFIG_DIR` | Claude config dir | `~/.claude` |
|
|
57
|
+
| `CURSOR_CONFIG_DIR` | Cursor config dir | `~/.cursor` |
|
|
58
|
+
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Enable async extraction | unset |
|
|
59
|
+
|
|
60
|
+
## Source files
|
|
61
|
+
|
|
62
|
+
- `bin/thatch` — the CLI (Bun script, 486 lines)
|
|
63
|
+
- `bin/release` — release helper (bash, separate from the main CLI)
|
|
64
|
+
|
|
65
|
+
## Interactions with other features
|
|
66
|
+
|
|
67
|
+
- Memory store ([memory-store.md](memory-store.md)): `stores`, `list`, `show`, `forget`, `search` subcommands
|
|
68
|
+
- Setup ([setup.md](setup.md)): `setup` subcommand
|
|
69
|
+
- Nudge pipeline ([nudge-pipeline.md](nudge-pipeline.md)): `flush-tools`, `flush-predictions`, `reminder` subcommands
|
|
70
|
+
- Extraction ([extraction.md](extraction.md)): `buffer-batch`, `buffer-tool`, `flush-tools` subcommands
|
|
71
|
+
- Hygiene ([hygiene.md](hygiene.md)): `hygiene`, `reminder` subcommands
|
|
72
|
+
- Multi-host ([multi-host.md](multi-host.md)): `mcp` subcommand starts the MCP server for Claude Code and Cursor
|
|
73
|
+
|
|
74
|
+
## Key invariants
|
|
75
|
+
|
|
76
|
+
- No arg-parsing library. Args are positional, parsed by hand from `process.argv.slice(2)`.
|
|
77
|
+
- No `--version`, `--help`, `-h`, or short flags. No subcommand aliases.
|
|
78
|
+
- DB opened once at startup, closed at end (except `mcp` which closes early).
|
|
79
|
+
- Store name auto-detected from git remote. "global" is the shared store. "all" (search only) means project + global.
|
|
80
|
+
- Unknown command or missing required arg calls `usage()` and exits 1.
|
|
81
|
+
- `prime` searches PATH in order: `opencode`, `agent`, `claude`. Uses the first found.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Compaction Recovery (opencode)
|
|
2
|
+
|
|
3
|
+
opencode compacts long sessions by generating a summary and replacing the conversation history. Without intervention, a compacted session would lose awareness of thatch -- the agent would stop using memory tools, extraction would stop, and nudges would fire during summary generation where tools are blocked.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Suppresses nudges during compaction (tools are blocked during summary generation)
|
|
8
|
+
- Injects compaction context so the agent retains awareness of thatch after compaction
|
|
9
|
+
- Clears the compacting flag on success (three mechanisms, belt-and-suspenders)
|
|
10
|
+
- Handles compaction failure (clears stale flag if autocontinue never fired)
|
|
11
|
+
|
|
12
|
+
## How it works
|
|
13
|
+
|
|
14
|
+
### Four hooks/events
|
|
15
|
+
|
|
16
|
+
1. **experimental.session.compacting** (hook) -- fires when opencode starts compacting. Adds sessionID to the `compacting` set. Pushes `compactionContext(repo)` into `output.context`. The context tells the agent to preserve what's been learned and decided this session so there's continuity after compaction.
|
|
17
|
+
|
|
18
|
+
2. **experimental.compaction.autocontinue** (hook) -- fires after compaction succeeds and the auto-continue turn fires. Removes sessionID from the `compacting` set so nudges resume.
|
|
19
|
+
|
|
20
|
+
3. **session.compacted** (event) -- fires on successful compaction. Clears the `compacting` flag. Belt-and-suspenders: if autocontinue didn't fire or wasn't installed, the event still clears the flag.
|
|
21
|
+
|
|
22
|
+
4. **Compaction failure fallback** (chat.message hook) -- if a non-compaction chat.message arrives while the session is in the `compacting` set, compaction failed (autocontinue never fired). The hook clears the stale `compacting` flag and proceeds normally. Without this, the session would be stuck with nudges off forever.
|
|
23
|
+
|
|
24
|
+
### Why nudges are suppressed during compaction
|
|
25
|
+
|
|
26
|
+
During summary generation, tools are blocked. A nudge that instructs the agent to use `thatch_memory_recall` or `thatch_memory_remember` would cause a blocked-tool error. The `compacting` set is checked at the top of the chat.message hook -- if the session is compacting, all nudges are skipped.
|
|
27
|
+
|
|
28
|
+
### Compaction context
|
|
29
|
+
|
|
30
|
+
`compactionContext(repo)` in `src/prompts.ts` generates text that tells the agent: you are using thatch, here's what you've learned and decided this session, preserve this context after compaction. This is pushed into `output.context` (not `output.system`) so it's part of the compaction output, not the permanent system prompt.
|
|
31
|
+
|
|
32
|
+
## Interactions with other features
|
|
33
|
+
|
|
34
|
+
- Nudge pipeline ([nudge-pipeline.md](nudge-pipeline.md)): compaction guard is the first check in the chat.message hook; suppresses all 4 tiers
|
|
35
|
+
- Session lifecycle ([session-lifecycle.md](session-lifecycle.md)): `session.compacted` event is handled in the event handler
|
|
36
|
+
- Multi-host ([multi-host.md](multi-host.md)): compaction recovery is opencode-only. Claude Code and Cursor do not have this feature. Mitigation: `CLAUDE.md`/`AGENTS.md` persists through compaction, and SessionStart hooks fire after compaction.
|
|
37
|
+
|
|
38
|
+
## Source files
|
|
39
|
+
|
|
40
|
+
- `src/index.ts` -- all four hooks/events (experimental.session.compacting, experimental.compaction.autocontinue, session.compacted event, chat.message compaction guard)
|
|
41
|
+
- `src/prompts.ts` -- compactionContext function
|
|
42
|
+
|
|
43
|
+
## Key invariants
|
|
44
|
+
|
|
45
|
+
- The `compacting` set is checked at the top of chat.message. If set, all nudges are skipped.
|
|
46
|
+
- Three mechanisms clear the flag (autocontinue, session.compacted event, chat.message fallback). Belt-and-suspenders.
|
|
47
|
+
- Compaction failure fallback: a non-compaction message during compacting = compaction failed. Clear and proceed.
|
|
48
|
+
- opencode-only feature. MCP hosts rely on static instructions persisting through compaction.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Database
|
|
2
|
+
|
|
3
|
+
Thatch stores all data in a single SQLite file. The schema has 11 tables organized around three concerns: memory entries, the prediction engine, and the behavior engine.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
- Default path: `$XDG_CONFIG_HOME/thatch/thatch.db` (override via `THATCH_DB_PATH`)
|
|
8
|
+
- PRAGMAs: `journal_mode=WAL`, `busy_timeout=5000`, `foreign_keys=ON`
|
|
9
|
+
- Single file, no external services
|
|
10
|
+
|
|
11
|
+
## Schema overview
|
|
12
|
+
|
|
13
|
+
11 tables in three groups:
|
|
14
|
+
|
|
15
|
+
| Group | Tables | Purpose |
|
|
16
|
+
|-------|--------|---------|
|
|
17
|
+
| Memory | `stores`, `entries`, `dedup_pairs` | Core memory CRUD + dedup verdicts |
|
|
18
|
+
| Prediction engine | `prediction_matchers`, `predictions`, `prediction_edges`, `prediction_provenance` | User decision model |
|
|
19
|
+
| Behavior engine | `behavior_matchers`, `behaviors`, `behavior_edges`, `behavior_provenance` | LLM self-discipline rules |
|
|
20
|
+
|
|
21
|
+
The prediction and behavior engines share the same four-table shape (matchers, items, edges, provenance) with different table names. They are separate because the semantics differ: predictions model what the user wants; behaviors model what the LLM should do.
|
|
22
|
+
|
|
23
|
+
## Memory tables
|
|
24
|
+
|
|
25
|
+
### stores
|
|
26
|
+
|
|
27
|
+
Registry of all store names. The `global` store is inserted automatically at schema init.
|
|
28
|
+
|
|
29
|
+
```sql
|
|
30
|
+
stores(name TEXT PRIMARY KEY)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### entries
|
|
34
|
+
|
|
35
|
+
The core table. Memory entries with embeddings, metadata, and usage telemetry.
|
|
36
|
+
|
|
37
|
+
```sql
|
|
38
|
+
entries(
|
|
39
|
+
slug TEXT NOT NULL,
|
|
40
|
+
store TEXT NOT NULL REFERENCES stores(name),
|
|
41
|
+
label TEXT NOT NULL,
|
|
42
|
+
content TEXT NOT NULL,
|
|
43
|
+
embedding BLOB,
|
|
44
|
+
model TEXT,
|
|
45
|
+
branch TEXT,
|
|
46
|
+
confidence INTEGER,
|
|
47
|
+
archived INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
updated_at TEXT NOT NULL,
|
|
50
|
+
recall_count INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
last_recalled_at TEXT,
|
|
52
|
+
PRIMARY KEY (slug, store)
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `slug`: derived from label via `slugify()`. Unicode letters/digits preserved. All-symbol labels fall back to hash.
|
|
57
|
+
- `embedding`: raw Float32Array bytes. Null if not yet embedded.
|
|
58
|
+
- `model`: embedding model tag (informational; dimension is the real discriminator).
|
|
59
|
+
- `branch`: git branch scope. Null = project-wide.
|
|
60
|
+
- `confidence`: 1-10 scale. Null = unset.
|
|
61
|
+
- `archived`: 0 = live, 1 = archived. Search, dedup, and staleness queries all exclude archived by default.
|
|
62
|
+
- `recall_count` / `last_recalled_at`: usage telemetry, stamped by `db.recall()` (not `db.search()`).
|
|
63
|
+
|
|
64
|
+
### dedup_pairs
|
|
65
|
+
|
|
66
|
+
Records which duplicate-candidate pairs have been reviewed.
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
dedup_pairs(
|
|
70
|
+
store TEXT NOT NULL,
|
|
71
|
+
slug_a TEXT NOT NULL,
|
|
72
|
+
slug_b TEXT NOT NULL,
|
|
73
|
+
status TEXT NOT NULL,
|
|
74
|
+
checked_at TEXT NOT NULL,
|
|
75
|
+
PRIMARY KEY (store, slug_a, slug_b)
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Slugs stored sorted (a < b) for canonical matching.
|
|
80
|
+
- `status`: "duplicate", "supplement", "contradiction", or "unrelated".
|
|
81
|
+
- Overwriting or forgetting either memory clears the verdict (DELETE in `remember()` and `forgetEntry()`).
|
|
82
|
+
|
|
83
|
+
## Prediction engine tables
|
|
84
|
+
|
|
85
|
+
### prediction_matchers
|
|
86
|
+
|
|
87
|
+
Situation descriptions that trigger predictions. Embedded, cosine-matched at `chat.message`.
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
prediction_matchers(
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
store TEXT NOT NULL REFERENCES stores(name),
|
|
93
|
+
description TEXT NOT NULL,
|
|
94
|
+
embedding BLOB,
|
|
95
|
+
model TEXT,
|
|
96
|
+
created_at TEXT NOT NULL,
|
|
97
|
+
updated_at TEXT NOT NULL
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### predictions
|
|
102
|
+
|
|
103
|
+
User preference predictions with Bayesian confidence.
|
|
104
|
+
|
|
105
|
+
```sql
|
|
106
|
+
predictions(
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
store TEXT NOT NULL REFERENCES stores(name),
|
|
109
|
+
statement TEXT NOT NULL,
|
|
110
|
+
rationale TEXT,
|
|
111
|
+
embedding BLOB,
|
|
112
|
+
model TEXT,
|
|
113
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
114
|
+
confirm_count REAL NOT NULL DEFAULT 0,
|
|
115
|
+
disconfirm_count REAL NOT NULL DEFAULT 0,
|
|
116
|
+
created_at TEXT NOT NULL,
|
|
117
|
+
updated_at TEXT NOT NULL
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- `confidence`: Bayesian posterior. `p = (confirm + K*P0) / (confirm + disconfirm + K)`, K=5, P0=0.5.
|
|
122
|
+
- `confirm_count` / `disconfirm_count`: REAL (fractional for soft signals, weight 0.25).
|
|
123
|
+
|
|
124
|
+
### prediction_edges
|
|
125
|
+
|
|
126
|
+
Many-to-many links between matchers and predictions.
|
|
127
|
+
|
|
128
|
+
```sql
|
|
129
|
+
prediction_edges(
|
|
130
|
+
matcher_id TEXT NOT NULL REFERENCES prediction_matchers(id) ON DELETE CASCADE,
|
|
131
|
+
prediction_id TEXT NOT NULL REFERENCES predictions(id) ON DELETE CASCADE,
|
|
132
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
133
|
+
created_at TEXT NOT NULL,
|
|
134
|
+
PRIMARY KEY (matcher_id, prediction_id)
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### prediction_provenance
|
|
139
|
+
|
|
140
|
+
Audit trail of every signal applied to a prediction.
|
|
141
|
+
|
|
142
|
+
```sql
|
|
143
|
+
prediction_provenance(
|
|
144
|
+
id TEXT PRIMARY KEY,
|
|
145
|
+
prediction_id TEXT NOT NULL REFERENCES predictions(id) ON DELETE CASCADE,
|
|
146
|
+
signal TEXT NOT NULL,
|
|
147
|
+
detail TEXT,
|
|
148
|
+
created_at TEXT NOT NULL
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `signal`: "confirm", "disconfirm", "soft", or "create".
|
|
153
|
+
|
|
154
|
+
## Behavior engine tables
|
|
155
|
+
|
|
156
|
+
Same shape as prediction engine, with behavior-specific semantics.
|
|
157
|
+
|
|
158
|
+
### behavior_matchers
|
|
159
|
+
|
|
160
|
+
```sql
|
|
161
|
+
behavior_matchers(
|
|
162
|
+
id TEXT PRIMARY KEY,
|
|
163
|
+
store TEXT NOT NULL REFERENCES stores(name),
|
|
164
|
+
description TEXT NOT NULL,
|
|
165
|
+
embedding BLOB,
|
|
166
|
+
model TEXT,
|
|
167
|
+
created_at TEXT NOT NULL,
|
|
168
|
+
updated_at TEXT NOT NULL
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### behaviors
|
|
173
|
+
|
|
174
|
+
```sql
|
|
175
|
+
behaviors(
|
|
176
|
+
id TEXT PRIMARY KEY,
|
|
177
|
+
store TEXT NOT NULL REFERENCES stores(name),
|
|
178
|
+
statement TEXT NOT NULL,
|
|
179
|
+
rationale TEXT,
|
|
180
|
+
embedding BLOB,
|
|
181
|
+
model TEXT,
|
|
182
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
183
|
+
confirm_count REAL NOT NULL DEFAULT 0,
|
|
184
|
+
disconfirm_count REAL NOT NULL DEFAULT 0,
|
|
185
|
+
created_at TEXT NOT NULL,
|
|
186
|
+
updated_at TEXT NOT NULL
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### behavior_edges
|
|
191
|
+
|
|
192
|
+
```sql
|
|
193
|
+
behavior_edges(
|
|
194
|
+
matcher_id TEXT NOT NULL REFERENCES behavior_matchers(id) ON DELETE CASCADE,
|
|
195
|
+
behavior_id TEXT NOT NULL REFERENCES behaviors(id) ON DELETE CASCADE,
|
|
196
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
197
|
+
created_at TEXT NOT NULL,
|
|
198
|
+
PRIMARY KEY (matcher_id, behavior_id)
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### behavior_provenance
|
|
203
|
+
|
|
204
|
+
```sql
|
|
205
|
+
behavior_provenance(
|
|
206
|
+
id TEXT PRIMARY KEY,
|
|
207
|
+
behavior_id TEXT NOT NULL REFERENCES behaviors(id) ON DELETE CASCADE,
|
|
208
|
+
signal TEXT NOT NULL,
|
|
209
|
+
detail TEXT,
|
|
210
|
+
created_at TEXT NOT NULL
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- `signal`: "confirm" (ham), "disconfirm" (spam), or "codify".
|
|
215
|
+
|
|
216
|
+
## Schema migration
|
|
217
|
+
|
|
218
|
+
`recall_count`, `last_recalled_at`, and `archived` are added to pre-existing databases by an idempotent column migration at init. The migration uses `PRAGMA table_info` to check for the column's existence, then `ALTER TABLE ADD COLUMN` if missing. This handles databases created before these columns existed.
|
|
219
|
+
|
|
220
|
+
## Embedding serialization
|
|
221
|
+
|
|
222
|
+
Embeddings are raw Float32Array bytes stored as BLOBs. Serialization honors `byteOffset`/`byteLength` — transformers.js can return views into larger tensor buffers, and serializing the whole backing buffer corrupts vectors. Always serialize the view's own bytes, not the underlying buffer.
|
|
223
|
+
|
|
224
|
+
## Key invariants
|
|
225
|
+
|
|
226
|
+
- Single SQLite file, WAL mode, 5s busy timeout, foreign keys ON.
|
|
227
|
+
- Embedding spaces are discriminated by vector dimension, not model tag.
|
|
228
|
+
- Embedding serialization honors `byteOffset`/`byteLength` (views into larger buffers).
|
|
229
|
+
- Slugs are slugified labels; slug + store = composite PK.
|
|
230
|
+
- Dedup verdicts auto-clear when either memory is overwritten or forgotten.
|
|
231
|
+
- Archived memories excluded from search, dedup, and staleness by default.
|
|
232
|
+
- Column migration is idempotent (`PRAGMA table_info` + `ALTER TABLE`).
|
|
233
|
+
|
|
234
|
+
## Interactions with other features
|
|
235
|
+
|
|
236
|
+
- Memory store ([memory-store.md](memory-store.md)): `entries` table is the core; `stores` table is the registry
|
|
237
|
+
- Deduplication ([deduplication.md](deduplication.md)): `dedup_pairs` table tracks reviewed pairs
|
|
238
|
+
- Prediction engine ([prediction-engine.md](prediction-engine.md)): 4 prediction tables
|
|
239
|
+
- Behavior engine ([behavior-engine.md](behavior-engine.md)): 4 behavior tables
|
|
240
|
+
- Hygiene ([hygiene.md](hygiene.md)): `staleEntryCount` uses `recall_count` and `last_recalled_at`; `branchesInStore` uses `branch` column
|
|
241
|
+
|
|
242
|
+
## Source files
|
|
243
|
+
|
|
244
|
+
- `src/db.ts` — all schema, CRUD, search, scoring, migrations
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Deduplication System
|
|
2
|
+
|
|
3
|
+
Agent-driven detection and resolution of duplicate memories. The system
|
|
4
|
+
surfaces candidates; the agent decides what to do.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- `find_duplicates` tool: cosine similarity pairs, grouped into
|
|
9
|
+
connected-component clusters
|
|
10
|
+
- `dedup_mark_checked` tool: records a pairwise verdict so `find_duplicates`
|
|
11
|
+
stops re-reporting
|
|
12
|
+
- Automatic verdict clearing: overwriting either memory clears the verdict
|
|
13
|
+
- `thatch-dedup-classifier` skill: classifies the relationship and reconciles
|
|
14
|
+
pairs
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
### find_duplicates (tool)
|
|
19
|
+
|
|
20
|
+
- Parameters: `store` (optional, default project store), `threshold`
|
|
21
|
+
(optional, default 0.85)
|
|
22
|
+
- Finds memory pairs whose embeddings exceed the cosine similarity threshold
|
|
23
|
+
- Groups related pairs into connected-component clusters: a cluster of 3+
|
|
24
|
+
labels usually means one topic fragmented across entries that should be
|
|
25
|
+
consolidated
|
|
26
|
+
- Pairs already resolved via `dedup_mark_checked` are skipped
|
|
27
|
+
- Output: clustered rendering — "Cluster of N:" then
|
|
28
|
+
`[score:N] labelA <-> labelB` lines
|
|
29
|
+
- Read-only
|
|
30
|
+
|
|
31
|
+
### dedup_mark_checked (tool)
|
|
32
|
+
|
|
33
|
+
- Parameters: `label_a`, `label_b` (both required), `status` (one of
|
|
34
|
+
`duplicate`, `supplement`, `contradiction`, `unrelated`), `store` (optional)
|
|
35
|
+
- Records a pairwise verdict so `find_duplicates` stops re-reporting that pair
|
|
36
|
+
- Labels are slugified internally
|
|
37
|
+
- Overwriting either memory later clears the verdict automatically
|
|
38
|
+
(DB-level: the `remember()` and `forgetEntry()` functions `DELETE` from
|
|
39
|
+
`dedup_pairs` for the affected slug)
|
|
40
|
+
- Returns `[checked] store :: a <-> b -> status`
|
|
41
|
+
|
|
42
|
+
### Verdict lifecycle
|
|
43
|
+
|
|
44
|
+
1. `find_duplicates` surfaces a pair above threshold
|
|
45
|
+
2. Agent loads the `thatch-dedup-classifier` skill
|
|
46
|
+
3. Agent classifies the pair: `duplicate` (merge), `supplement` (keep both),
|
|
47
|
+
`contradiction` (resolve), `unrelated` (mark and ignore)
|
|
48
|
+
4. For duplicates: agent merges via `memory_remember(overwrite: true)` +
|
|
49
|
+
`memory_forget` the other entry
|
|
50
|
+
5. Agent calls `dedup_mark_checked` for the surviving pair
|
|
51
|
+
6. Overwriting either memory clears the verdict → the pair can re-flag if the
|
|
52
|
+
merge created a new similarity
|
|
53
|
+
|
|
54
|
+
### The dedup_pairs table
|
|
55
|
+
|
|
56
|
+
- PK: `(store, slug_a, slug_b)` — slugs stored sorted (`a < b`) for canonical
|
|
57
|
+
matching
|
|
58
|
+
- No FK to entries (slugs are compared by string, not by FK constraint)
|
|
59
|
+
- Overwriting or forgetting an entry clears all verdicts involving that slug
|
|
60
|
+
(`DELETE` in `remember()` and `forgetEntry()`)
|
|
61
|
+
|
|
62
|
+
### thatch-dedup-classifier skill
|
|
63
|
+
|
|
64
|
+
- Shared skill (`artifacts/skills/thatch-dedup-classifier.md`)
|
|
65
|
+
- Classifies the relationship between two similar memory entries
|
|
66
|
+
- Provides a structured process: read both entries, compare content, decide
|
|
67
|
+
verdict, reconcile
|
|
68
|
+
- Can be dispatched as a sub-agent by the extraction pipeline or loaded
|
|
69
|
+
directly by the agent
|
|
70
|
+
|
|
71
|
+
## Interactions with other features
|
|
72
|
+
|
|
73
|
+
- [Memory store](memory-store.md): `find_duplicates` searches stored memories;
|
|
74
|
+
merging uses `memory_remember(overwrite)` + `memory_forget`.
|
|
75
|
+
- [Hygiene](hygiene.md): hygiene report counts pending dedup pairs (same
|
|
76
|
+
underlying `db.findDuplicates`).
|
|
77
|
+
- Skills: `thatch-dedup-classifier` is the skill that guides the
|
|
78
|
+
reconciliation process.
|
|
79
|
+
- [Extraction](extraction.md): extraction can trigger the dedup cycle when
|
|
80
|
+
write-time duplicate detection fires.
|
|
81
|
+
|
|
82
|
+
## Source files
|
|
83
|
+
|
|
84
|
+
- `src/db.ts` — `findDuplicates`, `markChecked`, `dedup_pairs` table
|
|
85
|
+
- `src/tool-defs.ts` — 2 dedup tools (`find_duplicates`, `dedup_mark_checked`)
|
|
86
|
+
- `artifacts/skills/thatch-dedup-classifier.md` — classifier skill
|
|
87
|
+
|
|
88
|
+
## Database tables
|
|
89
|
+
|
|
90
|
+
- `dedup_pairs(store, slug_a, slug_b, status, checked_at, PK(store, slug_a, slug_b))`
|
|
91
|
+
|
|
92
|
+
## Key invariants
|
|
93
|
+
|
|
94
|
+
- `find_duplicates` groups pairs into clusters (connected components). A
|
|
95
|
+
cluster of 3+ should be consolidated into one memory.
|
|
96
|
+
- Verdicts are automatically cleared when either memory is overwritten or
|
|
97
|
+
forgotten. This is intended — a merge can create a new similarity that
|
|
98
|
+
should re-flag.
|
|
99
|
+
- The threshold (0.85) is higher than the recall nudge threshold (0.55) because
|
|
100
|
+
"duplicate" is a stronger claim than "relates to".
|
|
101
|
+
- Slugs in `dedup_pairs` are stored sorted (`a < b`) for canonical matching
|
|
102
|
+
regardless of argument order.
|