@mono-agent/agent-app 0.15.3 → 0.16.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.
Files changed (97) hide show
  1. package/README.md +13 -0
  2. package/dist/app-controller-responder.d.ts.map +1 -1
  3. package/dist/app-controller-responder.js +9 -7
  4. package/dist/app-controller-responder.js.map +1 -1
  5. package/dist/channel-drivers/native-notify.d.ts.map +1 -1
  6. package/dist/channel-drivers/native-notify.js +18 -8
  7. package/dist/channel-drivers/native-notify.js.map +1 -1
  8. package/dist/channel-drivers/slack.d.ts.map +1 -1
  9. package/dist/channel-drivers/slack.js +12 -0
  10. package/dist/channel-drivers/slack.js.map +1 -1
  11. package/dist/config-reference.d.ts.map +1 -1
  12. package/dist/config-reference.js +126 -2
  13. package/dist/config-reference.js.map +1 -1
  14. package/dist/configured-agent.d.ts.map +1 -1
  15. package/dist/configured-agent.js +265 -6
  16. package/dist/configured-agent.js.map +1 -1
  17. package/dist/doctor.d.ts.map +1 -1
  18. package/dist/doctor.js +187 -0
  19. package/dist/doctor.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/catalog.js +1 -1
  25. package/dist/modules/catalog.js.map +1 -1
  26. package/dist/modules/known-tools.d.ts +1 -1
  27. package/dist/modules/known-tools.d.ts.map +1 -1
  28. package/dist/modules/known-tools.js +4 -0
  29. package/dist/modules/known-tools.js.map +1 -1
  30. package/dist/pi-oauth-login.d.ts +2 -2
  31. package/dist/pi-oauth-login.d.ts.map +1 -1
  32. package/dist/pi-oauth-login.js +49 -23
  33. package/dist/pi-oauth-login.js.map +1 -1
  34. package/dist/provider-setup.d.ts.map +1 -1
  35. package/dist/provider-setup.js +5 -2
  36. package/dist/provider-setup.js.map +1 -1
  37. package/dist/run-history.js +1 -0
  38. package/dist/run-history.js.map +1 -1
  39. package/dist/runtime-routes.d.ts +10 -0
  40. package/dist/runtime-routes.d.ts.map +1 -1
  41. package/dist/runtime-routes.js +12 -0
  42. package/dist/runtime-routes.js.map +1 -1
  43. package/dist/wizard/model-discovery.d.ts.map +1 -1
  44. package/dist/wizard/model-discovery.js +3 -2
  45. package/dist/wizard/model-discovery.js.map +1 -1
  46. package/dist/wizard/prompts.d.ts.map +1 -1
  47. package/dist/wizard/prompts.js +1 -0
  48. package/dist/wizard/prompts.js.map +1 -1
  49. package/dist/wizard/run.js +1 -1
  50. package/dist/wizard/run.js.map +1 -1
  51. package/package.json +16 -16
  52. package/schema/mono-agent.config.schema.json +297 -0
  53. package/skills/mono-agent-composer/references/config-blueprint.md +13 -3
  54. package/skills/mono-agent-composer/references/discovery-questions.md +12 -1
  55. package/skills/mono-agent-composer/references/feature-coverage.md +6 -1
  56. package/skills/mono-agent-composer/references/playbooks.md +25 -2
  57. package/skills/mono-agent-composer/references/validation.md +1 -0
  58. package/dist/broadcast-recorder.d.ts +0 -27
  59. package/dist/broadcast-recorder.d.ts.map +0 -1
  60. package/dist/broadcast-recorder.js +0 -104
  61. package/dist/broadcast-recorder.js.map +0 -1
  62. package/dist/channel-drivers/live.d.ts +0 -12
  63. package/dist/channel-drivers/live.d.ts.map +0 -1
  64. package/dist/channel-drivers/live.js +0 -48
  65. package/dist/channel-drivers/live.js.map +0 -1
  66. package/dist/notify-runtime.d.ts +0 -26
  67. package/dist/notify-runtime.d.ts.map +0 -1
  68. package/dist/notify-runtime.js +0 -27
  69. package/dist/notify-runtime.js.map +0 -1
  70. package/dist/notify-tool.d.ts +0 -51
  71. package/dist/notify-tool.d.ts.map +0 -1
  72. package/dist/notify-tool.js +0 -182
  73. package/dist/notify-tool.js.map +0 -1
  74. package/dist/recipes/base.d.ts +0 -15
  75. package/dist/recipes/base.d.ts.map +0 -1
  76. package/dist/recipes/base.js +0 -51
  77. package/dist/recipes/base.js.map +0 -1
  78. package/dist/recipes/catalog.d.ts +0 -4
  79. package/dist/recipes/catalog.d.ts.map +0 -1
  80. package/dist/recipes/catalog.js +0 -525
  81. package/dist/recipes/catalog.js.map +0 -1
  82. package/dist/recipes/index.d.ts +0 -11
  83. package/dist/recipes/index.d.ts.map +0 -1
  84. package/dist/recipes/index.js +0 -14
  85. package/dist/recipes/index.js.map +0 -1
  86. package/dist/recipes/types.d.ts +0 -70
  87. package/dist/recipes/types.d.ts.map +0 -1
  88. package/dist/recipes/types.js +0 -15
  89. package/dist/recipes/types.js.map +0 -1
  90. package/dist/sessions-command.d.ts +0 -53
  91. package/dist/sessions-command.d.ts.map +0 -1
  92. package/dist/sessions-command.js +0 -309
  93. package/dist/sessions-command.js.map +0 -1
  94. package/dist/setup.d.ts +0 -29
  95. package/dist/setup.d.ts.map +0 -1
  96. package/dist/setup.js +0 -97
  97. package/dist/setup.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-app",
