mercury-agent 0.24.1 → 0.24.2-beta.2
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 +1 -1
- package/docs/auth/whatsapp.md +6 -4
- package/docs/configuration.md +5 -0
- package/docs/container-lifecycle.md +2 -2
- 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/rehearsal-bench/roadmap.md +1 -1
- 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/live-testing.md +34 -3
- package/docs/pending-verification.md +176 -1
- package/docs/plans/{flat-0916 → archive/2026-09-28-flat-0916}/plan.md +4 -4
- package/docs/plans/{p0-plan-0922 → archive/2026-09-28-p0-plan-0922}/plan.md +2 -2
- package/docs/plans/archive/2026-10-02-media-bugs-0928/briefs/S1.md +59 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/briefs/S2.md +64 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/briefs/S3.md +61 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/decisions.md +6 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/plan.md +111 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/sessions/S1.md +34 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/sessions/S2.md +43 -0
- package/docs/plans/archive/2026-10-02-media-bugs-0928/sessions/S3.md +35 -0
- package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/plan.md +2 -2
- package/docs/plans/archive/2026-10-02-sensitive-bugs-0922/sessions/S1.md +35 -0
- package/docs/plans/archive/2026-10-02-sensitive-bugs-0922/sessions/S2.md +30 -0
- package/docs/plans/archive/2026-10-02-sensitive-bugs-0922/sessions/S3.md +39 -0
- package/docs/plans/archive/2026-10-02-sensitive-bugs-0922/sessions/S4.md +38 -0
- package/docs/plans/archive/2026-10-02-sensitive-bugs-0922/sessions/S5.md +37 -0
- package/docs/plans/general-0928/briefs/S1.md +66 -0
- package/docs/plans/general-0928/briefs/S2.md +64 -0
- package/docs/plans/general-0928/briefs/S3.md +58 -0
- package/docs/plans/general-0928/briefs/S4.md +58 -0
- package/docs/plans/general-0928/briefs/S5.md +58 -0
- package/docs/plans/general-0928/decisions.md +8 -0
- package/docs/plans/general-0928/plan.md +107 -0
- package/docs/plans/search-bugs-0930/briefs/S1.md +73 -0
- package/docs/plans/search-bugs-0930/briefs/S2.md +75 -0
- package/docs/plans/search-bugs-0930/briefs/S3.md +77 -0
- package/docs/plans/search-bugs-0930/briefs/S4.md +75 -0
- package/docs/plans/search-bugs-0930/briefs/S5.md +74 -0
- package/docs/plans/search-bugs-0930/briefs/S6.md +77 -0
- package/docs/plans/search-bugs-0930/decisions.md +8 -0
- package/docs/plans/search-bugs-0930/plan.md +165 -0
- package/docs/plans/search-bugs-0930/sessions/S1.md +28 -0
- package/docs/plans/search-bugs-0930/sessions/S2.md +27 -0
- package/docs/plans/search-bugs-0930/sessions/S3.md +40 -0
- package/docs/plans/search-bugs-0930/sessions/S5.md +39 -0
- package/docs/plans/search-bugs-0930/sessions/S6.md +37 -0
- package/docs/plans/search-quality-0930/briefs/S12.md +69 -0
- package/docs/plans/search-quality-0930/briefs/S7.md +76 -0
- package/docs/plans/search-quality-0930/briefs/S9.md +68 -0
- package/docs/plans/search-quality-0930/decisions.md +11 -0
- package/docs/plans/search-quality-0930/plan.md +107 -0
- package/docs/plans/unattended-1003/briefs/S1.md +92 -0
- package/docs/plans/unattended-1003/briefs/S2.md +92 -0
- package/docs/plans/unattended-1003/briefs/S3.md +90 -0
- package/docs/plans/unattended-1003/briefs/S4.md +92 -0
- package/docs/plans/unattended-1003/briefs/S5.md +92 -0
- package/docs/plans/unattended-1003/briefs/S6.md +94 -0
- package/docs/plans/unattended-1003/briefs/S7.md +92 -0
- package/docs/plans/unattended-1003/briefs/S8.md +90 -0
- package/docs/plans/unattended-1003/briefs/S9.md +92 -0
- package/docs/plans/unattended-1003/decisions.md +7 -0
- package/docs/plans/unattended-1003/plan.md +153 -0
- package/docs/scheduler.md +5 -1
- package/docs/web-search.md +13 -7
- package/examples/extensions/napkin/distill-input.ts +27 -6
- package/examples/extensions/napkin/prompts/kb-distillation.md +3 -1
- 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/pinchtab/bin/pinchtab_ensure +129 -0
- package/examples/extensions/pinchtab/bin/pinchtab_nav +50 -0
- package/examples/extensions/pinchtab/bin/pinchtab_session_inject.cjs +184 -0
- package/examples/extensions/pinchtab/index.ts +41 -237
- package/examples/extensions/pinchtab/skill/SKILL.md +7 -110
- package/examples/extensions/voice-transcribe/index.ts +192 -50
- package/examples/extensions/voice-transcribe/skill/SKILL.md +2 -0
- package/examples/extensions/web-search/brave.ts +396 -18
- package/examples/extensions/web-search/index.ts +122 -22
- package/examples/extensions/web-search/ledger.ts +156 -0
- package/examples/extensions/web-search/skill/SKILL.md +30 -6
- package/examples/extensions/web-search/skill/references/zap.md +148 -0
- package/examples/profiles/football-reporter/config.yaml +11 -2
- package/examples/profiles/tel-aviv-flat-search/config.yaml +3 -2
- package/package.json +4 -4
- package/src/adapters/whatsapp-media.ts +262 -40
- package/src/adapters/whatsapp.ts +1 -1
- package/src/agent/container-entry.ts +348 -36
- package/src/agent/container-error.ts +39 -2
- package/src/agent/container-runner.ts +162 -23
- package/src/agent/pi-failure-class.ts +36 -6
- package/src/agent/pi-jsonl-parser.ts +151 -33
- package/src/agent/provider-auth-rejection.ts +279 -0
- package/src/agent/user-error-messages.ts +26 -0
- package/src/bridges/discord.ts +7 -0
- package/src/bridges/slack.ts +13 -1
- package/src/bridges/teams.ts +29 -16
- package/src/bridges/telegram.ts +13 -0
- package/src/bridges/whatsapp.ts +6 -0
- package/src/cli/mercury.ts +85 -0
- package/src/cli/mrctl-http.ts +67 -0
- package/src/cli/mrctl.ts +14 -12
- package/src/config.ts +5 -3
- package/src/core/api-types.ts +5 -0
- package/src/core/api.ts +9 -1
- package/src/core/auth.ts +1 -1
- package/src/core/caller-token.ts +32 -1
- package/src/core/handler.ts +11 -1
- package/src/core/media.ts +216 -16
- package/src/core/operator-alerts.ts +204 -4
- package/src/core/outbox.ts +125 -12
- package/src/core/routes/capability.ts +2 -2
- package/src/core/routes/config-builtin.ts +27 -0
- package/src/core/routes/console.ts +7 -0
- package/src/core/routes/dashboard.ts +10 -0
- package/src/core/routes/tasks.ts +11 -0
- package/src/core/runtime.ts +705 -113
- package/src/core/space-links.ts +273 -0
- package/src/core/system-messages.ts +45 -2
- package/src/core/task-scheduler.ts +115 -5
- package/src/env-dump/build-deps.ts +2 -55
- package/src/extensions/catalog.ts +5 -3
- package/src/extensions/context.ts +4 -0
- package/src/extensions/image-builder.ts +265 -65
- package/src/extensions/reserved.ts +6 -0
- package/src/extensions/types.ts +30 -0
- package/src/ops/shadow-diff.ts +1027 -0
- package/src/preflight/binary-version.ts +103 -0
- package/src/preflight/build-deps.ts +2 -0
- package/src/preflight/checks/credential.ts +16 -9
- package/src/preflight/checks/host-deps.ts +107 -10
- package/src/preflight/deps.ts +5 -0
- package/src/profile/space-profile.ts +21 -0
- package/src/server.ts +56 -1
- package/src/storage/db.ts +603 -0
- package/src/storage/pi-auth.ts +92 -8
- package/src/text/reporter-lint.ts +77 -30
- package/docs/plans/flat-0916/briefs/S1.md +0 -55
- package/docs/plans/flat-0916/briefs/S2.md +0 -55
- /package/docs/plans/{flat-0916 → archive/2026-09-28-flat-0916}/decisions.md +0 -0
- /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/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/briefs/S1.md +0 -0
- /package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/briefs/S2.md +0 -0
- /package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/briefs/S3.md +0 -0
- /package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/briefs/S4.md +0 -0
- /package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/briefs/S5.md +0 -0
- /package/docs/plans/{sensitive-bugs-0922 → archive/2026-10-02-sensitive-bugs-0922}/decisions.md +0 -0
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ Install extensions from the dashboard (**Features** page), the CLI, or during se
|
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
168
|
# From the CLI
|
|
169
|
-
mercury add web-browser # Web browsing
|
|
169
|
+
mercury add web-browser # Web browsing: open pages, fill forms, log into sites (no API key needed)
|
|
170
170
|
mercury add web-search # Brave Search API + site search skill (key stays on the host)
|
|
171
171
|
mercury add napkin # Obsidian-style knowledge vault
|
|
172
172
|
|
package/docs/auth/whatsapp.md
CHANGED
|
@@ -105,14 +105,14 @@ The auth script writes status files for external monitoring (useful for headless
|
|
|
105
105
|
When Mercury is running, you can check auth status via the API:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
curl http://localhost:8787/auth/whatsapp
|
|
108
|
+
curl -H "Authorization: Bearer $MERCURY_API_SECRET" http://localhost:8787/auth/whatsapp
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
**Responses:**
|
|
112
112
|
|
|
113
113
|
```json
|
|
114
114
|
// Authenticated and connected
|
|
115
|
-
{ "status": "authenticated" }
|
|
115
|
+
{ "status": "authenticated", "phoneNumber": "15550001111" }
|
|
116
116
|
|
|
117
117
|
// Waiting for QR scan (includes raw QR data)
|
|
118
118
|
{ "status": "waiting", "qr": "2@AbCdEf123..." }
|
|
@@ -121,7 +121,9 @@ curl http://localhost:8787/auth/whatsapp
|
|
|
121
121
|
{ "status": "disconnected" }
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
This endpoint requires
|
|
124
|
+
This endpoint requires the `MERCURY_API_SECRET` Bearer token, like `/api/*`. Without it the
|
|
125
|
+
response is `401` (`503` when no secret is configured) and carries no field of the auth state:
|
|
126
|
+
the QR it serves while pairing links the bot to whoever scans it first.
|
|
125
127
|
|
|
126
128
|
## Troubleshooting
|
|
127
129
|
|
|
@@ -169,5 +171,5 @@ Mercury ignores messages that were sent before it connected to prevent processin
|
|
|
169
171
|
|
|
170
172
|
- Auth credentials in `.mercury/whatsapp-auth/` are sensitive — treat them like passwords
|
|
171
173
|
- Anyone with access to these files can impersonate your WhatsApp account
|
|
172
|
-
- The `/auth/whatsapp` endpoint
|
|
174
|
+
- The `/auth/whatsapp` endpoint serves the live pairing QR, so it requires `MERCURY_API_SECRET`; keep that secret out of anything a browser or a log can read
|
|
173
175
|
- Consider using a dedicated WhatsApp number for your bot
|
package/docs/configuration.md
CHANGED
|
@@ -284,6 +284,11 @@ Env equivalents: **`MERCURY_ALERTS_ENABLED`**, **`MERCURY_ALERTS_COOLDOWN_MS`**.
|
|
|
284
284
|
instead, and its space hears nothing, even when no operator is reachable
|
|
285
285
|
(then the only record is the ERROR log line). No cooldown: a final failure is
|
|
286
286
|
already once per due cycle.
|
|
287
|
+
- **Scheduled runs skipped by the sensitive-connection deny** (a group space
|
|
288
|
+
without `security.sensitive_connections_allowed: true`) report to the
|
|
289
|
+
operators above, one message per skip, never to the space. The same
|
|
290
|
+
`scheduling.notify_on_failure` switch governs it: with it off, nobody is
|
|
291
|
+
told and the host logs a warn line saying so.
|
|
287
292
|
- **Partial deliveries.** A scheduled post that reached some of a space's linked
|
|
288
293
|
conversations and not others is **not** retried — a retry would post a second
|
|
289
294
|
copy where the first landed — and the operators are told which conversations
|
|
@@ -257,7 +257,7 @@ while something is wrong; rolling an upgrade back over one would be wrong.
|
|
|
257
257
|
| `image.contract` | blocked | every `container/Dockerfile` `COPY` target is in the image, and the version the build stamped into `/app/image-manifest.json` equals the host's |
|
|
258
258
|
| `container.mrctl-roundtrip` | blocked | a container on the production launch shape runs `mrctl whoami` and comes back as the caller preflight asserted — by minted token where a caller-token key is configured, by identity header where none is (evidence `callerAuth`; see below). On runsc the host API socket is connect-tested first, so a stopped service blocks with the service remedy and no container is created |
|
|
259
259
|
| `credential.first-leg` | blocked | the chain's first leg (or `--space`'s pinned leg) resolves a credential the way a spawn resolves it |
|
|
260
|
-
| `host.pi` | warn | `pi` is on the host when napkin or longview is loaded |
|
|
260
|
+
| `host.pi` | warn | `pi` is on the host when napkin or longview is loaded, and its `--version` satisfies the pi range in `container/agent-package.json` |
|
|
261
261
|
| `host.timezone` | warn | no active cron task carries `timezone: null` — a zoneless cron is parsed as UTC on every host, so the host's own zone is evidence, not the condition |
|
|
262
262
|
| `host.swap` | warn | a host of ≤ 4 GiB has swap |
|
|
263
263
|
| `extensions.load-report` | warn | every extension on disk is loaded, or skipped **with a reason** |
|
|
@@ -476,7 +476,7 @@ RUN curl -fsSL https://bun.sh/install | bash
|
|
|
476
476
|
ENV PATH="/home/mercury/.bun/bin:$PATH"
|
|
477
477
|
|
|
478
478
|
# Install required CLIs
|
|
479
|
-
RUN bun add -g @earendil-works/pi-coding-agent
|
|
479
|
+
RUN bun add -g @earendil-works/pi-coding-agent@~0.87.1 # the range in container/agent-package.json
|
|
480
480
|
|
|
481
481
|
# Optional: install Playwright/Chromium if your extensions need browser automation
|
|
482
482
|
RUN bunx playwright install chromium
|
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.
|