mercury-agent 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/container/Dockerfile +127 -0
  4. package/container/Dockerfile.base +109 -0
  5. package/container/Dockerfile.power +17 -0
  6. package/container/agent-package.json +8 -0
  7. package/container/build.sh +54 -0
  8. package/docs/TODOS.md +147 -0
  9. package/docs/auth/dashboard.md +28 -0
  10. package/docs/auth/overview.md +109 -0
  11. package/docs/auth/whatsapp.md +173 -0
  12. package/docs/configuration.md +54 -0
  13. package/docs/container-lifecycle.md +349 -0
  14. package/docs/context-architecture.md +87 -0
  15. package/docs/deployment.md +199 -0
  16. package/docs/extensions.md +375 -0
  17. package/docs/graceful-shutdown.md +62 -0
  18. package/docs/kb-distillation.md +77 -0
  19. package/docs/media/overview.md +140 -0
  20. package/docs/media/whatsapp.md +171 -0
  21. package/docs/memory.md +137 -0
  22. package/docs/permissions.md +217 -0
  23. package/docs/pipeline.md +228 -0
  24. package/docs/prd-chat-memory.md +76 -0
  25. package/docs/prd-config-load.md +82 -0
  26. package/docs/rate-limiting.md +166 -0
  27. package/docs/scheduler.md +288 -0
  28. package/docs/setup-discord.md +100 -0
  29. package/docs/setup-slack.md +119 -0
  30. package/docs/setup-whatsapp.md +94 -0
  31. package/docs/subagents.md +166 -0
  32. package/docs/web-search.md +62 -0
  33. package/examples/extensions/README.md +12 -0
  34. package/examples/extensions/charts/index.ts +13 -0
  35. package/examples/extensions/charts/skill/SKILL.md +98 -0
  36. package/examples/extensions/gws/README.md +52 -0
  37. package/examples/extensions/gws/index.ts +106 -0
  38. package/examples/extensions/gws/skill/SKILL.md +57 -0
  39. package/examples/extensions/gws/skill/references/calendar.md +101 -0
  40. package/examples/extensions/gws/skill/references/docs.md +65 -0
  41. package/examples/extensions/gws/skill/references/drive.md +79 -0
  42. package/examples/extensions/gws/skill/references/gmail.md +85 -0
  43. package/examples/extensions/gws/skill/references/sheets.md +60 -0
  44. package/examples/extensions/napkin/index.ts +821 -0
  45. package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
  46. package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
  47. package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -0
  49. package/examples/extensions/pdf/index.ts +23 -0
  50. package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
  51. package/examples/extensions/pdf/skill/SKILL.md +314 -0
  52. package/examples/extensions/pdf/skill/forms.md +294 -0
  53. package/examples/extensions/pdf/skill/reference.md +612 -0
  54. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
  55. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
  56. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
  57. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
  58. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
  59. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
  60. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
  61. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
  62. package/examples/extensions/permission-guard/index.ts +65 -0
  63. package/examples/extensions/pinchtab/index.ts +199 -0
  64. package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
  65. package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
  66. package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
  67. package/examples/extensions/pinchtab/skill/references/api.md +297 -0
  68. package/examples/extensions/pinchtab/skill/references/env.md +45 -0
  69. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
  70. package/examples/extensions/tradestation/host/refresh.ts +102 -0
  71. package/examples/extensions/tradestation/index.ts +153 -0
  72. package/examples/extensions/tradestation/skill/SKILL.md +67 -0
  73. package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
  74. package/examples/extensions/voice-synth/index.ts +94 -0
  75. package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
  76. package/examples/extensions/voice-transcribe/index.ts +381 -0
  77. package/examples/extensions/voice-transcribe/requirements.txt +8 -0
  78. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
  79. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
  80. package/examples/extensions/web-search/index.ts +22 -0
  81. package/examples/extensions/web-search/skill/SKILL.md +114 -0
  82. package/examples/extensions/web-search/skill/references/apartments.md +178 -0
  83. package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
  84. package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
  85. package/examples/extensions/web-search/skill/references/flights.md +133 -0
  86. package/examples/extensions/web-search/skill/references/hotels.md +148 -0
  87. package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
  88. package/examples/extensions/yahoo-mail/cli/package.json +13 -0
  89. package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
  90. package/examples/extensions/yahoo-mail/index.ts +57 -0
  91. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
  92. package/package.json +106 -0
  93. package/resources/agents/explore.md +50 -0
  94. package/resources/agents/worker.md +24 -0
  95. package/resources/builtin-extensions.txt +3 -0
  96. package/resources/connection-env-vars.json +25 -0
  97. package/resources/extensions/.gitkeep +0 -0
  98. package/resources/pi-extensions/subagent/agents.ts +126 -0
  99. package/resources/pi-extensions/subagent/index.ts +964 -0
  100. package/resources/profiles/coding/AGENTS.md +43 -0
  101. package/resources/profiles/coding/mercury-profile.yaml +15 -0
  102. package/resources/profiles/general/AGENTS.md +31 -0
  103. package/resources/profiles/general/mercury-profile.yaml +15 -0
  104. package/resources/profiles/research/AGENTS.md +40 -0
  105. package/resources/profiles/research/mercury-profile.yaml +15 -0
  106. package/resources/skills/config/SKILL.md +25 -0
  107. package/resources/skills/context/SKILL.md +33 -0
  108. package/resources/skills/conversation-recap/SKILL.md +19 -0
  109. package/resources/skills/media/SKILL.md +27 -0
  110. package/resources/skills/mutes/SKILL.md +31 -0
  111. package/resources/skills/permissions/SKILL.md +19 -0
  112. package/resources/skills/preferences/SKILL.md +31 -0
  113. package/resources/skills/recall/SKILL.md +24 -0
  114. package/resources/skills/roles/SKILL.md +18 -0
  115. package/resources/skills/spaces/SKILL.md +18 -0
  116. package/resources/skills/tasks/SKILL.md +45 -0
  117. package/resources/templates/AGENTS.md +157 -0
  118. package/resources/templates/env.template +34 -0
  119. package/resources/templates/mercury.example.yaml +75 -0
  120. package/src/adapters/discord-native.ts +534 -0
  121. package/src/adapters/discord.ts +38 -0
  122. package/src/adapters/setup.ts +89 -0
  123. package/src/adapters/slack.ts +9 -0
  124. package/src/adapters/whatsapp-media.ts +337 -0
  125. package/src/adapters/whatsapp.ts +629 -0
  126. package/src/agent/api-socket.ts +127 -0
  127. package/src/agent/container-entry.ts +967 -0
  128. package/src/agent/container-error.ts +49 -0
  129. package/src/agent/container-runner.ts +1272 -0
  130. package/src/agent/model-capabilities-core.ts +23 -0
  131. package/src/agent/model-capabilities.ts +231 -0
  132. package/src/agent/pi-failure-class.ts +83 -0
  133. package/src/agent/pi-jsonl-parser.ts +306 -0
  134. package/src/agent/preferences-prompt.ts +20 -0
  135. package/src/agent/user-error-messages.ts +78 -0
  136. package/src/bridges/discord.ts +171 -0
  137. package/src/bridges/slack.ts +177 -0
  138. package/src/bridges/teams.ts +160 -0
  139. package/src/bridges/telegram.ts +571 -0
  140. package/src/bridges/whatsapp.ts +290 -0
  141. package/src/chat-shim.ts +259 -0
  142. package/src/cli/mercury.ts +2508 -0
  143. package/src/cli/mrctl-http.ts +27 -0
  144. package/src/cli/mrctl.ts +611 -0
  145. package/src/cli/whatsapp-auth.ts +260 -0
  146. package/src/config-file.ts +397 -0
  147. package/src/config-model-chain.ts +30 -0
  148. package/src/config.ts +316 -0
  149. package/src/core/api-types.ts +58 -0
  150. package/src/core/api.ts +105 -0
  151. package/src/core/commands.ts +76 -0
  152. package/src/core/conversation.ts +47 -0
  153. package/src/core/handler.ts +206 -0
  154. package/src/core/media.ts +200 -0
  155. package/src/core/mute-duration.ts +22 -0
  156. package/src/core/outbox.ts +76 -0
  157. package/src/core/permissions.ts +192 -0
  158. package/src/core/profiles.ts +245 -0
  159. package/src/core/rate-limiter.ts +127 -0
  160. package/src/core/router.ts +191 -0
  161. package/src/core/routes/chat.ts +172 -0
  162. package/src/core/routes/config-builtin.ts +107 -0
  163. package/src/core/routes/config.ts +81 -0
  164. package/src/core/routes/connections.ts +190 -0
  165. package/src/core/routes/console.ts +668 -0
  166. package/src/core/routes/control.ts +46 -0
  167. package/src/core/routes/conversations.ts +66 -0
  168. package/src/core/routes/dashboard.ts +2491 -0
  169. package/src/core/routes/extensions.ts +37 -0
  170. package/src/core/routes/index.ts +14 -0
  171. package/src/core/routes/media.ts +72 -0
  172. package/src/core/routes/messages.ts +37 -0
  173. package/src/core/routes/mutes.ts +89 -0
  174. package/src/core/routes/prefs.ts +95 -0
  175. package/src/core/routes/roles.ts +125 -0
  176. package/src/core/routes/spaces.ts +60 -0
  177. package/src/core/routes/storage.ts +126 -0
  178. package/src/core/routes/tasks.ts +189 -0
  179. package/src/core/routes/tradestation.ts +268 -0
  180. package/src/core/routes/tts.ts +51 -0
  181. package/src/core/runtime.ts +1140 -0
  182. package/src/core/space-queue.ts +103 -0
  183. package/src/core/storage-cleanup.ts +140 -0
  184. package/src/core/storage-guard.ts +24 -0
  185. package/src/core/task-scheduler.ts +132 -0
  186. package/src/core/telegram-format.ts +178 -0
  187. package/src/core/trigger.ts +142 -0
  188. package/src/dashboard/index.html +729 -0
  189. package/src/dashboard/tokens.css +53 -0
  190. package/src/extensions/api.ts +252 -0
  191. package/src/extensions/catalog.ts +117 -0
  192. package/src/extensions/config-registry.ts +83 -0
  193. package/src/extensions/context.ts +36 -0
  194. package/src/extensions/hooks.ts +156 -0
  195. package/src/extensions/image-builder.ts +617 -0
  196. package/src/extensions/installer.ts +306 -0
  197. package/src/extensions/jobs.ts +122 -0
  198. package/src/extensions/loader.ts +271 -0
  199. package/src/extensions/permission-guard.ts +52 -0
  200. package/src/extensions/reserved.ts +28 -0
  201. package/src/extensions/skills.ts +123 -0
  202. package/src/extensions/types.ts +462 -0
  203. package/src/logger.ts +174 -0
  204. package/src/main.ts +586 -0
  205. package/src/server.ts +391 -0
  206. package/src/storage/db.ts +1624 -0
  207. package/src/storage/memory.ts +45 -0
  208. package/src/storage/pi-auth.ts +95 -0
  209. package/src/text/markdown.ts +117 -0
  210. package/src/text/rtl.ts +38 -0
  211. package/src/tradestation/host-api.ts +77 -0
  212. package/src/tradestation/pending-orders.ts +69 -0
  213. package/src/tts/azure.ts +52 -0
  214. package/src/tts/google.ts +128 -0
  215. package/src/tts/index.ts +8 -0
  216. package/src/tts/language.ts +20 -0
  217. package/src/tts/synthesize.ts +133 -0
  218. package/src/types.ts +295 -0
