mercury-agent 0.24.0 → 0.24.2-beta.0
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/docs/extensions.md +1 -0
- package/docs/goals/conversation-review/decisions.md +352 -0
- package/docs/goals/conversation-review/goal.md +150 -0
- package/docs/goals/conversation-review/roadmap.md +94 -0
- package/docs/goals/space-links/decisions.md +443 -0
- package/docs/goals/space-links/goal.md +141 -0
- package/docs/goals/space-links/roadmap.md +129 -0
- package/docs/pending-verification.md +14 -1
- package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/plan.md +2 -2
- package/examples/extensions/office-365-mail/README.md +3 -2
- package/examples/extensions/office-365-mail/index.ts +35 -1
- package/examples/extensions/office-365-mail/lib/graph.ts +158 -11
- package/examples/extensions/office-365-mail/lib/save.ts +140 -0
- package/examples/extensions/office-365-mail/skill/SKILL.md +15 -1
- package/examples/extensions/voice-transcribe/index.ts +192 -50
- package/examples/extensions/voice-transcribe/skill/SKILL.md +2 -0
- package/package.json +7 -7
- package/src/core/runtime.ts +260 -53
- package/src/extensions/types.ts +14 -0
- package/src/storage/db.ts +58 -1
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S1.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S2.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S3.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S4.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S5.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S6.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S7.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/briefs/S8.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/decisions.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S1.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S2.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S3.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S4.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S5.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S6.md +0 -0
- /package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/sessions/S7.md +0 -0
package/docs/extensions.md
CHANGED
|
@@ -174,6 +174,7 @@ Both `workspace_init` and `before_container` events include:
|
|
|
174
174
|
|
|
175
175
|
`before_container` also includes:
|
|
176
176
|
- `attachments` — incoming `MessageAttachment[]` (e.g. voice, images), if any
|
|
177
|
+
- `ambientAttachments` — files overheard in this conversation since the bot last ran in it (a group voice note sent without a mention), each with `authorId` / `authorName`. Only authors whose role holds `media.receive`, only files still on disk, at most 10 rows, never on scheduled runs. Handed over once: the run saves its user turn, and the next run looks only past it — including when the handler failed, so a failed transcription is not retried.
|
|
177
178
|
|
|
178
179
|
#### `before_container` mutations
|
|
179
180
|
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Decisions: Conversation Review
|
|
2
|
+
|
|
3
|
+
**Goal**: [conversation-review](goal.md)
|
|
4
|
+
|
|
5
|
+
## D-001: The scan is a host-side Mercury extension, `conversation-review`, running on each bot
|
|
6
|
+
- **Category:** architecture
|
|
7
|
+
- **Decided:**
|
|
8
|
+
- New directory `examples/extensions/conversation-review/`, host-side only. It
|
|
9
|
+
has no CLI, no skill, no permission and adds nothing to the agent image.
|
|
10
|
+
- It borrows two existing patterns:
|
|
11
|
+
- from **napkin**: `mercury.job` scheduling, a throttle kept in
|
|
12
|
+
`mercury.store`, and a read-only `bun:sqlite` open of `state.db`;
|
|
13
|
+
- from **longview**: a `pi` spawn with no tools.
|
|
14
|
+
- Both bots run the same code. The local bot gets it from the installed
|
|
15
|
+
snapshot. Tagula gets it through the release.
|
|
16
|
+
- **Alternatives considered:**
|
|
17
|
+
- **A laptop-side scanner that pulls `state.db` from both boxes.** Raw client
|
|
18
|
+
conversations would leave Hetzner, and the Tagula runbooks already exclude
|
|
19
|
+
`state.db` from backups for exactly that reason.
|
|
20
|
+
- **A scheduled agent task** (a `tasks` row with a review prompt). The agent
|
|
21
|
+
would then read other spaces' messages from inside a container, which needs
|
|
22
|
+
cross-space read authority that does not exist until `space-links`. Its
|
|
23
|
+
output also becomes a chat message, not a file.
|
|
24
|
+
- **Core code in `src/`.** Every deployment would pay for it. An extension can
|
|
25
|
+
be off by default.
|
|
26
|
+
- **Reasoning:** Data stays where it lives, one code path serves both bots, and
|
|
27
|
+
napkin already proves the shape: an hourly job, a read-only DB and a pi spawn
|
|
28
|
+
on the owner's subscription.
|
|
29
|
+
- **Revisit if:** `space-links` ships a management space with cross-space read on
|
|
30
|
+
Tagula. The weekly deep pass could then run as a management-space task that
|
|
31
|
+
can take follow-up questions.
|
|
32
|
+
- **Used by:** all stories.
|
|
33
|
+
|
|
34
|
+
## D-002: A shared identifier redactor with a fail-closed gate, in core
|
|
35
|
+
- **Category:** pattern
|
|
36
|
+
- **Decided:**
|
|
37
|
+
- `src/core/redact-identifiers.ts` exports `redactIdentifiers(text, known)` and
|
|
38
|
+
`assertNoIdentifiers(text)`.
|
|
39
|
+
- It replaces:
|
|
40
|
+
- phone numbers (international and Israeli local forms)
|
|
41
|
+
- WhatsApp JIDs and LIDs (`…@s.whatsapp.net`, `…@lid`, `…@g.us`)
|
|
42
|
+
- e-mail addresses
|
|
43
|
+
- every `author_name` and `display_name` the scan saw in the window, and
|
|
44
|
+
`space_roles.display_name`
|
|
45
|
+
- Each replacement is a stable per-report tag (`[member-3]`, `[phone-1]`), so
|
|
46
|
+
one person reads consistently within a report. Authors become their role.
|
|
47
|
+
- The gate runs on the final assembled report string and throws if any
|
|
48
|
+
identifier survives. The report is then not written, and the DM says why.
|
|
49
|
+
- **Alternatives considered:**
|
|
50
|
+
- **Redact per field.** CLAUDE.md warns against exactly this: each field fixed
|
|
51
|
+
in turn, and the next one missed.
|
|
52
|
+
- **Paraphrase only.** Rejected by the owner, 2026-09-28. Evidence is what
|
|
53
|
+
makes a finding actionable.
|
|
54
|
+
- **Reuse `redactTokens`.** It covers secrets, not people.
|
|
55
|
+
- **Reasoning:** "Prefer one pass at the join." The gate on the final string
|
|
56
|
+
cannot miss a site. Phone numbers also appear inside message bodies, not just
|
|
57
|
+
in columns (Tagula INBOX thread, 2026-08-28), so a column-level redactor is not
|
|
58
|
+
enough. It lives in core because `secret-redaction-at-ingress` and
|
|
59
|
+
`mercury-triage` will want the same function.
|
|
60
|
+
- **Revisit if:** a report needs a real identifier to be actionable (for example
|
|
61
|
+
"this member's LID never resolves"). The fix is a local lookup table beside the
|
|
62
|
+
report on the box, never an identifier in the committed file.
|
|
63
|
+
- **Used by:** redact-identifiers-with-gate, conversation-review-report-and-digest,
|
|
64
|
+
review-intake-to-repo.
|
|
65
|
+
|
|
66
|
+
## D-003: The daily judge runs on a cheap model on the chain's provider; the weekly pass runs on the strong leg
|
|
67
|
+
- **Category:** trade-off
|
|
68
|
+
- **Decided:**
|
|
69
|
+
- **Daily.** The daily judge spawns pi on the provider of the space's
|
|
70
|
+
resolved leg (`resolveSpaceModelLeg`), the same one it would use anyway,
|
|
71
|
+
with an explicit model from `conversation-review.judge_model`. The default is
|
|
72
|
+
the cheap tier (`claude-haiku-4-5`). Credentials come from the leg's provider,
|
|
73
|
+
exactly as longview gets them.
|
|
74
|
+
- **Weekly.** The weekly synthesis runs on the space's resolved leg unchanged,
|
|
75
|
+
which is the strong tier.
|
|
76
|
+
- Both spawns get a row in `tests/inventories/pi-spawns.ts`. The row records
|
|
77
|
+
the explicit model override and its reason.
|
|
78
|
+
- **Alternatives considered:**
|
|
79
|
+
- **The strong leg for every daily exchange.** At the owner's volume that is
|
|
80
|
+
Opus on every exchange every day, which is the cost shape
|
|
81
|
+
`feed-watch-triaged-roundup` exists to remove.
|
|
82
|
+
- **A heuristic pre-filter.** Rejected by the owner: the judge reads every
|
|
83
|
+
exchange.
|
|
84
|
+
- **Reasoning:** The 2026-09-07 owner decision for the `explore` subagent set the
|
|
85
|
+
precedent: a cheap tier as an explicit `provider` + `model` pair on the same
|
|
86
|
+
subscription. The house rule forbids pi's default preamble and a foreign
|
|
87
|
+
credential. It does not forbid a cheaper model on the same provider.
|
|
88
|
+
- **Revisit if:** the weekly spot check (D-006) finds that the cheap judge misses
|
|
89
|
+
real problems at a rate the owner won't accept. Then raise
|
|
90
|
+
`judge_model`, which is a config change.
|
|
91
|
+
- **Used by:** conversation-review-daily-judge,
|
|
92
|
+
conversation-review-weekly-synthesis.
|
|
93
|
+
|
|
94
|
+
## D-004: The unit of judgement is the exchange: user turns, then the bot's reply, plus context before them
|
|
95
|
+
- **Category:** architecture
|
|
96
|
+
- **Decided:**
|
|
97
|
+
- An **exchange** is one or more consecutive `role='user'` rows in one
|
|
98
|
+
`conversation`, followed by the `role='assistant'` rows up to the next user
|
|
99
|
+
turn.
|
|
100
|
+
- The judge also sees up to N earlier rows as context. The default is 6,
|
|
101
|
+
capped by characters.
|
|
102
|
+
- Rows with `source='scheduler'` (task output) are judged as **task
|
|
103
|
+
exchanges**, where the prompt is the task, not a user.
|
|
104
|
+
- `ambient` rows are context only, never judged.
|
|
105
|
+
- `tool` rows are dropped.
|
|
106
|
+
- Exchanges are **batched per conversation** into one judge spawn, up to a
|
|
107
|
+
character budget, so a space with 40 exchanges costs a few spawns rather than
|
|
108
|
+
40.
|
|
109
|
+
- **Alternatives considered:**
|
|
110
|
+
- **Whole-day conversation per spawn.** It overflows the argv cap
|
|
111
|
+
(`MAX_ARG_STRLEN`) and the judge loses precision.
|
|
112
|
+
- **One spawn per exchange.** Cost and wall clock scale with volume.
|
|
113
|
+
- **Reasoning:** The 48 h audit cited findings as message `#ids` inside
|
|
114
|
+
exchanges. Batching keeps cost linear in text size, not in message count.
|
|
115
|
+
- **Revisit if:** trace linking lands (`trace-records-system-prompt`, M4). The
|
|
116
|
+
unit could then include the run's tool calls.
|
|
117
|
+
- **Used by:** review-exchange-reader, conversation-review-daily-judge.
|
|
118
|
+
|
|
119
|
+
## D-005: The finding is a fixed JSON schema; the report is Markdown rendered from it
|
|
120
|
+
- **Category:** pattern
|
|
121
|
+
- **Decided:**
|
|
122
|
+
- The judge returns JSON: `{findings: Finding[]}`. The host validates it and
|
|
123
|
+
drops anything invalid, counting and reporting the drops.
|
|
124
|
+
- `Finding` fields:
|
|
125
|
+
- `kind`: `bug | feature | profile | config`
|
|
126
|
+
- `scope`: `global | space`
|
|
127
|
+
- `target_layer`: one of the named layers in `docs/behavior-layers.md` §3, a
|
|
128
|
+
closed enum copied into the prompt
|
|
129
|
+
- `severity`: `high | moderate | minor`
|
|
130
|
+
- `confidence`: 0–1
|
|
131
|
+
- `message_ids`: must exist in the batch; the host checks this
|
|
132
|
+
- `what_went_wrong`
|
|
133
|
+
- `proposed_improvement`
|
|
134
|
+
- `fingerprint`: normalized kind + target_layer + a short slug, used to find
|
|
135
|
+
duplicates across days
|
|
136
|
+
- The host renders `report.md` and writes `report.json` beside it. Intake reads
|
|
137
|
+
the JSON.
|
|
138
|
+
- **Alternatives considered:**
|
|
139
|
+
- **Free-text reports.** Nothing can be deduplicated or promoted in one step.
|
|
140
|
+
- **Reasoning:** The finding already had this shape in the 72 h review. The
|
|
141
|
+
closed layer enum is what makes "global or space-specific" something the host
|
|
142
|
+
checks, not prose.
|
|
143
|
+
- **Revisit if:** the layer set in `behavior-layers.md` §3 changes. The enum is
|
|
144
|
+
pinned by a test against that file (one contract, one statement).
|
|
145
|
+
- **Used by:** conversation-review-daily-judge,
|
|
146
|
+
conversation-review-report-and-digest, review-intake-to-repo,
|
|
147
|
+
conversation-review-weekly-synthesis.
|
|
148
|
+
|
|
149
|
+
## D-006: The weekly pass synthesizes the daily findings and spot-checks unflagged exchanges
|
|
150
|
+
- **Category:** scope
|
|
151
|
+
- **Decided:**
|
|
152
|
+
- The weekly job reads the last 7 daily `report.json` files, not the raw week.
|
|
153
|
+
- It clusters findings by fingerprint and meaning across spaces, and promotes
|
|
154
|
+
recurring space-level findings to global candidates.
|
|
155
|
+
- It re-judges a random sample of exchanges the daily judge passed, on the
|
|
156
|
+
strong leg. The default sample is 20 per bot.
|
|
157
|
+
- It reports the miss rate as the daily judge's measured recall.
|
|
158
|
+
- **Alternatives considered:**
|
|
159
|
+
- **A strong-model re-read of the whole week.** It duplicates the daily cost
|
|
160
|
+
at Opus prices.
|
|
161
|
+
- **Reasoning:** Synthesis is where "global vs space" becomes visible. The same
|
|
162
|
+
failure seen in three spaces is a code or global-profile finding, not three
|
|
163
|
+
`AGENTS.md` lines. The spot check turns "the cheap judge is good enough" into a
|
|
164
|
+
number that is re-measured every week.
|
|
165
|
+
- **Revisit if:** recall stays near 100% for a month. Shrink the sample.
|
|
166
|
+
- **Used by:** conversation-review-weekly-synthesis.
|
|
167
|
+
|
|
168
|
+
## D-007: Reports reach the repo by a pull on this machine, run by the owner (`/f-review-intake`)
|
|
169
|
+
- **Category:** architecture
|
|
170
|
+
- **Decided:**
|
|
171
|
+
- Each bot writes `<dataDir>/reviews/<YYYY-MM-DD>-{daily|weekly}/report.{md,json}`
|
|
172
|
+
and keeps 60 days.
|
|
173
|
+
- `scripts/review-intake.ts` on this machine:
|
|
174
|
+
1. reads the local bot's directory directly;
|
|
175
|
+
2. copies Tagula's over `ssh root@<box>` (`scp` of the reviews directory
|
|
176
|
+
only);
|
|
177
|
+
3. re-runs `assertNoIdentifiers` on every file;
|
|
178
|
+
4. writes `docs/reviews/<date>-<bot>-<kind>.md`;
|
|
179
|
+
5. emits an index of candidate docs and the ledger for triage (D-011).
|
|
180
|
+
- The `/f-review-intake` command wraps the script and triages every finding
|
|
181
|
+
against what is already documented or landed, then proposes an action per
|
|
182
|
+
finding for the owner to approve ([D-011](#d-011-intake-relates-every-finding-to-existing-work-before-deciding-what-to-do-with-it)).
|
|
183
|
+
- Nothing is committed or pushed automatically.
|
|
184
|
+
- **Alternatives considered:**
|
|
185
|
+
- **The bot commits to the repo.** It would need a write-capable credential on
|
|
186
|
+
a client box, and it would race live sessions in the shared checkout
|
|
187
|
+
(CLAUDE.local.md: "staging in the shared checkout is a race").
|
|
188
|
+
- **Tagula's `/api/console/*` control plane.** It is authenticated with the box
|
|
189
|
+
secret and has no reviews route. Add a route only if SSH access goes away.
|
|
190
|
+
- **Reasoning:** Human in the loop is the stated scope. The DM is the trigger,
|
|
191
|
+
and intake is where review happens. SSH to the box is already how operators
|
|
192
|
+
work there (tagula-agents runbooks).
|
|
193
|
+
- **Revisit if:** detection is trusted enough to automate filing (a later goal).
|
|
194
|
+
- **Used by:** review-intake-to-repo, conversation-review-on-tagula.
|
|
195
|
+
|
|
196
|
+
## D-008: Scheduling is a cron with an explicit time zone, off by default, one `conversation-review.enabled` switch per bot
|
|
197
|
+
- **Category:** pattern
|
|
198
|
+
- **Decided:**
|
|
199
|
+
- The jobs are hourly `interval` ticks that check a due-time computed in
|
|
200
|
+
`conversation-review.timezone` (default `Asia/Jerusalem`). The defaults are
|
|
201
|
+
daily at 04:00 and weekly on Sunday at 05:00.
|
|
202
|
+
- The throttle is kept in `mercury.store`. Missed runs catch up once, never
|
|
203
|
+
several times.
|
|
204
|
+
- The extension is **off** until `enabled=true`. Spaces can be excluded with
|
|
205
|
+
`conversation-review.exclude` (per space).
|
|
206
|
+
- The owner DM target is `conversation-review.digest_to`, a caller id resolved
|
|
207
|
+
by `ctx.send`.
|
|
208
|
+
- **Alternatives considered:**
|
|
209
|
+
- **`mercury.job({cron})`.** It is parsed with no time zone (`jobs.ts:114`),
|
|
210
|
+
so it runs in the host's local time, and the Hetzner box is probably UTC.
|
|
211
|
+
- **Reasoning:** Napkin's interval + throttle shape already survives restarts and
|
|
212
|
+
missed ticks. A fixed hour in a named time zone reads the same on both boxes.
|
|
213
|
+
- **Revisit if:** `mercury.job` gains a time zone.
|
|
214
|
+
- **Used by:** conversation-review-daily-judge,
|
|
215
|
+
conversation-review-weekly-synthesis, conversation-review-on-tagula.
|
|
216
|
+
|
|
217
|
+
## D-009: Client spaces on Tagula are opt-in per bot, and client consent is an open owner question
|
|
218
|
+
- **Category:** scope
|
|
219
|
+
- **Decided:**
|
|
220
|
+
- On Tagula, `enabled=true` covers the owner's own spaces.
|
|
221
|
+
- Client spaces are scanned only when `conversation-review.include_clients=true`,
|
|
222
|
+
set by the owner after the consent question is answered.
|
|
223
|
+
- The local bot has no client spaces.
|
|
224
|
+
- **Alternatives considered:**
|
|
225
|
+
- **Scan everything from day one.** Nothing on disk records client consent to
|
|
226
|
+
AI review (tagula-agents survey, 2026-09-28).
|
|
227
|
+
- **Reasoning:** Excerpts of client conversations are the owner's decision to
|
|
228
|
+
expose, even redacted. That decision should be made once, explicitly, not by
|
|
229
|
+
deploying a release.
|
|
230
|
+
- **Open question (owner):** do the Tagula terms, or each client, need to allow
|
|
231
|
+
automated quality review of conversations before `include_clients` is turned
|
|
232
|
+
on?
|
|
233
|
+
- **Used by:** conversation-review-on-tagula.
|
|
234
|
+
|
|
235
|
+
## D-010: The daily judge is a screener; a finding is a hypothesis until intake verifies it
|
|
236
|
+
- **Category:** trade-off
|
|
237
|
+
- **Decided:** (owner question, 2026-09-28: "what if the weak model makes a
|
|
238
|
+
mistake?")
|
|
239
|
+
- Nothing the cheap judge says is acted on directly. Its finding is a
|
|
240
|
+
*hypothesis with evidence*; the verdict belongs to intake (D-011), which runs
|
|
241
|
+
on a strong model with the repo in hand, and to the owner.
|
|
242
|
+
- Every kind of judge mistake has a named catcher:
|
|
243
|
+
|
|
244
|
+
| Mistake | Caught by | When |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| Output that isn't valid JSON, or a missing or out-of-range field | The host validator drops it and counts the drop | Daily |
|
|
247
|
+
| Invented message ids | Checked against the batch; dropped as `badIds` | Daily |
|
|
248
|
+
| Misquoted or invented evidence | The excerpt is read from `state.db` by message id, never taken from the judge's text | Daily |
|
|
249
|
+
| Flags an answer that was actually fine | Intake re-judges the excerpt first. A rejection goes into the ledger, the same fingerprint is suppressed afterwards, and it becomes a counter-example (M3.2) | Intake |
|
|
250
|
+
| Wrong kind, layer, scope or proposed fix | Treated as a hint only. Intake re-derives the layer by reading the real layer: `behavior-layers.md`, the space's profile, the code | Intake |
|
|
251
|
+
| Misses a bad answer | The weekly strong-model spot check measures recall (D-006). If recall is low, raise `judge_model` (D-003) | Weekly |
|
|
252
|
+
| Obeys an instruction written inside a conversation | The judge has no tools. The conversation is framed in the prompt as quoted data. Output is schema-validated and length-capped, and intake treats finding text as untrusted | Daily and intake |
|
|
253
|
+
| Wrong ranking of the digest's top three | The digest labels findings *unverified*. The DM is a pointer, not a verdict | Daily |
|
|
254
|
+
|
|
255
|
+
- The judge prompt leans toward **recall**. A false positive costs one line of
|
|
256
|
+
triage, but a miss is invisible until the weekly spot check. Noise is bounded
|
|
257
|
+
by a per-batch findings cap instead.
|
|
258
|
+
- **Alternatives considered:**
|
|
259
|
+
- **A strong-model second opinion on every finding before the report is
|
|
260
|
+
written.** This duplicates intake's check without intake's repo context:
|
|
261
|
+
it can't see whether the behaviour is a documented decision, already fixed,
|
|
262
|
+
or a known bug. It also adds strong-leg spend on the box every day.
|
|
263
|
+
- **A confidence threshold that drops low-confidence findings.** The cheap
|
|
264
|
+
model's confidence is not calibrated, so a threshold would hide misses
|
|
265
|
+
rather than remove noise. Confidence is shown in the report and used only
|
|
266
|
+
for ordering.
|
|
267
|
+
- **Reasoning:** Evidence that comes from the DB can't be wrong the way a claim
|
|
268
|
+
can. Classification is cheap to re-derive when you have the repo. Recall is
|
|
269
|
+
the only error that no reader downstream can see, so recall is the one that
|
|
270
|
+
gets a scheduled measurement.
|
|
271
|
+
- **Revisit if:** precision measured at intake (M3.2) stays below about 50% for
|
|
272
|
+
two weeks. The owner's triage time is then the cost to cut, so tighten the
|
|
273
|
+
prompt or add counter-examples before anything else.
|
|
274
|
+
- **Used by:** conversation-review-daily-judge,
|
|
275
|
+
conversation-review-report-and-digest, review-intake-to-repo,
|
|
276
|
+
conversation-review-weekly-synthesis, review-verdict-feedback.
|
|
277
|
+
|
|
278
|
+
## D-011: Intake relates every finding to existing work before deciding what to do with it
|
|
279
|
+
- **Category:** architecture
|
|
280
|
+
- **Decided:** (owner, 2026-09-28)
|
|
281
|
+
- `/f-review-intake` never files a finding as new work straight from the
|
|
282
|
+
report. Each finding goes through three steps, in this order:
|
|
283
|
+
1. **Verify.** Re-read the redacted excerpt and decide whether the answer
|
|
284
|
+
really was poor. If it wasn't, the action is `reject` and steps 2 and 3
|
|
285
|
+
are skipped.
|
|
286
|
+
2. **Relate.** Search everything already documented or landed, and cite each
|
|
287
|
+
related item by path:
|
|
288
|
+
- open work: `docs/ideas/`, `docs/backlog/`, `docs/in-progress/`,
|
|
289
|
+
`docs/bugs/`, goal roadmaps' stories, `docs/refactor/`;
|
|
290
|
+
- landed work: `docs/archive/` and `docs/debug/**` post-mortems, including
|
|
291
|
+
their Residuals sections, plus `git log` for the area;
|
|
292
|
+
- decisions: goal `decisions.md` files, owner decisions in `CLAUDE.md` and
|
|
293
|
+
`CLAUDE.local.md`, and documented out-of-scope or residual lines;
|
|
294
|
+
- earlier reviews: the ledger `docs/reviews/ledger.json`;
|
|
295
|
+
- pending checks: `docs/pending-verification.md`.
|
|
296
|
+
3. **Decide.** Assess whether the finding is worth working on, then pick
|
|
297
|
+
exactly one action from a closed set:
|
|
298
|
+
|
|
299
|
+
| Action | When | What intake does after the owner approves |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `reject` | The answer was fine (a judge error), or the problem is real but not worth the cost | Ledger entry with the reason. The fingerprint is suppressed |
|
|
302
|
+
| `known-limitation` | It matches a documented decision, out-of-scope line or accepted residual | Ledger entry linking that line. If the new evidence changes that decision's cost, the action becomes `needs-owner` instead |
|
|
303
|
+
| `duplicate` | The ledger already holds the same finding | Increment the seen count, then follow the earlier decision. A finding rejected earlier stays suppressed unless the evidence is new |
|
|
304
|
+
| `attach` | An open item already covers it: an idea, backlog or in-progress item, a bug, or a goal story | Append an "Evidence from conversation review" entry to that doc. The recurrence is priority signal |
|
|
305
|
+
| `fixed-not-deployed` | A landed change covers it, but the bot that produced the report runs a version that doesn't contain it | Ledger entry. Add or confirm a MANUAL-VERIFY line to re-check after the next deploy of that bot |
|
|
306
|
+
| `revisit` | A landed change covers it, **and** the bot's running version contains it | A new bug that links the closed item: "the fix for X does not cover this case", or a regression. If it's unclear whether the fix is even active, add a MANUAL-VERIFY re-check instead |
|
|
307
|
+
| `new` | Nothing related exists | Prefill `/f-bug-report` or `/f-feature-idea`, or propose a profile or config edit plus a MANUAL-VERIFY line |
|
|
308
|
+
| `needs-owner` | It conflicts with an owner decision, touches consent or scope, or the value call is close | Nothing. It is listed for the owner |
|
|
309
|
+
|
|
310
|
+
- **Worth working on** is judged on these points, and each one appears in the
|
|
311
|
+
triage row:
|
|
312
|
+
- severity;
|
|
313
|
+
- recurrence: days, spaces, and both bots;
|
|
314
|
+
- who was affected (a paying client over an owner test space);
|
|
315
|
+
- breadth of impact on Mercury over a single personal space (owner priority
|
|
316
|
+
of 2026-09-22);
|
|
317
|
+
- the cost of the target layer (an `AGENTS.md` line versus a code change);
|
|
318
|
+
- fit with `docs/ROADMAP.md`.
|
|
319
|
+
- The skill proposes a triage table with one row per finding: verdict,
|
|
320
|
+
related items with paths, action, value note. The owner approves, edits or
|
|
321
|
+
overrides each row, and only then does the skill apply the approved actions.
|
|
322
|
+
Every decision, approved or overridden, lands in the ledger.
|
|
323
|
+
- The split of work:
|
|
324
|
+
- `scripts/review-intake.ts` is deterministic. It pulls, re-gates, writes
|
|
325
|
+
`docs/reviews/`, and emits an index of candidate documents (path, title,
|
|
326
|
+
stage, status) plus the ledger.
|
|
327
|
+
- Matching and judgement are done by the skill, because similarity by
|
|
328
|
+
meaning is not a grep.
|
|
329
|
+
- The ledger also records the aliases of each canonical fingerprint, because
|
|
330
|
+
the cheap judge will slug the same problem differently on different days.
|
|
331
|
+
- `revisit` needs the bot's running version: every report records it (M1.4).
|
|
332
|
+
- **Alternatives considered:**
|
|
333
|
+
- **Exact-fingerprint dedupe only** (the original D-007 step 5). It misses the
|
|
334
|
+
same problem under a different slug, and it cannot tell that a finding is
|
|
335
|
+
evidence for an open idea, a known limitation, or a regression of a fix.
|
|
336
|
+
- **Letting the script auto-file `new` findings.** That breaks the
|
|
337
|
+
human-in-the-loop scope, and it is exactly where a judge's false positive
|
|
338
|
+
would turn into backlog noise.
|
|
339
|
+
- **Reasoning:** The value of a finding depends on what already exists. The same
|
|
340
|
+
excerpt can be:
|
|
341
|
+
- noise, when it restates an accepted limitation;
|
|
342
|
+
- priority signal, as the third occurrence of an open idea;
|
|
343
|
+
- the most valuable kind of finding, when it shows that a landed fix doesn't
|
|
344
|
+
work.
|
|
345
|
+
|
|
346
|
+
Only a pass that has read the corpus can tell these apart, and the planning
|
|
347
|
+
docs are the corpus.
|
|
348
|
+
- **Revisit if:** the ledger shows the owner accepting the proposed action over
|
|
349
|
+
about 90% of the time for a month. Automating `duplicate` and
|
|
350
|
+
`known-limitation` is then the next step, in a later goal.
|
|
351
|
+
- **Used by:** review-intake-to-repo, review-verdict-feedback,
|
|
352
|
+
conversation-review-report-and-digest.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Conversation Review
|
|
2
|
+
|
|
3
|
+
**Status**: Active
|
|
4
|
+
**Slug**: conversation-review
|
|
5
|
+
**Created**: 2026-09-28
|
|
6
|
+
**Last updated**: 2026-09-28
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Goal Statement
|
|
11
|
+
|
|
12
|
+
Scan the conversation logs of every space on both of the owner's bots on a
|
|
13
|
+
schedule. The two bots are the local live bot (`~/whatsapp-bot`, the owner's
|
|
14
|
+
personal bot) and the Tagula bot on the Hetzner server (client spaces; see the
|
|
15
|
+
`tagula-agents` repo). Each scan produces a report of **poor bot answers that
|
|
16
|
+
could be significantly improved**, with a proposed kind of fix for each one:
|
|
17
|
+
|
|
18
|
+
- a **bug** in Mercury code
|
|
19
|
+
- a **feature** Mercury does not have yet
|
|
20
|
+
- a **profile/character change**, such as an `AGENTS.md` line, a preference, a
|
|
21
|
+
task prompt or a skill
|
|
22
|
+
|
|
23
|
+
Each finding also says whether the change is **global** or **specific to one
|
|
24
|
+
space**.
|
|
25
|
+
|
|
26
|
+
The end state is a system that keeps improving Mercury on its own. This goal
|
|
27
|
+
automates only the **detection** step. A human still reviews every finding and
|
|
28
|
+
still does every implementation. Later milestones add more sophisticated scan
|
|
29
|
+
features.
|
|
30
|
+
|
|
31
|
+
Owner's words (2026-09-28): *"the scan should detect poor answers by the bot
|
|
32
|
+
that can be significantly improved either by some fix or by a new feature for
|
|
33
|
+
mercury. can be either code modifications or profile/character modifications,
|
|
34
|
+
global or space specific. the end goal is to have an automated system that keeps
|
|
35
|
+
improving mercury. as mentioned, at this point in time I first want to automate
|
|
36
|
+
the detection of such improvements - review and implementation will still
|
|
37
|
+
require a human in the loop."*
|
|
38
|
+
|
|
39
|
+
This automates the manual reviews already done by hand:
|
|
40
|
+
|
|
41
|
+
- `docs/notes/football-group-48h-audit.md`: 128 rows read one by one, producing
|
|
42
|
+
findings F1–F8.
|
|
43
|
+
- The 72 h log review of 2026-09-16. It left about ten `docs/ideas/*` files,
|
|
44
|
+
four `AGENTS.md` sentences applied live, and MANUAL-VERIFY lines, but no
|
|
45
|
+
single note of its own.
|
|
46
|
+
|
|
47
|
+
The triage those reviews used becomes the report's schema:
|
|
48
|
+
|
|
49
|
+
| Kind of finding | Where it goes |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Bug | `docs/bugs/` |
|
|
52
|
+
| Feature | `docs/ideas/` |
|
|
53
|
+
| Profile change | a layer named from `docs/behavior-layers.md` §3 |
|
|
54
|
+
|
|
55
|
+
## Constraints
|
|
56
|
+
|
|
57
|
+
- **Detection only.** The scan never changes a profile or a config, never files
|
|
58
|
+
a doc and never opens a PR. It writes a report and sends a DM, nothing else.
|
|
59
|
+
- **Raw conversations never leave their box.** The scan runs on each bot, next
|
|
60
|
+
to that bot's `state.db`. Only the redacted report crosses to this machine.
|
|
61
|
+
- **Redaction is enforced, not just intended.** Before a report file is written:
|
|
62
|
+
- phone numbers, JIDs, LIDs, e-mail addresses and member names are replaced;
|
|
63
|
+
- authors appear only as `owner` / `admin` / `member` / `bot`;
|
|
64
|
+
- excerpts are capped in length.
|
|
65
|
+
|
|
66
|
+
A gate aborts the write if any identifier survives. This follows the tagula-agents
|
|
67
|
+
`scripts/export-space-messages.ts` precedent.
|
|
68
|
+
- **Client spaces:** a report may quote short excerpts with identifiers redacted
|
|
69
|
+
(owner, 2026-09-28). The client-consent question is open, see Open Questions
|
|
70
|
+
in [decisions.md](decisions.md) D-009.
|
|
71
|
+
- **Subscription OAuth only.** No Anthropic API key is used
|
|
72
|
+
(`no-api-key-model-billing`).
|
|
73
|
+
- **Every scan is bounded, and the bound can be read.** There is a cap on the
|
|
74
|
+
exchanges judged and on the wall clock per scan and per spawn. Anything skipped
|
|
75
|
+
is stated in the report, never dropped silently (CLAUDE.md: "a limit the actor
|
|
76
|
+
cannot read is a trap").
|
|
77
|
+
- **Every pi spawn follows the house rule.** It uses `--system-prompt`, runs with
|
|
78
|
+
no tools, and has a row in `tests/inventories/pi-spawns.ts`.
|
|
79
|
+
- **The bot is never disturbed.** The scan reads `state.db` read-only and never
|
|
80
|
+
through `Db`, because `Db` runs migrations. It never blocks the host thread.
|
|
81
|
+
- **Tagula runs released Mercury.** The Hetzner box installs the `mercury-agent`
|
|
82
|
+
npm package and image and updates nightly to `:latest`. Code reaches it only
|
|
83
|
+
through a release, never through `~/sync-mercury.sh`.
|
|
84
|
+
|
|
85
|
+
## Preferences
|
|
86
|
+
|
|
87
|
+
- Findings land in two places (owner, 2026-09-28):
|
|
88
|
+
- a report file in the mercury repo, under `docs/reviews/`, shaped so that
|
|
89
|
+
`/f-bug-report`, `/f-feature-idea` or a profile edit can promote any finding
|
|
90
|
+
in one step (required);
|
|
91
|
+
- a WhatsApp DM digest to the owner, a short summary pointing at the report
|
|
92
|
+
(required).
|
|
93
|
+
- An LLM judge reads **every** exchange. Heuristics are not used as a pre-filter
|
|
94
|
+
(owner, 2026-09-28) (required).
|
|
95
|
+
- There are two scans (owner, 2026-09-28) (required):
|
|
96
|
+
- a **light daily** pass: a cheap-tier judge over the last 24 h;
|
|
97
|
+
- a **deep weekly** pass: strong-tier synthesis over the week's findings, plus
|
|
98
|
+
a spot check of exchanges the daily judge did not flag.
|
|
99
|
+
- Intake checks each finding against what is already documented or landed, and
|
|
100
|
+
decides from that and from whether the finding is worth the work. The options
|
|
101
|
+
are:
|
|
102
|
+
- merge it into an open item;
|
|
103
|
+
- file it as new;
|
|
104
|
+
- revisit a closed item to check that it works;
|
|
105
|
+
- drop it.
|
|
106
|
+
|
|
107
|
+
(owner, 2026-09-28) (required → [D-011](decisions.md))
|
|
108
|
+
- It is built as a Mercury extension, in the style of napkin and longview, so both
|
|
109
|
+
bots run the same code (you decide → [D-001](decisions.md)).
|
|
110
|
+
|
|
111
|
+
## Target Users
|
|
112
|
+
|
|
113
|
+
- **The owner, as reviewer.** They get a DM each day and read a report that lists
|
|
114
|
+
the improvements worth making, each with evidence and a suggested target layer.
|
|
115
|
+
They then promote, reject or ignore each finding. (inferred)
|
|
116
|
+
- **Future Claude sessions, as implementers.** They read a promoted finding the
|
|
117
|
+
same way they read `/f-bug-report` or `/f-feature-idea` output. (inferred)
|
|
118
|
+
|
|
119
|
+
## Success Criteria
|
|
120
|
+
|
|
121
|
+
1. On each bot, a daily scan runs unattended. It writes a report covering every
|
|
122
|
+
space with user exchanges in the window, and sends the owner a DM digest.
|
|
123
|
+
2. Every finding carries the following, and a report missing any field fails
|
|
124
|
+
validation:
|
|
125
|
+
- kind (bug / feature / profile / config)
|
|
126
|
+
- scope (global / space)
|
|
127
|
+
- the target layer from `behavior-layers.md` §3
|
|
128
|
+
- severity and confidence
|
|
129
|
+
- message ids
|
|
130
|
+
- a redacted excerpt
|
|
131
|
+
- what went wrong and the proposed improvement
|
|
132
|
+
3. A committed report contains no phone number, JID, LID, e-mail address or member
|
|
133
|
+
name. This is proven by the redaction gate and by a test that plants each form.
|
|
134
|
+
4. A seeded bad answer is flagged by the next daily scan in a test space on the
|
|
135
|
+
local bot. The weekly report lists recurring issues across spaces with their
|
|
136
|
+
frequency.
|
|
137
|
+
5. One command (`/f-review-intake`) brings both bots' reports into
|
|
138
|
+
`docs/reviews/` and triages every finding before any of it becomes work
|
|
139
|
+
([D-011](decisions.md)):
|
|
140
|
+
- it verifies the finding;
|
|
141
|
+
- it relates it to documented and landed items, citing paths;
|
|
142
|
+
- it proposes an action: reject, known limitation, duplicate, attach to an
|
|
143
|
+
open item, fixed but not deployed, revisit a landed item, new, or needs
|
|
144
|
+
owner;
|
|
145
|
+
- it applies only the actions the owner approves.
|
|
146
|
+
6. A judge mistake never becomes work unreviewed ([D-010](decisions.md)):
|
|
147
|
+
- evidence is read from the DB, never from the judge's text;
|
|
148
|
+
- classification is re-derived at intake;
|
|
149
|
+
- rejected false positives are not re-raised;
|
|
150
|
+
- recall is measured weekly.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Roadmap: Conversation Review
|
|
2
|
+
|
|
3
|
+
**Goal**: [conversation-review](goal.md)
|
|
4
|
+
**Last updated**: 2026-09-28
|
|
5
|
+
|
|
6
|
+
## Milestones
|
|
7
|
+
|
|
8
|
+
### Milestone 1: Daily judge on the local bot
|
|
9
|
+
> On the local bot, a daily scan judges every exchange of the last 24 h and
|
|
10
|
+
> writes a redacted report on the box. The owner gets a DM digest.
|
|
11
|
+
|
|
12
|
+
| ID | Story | Slug | Depends on | Status |
|
|
13
|
+
|----|-------|------|------------|--------|
|
|
14
|
+
| M1.1 | Read a window of exchanges per space from `state.db`, read-only | review-exchange-reader | — | backlog |
|
|
15
|
+
| M1.2 | `redactIdentifiers` + fail-closed `assertNoIdentifiers` in core | redact-identifiers-with-gate | — | backlog |
|
|
16
|
+
| M1.3 | `conversation-review` extension: daily job, batched no-tools judge, validated findings | conversation-review-daily-judge | M1.1 | blocked |
|
|
17
|
+
| M1.4 | Report (`report.md` + `report.json`) on the box and the owner DM digest | conversation-review-report-and-digest | M1.2, M1.3 | blocked |
|
|
18
|
+
|
|
19
|
+
**Checkpoint:** on the local bot, after deploying and enabling:
|
|
20
|
+
|
|
21
|
+
1. In a test space, the owner provokes one bad answer: a question the bot answers
|
|
22
|
+
wrongly or refuses without reason. They trigger a scan with the extension's
|
|
23
|
+
run-now hook (or wait for 04:00).
|
|
24
|
+
2. `~/whatsapp-bot/.../reviews/<date>-daily/report.md` exists and flags that
|
|
25
|
+
exchange with a kind, a scope, a target layer and the right message id.
|
|
26
|
+
3. `grep -E` for a phone number, `@s.whatsapp.net`, `@lid` and the owner's name
|
|
27
|
+
in the report returns nothing.
|
|
28
|
+
4. A DM arrives with the finding count, the top three findings and the report
|
|
29
|
+
path.
|
|
30
|
+
5. The report's footer lists spaces and exchanges judged, anything skipped with
|
|
31
|
+
the reason, spawns, wall clock and cost. The journal shows the scan never
|
|
32
|
+
stalled a live reply.
|
|
33
|
+
|
|
34
|
+
### Milestone 2: Both bots, into the repo
|
|
35
|
+
> The Tagula bot runs the same scan on its release. `/f-review-intake` brings both
|
|
36
|
+
> bots' reports into `docs/reviews/` and triages each finding against what is
|
|
37
|
+
> already documented or landed before any of it becomes work (D-011).
|
|
38
|
+
|
|
39
|
+
| ID | Story | Slug | Depends on | Status |
|
|
40
|
+
|----|-------|------|------------|--------|
|
|
41
|
+
| M2.1 | `scripts/review-intake.ts` + `/f-review-intake`: pull, re-gate, then verify → relate → decide per finding, owner approves, ledger | review-intake-to-repo | M1.4 | blocked |
|
|
42
|
+
| M2.2 | Enable on Tagula: release, config, `include_clients` gate, tagula-agents runbook | conversation-review-on-tagula | M1.4 | blocked |
|
|
43
|
+
|
|
44
|
+
**Checkpoint:** a daily report from each bot lands in `docs/reviews/`. The
|
|
45
|
+
intake triage table then shows, each row citing the paths it matched:
|
|
46
|
+
- at least one finding re-judged as fine and proposed as `reject`;
|
|
47
|
+
- one proposed as `attach` to an existing open item;
|
|
48
|
+
- one checked against a landed item, as `fixed-not-deployed` or `revisit`;
|
|
49
|
+
- a finding raised again on a later day, under a different slug, reads
|
|
50
|
+
`duplicate` and follows the earlier decision.
|
|
51
|
+
|
|
52
|
+
Only the rows the owner approved were applied, and every row is in
|
|
53
|
+
`docs/reviews/ledger.json`.
|
|
54
|
+
|
|
55
|
+
### Milestone 3: Weekly deep pass and a feedback loop
|
|
56
|
+
> A weekly strong-model synthesis clusters recurring issues across spaces and
|
|
57
|
+
> measures the daily judge's recall. The owner's verdicts on findings sharpen the
|
|
58
|
+
> judge.
|
|
59
|
+
|
|
60
|
+
| ID | Story | Slug | Depends on | Status |
|
|
61
|
+
|----|-------|------|------------|--------|
|
|
62
|
+
| M3.1 | Weekly job: cluster 7 days of findings, space → global promotion, recall spot check | conversation-review-weekly-synthesis | M1.4 | blocked |
|
|
63
|
+
| M3.2 | The M2.1 ledger feeds the judge: suppressed fingerprints and counter-examples from rejected findings go back to each bot; precision appears in the weekly report | review-verdict-feedback | M2.1, M3.1 | blocked |
|
|
64
|
+
|
|
65
|
+
**Checkpoint:** the first weekly report lists at least one cluster that spans two
|
|
66
|
+
or more spaces, and states the measured recall of the daily judge. After a week
|
|
67
|
+
of intake verdicts, it also states precision, and a rejected false positive is
|
|
68
|
+
not re-raised.
|
|
69
|
+
|
|
70
|
+
### Future milestones (to be decomposed when M3 completes)
|
|
71
|
+
- **Trace-aware analysis:** link a reply to its run trace (message id in the
|
|
72
|
+
trace name, the system prompt recorded) so a finding can cite the tool call
|
|
73
|
+
that failed. This builds on `trace-records-system-prompt`.
|
|
74
|
+
- **Friction signals as extra evidence:** corrections, rephrasing, "you didn't
|
|
75
|
+
answer". Relates to `repeated-style-corrections-become-preferences`.
|
|
76
|
+
- **Cross-bot clustering** at intake, and a view of trends over time.
|
|
77
|
+
- **Proposal → rehearsal:** replay the flagged exchange against a proposed
|
|
78
|
+
profile change on the shadow bench (`rehearsal-bench`, `mercury smoke`) and
|
|
79
|
+
attach the before/after answer to the finding.
|
|
80
|
+
- **Drafted fixes:** a finding becomes a pre-filled bug or idea doc, or a draft
|
|
81
|
+
`AGENTS.md` diff, still approved by a human.
|
|
82
|
+
|
|
83
|
+
## Dependency Graph
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
M1.1 ─┐
|
|
87
|
+
├→ M1.3 ─┐
|
|
88
|
+
M1.2 ─┼────────┴→ M1.4 ─┬→ M2.1 ─┐
|
|
89
|
+
│ ├→ M2.2 ├→ M3.2
|
|
90
|
+
│ └→ M3.1 ─┘
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
M1.1 and M1.2 can run in parallel. M2.1, M2.2 and M3.1 can run in parallel once
|
|
94
|
+
M1.4 lands.
|