mercury-agent 0.5.0 → 0.5.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 +452 -451
- package/container/Dockerfile +127 -127
- package/container/Dockerfile.base +109 -109
- package/container/Dockerfile.power +17 -17
- package/container/agent-package.json +8 -8
- package/container/build.sh +54 -54
- package/docs/ROADMAP.md +3 -4
- package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
- package/docs/auth/dashboard.md +28 -28
- package/docs/auth/overview.md +109 -109
- package/docs/auth/whatsapp.md +173 -173
- package/docs/authoring-profiles.md +174 -174
- package/docs/configuration.md +54 -54
- package/docs/container-lifecycle.md +349 -349
- package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
- package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
- package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
- package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
- package/docs/deployment.md +199 -199
- package/docs/extensions.md +375 -375
- package/docs/graceful-shutdown.md +62 -62
- package/docs/kb-distillation.md +77 -77
- package/docs/media/overview.md +140 -140
- package/docs/media/whatsapp.md +171 -171
- package/docs/memory.md +137 -137
- package/docs/permissions.md +217 -217
- package/docs/pipeline.md +228 -228
- package/docs/prd-chat-memory.md +76 -76
- package/docs/prd-config-load.md +82 -82
- package/docs/rate-limiting.md +229 -229
- package/docs/runbooks/publish-checklist.md +9 -1
- package/docs/scheduler.md +288 -288
- package/docs/setup-discord.md +100 -100
- package/docs/setup-slack.md +119 -119
- package/docs/setup-whatsapp.md +94 -94
- package/docs/subagents.md +166 -166
- package/docs/web-search.md +62 -62
- package/examples/extensions/README.md +12 -12
- package/examples/extensions/charts/index.ts +13 -13
- package/examples/extensions/charts/skill/SKILL.md +98 -98
- package/examples/extensions/gws/README.md +52 -52
- package/examples/extensions/gws/skill/SKILL.md +57 -57
- package/examples/extensions/gws/skill/references/calendar.md +101 -101
- package/examples/extensions/gws/skill/references/docs.md +65 -65
- package/examples/extensions/gws/skill/references/drive.md +79 -79
- package/examples/extensions/gws/skill/references/gmail.md +85 -85
- package/examples/extensions/gws/skill/references/sheets.md +60 -60
- package/examples/extensions/napkin/skill/SKILL.md +728 -728
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
- package/examples/extensions/pdf/skill/SKILL.md +314 -314
- package/examples/extensions/pdf/skill/forms.md +294 -294
- package/examples/extensions/pdf/skill/reference.md +611 -611
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
- package/examples/extensions/permission-guard/index.ts +65 -65
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
- package/examples/extensions/pinchtab/skill/references/api.md +297 -297
- package/examples/extensions/pinchtab/skill/references/env.md +45 -45
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
- package/examples/extensions/tradestation/host/refresh.ts +102 -102
- package/examples/extensions/tradestation/index.ts +153 -153
- package/examples/extensions/tradestation/skill/SKILL.md +67 -67
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
- package/examples/extensions/voice-transcribe/requirements.txt +8 -8
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
- package/examples/extensions/yahoo-mail/cli/package.json +13 -13
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
- package/package.json +106 -106
- package/resources/agents/explore.md +50 -50
- package/resources/agents/worker.md +24 -24
- package/resources/connection-env-vars.json +25 -25
- package/resources/pi-extensions/subagent/agents.ts +126 -126
- package/resources/pi-extensions/subagent/index.ts +964 -964
- package/resources/profiles/coding/AGENTS.md +43 -43
- package/resources/profiles/coding/mercury-profile.yaml +15 -15
- package/resources/profiles/general/AGENTS.md +31 -31
- package/resources/profiles/general/mercury-profile.yaml +15 -15
- package/resources/profiles/research/AGENTS.md +40 -40
- package/resources/profiles/research/mercury-profile.yaml +15 -15
- package/resources/skills/config/SKILL.md +25 -25
- package/resources/skills/context/SKILL.md +33 -33
- package/resources/skills/conversation-recap/SKILL.md +19 -19
- package/resources/skills/mutes/SKILL.md +31 -31
- package/resources/skills/permissions/SKILL.md +19 -19
- package/resources/skills/preferences/SKILL.md +31 -31
- package/resources/skills/recall/SKILL.md +24 -24
- package/resources/skills/roles/SKILL.md +18 -18
- package/resources/skills/spaces/SKILL.md +18 -18
- package/resources/skills/tasks/SKILL.md +45 -45
- package/resources/templates/AGENTS.md +157 -157
- package/resources/templates/env.template +38 -38
- package/resources/templates/mercury.example.yaml +99 -99
- package/src/agent/container-entry.ts +1 -1
- package/src/agent/container-runner.ts +1345 -1346
- package/src/cli/mercury.ts +39 -7
- package/src/cli/mrctl.ts +636 -636
- package/src/config-file.ts +540 -540
- package/src/config.ts +339 -339
- package/src/core/api.ts +125 -125
- package/src/core/caller-token.ts +101 -101
- package/src/core/permissions.ts +228 -228
- package/src/core/profiles.ts +271 -271
- package/src/core/routes/capability.ts +70 -70
- package/src/core/routes/index.ts +15 -15
- package/src/core/runtime.ts +1530 -1530
- package/src/dashboard/index.html +729 -729
- package/src/extensions/api.ts +273 -273
- package/src/extensions/loader.ts +286 -286
- package/src/extensions/types.ts +517 -517
- package/src/main.ts +605 -605
- package/docs/pending-updates/applicative-profiles.md +0 -15
- package/docs/pending-updates/caller-bound-capability-token.md +0 -15
- package/docs/pending-updates/dm-auto-space.md +0 -15
- /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
- /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
- /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
|
@@ -1,349 +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/avishai-tsabari/mercury-agent:latest # Full (default)
|
|
180
|
-
export MERCURY_AGENT_IMAGE=ghcr.io/avishai-tsabari/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/avishai-tsabari/mercury-agent:latest` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
|
|
208
|
-
| `ghcr.io/avishai-tsabari/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 (`@earendil-works/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 @earendil-works/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
|
-
```
|
|
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/avishai-tsabari/mercury-agent:latest # Full (default)
|
|
180
|
+
export MERCURY_AGENT_IMAGE=ghcr.io/avishai-tsabari/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/avishai-tsabari/mercury-agent:latest` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
|
|
208
|
+
| `ghcr.io/avishai-tsabari/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 (`@earendil-works/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 @earendil-works/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
|
+
```
|