@jeffober/thatch 0.2.4 → 0.2.5
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 +8 -1
- package/artifacts/skills/thatch-clear-writing.md +103 -5
- package/artifacts/skills/thatch-coordination.md +69 -0
- package/artifacts/skills/thatch-pr-description.md +21 -13
- package/artifacts/skills/thatch-review-no-slop.md +15 -0
- package/bin/thatch +27 -3
- package/docs/assets/deepwiki-badge.svg +1 -0
- package/docs/dev/README.md +7 -2
- package/docs/dev/features/cli.md +2 -0
- package/docs/dev/features/cross-session-chat.md +118 -11
- package/docs/dev/features/notifications.md +1 -2
- package/docs/dev/features/opencode-plugin.md +18 -3
- package/docs/dev/features/prediction-engine.md +44 -5
- package/docs/dev/features/session-tabs.md +155 -0
- package/docs/dev/features/setup.md +5 -0
- package/docs/dev/features/watchers.md +62 -6
- package/docs/dev/gotchas.md +68 -0
- package/docs/dev/mcp-parity.md +2 -2
- package/docs/dev/setup-and-hooks.md +7 -2
- package/docs/dev/skills.md +1 -1
- package/docs/plans/generalized-session-heartbeat.md +212 -0
- package/docs/plans/prediction-consolidation.md +88 -68
- package/docs/user/README.md +12 -3
- package/docs/user/cli.md +3 -0
- package/docs/user/commands.md +11 -4
- package/docs/user/cross-session-chat.md +31 -8
- package/docs/user/prediction-engine.md +11 -0
- package/docs/user/setup.md +16 -0
- package/docs/user/skills.md +1 -1
- package/docs/user/watchers.md +22 -7
- package/package.json +10 -2
- package/src/alerts.ts +1 -1
- package/src/chat.ts +320 -27
- package/src/commands.ts +14 -0
- package/src/db.ts +51 -1
- package/src/git.ts +3 -1
- package/src/hygiene.ts +5 -0
- package/src/mcp.ts +95 -7
- package/src/opencode/tui-plugin.ts +70 -0
- package/src/opencode/v2.ts +197 -4
- package/src/prediction.ts +90 -0
- package/src/prompts.ts +110 -23
- package/src/runtime.ts +187 -9
- package/src/session-db.ts +4 -4
- package/src/session-tab-shared.ts +180 -0
- package/src/setup.ts +9 -3
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +412 -55
- package/src/tools.ts +2 -0
- package/src/watchers.ts +263 -26
- package/docs/plans/intuition-drives.md +0 -147
- package/docs/plans/llm-alerts.md +0 -287
- package/docs/plans/thatch-actions.md +0 -179
- package/docs/plans/worktree-deletion-recovery.md +0 -246
package/docs/dev/gotchas.md
CHANGED
|
@@ -293,3 +293,71 @@ instead of throwing "Cannot use a closed database". Two rules keep this intact:
|
|
|
293
293
|
renderer quotes every description (`yamlQuote` in src/commands.ts) and a
|
|
294
294
|
unit test pins the quoted form. Keep it that way for any new frontmatter
|
|
295
295
|
field whose value is free-form prose.
|
|
296
|
+
|
|
297
|
+
## In-memory maps gate nothing durable: reloads and restarts wipe them
|
|
298
|
+
|
|
299
|
+
- **Any guard whose correctness depends on a runtime.ts Map silently
|
|
300
|
+
disappears for sessions whose events arrive after a plugin reload or a
|
|
301
|
+
daemon restart.** The chat auto-registration child check learned this the
|
|
302
|
+
hard way: `childToParent` kept extraction children out of the roster, but
|
|
303
|
+
an instance that lost the map to a reload registered every event-bearing
|
|
304
|
+
child, and v2 cannot delete sessions (sessionDelete is a no-op), so the
|
|
305
|
+
poller heartbeat those corpse rows forever and the stale-row reaper never
|
|
306
|
+
reaped them (81 `thatch-extraction` rows by 2026-10-07). The fix pattern:
|
|
307
|
+
the plugin writes a DURABLE marker at creation time
|
|
308
|
+
(`chat_machinery`, src/db.ts), and every registration path checks the
|
|
309
|
+
marker plus a stable title (`isMachinerySessionTitle`) instead of
|
|
310
|
+
trusting in-memory state alone. If you add a new plugin-created session
|
|
311
|
+
kind, mark it in the same table and route its events through the same
|
|
312
|
+
guards - do not rely on Maps surviving a reload.
|
|
313
|
+
|
|
314
|
+
## A running TUI never adopts a NEW ./tui plugin entrypoint - restart the TUI
|
|
315
|
+
|
|
316
|
+
- **The v2 TUI discovers its CLI plugins at process startup only.** The
|
|
317
|
+
server side hot-reloads plugin code (the digest watcher), and the TUI
|
|
318
|
+
hot-reloads the SOURCE of plugins it already loaded - but a plugin that
|
|
319
|
+
GAINS a `./tui` entrypoint (or a fresh install of one) is a discovery-
|
|
320
|
+
level change: a TUI process started before the entrypoint existed never
|
|
321
|
+
loads it. Symptom (2026-10-08, the thog TUI): `thatch_session_tab` ran
|
|
322
|
+
fine server-side (the session, the chat registration, the response all
|
|
323
|
+
real) but the tab never appeared in the strip - the TUI had no
|
|
324
|
+
`ui.tabs` consumer loaded, so the rpc event landed nowhere. The
|
|
325
|
+
persisted `tabs.json` (in `$XDG_STATE_HOME/opencode/<channel>/tui/`)
|
|
326
|
+
proved the registration logic was correct the whole time: a FRESH TUI
|
|
327
|
+
(the docker sandbox attach) showed the tab immediately.
|
|
328
|
+
- **Fix**: convert the install to the directory shim (docs/user/setup.md),
|
|
329
|
+
THEN restart the TUI. A restart ALONE fixes nothing when the install is
|
|
330
|
+
a FILE shim - the file shape never loads a `./tui` entrypoint, whatever
|
|
331
|
+
the process age. First diagnostic: `ls ~/.config/opencode/plugins/` -
|
|
332
|
+
a FILE (`thatch.ts`) means no TUI consumer has ever loaded there,
|
|
333
|
+
restart or not; the directory form (`thatch/index.ts` + `tui.ts`) is
|
|
334
|
+
required. (2026-10-08, the thog TUI: the restart-and-respawn test
|
|
335
|
+
DISPROVED the stale-process theory and pointed here - the conversion
|
|
336
|
+
was the missing install step.)
|
|
337
|
+
- **Fix**: restart the TUI window once after adopting (or changing the
|
|
338
|
+
shape of) a `./tui` entrypoint. DIAGNOSTIC WARNING: `tabs.json` cannot
|
|
339
|
+
discriminate here - it is SHARED across windows and written by ANY tab
|
|
340
|
+
open, manual (a picker navigation) or event-driven - an entry for the
|
|
341
|
+
session proves only that SOMETHING opened it, not that the spawn-time
|
|
342
|
+
event worked. The discriminating test: restart the TUI and re-spawn; a
|
|
343
|
+
tab appearing post-restart confirms the stale-process diagnosis; a tab
|
|
344
|
+
still absent is a real bug worth a report.
|
|
345
|
+
|
|
346
|
+
## The two opencode lines discover plugins differently: no one shim shape auto-loads on both
|
|
347
|
+
|
|
348
|
+
- **v1 auto-discovers plugin FILES only** - its config plugin scan globs
|
|
349
|
+
`{plugin,plugins}/*.{ts,js}`, which never matches a directory. **v2
|
|
350
|
+
auto-discovers files AND directories** (and symlinks), and its activation
|
|
351
|
+
dies on duplicate ids - so a plugins directory holding BOTH `thatch.ts`
|
|
352
|
+
and `thatch/` loads the server plugin twice on v2 and dies with
|
|
353
|
+
"duplicate instance plugin ids" before any plugin code runs. There is no
|
|
354
|
+
single on-disk shape that auto-loads on both lines as a directory-based
|
|
355
|
+
multi-entry plugin. Consequences: the dev-checkout install uses the file
|
|
356
|
+
shim for v1-only or v2-only use, or the directory shim for v2's `./tui`
|
|
357
|
+
entrypoint (session tabs) - deleting the file shim in the same step; the
|
|
358
|
+
QA fixture keeps the FILE shim because the QA suite is headless (no TUI
|
|
359
|
+
process ever loads `./tui`, and the file shape is the only one that loads
|
|
360
|
+
on both lines - a directory shim deterministically killed UC-100's v1
|
|
361
|
+
chat canary). Local plugin specs handed to a loader explicitly
|
|
362
|
+
(`"plugin": ["./path"]`) can point at a directory on v1 - it is only the
|
|
363
|
+
AUTO-discovery scan that is files-only.
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -18,7 +18,7 @@ This document maps the feature parity and documents the remaining gaps.
|
|
|
18
18
|
|
|
19
19
|
| Feature | OpenCode plugin | Claude Code MCP + hooks | Cursor MCP + hooks | Parity |
|
|
20
20
|
|---------|----------------|------------------------|---------------------|--------|
|
|
21
|
-
| **Tools** (single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only tools filtered out) | **Full** for the shared tools; `get_session_info` (session identity) and `session_search`/`session_get` (conversation archaeology) are opencode-only — MCP hosts have no session concept. The `thatch session` CLI subcommands cover the same archaeology on any host with an opencode.db file. The `watch_*` tools (PR, branch, and command watchers) are opencode-only — MCP hosts have no poller or proactive-prompt channel. The `chat_*` tools (cross-session chat) are opencode-only — no host session identity and no wake-up channel |
|
|
21
|
+
| **Tools** (single source of truth) | Plugin tool registration, `thatch_` prefix | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only and v2-only tools filtered out) | MCP `tools/list` + `tools/call`, `mcp__thatch__` prefix (opencode-only and v2-only tools filtered out) | **Full** for the shared tools; `get_session_info` (session identity) and `session_search`/`session_get` (conversation archaeology) are opencode-only — MCP hosts have no session concept. The `thatch session` CLI subcommands cover the same archaeology on any host with an opencode.db file. The `watch_*` tools (PR, branch, and command watchers) are opencode-only — MCP hosts have no poller or proactive-prompt channel. The `chat_*` tools (cross-session chat) are opencode-only — no host session identity and no wake-up channel. `session_tab` and `session_tab_close` are v2-opencode-only — they drive TUI session tabs over the v2 rpc-event bridge |
|
|
22
22
|
| **System prompt** (store names, usage rules) | `experimental.chat.system.transform` — dynamic, repo baked in at runtime | CLAUDE.md static text appended by `thatch setup`; repo auto-detected by the MCP server at startup | AGENTS.md static text appended by `thatch setup`; repo auto-detected at startup | **Approximate** — static text persists through compaction, so the agent retains the usage instructions, but the dynamic per-turn refresh is lost |
|
|
23
23
|
| **Session-start reminder** (recall nudge + hygiene heartbeat) | `session.created` event → `client.session.prompt` injects a synthetic message | `SessionStart` hook → `thatch reminder`; stdout becomes context | `sessionStart` hook → `thatch reminder --json`; output is `additional_context` JSON | **Full** |
|
|
24
24
|
| **Compaction context** (re-familiarize after compaction) | `experimental.session.compacting` hook appends context to the compaction output | `PostCompact` hook — side-effects only, **cannot inject context** | No equivalent hook | **Gapped** — see below |
|
|
@@ -31,7 +31,7 @@ This document maps the feature parity and documents the remaining gaps.
|
|
|
31
31
|
| **Prediction auto-fire** (user decision model) | `chat.message` hook scores the prompt embedding against prediction matchers; injects a `[thatch] User decision model` nudge alongside the recall nudge (separate synthetic part, same embedding call) | `UserPromptSubmit` hook → `thatch flush-tools` fires prediction query via the sideband socket's `predictions` method in parallel with the recall nudge | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — same `scorePredictionNudge` entry point in both paths |
|
|
32
32
|
| **Behavior auto-fire** (self-discipline rules) | `chat.message` hook scores the prompt embedding against behavior matchers; injects a `[thatch] Situational behaviors` nudge (separate synthetic part, same embedding call) | `UserPromptSubmit` hook → `thatch flush-tools` fires behavior query via the sideband socket's `behaviors` method in parallel with recall and predictions | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — same `scoreBehaviorNudge` entry point in both paths |
|
|
33
33
|
| **Skills** | Installed to `$XDG_CONFIG_HOME/opencode/skills` at plugin init (shared **+** opencode-only) | Installed by `thatch setup` (shared only) to the repo's `.claude/skills/` (local) or `$CLAUDE_CONFIG_DIR/skills/` (global) | Installed by `thatch setup` (shared only) to the repo's `.cursor/skills/` (local) or `~/.cursor/skills/` (global) | **Full** — same SKILL.md format; the code-review coordinator is opencode-only (needs sub-agents) |
|
|
34
|
-
| **Store detection** (repo identity from git remote) | `worktree` parameter from the opencode plugin | `CLAUDE_PROJECT_DIR` env
|
|
34
|
+
| **Store detection** (repo identity from git remote) | `worktree` parameter from the opencode plugin | `THATCH_PROJECT_DIR` (pinned by project-local setup) → `CLAUDE_PROJECT_DIR` env → cwd | `THATCH_PROJECT_DIR` (pinned by project-local setup) → `CURSOR_PROJECT_DIR` then `CLAUDE_PROJECT_DIR` then cwd | **Full** |
|
|
35
35
|
| **Setup detection at startup** | n/a — plugin auto-installs at init | `checkSetup` in MCP server: detects missing or broken instructions in CLAUDE.md | Same as Claude Code (checks AGENTS.md) | **Full** — warns the agent to tell the user to run `thatch setup` |
|
|
36
36
|
| **Hook config format** | n/a — plugin registers hooks in code | Nested: `.claude/settings.json` `{hooks:{Event:{hooks:[{type,command}]}}}` | Flat: `.cursor/hooks.json` `{version:1,hooks:{event:[{command}]}}` | n/a |
|
|
37
37
|
|
|
@@ -80,7 +80,7 @@ project and every contributor gets them).
|
|
|
80
80
|
|-------|---------|--------|------|
|
|
81
81
|
| `SessionStart` | `thatch reminder` | plain text to stdout (becomes context) | Recall instructions + hygiene heartbeat + chat identity/mail line (reads the hook's stdin `session_id`) |
|
|
82
82
|
| `PostToolBatch` | `thatch buffer-batch` | **silent** (no stdout) | Appends a batch of tool calls to the file-backed JSONL queue |
|
|
83
|
-
| `UserPromptSubmit` | `thatch flush-tools` | nudge text to stdout | Peeks queue (extraction nudge), else fires recall, prediction, and behavior nudges via sideband in parallel, else write nudge |
|
|
83
|
+
| `UserPromptSubmit` | `thatch flush-tools` | nudge text to stdout | Peeks queue (extraction nudge; degraded to a restart instruction under version skew - the old server may lack the referenced tools), else fires recall, prediction, and behavior nudges via sideband in parallel, else write nudge |
|
|
84
84
|
| `Stop` | `thatch chat-notify` | `{ hookSpecificOutput: { additionalContext } }` when chat mail is unread, else `{}` | Post-turn chat wake: the turn continues so the model reads its mail (Claude Code's analog of opencode's poller wake); host loop protections (`stop_hook_active`, 8-consecutive cap) plus the delivered stamp bound repeats |
|
|
85
85
|
|
|
86
86
|
`PostToolBatch` is silent so the agent loop is not delayed; the buffered
|
|
@@ -122,7 +122,7 @@ is honored for symmetry and forward-compatibility. Cursor has no equivalent of
|
|
|
122
122
|
|-------|---------|--------|------|
|
|
123
123
|
| `sessionStart` | `thatch reminder --json` | `{ additional_context: "..." }` | Recall + heartbeat + chat identity/mail line, JSON-wrapped for Cursor |
|
|
124
124
|
| `postToolUse` | `thatch buffer-tool` | **silent** | Appends a **single** tool call to the file-backed queue |
|
|
125
|
-
| `beforeSubmitPrompt` | `thatch flush-tools --json` | JSON `additional_context` | Peeks queue, else recall, prediction, and behavior via sideband, else write nudge |
|
|
125
|
+
| `beforeSubmitPrompt` | `thatch flush-tools --json` | JSON `additional_context` | Peeks queue (extraction nudge; degraded to a restart instruction under version skew), else recall, prediction, and behavior via sideband, else write nudge |
|
|
126
126
|
| `stop` | `thatch chat-notify` | `{ followup_message }` or `{}` | Post-turn chat wake: when chat mail is unread, Cursor auto-submits the follow-up as the next user message (Cursor's analog of opencode's poller wake); `loop_limit: 3` bounds consecutive follow-ups |
|
|
127
127
|
|
|
128
128
|
Differences from Claude Code:
|
|
@@ -179,6 +179,11 @@ The host is determined from env vars set by the host process:
|
|
|
179
179
|
|
|
180
180
|
Cursor takes priority because Cursor also sets `CLAUDE_PROJECT_DIR` as an alias.
|
|
181
181
|
|
|
182
|
+
Separately from host detection, the project directory used for store/chat
|
|
183
|
+
identity resolution resolves as `THATCH_PROJECT_DIR ?? CURSOR_PROJECT_DIR
|
|
184
|
+
?? CLAUDE_PROJECT_DIR ?? cwd` in the MCP server; project-local `thatch
|
|
185
|
+
setup` runs pin `THATCH_PROJECT_DIR` into the written MCP config.
|
|
186
|
+
|
|
182
187
|
### What it checks
|
|
183
188
|
|
|
184
189
|
`checkSetup` looks for the instruction markers (start and end) in the host's
|
package/docs/dev/skills.md
CHANGED
|
@@ -69,7 +69,7 @@ description: Extract durable project facts ... Use when ...
|
|
|
69
69
|
| `thatch-review-response` | Author-side review response: triage findings, fix bugs one by one, reply on each thread, post a top-level summary comment. |
|
|
70
70
|
| `thatch-memory-verify` | Fact-check a single memory against the current codebase and correct stale claims. Uses git archaeology to preserve historical context when changes were intentional. |
|
|
71
71
|
| `thatch-knowledge-export` | Compile everything thatch knows about a topic into a curated markdown file for knowledge transfer. Searches across stores, curates out personal noise, fact-checks code-related memories via `thatch-memory-verify`. |
|
|
72
|
-
| `thatch-clear-writing` | Prose rules for any human-facing text not covered by a dedicated writing skill: PR and ticket comments, documentation, plans, reports, and
|
|
72
|
+
| `thatch-clear-writing` | Prose rules for any human-facing text not covered by a dedicated writing skill: PR and ticket comments, documentation, plans, reports, chat replies, and code comments. Clarity over compression; dedicated skills win when loaded. |
|
|
73
73
|
|
|
74
74
|
**opencode-only** — the coordinator needs sub-agent support:
|
|
75
75
|
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Plan: watcher death detection for closed v2 tabs
|
|
2
|
+
|
|
3
|
+
Status: v3.1 - empirical premise VERIFIED live (2026-10-07: prompt_async
|
|
4
|
+
to a closed v2 tab returns 404; no silent success; the session row
|
|
5
|
+
persists, so the pre-cancel re-check keys on the throw class). The
|
|
6
|
+
build's gate is open. Two refinement rounds converged (round 2: BUILD,
|
|
7
|
+
no blockers).
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
v2 tab close fires no `session.deleted`, so the closed tab's entry in
|
|
12
|
+
the runtime's `sessionStatus` map (src/runtime.ts:217-222, keys "leave
|
|
13
|
+
only in session.deleted") never leaves, the chat poller keeps hosting
|
|
14
|
+
and heartbeat-ing the dead tab's row (src/runtime.ts:363-368,
|
|
15
|
+
src/chat.ts:879-883), and its watchers poll until the 8h TTL with
|
|
16
|
+
deliveries queuing forever. The session.deleted handler itself names
|
|
17
|
+
the failure mode: "a leaked status key would keep heartbeat-ing a dead
|
|
18
|
+
session as permanently fresh" (src/runtime.ts:1891).
|
|
19
|
+
|
|
20
|
+
## Rejected: heartbeat-staleness death detection (the original plan)
|
|
21
|
+
|
|
22
|
+
The original design gated the heartbeat stamp on `fetchStatuses`
|
|
23
|
+
presence so a closed tab would age out. Round 1 proved the premise
|
|
24
|
+
inverted: the live status map carries only ACTIVE (mid-turn) sessions -
|
|
25
|
+
the host deletes idle entries (thatch's own wake-gate contract,
|
|
26
|
+
src/chat.ts:366-368; host source session/status.ts:42-46) - so absence
|
|
27
|
+
means "idle OR closed". Gating stamps on presence would mark every
|
|
28
|
+
idle-open tab stale in 60s (regressing e486148, whose grace exists
|
|
29
|
+
precisely because idle-open tabs emit nothing) and death-cancel the
|
|
30
|
+
watchers of every idle session. And on v2 - the only host with the bug
|
|
31
|
+
- `fetchStatuses` is a permanent `{}` stub (src/opencode/v2.ts:524;
|
|
32
|
+
"session.status exists in v2's schema but nothing publishes it",
|
|
33
|
+
v2.ts:417), so the mechanism does not exist where the bug lives.
|
|
34
|
+
Chat-off sessions (chatPoller.start gated on chatOn,
|
|
35
|
+
src/runtime.ts:407-409) get no stamps at all - a third false-death
|
|
36
|
+
path.
|
|
37
|
+
|
|
38
|
+
Conclusion: the heartbeat cannot carry tab-death detection on v2. The
|
|
39
|
+
heartbeat keeps its current role (roster liveness for chat, unconditional
|
|
40
|
+
stamps for hosted sessions); death detection needs a different clock.
|
|
41
|
+
|
|
42
|
+
## New design: delivery-persistence death detection
|
|
43
|
+
|
|
44
|
+
The signal that DOES distinguish a closed v2 tab from a live one, with
|
|
45
|
+
no host API: **what happens to a pending watcher event.**
|
|
46
|
+
|
|
47
|
+
- A live v2 session with queued events consumes them: the delivery gate
|
|
48
|
+
opens (on v2 the stub map reads as idle) and `promptAsync` succeeds -
|
|
49
|
+
there is no busy-rejection on the v2 prompt path. Queues drain.
|
|
50
|
+
- A closed v2 session: every `deliver()` attempt throws (the session no
|
|
51
|
+
longer exists server-side) and the queue retries forever
|
|
52
|
+
(src/watchers.ts:1296-1298 - failed delivery stays pending).
|
|
53
|
+
|
|
54
|
+
So: **death = a session's pending watcher queue has been non-empty for
|
|
55
|
+
longer than the threshold AND recent delivery attempts have THROWN**
|
|
56
|
+
(not been skipped - a skipped attempt is a busy/compacting session and
|
|
57
|
+
must never count). The heartbeat is untouched; this is one extra clock
|
|
58
|
+
on one queue, per session.
|
|
59
|
+
|
|
60
|
+
- Threshold: 2 hours default (env `THATCH_WATCH_DEATH_MINUTES`). Far
|
|
61
|
+
above any legitimate busy turn; far below the 8h TTL, which remains
|
|
62
|
+
the hard backstop. The failure counter counts CONSECUTIVE thrown
|
|
63
|
+
deliveries per session (side map keyed by session id - WatcherEvent
|
|
64
|
+
carries no timestamp); a SKIPPED attempt (busy/compacting) or a
|
|
65
|
+
success RESETS it - skips are never death evidence.
|
|
66
|
+
- Pre-cancel re-check (round 2): when the threshold fires, re-verify
|
|
67
|
+
the session via `caps.sessionGet` before cancelling - only a
|
|
68
|
+
not-found-class result confirms death. A wedged server or 500-loop
|
|
69
|
+
throws exactly like a dead session does, and without the re-check a
|
|
70
|
+
two-hour API outage would mass-cancel every hosted watcher.
|
|
71
|
+
- v1 is out of scope: v1 fires session.deleted on tab close (the
|
|
72
|
+
known-gaps table), so v1 tabs already clean up; crashed v1 daemons
|
|
73
|
+
are covered by the dormant-row machinery.
|
|
74
|
+
- On death: cancel the session's watchers (watchers.cancelSession -
|
|
75
|
+
also drops the pending queue, src/watchers.ts:1195-1201), emit a
|
|
76
|
+
death notice via the existing promptAsync path to other live
|
|
77
|
+
same-project sessions (`watcherDeathNotice`, src/prompts.ts:1051;
|
|
78
|
+
dedup per dead session like notifiedWatcherDeaths,
|
|
79
|
+
src/runtime.ts:801-806), and persist a `watcher_death` runtime_state
|
|
80
|
+
row (generic runtimeStatePut/All/Delete - no new db helpers).
|
|
81
|
+
- Reader for the persisted row (round 2): the rehydration switch
|
|
82
|
+
(src/runtime.ts:609-713) must learn the `watcher_death` kind
|
|
83
|
+
(foreign-pid rows KEPT like dormant watcher rows, not deleted) and
|
|
84
|
+
the dormant-scan path (src/runtime.ts:779-825) surfaces pending
|
|
85
|
+
death rows to the scanning session, deduping against
|
|
86
|
+
`notifiedWatcherDeaths`, deleting the row on surfacing. Without this
|
|
87
|
+
the persistence half is write-only: unknown foreign-pid kinds are
|
|
88
|
+
deleted on restart (:615-641) and `dormantScanned` runs once per
|
|
89
|
+
session per process.
|
|
90
|
+
- Scope guard: only sessions this process HOSTS via the
|
|
91
|
+
`hostedSessionIds` helper (src/runtime.ts:363-370) are eligible -
|
|
92
|
+
sibling v1 processes and MCP sessions are never touched.
|
|
93
|
+
- Residual case (round 2, documented): a tab closed MID-TURN whose
|
|
94
|
+
host never emits a terminal execution event keeps its mapped status
|
|
95
|
+
`busy` forever - deliveries skip forever, death never fires, TTL
|
|
96
|
+
covers it. Accepted; the notice will not fire for that shape.
|
|
97
|
+
- The roster zombie (a closed tab's row shows Active for days; the
|
|
98
|
+
7-day auto-reaper bounds auto rows) is NOT fixed by this plan - no v2-
|
|
99
|
+
reachable signal distinguishes it. Documented as a known limitation;
|
|
100
|
+
the reviewer confirmed no last_seen consumer treats staleness as a
|
|
101
|
+
trigger today, so the honest heartbeat stays unconditional.
|
|
102
|
+
- Death notice payload (session-tab coordination, wanda-warpdrive-00009
|
|
103
|
+
2026-10-07): the notice carries the dead session's CHAT NAME and
|
|
104
|
+
session id, not just the watch targets, so a coordinator session
|
|
105
|
+
running the thatch-coordination skill can mark its task-list entry
|
|
106
|
+
failed and respawn keyed by chat name, no SDK lookup.
|
|
107
|
+
|
|
108
|
+
### Confirmed-close fast path (integration with session-tab)
|
|
109
|
+
|
|
110
|
+
The session-tab tool (docs/dev/features/session-tabs.md; merged to main
|
|
111
|
+
as 5abb381) emits `rpc.thatch-tabs.tab-closed {sessionID,
|
|
112
|
+
chatName?}` from its TUI plugin when a close is TOOL-initiated (shared
|
|
113
|
+
module session-tab-shared.ts; user-initiated closes stay out of scope
|
|
114
|
+
there - the delivery-persistence heuristic covers those). When the
|
|
115
|
+
event lands for a session with watchers, treat it as a CONFIRMED
|
|
116
|
+
close: skip the 2h heuristic entirely and run the death path
|
|
117
|
+
immediately. The rpc definition is imported from session-tab-shared
|
|
118
|
+
(same package). Fallback layering: confirmed close (instant) >
|
|
119
|
+
delivery-persistence heuristic (2h) > TTL (8h).
|
|
120
|
+
|
|
121
|
+
## Empirical premise: VERIFIED (2026-10-07, live probe via the session-tab smoke flow)
|
|
122
|
+
|
|
123
|
+
Jeff closed a real v2 tab (session chatting as rosie-unit-one-00007,
|
|
124
|
+
session ses_ee7ec0baeffeziwE83AT2mgLxI) with a command watch armed
|
|
125
|
+
before the close. Probe results against the serve daemon's /api surface
|
|
126
|
+
(Basic auth: username `opencode`, password from
|
|
127
|
+
~/.config/opencode/service.json; unprefixed /session/* paths are the
|
|
128
|
+
TUI SPA fallback - 200 HTML for anything, do not trust them):
|
|
129
|
+
|
|
130
|
+
- POST /api/session/{id}/prompt_async -> HTTP **404** (clean, no body):
|
|
131
|
+
the delivery throw is real and 404-class.
|
|
132
|
+
- Message count in the closed session before/after: 0 / 0 - **no silent
|
|
133
|
+
success, no headless turn**.
|
|
134
|
+
- GET /api/session/{id} -> 200 with the full record: the session ROW
|
|
135
|
+
persists server-side after tab close, so existence checks cannot
|
|
136
|
+
confirm death.
|
|
137
|
+
|
|
138
|
+
Consequence for design point 2's pre-cancel re-check: key on the throw
|
|
139
|
+
CLASS (404 = route/session gone) not on sessionGet existence - a
|
|
140
|
+
sessionGet 200 is expected even for dead tabs. Wedged-server 5xx loops
|
|
141
|
+
still do not count as death; only 404-class delivery throws do.
|
|
142
|
+
|
|
143
|
+
Location-scoping nuance (wanda-warpdrive-00009, post-probe): the v2
|
|
144
|
+
prompt route is session-location-middleware'd, so the probe's
|
|
145
|
+
cross-location 404 may encode location scoping rather than closed-ness
|
|
146
|
+
per se. The death signal keys on the HOSTING poller's own deliveries
|
|
147
|
+
throwing - same-location throws are the evidence that counts, and the
|
|
148
|
+
session-tab `tab-closed` event papers over the ambiguous cases. The
|
|
149
|
+
pre-cancel re-check and the threshold both run against the hosting
|
|
150
|
+
instance's observed throw class.
|
|
151
|
+
|
|
152
|
+
## Surfaces touched
|
|
153
|
+
|
|
154
|
+
### Pending-event durability (from the 2026-10-07 reload-loss incident)
|
|
155
|
+
|
|
156
|
+
A reload between DETECTION and DELIVERY silently eats notifications:
|
|
157
|
+
pending events live only in the in-memory `#pending` queue, and one-shot
|
|
158
|
+
watchers delete their journal row at detection time ("ends at first
|
|
159
|
+
detection, not first delivery") - so the durability layer is gone
|
|
160
|
+
exactly when undelivered events exist (observed: watch_qn449jio's CI
|
|
161
|
+
notification lost in the 09b805f reload window). Fix folded into this
|
|
162
|
+
build: journal PENDING EVENTS per session (alongside the watcher
|
|
163
|
+
definitions), delete them on successful delivery - which the death
|
|
164
|
+
detection needs anyway for its pending-age signal to survive reloads.
|
|
165
|
+
(Found by robot-devil-00012's code-verified analysis; my own watcher
|
|
166
|
+
was not one-shot, so its exact deletion path stays unproven until a
|
|
167
|
+
THATCH_DEBUG=1 repro.)
|
|
168
|
+
|
|
169
|
+
## Surfaces touched
|
|
170
|
+
|
|
171
|
+
- `src/watchers.ts` - per-session delivery-failure tracking (consecutive
|
|
172
|
+
throws + oldest-pending age) and a death-scan hook the poller calls.
|
|
173
|
+
- `src/runtime.ts` - wire the death callback: notice delivery to live
|
|
174
|
+
same-project sessions, the `watcher_death` runtime_state row, dedup.
|
|
175
|
+
- `src/db.ts` - runtime_state row helpers for the death record.
|
|
176
|
+
- `tests/watchers.test.ts` - death detection (throwing deliver + old
|
|
177
|
+
pending vs skipped-deliver + old pending), threshold env, MCP-owner
|
|
178
|
+
exclusion.
|
|
179
|
+
- `tests/qa/auto/uc-118-watcher-death-detection.ts` (new,
|
|
180
|
+
barrel-imported).
|
|
181
|
+
- Docs: `docs/user/watchers.md` (death notices), `docs/dev/features/
|
|
182
|
+
watchers.md` (detection subsection), `docs/dev/features/
|
|
183
|
+
opencode-plugin.md` known-gaps row (bounded by death detection),
|
|
184
|
+
`docs/user/cli.md` + `docs/dev/features/cli.md` (new env var).
|
|
185
|
+
|
|
186
|
+
## Acceptance criteria
|
|
187
|
+
|
|
188
|
+
1. A closed v2 tab's watchers are cancelled within the threshold once
|
|
189
|
+
deliveries have been failing; a live same-project session hears the
|
|
190
|
+
death notice.
|
|
191
|
+
2. A busy or idle LIVE session never triggers death (skipped delivery
|
|
192
|
+
attempts never count; successful delivery drains the queue).
|
|
193
|
+
3. A transient delivery failure (< threshold) cancels nothing.
|
|
194
|
+
4. MCP-owner and non-hosted sessions are never touched.
|
|
195
|
+
5. `mise run check` + `mise run qa-auto` green.
|
|
196
|
+
|
|
197
|
+
## Review rounds
|
|
198
|
+
|
|
199
|
+
- Round 1 (fresh reviewer): DO-NOT-BUILD on the heartbeat mechanism -
|
|
200
|
+
inverted premise (status absence means idle, not dead; v2 stub
|
|
201
|
+
returns {}), chat-off and sleep false-death paths, dormantScanned
|
|
202
|
+
once-per-session guard, incomplete death cleanup. The revision
|
|
203
|
+
replaces the mechanism with delivery-persistence detection
|
|
204
|
+
(v2-scoped) and drops the roster-zombie claim.
|
|
205
|
+
- Round 2 (fresh reviewer): BUILD conditional on the empirical probe;
|
|
206
|
+
all premises verified (thrown deliveries stay pending at
|
|
207
|
+
src/watchers.ts:1344-1349, live sessions drain at runtime.ts:1866-
|
|
208
|
+
1874), with three required adjustments folded in: the death-row
|
|
209
|
+
reader + rehydration handling, the pre-cancel existence re-check,
|
|
210
|
+
and the known-gaps doc correction. Consensus to build - AFTER the
|
|
211
|
+
probe.
|
|
212
|
+
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fire Tracking and Compound Predictions
|
|
2
2
|
|
|
3
3
|
## Synopsis
|
|
4
4
|
|
|
5
|
-
Post-processing layers for the prediction engine: fire tracking
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Post-processing layers for the prediction engine: fire tracking and compound
|
|
6
|
+
(tier-2) predictions minted from co-firing constellations of existing
|
|
7
|
+
predictions. Uses nudge-driven detection and the existing edge model for fire
|
|
8
|
+
behavior. No cron, no separate backend.
|
|
9
|
+
|
|
10
|
+
The consolidation (dedup) half was implemented separately and has graduated —
|
|
11
|
+
the shipped design is documented in
|
|
12
|
+
[../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)
|
|
13
|
+
("Dedup pass"). What remains here is the fire-tracking prerequisite
|
|
14
|
+
and the co-fire refinements (behavioral dedup, compound detection) that
|
|
15
|
+
consume the accumulated data.
|
|
9
16
|
|
|
10
17
|
## Background
|
|
11
18
|
|
|
@@ -14,10 +21,10 @@ auto-fires at chat.message and updates via agent tool calls. What it lacks
|
|
|
14
21
|
relative to nak's samskara system:
|
|
15
22
|
|
|
16
23
|
- **Fire tracking** -- no record of which predictions fire together on the same
|
|
17
|
-
turn. This is the prerequisite for
|
|
18
|
-
- **Consolidation** --
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
turn. This is the prerequisite for co-fire dedup and compound detection.
|
|
25
|
+
- **Consolidation** -- implemented and graduated: a cosine-only dedup pass
|
|
26
|
+
over the prediction corpus, no prerequisites
|
|
27
|
+
([../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)).
|
|
21
28
|
- **Compound predictions** -- no mechanism to mint a higher-order prediction
|
|
22
29
|
from a constellation of predictions that reliably co-fire. Nak's tier-2
|
|
23
30
|
samskaras fire at 77% genuine-engagement vs tier-1's 21% because compound
|
|
@@ -43,10 +50,13 @@ existing edge model.
|
|
|
43
50
|
The co-fire data is for *deciding what to compound*, not for *making the
|
|
44
51
|
compound fire*. This means:
|
|
45
52
|
|
|
46
|
-
1. A compound needs no tier column
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
1. A compound needs no tier column and no fire-path changes. It is a
|
|
54
|
+
prediction with multiple matcher edges. The scoring path does get one
|
|
55
|
+
addition: dynamic child suppression (see the compound-domination risk).
|
|
56
|
+
2. The existing `scoreNudge`/`scoreItems` already follow all edges and
|
|
57
|
+
deduplicate by prediction_id. A compound scores through its children's
|
|
58
|
+
edges. Minting records the compound-to-children relation (Phase 4) so
|
|
59
|
+
suppression can tell parent from child.
|
|
50
60
|
3. The existing `prediction_update` tool already finds existing matchers by
|
|
51
61
|
cosine (0.85 threshold) and creates edges. Calling it multiple times with
|
|
52
62
|
different matcher texts but the same prediction statement creates a
|
|
@@ -111,14 +121,18 @@ pass null or derive from the socket connection.
|
|
|
111
121
|
tables. Same conventions (uuid PK, store column, idempotent CREATE TABLE IF
|
|
112
122
|
NOT EXISTS).
|
|
113
123
|
|
|
114
|
-
### Phase 2:
|
|
115
|
-
|
|
116
|
-
**New DB method: `findPredictionDuplicates(store, threshold)`**
|
|
124
|
+
### Phase 2: Co-fire refinement of dedup
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
The cosine-only dedup pass (`findPredictionDuplicates`, the
|
|
127
|
+
`prediction_dedup_pairs` table, the hygiene line, the merge flow) is
|
|
128
|
+
IMPLEMENTED and graduated — documented in
|
|
129
|
+
[../dev/features/prediction-engine.md](../dev/features/prediction-engine.md)
|
|
130
|
+
("Dedup pass"). Once
|
|
131
|
+
fire data accumulates, this plan upgrades that same method with behavioral
|
|
132
|
+
evidence, mirroring nak's `samskara_collapse_by_cofiring`:
|
|
119
133
|
|
|
120
|
-
**
|
|
121
|
-
where both have fire_count > 0:
|
|
134
|
+
**Behavioral pass (added to `findPredictionDuplicates`):** For each pair of
|
|
135
|
+
predictions (A, B) in the store where both have fire_count > 0:
|
|
122
136
|
|
|
123
137
|
```
|
|
124
138
|
cofires(A, B) >= 3
|
|
@@ -131,26 +145,11 @@ when either fires are duplicates. Two that co-fire but also fire independently
|
|
|
131
145
|
are adjacent-but-distinct. Cosine is a sanity floor against spurious co-fires.
|
|
132
146
|
|
|
133
147
|
**Safety cap (cosine-only):** If the prediction count exceeds a target (e.g.,
|
|
134
|
-
100),
|
|
135
|
-
|
|
136
|
-
finds nothing but the pool is still growing.
|
|
137
|
-
|
|
138
|
-
**Output:** A list of candidate merge pairs with {predictionA, predictionB,
|
|
139
|
-
cofires, ratio, cosine, firesA, firesB}. Surfaced via the hygiene nudge, not
|
|
140
|
-
auto-merged. The agent reviews and merges via prediction_delete + (optionally)
|
|
141
|
-
prediction_update to fold the loser's matchers into the winner.
|
|
142
|
-
|
|
143
|
-
The merge itself is agent-driven: the agent sees the candidate pair, reads both
|
|
144
|
-
predictions, deletes the weaker one (or uses a future `prediction_merge` tool
|
|
145
|
-
if one is added). This matches the memory dedup pattern
|
|
146
|
-
(`find_duplicates` + `dedup_mark_checked`).
|
|
147
|
-
|
|
148
|
-
**Integration point:** `hygiene.ts` -- add a line to `hygieneReport`:
|
|
149
|
-
`N prediction duplicate pairs pending review` when candidates exist.
|
|
148
|
+
100), the implemented pure cosine pass already covers the pool;
|
|
149
|
+
no behavioral evidence is required to keep dedup moving.
|
|
150
150
|
|
|
151
|
-
**
|
|
152
|
-
|
|
153
|
-
(tens, not hundreds) so the cost is negligible.
|
|
151
|
+
**Output:** the dedup plan's candidate shape extended with {cofires, ratio,
|
|
152
|
+
firesA, firesB} per pair.
|
|
154
153
|
|
|
155
154
|
### Phase 3: Compound Prediction Detection
|
|
156
155
|
|
|
@@ -216,7 +215,14 @@ first child's matcher. Subsequent calls find the existing compound by
|
|
|
216
215
|
`findNearestPrediction` (0.85 store-wide dedup) and add edges from the other
|
|
217
216
|
children's matchers.
|
|
218
217
|
|
|
219
|
-
This works with the existing tool surface.
|
|
218
|
+
This works with the existing tool surface. One addition: minting must also
|
|
219
|
+
record WHICH predictions the compound supersedes, because child suppression
|
|
220
|
+
(see the compound-domination risk) needs the parent-to-children relation at
|
|
221
|
+
scoring time -- shared matcher edges are implicit and cannot distinguish a
|
|
222
|
+
compound from a busy multi-matcher prediction. `prediction_update` gains an
|
|
223
|
+
optional `supersedes` arg (array of prediction ids), carried on the first
|
|
224
|
+
mint call, writing one `prediction_compounds(compound_id, child_id)` row per
|
|
225
|
+
child. No tier column; the compound is still an ordinary prediction.
|
|
220
226
|
|
|
221
227
|
**Prompt instructions addition** (in prompts.ts, all three host variants):
|
|
222
228
|
|
|
@@ -225,8 +231,9 @@ After the existing prediction instructions, add guidance for compounds:
|
|
|
225
231
|
- When the hygiene nudge reports co-fire constellations, consider whether the
|
|
226
232
|
members share a generalizable behavior.
|
|
227
233
|
- If so, synthesize a compound statement and call `prediction_update` once per
|
|
228
|
-
child matcher with the compound statement
|
|
229
|
-
|
|
234
|
+
child matcher with the compound statement, passing the child prediction ids
|
|
235
|
+
as `supersedes` on the first call. The tool handles matcher dedup and edge
|
|
236
|
+
creation.
|
|
230
237
|
- A compound should be strictly more general than any single child. If you
|
|
231
238
|
cannot generalize, do not compound.
|
|
232
239
|
- The compound inherits the fire coverage of its children's matchers. It will
|
|
@@ -258,7 +265,7 @@ chat.message (user text)
|
|
|
258
265
|
session.created (top-level)
|
|
259
266
|
→ hygieneReport
|
|
260
267
|
→ findDuplicates (memories, existing)
|
|
261
|
-
→ findPredictionDuplicates (
|
|
268
|
+
→ findPredictionDuplicates (implemented; co-fire-refined here)
|
|
262
269
|
→ findCoFireConstellations (NEW: compound candidates)
|
|
263
270
|
→ inject hygiene nudge with all signals
|
|
264
271
|
|
|
@@ -276,44 +283,48 @@ computation, same as existing prediction methods.
|
|
|
276
283
|
db.ts.
|
|
277
284
|
|
|
278
285
|
**Code changes by file:**
|
|
279
|
-
- `src/db.ts` -- new table
|
|
280
|
-
cofireCount, totalCohorts,
|
|
286
|
+
- `src/db.ts` -- one new table (`prediction_fires`) + 4 new methods
|
|
287
|
+
(recordFires, fireCount, cofireCount, totalCohorts, findCoFireConstellations);
|
|
288
|
+
the co-fire refinement extends the implemented `findPredictionDuplicates`
|
|
289
|
+
method
|
|
281
290
|
- `src/runtime.ts` -- generate cohort_id, pass to recordFires in auto-fire path
|
|
282
291
|
- `src/sideband.ts` -- same for MCP path
|
|
283
|
-
- `src/hygiene.ts` -- add
|
|
292
|
+
- `src/hygiene.ts` -- add constellation count to report (the dedup count is
|
|
293
|
+
already implemented there)
|
|
284
294
|
- `src/prompts.ts` -- compound minting guidance in all three prompt variants
|
|
285
295
|
- `tests/prediction.test.ts` -- tests for new DB methods
|
|
286
296
|
- `tests/plugin.test.ts` -- test that fire tracking records cohorts
|
|
287
297
|
|
|
288
298
|
## Implementation Order
|
|
289
299
|
|
|
300
|
+
0. **Cosine dedup** -- IMPLEMENTED and graduated (Oct 2026):
|
|
301
|
+
`findPredictionDuplicates` + `prediction_mark_checked`; nothing left to do.
|
|
290
302
|
1. **Fire tracking** (table + recordFires + caller wiring). Foundation for
|
|
291
|
-
everything else. Ship and let fire data accumulate.
|
|
292
|
-
2. **
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
3. **
|
|
296
|
-
findPredictionDuplicates). Needs fire data to accumulate first. Wait a few
|
|
297
|
-
weeks after phase 1 ships.
|
|
298
|
-
4. **Compound detection** (findCoFireConstellations + hygiene integration).
|
|
303
|
+
everything else here. Ship and let fire data accumulate.
|
|
304
|
+
2. **Co-fire dedup** (add the behavioral pass to findPredictionDuplicates).
|
|
305
|
+
Needs fire data only -- the method and pair table already exist. Wait a
|
|
306
|
+
few weeks after fire tracking ships.
|
|
307
|
+
3. **Compound detection** (findCoFireConstellations + hygiene integration).
|
|
299
308
|
Needs more fire data than dedup (p_min_cofires = 10). Wait longer.
|
|
300
|
-
|
|
301
|
-
tools). Ships with
|
|
302
|
-
compounds.
|
|
309
|
+
4. **Compound minting** (prompt instructions + agent-driven via existing
|
|
310
|
+
tools). Ships with compound detection -- the nudge surfaces candidates,
|
|
311
|
+
the agent compounds.
|
|
303
312
|
|
|
304
|
-
|
|
313
|
+
Fire tracking ships immediately; the rest follow once fire data accumulates.
|
|
305
314
|
|
|
306
315
|
## Test Plan
|
|
307
316
|
|
|
308
317
|
- `prediction.test.ts`: test recordFires, fireCount, cofireCount,
|
|
309
|
-
totalCohorts with synthetic cohorts
|
|
310
|
-
known co-firing pairs
|
|
318
|
+
totalCohorts with synthetic cohorts, the co-fire refinement of
|
|
319
|
+
findPredictionDuplicates with known co-firing pairs, and
|
|
320
|
+
findCoFireConstellations with lift thresholds.
|
|
311
321
|
Use the existing `makeEmbed(seed, dim=384)` helper.
|
|
312
322
|
- `plugin.test.ts`: test that chat.message auto-fire records a cohort.
|
|
313
323
|
Follow the recall-nudge test pattern (mock.module transformers + server
|
|
314
324
|
hooks).
|
|
315
325
|
- `hygiene.test.ts` (if exists) or `plugin.test.ts`: test that hygiene report
|
|
316
|
-
includes
|
|
326
|
+
includes the constellation count when candidates exist (the dedup count
|
|
327
|
+
tests are implemented, riding UC-113).
|
|
317
328
|
|
|
318
329
|
## Risks and Mitigations
|
|
319
330
|
|
|
@@ -324,11 +335,18 @@ precondition gate (minimum prediction count, minimum fire count) before running
|
|
|
324
335
|
the analysis.
|
|
325
336
|
|
|
326
337
|
**Risk: Compound predictions fire too much, dominating the nudge.**
|
|
327
|
-
Mitigation:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
Mitigation (nak-proven): dynamic child suppression, not equal-footing
|
|
339
|
+
competition. When a compound and its child both score in the same round and
|
|
340
|
+
the compound's score is higher, the child is skipped. The rule resolves
|
|
341
|
+
during the scoring pass, per query -- not as a stored flag -- so the
|
|
342
|
+
surviving set adjusts as it searches and freed slots refill from the
|
|
343
|
+
remaining scored list. If the child outscores the compound (it matched a
|
|
344
|
+
matcher the compound did not inherit), both surface. Implementation: the
|
|
345
|
+
per-round dedup filter in `scoreItems` (src/scoring-engine.ts:153-178)
|
|
346
|
+
gains the suppression check, using the compound-to-children relation
|
|
347
|
+
recorded at mint time. Plain prediction_id dedup and the limit=5 slice are
|
|
348
|
+
unchanged. Fire rows record post-suppression survivors only, so co-fire
|
|
349
|
+
data means co-surfaced.
|
|
332
350
|
|
|
333
351
|
**Risk: Agent creates poor compounds (too generic, not useful).**
|
|
334
352
|
Mitigation: The prompt instructions include the "strictly more general" guard
|
|
@@ -351,9 +369,11 @@ this becomes a concern. Low priority.
|
|
|
351
369
|
a separate improvement, not part of this plan, but worth doing alongside
|
|
352
370
|
phase 1.
|
|
353
371
|
- The existing write-time dedup (`findNearestPrediction` at 0.85) catches
|
|
354
|
-
near-identical statements at creation. The
|
|
355
|
-
|
|
356
|
-
|
|
372
|
+
near-identical statements at creation. The implemented dedup pass
|
|
373
|
+
(cosine, with its co-fire refinement here) catches
|
|
374
|
+
behavioral duplicates that differ in wording. The layers are
|
|
375
|
+
complementary: write-time catches semantic twins, the dedup passes catch
|
|
376
|
+
behavioral twins.
|
|
357
377
|
- Nak's tier-2 decline ledger (`samskara_tier2_declines`, TTL'd 7 days) prevents
|
|
358
378
|
re-offering a declined constellation every sweep. Thatch's equivalent: a
|
|
359
379
|
`prediction_constellation_declines` table or a `dedup_pairs`-style
|