mercury-agent 0.4.28 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,3 +28,7 @@
28
28
  ## Extension Points
29
29
 
30
30
  > Where the system is designed to be extended — plugin interfaces, hook points, configuration surfaces.
31
+
32
+ - **Applicative profiles** (`src/core/profiles.ts`) — a `mercury-profile.yaml` declares `capabilities`, an exhaustive `member_permissions` set, and a `system_prompt`. `applyProfile` validates capabilities and persists `.mercury/active-profile.json`; `main.ts` activates it at startup (project-wide). Member permission scoping is in `getRolePermissions` (`src/core/permissions.ts`). Authoring contract: `docs/authoring-profiles.md`.
33
+ - **Host-side capability broker** — extensions register `mercury.capability(name, handler)` (`src/extensions/types.ts`, `api.ts`); the agent invokes them from the container via `mrctl capability <name> <action>` → `POST /api/capability/:name/:action` (`src/core/routes/capability.ts`). Handlers run on the host with credentials that never enter the container; identity is the token-derived caller.
34
+ - **Caller-bound capability token** (`src/core/caller-token.ts`) — per-turn HMAC token minted at container spawn, made authoritative over spoofable identity headers in the control-plane API auth middleware (`src/core/api.ts`).
package/docs/ROADMAP.md CHANGED
@@ -10,7 +10,7 @@ Single source of truth for priority. Rule: ≤15 min/week to maintain.
10
10
 
11
11
  | Item | Doc | Notes |
12
12
  |------|-----|-------|
13
- | **business-extensions** | [doc](ideas/business-extensions.md) | Dedicated business extensions wrapping raw capabilities with deterministic logic |
13
+ | | | |
14
14
 
15
15
  ## Now
16
16
 
@@ -1,6 +1,6 @@
1
1
  # Applicative Profiles
2
2
 
3
- **Status**: Backlog
3
+ **Status**: Done
4
4
  **Slug**: applicative-profiles
5
5
  **Created**: 2026-07-01
6
6
  **Last updated**: 2026-07-01
@@ -70,7 +70,7 @@ Therefore **any credential injected into a customer's container is readable by t
70
70
  | 1. Profile schema + permission scoping | `mercury-public` | New `mercury-profile.yaml` fields; profile-aware member permissions |
71
71
  | 2. Host-side capability broker | `mercury-public` | Profile capabilities run host-side via `mrctl`→API, à la TradeStation; secrets stay on host |
72
72
  | 3. Per-turn caller token | `mercury-public` | [[caller-bound-capability-token]] — trustworthy `callerId` on the host (dependency) |
73
- | 4. Barber business logic | **private profiles repo** | Availability, 2 barbers, per-number limits, ownership rules — the moat |
73
+ | 4. Barber business logic | **private repo** `tagula-agents/tagula-agent-profiles` | Availability, 2 barbers, per-number limits, ownership rules — the moat |
74
74
 
75
75
  ### Part 1 — Schema + permission scoping
76
76
 
@@ -115,7 +115,7 @@ The broker's ownership checks require a `callerId` the customer cannot forge. To
115
115
 
116
116
  ### Part 4 — Barber business logic (private repo)
117
117
 
118
- Lives in `tagula-agent-profiles` (or similar). Contains the `mercury-profile.yaml`, the agent persona, and the host-side route implementations' business rules (availability windows, 2-barber scheduling, per-number booking limits, double-booking prevention). Consumes Parts 1-3 as the public contract; never touches `mercury-public`.
118
+ Lives in the private monorepo `Avishai-tsabari/tagula-agents` (`c:/code/agentic/tagula-agents`), under `tagula-agent-profiles/` (the repo also holds the cloud console). Checked out as a sibling of `mercury-public`. Contains the `mercury-profile.yaml`, the agent persona, and the host-side route implementations' business rules (availability windows, 2-barber scheduling, per-number booking limits, double-booking prevention). Consumes Parts 1-3 as the public contract; never touches `mercury-public`.
119
119
 
120
120
  ### Example manifest (in the private repo)
121
121
 
@@ -217,14 +217,15 @@ defaults:
217
217
  - [x] Context for Claude pointers filled in
218
218
 
219
219
  ### Implementation