3
- "version": "0.15.3",
3
+ "version": "0.16.0",
4
4
  "description": "Config-first mono-agent host: builds a responder and starts every configured communication channel and traceability from one mono-agent.config.json.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -35,22 +35,22 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@clack/prompts": "^1.7.0",
38
- "@earendil-works/pi-ai": "0.80.6",
38
+ "@earendil-works/pi-ai": "0.83.0",
39
39
  "@modelcontextprotocol/sdk": "^1.29.0",
40
- "@mono-agent/agent-contracts": "0.15.3",
41
- "@mono-agent/agent-harness": "0.15.3",
42
- "@mono-agent/config": "0.15.3",
43
- "@mono-agent/cron-adapter": "0.15.3",
44
- "@mono-agent/operator-adapter": "0.15.3",
45
- "@mono-agent/memory": "0.15.3",
46
- "@mono-agent/observability": "0.15.3",
47
- "@mono-agent/openai-api-adapter": "0.15.3",
48
- "@mono-agent/runtime-adapter": "0.15.3",
49
- "@mono-agent/slack-adapter": "0.15.3",
50
- "@mono-agent/telegram-adapter": "0.15.3",
51
- "@mono-agent/tui": "0.15.3",
52
- "@mono-agent/web": "0.15.3",
53
- "@mono-agent/webhook-adapter": "0.15.3",
40
+ "@mono-agent/agent-contracts": "0.16.0",
41
+ "@mono-agent/agent-harness": "0.16.0",
42
+ "@mono-agent/config": "0.16.0",
43
+ "@mono-agent/cron-adapter": "0.16.0",
44
+ "@mono-agent/operator-adapter": "0.16.0",
45
+ "@mono-agent/memory": "0.16.0",
46
+ "@mono-agent/observability": "0.16.0",
47
+ "@mono-agent/openai-api-adapter": "0.16.0",
48
+ "@mono-agent/runtime-adapter": "0.16.0",
49
+ "@mono-agent/slack-adapter": "0.16.0",
50
+ "@mono-agent/telegram-adapter": "0.16.0",
51
+ "@mono-agent/tui": "0.16.0",
52
+ "@mono-agent/web": "0.16.0",
53
+ "@mono-agent/webhook-adapter": "0.16.0",
54
54
  "undici": "8.7.0",
55
55
  "zod": "^4.4.3"
56
56
  },
@@ -89,11 +89,52 @@
89
89
  "max",
90
90
  "ultra"
91
91
  ]
92
+ },
93
+ "attempts": {
94
+ "type": "integer",
95
+ "minimum": 1,
96
+ "maximum": 10
92
97
  }
93
98
  }
94
99
  },
95
100
  "default": []
96
101
  },
102
+ "retry": {
103
+ "type": "object",
104
+ "additionalProperties": false,
105
+ "properties": {
106
+ "primaryAttempts": {
107
+ "description": "Total attempts on runtime.model including the first, before the chain advances. Retries fire only for transient provider failures (overloaded, rate-limited, timeout, network, 5xx); context overflow and bad credentials advance immediately. Set 1 to disable.",
108
+ "examples": [
109
+ 3
110
+ ],
111
+ "type": "integer",
112
+ "default": 2,
113
+ "minimum": 1,
114
+ "maximum": 10
115
+ },
116
+ "backoffMs": {
117
+ "description": "Delay before the first same-model retry. Doubles on each further retry, capped by runtime.retry.maxBackoffMs.",
118
+ "examples": [
119
+ 2000
120
+ ],
121
+ "type": "integer",
122
+ "default": 1000,
123
+ "minimum": 0,
124
+ "maximum": 60000
125
+ },
126
+ "maxBackoffMs": {
127
+ "description": "Ceiling for the doubling same-model retry delay.",
128
+ "examples": [
129
+ 30000
130
+ ],
131
+ "type": "integer",
132
+ "default": 15000,
133
+ "minimum": 0,
134
+ "maximum": 300000
135
+ }
136
+ }
137
+ },
97
138
  "routeSafety": {
98
139
  "description": "Uniform preserves one shared safety contract; per-route-native uses and reports each provider's explicit contract.",
99
140
  "examples": [
@@ -279,6 +320,150 @@
279
320
  "model"
280
321
  ]
281
322
  },