@@ -0,0 +1,54 @@
1
+ # Configuration
2
+
3
+ Mercury reads settings from **environment variables** (`MERCURY_*`) and, optionally, a project **`mercury.yaml`** file in the current working directory.
4
+
5
+ **Product / design spec:** [prd-config-load.md](prd-config-load.md) (config load: YAML + env precedence, security, non-goals).
6
+
7
+ ## Precedence
8
+
9
+ 1. If a `MERCURY_*` variable is **set** in the environment (the key exists, including empty string), its value wins.
10
+ 2. Otherwise, if **`mercury.yaml`** (or **`mercury.yml`**) exists in `cwd`, values from that file apply.
11
+ 3. Otherwise, built-in defaults from `config.ts` apply.
12
+
13
+ Set **`MERCURY_CONFIG_FILE`** to an explicit path to load a different file. Set it to **`""`** (empty) or **`none`** to **disable** loading any file (useful for tests or when you want env/defaults only).
14
+
15
+ Relative paths in `MERCURY_CONFIG_FILE` are resolved against `cwd`.
16
+
17
+ ## Secrets (never use `mercury.yaml` for these)
18
+
19
+ These must be supplied via environment variables only; they are **not** read from YAML:
20
+
21
+ - `MERCURY_API_SECRET`
22
+ - `MERCURY_CHAT_API_KEY`
23
+ - `MERCURY_DISCORD_GATEWAY_SECRET`
24
+
25
+ Platform tokens, provider API keys, and extension keys (e.g. `MERCURY_TELEGRAM_BOT_TOKEN`, `MERCURY_BRAVE_API_KEY`) are also **env-only** today—they are not part of the YAML schema.
26
+
27
+ ## YAML layout
28
+
29
+ See [`resources/templates/mercury.example.yaml`](../resources/templates/mercury.example.yaml) for a commented template. Supported sections include `server`, `model`, `ingress`, `runtime`, `trigger`, `context`, `conditional_context`, `compaction`, `agent`, `discord`, `telegram`, `media`, and `permissions`.
30
+
31
+ The `context:` block seeds default conversation-context behavior into the `main` space on first boot:
32
+
33
+ ```yaml
34
+ context:
35
+ mode: context # clear | context (default: context)
36
+ window_size: 10 # 1-50 (default: 10). Sliding-window turns when mode=context.
37
+ reply_chain_depth: 10 # 1-50 (default: 10). Reply chain depth when mode=clear.
38
+ ```
39
+
40
+ Per-space overrides via `mrctl config set context.<key> <value>` always win over YAML defaults; YAML re-reads on restart do not overwrite an existing space row.
41
+
42
+ You may also set a top-level **`model_chain`** array as an alias for `model.chain`.
43
+
44
+ ## Model chain
45
+
46
+ In YAML, use a list of `{ provider, model }` objects under `model.chain` (max 4 legs). The same rules apply as for `MERCURY_MODEL_CHAIN` JSON.
47
+
48
+ Optional **`model.capabilities`** may be a mapping; it is applied like `MERCURY_MODEL_CAPABILITIES` JSON.
49
+
50
+ ### Removed: `provider: cursor`
51
+
52
+ The **Cursor Agent CLI** integration has been removed. All model legs use **pi** with standard providers (`anthropic`, `openai`, `google`, `mistral`, `groq`, `openrouter`, etc.).
53
+
54
+ If your chain still has `provider: cursor`, the agent run **fails fast** with an error that points here. Switch to the **native provider** for the model you want (for example `anthropic` for Claude, `openai` for GPT) and set the matching **`MERCURY_*_API_KEY`**.
@@ -0,0 +1,349 @@
1
+ # Container Lifecycle
2
+
3
+ Mercury runs agent code inside Docker containers. This document covers how containers are managed, what happens when they fail, and how the system recovers.
4
+
5
+ ## Deployment Topology
6
+
7
+ Mercury uses a two-layer container model. The layers differ between local and production (Hetzner) deployments.
8
+
9
+ ### Local (`mercury run`)
10
+
11
+ ```
12
+ Local machine
13
+ └── mercury run (host process)
14
+ └── mercury-<ts>-<id> (inner container, ephemeral --rm, one per message)
15
+ ```
16
+
17
+ Mercury runs directly on the host. Each incoming message spawns a short-lived inner container to run the Claude agent, which is deleted automatically on exit (`--rm`).
18
+
19
+ ### Production node
20
+
21
+ ```
22
+ Production node
23
+ ├── orchestrator (manages the node — start/stop/update agents)
24
+ ├── traefik (routes *.baseDomain → agent containers)
25
+ ├── mercury-agent-<user1> (outer container, persistent, one per tenant)
26
+ │ └── mercury-<ts>-<id> (inner container, ephemeral --rm, one per message)
27
+ ├── mercury-agent-<user2> (outer container, persistent)
28
+ │ └── mercury-<ts>-<id>
29
+ └── ...
30
+ ```
31
+
32
+ A single node hosts many tenants. Each user's Mercury process runs inside its own persistent outer container (`--restart=unless-stopped`). Inside that, per-message inner containers work exactly as they do locally.
33
+
34
+ ### Why outer containers in production?
35
+
36
+ | Concern | How outer containers solve it |
37
+ |---|---|
38
+ | Tenant isolation | Each agent runs in its own container — can't interfere with others |
39
+ | Resource limits | `--memory` and `--cpus` enforced per-agent by the orchestrator |
40
+ | Routing | Traefik labels assign each container its own subdomain (`agentId.baseDomain`) |
41
+ | Independent lifecycle | orchestrator can start/stop/restart/update one agent without touching others |
42
+ | Persistent state | Named Docker volume per agent (`mercury-<agentId>-data`) holds SQLite DB, WhatsApp auth, and spaces |
43
+
44
+ ### Comparison
45
+
46
+ | | Local (`mercury run`) | Production node |
47
+ |---|---|---|
48
+ | Mercury process | host process | `mercury-agent-<id>` container (`-d --restart=unless-stopped`) |
49
+ | Per-message agent | ephemeral container (`--rm`) | ephemeral container (`--rm`) inside the outer container |
50
+ | Logs | lost on exit | retained — `--log-opt max-size=20m --log-opt max-file=3` |
51
+ | State | host filesystem | named Docker volume |
52
+
53
+ ### Debugging inner container logs
54
+
55
+ Inner containers are `--rm` and their logs are gone once they exit. To capture them you must stream live while the container runs:
56
+
57
+ ```bash
58
+ # Watch for the container to appear
59
+ docker ps --filter "label=mercury.managed=true"
60
+
61
+ # Tail its logs while it runs
62
+ docker logs -f mercury-<ts>-<id>
63
+ ```
64
+
65
+ On a production node, the outer container logs are always available via SSH:
66
+ ```bash
67
+ docker logs mercury-agent-<agentId> -f
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Container Identity
73
+
74
+ Each container is tagged for tracking and cleanup:
75
+
76
+ | Property | Format | Purpose |
77
+ |----------|--------|---------|
78
+ | **Name** | `mercury-<timestamp>-<id>` | Unique identifier for logging/debugging |
79
+ | **Label** | `mercury.managed=true` | Identifies mercury-owned containers for cleanup |
80
+
81
+ Example:
82
+ ```
83
+ docker ps --filter "label=mercury.managed=true"
84
+ CONTAINER ID IMAGE NAMES
85
+ a1b2c3d4e5f6 mercury-agent mercury-1709312456789-1
86
+ ```
87
+
88
+ ## Timeout
89
+
90
+ Containers have a maximum runtime to prevent runaway processes.
91
+
92
+ | Config | Env Var | Default | Range |
93
+ |--------|---------|---------|-------|
94
+ | `containerTimeoutMs` | `MERCURY_CONTAINER_TIMEOUT_MS` | 5 minutes | 10s – 1h |
95
+
96
+ When a container exceeds the timeout:
97
+ 1. Container is killed via `docker kill`
98
+ 2. `ContainerError` thrown with `reason: "timeout"`
99
+ 3. User sees: "Container timed out."
100
+ 4. Queue unblocks, next message can proceed
101
+
102
+ The host always injects a resolved **model chain** into the container (after `MERCURY_*` passthrough) so retries and fallbacks use the same policy Mercury loaded at startup:
103
+
104
+ | In-container env | Source (host) | Purpose |
105
+ |------------------|---------------|---------|
106
+ | `MODEL_CHAIN` | `resolvedModelChain` (from `MERCURY_MODEL_CHAIN` or primary+fallback) | Ordered `{ provider, model }` legs (max 4) |
107
+ | `MODEL_RETRY_MAX_PER_LEG` | `MERCURY_MODEL_MAX_RETRIES_PER_LEG` | Extra attempts per leg for transient errors |
108
+ | `MODEL_CHAIN_BUDGET_MS` | `effectiveModelChainBudgetMs` | Wall-clock budget for the whole chain (clamped below container timeout) |
109
+
110
+ ## Error Types
111
+
112
+ Container failures are classified by `ContainerError`:
113
+
114
+ | Reason | Exit Code | Cause | User Message |
115
+ |--------|-----------|-------|--------------|
116
+ | `timeout` | — | Exceeded `containerTimeoutMs` | "Container timed out." |
117
+ | `oom` | 137 | SIGKILL (OOM, resource limits, or manual kill) | "Container was killed (possibly out of memory)." |
118
+ | `aborted` | — | User sent `stop` command | "Stopped current run." |
119
+ | `error` | non-zero | Agent crashed or failed | *(error thrown, logged)* |
120
+
121
+ Exit code 137 = 128 + 9 (SIGKILL), typically from Docker's OOM killer.
122
+
123
+ ## Orphan Cleanup
124
+
125
+ If the host process crashes or restarts while containers are running, those containers become orphans. On startup, mercury cleans them up:
126
+
127
+ ```
128
+ Startup
129
+
130
+ └─► runtime.initialize()
131
+
132
+ └─► containerRunner.cleanupOrphans()
133
+
134
+ ├─► docker ps -a --filter "label=mercury.managed=true"
135
+ ├─► docker rm -f <container-ids>
136
+ └─► Log: "Cleaned up N orphaned container(s)"
137
+ ```
138
+
139
+ This ensures:
140
+ - No zombie containers consuming resources
141
+ - No blocked space queues from previous runs
142
+ - Clean state before accepting new work
143
+
144
+ ## Lifecycle Diagram
145
+
146
+ ```
147
+ Message received
148
+
149
+ ├─► Queue (one per space)
150
+
151
+ ├─► Spawn container
152
+ │ • --name mercury-<ts>-<id>
153
+ │ • --label mercury.managed=true
154
+ │ • --rm (auto-remove on exit)
155
+
156
+ ├─► Start timeout timer
157
+
158
+ ├─► Wait for completion
159
+ │ │
160
+ │ ├─► Success (exit 0) → parse reply + scan outbox/ → respond
161
+ │ ├─► Timeout → kill container → ContainerError(timeout)
162
+ │ ├─► OOM (exit 137) → ContainerError(oom)
163
+ │ ├─► Aborted → ContainerError(aborted)
164
+ │ └─► Other failure → ContainerError(error)
165
+
166
+ └─► Cleanup
167
+ • Clear timeout timer
168
+ • Remove from tracking map
169
+ • Queue unblocks (finally block)
170
+ ```
171
+
172
+ ## Configuration
173
+
174
+ ```bash
175
+ # Set container timeout to 10 minutes
176
+ export MERCURY_CONTAINER_TIMEOUT_MS=600000
177
+
178
+ # Use a preset image from GitHub Container Registry
179
+ export MERCURY_AGENT_IMAGE=ghcr.io/michaelliv/mercury-agent:latest # Full (default)
180
+ export MERCURY_AGENT_IMAGE=ghcr.io/michaelliv/mercury-agent:minimal # Lightweight
181
+ ```
182
+
183
+ ## Sandboxing (Bubblewrap)
184
+
185
+ Mercury uses a two-layer isolation model:
186
+
187
+ 1. **Docker** — isolates the agent from the host
188
+ 2. **Bubblewrap** — restricts the pi process within the container (defense-in-depth)
189
+
190
+ The pi agent runs inside `bwrap`, which creates a minimal mount namespace with only the paths needed for the agent: workspace (`/spaces`), app code (`/app`), docs (`/docs`), and runtime dirs (`/root`, `/usr`, `/etc`, `/proc`, `/dev`, `/tmp`). This limits blast radius if the agent is compromised.
191
+
192
+ | Env Var | Purpose |
193
+ |---------|---------|
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_DISABLE_BUBBLEWRAP=1` | Disable bubblewrap; run pi directly (last resort / debugging) |
196
+
197
+ 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.
198
+
199
+ Custom images must install `bubblewrap` for sandboxing to work.
200
+
201
+ ## Agent Image Presets
202
+
203
+ Mercury publishes two image presets to GitHub Container Registry:
204
+
205
+ | Preset | Size | Contents |
206
+ |--------|------|----------|
207
+ | `ghcr.io/michaelliv/mercury-agent:latest` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
208
+ | `ghcr.io/michaelliv/mercury-agent:minimal` | ~1.9GB | Lightweight runtime: Bun + pi + Chromium deps |
209
+
210
+ Images are published on each release. Version-specific tags are also available (e.g., `:0.2.0`, `:0.2.0-minimal`).
211
+
212
+ ### Building Locally
213
+
214
+ To build images locally instead of pulling from the registry:
215
+ ```bash
216
+ ./container/build.sh all # Both presets
217
+ ./container/build.sh latest # Full image only (default)
218
+ ./container/build.sh minimal # Lightweight image only
219
+ ```
220
+
221
+ Then use `mercury-agent:latest` or `mercury-agent:minimal` (without the ghcr.io prefix).
222
+
223
+ ## Custom Agent Images
224
+
225
+ You can use custom Docker images via `MERCURY_AGENT_IMAGE`.
226
+
227
+ ### Requirements
228
+
229
+ Your image **must** have:
230
+ - `bun` runtime
231
+ - `pi` CLI (`@mariozechner/pi-coding-agent`)
232
+ - `bubblewrap` (for agent sandboxing)
233
+ - `mrctl` wrapper (copied during build)
234
+ Extension CLIs (e.g. `pinchtab`, `napkin`, `gws`) are installed in derived images at runtime based on `.mercury/extensions/*` declarations.
235
+
236
+ ### Entry Point
237
+
238
+ The image must use this entrypoint:
239
+ ```dockerfile
240
+ ENTRYPOINT ["bun", "run", "/app/src/agent/container-entry.ts"]
241
+ ```
242
+
243
+ ### Required Files
244
+
245
+ Copy these files into your image at `/app/`:
246
+ ```dockerfile
247
+ COPY src/agent/container-entry.ts /app/src/agent/container-entry.ts
248
+ COPY src/agent/pi-failure-class.ts /app/src/agent/pi-failure-class.ts
249
+ COPY src/agent/pi-jsonl-parser.ts /app/src/agent/pi-jsonl-parser.ts
250
+ COPY src/agent/preferences-prompt.ts /app/src/agent/preferences-prompt.ts
251
+ COPY src/cli/mrctl.ts /app/src/cli/mrctl.ts
252
+ COPY src/cli/mrctl-http.ts /app/src/cli/mrctl-http.ts
253
+ COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
254
+ COPY src/types.ts /app/src/types.ts
255
+ ```
256
+
257
+ ### mrctl Setup
258
+
259
+ Create the mrctl wrapper:
260
+ ```dockerfile
261
+ RUN echo '#!/bin/sh\nbun run /app/src/cli/mrctl.ts "$@"' > /usr/local/bin/mrctl && \
262
+ chmod +x /usr/local/bin/mrctl
263
+ ```
264
+
265
+ ### Volume Mounts
266
+
267
+ Mercury mounts these paths into containers:
268
+ - `/spaces` — Space workspaces (read/write)
269
+ - `/home/mercury/.pi/agent` — Global agent config, skills, auth (read/write)
270
+ - `/docs/mercury/` — Self-documentation (read-only)
271
+
272
+ ### Example Custom Dockerfile
273
+
274
+ ```dockerfile
275
+ FROM your-base-image:tag
276
+
277
+ # Install Bun
278
+ RUN curl -fsSL https://bun.sh/install | bash
279
+ ENV PATH="/home/mercury/.bun/bin:$PATH"
280
+
281
+ # Install required CLIs
282
+ RUN bun add -g @mariozechner/pi-coding-agent
283
+
284
+ # Optional: install Playwright/Chromium if your extensions need browser automation
285
+ RUN bunx playwright install chromium
286
+
287
+ WORKDIR /app
288
+
289
+ # Copy Mercury agent files
290
+ COPY src/agent/container-entry.ts /app/src/agent/container-entry.ts
291
+ COPY src/agent/pi-failure-class.ts /app/src/agent/pi-failure-class.ts
292
+ COPY src/agent/pi-jsonl-parser.ts /app/src/agent/pi-jsonl-parser.ts
293
+ COPY src/agent/preferences-prompt.ts /app/src/agent/preferences-prompt.ts
294
+ COPY src/cli/mrctl.ts /app/src/cli/mrctl.ts
295
+ COPY src/cli/mrctl-http.ts /app/src/cli/mrctl-http.ts
296
+ COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
297
+ COPY src/types.ts /app/src/types.ts
298
+
299
+ # Setup mrctl
300
+ RUN echo '#!/bin/sh\nbun run /app/src/cli/mrctl.ts "$@"' > /usr/local/bin/mrctl && \
301
+ chmod +x /usr/local/bin/mrctl
302
+
303
+ ENTRYPOINT ["bun", "run", "/app/src/agent/container-entry.ts"]
304
+ ```
305
+
306
+ ### Validation
307
+
308
+ When using a custom image (not `mercury-agent:*`), Mercury logs a warning at startup:
309
+ ```
310
+ WARN Using custom agent image
311
+ image: your-image:tag
312
+ note: Ensure image has: bun, pi, bubblewrap, mrctl
313
+ ```
314
+
315
+ ## API
316
+
317
+ ### `AgentContainerRunner`
318
+
319
+ ```ts
320
+ runner.cleanupOrphans() // Remove orphaned containers (called on startup)
321
+ runner.reply(input) // Run container, returns ContainerResult (reply + outbox files)
322
+ runner.abort(spaceId) // Kill container for a space
323
+ runner.killAll() // Kill all running containers (shutdown)
324
+ runner.isRunning(spaceId) // Check if container is active
325
+ runner.activeCount // Number of running containers
326
+ ```
327
+
328
+ ### `MercuryCoreRuntime`
329
+
330
+ ```ts
331
+ await runtime.initialize() // Call before accepting work (runs orphan cleanup)
332
+ ```
333
+
334
+ ### `ContainerError`
335
+
336
+ ```ts
337
+ import { ContainerError } from "./agent/container-error.js";
338
+
339
+ // Properties
340
+ error.reason // "timeout" | "oom" | "aborted" | "error"
341
+ error.exitCode // number | null
342
+ error.message // Human-readable description
343
+
344
+ // Factory methods
345
+ ContainerError.timeout(spaceId)
346
+ ContainerError.oom(spaceId, exitCode)
347
+ ContainerError.aborted(spaceId)
348
+ ContainerError.error(exitCode, output)
349
+ ```
@@ -0,0 +1,87 @@
1
+ # Context Architecture
2
+
3
+ Mercury uses a three-layer approach to give the agent the right context for every request — deterministic, bounded, and never accidentally referencing stale history.
4
+
5
+ ## The Three Layers
6
+
7
+ ### Layer 1 — Identity (always present)
8
+
9
+ Built by `buildSystemPrompt()` in `container-entry.ts`:
10
+
11
+ - `AGENTS.md` — the space's agent persona and instructions
12
+ - System capabilities (tools, permissions, platform)
13
+ - Moderation rules
14
+ - Memory guidance (see Layer 2)
15
+
16
+ This layer is static per space configuration.
17
+
18
+ ### Layer 2 — Episodic Memory (per-space, curated)
19
+
20
+ A `MEMORY.md` file that lives in the space's workspace directory (alongside `AGENTS.md`). If it exists, it is injected as `<episodic_memory>` XML at the start of every prompt.
21
+
22
+ The agent can read and write `MEMORY.md` freely. It should use it to:
23
+ - Record significant events, decisions, or patterns
24
+ - Summarise long threads into compact notes
25
+ - Remember user preferences or recurring context
26
+ - Note anything that would be annoying to re-explain each session
27
+
28
+ Keep it concise (~1500 tokens max). Use `mrctl recall` to search the full message archive when more history is needed.
29
+
30
+ ### Layer 3 — Searchable Archive (on demand)
31
+
32
+ The full message history lives in SQLite and is searchable via `mrctl recall <query>`. The agent uses this explicitly when it needs to look up something specific from the past.
33
+
34
+ The sliding window (see below) makes the most recent history available automatically — `mrctl recall` is for reaching further back.
35
+
36
+ ---
37
+
38
+ ## Per-Request Context
39
+
40
+ Every request runs with `--no-session` (no pi session file). Continuity across requests comes from:
41
+
42
+ 1. **Sliding window** — the last N user+assistant turn pairs fetched from SQLite via `getRecentTurns(spaceId, 10)`, injected as `<history>` XML
43
+ 2. **MEMORY.md** — injected as `<episodic_memory>` if it exists
44
+ 3. **Ambient messages** — platform-sourced messages (e.g., thread context) passed separately
45
+
46
+ The session boundary (`chat_state.min_message_id`) excludes messages older than the last `compact` call from the sliding window. Run `mrctl compact` to reset the boundary and start fresh.
47
+
48
+ ### Prompt structure (inside container)
49
+
50
+ ```
51
+ <system>
52
+ [identity: AGENTS.md + capabilities + memory guidance]
53
+ </system>
54
+
55
+ <caller>…</caller>
56
+ <episodic_memory>…</episodic_memory> ← MEMORY.md (if present)
57
+ <history> ← sliding window from DB
58
+ <turn timestamp="…">
59
+ <user>…</user>
60
+ <assistant>…</assistant>
61
+ </turn>
62
+
63
+ </history>
64
+ <ambient_messages>…</ambient_messages>
65
+ <preferences>…</preferences>
66
+ <attachments>…</attachments>
67
+
68
+ [user prompt text]
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Why Not a Pi Session File?
74
+
75
+ Pi session files (`.mercury.session.jsonl`) are pi's intra-run working memory — essential for tracking tool calls within a single agent run. But accumulating them across separate user requests causes problems:
76
+
77
+ - The session file grows unbounded
78
+ - Loading it on every request exposes the agent to the entire conversation history
79
+ - The agent unexpectedly references old requests
80
+
81
+ By always using `--no-session`, each run starts clean. Cross-request continuity comes from the explicit, bounded sliding window instead.
82
+
83
+ ---
84
+
85
+ ## Compact
86
+
87
+ `mrctl compact` (or `POST /api/compact`) sets the session boundary to the latest message ID. Messages older than this boundary are excluded from the sliding window, so the agent starts with a clean slate while the archive remains searchable via `mrctl recall`.
@@ -0,0 +1,199 @@
1
+ # Deployment
2
+
3
+ Mercury can run as a background daemon with automatic restart on crash.
4
+
5
+ For **environment variables vs `mercury.yaml`**, see [configuration.md](configuration.md).
6
+
7
+ ## Quick Setup
8
+
9
+ ```bash
10
+ # Install as user service (recommended)
11
+ mercury service install
12
+
13
+ # Check status
14
+ mercury service status
15
+
16
+ # View logs
17
+ mercury service logs -f
18
+
19
+ # Uninstall when needed
20
+ mercury service uninstall
21
+ ```
22
+
23
+ ## Platform Support
24
+
25
+ ### Linux (systemd)
26
+
27
+ Mercury installs as a systemd user service by default:
28
+
29
+ ```bash
30
+ # Install as user service (no sudo required)
31
+ mercury service install
32
+
33
+ # Or explicitly specify user mode
34
+ mercury service install --user
35
+ ```
36
+
37
+ The service file is written to `~/.config/systemd/user/mercury.service`.
38
+
39
+ **Manual systemd commands:**
40
+
41
+ ```bash
42
+ # Check status
43
+ systemctl --user status mercury
44
+
45
+ # Restart service
46
+ systemctl --user restart mercury
47
+
48
+ # Stop service
49
+ systemctl --user stop mercury
50
+
51
+ # View logs (follow mode)
52
+ journalctl --user -u mercury -f
53
+ ```
54
+
55
+ **User service notes:**
56
+ - No root/sudo required
57
+ - Service runs under your user account
58
+ - Starts automatically on user login
59
+ - For 24/7 operation without login, enable lingering: `loginctl enable-linger $USER`
60
+
61
+ ### macOS (launchd)
62
+
63
+ Mercury installs as a launchd user agent:
64
+
65
+ ```bash
66
+ mercury service install
67
+ ```
68
+
69
+ The plist is written to `~/Library/LaunchAgents/com.mercury.agent.plist`.
70
+
71
+ Logs are written to `.mercury/logs/` in your project directory:
72
+ - `mercury.log` — stdout
73
+ - `mercury.error.log` — stderr
74
+
75
+ **Manual launchd commands:**
76
+
77
+ ```bash
78
+ # Check if running
79
+ launchctl list com.mercury.agent
80
+
81
+ # Stop service
82
+ launchctl stop com.mercury.agent
83
+
84
+ # Start service
85
+ launchctl start com.mercury.agent
86
+
87
+ # Unload completely
88
+ launchctl unload ~/Library/LaunchAgents/com.mercury.agent.plist
89
+
90
+ # View logs
91
+ tail -f .mercury/logs/mercury.log
92
+ ```
93
+
94
+ ### Windows
95
+
96
+ Not currently supported via `mercury service`. Options:
97
+
98
+ 1. **Task Scheduler**: Create a task that runs `mercury run` at startup
99
+ 2. **NSSM**: Use [NSSM](https://nssm.cc/) to wrap Mercury as a Windows service
100
+ 3. **PM2**: Use `pm2 start "mercury run" --name mercury`
101
+
102
+ ## Auto-Restart Behavior
103
+
104
+ Both systemd and launchd are configured to automatically restart Mercury if it crashes:
105
+
106
+ - **systemd**: `Restart=on-failure` with 10-second delay
107
+ - **launchd**: `KeepAlive=true` for immediate restart
108
+
109
+ ## Working Directory
110
+
111
+ The service is configured to run from the directory where you ran `mercury service install`. This means:
112
+
113
+ - Your `.env` file is loaded from that directory
114
+ - Relative paths in configuration resolve from there
115
+ - The `.mercury/` data directory is in that location
116
+
117
+ If you move your Mercury project, you'll need to uninstall and reinstall the service.
118
+
119
+ ## Logs
120
+
121
+ ### Linux
122
+
123
+ Logs go to the systemd journal:
124
+
125
+ ```bash
126
+ # View recent logs
127
+ mercury service logs
128
+
129
+ # Follow logs in real-time
130
+ mercury service logs -f
131
+
132
+ # Or use journalctl directly
133
+ journalctl --user -u mercury -n 100
134
+ journalctl --user -u mercury --since "1 hour ago"
135
+ ```
136
+
137
+ ### macOS
138
+
139
+ Logs go to files in `.mercury/logs/`:
140
+
141
+ ```bash
142
+ # View recent logs
143
+ mercury service logs
144
+
145
+ # Follow logs in real-time
146
+ mercury service logs -f
147
+
148
+ # Or use tail directly
149
+ tail -f .mercury/logs/mercury.log
150
+ ```
151
+
152
+ ## Troubleshooting
153
+
154
+ ### Service fails to start
155
+
156
+ 1. Run `mercury doctor` to check for common issues
157
+ 2. Check that `mercury run` works manually first
158
+ 3. Verify `.env` exists and is configured
159
+ 4. Check logs for errors: `mercury service logs`
160
+
161
+ ### Permission denied (Linux)
162
+
163
+ If you see permission errors with system-level install, use user mode:
164
+
165
+ ```bash
166
+ mercury service install --user
167
+ ```
168
+
169
+ ### Service not found after reboot (Linux)
170
+
171
+ Enable user lingering so services start without login:
172
+
173
+ ```bash
174
+ loginctl enable-linger $USER
175
+ ```
176
+
177
+ ### Logs not appearing (macOS)
178
+
179
+ Check that the log directory exists:
180
+
181
+ ```bash
182
+ mkdir -p .mercury/logs
183
+ ```
184
+
185
+ Then reinstall the service:
186
+
187
+ ```bash
188
+ mercury service uninstall
189
+ mercury service install
190
+ ```
191
+
192
+ ### Multiple instances
193
+
194
+ Each Mercury project should be installed as a separate service from its own directory. The service name is always `mercury`, so only one instance can be managed per user account.
195
+
196
+ For multiple instances, consider:
197
+ - Running different instances under different user accounts
198
+ - Using Docker/Podman with separate containers
199
+ - Manual systemd service files with unique names