220
- - [ ] Step 1 — Extend profile schema
221
- - [ ] Step 2 — Capability validation
222
- - [ ] Step 3 — Activation + config
223
- - [ ] Step 4 — Profile-aware permissions
224
- - [ ] Step 5 — Caller token dependency ([[caller-bound-capability-token]])
225
- - [ ] Step 6Broker route pattern (mirror `tradestation.ts`)
226
- - [ ] Step 7Tests
227
- - [ ] Step 8Example config
220
+ - [x] Step 1 — Extend profile schema (`capabilities`, `member_permissions`, `system_prompt`); swapped hand-rolled `parseSimpleYaml` for the `yaml` lib (multiline `system_prompt` support, less fragility)
221
+ - [x] Step 2 — Capability validation (`validateProfileCapabilities`, called in `applyProfile`); tests in `tests/applicative-profiles.test.ts`
222
+ - [x] Step 3 — Activation + config: `profile` config key; `applyProfile` persists `.mercury/active-profile.json`; `main.ts` reads it at startup (after extensions load) and calls `setActiveProfileMemberPermissions`
223
+ - [x] Step 4 — Profile-aware permissions: `getRolePermissions` uses the active profile's member set as exhaustive; precedence = explicit per-space override > profile > defaults; admin/system unaffected
224
+ - [x] Step 5 — Caller token dependency ([[caller-bound-capability-token]]) — implemented and committed
225
+ - [x] Step 5bInject `system_prompt` at container spawn (appended to `MERCURY_EXT_SYSTEM_PROMPT` in `runtime.ts`, ahead of per-space prompt)
226
+ - [x] Step 6Host-side capability broker: `mercury.capability(name, handler)` extension API; `POST /api/capability/:name/:action` route (auth = token-derived caller, authz = permission named after the capability); `registry.getCapability`; `mrctl capability <name> <action> [json]`
227
+ - [x] Step 7Tests: `tests/api.test.ts` capability broker (dispatch with token-derived caller; permission-denied → 403) + schema/scoping tests in `tests/applicative-profiles.test.ts`
228
+ - [x] Step 8 — Example config: profile + `mrctl capability` + `MERCURY_CALLER_TOKEN_KEY` notes in `mercury.example.yaml`
228
229
  - [ ] `bun run check` passes
229
230
  - [ ] No secrets or `.env` files committed
230
231
  - [ ] No unrelated files modified
@@ -243,17 +244,27 @@ defaults:
243
244
  ## Retrospective
244
245
 
245
246
  **Residual risks / follow-ups:**