323
+ "subagents": {
324
+ "description": "Subagent profiles the Agent tool can deploy, plus its caps. Disabled unless enabled is true, in which case Agent must also appear in tools.allowedTools. Each definition needs exactly one of prompt or promptPath; omitted allowedTools means a read-only default set, and the \"*\" wildcard is rejected. The agent may also author a specialized subagent at call time unless inline.enabled is false; inline.allowedTools caps what an authored subagent may request, defaulting to the parent agent's own built-ins.",
325
+ "examples": [
326
+ {
327
+ "enabled": true,
328
+ "maxConcurrent": 5,
329
+ "definitions": [
330
+ {
331
+ "name": "researcher",
332
+ "description": "Reads code and docs to answer a factual question about the codebase. Read-only.",
333
+ "prompt": "You are a codebase researcher. Answer with file:line citations. Never modify files.",
334
+ "allowedTools": [
335
+ "Read",
336
+ "Glob",
337
+ "Grep"
338
+ ]
339
+ }
340
+ ]
341
+ }
342
+ ],
343
+ "type": "object",
344
+ "additionalProperties": false,
345
+ "properties": {
346
+ "enabled": {
347
+ "type": "boolean"
348
+ },
349
+ "maxConcurrent": {
350
+ "type": "integer",
351
+ "minimum": 1,
352
+ "maximum": 10
353
+ },
354
+ "maxPerTurn": {
355
+ "type": "integer",
356
+ "minimum": 1,
357
+ "maximum": 200
358
+ },
359
+ "timeoutMs": {
360
+ "type": "integer",
361
+ "minimum": 1000,
362
+ "maximum": 3600000
363
+ },
364
+ "maxTurns": {
365
+ "type": "integer",
366
+ "minimum": 1,
367
+ "maximum": 200
368
+ },
369
+ "definitions": {
370
+ "type": "array",
371
+ "items": {
372
+ "type": "object",
373
+ "additionalProperties": false,
374
+ "required": [
375
+ "name",
376
+ "description"
377
+ ],
378
+ "properties": {
379
+ "name": {
380
+ "type": "string",
381
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
382
+ },
383
+ "description": {
384
+ "type": "string",
385
+ "minLength": 1
386
+ },
387
+ "prompt": {
388
+ "type": "string",
389
+ "minLength": 1
390
+ },
391
+ "promptPath": {
392
+ "type": "string",
393
+ "minLength": 1
394
+ },
395
+ "model": {
396
+ "type": "string",
397
+ "minLength": 1
398
+ },
399
+ "effort": {
400
+ "type": "string",
401
+ "enum": [
402
+ "none",
403
+ "minimal",
404
+ "low",
405
+ "medium",
406
+ "high",
407
+ "xhigh",
408
+ "max",
409
+ "ultra"
410
+ ]
411
+ },
412
+ "allowedTools": {
413
+ "type": "array",
414
+ "items": {
415
+ "type": "string",
416
+ "minLength": 1
417
+ }
418
+ },
419
+ "disallowedTools": {
420
+ "type": "array",
421
+ "items": {
422
+ "type": "string",
423
+ "minLength": 1
424
+ }
425
+ },
426
+ "mcpServers": {
427
+ "type": "array",
428
+ "items": {
429
+ "type": "string",
430
+ "minLength": 1
431
+ }
432
+ },
433
+ "maxTurns": {
434
+ "type": "integer",
435
+ "minimum": 1,
436
+ "maximum": 200
437
+ },
438
+ "timeoutMs": {
439
+ "type": "integer",
440
+ "minimum": 1000,
441
+ "maximum": 3600000
442
+ }
443
+ }
444
+ }
445
+ },
446
+ "inline": {
447
+ "type": "object",
448
+ "additionalProperties": false,
449
+ "properties": {
450
+ "enabled": {
451
+ "type": "boolean"
452
+ },
453
+ "allowedTools": {
454
+ "type": "array",
455
+ "items": {
456
+ "type": "string",
457
+ "minLength": 1
458
+ }
459
+ }
460
+ }
461
+ }
462
+ },
463
+ "default": {
464
+ "enabled": false
465
+ }
466
+ },
282
467
  "concurrency": {
283
468
  "type": "object",
284
469
  "additionalProperties": false,
@@ -906,6 +1091,64 @@
906
1091
  ],
907
1092
  "type": "integer",
908
1093
  "default": 2700000
1094
+ },
1095
+ "web": {
1096
+ "type": "object",
1097
+ "additionalProperties": false,
1098
+ "properties": {
1099
+ "search": {
1100
+ "type": "object",
1101
+ "additionalProperties": false,
1102
+ "properties": {
1103
+ "backend": {
1104
+ "description": "WebSearch backend: auto tries a configured local SearXNG endpoint then keyless fallbacks; searxng is strict; keyless skips SearXNG.",
1105
+ "examples": [
1106
+ "auto"
1107
+ ],
1108
+ "type": "string",
1109
+ "default": "auto",
1110
+ "enum": [
1111
+ "auto",
1112
+ "searxng",
1113
+ "keyless"
1114
+ ]
1115
+ },
1116
+ "endpoint": {
1117
+ "description": "Optional unauthenticated loopback HTTP SearXNG base URL. Remote HTTPS, credentials, query strings, and fragments are rejected.",
1118
+ "examples": [
1119
+ "http://127.0.0.1:8088"
1120
+ ],
1121
+ "type": "string"
1122
+ }
1123
+ }
1124
+ },
1125
+ "fetch": {
1126
+ "type": "object",
1127
+ "additionalProperties": false,
1128
+ "properties": {
1129
+ "render": {
1130
+ "description": "Browser-render capability for sparse JavaScript pages. never forces every call to static extraction; auto permits an isolated agent-browser session when needed.",
1131
+ "examples": [
1132
+ "never"
1133
+ ],
1134
+ "type": "string",
1135
+ "default": "never",
1136
+ "enum": [
1137
+ "never",
1138
+ "auto"
1139
+ ]
1140
+ },
1141
+ "browserCommand": {
1142
+ "description": "Direct executable name or path for agent-browser; shell fragments are not evaluated.",
1143
+ "examples": [
1144
+ "agent-browser"
1145
+ ],
1146
+ "type": "string",
1147
+ "default": "agent-browser"
1148
+ }
1149
+ }
1150
+ }
1151
+ }
909
1152
  }
