@jeffober/thatch 0.1.43 → 0.1.45
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 +84 -9
- package/bin/thatch +307 -95
- package/docs/dev/README.md +12 -3
- package/docs/dev/features/README.md +1 -0
- package/docs/dev/features/cli.md +15 -17
- package/docs/dev/features/commands.md +74 -0
- package/docs/dev/features/cross-session-chat.md +128 -45
- package/docs/dev/features/database.md +7 -3
- package/docs/dev/features/extraction.md +5 -0
- package/docs/dev/features/multi-host.md +1 -1
- package/docs/dev/features/session-lifecycle.md +1 -1
- package/docs/dev/gotchas.md +20 -0
- package/docs/dev/mcp-parity.md +2 -0
- package/docs/dev/setup-and-hooks.md +20 -8
- package/docs/plans/thatch-actions.md +179 -0
- package/docs/user/README.md +10 -4
- package/docs/user/cli.md +45 -44
- package/docs/user/commands.md +50 -0
- package/docs/user/cross-session-chat.md +94 -22
- package/docs/user/setup.md +9 -3
- package/package.json +1 -1
- package/src/chat-names.ts +2 -2
- package/src/chat.ts +372 -185
- package/src/commands.ts +138 -19
- package/src/db.ts +46 -0
- package/src/debug.ts +44 -0
- package/src/embeddings.ts +42 -1
- package/src/index.ts +262 -39
- package/src/mcp.ts +78 -3
- package/src/prompts.ts +57 -3
- package/src/setup.ts +212 -33
- package/src/tool-defs.ts +115 -30
- package/src/watchers.ts +51 -29
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Thatch Actions: Prompt Cores Shared by Nudges and /thatch Commands
|
|
2
|
+
|
|
3
|
+
## Synopsis
|
|
4
|
+
|
|
5
|
+
Extract the behavior instructions currently embedded in nudge text into
|
|
6
|
+
prompt-core constants in `src/prompts.ts`, then build `/thatch/*` user-facing
|
|
7
|
+
commands on top of the same constants. The nudge and the command become two
|
|
8
|
+
envelopes around one instruction body: the nudge adds escalation tiers and
|
|
9
|
+
stop-and-wait framing, the command adds user-invocation framing. First
|
|
10
|
+
payoff is `/thatch/defrag`, but the structure covers any behavior the nudges
|
|
11
|
+
currently ask the agent to perform on demand.
|
|
12
|
+
|
|
13
|
+
## Background
|
|
14
|
+
|
|
15
|
+
Nudges are instruction-carrying messages injected by hooks. Several encode a
|
|
16
|
+
multi-step behavior the agent must execute (dispatch a sub-agent, fetch the
|
|
17
|
+
extraction payload, run a skill, ack). The same behaviors have no user-facing
|
|
18
|
+
invocation path: the user cannot say "drain the extraction queue now" or
|
|
19
|
+
"consolidate duplicate memories now" -- they can only wait for a nudge or
|
|
20
|
+
describe the desired behavior in prose and hope the agent maps it to the right
|
|
21
|
+
skill + tool sequence.
|
|
22
|
+
|
|
23
|
+
Two existing pieces make this cheap to add:
|
|
24
|
+
|
|
25
|
+
- `src/commands.ts` already ships `/thatch/compact` and `/thatch/exit` as
|
|
26
|
+
markdown command files synced into the opencode config dir, and already
|
|
27
|
+
composes them from shared fragments (`sharedChecklist`,
|
|
28
|
+
`userMessageSection`).
|
|
29
|
+
- The "Shared prose sync across prompt variants" problem is documented:
|
|
30
|
+
prompt prose lives in parallel functions with no single source of truth,
|
|
31
|
+
and drift in unasserted sections passes tests. This plan makes the shared
|
|
32
|
+
behavior text a true constant.
|
|
33
|
+
|
|
34
|
+
## Design: prompt cores and envelopes
|
|
35
|
+
|
|
36
|
+
A **prompt core** is a constant string in `src/prompts.ts` holding the
|
|
37
|
+
host-agnostic instruction body for one behavior: which tools to call, in what
|
|
38
|
+
order, with what arguments, and what "done" looks like.
|
|
39
|
+
|
|
40
|
+
An **envelope** wraps a core with delivery-specific framing:
|
|
41
|
+
|
|
42
|
+
- The nudge envelope adds: escalation tier wording, the load-bearing
|
|
43
|
+
stop-and-wait ending (see "Extraction nudge stop-and-wait wording is
|
|
44
|
+
load-bearing" -- the stop-and-wait text is nudge-only and must NOT leak
|
|
45
|
+
into action envelopes, because an action IS user input), and the
|
|
46
|
+
"system nudge, not user input" disclaimer.
|
|
47
|
+
- The action envelope adds: frontmatter description, the `# User Message`
|
|
48
|
+
section with `$ARGUMENTS`, and any greenlight-token mechanics (wrap-up
|
|
49
|
+
commands only).
|
|
50
|
+
|
|
51
|
+
Cores are plain string constants, not template functions, so both envelopes
|
|
52
|
+
interpolate them identically. Where a core needs host-specific tool names
|
|
53
|
+
(`thatch_memory_remember` vs `mcp__thatch__memory_remember`), the core takes
|
|
54
|
+
the opencode spelling and MCP consumers apply the documented token
|
|
55
|
+
substitution, matching the existing systemPrompt/mcpInstructions convention.
|
|
56
|
+
|
|
57
|
+
## Actions v1
|
|
58
|
+
|
|
59
|
+
| Command | Core behavior | Existing machinery |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `/thatch/defrag` | Run `thatch_find_duplicates`, classify each pair with the thatch-dedup-classifier skill, consolidate (remember/overwrite or forget), mark pairs checked | find_duplicates + dedup classifier skill |
|
|
62
|
+
| `/thatch/extract` | Fetch the extraction payload, run the fact-extractor skill, call `extraction_done` | get_extraction_payload + fact-extractor skill |
|
|
63
|
+
| `/thatch/hygiene` | Run the hygiene report, act on stale and orphaned branch-scoped memories | `hygieneReport()` + `/thatch hygiene` CLI |
|
|
64
|
+
| `/thatch/reflect` | Run the session-reflection skill over the current session | thatch-session-reflection skill |
|
|
65
|
+
|
|
66
|
+
The wrap-up commands (`compact`, `exit`) stay as-is; their checklist text
|
|
67
|
+
becomes a core reused by both templates (already true via `sharedChecklist`).
|
|
68
|
+
|
|
69
|
+
`/thatch/extract` interacts with opencode's direct extraction (parent idle
|
|
70
|
+
spawns the extractor child; the nudge is only a fallback). The action must
|
|
71
|
+
reuse the same accept/complete semantics and must not fight the `extracting`
|
|
72
|
+
set: if direct extraction is already running for the session, the action
|
|
73
|
+
should report that and stop rather than double-dispatch.
|
|
74
|
+
|
|
75
|
+
## Host parity
|
|
76
|
+
|
|
77
|
+
Commands are markdown or MCP prompts, synced per host. Behavior parity is the
|
|
78
|
+
goal; format differences are documented, not hidden.
|
|
79
|
+
|
|
80
|
+
- **opencode** (existing): `installOpencodeCommands` syncs markdown to
|
|
81
|
+
`~/.config/opencode/command/thatch/` on every plugin load.
|
|
82
|
+
- **Claude Code**: native user-level slash commands read markdown from
|
|
83
|
+
`~/.claude/commands/thatch/` (user scope) or `.claude/commands/thatch/`
|
|
84
|
+
(project scope), with the same `$ARGUMENTS` substitution. The sync lives in
|
|
85
|
+
`setup.ts` alongside the existing installers and follows their patterns:
|
|
86
|
+
idempotent content-compare like `installSkills`, both global and local
|
|
87
|
+
scopes, reported in the setup output next to the skills report.
|
|
88
|
+
(Deviation from the draft below: no `checkSetup` entry. That function
|
|
89
|
+
answers one question - "is thatch set up at all" - and command files
|
|
90
|
+
self-heal on every setup run, so flagging their absence as a startup
|
|
91
|
+
warning would be noise.) Differences: no plugin, so no
|
|
92
|
+
`command.execute.before` arming -- greenlight-token wrap-up commands do not
|
|
93
|
+
ship to Claude Code; and tool names use the `mcp__thatch__` prefix. MCP
|
|
94
|
+
hosts do have hooks now (session-start reminders, chat-wake stop hooks), so
|
|
95
|
+
hook wiring is not the barrier; the missing piece is only the plugin's TUI
|
|
96
|
+
control routes.
|
|
97
|
+
- **Cursor**: no file-based commands. The stdio MCP server (`mcp.ts`) gains
|
|
98
|
+
MCP Prompts support (`prompts/list`, `prompts/get`), which Cursor surfaces
|
|
99
|
+
as slash commands. The dispatch switch currently handles `initialize`,
|
|
100
|
+
`tools/list`, `tools/call`, and `ping`; prompts get a `compileTools`-style
|
|
101
|
+
`compilePrompts()` and two new cases. Prompt arguments use MCP's
|
|
102
|
+
`{{argument}}`-style templating, so each action core gets an MCP-prompt
|
|
103
|
+
rendering alongside the markdown rendering.
|
|
104
|
+
|
|
105
|
+
README gains "What works in Claude Code" and "What works in Cursor" sections
|
|
106
|
+
listing per-host support without restating the shared docs. The parity matrix
|
|
107
|
+
in `docs/dev/mcp-parity.md` gets an Actions row next to the existing
|
|
108
|
+
Wrap-up commands row.
|
|
109
|
+
|
|
110
|
+
## Session ID plumbing
|
|
111
|
+
|
|
112
|
+
Since the plan was drafted, `HostToolContext` (tool-defs.ts) landed: every
|
|
113
|
+
tool's `execute` receives the invoking session's ID as a third parameter on
|
|
114
|
+
the opencode path, and the watch tools already consume it. `extraction_done`
|
|
115
|
+
also already has the optional `session_id` with the omit-vs-parent rule in
|
|
116
|
+
its description. The remaining work is narrower than first planned:
|
|
117
|
+
|
|
118
|
+
- `get_extraction_payload`: `session_id` becomes optional. `execute` uses
|
|
119
|
+
`args.session_id ?? host?.sessionID`. On the opencode path the wrapper
|
|
120
|
+
supplies the invoking session, so omitted means "this session."
|
|
121
|
+
- MCP path: the host context is omitted (no session concept), so an omitted
|
|
122
|
+
`session_id` returns a clear error telling the model to pass the parent
|
|
123
|
+
session's ID from the extraction nudge.
|
|
124
|
+
|
|
125
|
+
One wrinkle to document: the zod arg shape is shared between hosts, so
|
|
126
|
+
optionality is global -- you cannot make the arg required on MCP and optional
|
|
127
|
+
on opencode at the schema level. Enforcement happens in `execute` per host,
|
|
128
|
+
and the arg description must carry the rule so the model self-corrects.
|
|
129
|
+
|
|
130
|
+
The rule the LLM must apply, stated everywhere the tools are described:
|
|
131
|
+
|
|
132
|
+
> Omit `session_id` when acting on the session you are running in. Pass the
|
|
133
|
+
> parent session's `session_id` when running inside a sub-agent that was
|
|
134
|
+
> dispatched to process another session's extraction queue.
|
|
135
|
+
|
|
136
|
+
This rule appears in: the arg descriptions in `tool-defs.ts`, the extraction
|
|
137
|
+
prompt core (used by both nudge and action envelopes), the fact-extractor
|
|
138
|
+
skill, and the dev docs. Tests cover both paths: omit-resolves-to-current
|
|
139
|
+
and explicit-parent-ID (the existing sub-agent drain bug's regression path).
|
|
140
|
+
|
|
141
|
+
## Decisions
|
|
142
|
+
|
|
143
|
+
- Prompt cores live in `src/prompts.ts`, not a new module; `commands.ts`
|
|
144
|
+
imports them. One prompt module, consistent with the existing module map.
|
|
145
|
+
- Actions are generated, not hand-written: each action has one core and a
|
|
146
|
+
per-host renderer, so a wording change cannot drift between nudge and
|
|
147
|
+
command.
|
|
148
|
+
- No `--dry-run` flag for defrag in v1; the dedup classifier skill already
|
|
149
|
+
asks for pair-by-pair verdicts, which is the interactive safety net.
|
|
150
|
+
- MCP Prompts for Cursor are additive to `mcp.ts` and do not change the
|
|
151
|
+
tools/list contract.
|
|
152
|
+
|
|
153
|
+
## Testing
|
|
154
|
+
|
|
155
|
+
- `tests/commands.test.ts` (or the existing plugin tests): command-file
|
|
156
|
+
content assertions per host, including that each action's markdown contains
|
|
157
|
+
its core text verbatim.
|
|
158
|
+
- Claude setup tests: command sync writes, updates only on content change,
|
|
159
|
+
and `checkSetup` flags a missing or stale command file.
|
|
160
|
+
- MCP tests: `prompts/list` returns every action; `prompts/get` interpolates
|
|
161
|
+
arguments; an omitted-session-ID `get_extraction_payload` call errors with
|
|
162
|
+
the pass-the-parent-ID message (no host context on the MCP path).
|
|
163
|
+
- Plugin tests: `get_extraction_payload` without `session_id` resolves via
|
|
164
|
+
the host context to the invoking session; with an explicit parent ID,
|
|
165
|
+
drains the parent's queue (the existing sub-agent drain bug's regression
|
|
166
|
+
path).
|
|
167
|
+
- Parity guard: a test asserts every opencode command def has a
|
|
168
|
+
corresponding MCP prompt (and vice versa) except the documented exclusions
|
|
169
|
+
(wrap-up greenlight commands on non-opencode hosts).
|
|
170
|
+
|
|
171
|
+
## Docs
|
|
172
|
+
|
|
173
|
+
- `docs/dev/features/commands.md` and `docs/user/commands.md` (two-tier):
|
|
174
|
+
action list, host parity matrix, session ID rule.
|
|
175
|
+
- `docs/dev/mcp-parity.md`: Actions row in the parity matrix; revisit the
|
|
176
|
+
Wrap-up commands row if greenlight arming ever lands on MCP hosts.
|
|
177
|
+
- README: "What works in Claude Code" / "What works in Cursor" sections.
|
|
178
|
+
- QA use cases: one auto use case for `/thatch/defrag` end-to-end, one for
|
|
179
|
+
`/thatch/extract` with omitted session ID.
|
package/docs/user/README.md
CHANGED
|
@@ -19,6 +19,7 @@ This README is the overview. Each feature has its own guide:
|
|
|
19
19
|
- [default-behaviors.md](default-behaviors.md): what ships automatically
|
|
20
20
|
- [hygiene.md](hygiene.md): store maintenance signals
|
|
21
21
|
- [deduplication.md](deduplication.md): duplicate detection and resolution
|
|
22
|
+
- [commands.md](commands.md): `/thatch/*` slash commands (on-demand actions and wrap-ups)
|
|
22
23
|
- [skills.md](skills.md): structured workflow skills
|
|
23
24
|
- [code-review.md](code-review.md): multi-agent code review pipeline
|
|
24
25
|
- [setup.md](setup.md): installation and configuration
|
|
@@ -190,8 +191,8 @@ requirements.
|
|
|
190
191
|
|
|
191
192
|
| Tool | What it does |
|
|
192
193
|
|------|-------------|
|
|
193
|
-
| `thatch_chat_register` | Join the cross-session chat directory, so other sessions on this machine can message you. Display names are assigned by thatch, never chosen (a
|
|
194
|
-
| `thatch_chat_list` | List registered sessions
|
|
194
|
+
| `thatch_chat_register` | Join the cross-session chat directory, so other sessions on this machine can message you. Display names are assigned by thatch, never chosen (a pool name plus a never-reused counter), so a name always refers to the same session. Call with no arguments to join, rejoin after unregistering, or check your name. On hosts without session context (Claude Code, Cursor), pass the name your thatch hook printed as `as`. |
|
|
195
|
+
| `thatch_chat_list` | List registered sessions grouped into Active and Stale sections (stale = two missed heartbeats, about a minute; its host process has probably stopped), with how long ago each last checked in, each session's project and checkout kind (project root or linked worktree), its topic, and your unread count. |
|
|
195
196
|
| `thatch_chat_send` | Send a message to another registered session, by name or session id. The recipient is nudged when its session is idle. |
|
|
196
197
|
| `thatch_chat_read` | Drain your inbox: all unread messages oldest-first, marked read. Senders identified by display name. |
|
|
197
198
|
| `thatch_chat_unregister` | Leave the chat directory. |
|
|
@@ -240,8 +241,12 @@ Beyond the tools, thatch hooks into opencode itself:
|
|
|
240
241
|
watches for the token and then runs the compaction or exits opencode.
|
|
241
242
|
Without the token nothing fires -- a toast points you at the items the
|
|
242
243
|
agent listed, and you re-run the command once they're handled. Text typed
|
|
243
|
-
after the command
|
|
244
|
-
goodbye or hand off context:
|
|
244
|
+
after the command reaches the agent first, labeled as a `User Message`
|
|
245
|
+
section ahead of the checklist (say goodbye or hand off context:
|
|
246
|
+
`/thatch/exit Good work - see you tomorrow`). Alongside the wrap-ups,
|
|
247
|
+
on-demand actions (`/thatch/defrag`, `/thatch/extract`, `/thatch/hygiene`,
|
|
248
|
+
`/thatch/reflect`) run the memory-maintenance behaviors whenever you want,
|
|
249
|
+
not just when a nudge fires -- see [commands.md](commands.md).
|
|
245
250
|
A greenlit `/thatch/exit` also unregisters the session from the chat
|
|
246
251
|
directory, so other sessions stop sending it mail. The commands
|
|
247
252
|
self-install into `~/.config/opencode/command/thatch/` at plugin startup;
|
|
@@ -445,6 +450,7 @@ Most configuration needs none. Two layers exist:
|
|
|
445
450
|
| `THATCH_DB_PATH` | `$XDG_CONFIG_HOME/thatch/thatch.db` | Override database location (the config file follows it) |
|
|
446
451
|
| `THATCH_MODEL` | `Xenova/bge-small-en-v1.5` | Override embedding model |
|
|
447
452
|
| `THATCH_EMBEDDING_BACKEND` | `wasm` | Set to `native` to run embeddings on onnxruntime-node (NAPI) instead of the wasm runtime |
|
|
453
|
+
| `THATCH_DEBUG` | unset (off) | Diagnostic logging to `debug.log` beside the database. `1` logs everything; or a comma-separated list of tags, e.g. `chat` or `chat:startup` |
|
|
448
454
|
|
|
449
455
|
Unchanged defaults: the database is created automatically, the embedding
|
|
450
456
|
model downloads once and is cached, the store name is auto-detected from
|
package/docs/user/cli.md
CHANGED
|
@@ -46,7 +46,8 @@ Read-only access to the chat directory your opencode sessions share
|
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
thatch chat list # who is registered: name, age, project, topic
|
|
49
|
-
thatch chat
|
|
49
|
+
thatch chat list --stale all # also show stale sessions older than a day
|
|
50
|
+
thatch chat tail # follow sent and read events, live (JSONL)
|
|
50
51
|
thatch chat tail --once # print the last 20 messages and exit
|
|
51
52
|
thatch chat tail --once --limit 50 # a longer backlog (or --limit all)
|
|
52
53
|
thatch chat tail --match "rebase" --match "payments" # body must match both
|
|
@@ -54,47 +55,47 @@ thatch chat tail --from al --to bob # substring match on names
|
|
|
54
55
|
thatch chat tail --since 2026-09-12 --until "2026-09-13 09:00" # a window
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
`chat list` shows every registered session
|
|
58
|
-
header row: how long since its
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
`chat list` shows every registered session in two sections, Active and
|
|
59
|
+
Stale, each as aligned columns under a header row: how long since its
|
|
60
|
+
last heartbeat, its status (`fresh` or `stale` for opencode sessions;
|
|
61
|
+
`active` or `idle` for Claude Code and Cursor sessions, which only beat
|
|
62
|
+
when a prompt runs), its project, and its topic (the session's title) -
|
|
63
|
+
the fastest way to answer "which session should I talk to about X?"
|
|
64
|
+
Rows are grouped by project (alphabetical; sessions with no project
|
|
65
|
+
last), most recently seen first within each project. A
|
|
66
|
+
stale session has missed two heartbeats, about a minute: the opencode
|
|
67
|
+
process hosting it has probably stopped. The stale section shows rows
|
|
68
|
+
up to one day old by default; older ones collapse into a hidden-count
|
|
69
|
+
note so long-dead sessions cannot bury the fresh signal. Pass
|
|
70
|
+
`--stale N` (days) to widen the window or `--stale all` to see every
|
|
71
|
+
stale row. Color is added only when the
|
|
72
|
+
output is a terminal; a pipe prints plain aligned text.
|
|
73
|
+
|
|
74
|
+
`chat tail` prints the conversation as a JSONL event log: one JSON
|
|
75
|
+
object per line, one event per line. Sending a message and reading it
|
|
76
|
+
are separate events, linked by the message `id`. Every field is plain
|
|
77
|
+
data: names as registered, each participant's topic, the message body
|
|
78
|
+
exactly as sent, and ISO-8601 UTC timestamps. No color, no markdown
|
|
79
|
+
rendering, no local time; pipe through `jq` for either.
|
|
73
80
|
|
|
74
81
|
```text
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
CI is green on main
|
|
80
|
-
____________________________________________________________
|
|
81
|
-
|
|
82
|
-
To broadcast
|
|
83
|
-
When 2026-09-12 21:07 America/Denver
|
|
84
|
-
|
|
85
|
-
rebasing payments, hold off
|
|
86
|
-
____________________________________________________________
|
|
82
|
+
{"event":"sent","at":"2026-09-12T20:02:11Z","id":41,"from":"al-go-rithm-00001","from_topic":"cross-session messaging","to":"brute-the-dream-farrier-00001","to_topic":"diagnosing watcher underuse","broadcast":false,"body":"CI is green on main"}
|
|
83
|
+
{"event":"read","at":"2026-09-12T20:02:40Z","id":41,"reader":"brute-the-dream-farrier-00001","reader_topic":"diagnosing watcher underuse","from":"al-go-rithm-00001","from_topic":"cross-session messaging"}
|
|
84
|
+
{"event":"sent","at":"2026-09-13T03:07:02Z","id":42,"from":"al-go-rithm-00001","from_topic":"cross-session messaging","to":"kurn-the-typechecker-00001","to_topic":"release QA","broadcast":true,"body":"rebasing payments, hold off"}
|
|
85
|
+
```
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
A broadcast is one `sent` event per recipient, each with `broadcast:
|
|
88
|
+
true` and its real `to`, so every copy tracks its own read. Handy
|
|
89
|
+
one-liners:
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
```bash
|
|
92
|
+
thatch chat tail | jq -r '"\(.at) \(.event) \(.from) -> \(.to // .reader): \(.body // "")"'
|
|
93
|
+
thatch chat tail --once --limit all | jq -c 'select(.event == "read")'
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
Follow mode runs until Ctrl-C.
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
Follow mode runs until Ctrl-C. The backlog includes the `read` event of
|
|
97
|
+
every message already read; in follow mode, reads appear as inboxes
|
|
98
|
+
drain. The chat itself flows through the
|
|
98
99
|
agents (the CLI never sends, reads, or registers on a session's behalf;
|
|
99
100
|
that happens from opencode sessions via the `thatch_chat_*` tools).
|
|
100
101
|
|
|
@@ -108,10 +109,8 @@ too, so a filtered tail keeps watching the same way:
|
|
|
108
109
|
together: every one must match the message body.
|
|
109
110
|
- `--from NAME` and `--to NAME` match participant names (case doesn't
|
|
110
111
|
matter). A departed sender can only be found as the `unknown`
|
|
111
|
-
rendering the tail shows. Broadcast
|
|
112
|
-
|
|
113
|
-
finds the fan-out rows that reached them, while `--to broadcast`
|
|
114
|
-
matches nothing.
|
|
112
|
+
rendering the tail shows. Broadcast events carry their real recipient,
|
|
113
|
+
so `--to <name>` finds the fan-out copies that reached them.
|
|
115
114
|
- `--since DT` and `--until DT` bound the window; a message counts if
|
|
116
115
|
it was sent at or after `--since` and before `--until`. The date
|
|
117
116
|
format is `YYYY-MM-DD` with an optional `HH:MM` in your local
|
|
@@ -119,9 +118,9 @@ too, so a filtered tail keeps watching the same way:
|
|
|
119
118
|
|
|
120
119
|
A `--until` already in the past exits after the backlog (a closed
|
|
121
120
|
window has nothing left to follow); one in the future follows until
|
|
122
|
-
the window closes.
|
|
123
|
-
|
|
124
|
-
its read
|
|
121
|
+
the window closes. In the backlog, a hidden message's read is hidden
|
|
122
|
+
with it; in follow mode, a hidden message that gets read still emits
|
|
123
|
+
its read event.
|
|
125
124
|
|
|
126
125
|
## Priming a new project
|
|
127
126
|
|
|
@@ -168,7 +167,9 @@ thatch setup --claude [--cursor] [--global] # install config + hooks + skills
|
|
|
168
167
|
|
|
169
168
|
## Limitations
|
|
170
169
|
|
|
171
|
-
-
|
|
170
|
+
- For non-Nix installs, `bun` must be installed and on PATH. Thatch does
|
|
171
|
+
not bundle its own runtime (the Nix package bundles it; see the
|
|
172
|
+
README's Nix section).
|
|
172
173
|
- `search` is limited to 10 results. There is no pagination.
|
|
173
174
|
- `prime` requires an external CLI (`opencode`, `agent`, or `claude`)
|
|
174
175
|
on PATH. If none is found, it exits with an error.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Slash commands
|
|
2
|
+
|
|
3
|
+
Thatch ships `/thatch/*` commands you run yourself, on top of the nudges the
|
|
4
|
+
plugin fires on its own schedule. Nudges and commands share the same
|
|
5
|
+
instruction text, so a behavior works the same whether it fires automatically
|
|
6
|
+
or you invoke it.
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
- `/thatch/defrag` — find duplicate-candidate memory pairs and clusters,
|
|
11
|
+
classify each one, consolidate duplicates, and mark the rest checked.
|
|
12
|
+
Text after the command narrows the scope (for example, a store name).
|
|
13
|
+
- `/thatch/extract` — drain the extraction queue now, instead of waiting
|
|
14
|
+
for the next nudge or idle-time extraction. opencode only.
|
|
15
|
+
- `/thatch/hygiene` — run the hygiene report and act on it: stale
|
|
16
|
+
memories, memories scoped to deleted branches, and pending dedup pairs.
|
|
17
|
+
The agent asks before forgetting anything that may still matter.
|
|
18
|
+
- `/thatch/reflect` — run the session-reflection skill to persist what the
|
|
19
|
+
current session learned.
|
|
20
|
+
- `/thatch/compact` and `/thatch/exit` — the wrap-up commands. They run a
|
|
21
|
+
pre-flight checklist (flush extraction, finish promised memory writes,
|
|
22
|
+
surface loose ends) before a compaction or an exit, gated by a greenlight
|
|
23
|
+
token. opencode only; the greenlight mechanics are described in the
|
|
24
|
+
[user guide overview](README.md).
|
|
25
|
+
|
|
26
|
+
## Host support
|
|
27
|
+
|
|
28
|
+
| Command | opencode | Claude Code | Cursor |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| `/thatch/defrag` | yes | yes | yes (as an MCP prompt) |
|
|
31
|
+
| `/thatch/extract` | yes | no | no |
|
|
32
|
+
| `/thatch/hygiene` | yes | yes | yes (as an MCP prompt) |
|
|
33
|
+
| `/thatch/reflect` | yes | yes | yes (as an MCP prompt) |
|
|
34
|
+
| `/thatch/compact`, `/thatch/exit` | yes | no | no |
|
|
35
|
+
|
|
36
|
+
Why the gaps: `/thatch/extract` needs the invoking session's ID, which only
|
|
37
|
+
the opencode plugin can supply. The wrap-up commands trigger TUI actions
|
|
38
|
+
through the plugin. Cursor has no file-based commands; its prompts come from
|
|
39
|
+
the MCP server (`prompts/list` and `prompts/get`).
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
- **opencode**: installed by the plugin on every load.
|
|
44
|
+
- **Claude Code**: installed by `thatch setup --claude` into the same scope
|
|
45
|
+
as the skills (`.claude/commands/thatch/` project-local, or the Claude
|
|
46
|
+
config dir for `--global`). Re-run setup to refresh after an upgrade.
|
|
47
|
+
|
|
48
|
+
Tool names inside the command text follow each host's spelling
|
|
49
|
+
(`thatch_memory_remember` on opencode, `mcp__thatch__memory_remember` on
|
|
50
|
+
Claude Code and Cursor).
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Cross-Session Chat
|
|
2
2
|
|
|
3
|
-
Cross-session chat lets your
|
|
3
|
+
Cross-session chat lets your agent sessions talk to each other, in
|
|
4
|
+
opencode, Claude Code, and Cursor. If one
|
|
4
5
|
session is QAing a release and another is planning a feature, the second can
|
|
5
6
|
ask the first "is the release green yet?" instead of you relaying messages
|
|
6
7
|
between tabs all afternoon.
|
|
7
8
|
|
|
8
9
|
## What it does
|
|
9
10
|
|
|
10
|
-
opencode sessions join the directory automatically,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
opencode sessions join the directory automatically, the moment their
|
|
12
|
+
first user message arrives (before the assistant responds), and a session
|
|
13
|
+
resumed with `opencode -c` or `opencode -s <id>` is back in the directory
|
|
14
|
+
at harness start with its old name. Names are assigned by thatch, never chosen: a name drawn from a
|
|
15
|
+
built-in pool plus a counter (`al-go-rithm-00001`). The counter only ever
|
|
16
|
+
increments, so a name is
|
|
14
17
|
minted exactly once per machine - pruning an old session never reissues its
|
|
15
18
|
name to someone else, and the same name always refers to the same session.
|
|
16
19
|
The live title rides along as the session's topic (refreshed as the
|
|
@@ -19,12 +22,14 @@ board without anyone filling in forms. A session can also join or rejoin
|
|
|
19
22
|
explicitly with `thatch_chat_register` (no arguments) - useful after
|
|
20
23
|
`chat_unregister`, or just to check your assigned name. Once registered:
|
|
21
24
|
|
|
22
|
-
- `thatch_chat_list` shows every registered session on the machine,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
- `thatch_chat_list` shows every registered session on the machine, grouped
|
|
26
|
+
into Active and Stale sections: active sessions can be woken; stale ones
|
|
27
|
+
have missed two heartbeats, about a minute (the opencode process hosting
|
|
28
|
+
them has probably stopped). Each row carries how long ago the session last
|
|
29
|
+
checked in, its project, whether it runs in the project root or a linked
|
|
30
|
+
git worktree, its topic, and your unread count
|
|
26
31
|
- `thatch_chat_send` delivers a message to another registered session, by
|
|
27
|
-
name or session id
|
|
32
|
+
name or session id, and states the recipient's liveness at send time
|
|
28
33
|
- `thatch_chat_read` drains the session's inbox
|
|
29
34
|
- `thatch_chat_broadcast` delivers one message to every other live
|
|
30
35
|
registered session at once
|
|
@@ -56,8 +61,14 @@ toggle reveals those output blocks if you want them.)
|
|
|
56
61
|
|
|
57
62
|
## How to use it
|
|
58
63
|
|
|
59
|
-
With auto-registration you usually do nothing:
|
|
60
|
-
|
|
64
|
+
With auto-registration you usually do nothing: a session joins the
|
|
65
|
+
directory when its first user message arrives, and a session continued
|
|
66
|
+
with `opencode -c` or `opencode -s <id>` is registered again the moment
|
|
67
|
+
its harness starts - same session id, same name, reclaimed. The agent
|
|
68
|
+
gets a quiet toast with
|
|
69
|
+
its assigned name when it first registers, and a resumed session
|
|
70
|
+
announces its reclaimed name on its first prompt. To introduce two
|
|
71
|
+
sessions, just tell each one who to talk to:
|
|
61
72
|
|
|
62
73
|
> Ask the other session whether the release has shipped; answer its
|
|
63
74
|
> questions directly.
|
|
@@ -72,14 +83,65 @@ doing. When in doubt it summarizes the mail for you and waits. If you want
|
|
|
72
83
|
two sessions to actively cooperate on something, say so explicitly in both
|
|
73
84
|
sessions.
|
|
74
85
|
|
|
86
|
+
## Claude Code and Cursor
|
|
87
|
+
|
|
88
|
+
Chat works from Claude Code and Cursor through the same MCP server and the
|
|
89
|
+
same shared directory. The seven tools are identical on every host; two
|
|
90
|
+
things differ.
|
|
91
|
+
|
|
92
|
+
**Identity comes from the hook.** Claude Code and Cursor pass no session
|
|
93
|
+
context to MCP tool calls, so the thatch hook anchors identity instead. It
|
|
94
|
+
registers the conversation under a stable id derived from the host's
|
|
95
|
+
session id, and prints `you are NAME (unread count)` at session start and
|
|
96
|
+
on every prompt. The model passes that name as the `as` argument on the
|
|
97
|
+
chat tools. Without the hook (MCP registered by hand, hooks removed), the
|
|
98
|
+
model can still join: `chat_register` with no arguments mints a fresh
|
|
99
|
+
identity for the conversation, and the model uses the returned name from
|
|
100
|
+
there. That identity does not survive a compacted context - re-register
|
|
101
|
+
then, or prefer the hooked path.
|
|
102
|
+
|
|
103
|
+
**Delivery model.** Only opencode can start a turn when mail arrives
|
|
104
|
+
while you are away. The other hosts surface mail at their hook points,
|
|
105
|
+
and both wake the agent when a turn ends:
|
|
106
|
+
|
|
107
|
+
- **Cursor** checks the mailbox when a turn ends (the `stop` hook): unread
|
|
108
|
+
mail is auto-submitted as a follow-up message, so the agent reads it
|
|
109
|
+
without you typing anything. The notification is a pointer (sender
|
|
110
|
+
names + count, never message bodies); bodies flow only through the
|
|
111
|
+
framed `chat_read`. A loop cap bounds consecutive follow-ups, and an
|
|
112
|
+
aborted or errored turn is never auto-continued.
|
|
113
|
+
- **Claude Code** also checks at turn end (the `Stop` hook): unread mail
|
|
114
|
+
arrives as a Stop-hook reminder and the turn continues so the agent
|
|
115
|
+
reads it. The host's own loop protections plus the delivered stamp
|
|
116
|
+
bound repeats, and a session paused on background work is not
|
|
117
|
+
interrupted - the wake lands when that work's turn ends. Mail is also
|
|
118
|
+
announced at startup and resume (the `SessionStart` hook line) and at
|
|
119
|
+
every prompt; if an answer is urgent, say so to the agent directly.
|
|
120
|
+
|
|
121
|
+
Two limits worth knowing:
|
|
122
|
+
|
|
123
|
+
- Cursor cloud agents run no client-side hooks: no identity anchor, no
|
|
124
|
+
mail lines. The MCP tools still work, with a self-registered identity.
|
|
125
|
+
- A conversation idle past the prune window (a week) re-registers under a
|
|
126
|
+
fresh name; mail addressed to the old name is swept.
|
|
127
|
+
|
|
128
|
+
The terminal view ([cli.md](cli.md)) works the same regardless of which
|
|
129
|
+
host the sessions run in.
|
|
130
|
+
|
|
75
131
|
## Liveness
|
|
76
132
|
|
|
77
|
-
A session shows as fresh while
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
133
|
+
A session shows as fresh while the opencode process hosting it is
|
|
134
|
+
running: hosted sessions keep beating (idle ones included), so the
|
|
135
|
+
freshness is real. When that process dies, its sessions stop beating and
|
|
136
|
+
show as stale about a minute later (two missed heartbeats), grouped into
|
|
137
|
+
a separate Stale section. A laptop that slept reads the same way until
|
|
138
|
+
its processes catch up.
|
|
139
|
+
Messages to a stale session wait in its inbox unread - a dead session
|
|
140
|
+
never reads them, and `chat_send` says so at send time. Continuing a
|
|
141
|
+
session with `opencode -s <id>` reclaims its name and row, and any mail
|
|
142
|
+
that queued while it was down is delivered at startup. Explicitly
|
|
143
|
+
closing a session (deleting it in the TUI) unregisters it immediately,
|
|
144
|
+
and a greenlit `/thatch/exit` unregisters the session too: the wrap-up
|
|
83
145
|
checklist calls `chat_unregister`, and the plugin removes the row itself
|
|
84
146
|
when the exit token passes the greenlight check, so other sessions stop
|
|
85
147
|
addressing mail to a process that is about to vanish.
|
|
@@ -132,16 +194,26 @@ construction. Three mitigations:
|
|
|
132
194
|
- **Names are assigned, not claimed.** Display names are minted by thatch
|
|
133
195
|
with a never-reused counter, so a name cannot be grabbed by an unrelated
|
|
134
196
|
session and an old name cannot be resurrected by an impersonator.
|
|
197
|
+
- **Claude Code identity is anchored by the host process.** Hooks learn
|
|
198
|
+
the true session id from the host and record which process spawned them;
|
|
199
|
+
the MCP server resolves its own parent process against that record, so
|
|
200
|
+
the model cannot claim another session's identity there. On Cursor, chat
|
|
201
|
+
tools still trust the `as` argument the caller supplies (hooks and MCP
|
|
202
|
+
servers share one workspace process, so process identity is ambiguous) -
|
|
203
|
+
a local session that knows a name can act as it. opencode is immune (the
|
|
204
|
+
host supplies identity). The threat model is one user's machine.
|
|
135
205
|
- **No external content via wake.** The wake notification names senders
|
|
136
206
|
and counts only - bodies flow exclusively through the framed
|
|
137
207
|
`chat_read` surface.
|
|
138
208
|
|
|
139
209
|
## Requirements and limitations
|
|
140
210
|
|
|
141
|
-
- **
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
211
|
+
- **Idle wake (starting a turn with nobody at the keyboard) is opencode
|
|
212
|
+
only.** The chat tools work everywhere (other hosts declare their
|
|
213
|
+
identity with `as`). Cursor and Claude Code additionally wake the agent
|
|
214
|
+
when a turn ends with mail unread (the `stop`/`Stop` hooks); Claude
|
|
215
|
+
Code also announces mail at startup and resume, and both see pending
|
|
216
|
+
mail at prompt time (the flush-tools hook line, or `chat_status`).
|
|
145
217
|
- **Same machine only.** The directory and inbox live in thatch's local
|
|
146
218
|
database. There is no network relay.
|
|
147
219
|
- **Opt-in.** Unregistered sessions cannot be messaged and cannot send.
|
package/docs/user/setup.md
CHANGED
|
@@ -90,6 +90,7 @@ and `--json` output for hook commands.
|
|
|
90
90
|
| `THATCH_DB_PATH` | `~/.config/thatch/thatch.db` | SQLite database path |
|
|
91
91
|
| `THATCH_MODEL` | `Xenova/bge-small-en-v1.5` | Hugging Face model name for embeddings |
|
|
92
92
|
| `THATCH_EMBEDDING_BACKEND` | `wasm` | Set to `native` to run embeddings on onnxruntime-node (NAPI) instead of the wasm runtime |
|
|
93
|
+
| `THATCH_MODEL_CACHE` | platform per-user cache dir | Where downloaded model files are stored |
|
|
93
94
|
| `THATCH_RECALL_THRESHOLD` | `0.55` | Cosine threshold for recall nudge |
|
|
94
95
|
| `THATCH_PREDICTION_THRESHOLD` | `0.60` | Cosine threshold for prediction auto-fire |
|
|
95
96
|
| `THATCH_BEHAVIOR_THRESHOLD` | `0.60` | Cosine threshold for behavior auto-fire |
|
|
@@ -110,10 +111,15 @@ instructions are deployed without requiring you to manually re-run
|
|
|
110
111
|
|
|
111
112
|
## Limitations
|
|
112
113
|
|
|
113
|
-
- `bun` must be installed and on PATH. Thatch does
|
|
114
|
-
own runtime
|
|
114
|
+
- For non-Nix installs, `bun` must be installed and on PATH. Thatch does
|
|
115
|
+
not bundle its own runtime (the Nix package bundles it; see the
|
|
116
|
+
README's Nix section).
|
|
115
117
|
- The embedding model (~34 MB) is downloaded once on first use and
|
|
116
|
-
cached in
|
|
118
|
+
cached in the platform's per-user cache dir
|
|
119
|
+
(`~/Library/Caches/thatch/models` on macOS (or
|
|
120
|
+
`$XDG_CACHE_HOME/thatch/models` when `XDG_CACHE_HOME` is set),
|
|
121
|
+
`$XDG_CACHE_HOME/thatch/models` elsewhere). Override with
|
|
122
|
+
`THATCH_MODEL_CACHE`.
|
|
117
123
|
- There is no web UI or dashboard. All interaction is through the
|
|
118
124
|
agent's tool calls and the `thatch` CLI.
|
|
119
125
|
- The MCP server is a long-lived process that keeps the embedding model
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/chat-names.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* unnamed-cast extras). fnord generates its names with an LLM at runtime;
|
|
10
10
|
* thatch bakes the pool in - static contents, no model call.
|
|
11
11
|
*
|
|
12
|
-
* The pool is the
|
|
13
|
-
*
|
|
12
|
+
* The pool is the base for every registration (ChatStore.register
|
|
13
|
+
* slugifies a random pool name and appends a counter). Pool names are
|
|
14
14
|
* case-insensitively distinct from each other; slugify is lossy, but a
|
|
15
15
|
* shared base is harmless - the per-base counter still yields unique
|
|
16
16
|
* names.
|