mercury-agent 0.16.3 → 0.17.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/README.md +12 -0
- package/container/Dockerfile +1 -0
- package/container/Dockerfile.base +1 -0
- package/docs/autopilot/config.yaml +43 -0
- package/docs/behavior-layers.md +3 -0
- package/docs/container-lifecycle.md +1 -0
- package/docs/extensions.md +43 -0
- package/docs/goals/football-reporter-profile/decisions.md +37 -1
- package/docs/goals/football-reporter-profile/roadmap.md +63 -5
- package/docs/permissions.md +15 -0
- package/docs/profile-guide.md +304 -18
- package/docs/skills-guide.md +24 -0
- package/examples/extensions/feed-watch/config.ts +36 -0
- package/examples/extensions/feed-watch/digest.ts +230 -32
- package/examples/extensions/feed-watch/index.ts +8 -0
- package/examples/extensions/feed-watch/items.ts +48 -4
- package/examples/extensions/overview/actions.ts +264 -0
- package/examples/extensions/overview/gate.ts +82 -0
- package/examples/extensions/overview/index.ts +123 -0
- package/examples/extensions/overview/skill/SKILL.md +80 -0
- package/examples/profiles/_template/config.yaml +34 -0
- package/examples/profiles/football-reporter/AGENTS.md +24 -6
- package/examples/profiles/football-reporter/README.md +8 -0
- package/examples/profiles/football-reporter/config.yaml +21 -2
- package/examples/profiles/football-reporter/tasks/daily-article.md +6 -4
- package/package.json +7 -3
- package/resources/templates/mercury.example.yaml +5 -0
- package/src/adapters/whatsapp-media.ts +80 -3
- package/src/agent/container-entry.ts +100 -15
- package/src/agent/container-env.ts +11 -0
- package/src/agent/container-error.ts +12 -1
- package/src/agent/container-runner.ts +59 -7
- package/src/agent/pi-jsonl-parser.ts +166 -5
- package/src/bridges/discord.ts +4 -0
- package/src/bridges/slack.ts +4 -0
- package/src/bridges/teams.ts +4 -0
- package/src/bridges/telegram.ts +33 -1
- package/src/cli/mercury.ts +1 -0
- package/src/config-file.ts +6 -0
- package/src/config.ts +23 -0
- package/src/core/attachment-notes.ts +4 -0
- package/src/core/confirmation.ts +3 -1
- package/src/core/handler.ts +2 -1
- package/src/core/media.ts +104 -2
- package/src/core/outbox.ts +23 -3
- package/src/core/reply-context.ts +61 -0
- package/src/core/routes/chat.ts +70 -3
- package/src/core/routes/dashboard.ts +80 -5
- package/src/core/runtime.ts +139 -11
- package/src/core/task-output.ts +36 -18
- package/src/core/task-scheduler.ts +18 -1
- package/src/extensions/catalog.ts +1 -0
- package/src/extensions/hooks.ts +8 -0
- package/src/extensions/loader.ts +68 -7
- package/src/extensions/types.ts +35 -2
- package/src/main.ts +19 -0
- package/src/server.ts +15 -8
- package/src/storage/db.ts +231 -12
- package/src/text/sanitize-text.ts +118 -0
- package/src/types.ts +73 -1
package/README.md
CHANGED
|
@@ -417,10 +417,22 @@ Supported OAuth providers: Anthropic, GitHub Copilot, Google Gemini CLI, Antigra
|
|
|
417
417
|
| `MERCURY_CONTAINER_TIMEOUT_MS` | `300000` | Container timeout (5 min) |
|
|
418
418
|
| `MERCURY_CONTAINER_RUNTIME` | `runc` | `runc` (default) or `runsc` ([gVisor](https://gvisor.dev)) |
|
|
419
419
|
| `MERCURY_CONTAINER_BWRAP_DOCKER_COMPAT` | `false` | Set `true` on Linux Docker Engine (see note below) |
|
|
420
|
+
| `MERCURY_CONTAINER_ALLOW_UNCONFINED` | `false` | Set `true` to accept the Docker Desktop sandbox relaxation (see note below) |
|
|
420
421
|
| `MERCURY_AGENT_ID` | — | Unique ID for this assistant (required when running multiple assistants on the same Docker daemon) |
|
|
421
422
|
|
|
422
423
|
> **Multiple assistants on the same machine:** Mercury builds a derived Docker image (`mercury-agent-ext:<hash>`) from the base image plus your extensions. When a new image is built, older tags in the same repo are pruned. If two assistants share a Docker daemon without distinct `MERCURY_AGENT_ID` values, they share the same image repo and **one will silently prune the other's image**, causing container launch failures. Set `MERCURY_AGENT_ID` to a unique value per project (e.g. in `.env`). Cloud console deployments set this automatically.
|
|
423
424
|
|
|
425
|
+
> **Docker Desktop (macOS/Windows):** Mercury detects Docker Desktop and would
|
|
426
|
+
> otherwise relax the sandbox automatically (`--security-opt seccomp=unconfined
|
|
427
|
+
> --cap-add SYS_ADMIN`) so `bwrap` can nest. It now **refuses to do that
|
|
428
|
+
> silently**: containers fail to start with a message naming the remedy.
|
|
429
|
+
> Set `MERCURY_CONTAINER_ALLOW_UNCONFINED=true` (or `agent.allow_unconfined:
|
|
430
|
+
> true` in `mercury.yaml`) to accept the trade knowingly — it is then logged at
|
|
431
|
+
> `warn` on every spawn. **Upgrading an existing Docker Desktop install requires
|
|
432
|
+
> this**, or every message gets a generic container failure. Linux hosts are
|
|
433
|
+
> unaffected; `MERCURY_CONTAINER_RUNTIME=runsc` (gVisor) avoids the relaxation
|
|
434
|
+
> entirely, and the flag is ignored when `container_bwrap_docker_compat` is set.
|
|
435
|
+
|
|
424
436
|
> **Linux Docker Engine:** Mercury uses [bubblewrap](https://github.com/containers/bubblewrap) for in-container sandboxing. On Linux Docker Engine (not Docker Desktop), bwrap cannot mount `/proc` without extra privileges. Either set `container_bwrap_docker_compat: true` in `mercury.yaml` (adds `--privileged` to `docker run`), or install [gVisor](https://gvisor.dev/docs/user_guide/install/) and set `MERCURY_CONTAINER_RUNTIME=runsc` to skip bwrap entirely.
|
|
425
437
|
|
|
426
438
|
**KB Distillation:**
|
package/container/Dockerfile
CHANGED
|
@@ -111,6 +111,7 @@ COPY src/cli/mrctl.ts /app/src/cli/mrctl.ts
|
|
|
111
111
|
COPY src/cli/mrctl-http.ts /app/src/cli/mrctl-http.ts
|
|
112
112
|
COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
|
|
113
113
|
COPY src/extensions/permission-guard.ts /app/src/extensions/permission-guard.ts
|
|
114
|
+
COPY src/text/sanitize-text.ts /app/src/text/sanitize-text.ts
|
|
114
115
|
COPY src/types.ts /app/src/types.ts
|
|
115
116
|
COPY resources/ /app/resources/
|
|
116
117
|
COPY examples/extensions/ /tmp/examples-extensions/
|
|
@@ -80,6 +80,7 @@ COPY src/cli/mrctl.ts /app/src/cli/mrctl.ts
|
|
|
80
80
|
COPY src/cli/mrctl-http.ts /app/src/cli/mrctl-http.ts
|
|
81
81
|
COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
|
|
82
82
|
COPY src/extensions/permission-guard.ts /app/src/extensions/permission-guard.ts
|
|
83
|
+
COPY src/text/sanitize-text.ts /app/src/text/sanitize-text.ts
|
|
83
84
|
COPY src/types.ts /app/src/types.ts
|
|
84
85
|
COPY resources/ /app/resources/
|
|
85
86
|
COPY examples/extensions/ /tmp/examples-extensions/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Autopilot Configuration
|
|
2
|
+
# Read by the orchestrator each iteration. See docs/templates/TEMPLATE-AUTOPILOT-CONFIG.yaml.
|
|
3
|
+
|
|
4
|
+
gates:
|
|
5
|
+
max_iterations: 10
|
|
6
|
+
max_features_without_review: 3
|
|
7
|
+
max_retries_same_task: 3
|
|
8
|
+
iteration_timeout_minutes: 30
|
|
9
|
+
|
|
10
|
+
risk:
|
|
11
|
+
sensitive_paths: # changes to these = immediate stop for review
|
|
12
|
+
- "**/auth/**"
|
|
13
|
+
- "**/migration*"
|
|
14
|
+
- "**/.env*"
|
|
15
|
+
- "**/schema*"
|
|
16
|
+
# Container spawn shape: sandbox flags, mounts, network, runtime selection.
|
|
17
|
+
# A wrong change here passes `bun run check` and is only observable in the
|
|
18
|
+
# running container — the check gate cannot verify it. Human-only.
|
|
19
|
+
- "src/agent/container-runner.ts"
|
|
20
|
+
- "src/agent/container-entry.ts"
|
|
21
|
+
auto_stop_risk_threshold: 8
|
|
22
|
+
|
|
23
|
+
scope:
|
|
24
|
+
allowed_actions:
|
|
25
|
+
- implement_feature
|
|
26
|
+
- continue_feature
|
|
27
|
+
- fix_bug
|
|
28
|
+
- execute_refactor
|
|
29
|
+
- sync_docs
|
|
30
|
+
skip_slugs: # never touch autonomously
|
|
31
|
+
# Both are design decisions, not mechanical fixes, and neither is verifiable
|
|
32
|
+
# by the check suite. See docs/bugs/ for the filed reports.
|
|
33
|
+
- docker-desktop-sandbox-relaxation-silent # fix = a product call: warn, or refuse and break Docker Desktop users
|
|
34
|
+
- container-egress-unrestricted # architecture: needs a chosen proxy/allowlist shape
|
|
35
|
+
# attachment-ingest-type-gating — unblocked 2026-08-27 (warn-only decision
|
|
36
|
+
# recorded in the spec). Eligible once it is listed under ROADMAP Now/Next.
|
|
37
|
+
|
|
38
|
+
planning:
|
|
39
|
+
allow_autonomous_planning: false
|
|
40
|
+
|
|
41
|
+
notifications:
|
|
42
|
+
on_stop: console
|
|
43
|
+
decision_log: docs/autopilot/decision-log.md
|
package/docs/behavior-layers.md
CHANGED
|
@@ -29,6 +29,9 @@ the repo, after a pi upgrade.
|
|
|
29
29
|
## Run budget (tools-capable models only)
|
|
30
30
|
## Destructive Operations — Confirmation Required
|
|
31
31
|
## Presenting tool results ("simple lists", "never show JSON/commands")
|
|
32
|
+
## Delivery ("[reply] … [/reply] — only the span inside is sent";
|
|
33
|
+
pi-jsonl-parser.ts extractReplyEnvelope honours it,
|
|
34
|
+
a reply with no markers is sent whole)
|
|
32
35
|
## Character (mrctl character flow + "standing instruction → set a preference"
|
|
33
36
|
— only for a caller holding prefs.set)
|
|
34
37
|
reply-anchor sentence (swipe-replies only)
|
|
@@ -192,6 +192,7 @@ The pi agent runs inside `bwrap`, which creates a minimal mount namespace with o
|
|
|
192
192
|
| Env Var | Purpose |
|
|
193
193
|
|---------|---------|
|
|
194
194
|
| `MERCURY_CONTAINER_BWRAP_DOCKER_COMPAT=1` | **Host only.** Adds `docker run --security-opt seccomp=unconfined --cap-add SYS_ADMIN` so `bwrap` can nest inside the agent container (e.g. Docker Desktop). Keeps bubblewrap on. |
|
|
195
|
+
| `MERCURY_CONTAINER_ALLOW_UNCONFINED=true` | **Host only.** Accepts the *auto-detected* Docker Desktop relaxation (same flags as above). Defaults to `false`, so on Docker Desktop containers refuse to start until it is set — the relaxation is never applied without the operator seeing it. Logged at `warn` on every spawn once enabled. Ignored under `runsc` and when `BWRAP_DOCKER_COMPAT` is set. |
|
|
195
196
|
| `MERCURY_DISABLE_BUBBLEWRAP=1` | Disable bubblewrap; run pi directly (last resort / debugging) |
|
|
196
197
|
|
|
197
198
|
If you see `bwrap: Creating new namespace failed: Operation not permitted`, try **`MERCURY_CONTAINER_BWRAP_DOCKER_COMPAT=1`** first so you keep defense-in-depth. Only use `MERCURY_DISABLE_BUBBLEWRAP=1` if compat mode is not enough.
|
package/docs/extensions.md
CHANGED
|
@@ -397,6 +397,49 @@ mercury extensions list # or: mercury ext list
|
|
|
397
397
|
|
|
398
398
|
Shows all installed extensions (user + built-in) with features and descriptions.
|
|
399
399
|
|
|
400
|
+
## Cross-Space Overview (`overview`)
|
|
401
|
+
|
|
402
|
+
`examples/extensions/overview/` gives **one** designated space a read-only
|
|
403
|
+
window onto the others — the only cross-space read an agent has. It is not a
|
|
404
|
+
builtin: install it deliberately.
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
mercury add ./examples/extensions/overview
|
|
408
|
+
MERCURY_MANAGEMENT_SPACE_ID=<space id> # host-only; unset = every read denied
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The agent calls it with `mrctl capability overview <action> '<json>'`:
|
|
412
|
+
|
|
413
|
+
| Action | Body | Returns |
|
|
414
|
+
|--------|------|---------|
|
|
415
|
+
| `list` | — | every space with `messageCount`, `conversationCount`, `platforms`, `lastActivity` |
|
|
416
|
+
| `recent` | `{spaceId, limit?}` | that space's latest messages, newest first |
|
|
417
|
+
| `search` | `{q, spaceId?, limit?}` | substring matches; every space when `spaceId` is omitted |
|
|
418
|
+
| `tasks` | `{spaceId?, limit?}` | scheduled tasks, one space or all |
|
|
419
|
+
| `roles` | `{spaceId}` | role assignments |
|
|
420
|
+
|
|
421
|
+
`limit` defaults to 20 and is capped at 100; message bodies — and a task's
|
|
422
|
+
`prompt` and `lastError` — are truncated at 500 characters with
|
|
423
|
+
`truncated: true`. Every action is a plain `SELECT` — there is no
|
|
424
|
+
cross-space write, and `listMutes` is deliberately not exposed because it deletes
|
|
425
|
+
expired rows.
|
|
426
|
+
|
|
427
|
+
Three gates run before any action, in the handler, and they are the whole
|
|
428
|
+
boundary:
|
|
429
|
+
|
|
430
|
+
1. `MERCURY_MANAGEMENT_SPACE_ID` is set — **absence denies**.
|
|
431
|
+
2. The caller's token-bound space id equals it, by exact string equality.
|
|
432
|
+
3. The caller is a global admin (`config.admins` / `dmAutoSpaceAdminIds`).
|
|
433
|
+
|
|
434
|
+
The `overview` permission checked by the capability route is **not** a scope:
|
|
435
|
+
`admin` is granted every registered permission in its own space, so every space
|
|
436
|
+
admin in the deployment passes it. Only the three gates above stop them.
|
|
437
|
+
|
|
438
|
+
The skill is installed into the management space's own workspace on
|
|
439
|
+
`workspace_init`, not via `mercury.skill()` — that copies into the global skills
|
|
440
|
+
dir mounted into *every* container, which would advertise the capability to
|
|
441
|
+
spaces that cannot use it.
|
|
442
|
+
|
|
400
443
|
## Examples
|
|
401
444
|
|
|
402
445
|
See [`examples/extensions/`](../examples/extensions/) for complete, working extensions ranging from minimal (charts — CLI + skill) to full-featured (napkin — hooks, jobs, config, widgets, KB distillation).
|
|
@@ -475,5 +475,41 @@
|
|
|
475
475
|
implementing: `## Current State (as of …)` is not matched by the injector, so
|
|
476
476
|
every note napkin had written for this space was reaching the model as a
|
|
477
477
|
summary line with no facts —
|
|
478
|
-
`docs/
|
|
478
|
+
`docs/debug/major/2026-08-31-episode-current-state-heading-suffix-drops-body.md`.
|
|
479
479
|
- **Used by:** reporter-notebook (M2.3, re-cut), football-reporter-fixture-harness (provenance source = `episodes/`), napkin (distill addendum)
|
|
480
|
+
|
|
481
|
+
## D-020: Live tuning is adopted into the repo, not reverted — and scans carry a fixed club emoji (decided 2026-08-31)
|
|
482
|
+
- **Category:** layer placement / process (refines [D-003](#d-003-layer-placement-for-football), [D-013](#d-013-tone-is-editorial-and-scan-length-is-per-item-decided-2026-08-20))
|
|
483
|
+
- **Decided:** (1) When the owner edits the live `AGENTS.md` on the box while
|
|
484
|
+
the bot is running, the repo **adopts** the live text (copy live → repo,
|
|
485
|
+
read the whole file for the contradiction the edit introduced, fix that in
|
|
486
|
+
the repo, `apply`) — it never reverts it with `apply`. The live file is the
|
|
487
|
+
owner's most recent editorial judgement and the repo's job is to record it;
|
|
488
|
+
`check` failing is the signal to reconcile, not to overwrite. (2) The
|
|
489
|
+
2026-08-27 edits are now the standard: every scan item opens with one fixed
|
|
490
|
+
club emoji (😈 United, 🔵 Barcelona, ⚪ Real, 🔴 Hapoel TA, 💛 Beitar,
|
|
491
|
+
🟢 Maccabi Haifa; none for an item outside those six, none invented), items
|
|
492
|
+
are grouped under one club heading when several concern one club, an item
|
|
493
|
+
is ≤ 200 characters including its `(source, dd/mm)`, items are separated by
|
|
494
|
+
a blank line, the displayed date is `dd/mm` while the year is still verified
|
|
495
|
+
in the source, and the article stays at zero emoji.
|
|
496
|
+
- **Alternatives considered:**
|
|
497
|
+
- *Revert the box to the repo with `apply`.* Rejected: the edits are the
|
|
498
|
+
owner's, dated, and were running for four days; the repo was the stale
|
|
499
|
+
copy.
|
|
500
|
+
- *Keep "0 emoji in scheduled output" as decided at the M1 checkpoint.*
|
|
501
|
+
Rejected by the owner's edit: a fixed per-club marker is a format element
|
|
502
|
+
(it tells a phone reader which club before the sentence), not decoration.
|
|
503
|
+
M3.1's emoji lint becomes an **allowlist** (exactly one of the six, at line
|
|
504
|
+
start, scan only) rather than a zero count.
|
|
505
|
+
- **Reasoning:** `check` on 2026-08-31 reported one difference — `AGENTS.md`
|
|
506
|
+
— whose diff was purely additive owner tuning from 2026-08-27 15:39. The
|
|
507
|
+
same file's `## סגנון` still said "scan and article: zero", so the adoption
|
|
508
|
+
also fixed the in-file contradiction the guide's §4.5 warns about. The
|
|
509
|
+
fourth bullet the owner added ("no English working sentence as a first
|
|
510
|
+
line") is a prose rule over a host defect and did not hold (four leaks after
|
|
511
|
+
it); it stays in the file as documentation of intent but the fix is
|
|
512
|
+
`docs/bugs/narration-in-same-text-block-leaks-into-reply.md`.
|
|
513
|
+
- **Revisit if:** live edits become frequent — then the right tool is a
|
|
514
|
+
profile PR flow from the box (edit → `dump` → commit), not a rule.
|
|
515
|
+
- **Used by:** football-reporter profile (`AGENTS.md`), football-reporter-fixture-harness (M3.1 lint spec)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Roadmap: Football Reporter Profile
|
|
2
2
|
|
|
3
3
|
**Goal**: [football-reporter-profile](goal.md)
|
|
4
|
-
**Last updated**: 2026-08-
|
|
4
|
+
**Last updated**: 2026-08-31 (M2.1/M2.3/M2.4 live; the M2 cost failure is diagnosed, fixed and verified on the live bot — verify-run prefix 118 K → 50.6 K, projection ≈ $5.4/day, ledger confirmation due 09-01 → 09-03; live `AGENTS.md` tuning adopted per D-020)
|
|
5
5
|
|
|
6
6
|
> Sequence chosen by the owner on 2026-08-20: (1) profile + one editorial
|
|
7
7
|
> layer → (2) deterministic feed → (3) proof of quality, with a parallel bug
|
|
@@ -55,11 +55,11 @@ the group's reaction to the first two articles is recorded in the goal's notes.
|
|
|
55
55
|
|
|
56
56
|
| ID | Story | Slug | Depends on | Status |
|
|
57
57
|
|----|-------|------|------------|--------|
|
|
58
|
-
| M2.1 | Feed Watch — host-side poller, digest into the article, verify one-shots (existing spec, absorbed) | feed-watch | M1.1 | done (merged `e07b239` 2026-08-21; **
|
|
58
|
+
| M2.1 | Feed Watch — host-side poller, digest into the article, verify one-shots (existing spec, absorbed) | feed-watch | M1.1 | done (merged `e07b239` 2026-08-21; **live since 2026-08-25 17:16 UTC**, enabled by the owner from chat — the runbook's 19 checks were never walked; cost evidence below) |
|
|
59
59
|
| M2.2 | Scheduled task model-leg policy: primary-or-skip for research runs (F2) | scheduled-task-model-leg-policy | — | deferred until after M3.1 (D-015) |
|
|
60
|
-
| M2.3 | Reporter notebook, re-cut 2026-08-21 (D-019): topic notes are napkin-shaped **episodes** the profile seeds once and napkin maintains nightly, written in-turn before posting; seeded `MEMORY.md` for standing facts; priorities tie-break in `AGENTS.md`; `workspace_seed` in `space-profile`; per-space distillation addendum in napkin; member perms and stale-note hygiene (audit R1/R6/R7, D-016, D-019) | reporter-notebook | M1.1 | done (merged 2026-08-22; **
|
|
61
|
-
| M2.4 | One voice per space: `persona.exclusive` drops the global character and global `AGENTS.md` for a space that owns its standard; platform-prompt capability paragraphs gated on the caller's permissions (audit R2, D-017) | one-voice-per-space | — | done (merged 2026-08-25; **
|
|
62
|
-
| M2.5 | Chat window holds conversation, not procedure (scheduled prompts out of the turn count, no halving on swipe-reply); silence is a legal chat reply (audit R3/R4, D-018) | chat-window-and-silence | — |
|
|
60
|
+
| M2.3 | Reporter notebook, re-cut 2026-08-21 (D-019): topic notes are napkin-shaped **episodes** the profile seeds once and napkin maintains nightly, written in-turn before posting; seeded `MEMORY.md` for standing facts; priorities tie-break in `AGENTS.md`; `workspace_seed` in `space-profile`; per-space distillation addendum in napkin; member perms and stale-note hygiene (audit R1/R6/R7, D-016, D-019) | reporter-notebook | M1.1 | done (merged 2026-08-22; **deployed 2026-08-22 23:07 IDT**; 19 episode notes on the box by 08-31, `MEMORY.md` written by the bot — 7 of 10 evidence checks still unticked in `pending-verification.md`) |
|
|
61
|
+
| M2.4 | One voice per space: `persona.exclusive` drops the global character and global `AGENTS.md` for a space that owns its standard; platform-prompt capability paragraphs gated on the caller's permissions (audit R2, D-017) | one-voice-per-space | — | done (merged 2026-08-25; **deployed 2026-08-25 16:44 IDT** with the image rebuild; `persona.exclusive=true` live; 6 behaviour checks still unticked) |
|
|
62
|
+
| M2.5 | Chat window holds conversation, not procedure (scheduled prompts out of the turn count, no halving on swipe-reply); silence is a legal chat reply (audit R3/R4, D-018) | chat-window-and-silence | — | done (merged 2026-08-31; live checks queued in `pending-verification.md` §M2.5, deploy-gated) |
|
|
63
63
|
|
|
64
64
|
> **Revisit 2026-08-21 — napkin works now** (`docs/notes/napkin-revisit-2026-08-21.md`). **Approved by the owner the same day ([D-019](decisions.md)):** M2.3 re-cut so the topic notes are napkin-maintained episodes under `knowledge/episodes/` (the only injected dir) that the profile seeds once, plus a per-space distillation addendum. Nothing removed; M2.4/M2.5/M3.x unchanged. Idea parked: `docs/ideas/napkin-member-notes.md`.
|
|
65
65
|
|
|
@@ -91,6 +91,58 @@ and a member run's system prompt carries no preference-management paragraph; a
|
|
|
91
91
|
swipe-reply follow-up sees the bot's own post from the previous evening; a
|
|
92
92
|
"don't answer" message in `main` gets no reply.
|
|
93
93
|
|
|
94
|
+
> **Checkpoint evidence, read from the box 2026-08-31** (ledger `task_runs`,
|
|
95
|
+
> `space_config`, `messages`; nothing gated, everything reported per D-008):
|
|
96
|
+
>
|
|
97
|
+
> - **Passed:** task 28 and `tasks/scan.md` are gone; verify one-shots produce
|
|
98
|
+
> either a sourced item line or `NO_UPDATE` (08-31 sample: 5 posted / 6
|
|
99
|
+
> no_update); the 09:00 article runs daily with the digest (`last_status=ok`,
|
|
100
|
+
> 608-char summary on 08-31); `persona.exclusive=true` is live and the image
|
|
101
|
+
> carries `callerMay`; every run since 08-25 is single-leg (no fallback
|
|
102
|
+
> possible under the pin).
|
|
103
|
+
> - **Failed:** the **cost direction**. 46–48 runs/day, ~70 % `no_update`,
|
|
104
|
+
> **$14–20/day** for the space (08-26 → 08-30) against "well under
|
|
105
|
+
> ~$5.30/day" and a pre-M2 baseline of ~$3/day.
|
|
106
|
+
> **Diagnosis corrected 2026-08-31 (second read, from `token_usage` and the
|
|
107
|
+
> traces, not `task_runs`):** the standard + notebook is *not* the cost — a
|
|
108
|
+
> verify run on that prefix was ~37 K tokens / $0.09 on 08-25. Since 08-26
|
|
109
|
+
> every run cache-writes ~121 K tokens / $0.30, because the daily article's
|
|
110
|
+
> expanded 103 KB digest is persisted and replayed through the history
|
|
111
|
+
> window — `docs/debug/major/2026-08-31-feed-watch-digest-persisted-and-replayed.md`, on the
|
|
112
|
+
> bug track below. Cost law: `cost/day ≈ runs × prefix tokens × $2.50/M`.
|
|
113
|
+
> Fix order: host persists the pre-append prompt (removes the replay) →
|
|
114
|
+
> digest without links + a cap (the article's own prefix) → profile levers
|
|
115
|
+
> `max_per_hour` 1, `batch_minutes` 15, `exclude` mined from `NO_UPDATE`
|
|
116
|
+
> replies (the run count). Expected after the first two: ~$4.5/day at
|
|
117
|
+
> today's run count, ~$2.5–3 with the levers.
|
|
118
|
+
> **All three shipped and were verified on the live bot 2026-08-31** (`fad12f9`,
|
|
119
|
+
> `81d022e`; two on-demand article runs against the test group rather than
|
|
120
|
+
> waiting for 09:00). The article turn is 105,482 → 4,322 chars, the digest
|
|
121
|
+
> block 36.0 KB for a *larger* window, and a verify run's first-round prefix
|
|
122
|
+
> **118 K → 50.6 K**. Projection ≈ $5.4/day; the ledger over 09-01 → 09-03 is
|
|
123
|
+
> what closes the spend check. `exclude` mining was **not** done — the run
|
|
124
|
+
> count is already capped at 1/hour and the prefix is the bigger factor.
|
|
125
|
+
> Correction to the measurement method, worth more than the number: the
|
|
126
|
+
> comparable prefix is the **first round's** `cacheWrite` from the trace, not
|
|
127
|
+
> `token_usage.cache_write_tokens`, which sums every round and so makes a
|
|
128
|
+
> 16-round article look more expensive than a 1-round `NO_UPDATE`. **The "per-task cheaper leg"
|
|
129
|
+
> rationale for re-opening M2.2 does not survive this** — decide M2.2 on
|
|
130
|
+
> safety after the bug is fixed, not on cost before it. Second-order levers
|
|
131
|
+
> filed as ideas: `isolated-verify-task-runs.md`, `cross-run-prompt-caching.md`.
|
|
132
|
+
> The ledger that hid this for six days is its own bug:
|
|
133
|
+
> `task-runs-ledger-input-tokens-is-uncached-sliver.md`.
|
|
134
|
+
> - **Changed under us:** the space's `model.active` is `claude-sonnet-5`
|
|
135
|
+
> since 2026-08-24 (owner's `/model switch`), so "zero posts from a
|
|
136
|
+
> non-primary leg" is true only because there is no fallback; the primary is
|
|
137
|
+
> no longer Opus. Sonnet narrates: 10 of 121 replies since 08-22 open with an
|
|
138
|
+
> English working sentence (`docs/bugs/narration-in-same-text-block-leaks-into-reply.md`).
|
|
139
|
+
> - **Not evidenced:** the trace checks (topic note read on a factual
|
|
140
|
+
> question, no `AGENTS.md` mount for the exclusive space during a run), the
|
|
141
|
+
> swipe-reply continuity and the "don't answer" silence — all need a person
|
|
142
|
+
> watching a run; still open in `pending-verification.md`.
|
|
143
|
+
> - **Drift:** the owner tuned the live `AGENTS.md` on 08-27; adopted into the
|
|
144
|
+
> repo 08-31 ([D-020](decisions.md)).
|
|
145
|
+
|
|
94
146
|
> **Extended 2026-08-21 ([D-016](decisions.md)–[D-018](decisions.md)).** The
|
|
95
147
|
> 2026-08-21 audit (`docs/notes/football-audit-2026-08-21.md`) traced both
|
|
96
148
|
> owner-reported failures — a hallucinated name and a joke refused as a
|
|
@@ -146,6 +198,12 @@ is filed the same way, never fixed inside the story.
|
|
|
146
198
|
| — | `model-switch-disables-fallback.md` | moderate | M4.1 |
|
|
147
199
|
| audit-2 C9 | `feed-watch-seen-ttl-not-refreshed.md` | moderate | M4.1 (fix **before** the feed-watch deploy) |
|
|
148
200
|
| audit-2 C10 | `feed-watch-runas-non-user-principal.md` | moderate | M4.1 (fix **before** the feed-watch deploy) |
|
|
201
|
+
| M2 checkpoint | `feed-watch-digest-persisted-and-replayed.md` | major | **DONE** 2026-08-31 — fixed, deployed, verified on the live bot (`fad12f9` + `81d022e`). M2.2 is now unblocked and must be decided on *safety*, not cost |
|
|
202
|
+
| M2 checkpoint | `task-runs-ledger-input-tokens-is-uncached-sliver.md` | minor | **DONE** 2026-08-31 — `cache_read_tokens`/`cache_write_tokens`/`prompt_prefix_tokens`/`rounds` on the row, one derived `billedPromptTokens` for every reader, dashboard shows Prefix · Rounds · Billed. Deployed with an image rebuild (`src/types.ts` is baked); audited 2026-08-31 against raw traces and a smoke run (298). Post-mortem in `docs/debug/minor/` |
|
|
203
|
+
| M2.3 checkpoint | `episode-current-state-over-budget.md` | moderate | **Re-ranked 2026-08-31** — only ONE episode is injected per run, not all: 1-12% of the prompt (barcelona 11.6 KB worst case), not the "24 KB every run" filed. Still worth the pruning rule for note *quality*; it is not the cost lever it was filed as |
|
|
204
|
+
| — | `football-group-went-silent-under-the-feed.md` | minor | posting budget for a test group; stale `space_roles` tidy |
|
|
205
|
+
| — | `episode-current-state-heading-suffix-drops-body.md` | major (latent) | **DONE** 2026-08-31 (`b18e9ca`, deployed with a rebuild) — does not reproduce today: every live note carries an HTML-comment workaround telling the distiller to keep the heading bare. Fixed at the parser so one rewrite cannot silently return it |
|
|
206
|
+
| M2 follow-on | `history-window-dominates-the-run-prefix.md` | major | **NEW** 2026-08-31 — `<history>` is 65–81% of every prompt (~68 KB, near-constant across runs). The largest remaining prefix term by a wide margin, and never a bug — a default meeting an unusual space. **Read M2.5's architecture first**: this may be evidence for that story rather than a separate fix |
|
|
149
207
|
|
|
150
208
|
The two `feed-watch-*` rows come from the 2026-08-21 audit's second pass
|
|
151
209
|
(`docs/notes/football-audit-2026-08-21.md` §3.1): a `seen` TTL that counts
|
package/docs/permissions.md
CHANGED
|
@@ -210,6 +210,21 @@ Permissions are **per-space**:
|
|
|
210
210
|
- Custom role permissions are space-specific
|
|
211
211
|
- No global roles (except seeded admins, which apply on first interaction per space)
|
|
212
212
|
|
|
213
|
+
### An extension permission is not a scope
|
|
214
|
+
|
|
215
|
+
`admin` is granted **all** permissions — built-in *and* extension-registered — in
|
|
216
|
+
its own space. So a permission registered by an extension gates members, and
|
|
217
|
+
nothing more: every space admin in the deployment passes `checkPerm(<ext>)`
|
|
218
|
+
automatically, including for a capability meant for one space only.
|
|
219
|
+
|
|
220
|
+
An extension that must be restricted to particular spaces or particular callers
|
|
221
|
+
has to enforce that inside its own handler. The `overview` extension
|
|
222
|
+
(`examples/extensions/overview/`, see [extensions.md](extensions.md#cross-space-overview-overview))
|
|
223
|
+
is the worked example: it reads other spaces' messages, and its real boundary is
|
|
224
|
+
three in-handler checks — a host-only `MERCURY_MANAGEMENT_SPACE_ID` env var
|
|
225
|
+
(absence denies), exact equality against the caller's token-bound space id, and
|
|
226
|
+
`isGlobalAdmin`. Its `defaultRoles: []` keeps members out and does nothing else.
|
|
227
|
+
|
|
213
228
|
## API
|
|
214
229
|
|
|
215
230
|
### `resolveRole(db, spaceId, platformUserId, seededAdmins)`
|