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.
- package/LICENSE +22 -0
- package/README.md +438 -0
- package/container/Dockerfile +127 -0
- package/container/Dockerfile.base +109 -0
- package/container/Dockerfile.power +17 -0
- package/container/agent-package.json +8 -0
- package/container/build.sh +54 -0
- package/docs/TODOS.md +147 -0
- package/docs/auth/dashboard.md +28 -0
- package/docs/auth/overview.md +109 -0
- package/docs/auth/whatsapp.md +173 -0
- package/docs/configuration.md +54 -0
- package/docs/container-lifecycle.md +349 -0
- package/docs/context-architecture.md +87 -0
- package/docs/deployment.md +199 -0
- package/docs/extensions.md +375 -0
- package/docs/graceful-shutdown.md +62 -0
- package/docs/kb-distillation.md +77 -0
- package/docs/media/overview.md +140 -0
- package/docs/media/whatsapp.md +171 -0
- package/docs/memory.md +137 -0
- package/docs/permissions.md +217 -0
- package/docs/pipeline.md +228 -0
- package/docs/prd-chat-memory.md +76 -0
- package/docs/prd-config-load.md +82 -0
- package/docs/rate-limiting.md +166 -0
- package/docs/scheduler.md +288 -0
- package/docs/setup-discord.md +100 -0
- package/docs/setup-slack.md +119 -0
- package/docs/setup-whatsapp.md +94 -0
- package/docs/subagents.md +166 -0
- package/docs/web-search.md +62 -0
- package/examples/extensions/README.md +12 -0
- package/examples/extensions/charts/index.ts +13 -0
- package/examples/extensions/charts/skill/SKILL.md +98 -0
- package/examples/extensions/gws/README.md +52 -0
- package/examples/extensions/gws/index.ts +106 -0
- package/examples/extensions/gws/skill/SKILL.md +57 -0
- package/examples/extensions/gws/skill/references/calendar.md +101 -0
- package/examples/extensions/gws/skill/references/docs.md +65 -0
- package/examples/extensions/gws/skill/references/drive.md +79 -0
- package/examples/extensions/gws/skill/references/gmail.md +85 -0
- package/examples/extensions/gws/skill/references/sheets.md +60 -0
- package/examples/extensions/napkin/index.ts +821 -0
- package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
- package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
- package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
- package/examples/extensions/napkin/skill/SKILL.md +728 -0
- package/examples/extensions/pdf/index.ts +23 -0
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
- package/examples/extensions/pdf/skill/SKILL.md +314 -0
- package/examples/extensions/pdf/skill/forms.md +294 -0
- package/examples/extensions/pdf/skill/reference.md +612 -0
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/examples/extensions/permission-guard/index.ts +65 -0
- package/examples/extensions/pinchtab/index.ts +199 -0
- package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
- package/examples/extensions/pinchtab/skill/references/api.md +297 -0
- package/examples/extensions/pinchtab/skill/references/env.md +45 -0
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
- package/examples/extensions/tradestation/host/refresh.ts +102 -0
- package/examples/extensions/tradestation/index.ts +153 -0
- package/examples/extensions/tradestation/skill/SKILL.md +67 -0
- package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
- package/examples/extensions/voice-synth/index.ts +94 -0
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
- package/examples/extensions/voice-transcribe/index.ts +381 -0
- package/examples/extensions/voice-transcribe/requirements.txt +8 -0
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
- package/examples/extensions/web-search/index.ts +22 -0
- package/examples/extensions/web-search/skill/SKILL.md +114 -0
- package/examples/extensions/web-search/skill/references/apartments.md +178 -0
- package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
- package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
- package/examples/extensions/web-search/skill/references/flights.md +133 -0
- package/examples/extensions/web-search/skill/references/hotels.md +148 -0
- package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
- package/examples/extensions/yahoo-mail/cli/package.json +13 -0
- package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
- package/examples/extensions/yahoo-mail/index.ts +57 -0
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
- package/package.json +106 -0
- package/resources/agents/explore.md +50 -0
- package/resources/agents/worker.md +24 -0
- package/resources/builtin-extensions.txt +3 -0
- package/resources/connection-env-vars.json +25 -0
- package/resources/extensions/.gitkeep +0 -0
- package/resources/pi-extensions/subagent/agents.ts +126 -0
- package/resources/pi-extensions/subagent/index.ts +964 -0
- package/resources/profiles/coding/AGENTS.md +43 -0
- package/resources/profiles/coding/mercury-profile.yaml +15 -0
- package/resources/profiles/general/AGENTS.md +31 -0
- package/resources/profiles/general/mercury-profile.yaml +15 -0
- package/resources/profiles/research/AGENTS.md +40 -0
- package/resources/profiles/research/mercury-profile.yaml +15 -0
- package/resources/skills/config/SKILL.md +25 -0
- package/resources/skills/context/SKILL.md +33 -0
- package/resources/skills/conversation-recap/SKILL.md +19 -0
- package/resources/skills/media/SKILL.md +27 -0
- package/resources/skills/mutes/SKILL.md +31 -0
- package/resources/skills/permissions/SKILL.md +19 -0
- package/resources/skills/preferences/SKILL.md +31 -0
- package/resources/skills/recall/SKILL.md +24 -0
- package/resources/skills/roles/SKILL.md +18 -0
- package/resources/skills/spaces/SKILL.md +18 -0
- package/resources/skills/tasks/SKILL.md +45 -0
- package/resources/templates/AGENTS.md +157 -0
- package/resources/templates/env.template +34 -0
- package/resources/templates/mercury.example.yaml +75 -0
- package/src/adapters/discord-native.ts +534 -0
- package/src/adapters/discord.ts +38 -0
- package/src/adapters/setup.ts +89 -0
- package/src/adapters/slack.ts +9 -0
- package/src/adapters/whatsapp-media.ts +337 -0
- package/src/adapters/whatsapp.ts +629 -0
- package/src/agent/api-socket.ts +127 -0
- package/src/agent/container-entry.ts +967 -0
- package/src/agent/container-error.ts +49 -0
- package/src/agent/container-runner.ts +1272 -0
- package/src/agent/model-capabilities-core.ts +23 -0
- package/src/agent/model-capabilities.ts +231 -0
- package/src/agent/pi-failure-class.ts +83 -0
- package/src/agent/pi-jsonl-parser.ts +306 -0
- package/src/agent/preferences-prompt.ts +20 -0
- package/src/agent/user-error-messages.ts +78 -0
- package/src/bridges/discord.ts +171 -0
- package/src/bridges/slack.ts +177 -0
- package/src/bridges/teams.ts +160 -0
- package/src/bridges/telegram.ts +571 -0
- package/src/bridges/whatsapp.ts +290 -0
- package/src/chat-shim.ts +259 -0
- package/src/cli/mercury.ts +2508 -0
- package/src/cli/mrctl-http.ts +27 -0
- package/src/cli/mrctl.ts +611 -0
- package/src/cli/whatsapp-auth.ts +260 -0
- package/src/config-file.ts +397 -0
- package/src/config-model-chain.ts +30 -0
- package/src/config.ts +316 -0
- package/src/core/api-types.ts +58 -0
- package/src/core/api.ts +105 -0
- package/src/core/commands.ts +76 -0
- package/src/core/conversation.ts +47 -0
- package/src/core/handler.ts +206 -0
- package/src/core/media.ts +200 -0
- package/src/core/mute-duration.ts +22 -0
- package/src/core/outbox.ts +76 -0
- package/src/core/permissions.ts +192 -0
- package/src/core/profiles.ts +245 -0
- package/src/core/rate-limiter.ts +127 -0
- package/src/core/router.ts +191 -0
- package/src/core/routes/chat.ts +172 -0
- package/src/core/routes/config-builtin.ts +107 -0
- package/src/core/routes/config.ts +81 -0
- package/src/core/routes/connections.ts +190 -0
- package/src/core/routes/console.ts +668 -0
- package/src/core/routes/control.ts +46 -0
- package/src/core/routes/conversations.ts +66 -0
- package/src/core/routes/dashboard.ts +2491 -0
- package/src/core/routes/extensions.ts +37 -0
- package/src/core/routes/index.ts +14 -0
- package/src/core/routes/media.ts +72 -0
- package/src/core/routes/messages.ts +37 -0
- package/src/core/routes/mutes.ts +89 -0
- package/src/core/routes/prefs.ts +95 -0
- package/src/core/routes/roles.ts +125 -0
- package/src/core/routes/spaces.ts +60 -0
- package/src/core/routes/storage.ts +126 -0
- package/src/core/routes/tasks.ts +189 -0
- package/src/core/routes/tradestation.ts +268 -0
- package/src/core/routes/tts.ts +51 -0
- package/src/core/runtime.ts +1140 -0
- package/src/core/space-queue.ts +103 -0
- package/src/core/storage-cleanup.ts +140 -0
- package/src/core/storage-guard.ts +24 -0
- package/src/core/task-scheduler.ts +132 -0
- package/src/core/telegram-format.ts +178 -0
- package/src/core/trigger.ts +142 -0
- package/src/dashboard/index.html +729 -0
- package/src/dashboard/tokens.css +53 -0
- package/src/extensions/api.ts +252 -0
- package/src/extensions/catalog.ts +117 -0
- package/src/extensions/config-registry.ts +83 -0
- package/src/extensions/context.ts +36 -0
- package/src/extensions/hooks.ts +156 -0
- package/src/extensions/image-builder.ts +617 -0
- package/src/extensions/installer.ts +306 -0
- package/src/extensions/jobs.ts +122 -0
- package/src/extensions/loader.ts +271 -0
- package/src/extensions/permission-guard.ts +52 -0
- package/src/extensions/reserved.ts +28 -0
- package/src/extensions/skills.ts +123 -0
- package/src/extensions/types.ts +462 -0
- package/src/logger.ts +174 -0
- package/src/main.ts +586 -0
- package/src/server.ts +391 -0
- package/src/storage/db.ts +1624 -0
- package/src/storage/memory.ts +45 -0
- package/src/storage/pi-auth.ts +95 -0
- package/src/text/markdown.ts +117 -0
- package/src/text/rtl.ts +38 -0
- package/src/tradestation/host-api.ts +77 -0
- package/src/tradestation/pending-orders.ts +69 -0
- package/src/tts/azure.ts +52 -0
- package/src/tts/google.ts +128 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/language.ts +20 -0
- package/src/tts/synthesize.ts +133 -0
- 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
|