246
- - Per-turn caller token is a hard dependency for safe ownership backlog/caller-bound-capability-token.md
247
- - Barber business logic tracked in the private profiles repo (out of this repo's scope) — accepted
247
+ - Barber business logic (availability, 2-barber, per-number limits) lives in the private `tagula-agents` repo out of this repo's scope — accepted
248
+ - Ephemeral caller-token key breaks `mrctl` if the API and container-runner ever run in separate processes without `MERCURY_CALLER_TOKEN_KEY` set; documented in config + `docs/authoring-profiles.md` — accepted
249
+ - `mrctl capability <name>/<action>` are not URL-encoded (path traversal only reaches other RBAC-guarded routes, no escalation) — accepted
250
+ - Duplicate capability-name registration across two extensions silently first-wins in `registry.getCapability` — accepted
251
+ - Empty `member_permissions: []` in a manifest gives members zero permissions (author's explicit exhaustive choice) — accepted
248
252
 
249
253
  **What changed from the plan:**
250
- - ...
254
+ - Signing key is NOT derived from `API_SECRET` (the plan's fallback) — `API_SECRET` is readable in the container, so an ephemeral host-only random key is used instead (config `callerTokenKey` for multi-process).
255
+ - Caller token split into its own delivered dependency (`caller-bound-capability-token`) — also hardened TradeStation, which reads identity from the same auth context (no code change needed there).
256
+ - Replaced the hand-rolled `parseSimpleYaml` in `profiles.ts` with the `yaml` lib (multiline `system_prompt` support + less fragility); built-in profiles still parse.
257
+ - Profile activation is project-wide via a module-level registry + persisted `.mercury/active-profile.json`, not per-space `space_config` writes (simpler, works for later-created dm-auto-space spaces).
258
+ - Broker registration model chosen with the user: generic `mercury.capability(name, handler)` + `POST /api/capability/:name/:action` + `mrctl capability`, authorized by the permission named after the capability.
259
+ - Added `docs/authoring-profiles.md` as the contract for the external profiles repo.
251
260
 
252
261
  **Key decisions made during implementation:**
253
- - ...
262
+ - Token = HMAC-SHA256 over `base64url(payload).base64url(sig)`, constant-time compare with length guard; TTL = container timeout + 60s.
263
+ - `getRolePermissions` uses `Array.isArray` (not `!== null`) on the active-profile member set so a malformed `active-profile.json` falls back to defaults instead of throwing in the hot path (found in session review).
264
+ - Profile schema uses plain `z.object` (no `.strict()`), so unknown manifest keys are stripped, honoring the `2026-07-01-strict-yaml-schema-crash` post-mortem (warn/ignore unknown keys, fail only on invalid values); startup `loadActiveProfile` is try/catch → null.
254
265
 
255
266
  **Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
256
- - [ ] New package/module added
257
- - [ ] New data model or schema change
258
- - [ ] New inter-package interaction
267
+ - [x] New package/module added — `src/core/caller-token.ts`, `src/core/routes/capability.ts`
268
+ - [ ] New data model or schema change (uses existing `space_config`; adds a local `.mercury/active-profile.json` file, no DB schema change)
269
+ - [x] New inter-package interaction — extension host-side capability handlers invoked via the control-plane API
259
270
  - [ ] Deployment topology changed
@@ -1,6 +1,6 @@
1
1
  # Caller-Bound Capability Token
2
2
 
3
- **Status**: Backlog
3
+ **Status**: Done
4
4
  **Slug**: caller-bound-capability-token
5
5
  **Created**: 2026-07-01
6
6
  **Last updated**: 2026-07-01
@@ -144,14 +144,14 @@ The signing key never enters the container. The container holds only its **own**
144
144
  - [x] Context for Claude pointers filled in
145
145
 
146
146
  ### Implementation
147
- - [ ] Step 1 — Token helpers
148
- - [ ] Step 2 — Mint at spawn
149
- - [ ] Step 3 — Forward in mrctl
150
- - [ ] Step 4 — Verify host-side
151
- - [ ] Step 5 — Migrate TradeStation guard
152
- - [ ] Step 6 — Tests
153
- - [ ] `bun run check` passes
154
- - [ ] No secrets committed
147
+ - [x] Step 1 — Token helpers (`src/core/caller-token.ts`)
148
+ - [x] Step 2 — Mint at spawn (`container-runner.ts`; key added to `BLOCKED_ENV_VARS`)
149
+ - [x] Step 3 — Forward in mrctl (`x-mercury-token` header)
150
+ - [x] Step 4 — Verify host-side (`api.ts` middleware; token identity wins)
151
+ - [x] Step 5 — TradeStation guard: **no change needed** — it reads `c.get("auth")`, which now derives from the token
152
+ - [x] Step 6 — Tests (`tests/caller-token.test.ts` unit; `tests/api.test.ts` integration: token overrides spoofed header, invalid token → 401)
153
+ - [x] `bun run check` passes (one unrelated discord-adapter timeout flake under full parallel run; passes in isolation)
154
+ - [x] No secrets committed
155
155
 
156
156
  ---
157
157
 
@@ -168,10 +168,14 @@ The signing key never enters the container. The container holds only its **own**
168
168
  - none
169
169
 
170
170
  **What changed from the plan:**
171
- - ...
171
+ - **Signing key is NOT derived from `API_SECRET`.** The plan suggested deriving it when unset, but `API_SECRET` is injected into containers — the agent could recompute the key and forge tokens. Instead: an optional host-only `callerTokenKey` (env `MERCURY_CALLER_TOKEN_KEY`, in `BLOCKED_ENV_VARS`), falling back to an **ephemeral random key** generated once per host process. Zero required config; nothing forgeable from inside the container.
172
+ - **TradeStation guard needed no code change** — it already reads identity from `c.get("auth")`, which the middleware now sets from the token.
173
+ - Token injected as container env `CALLER_TOKEN` (unprefixed, like `CALLER_ID`/`API_SECRET`), forwarded as `x-mercury-token`.
172
174
 
173
175
  **Key decisions made during implementation:**
174
- - ...
176
+ - HMAC-SHA256 over `base64url(payload).base64url(sig)`, payload `{c,s,exp}`; constant-time compare; no new dependency (`node:crypto`).
177
+ - TTL = container timeout + 60s buffer, so a token never expires mid-turn but stays short-lived vs the permanent shared secret.
178
+ - Header path retained as fallback → fully backward compatible; existing header-based tests and old container images keep working.
175
179
 
176
180
  **Architecture impact:**
177
181
  - [ ] New package/module added
@@ -0,0 +1,174 @@
1
+ # Authoring Applicative Profiles
2
+
3
+ The contract for building a **profile** — a package of deterministic business
4
+ logic that wraps raw capabilities (Calendar, email, …) and scopes what members
5
+ can do. Mercury (this repo) provides the schema, loader, permission scoping, and
6
+ the host-side capability broker; **profiles live in a separate repo** and depend
7
+ only on this contract.
8
+
9
+ > Layers: **Mercury** = platform (messaging, spaces, permissions, tokens,
10
+ > broker). **Capability extensions** = raw API access (e.g. `gws`), admin-only.
11
+ > **Profiles** = deterministic domain logic wrapping capabilities, member-facing.
12
+
13
+ ## 1. Profile manifest — `mercury-profile.yaml`
14
+
15
+ ```yaml
16
+ name: barber-appointments # lowercase-kebab; required
17
+ description: Appointment booking for a barber shop
18
+ version: 0.1.0
19
+
20
+ agents_md: ./AGENTS.md # optional; copied to the global AGENTS.md
21
+
22
+ # Raw capability extensions this profile requires to be installed. Validated at
23
+ # apply time — activation fails loudly if any are missing.
24
+ capabilities:
25
+ - gws
26
+
27
+ # Extensions this profile ships (copied into .mercury/extensions).
28
+ extensions:
29
+ - name: barber
30
+ source: ./extensions/barber
31
+
32
+ # EXHAUSTIVE member permission set while active. When present it REPLACES the
33
+ # default member permissions — nothing is merged in — so raw capabilities stay
34
+ # admin-only unless listed here. Omit entirely to keep built-in defaults.
35
+ member_permissions:
36
+ - prompt
37
+ - prefs.get
38
+ - barber # the profile's own capability, NOT gws
39
+
40
+ # Optional env the profile needs (host-side credentials for capabilities).
41
+ env:
42
+ - key: MERCURY_BARBER_BUSINESS_HOURS
43
+ description: "Business hours"
44
+ default: "09:00-18:00"
45
+
46
+ # Project-wide agent persona, injected into every container's system prompt.
47
+ system_prompt: |
48
+ You are a barber shop assistant. Help each customer book, view, and cancel
49
+ ONLY their own appointments. Never reveal other customers' schedules.
50
+
51
+ defaults:
52
+ trigger_patterns: always
53
+ ```
54
+
55
+ Apply it with `mercury profile apply <name|path|git-url>`. That validates
56
+ capabilities, copies extensions/AGENTS.md, and persists activation to
57
+ `.mercury/active-profile.json`, which Mercury loads at startup.
58
+
59
+ ## 2. The capability broker (where the business logic runs)
60
+
61
+ A profile's privileged work runs **on the host**, so credentials never enter the
62
+ customer-controlled agent container. The profile's extension registers a
63
+ handler; the agent invokes it through a thin CLI.
64
+
65
+ ### Register a handler (in the extension's `index.ts`)
66
+
67
+ ```ts
68
+ import type { ExtensionSetupFn } from "mercury-agent"; // types from this repo
69
+
70
+ const setup: ExtensionSetupFn = (mercury) => {
71
+ // Make the capability gate-able. Members get it via member_permissions.
72
+ mercury.permission({ defaultRoles: [] });
73
+
74
+ // Host-side credentials for the wrapped capability (never sent to the container).
75
+ mercury.env({ from: "MERCURY_BARBER_GOOGLE_CREDENTIALS" });
76
+
77
+ mercury.capability("barber", async (req, ctx) => {
78
+ // req.callerId is the TOKEN-DERIVED, unspoofable caller — safe for ownership.
79
+ switch (req.action) {
80
+ case "availability":
81
+ return { data: await listOpenSlots(req.body, ctx) };
82
+ case "book":
83
+ return { data: await book(req.callerId, req.body, ctx) };
84
+ case "cancel":
85
+ return { data: await cancelOwn(req.callerId, req.body, ctx) };
86
+ case "my-appointments":
87
+ return { data: await listOwn(req.callerId, ctx) };
88
+ default:
89
+ return { status: 400, data: { error: `unknown action: ${req.action}` } };
90
+ }
91
+ });
92
+ };
93
+
94
+ export default setup;
95
+ ```
96
+
97
+ ### The contract types (exported from this repo)
98
+
99
+ ```ts
100
+ type CapabilityHandler = (
101
+ req: CapabilityRequest,
102
+ ctx: MercuryExtensionContext,
103
+ ) => Promise<CapabilityResult>;
104
+
105
+ interface CapabilityRequest {
106
+ name: string; // capability name
107
+ action: string; // sub-action (book, cancel, …)
108
+ callerId: string; // token-derived, TRUSTWORTHY — use for ownership checks
109
+ spaceId: string;
110
+ body: unknown; // parsed JSON request body (or null)
111
+ }
112
+
113
+ interface CapabilityResult {
114
+ status?: number; // default 200
115
+ data: unknown; // JSON-serializable response
116
+ }
117
+ ```
118
+
119
+ `ctx` (`MercuryExtensionContext`) gives you `db`, `config`, `log`, and
120
+ `hasCallerPermission(spaceId, callerId, permission)`. Store per-profile state via
121
+ `mercury.store` (namespaced KV) or your own tables through `ctx.db`.
122
+
123
+ ### How the agent invokes it
124
+
125
+ Inside the container the agent runs:
126
+
127
+ ```
128
+ mrctl capability barber book '{"slot":"2026-07-02T15:00"}'
129
+ ```
130
+
131
+ → `POST /api/capability/barber/book`. Document these commands for the agent in
132
+ the extension's `SKILL.md` or the profile's `AGENTS.md`.
133
+
134
+ ## 3. Permission & security model (non-negotiable)
135
+
136
+ - **`member_permissions` is exhaustive.** List every permission a member may
137
+ hold, including the capability name (e.g. `barber`). Anything not listed —
138
+ including raw capabilities like `gws` — is unavailable to members.
139
+ - **Authorization = permission named after the capability.** The broker route
140
+ requires the caller to hold the `<name>` permission; the same grant gates both
141
+ the `mrctl capability <name> …` CLI and the route. Keep capability name ==
142
+ permission name == the entry in `member_permissions`.
143
+ - **Credentials stay on the host.** Put capability credentials in host env
144
+ (`mercury.env`) / `mercury.store` and use them only inside the handler. Never
145
+ expose a raw capability CLI (e.g. `gws`) to members — its secret would be
146
+ readable inside the container.
147
+ - **Enforce ownership with `req.callerId`.** It is token-derived and cannot be
148
+ spoofed by the container. Never trust an id passed in `req.body`.
149
+ - **`callerId === "system"`** for scheduled/system runs — handle it explicitly
150
+ (e.g. skip per-customer ownership).
151
+
152
+ ## 4. Suggested profile repo layout
153
+
154
+ ```
155
+ <profile-repo>/
156
+ barber/ # one folder per profile
157
+ mercury-profile.yaml
158
+ AGENTS.md
159
+ extensions/
160
+ barber/
161
+ index.ts # registers permission + capability
162
+ package.json
163
+ ```
164
+
165
+ Depend on this repo (`mercury-agent`) for the types
166
+ (`ExtensionSetupFn`, `CapabilityRequest`, `CapabilityResult`,
167
+ `MercuryExtensionContext`). See `src/extensions/types.ts` for the full surface.
168
+
169
+ ## 5. Local test loop
170
+
171
+ 1. `mercury profile apply ./barber`
172
+ 2. `mercury service install` (builds the derived image with the extension CLI)
173
+ 3. DM the bot as a non-admin number; confirm you can `book`/`cancel` only your
174
+ own appointments and cannot reach `gws`/Gmail.
@@ -0,0 +1,15 @@
1
+ ---
2
+ slug: applicative-profiles
3
+ shipped: 2026-07-01
4
+ ---
5
+
6
+ ## ROADMAP changes
7
+
8
+ **Remove from:** Next (table row where Item = applicative-profiles)
9
+
10
+ **Also pull:** none
11
+
12
+ **Add to Shipped table** (insert at top, after header row):
13
+ | **applicative-profiles** | 2026-07-01 | Generic profile layer: manifest schema, exhaustive member scoping, host-side capability broker |
14
+
15
+ **Milestone Map:** none
@@ -0,0 +1,15 @@
1
+ ---
2
+ slug: caller-bound-capability-token
3
+ shipped: 2026-07-01
4
+ ---
5
+
6
+ ## ROADMAP changes
7
+
8
+ **Remove from:** Next (table row where Item = caller-bound-capability-token)
9
+
10
+ **Also pull:** none
11
+
12
+ **Add to Shipped table** (insert at top, after header row):
13
+ | **caller-bound-capability-token** | 2026-07-01 | Per-turn unspoofable caller token for host API auth; also hardens TradeStation |
14
+
15
+ **Milestone Map:** none
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.4.28",
3
+ "version": "0.5.0",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",
@@ -1,84 +1,99 @@
1
- # Copy to mercury.yaml in your project root (optional).
2
- # Precedence: MERCURY_* env vars override this file when the env key is set.
3
- # Secrets (API keys, tokens, MERCURY_API_SECRET, etc.) belong in .env only — not here.
4
-
5
- # server:
6
- # port: 8787
7
- # bot_username: mercury
8
-
9
- # model:
10
- # chain:
11
- # - provider: anthropic
12
- # model: claude-sonnet-4-20250514
13
- # # Legacy single primary + optional fallback (when chain is omitted):
14
- # # provider: anthropic
15
- # # model: claude-sonnet-4-20250514
16
- # # fallback_provider: openai
17
- # # fallback: gpt-4o-mini
18
- # # max_retries_per_leg: 2
19
- # # chain_budget_ms: 120000
20
- # # capabilities: { tools: false }
21
-
22
- # Top-level alias for model.chain (optional; model.chain wins if both are set):
23
- # model_chain:
24
- # - provider: google
25
- # model: gemini-2.5-flash
26
-
27
- # ingress:
28
- # discord: false
29
- # slack: false
30
- # teams: false
31
- # whatsapp: false
32
- # telegram: false
33
-
34
- # runtime:
35
- # data_dir: .mercury
36
- # # auth_path: ...
37
- # # whatsapp_auth_dir: .mercury/whatsapp-auth
38
- # max_concurrency: 2
39
- # log_level: info
40
- # log_format: text
41
- # rate_limit_per_user: 10
42
- # rate_limit_window_ms: 60000
43
- # rate_limit_daily_member: 0 # 0 = unlimited; global daily message cap for members
44
- # rate_limit_daily_admin: 0 # 0 = unlimited; global daily message cap for admins
45
-
46
- # trigger:
47
- # patterns: "@Mercury,Mercury"
48
- # match: mention
49
-
50
- # conditional_context:
51
- # enabled: true
52
- # classifier: heuristic
53
- # # classifier_provider: groq
54
- # # classifier_model: llama-3.3-70b-versatile
55
-
56
- # compaction:
57
- # keep_recent_tokens: 20000
58
- # # After each full-session run, auto-compact if the pi session has more than this many entries (10–10000). Omit to disable.
59
- # # auto_compact_threshold: 50
60
-
61
- # agent:
62
- # image: ghcr.io/avishai-tsabari/mercury-agent:latest
63
- # container_timeout_ms: 300000
64
- # container_bwrap_docker_compat: false
65
-
66
- # discord:
67
- # gateway_duration_ms: 600000
68
-
69
- # telegram:
70
- # format_enabled: true
71
-
72
- # media:
73
- # enabled: true
74
- # max_size_mb: 10
75
-
76
- # permissions:
77
- # admins: ""
78
-
79
- # dm_auto_space:
80
- # enabled: false
81
- # admin_ids: # auto-link to "main" space
82
- # - "972501234567"
83
- # default_system_prompt: "" # seeded into auto-created customer spaces
84
- # default_member_permissions: "prompt,prefs.get" # restrict customers to chat only
1
+ # Copy to mercury.yaml in your project root (optional).
2
+ # Precedence: MERCURY_* env vars override this file when the env key is set.
3
+ # Secrets (API keys, tokens, MERCURY_API_SECRET, etc.) belong in .env only — not here.
4
+
5
+ # server:
6
+ # port: 8787
7
+ # bot_username: mercury
8
+
9
+ # model:
10
+ # chain:
11
+ # - provider: anthropic
12
+ # model: claude-sonnet-4-20250514
13
+ # # Legacy single primary + optional fallback (when chain is omitted):
14
+ # # provider: anthropic
15
+ # # model: claude-sonnet-4-20250514
16
+ # # fallback_provider: openai
17
+ # # fallback: gpt-4o-mini
18
+ # # max_retries_per_leg: 2
19
+ # # chain_budget_ms: 120000
20
+ # # capabilities: { tools: false }
21
+
22
+ # Top-level alias for model.chain (optional; model.chain wins if both are set):
23
+ # model_chain:
24
+ # - provider: google
25
+ # model: gemini-2.5-flash
26
+
27
+ # ingress:
28
+ # discord: false
29
+ # slack: false
30
+ # teams: false
31
+ # whatsapp: false
32
+ # telegram: false
33
+
34
+ # runtime:
35
+ # data_dir: .mercury
36
+ # # auth_path: ...
37
+ # # whatsapp_auth_dir: .mercury/whatsapp-auth
38
+ # max_concurrency: 2
39
+ # log_level: info
40
+ # log_format: text
41
+ # rate_limit_per_user: 10
42
+ # rate_limit_window_ms: 60000
43
+ # rate_limit_daily_member: 0 # 0 = unlimited; global daily message cap for members
44
+ # rate_limit_daily_admin: 0 # 0 = unlimited; global daily message cap for admins
45
+
46
+ # trigger:
47
+ # patterns: "@Mercury,Mercury"
48
+ # match: mention
49
+
50
+ # conditional_context:
51
+ # enabled: true
52
+ # classifier: heuristic
53
+ # # classifier_provider: groq
54
+ # # classifier_model: llama-3.3-70b-versatile
55
+
56
+ # compaction:
57
+ # keep_recent_tokens: 20000
58
+ # # After each full-session run, auto-compact if the pi session has more than this many entries (10–10000). Omit to disable.
59
+ # # auto_compact_threshold: 50
60
+
61
+ # agent:
62
+ # image: ghcr.io/avishai-tsabari/mercury-agent:latest
63
+ # container_timeout_ms: 300000
64
+ # container_bwrap_docker_compat: false
65
+
66
+ # discord:
67
+ # gateway_duration_ms: 600000
68
+
69
+ # telegram:
70
+ # format_enabled: true
71
+
72
+ # media:
73
+ # enabled: true
74
+ # max_size_mb: 10
75
+
76
+ # permissions:
77
+ # admins: ""
78
+
79
+ # dm_auto_space:
80
+ # enabled: false
81
+ # admin_ids: # auto-link to "main" space
82
+ # - "972501234567"
83
+ # default_system_prompt: "" # seeded into auto-created customer spaces
84
+ # default_member_permissions: "prompt,prefs.get" # restrict customers to chat only
85
+
86
+ # ─── Applicative profile ────────────────────────────────────────────────────
87
+ # A profile wraps raw capabilities with deterministic business logic and scopes
88
+ # what members may do. Apply one with `mercury profile apply <name|path|git-url>`
89
+ # (it persists to .mercury/active-profile.json and is loaded at startup).
90
+ # The active profile's member_permissions become the EXHAUSTIVE member set, and
91
+ # its capabilities run host-side via `mrctl capability <name> <action>` so
92
+ # credentials never enter the agent container.
93
+ #
94
+ # Env-only override of the active profile name (informational):
95
+ # MERCURY_PROFILE=barber-appointments
96
+ #
97
+ # Host-only signing key for per-turn caller tokens (auto-generated if unset;
98
+ # only set when the API and container-runner run in separate processes):
99
+ # MERCURY_CALLER_TOKEN_KEY=<random-secret>