910
1153
  }
911
1154
  },
@@ -2081,6 +2324,60 @@
2081
2324
  ],
2082
2325
  "type": "boolean"
2083
2326
  },
2327
+ "resolveUserNames": {
2328
+ "description": "Resolves the speaker's display name and handle via `users.info` so the agent knows who is talking. Requires the `users:read` scope; a missing scope degrades to an unnamed speaker rather than failing turns.",
2329
+ "examples": [
2330
+ true
2331
+ ],
2332
+ "type": "boolean",
2333
+ "default": true
2334
+ },
2335
+ "threadContext": {
2336
+ "type": "object",
2337
+ "additionalProperties": false,
2338
+ "properties": {
2339
+ "enabled": {
2340
+ "description": "Sends what was said in the conversation before the agent was triggered as untrusted background context. Reads the thread for an in-thread trigger and recent channel history otherwise; needs a `*:history` scope.",
2341
+ "examples": [
2342
+ true
2343
+ ],
2344
+ "type": "boolean",
2345
+ "default": true
2346
+ },
2347
+ "maxMessages": {
2348
+ "description": "Messages of context sent per turn, newest kept. `0` disables the read. Capped at the harness's own 30-message bound.",
2349
+ "examples": [
2350
+ 15
2351
+ ],
2352
+ "type": "integer",
2353
+ "default": 15
2354
+ },
2355
+ "requestLimit": {
2356
+ "description": "Objects requested from Slack per read. Slack caps this at 15 for non-Marketplace apps; internal apps can raise it.",
2357
+ "examples": [
2358
+ 15
2359
+ ],
2360
+ "type": "integer",
2361
+ "default": 15
2362
+ },
2363
+ "timeoutMs": {
2364
+ "description": "Budget for the whole context phase, including name resolution. Exceeding it submits the turn with less context rather than delaying it. `0` bounds the phase only by the turn itself.",
2365
+ "examples": [
2366
+ 4000
2367
+ ],
2368
+ "type": "integer",
2369
+ "default": 4000
2370
+ },
2371
+ "includeBotMessages": {
2372
+ "description": "Include other apps' messages (a CI or alert bot) labelled as bots. The agent's own posts are always excluded.",
2373
+ "examples": [
2374
+ true
2375
+ ],
2376
+ "type": "boolean",
2377
+ "default": true
2378
+ }
2379
+ }
2380
+ },
2084
2381
  "heartbeatIntervalMs": {
2085
2382
  "description": "Configures heartbeatIntervalMs for the slack section.",
2086
2383
  "examples": [
@@ -152,11 +152,21 @@ new agent.
152
152
  "tools": {
153
153
  "allowedTools": ["*"], // omit or include "*" = all tools; ["Read","Bash"] = just those; [] = none (chat-only)
154
154
  "disallowedTools": ["Bash"], // deny wins even under allow-all; the escape hatch to subtract one tool
155
- "mcpConfigPath": "./mcp.json" // stdio/sse/http servers; inlined for SDK runtimes
155
+ "mcpConfigPath": "./mcp.json", // stdio/sse/http servers; inlined for SDK runtimes
156
+ "web": {
157
+ "search": {
158
+ "backend": "auto", // auto | searxng (strict) | keyless
159
+ "endpoint": "http://127.0.0.1:8088" // optional unauthenticated loopback HTTP SearXNG base URL
160
+ },
161
+ "fetch": {
162
+ "render": "never", // never disables browser capability; auto is static-first
163
+ "browserCommand": "agent-browser" // direct executable name/path
164
+ }
165
+ }
156
166
  },
157
167
 
158
- // NodeRepl shares state only inside one run and uses the same sandbox policy
159
- // as Bash.
168
+ // Exec is direct argv; use Bash only for shell syntax. NodeRepl shares state
169
+ // only inside one run. All three use the same sandbox policy.
160
170
 
161
171
  // Human-in-the-loop bridge: structured blocking AskUser plus
162
172
  // run-scoped project-MCP progress. It auto-starts when either ask tool is
@@ -79,7 +79,18 @@ Plus, independently: MCP servers from an mcp.json config file?
79
79
 
80
80
  Fills: `tools.allowedTools`, `tools.disallowedTools` (denylist wins, even under allow-all), `tools.mcpConfigPath`. The default is allow-all (`["*"]`) — write that unless the user asks to narrow. Under allow-all the adapter-derived send tools (`SlackSendMessage` / `TelegramSendMessage` / …) are auto-available once the channel is enabled; only a **specific** allowlist needs their exact names added. Valid enabled Slack/Telegram adapter config and destination allowlists are required either way. On the pi-native runtime `disallowedTools` does not filter external MCP-server tools — to withhold one, don't declare its server.
81
81
 
82
- Offer the `NodeRepl` built-in when the user wants run-scoped JavaScript evaluation. It executes with the same sandbox authority as `Bash`.
82
+ For Pi process work, prefer the `Exec` built-in for one executable plus literal
83
+ argv and offer `Bash` only when shell syntax is required. Offer `NodeRepl` when
84
+ the user wants run-scoped JavaScript evaluation. All three execute with the same
85
+ sandbox authority.
86
+
87
+ If the agent needs public-web research, ask whether it should use the keyless
88
+ default, a loopback SearXNG companion, or strict SearXNG-only discovery. Fill
89
+ `tools.web.search.backend` and optional `tools.web.search.endpoint`. Keep
90
+ `tools.web.fetch.render: "never"` unless the user explicitly needs
91
+ JavaScript-heavy pages and has `agent-browser` 0.33.1 or newer; then select
92
+ `"auto"` plus the direct `browserCommand`. Explain that SearXNG is local
93
+ infrastructure but both search and fetch still generate public network traffic.
83
94
 
84
95
  ## 6. Memory Strategy
85
96
 
@@ -7,6 +7,8 @@ Every framework capability and how a composed agent reaches it. This table is th
7
7
  | Capability | Coverage | Where | Registry config ids |
8
8
  | --- | --- | --- | --- |
9
9
  | Model backends: claude (sdk/cli), codex (cli direct fallback), pi sdk providers (OpenAI, OpenAI-Codex preferred when Pi auth exists, Copilot, OpenRouter, OpenCode-through-Pi, Ollama, LM Studio, ...), plus hand-authored opencode runtime refs (cli, `opencode:<provider>:<model>` via the OpenCode server) | config | `runtime.model` | `runtime.multi-backend` |
10
+ | Subagent delegation via the Agent tool | config | pi runtime only. Requires BOTH `subagents.enabled: true` and `Agent` in `tools.allowedTools`. Each definition needs exactly one of `prompt` or `promptPath`; omitted `allowedTools` means a read-only default set and `"*"` is rejected. Subagents are capped by `maxConcurrent` (5) and `maxPerTurn` (20), never receive Agent/AskUser/channel-send tools, and cannot spawn subagents | `runtime.subagents` |
11
+ | Same-model retries before failover | config | `runtime.retry.primaryAttempts` (default 2) gives the primary a second attempt before the chain advances; per-route `runtime.fallbacks[].attempts` opts a backup in. Only transient provider failures retry — context overflow and bad credentials still advance. Set `primaryAttempts` to 1 to disable | `runtime.retry` |
10
12
  | Backup models on retryable provider failure | config | New configs use `runtime.fallbacks[]` with optional per-route effort. Legacy `runtime.fallbackModels` / `MONO_AGENT_FALLBACK_MODELS` remain compatibility inputs with no removal deadline; do not emit them for new agents | `runtime.fallback-models` |
11
13
  | Route-safety contract for primary and fallback models | config | `runtime.routeSafety`: `uniform` (default) or `per-route-native` | `runtime.route-safety` |
12
14
  | Execution mode (sdk/cli), effort, max turns, workspace | config + cli | `runtime.executionMode`, `runtime.effort` (`none` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max` / `ultra`; `mono-agent init --effort <level>`). Reasoning-capable `pi:*` maps `ultra` to LOW; Pi without reasoning uses OFF. Direct `codex:*` forwards `ultra` unchanged. Mono-agent rejects `ultra` on its Claude SDK route because the pinned SDK public contract ends at `max` (the SDK JavaScript itself forwards the value). The Claude CLI route passes `--effort ultra`, but both tested Claude Code binaries (SDK-bundled 2.1.206 and local 2.1.210) warn that it is unknown, ignore it, and use default effort. Direct OpenCode rejects explicit effort. Ranking above `max` only prevents keyword downgrade. `runtime.maxTurns`, `runtime.workspace` | `runtime.execution-modes`, `runtime.effort`, `runtime.max-turns`, `runtime.workspace` |
@@ -51,7 +53,8 @@ Every framework capability and how a composed agent reaches it. This table is th
51
53
  | Capability | Coverage | Where | Registry config ids |
52
54
  | --- | --- | --- | --- |
53
55
  | Allow-all tool policy (omitted / `["*"]` = all tools; `[]` = none) | config | default `tools.allowedTools`; the harness no-policy safety net is `failClosedToolPolicy()` | `tool-policy.allow-all` |
54
- | Built-in tool allow/deny lists (deny wins, even under allow-all; pi doesn't deny external MCP tools) | config | `tools.allowedTools`, `tools.disallowedTools`; managed built-ins are Read/Write/Edit/Glob/Grep/Bash/NodeRepl/WebFetch/WebSearch | `tool-policy.allowlist`, `tool-policy.denylist`, `runtime.builtin-tools` |
56
+ | Built-in tool allow/deny lists (deny wins, even under allow-all; pi doesn't deny external MCP tools) | config | `tools.allowedTools`, `tools.disallowedTools`; managed built-ins are Read/Write/Edit/Glob/Grep/Exec/Bash/NodeRepl/WebFetch/WebSearch | `tool-policy.allowlist`, `tool-policy.denylist`, `runtime.builtin-tools` |
57
+ | Local-first public-web research | config + auto | `tools.web.search.{backend,endpoint}` selects strict/first-choice loopback SearXNG or public no-credentials fallbacks; `tools.web.fetch.{render,browserCommand}` keeps static extraction as the default and optionally enables isolated agent-browser rendering; transient-fetch retry behavior is automatic rather than config-bearing | `runtime.web-research` |
55
58
  | MCP servers (stdio/sse/http) from a JSON file | config | `tools.mcpConfigPath` | `tool-policy.mcp-servers` |
56
59
  | Durable origin-bound continuations for trusted stdio/loopback-HTTP MCP services | config + auto | `tools.continuationServers` + `continuations.*`; interactive claims pin a bounded immutable origin snapshot before commit, exact rollover buckets are preserved, v3 state is restart-safe, and unavailable/legacy snapshots use a fixed zero-model fallback | `agent-app.durable-continuations` |
57
60
  | Adapter-derived send tools for enabled Slack/Telegram adapters | config | auto-available under allow-all once the channel is enabled; a **specific** `tools.allowedTools` must include `SlackSendMessage` / `TelegramSendMessage`; valid `slack.*` / `telegram.*` config and existing adapter allowlists provide credentials and destination bounds; confirmed message posts are idempotently recorded in destination history | `agent-app.adapter-send-tools` |
@@ -72,6 +75,8 @@ Every framework capability and how a composed agent reaches it. This table is th
72
75
  | Telegram runtime/command/reaction/button/file interactivity | config + code | built-in per-chat `/model` and `/effort` use configured primary/fallback models (no Telegram config key); `telegram.commands[]`, `telegram.reactions`, `telegram.quietHours`; `AskUser`, non-blocking `TelegramSendMessage.reply_options`, and `TelegramSendFile` | `telegram.interactive` |
73
76
  | Telegram inbound audio transcription | config | `telegram.transcription.{endpoint,model,language,timeoutMs}`; opt-in OpenAI-compatible transcription endpoint for voice notes, audio files, and round-video attachments | `telegram.transcription` |
74
77
  | Slack (Socket Mode, channel allowlist, mention handling, native runtime controls) | config + code | `slack` section; built-in Block Kit controls use configured primary/fallback choices through DM-wide/thread-local `@agent /model` and `@agent /effort`, plus channel-wide workspace commands `/<bot>-model` and `/<bot>-effort` derived from `auth.test.user` (Slack app registration + `commands` scope; no mono-agent config key) | `slack.socket-mode` |
78
+ | Slack speaker names (who sent each turn) | config | `slack.resolveUserNames` (default `true`) resolves the sender's display name and handle via `users.info`; requires the `users:read` bot scope; best-effort, so a missing scope leaves turns unnamed instead of failing them; the resolved name is durable (stored turn + memory label) | `slack.speaker-names` |
79
+ | Slack thread/channel turn context (what was said before the agent was triggered) | config | `slack.threadContext.{enabled,maxMessages,requestLimit,timeoutMs,includeBotMessages}`, defaults `true`/`15`/`15`/`4000`/`true`; reads `conversations.replies` in a thread and `conversations.history` otherwise; requires `channels:history` / `groups:history` / `im:history` / `mpim:history`; one request per turn with a per-channel rate-limit breaker; best-effort, so a missing scope or rate limit sends no transcript instead of failing the turn | `slack.thread-context` |
75
80
  | Slack global/message shortcuts | config | `slack.shortcuts[]: {callbackId, prompt, channelId?, ackText?, threadReply?}`; JSON-only | `slack.shortcuts` |
76
81
  | Slack App Home actions | config | `slack.homeTab: {enabled?, headerText?, buttons?:[{actionId, label, prompt, channelId?, ackText?, threadReply?}]}`; `enabled` defaults to `false`, `buttons` defaults to `[]`; JSON-only | `slack.app-home` |
77
82
  | External channel plugins | config | `channels.plugins[]: { package, id?, label?, config? }`; package must export `createChannelDriver(options)` or a default driver factory | `channel.plugins` |
@@ -175,7 +175,7 @@ const orchestrator = await createConfiguredAgentResponder({
175
175
 
176
176
  ## 9. Sandboxed code agent (loopback only, deny .env)
177
177
  **For:** a security team deploying an internal code assistant.
178
- **Goal:** read repos + run Bash or run-scoped NodeRepl inside the native srt sandbox with loopback-only network access and protected secrets.
178
+ **Goal:** read repos + run direct Exec, shell-syntax Bash, or run-scoped NodeRepl inside the native srt sandbox with loopback-only network access and protected secrets.
179
179
  **Features:** `sandbox.mode`, `sandbox.network-policy`, `sandbox.filesystem-scopes`, `sandbox.fallback`, `tool-policy.allow-all`, `memory.journal`.
180
180
 
181
181
  ```json
@@ -186,7 +186,7 @@ const orchestrator = await createConfiguredAgentResponder({
186
186
  }
187
187
  ```
188
188
  **Steps:** `mono-agent init --memory journal` → leave tools at the allow-all default (`["*"]`); the **sandbox**, not an allowlist, is what constrains the code tools → `sandbox.mode native` + `network localhost` + deny-write defaults → keep `fallback: fail-closed` (do NOT set `unsafe-host-process`) → `validate` → `start`.
189
- **Smoke:** ask it to read a file, run Bash, then use NodeRepl twice to retain a variable and produce `42` (all work); next fetch an external URL or write `.env` (both blocked in the artifact). Keep every primary/fallback/trigger model on Pi; direct Codex, Claude, and direct OpenCode reject this mono-agent sandbox policy.
189
+ **Smoke:** ask it to read a file, use Exec for one argv-safe command, run one Bash pipeline, then use NodeRepl twice to retain a variable and produce `42` (all work); next fetch an external URL or write `.env` (both blocked in the artifact). Keep every primary/fallback/trigger model on Pi; direct Codex, Claude, and direct OpenCode reject this mono-agent sandbox policy.
190
190
 
191
191
  ## 10. Phoenix-observed agent with the TUI
192
192
  **For:** an agent builder evaluating runs in a tracing dashboard.
@@ -287,3 +287,26 @@ per-route form for a new agent.
287
287
  ```
288
288
  **Steps:** run a loopback self-hosted Bot API server if files exceed 20 MB, wire a long-running MCP tool in `.mcp.json`, `validate`, `start`.
289
289
  **Smoke:** send media with no caption, answer the `AskUser` question, watch progress update during the long job, and receive the generated file via `TelegramSendFile`.
290
+
291
+ ## 16. Local-first web research agent
292
+ **For:** a researcher wanting operator-owned search infrastructure and bounded public-page extraction.
293
+ **Goal:** discover through loopback SearXNG, extract pages locally, and optionally render sparse JavaScript HTML in an isolated anonymous browser.
294
+ **Features:** `runtime.web-research`, `runtime.webfetch-retry`, `runtime.builtin-tools`, `sandbox.network-policy`.
295
+
296
+ ```json
297
+ {
298
+ "runtime": { "model": "pi:openai-codex:gpt-5.5" },
299
+ "tools": {
300
+ "allowedTools": ["Read", "Glob", "Grep", "WebSearch", "WebFetch"],
301
+ "web": {
302
+ "search": { "backend": "searxng", "endpoint": "http://127.0.0.1:8088" },
303
+ "fetch": { "render": "never", "browserCommand": "agent-browser" }
304
+ }
305
+ },
306
+ "sandbox": { "mode": "native", "network": { "mode": "all" }, "fallback": "fail-closed" }
307
+ }
308
+ ```
309
+
310
+ **Steps:** start the pinned loopback companion from `demos/searxng` → keep strict `searxng` or choose `auto` for keyless fallback → keep render `never`, or install `agent-browser >=0.33.1` and opt into `auto` for SPA pages → `validate` → `start`.
311
+ **Boundary:** local SearXNG is private infrastructure, not an offline index; it contacts public engines and WebFetch contacts result sites. `localhost` permits the companion but blocks public fetches.
312
+ **Smoke:** ask for two query variants and one official-page fetch; require canonical ranked URLs, untrusted-content boundaries, bounded timing metadata without query/URL leakage, and no duplicate network work for an identical call in the run.
@@ -126,6 +126,7 @@ pnpm run test:demo
126
126
  | Cron | Run a one-off scheduled invocation or wait for one tick. |
127
127
  | Observability | Confirm a run writes a JSONL artifact with strings capped: non-numeric values under sensitive-looking object keys are redacted; numeric values under matched keys are retained; retained free text is scanned for a closed set of high-confidence credential shapes. If an `observability.exporters` Phoenix entry is set, confirm the trace appears in Phoenix. |
128
128
  | Memory recall tool | With any memory tier configured (`memory.recallTool.enabled` defaults on), ask the agent to recall an old note and confirm `MemoryRecall` appears separately from action-tool allowlists and returns it. |
129
+ | Web research | Run `mono-agent validate` and require the Web search & fetch section to prove configured loopback SearXNG plus `agent-browser >=0.33.1` when rendering is enabled. Then ask for two query variants and one official-page fetch; require canonical ranked URLs, untrusted-content boundaries, and bounded timing metadata without query/URL leakage. |
129
130
  | Semantic memory search | With `memory.embeddings` set, first prove the configured provider only: Ollama model advertises `embedding` through `/api/show` and answers `/api/embed`, or LM Studio model has exact `type: "embedding"` in `/api/v1/models` and answers `/v1/embeddings`. Verify the finite vector dimension matches config, then ask a paraphrased question about an old note and confirm `MemoryRecall` returns it. Never accept a cross-provider fallback as proof. |
130
131
 
131
132
  ## Failure Handling
@@ -1,27 +0,0 @@
1
- import type { RunEventSink } from "@mono-agent/agent-contracts";
2
- import type { RunRecorder } from "@mono-agent/observability";
3
- /** Stable run context stamped onto every broadcast frame. */
4
- export interface BroadcastRunContext {
5
- readonly runId: string;
6
- readonly conversationId: string;
7
- /** Producing instance's trace-source id (empty when the host has none). */
8
- readonly sourceId: string;
9
- readonly sourceLabel?: string;
10
- /** Trigger channel, e.g. "cron" | "webhook" | "chat" | "memory". */
11
- readonly source?: string;
12
- /** Trigger detail (cron job id / webhook endpoint name). */
13
- readonly sourceDetail?: string;
14
- }
15
- /**
16
- * Wrap a {@link RunRecorder} so every run start/event/finish is ALSO published to
17
- * a {@link RunEventSink} (the in-process live bus), giving operator surfaces
18
- * sub-run visibility the on-disk recorder can't (it flushes only at start/finish).
19
- *
20
- * Broadcast is best-effort and additive, exactly like the Phoenix exporter path:
21
- * the inner recorder is the source of truth for the returned summary, and a
22
- * publish failure never changes the run outcome (publish is wrapped and the bus's
23
- * own `publish` is contracted never to throw). `seq` is left 0 here — the bus
24
- * stamps a process-wide monotonic value on publish.
25
- */
26
- export declare function createBroadcastRunRecorder(inner: RunRecorder, sink: RunEventSink, ctx: BroadcastRunContext): RunRecorder;
27
- //# sourceMappingURL=broadcast-recorder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"broadcast-recorder.d.ts","sourceRoot":"","sources":["../src/broadcast-recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,2BAA2B,CAAC;AAE9G,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,mBAAmB,GACvB,WAAW,CAyFb"}
@@ -1,104 +0,0 @@
1
- import { LIVE_EVENT_SCHEMA } from "@mono-agent/agent-contracts";
2
- import { redactJsonValue } from "@mono-agent/observability";
3
- /**
4
- * Wrap a {@link RunRecorder} so every run start/event/finish is ALSO published to
5
- * a {@link RunEventSink} (the in-process live bus), giving operator surfaces
6
- * sub-run visibility the on-disk recorder can't (it flushes only at start/finish).
7
- *
8
- * Broadcast is best-effort and additive, exactly like the Phoenix exporter path:
9
- * the inner recorder is the source of truth for the returned summary, and a
10
- * publish failure never changes the run outcome (publish is wrapped and the bus's
11
- * own `publish` is contracted never to throw). `seq` is left 0 here — the bus
12
- * stamps a process-wide monotonic value on publish.
13
- */
14
- export function createBroadcastRunRecorder(inner, sink, ctx) {
15
- let eventIndex = 0;
16
- let terminalStarted = false;
17
- let terminalPromise;
18
- const publish = (frame) => {
19
- try {
20
- sink.publish(frame);
21
- }
22
- catch {
23
- // Broadcast is best-effort; a sink failure must never break the run.
24
- }
25
- };
26
- const finished = async (summary) => {
27
- const redactedSummary = redactJsonValue(summary);
28
- publish({
29
- t: "run_finished",
30
- schema: LIVE_EVENT_SCHEMA,
31
- sourceId: ctx.sourceId,
32
- runId: ctx.runId,
33
- status: redactedSummary.status,
34
- summary: redactedSummary,
35
- seq: 0,
36
- });
37
- return summary;
38
- };
39
- const recorder = {
40
- onEvent(event) {
41
- // Terminal is a hard boundary in the live protocol. Events accepted after
42
- // commit starts could otherwise appear after `run_finished` or be absent
43
- // from the authoritative summary/export.
44
- if (terminalStarted)
45
- return;
46
- inner.onEvent(event);
47
- const redactedEvent = redactJsonValue(event);
48
- publish({
49
- t: "event",
50
- schema: LIVE_EVENT_SCHEMA,
51
- sourceId: ctx.sourceId,
52
- runId: ctx.runId,
53
- eventIndex: eventIndex++,
54
- event: redactedEvent,
55
- seq: 0,
56
- });
57
- },
58
- async prepareFinish(result) {
59
- await inner.prepareFinish?.(result);
60
- },
61
- async commitFinish(result) {
62
- if (terminalPromise === undefined) {
63
- terminalStarted = true;
64
- terminalPromise = (async () => await finished(inner.commitFinish === undefined ? await inner.finish(result) : await inner.commitFinish(result)))();
65
- }
66
- return await terminalPromise;
67
- },
68
- async finish(result) {
69
- await recorder.prepareFinish?.(result);
70
- return await recorder.commitFinish(result);
71
- },
72
- async fail(error) {
73
- if (terminalPromise === undefined) {
74
- terminalStarted = true;
75
- terminalPromise = (async () => await finished(await inner.fail(error)))();
76
- }
77
- return await terminalPromise;
78
- },
79
- };
80
- // `start` is optional on RunRecorder — only expose (and emit run_started) when
81
- // the inner recorder has one, so callers that probe `recorder.start?.()` behave
82
- // identically to the unwrapped recorder.
83
- if (inner.start !== undefined) {
84
- const innerStart = inner.start.bind(inner);
85
- recorder.start = async () => {
86
- const summary = await innerStart();
87
- publish({
88
- t: "run_started",
89
- schema: LIVE_EVENT_SCHEMA,
90
- sourceId: ctx.sourceId,
91
- ...(ctx.sourceLabel === undefined ? {} : { sourceLabel: ctx.sourceLabel }),
92
- runId: ctx.runId,
93
- conversationId: ctx.conversationId,
94
- ...(ctx.source === undefined ? {} : { source: ctx.source }),
95
- ...(ctx.sourceDetail === undefined ? {} : { sourceDetail: ctx.sourceDetail }),
96
- startedAt: summary.startedAt ?? new Date().toISOString(),
97
- seq: 0,
98
- });
99
- return summary;
100
- };
101
- }
102
- return recorder;
103
- }
104
- //# sourceMappingURL=broadcast-recorder.js.map