@aragon-agent/cli 0.6.38 → 0.6.40

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 (209) hide show
  1. package/CHANGELOG.md +263 -0
  2. package/README.md +544 -11
  3. package/dist/agent/controller.d.ts +245 -10
  4. package/dist/agent/controller.js +367 -22
  5. package/dist/agent/model-profile-settings.js +12 -1
  6. package/dist/agent/plan-prompt.d.ts +1 -3
  7. package/dist/agent/plan-prompt.js +13 -7
  8. package/dist/agent/reducer.d.ts +70 -2
  9. package/dist/agent/reducer.js +59 -6
  10. package/dist/agent/skill-triage-prompt.d.ts +42 -0
  11. package/dist/agent/skill-triage-prompt.js +74 -0
  12. package/dist/agent/system-prompt.d.ts +28 -2
  13. package/dist/agent/system-prompt.js +12 -4
  14. package/dist/cli.js +106 -32
  15. package/dist/commands/builtins.js +24 -3
  16. package/dist/commands/keys-command.d.ts +23 -0
  17. package/dist/commands/keys-command.js +38 -0
  18. package/dist/commands/registry.d.ts +39 -0
  19. package/dist/commands/skills.js +86 -1
  20. package/dist/commands/subagent-command.d.ts +25 -0
  21. package/dist/commands/subagent-command.js +242 -0
  22. package/dist/commands/task-command.d.ts +39 -0
  23. package/dist/commands/task-command.js +102 -0
  24. package/dist/compaction/command.js +16 -3
  25. package/dist/compaction/compactor.d.ts +25 -0
  26. package/dist/compaction/compactor.js +111 -10
  27. package/dist/compaction/digest.d.ts +24 -1
  28. package/dist/compaction/digest.js +139 -17
  29. package/dist/compaction/limits.d.ts +21 -0
  30. package/dist/compaction/limits.js +21 -0
  31. package/dist/compaction/reason-text.d.ts +36 -0
  32. package/dist/compaction/reason-text.js +78 -0
  33. package/dist/compaction/types.d.ts +82 -14
  34. package/dist/compaction/wiring.d.ts +13 -8
  35. package/dist/compaction/wiring.js +1 -0
  36. package/dist/config/cli-commands.d.ts +23 -0
  37. package/dist/config/cli-commands.js +36 -0
  38. package/dist/config/env.js +17 -4
  39. package/dist/config/load.js +55 -3
  40. package/dist/config/schema.d.ts +125 -10
  41. package/dist/config/schema.js +138 -14
  42. package/dist/config/store.js +23 -3
  43. package/dist/diagnostics/doctor.d.ts +9 -0
  44. package/dist/diagnostics/doctor.js +34 -0
  45. package/dist/diagnostics/info.d.ts +71 -0
  46. package/dist/diagnostics/info.js +91 -5
  47. package/dist/exec/bridge/caller-tools.d.ts +96 -0
  48. package/dist/exec/bridge/caller-tools.js +213 -0
  49. package/dist/exec/bridge/decisions.d.ts +78 -0
  50. package/dist/exec/bridge/decisions.js +116 -0
  51. package/dist/exec/bridge/human-answers.d.ts +38 -0
  52. package/dist/exec/bridge/human-answers.js +164 -0
  53. package/dist/exec/bridge/limits.d.ts +68 -0
  54. package/dist/exec/bridge/limits.js +68 -0
  55. package/dist/exec/bridge/ndjson.d.ts +17 -0
  56. package/dist/exec/bridge/ndjson.js +83 -0
  57. package/dist/exec/bridge/pending.d.ts +70 -0
  58. package/dist/exec/bridge/pending.js +132 -0
  59. package/dist/exec/bridge/protocol.d.ts +346 -0
  60. package/dist/exec/bridge/protocol.js +256 -0
  61. package/dist/exec/bridge/server.d.ts +142 -0
  62. package/dist/exec/bridge/server.js +502 -0
  63. package/dist/exec/bridge/source.d.ts +58 -0
  64. package/dist/exec/bridge/source.js +145 -0
  65. package/dist/exec/bridge/writer.d.ts +26 -0
  66. package/dist/exec/bridge/writer.js +156 -0
  67. package/dist/exec/cli-commands.js +30 -2
  68. package/dist/exec/emitter.d.ts +16 -2
  69. package/dist/exec/emitter.js +26 -6
  70. package/dist/exec/events.d.ts +465 -10
  71. package/dist/exec/events.js +47 -1
  72. package/dist/exec/hooks/dispatch.d.ts +76 -0
  73. package/dist/exec/hooks/dispatch.js +169 -0
  74. package/dist/exec/hooks/index.d.ts +83 -0
  75. package/dist/exec/hooks/index.js +205 -0
  76. package/dist/exec/hooks/limits.d.ts +56 -0
  77. package/dist/exec/hooks/limits.js +49 -0
  78. package/dist/exec/hooks/registry.d.ts +49 -0
  79. package/dist/exec/hooks/registry.js +91 -0
  80. package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
  81. package/dist/exec/hooks/tool-wrapper.js +154 -0
  82. package/dist/exec/hooks/turn.d.ts +61 -0
  83. package/dist/exec/hooks/turn.js +59 -0
  84. package/dist/exec/hooks/validate.d.ts +85 -0
  85. package/dist/exec/hooks/validate.js +310 -0
  86. package/dist/exec/index.d.ts +130 -4
  87. package/dist/exec/index.js +501 -10
  88. package/dist/exec/options.d.ts +124 -2
  89. package/dist/exec/options.js +413 -6
  90. package/dist/exec/permission.d.ts +9 -1
  91. package/dist/exec/permission.js +7 -1
  92. package/dist/exec/request/limits.d.ts +51 -0
  93. package/dist/exec/request/limits.js +49 -0
  94. package/dist/exec/request/scope.d.ts +106 -0
  95. package/dist/exec/request/scope.js +149 -0
  96. package/dist/exec/result/limits.d.ts +74 -0
  97. package/dist/exec/result/limits.js +72 -0
  98. package/dist/exec/result/prompt.d.ts +28 -0
  99. package/dist/exec/result/prompt.js +74 -0
  100. package/dist/exec/result/schema.d.ts +99 -0
  101. package/dist/exec/result/schema.js +464 -0
  102. package/dist/exec/result/sink.d.ts +56 -0
  103. package/dist/exec/result/sink.js +50 -0
  104. package/dist/exec/result/tool.d.ts +37 -0
  105. package/dist/exec/result/tool.js +118 -0
  106. package/dist/exec/runner.d.ts +395 -1
  107. package/dist/exec/runner.js +860 -8
  108. package/dist/exec/threads/aggregate.d.ts +34 -0
  109. package/dist/exec/threads/aggregate.js +30 -0
  110. package/dist/exec/threads/capacity.d.ts +23 -0
  111. package/dist/exec/threads/capacity.js +38 -0
  112. package/dist/exec/threads/dispatch-source.d.ts +60 -0
  113. package/dist/exec/threads/dispatch-source.js +106 -0
  114. package/dist/exec/threads/execute-threads.d.ts +53 -0
  115. package/dist/exec/threads/execute-threads.js +234 -0
  116. package/dist/exec/threads/gate.d.ts +42 -0
  117. package/dist/exec/threads/gate.js +81 -0
  118. package/dist/exec/threads/identity.d.ts +36 -0
  119. package/dist/exec/threads/identity.js +67 -0
  120. package/dist/exec/threads/interrupt.d.ts +29 -0
  121. package/dist/exec/threads/interrupt.js +79 -0
  122. package/dist/exec/threads/limits.d.ts +58 -0
  123. package/dist/exec/threads/limits.js +35 -0
  124. package/dist/exec/threads/outcome.d.ts +47 -0
  125. package/dist/exec/threads/outcome.js +26 -0
  126. package/dist/exec/threads/registry.d.ts +81 -0
  127. package/dist/exec/threads/registry.js +427 -0
  128. package/dist/exec/threads/runtime.d.ts +107 -0
  129. package/dist/exec/threads/runtime.js +523 -0
  130. package/dist/exec/threads/thread-source.d.ts +20 -0
  131. package/dist/exec/threads/thread-source.js +134 -0
  132. package/dist/fast/limits.d.ts +54 -0
  133. package/dist/fast/limits.js +54 -0
  134. package/dist/fast/summary-call.d.ts +72 -0
  135. package/dist/fast/summary-call.js +133 -0
  136. package/dist/fast/summary-prompt.d.ts +106 -0
  137. package/dist/fast/summary-prompt.js +320 -0
  138. package/dist/fast/task-summary-types.d.ts +66 -0
  139. package/dist/fast/task-summary-types.js +17 -0
  140. package/dist/logging/logger.d.ts +1 -1
  141. package/dist/proc/limits.d.ts +7 -0
  142. package/dist/proc/limits.js +7 -0
  143. package/dist/proc/log-mirror.d.ts +21 -4
  144. package/dist/proc/log-mirror.js +29 -7
  145. package/dist/proc/output-streams.d.ts +50 -0
  146. package/dist/proc/output-streams.js +54 -0
  147. package/dist/skills/service.d.ts +41 -0
  148. package/dist/skills/service.js +62 -0
  149. package/dist/skills/triage.d.ts +194 -0
  150. package/dist/skills/triage.js +393 -0
  151. package/dist/team/forced-dispatch.d.ts +95 -0
  152. package/dist/team/forced-dispatch.js +208 -0
  153. package/dist/team/limits.d.ts +21 -1
  154. package/dist/team/limits.js +21 -1
  155. package/dist/team/normalize.d.ts +15 -0
  156. package/dist/team/normalize.js +21 -4
  157. package/dist/team/prompt.d.ts +6 -3
  158. package/dist/team/prompt.js +9 -5
  159. package/dist/team/runtime.d.ts +9 -0
  160. package/dist/team/runtime.js +8 -0
  161. package/dist/team/subagent.d.ts +15 -0
  162. package/dist/team/subagent.js +45 -1
  163. package/dist/team/task-tool.js +11 -7
  164. package/dist/tools/bash-tool.d.ts +10 -0
  165. package/dist/tools/bash-tool.js +71 -6
  166. package/dist/tools/human-input.d.ts +35 -2
  167. package/dist/tools/human-input.js +38 -0
  168. package/dist/tools/index.d.ts +93 -1
  169. package/dist/tools/index.js +112 -3
  170. package/dist/tools/plan-tools.d.ts +20 -12
  171. package/dist/tools/plan-tools.js +24 -17
  172. package/dist/tools/tool-gate.d.ts +100 -0
  173. package/dist/tools/tool-gate.js +87 -0
  174. package/dist/ui/App.js +238 -12
  175. package/dist/ui/Header.d.ts +28 -16
  176. package/dist/ui/Header.js +21 -19
  177. package/dist/ui/StatusBar.d.ts +1 -0
  178. package/dist/ui/StatusBar.js +1 -0
  179. package/dist/ui/Transcript.js +1 -1
  180. package/dist/ui/entries/CompactionCard.d.ts +11 -1
  181. package/dist/ui/entries/CompactionCard.js +65 -4
  182. package/dist/ui/interaction-hints.js +6 -0
  183. package/dist/ui/layout/OverlayFrame.d.ts +18 -0
  184. package/dist/ui/layout/OverlayFrame.js +19 -4
  185. package/dist/ui/layout/header-hints.d.ts +55 -62
  186. package/dist/ui/layout/header-hints.js +59 -166
  187. package/dist/ui/layout/header-left.d.ts +115 -0
  188. package/dist/ui/layout/header-left.js +206 -0
  189. package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
  190. package/dist/ui/layout/shortcut-panel-layout.js +321 -0
  191. package/dist/ui/layout/status-detail-layout.js +5 -1
  192. package/dist/ui/layout/status-layout.d.ts +10 -0
  193. package/dist/ui/layout/status-layout.js +18 -1
  194. package/dist/ui/layout/task-summary-cell.d.ts +61 -0
  195. package/dist/ui/layout/task-summary-cell.js +65 -0
  196. package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
  197. package/dist/ui/overlays/HelpOverlay.js +29 -2
  198. package/dist/ui/overlays/SettingsScreen.js +29 -5
  199. package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
  200. package/dist/ui/overlays/ShortcutPanel.js +106 -0
  201. package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
  202. package/dist/ui/overlays/TeamFanoutPage.js +78 -0
  203. package/dist/ui/overlays/model-profile-state.d.ts +19 -1
  204. package/dist/ui/overlays/model-profile-state.js +12 -0
  205. package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
  206. package/dist/ui/overlays/team-fanout-state.js +97 -0
  207. package/dist/ui/shortcuts.d.ts +81 -0
  208. package/dist/ui/shortcuts.js +221 -0
  209. package/package.json +2 -2
package/README.md CHANGED
@@ -305,7 +305,7 @@ printf '%s\n' \
305
305
 
306
306
  ### Limits
307
307
 
308
- Four things this does **not** do, stated plainly because the audience for this
308
+ Six things this does **not** do, stated plainly because the audience for this
309
309
  section is the one that would be misled by their omission.
310
310
 
311
311
  1. **There is no filesystem sandbox.** Tool paths are not confined to the
@@ -330,6 +330,395 @@ section is the one that would be misled by their omission.
330
330
  hand-written `/save` files and never reaches a TUI checkpoint; `pinned`
331
331
  sessions and files whose owner process is still alive are never deleted.
332
332
  `sessions show` and `sessions rm` accept any id, because you named it.
333
+ 5. **A cancelled turn can leave one tool call unanswered.** If an abort lands
334
+ between the assistant message and the first tool execution, that `tool_use` has
335
+ no `tool_result`. This window predates `cancel_request` (an `interrupt` and a
336
+ spent `--max-turns` expose exactly the same one), it is not widened by it, and
337
+ the next compaction refuses to compress such a history rather than shipping it
338
+ to a provider. In practice the turn is over either way: a `cancel_request` stops
339
+ the work, and the conversation continues on the next `prompt`.
340
+ 6. **Schema validation strength depends on the machine.** `ajv` is an optional
341
+ dependency of Core. With it, every JSON Schema keyword is enforced; without it,
342
+ `required`, nested `properties`, `items`, `enum`, `const`, the length/number
343
+ bounds and `additionalProperties: false` are enforced by this CLI's own bounded
344
+ validator and `pattern` / `format` / `multipleOf` are not. A `$ref` target is
345
+ followed only by `ajv`: without it the referenced subschema is not checked at
346
+ all, so a schema that leans on `#/definitions/...` for its real constraints
347
+ needs `ajv` present. `aragon doctor` and `aragon info --json` both report which
348
+ one you have, because silently weaker validation is the one failure a schema is
349
+ supposed to prevent.
350
+
351
+ ## Let the caller decide
352
+
353
+ `--permission-mode ask` turns the three static postures into a conversation. With
354
+ `--input-format bridge` (which requires `--output-format stream-json`), a tool that
355
+ can change the world asks first:
356
+
357
+ ```bash
358
+ printf '%s\n' \
359
+ '{"type":"prompt","id":"u1","text":"run the test suite"}' \
360
+ '{"type":"end"}' \
361
+ | aragon exec --input-format bridge --output-format stream-json --permission-mode ask
362
+ ```
363
+
364
+ ```json
365
+ {"type":"decision_request","requestId":"req_1","sessionId":"s1","kind":"tool","createdAt":1730000000000,"deadlineAt":1730000300000,"timeoutMs":300000,"tool":{"name":"bash","callId":"t1","input":{"command":"npm test"},"summary":"Run: npm test","turn":1},"agent":{"id":"lead","label":"lead","depth":0},"seq":7}
366
+ ```
367
+
368
+ Answer it with a `decision` frame that names the request:
369
+
370
+ ```json
371
+ {"type":"decision","respondsTo":"req_1","decision":"deny","reason":"not in this sandbox"}
372
+ ```
373
+
374
+ The model receives a `tool_result` with `isError: true` that quotes the reason and
375
+ tells it not to retry the same call, so it routes around the refusal instead of
376
+ looping. `"scope":"session"` on an `allow` stops that tool being asked about again
377
+ for the rest of the process; it is never written to `config.json`.
378
+
379
+ | `decision_request.kind` | Asked when | Answer fields |
380
+ | --- | --- | --- |
381
+ | `tool` | `write_file` / `edit_file` / `bash` / `skill_create`, if not pre-authorised | `decision`, `scope?`, `reason?` |
382
+ | `questions` | the model calls `ask_user` | `answers[]`, `cancelled?` |
383
+ | `plan` | the model calls `submit_plan` | `planDecision`, `feedback?` |
384
+ | `skill_install` | the model calls `skill_install` | `decision` |
385
+
386
+ Which tools are gated is `CALLER_GATED_TOOLS`, reported by `aragon info --json` as
387
+ `bridge.gatedTools`, so you never have to infer it. `skill_install` is not in that
388
+ list because it has its own approval; `skill_create` is, because it writes
389
+ `SKILL.md` plus arbitrary bundled files and had no gate at all.
390
+
391
+ Four ways a wait ends, and they read differently on purpose: an answer (`allow` or
392
+ `deny`), `--decision-timeout` expiring (`The caller did not answer within 300000
393
+ ms. The tool was not run.`), the turn being interrupted (`The turn was interrupted
394
+ before the caller answered.`), and a frame that could not be trusted (fail-closed
395
+ denial plus `error{code:'bridge_frame_invalid'}`). `--on-decision-timeout error`
396
+ makes a timeout end the turn with `stopReason: "error"` and exit code 1; the
397
+ default `deny` treats it as an ordinary refusal and keeps going. There is no
398
+ `allow` setting: silently granting access is what `auto` already is.
399
+
400
+ Idle-watchdog pausing, the executor's two-phase timeout and the 5-second abort
401
+ grace are handled inside the CLI, so a caller that thinks for four minutes gets a
402
+ tool that still has its whole budget left.
403
+
404
+ | Inbound frame | Fields | Effect |
405
+ | --- | --- | --- |
406
+ | `prompt` | `text`, `id?` | a turn; the id is echoed onto its events |
407
+ | `steer` | `text`, `id?` | running guidance; refused when idle |
408
+ | `interrupt` | `requestId?`, `id?` | abort the turn in flight, or delete a queued one |
409
+ | `decision` | `respondsTo`, `decision`/`answers`/`planDecision` | answers a decision |
410
+ | `tool_result` | `respondsTo`, `content`, `isError?` | answers a `tool_invoke` |
411
+ | `tool_register` | `tools[]`, `id?` | replaces the caller's tool set at the next turn boundary |
412
+ | `ping` | `id?` | `pong` with the current `seq` |
413
+ | `end` | — | no more input; settle, persist, `result` |
414
+ | `user` | `text`, `content?` | the v1 spelling of `prompt` |
415
+
416
+ Anything else is ignored, because forward compatibility has to run in both
417
+ directions. An unknown `respondsTo` is *not* ignored: it is an `error` with
418
+ `code: "bridge_unknown_request"`, since silence would leave you waiting forever.
419
+
420
+ ### Budgets and limits
421
+
422
+ | Flag | Default | Range | Meaning |
423
+ | --- | --- | --- | --- |
424
+ | `--decision-timeout <ms>` | `300000` | 1000..1800000 | how long one decision may wait |
425
+ | `--on-decision-timeout <mode>` | `deny` | `deny` \| `error` | what an unanswered decision means for the turn |
426
+ | `--caller-tool-timeout <ms>` | `60000` | 1000..600000 | ceiling for one caller tool call |
427
+
428
+ All three, `--permission-mode ask`, and `--input-format bridge` itself are usage
429
+ errors (exit 2, empty stdout) without a bridge to talk to - and so is a positional
430
+ prompt or `--prompt-file`, because bridge prompts come from stdin only.
431
+
432
+ ## Serve your own tools
433
+
434
+ `tool_register` hands the model tools you implement. The frame declares the WHOLE
435
+ set (an empty array withdraws everything), and it is validated before anything is
436
+ written - name format, collisions with host/team/skill names, a JSON Schema whose
437
+ `type` is `object`, and a 16 KiB schema ceiling:
438
+
439
+ ```json
440
+ {"type":"tool_register","id":"u0","tools":[{"name":"db_query","description":"Read-only SQL against the reporting replica.","parameters":{"type":"object","properties":{"sql":{"type":"string"}},"required":["sql"]},"timeoutMs":20000}]}
441
+ ```
442
+
443
+ ```json
444
+ {"type":"ack","id":"u0","accepted":true,"scope":"lead","tools":["db_query"],"appliedAtTurn":1,"seq":2}
445
+ ```
446
+
447
+ The model then calls it, and you answer:
448
+
449
+ ```json
450
+ {"type":"tool_invoke","requestId":"req_2","sessionId":"s1","callId":"t2","turn":1,"tool":"db_query","input":{"sql":"select count(*) from users"},"timeoutMs":20000,"deadlineAt":1730000060000,"agent":{"id":"lead","label":"lead","depth":0},"seq":12}
451
+ {"type":"tool_result","respondsTo":"req_2","content":"count\n40"}
452
+ ```
453
+
454
+ `content` is a string or a list of text parts; an `image` part is refused with
455
+ `bridge_unsupported_content` rather than silently dropped. `durationMs` on the
456
+ emitted `tool_result` is measured by the CLI - a tool cannot self-report its own
457
+ cost. Results are clipped at 100000 bytes with a `[truncated at 100000 bytes]`
458
+ footer. Text only, this round.
459
+
460
+ Four refusals, each with its own code: `caller_tools_too_many`,
461
+ `caller_tool_bad_name`, `caller_tool_conflict`, `caller_tool_bad_schema` (all
462
+ delivered as `ack{accepted:false}` and leaving the registry untouched), plus
463
+ `plan_mode_read_only` when the session is in plan mode - caller tools have side
464
+ effects this process cannot see, so plan mode refuses the frame outright.
465
+
466
+ **Caller tools live outside the host's wrappers.** They are handed straight to the
467
+ registry, so they are not covered by the plan gate, by a skill's `allowed-tools`
468
+ ceiling, or by the job-notice tail. Plan mode refuses them wholesale and a name
469
+ collision is refused at registration; those are the only two defences, and they
470
+ are the two that registration time can still enforce.
471
+
472
+ A run with `task` passes caller tools to the **lead only**. A fan-out would call
473
+ `db_query` once per child, which is not in your budget - so `ack.scope` says
474
+ `"lead"` and `tool_invoke.agent` is always `{id:"lead",label:"lead",depth:0}`.
475
+
476
+ ### A complete run, and a reference host
477
+
478
+ ```bash
479
+ node packages/cli/examples/bridge-host.mjs -- npx aragon exec \
480
+ --input-format bridge --output-format stream-json --permission-mode ask
481
+ ```
482
+
483
+ `examples/bridge-host.mjs` is a zero-dependency host: it approves `bash`, denies
484
+ everything that would write, answers `ask_user` with a canned option, and serves
485
+ `db_query` from a fake table. It is the executable form of the two sections above,
486
+ and `exec-bridge-docs.test.ts` keeps it honest (`node --check`, plus both headings
487
+ have to exist).
488
+
489
+ ### Honest limits
490
+
491
+ - **Approval is authorisation, not isolation.** A `bash` you approved runs with
492
+ your privileges and no sandbox, exactly as it does in `auto`.
493
+ - **Tool arguments travel on the wire.** `tool_call`, `tool_invoke` and the audit
494
+ log all carry `input`, so never put a secret in a tool argument.
495
+ - **`scope: "session"` lives in memory and dies with the process.** `--resume`
496
+ does not bring back "you allowed bash last time", deliberately.
497
+ - **`seq` is per process.** A resumed session keeps the conversation, not the
498
+ counter.
499
+
500
+ ## Many conversations in one process
501
+
502
+ `--threads` serves N **isolated** conversations from one process: each has its own
503
+ controller, history, session file, `todo` list, prompt queue and account. One process
504
+ serves fifty tenants instead of fifty processes each paying startup, a skills scan and a
505
+ config load.
506
+
507
+ ```bash
508
+ aragon exec --threads --input-format bridge --output-format stream-json
509
+ ```
510
+
511
+ ```jsonc
512
+ {"type":"thread_open","id":"t1","threadId":"tenant-a"}
513
+ {"type":"prompt","id":"p1","threadId":"tenant-a","text":"summarise the open issues"}
514
+ {"type":"thread_open","id":"t2","threadId":"tenant-b"}
515
+ {"type":"prompt","id":"p2","threadId":"tenant-b","text":"what changed in the last release?"}
516
+ {"type":"thread_close","threadId":"tenant-a"}
517
+ ```
518
+
519
+ | Flag | Default | Meaning |
520
+ | --- | --- | --- |
521
+ | `--threads` | off | Thread multiplexing. Requires `--input-format bridge` **and** `--output-format stream-json`. |
522
+ | `--max-open-threads <n>` | 4 | How many conversations may exist at once (1-8, including the default `main`). |
523
+ | `--thread-concurrency <n>` | 2 | How many threads may hold a request in flight. Cannot exceed `--max-open-threads`. |
524
+
525
+ Four rules that answer most questions:
526
+
527
+ - **A frame without `threadId` lands on `main`**, which is opened for you. A v1 caller
528
+ that never heard of threads keeps working unchanged.
529
+ - **`thread_open` is asynchronous, and its prompts are not dropped.** Send `thread_open`
530
+ and `prompt` back to back; the prompt is queued until the thread is ready.
531
+ - **`interrupt` stays process-level.** `interrupt{threadId}` is refused with
532
+ `interrupt_is_process_level` - silently turning it into "cancel one request" would make
533
+ "the user pressed Ctrl-C" and "the backend withdrew a request" indistinguishable, and
534
+ their exit codes differ. Use `cancel_request{threadId}` for one request. `interrupt`
535
+ with a `requestId` and no `threadId` keeps its other meaning, unchanged since v1: it
536
+ deletes that one queued turn (exit code 0) and touches nothing else.
537
+ - **Thread frames sent to a run without `--threads` are refused, not ignored.**
538
+ `thread_open` / `thread_close` / `thread_list` answer
539
+ `ack{accepted:false, code:'threads_disabled'}`, so a host that pointed the wrong
540
+ invocation at its thread client learns it immediately instead of waiting for an `ack`
541
+ that will never come.
542
+ - **The process ends when stdin ends AND every thread is closed.** `end` (or closing stdin)
543
+ finishes every queue, `thread_close{settle}` waits for the request in flight, and only
544
+ then is `result` written - one `result` for the process, with `result.threads.items`
545
+ carrying each tenant's own account. `result.usage` / `result.cost` are the sums, and
546
+ `cost.known` is true only if **every** thread knew its price.
547
+
548
+ `--max-turns` is refused with `--threads` (a process-wide turn budget has no honest
549
+ meaning across N conversations); use `--request-max-turns`, which is per request. A thread
550
+ never resumes: `--resume` stays a process-level, `main`-only feature.
551
+
552
+ Each thread writes its own session file named `<base>-<threadId>` - the shape of the file
553
+ is unchanged, the name is a convention. `aragon sessions` lists them like any other.
554
+ Locking is unchanged, including its documented limit: a lock is stamped once and expires
555
+ after 600 s, so a thread that runs longer than ten minutes can be overtaken in another
556
+ process.
557
+
558
+ ## Intervene in the data flow: lifecycle hooks
559
+
560
+ Approval answers *"may the model do this?"*. Hooks answer *"what exactly does it say, and
561
+ what exactly does it see?"* - the question every compliance, redaction and tenant-scoping
562
+ integration actually asks.
563
+
564
+ ```jsonc
565
+ // 1. Install a redactor for one tool, on one thread (omit threadId for `main`).
566
+ {"type":"hook_register","id":"h1","threadId":"tenant-a","hooks":[
567
+ {"point":"tool_post","tools":["read_file"],"name":"redactor","timeoutMs":2000}
568
+ ]}
569
+
570
+ // 2. The CLI asks before the result reaches the model:
571
+ {"type":"hook_request","requestId":"c1","hookRequestId":"hx-1","hookId":"h1",
572
+ "point":"tool_post","payload":{"tool":"read_file","callId":"c1",
573
+ "content":[{"type":"text","text":"AKIA..."}],"isError":false}}
574
+
575
+ // 3. You answer with the round trip's id:
576
+ {"type":"hook_answer","respondsTo":"hx-1","action":"replace","content":"[REDACTED]"}
577
+ ```
578
+
579
+ Four points, one per edge of the data flow, and no fifth:
580
+
581
+ | Point | Where | Legal actions | Use it for |
582
+ | --- | --- | --- | --- |
583
+ | `prompt_pre` | caller -> host | `allow`, `rewrite{text}`, `deny` | Tenant prefixes, input policy, flattening rich text |
584
+ | `turn_start` | host -> model | `allow`, `inject{text}` | Retrieval, per-turn instructions |
585
+ | `tool_pre` | model -> world | `allow`, `rewrite{input}`, `deny` | Inject `tenant_id`, force `--dry-run`, clamp paths |
586
+ | `tool_post` | world -> model | `keep`, `replace{content}`, `deny` | Redaction, PII removal, compressing huge results |
587
+
588
+ The details that decide whether an integration works:
589
+
590
+ - **Answer `hookRequestId`, not `requestId`.** One hook fires many times inside one
591
+ request.
592
+ - **`tool_pre` rewrites are re-validated** against the tool's own schema. An illegal
593
+ rewrite is refused as `invalid` rather than blamed on the model, and never reaches the
594
+ executor.
595
+ - **`tool_post` is the outermost wrapper**, so it also covers denial text, a tool's own
596
+ error output and the `<job_notices>` tail of a background job - the three places a
597
+ secret actually leaks from.
598
+ - **A human always approves the rewritten arguments**, never the ones that were replaced.
599
+ - **An unanswered hook fails closed** after `--hook-timeout` (default 5 s): the tool does
600
+ not run and `tool_post` withholds the result. `--on-hook-timeout allow` is for purely
601
+ observational hooks, and clamps the timeout to 30 s so two round trips cannot eat a
602
+ tool's whole 120 s budget. `--on-hook-timeout error` also marks the request failed.
603
+ - **Every `hook_request` gets exactly one `hook_resolved`** (`answered`, `timeout`,
604
+ `aborted`, `invalid`, `saturated`, `unregistered`), so your bookkeeping can close.
605
+ - **Hooks do not wrap caller tools.** Tools you register with `tool_register` are yours,
606
+ executed as given: redact inside your own implementation (I-H11).
607
+ - **Hooks are per thread.** A hook registered on `tenant-a` never sees `tenant-b`'s data.
608
+
609
+ `packages/cli/examples/bridge-host-threads.mjs` is a complete host: two threads, one redactor, per-tenant
610
+ accounting. `node packages/cli/examples/bridge-host-threads.mjs --dry-run` prints the frames without
611
+ spawning anything.
612
+
613
+
614
+ ## Hand back a typed result
615
+
616
+ `--result-schema <path>` replaces "the last paragraph the model happened to write"
617
+ with **an object that satisfies your JSON Schema**. Nothing in the answer is left to
618
+ a parser you have to write and then distrust.
619
+
620
+ ```json
621
+ {"type":"object",
622
+ "properties":{"findings":{"type":"array","items":{"type":"string"}},"risk":"low|medium|high"},
623
+ "required":["findings"],"additionalProperties":false}
624
+ ```
625
+
626
+ ```bash
627
+ aragon exec --output-format json --result-schema ./findings.json \
628
+ "List the risks in packages/cli/src/exec/options.ts"
629
+ ```
630
+
631
+ ```json
632
+ {"type":"result","isError":false,"stopReason":"end_turn","exitCode":0,
633
+ "result":"{\"findings\":[\"…\"],\"risk\":\"low\"}",
634
+ "structured":{"value":{"findings":["…"],"risk":"low"},"schemaName":"findings.json",
635
+ "schemaBytes":183,"attempts":1,"acceptedAt":1730000000000},
636
+ "text":"Here is what I found, with two caveats.","...":"..."}
637
+ ```
638
+
639
+ Four things to know, because each one is a place the obvious assumption is wrong:
640
+
641
+ 1. **`result.result` becomes the JSON text, and the prose moves to `result.text`.**
642
+ The `| jq -r .result` idiom in the CI section above keeps working, and under a
643
+ schema "the answer" is the object. Nothing is lost: the model's own words are in
644
+ `text` (and, for a stream, in the `assistant` events).
645
+ 2. **Failure is a shape, not a mood.** If the model never submits something that
646
+ validates, `result.structured` is an **explicit `null`** and
647
+ `error.code` is `result_schema_unsatisfied`. An absent field would be
648
+ indistinguishable from an older build that never had the feature, which is why the
649
+ failure case is `null` rather than missing.
650
+ 3. **The model gets told what was wrong, inside the same run.** A rejected submission
651
+ returns the failing field paths to the model, and `--result-repair-turns` (default
652
+ `1`, max `2`) lets it fix them and submit again. A repair is an ordinary turn, so
653
+ `--max-turns` still binds - and a schema run needs at least two of them, which is
654
+ why `--max-turns 1` plus `--result-schema` prints a warning rather than a refusal.
655
+ 4. **Validation has two layers, and you can see which one you have.** With `ajv`
656
+ installed, every JSON Schema keyword is enforced. Without it (it is an *optional*
657
+ dependency of Core) `required`, nested `properties`, `items` and
658
+ `additionalProperties: false` are still enforced by this CLI's own bounded
659
+ validator; `pattern`, `format` and `multipleOf` are not. `aragon doctor` prints
660
+ `ajv: present|absent`, and `aragon info --json` reports it as `result.ajv` - the
661
+ strength of the check is a property of the machine, so it is stated rather than
662
+ implied.
663
+
664
+ `--on-result-unsatisfied error|continue` decides what an unsubmitted result means:
665
+ `error` (the default) ends the run with exit `1`, `continue` keeps a long-lived bridge
666
+ process serving the next request with exit `0` — and still sets `result.isError: true`,
667
+ because a script that reads the exit code and a script that reads the JSON must not be
668
+ told different things.
669
+
670
+ ## One request at a time: budgets, billing and cancellation
671
+
672
+ With `--input-format bridge` a single process serves many `prompt` frames. Process
673
+ budgets (`--max-turns`, `--max-duration`) bound the whole process, which is the wrong
674
+ unit for a backend serving several users: one request can spend the entire allowance,
675
+ and there is no way to stop just that one.
676
+
677
+ Every `prompt` now ends with its own account:
678
+
679
+ ```json
680
+ {"type":"request_end","requestId":"u1","requestSeq":1,"stopReason":"completed",
681
+ "isError":false,"turns":2,"durationMs":8123,
682
+ "usage":{"inputTokens":1234,"outputTokens":567,"totalTokens":1801},
683
+ "cost":{"amount":0.0123,"currency":"USD","known":true},
684
+ "result":"…","error":null,"seq":17}
685
+ ```
686
+
687
+ The numbers are a **difference**, not a snapshot: two `request_end` usages add up to
688
+ the process `result.usage`, and one request's `cost` is what running that request
689
+ alone would have cost. `cost.known: false` means this model has no price table -
690
+ reported as "unknown", never as a free ride.
691
+
692
+ Two flags, both bridge-only, both absent by default:
693
+
694
+ ```bash
695
+ # Two turns and at most 30 s per request; the process outlives both.
696
+ aragon exec --input-format bridge --output-format stream-json \
697
+ --request-max-turns 2 --request-max-duration 30000 < prompts.ndjson
698
+ ```
699
+
700
+ A request that runs out of budget ends with `request_end{stopReason:"max_turns"}`
701
+ (or `"timeout"`) and **the process keeps serving**. The process-level `stopReason`
702
+ and exit code are untouched: one user's ceiling is not the run's failure.
703
+
704
+ And one request can be withdrawn without taking the process down:
705
+
706
+ ```json
707
+ {"type":"cancel_request","id":"c1","requestId":"u2"}
708
+ {"type":"ack","id":"c1","requestId":"u2","accepted":true,"code":"queued_cancelled"}
709
+ {"type":"request_end","requestId":"u2","stopReason":"cancelled","isError":false,"turns":0,"...":"..."}
710
+ ```
711
+
712
+ - A request still **queued** is deleted, and its `request_end` has `turns: 0` and no
713
+ `requestSeq` — it was never served, so it never had one.
714
+ - A request **in flight** is stopped (`ack{accepted:true}`) and reports
715
+ `stopReason: "cancelled"`; the next `prompt` runs normally.
716
+ - A request that is **already finished** answers `ack{accepted:false,
717
+ code:"unknown_turn"}` and aborts nothing. That is deliberate and load-bearing: a
718
+ late cancel for a completed request must never kill the next one.
719
+
720
+ Use `interrupt` to stop the run (process-level, `128 + signo`) and `cancel_request` to
721
+ stop one request. Two different questions, two frames.
333
722
 
334
723
  ## Usage
335
724
 
@@ -553,6 +942,7 @@ ignores them rather than rejecting the library):
553
942
  | `/thinking <level>` | Set the thinking level |
554
943
  | `/max-tokens [<n>\|auto\|default]` | Output token cap; with no argument, reports the setting **and** the effective cap for the live model |
555
944
  | `/agents` | Open the **subagent board**: every child of the current (or most recent) dispatch, its full facts, its result and its 24-event timeline. The same board as `Ctrl+B`. Read-only - it can never nudge, kill or re-dispatch a child |
945
+ | `/subagent [<brief>]` | **Force the next turn to fan out.** With no argument it reports the live capacity, the subagent model, the supervisor and the team switch. With a brief it submits one directive telling the lead to call `task` once with that wide a team: `-n 4` fixes the count, `--parts api,ui,docs` names the split, `--read-only` denies every child writes and shell. A count above `team.maxSubagents` is narrowed **and disclosed**, and `-n 0` is an error rather than a silent 1. The forcing is instruction-level, not mechanical: the proof is the `task` card in the next turn |
556
946
  | `/tools` | List active tools |
557
947
  | `/clear` | Start a new conversation: interrupt the run, archive the old session, clear the context, transcript, queues and todo panel. `/clear screen` only clears the screen |
558
948
  | `/reset` | Alias of `/clear` - one implementation, so the two cannot drift apart |
@@ -587,8 +977,11 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
587
977
 
588
978
  ## Keybindings
589
979
 
980
+ `Ctrl+G` 打开**快捷键抽屉**,把下面所有键位连同「作用关键词」和选中行的说明一次性列出来;`/keys` 打开同一份清单。详见 [快捷键抽屉与任务摘要](#快捷键抽屉与任务摘要)。
981
+
590
982
  | Key | Action |
591
983
  | --- | --- |
984
+ | `Ctrl+G` | **快捷键抽屉**:自上而下列出全部快捷键、各自的作用关键词,选中行下方再给一句话说明。`Up`/`Down`(或 `k`/`j`)移动、`PgUp`/`PgDn` 翻页、`d` 切换状态详情行、`r` 重新生成任务摘要、`Esc` 关闭。再按一次 `Ctrl+G` 关闭;其它参考类浮层打开时按它会直接接管,等待决策的 `confirm` / `question` / `plan` 除外 |
592
985
  | `Enter` | 空闲时发送,运行时加入队列;收到 Core 已纳入会话的接收凭据后才移除 Queue 提示 |
593
986
  | `Ctrl+J` / `Alt+Enter` / `Shift+Enter` | 插入换行。应用默认请求终端区分修饰键(Windows 用 win32-input-mode,macOS/Linux 用 kitty/modifyOtherKeys),Shift+Enter 在支持的终端开箱即用;不支持的终端用 Ctrl+J。参见 [终端设置](#terminal-setup-for-shiftenter) |
594
987
  | `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan`. On Windows this needs a console flag Node < 22.17 does not set; aragon sets it at startup — see [Requirements](#requirements) |
@@ -655,6 +1048,55 @@ Turn the whole thing off with `--no-mouse`, `ARAGON_MOUSE=0`, or
655
1048
  nothing in full-screen mode — it does not fall back to editing your draft, which
656
1049
  is what it used to do. Inline mode is unaffected either way.
657
1050
 
1051
+ ## 快捷键抽屉与任务摘要
1052
+
1053
+ ### 抽屉
1054
+
1055
+ 头部右上角现在只有一件事:`^G more`。按下去,右侧滑出一块只读的抽屉,把**所有**
1056
+ 快捷键自上而下列出来——每一行是「键 + 作用关键词」,选中行下面再用一句话解释它是
1057
+ 干什么的。`Ctrl+G` 再按一次关闭;任何其它浮层打开时按它,抽屉会直接接管(而不是
1058
+ 被静默吞掉)——只有 `confirm` / `question` / `plan` 这类**等你决策**的浮层例外:
1059
+ 它们只能由 `Esc` 收尾,抽屉不会把键盘抢走,头部此刻显示的也正是 `Esc close`。`/keys [query]` 是同一个入口的命令面:脚本、粘贴和读屏软件都得有路
1060
+ 可走。
1061
+
1062
+ - 分组固定为 `COMPOSE / RUN / PANELS / SESSION / VIEW`;窗口够高时组头单独成行,
1063
+ 80×24 这种常见尺寸下组头让位,把行数留给你真正要看的键。
1064
+ - 关键词列**从表里推导**(`min(最长键, ceil(内容宽 * 0.55))`),不是写死的常量:
1065
+ 任何一条关键词都**不会被截成残词**,放不下的那一行改成「键一行、关键词缩进一行」
1066
+ 堆叠显示,只有那一行变高。
1067
+ - 翻页 = 移动光标一页。窗口永远覆盖选中行,所以「这个键是干什么的」那句话不会滑出
1068
+ 屏幕。
1069
+ - 抽屉里只有两个动作键:`d` 切换状态详情行(原来 `Ctrl+G` 的功能搬到这里),
1070
+ `r` 重新生成任务摘要。回车不会启动任何东西:误触成本最高的界面就是「按回车会开
1071
+ 新终端」的参考面。
1072
+
1073
+ ### 头部任务摘要
1074
+
1075
+ 「工作目录」右侧多了一格**任务摘要**:会话(或一次 `/clear` 之后的新任务)的第一
1076
+ 条提示词提交时,用**fast 模型**把它压成几个词,语言跟随提示词本身——中文提示词出
1077
+ 中文,英文出英文。它回答的是「我在干什么」,而 `provider:model` 回答「我在用什
1078
+ 么」。
1079
+
1080
+ - 一次任务只生成一次(头部是身份条,不是实时字幕);`/task refresh` 或抽屉里的
1081
+ `r` 是唯一的手动重算入口。
1082
+ - `/clear`、`/reset` 和会话切换都会清空它;`/clear screen` 只清屏,所以保留它。
1083
+ `/resume` 恢复会话时,用恢复后的第一条用户消息重建一次。
1084
+ - fast 档不可用、超时、被中止、模型乱写、语言不符——全部收敛到同一条降级:**用户
1085
+ 自己那句话的前缀**。它不可能编造,语言必然匹配,头部也不会突然空掉。
1086
+ `taskSummary.fallback=false` 时改为什么都不显示。
1087
+ - 摘要**不进**对话历史、不进会话文件、不进 `config.json`,它只是视图状态。
1088
+ - 隐私:提示词首句会被发到 fast 档所配置的供应商。`taskSummary.enabled=false`
1089
+ 是硬开关,也可以 `ARAGON_TASK_SUMMARY=0`。
1090
+
1091
+ | 键 / 命令 | 作用 |
1092
+ | --- | --- |
1093
+ | `Ctrl+G` | 打开 / 关闭快捷键抽屉 |
1094
+ | `/keys [query]` | 打开抽屉,`query` 按 id / 键位 / 关键词预过滤 |
1095
+ | `/task` | 打印当前任务摘要、来源和生成时间 |
1096
+ | `/task refresh` | 用最后一条用户消息重新生成摘要 |
1097
+ | `aragon config set taskSummary.enabled false` | 关掉摘要与那次模型调用 |
1098
+ | `aragon config set taskSummary.fallback false` | 失败时显示空,而不是用户原话前缀 |
1099
+
658
1100
  ## Quick prompt library
659
1101
 
660
1102
  `↑` history remembers what you said. It does not remember what you say *every*
@@ -1150,6 +1592,16 @@ questions.
1150
1592
  Cancelling either one is not an error. The agent is told to proceed on stated
1151
1593
  assumptions rather than to retry.
1152
1594
 
1595
+ **There is no limit on how many rounds of questions the agent may ask.** Call it
1596
+ for the fifth, tenth or twentieth time and it still works; the only bound is a
1597
+ 30-minute ceiling on a *single* wait (`planModeHumanTimeoutMs`), which exists so an
1598
+ unattended terminal cannot wedge a CI job — it says nothing about how often the
1599
+ agent may ask. The one thing the agent cannot do is re-ask a batch of questions you
1600
+ have already **answered** in the same turn: that call returns a note saying so, as
1601
+ a non-error result, and the overlay does not open. A *cancelled* round records
1602
+ nothing, so an `Esc` you did not mean leaves the question askable — a mis-touch
1603
+ must never cost you the chance to answer. `/plan status` reports only the mode.
1604
+
1153
1605
  **Dismissing a plan is not how you stop.** `Esc` on a plan card asks for a
1154
1606
  *better* plan — the agent is told to refine it and submit again — so a model that
1155
1607
  keeps submitting keeps getting cards. To stop the run itself, close the card with `Esc`, then **press `Esc`
@@ -1638,6 +2090,39 @@ back to the main model with a disclosed line in the dispatch report instead of
1638
2090
  failing the dispatch. The model itself still cannot downgrade its own
1639
2091
  delegation: a stale `model` key in a `task` call is ignored, exactly as before.
1640
2092
 
2093
+ **How wide a fan-out is also your setting.** `/settings` → `Subagent fan-out`
2094
+ (row four, directly under *Subagent model*) holds the two numbers and the
2095
+ supervisor switch: `Max subagents`, `Run at once` and `Supervisor`, adjusted
2096
+ with `Left` / `Right`. Nothing is written until you press `Ctrl+S` **on the main
2097
+ settings page** — the arrows edit a draft, and `Esc` on the fan-out page returns
2098
+ to the main page rather than saving. The same numbers are reachable without the
2099
+ screen: `/team max <n>`, `/team overseer on|off`, or
2100
+ `aragon config set team.maxSubagents <n>`.
2101
+
2102
+ Narrowing `Max subagents` also narrows `Run at once`, because a pool wider than
2103
+ its own population is meaningless; the follow-down is committed, so widening the
2104
+ population again does not silently restore a pool you did not ask for. And the
2105
+ width change never touches the model a session kit installed: only an explicit
2106
+ model choice releases that slot.
2107
+
2108
+ **Or bypass the model's judgement entirely with `/subagent`.** It compiles your
2109
+ intent into one user turn that tells the lead to call `task` once with that many
2110
+ children, so the escape hatch shares the single dispatch path — same Esc, same
2111
+ report, same spend accounting:
2112
+
2113
+ ```
2114
+ /subagent -n 6 review the error handling in packages/cli/src/team
2115
+ /subagent --parts api,ui,docs audit the request path
2116
+ /subagent --read-only --parts perf,memory profile the parser
2117
+ ```
2118
+
2119
+ With no argument it reports capacity, the subagent model, the supervisor and the
2120
+ team switch — and answers even in a `--no-team` session, where the honest answer
2121
+ is that this session has no team capability. Narrowing is always disclosed
2122
+ (`asked for 12; team.maxSubagents is 8`), `-n 0` is refused rather than silently
2123
+ becoming 1, and the notice says what the **next turn** will do rather than
2124
+ claiming children have started.
2125
+
1641
2126
  ```
1642
2127
  > read the auth middleware, map the route table, and check the migration
1643
2128
 
@@ -1652,12 +2137,18 @@ tools and the same working directory — but no memory of your conversation, so
1652
2137
  its brief has to be self-contained. It cannot delegate further (depth is capped
1653
2138
  at one by construction) and it cannot ask you anything.
1654
2139
 
1655
- **Delegate wide, not deep.** Two to four subagents is the usual size. The
1656
- default cap is 5 per dispatch and 3 in flight at once; the hard maximum is 10
1657
- and no config file can raise it. `maxConcurrent` is separate from `maxSubagents`
1658
- on purpose: five simultaneous streams against one API key is a reliable way to
1659
- collect rate-limit errors, and each one would land in the report as a failed
1660
- subagent.
2140
+ **Delegate wide, not deep.** Two to four subagents is usually enough. The
2141
+ default is **8 per dispatch and 8 in flight at once** (it was 5 and 3 before
2142
+ subagent-fanout); the hard maximum is 10 and no config file can raise it, so 8
2143
+ is a default you can move in either direction and 10 is the ceiling you cannot.
2144
+ Both numbers live in `/settings` (`Subagent fan-out`) and in
2145
+ `team.maxSubagents` / `team.maxConcurrent`.
2146
+
2147
+ `maxConcurrent` is separate from `maxSubagents` on purpose: eight simultaneous
2148
+ streams against one API key is a reliable way to collect rate-limit errors, and
2149
+ each one would land in the report as a failed subagent. **If you see rate-limit
2150
+ errors, lower `Run at once`** — that is the one number to change, and it is the
2151
+ only reason the two knobs are separate.
1661
2152
 
1662
2153
  **The report.** One text result: a header with the ok/failed counts, aggregate
1663
2154
  tokens and cost, then one section per subagent with its status, timing, the
@@ -2311,6 +2802,7 @@ This is the part that makes skills cheap enough to install a lot of:
2311
2802
  | Level | What the model gets | When |
2312
2803
  | --- | --- | --- |
2313
2804
  | 1 | Name, scope, and a one-line "what + when" | Always, in the system prompt. Whole catalog capped at 6 000 bytes. |
2805
+ | 1.5 | `<skill_triage>` protocol + up to 3 ranked `<skill_candidates>` | Always (protocol), rebuilt per task (candidates). ≤ 1 500 + ≤ 900 bytes. |
2314
2806
  | 2 | The full `SKILL.md` body + a list of bundled files | Only when the model calls `skill(name=…)` |
2315
2807
  | 3 | The bundled files themselves | Only when it reads them with `read_file` / `bash` |
2316
2808
 
@@ -2318,6 +2810,34 @@ A skill costs a line in the prompt rather than its whole body, so installing
2318
2810
  many of them stays cheap. The model decides on its own whether a task matches a
2319
2811
  skill — you do not have to name one.
2320
2812
 
2813
+ **Level 1.5 is what turns that from a claim into a step.** Reading a catalog is
2814
+ not the same as being told to read it: the catalog's own preamble is a statement
2815
+ ("if a task matches one, load it"), and a statement the model has seen forty times
2816
+ is the one it skims. So the prompt now carries a `<skill_triage>` protocol that
2817
+ fixes *when* the decision happens — after the project guidance and the directory
2818
+ listing, before any plan or edit — plus a short candidate list computed from your
2819
+ message against the in-memory skill records (name, description, keywords). Both are
2820
+ local string work: no model call, no file read, no body loaded, so "which skills
2821
+ could apply?" costs zero context bytes beyond the block itself.
2822
+
2823
+ Two consequences worth knowing:
2824
+
2825
+ - **It is a guess, not a verdict.** The block says so in as many words, and the
2826
+ model is told to load nothing when nothing fits. It never loads a skill for you.
2827
+ - **Silence on success, one line on failure.** When a strongly-matching skill was
2828
+ available and the turn did real work without loading it, the run ends with one
2829
+ `info` line naming the skill and pointing at `/skills route`. That happens at
2830
+ most once per skill per session — a message printed every turn is a message you
2831
+ learn to skip.
2832
+
2833
+ `/skills route <text>` shows the scoring for any phrasing you type (or with no
2834
+ argument, for the last real task), including the runner-up and the threshold when
2835
+ nothing qualified. `/skills triage off` turns the whole level off for the session
2836
+ and `skills.triage=false` makes it permanent; either way the prompt returns to its
2837
+ pre-feature bytes. **Subagents are not triaged** — a child has no `skill` tool, so
2838
+ proposing skills to it would describe a capability it does not have; name the skill
2839
+ in the `task` description instead.
2840
+
2321
2841
  At Level 2 the file list is split into what to read and what to run, and carries
2322
2842
  the skill's absolute root so the model can build real paths. The accompanying
2323
2843
  guidance is **platform-specific**: on Windows the `bash` tool runs `cmd.exe`, so
@@ -2393,7 +2913,8 @@ keeping `installedAt` and recording `updatedAt` + `previousVersion`.
2393
2913
 
2394
2914
  Interactively the same lives under `/skills …` (`/skills update <name> [--force]`,
2395
2915
  `/skills update --all`, `/skills list --sort=recent`, `/skills policy [mode]`,
2396
- `/skills unload`, `/skills usage [--reset]`). Every usable skill also becomes a
2916
+ `/skills unload`, `/skills usage [--reset]`, `/skills triage [on|off]`,
2917
+ `/skills route [text]`). Every usable skill also becomes a
2397
2918
  slash command:
2398
2919
 
2399
2920
  ```
@@ -2630,13 +3151,14 @@ env / `.env` → CLI flags**.
2630
3151
  | `renderGovernor` | `true` | Widen the streaming frame interval when a frame is genuinely expensive. See [Render performance](#render-performance). |
2631
3152
  | `maxRenderIntervalMs` | `320` | Ceiling of the governor ladder, clamped to `[33, 1000]`. `33` flattens it to the pre-0.6 cadence without disabling the mechanism. |
2632
3153
  | `planModeDefault` | `false` | Start every session in plan mode. `--no-plan` still overrides it for a single run. |
2633
- | `planModeMaxAskRounds` | `4` | `ask_user` rounds allowed per user message, clamped to `[1, 10]`. The counter resets on your next message; steering does **not** reset it. |
3154
+ | ~~`planModeMaxAskRounds`~~ | — | **Removed.** `ask_user` has no round limit any more, so there is nothing to configure. `aragon config set planModeMaxAskRounds 5` now prints an explanation and exits `2`; an existing key is removed from `config.json` at the next write. |
2634
3155
  | `planModeHumanTimeoutMs` | `1800000` | Ceiling on a single question / plan wait, clamped to `[60000, 7200000]`. Thirty minutes is far outside "went to get coffee", but a ceiling has to exist so an unattended terminal cannot wedge a CI job forever. |
2635
3156
  | `paste` | `true` | Recognise pasted text before Ink's key parser sees it: line breaks become buffer newlines instead of fifteen separate messages, escape SEQUENCES are removed whole (deleting only the introducer byte is what put `[38;2;18;52;86m` on screen and in the model's input) as are stray control bytes, and a paste above 6 lines or 400 characters collapses to a one-line placeholder that expands when you send. `false` treats pasted bytes as keystrokes, which is what 0.6.2 did. |
2636
3157
  | `historyEnabled` | `true` | Record submitted prompts to `prompt-history.jsonl` for `↑` recall. Setting it to `false` stops new writes; it does not delete what is already stored (that is `aragon history clear --yes`) and does not stop reading. See [Prompt history](#prompt-history). |
2637
3158
  | `team.enabled` | `true` | Master switch for team subagents. `false` means the `task` tool is never registered — `/team on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
2638
- | `team.maxSubagents` | `5` | Subagents per dispatch, clamped to `[1, 10]`. The 10 is the hard ceiling and is enforced twice — on the config value and again on what the model asks for — so hand-editing this file cannot raise it. |
2639
- | `team.maxConcurrent` | `3` | Subagents in flight at once, clamped to `[1, 10]` and capped at `maxSubagents`. A separate knob because provider rate limits are a different constraint from context economics. |
3159
+ | `team.maxSubagents` | `8` | Subagents per dispatch, clamped to `[1, 10]`. 8 is the default; 10 is the hard ceiling and is enforced twice — on the config value and again on what the model asks for — so hand-editing this file cannot raise it. A persisted `5`/`3` pair from an older install is migrated to `8`/`8` once (`fanoutMigrated` records that it happened, so moving back to 5/3 sticks). |
3160
+ | `team.maxConcurrent` | `8` | Subagents in flight at once, clamped to `[1, 10]` and capped at `maxSubagents`. A separate knob because provider rate limits are a different constraint from context economics: lower this one if you collect 429s. |
3161
+ | `team.fanoutMigrated` | absent | Written only by the one-shot fan-out migration. Never set it by hand; a `true` here tells the next launch "5/3 was my choice, leave it alone". |
2640
3162
  | `team.subagentTimeoutMs` | `0` | One subagent's wall clock, clamped to `[0, 1800000]`. With `team.overseer: false` it is a hard abort (the legacy regime). With the supervisor on (the default) it **never aborts anything** — a positive value is only a compatibility source for the supervisor's first cadence check. `0` (the default) = use `team.overseerIntervalMs` / the structural 5 min instead. |
2641
3163
  | `team.dispatchTimeoutMs` | `0` | The whole dispatch's wall clock (and the `task` tool timeout), clamped to `[0, 3600000]`. `0` (the default) = no ceiling; only Esc ends a dispatch early. |
2642
3164
  | `team.maxTurnsPerSubagent` | `0` | Runaway-loop cap, clamped to `[0, 100]`. `0` (the default) = no cap, matching the lead. A subagent stopped at a positive cap is reported as `stopped: turn cap reached`, with whatever it had produced. |
@@ -2667,6 +3189,7 @@ env / `.env` → CLI flags**.
2667
3189
  | `skills.integrity` | `warn` | What to do when `SKILL.md` no longer matches the copy you approved. `off` skips the check; `warn` flags it in `/skills list` and once per session; `strict` removes it from the catalog and makes `skill()` refuse it. Defaults to `warn` because editing an installed skill is a legitimate thing to do — `strict` suits CI and managed environments. |
2668
3190
  | `skills.usageTracking` | `true` | Keep local per-skill use counters to rank the catalog. Name, count and timestamp only; never transmitted. Inspect with `aragon skills usage`, erase with `--reset --yes`. |
2669
3191
  | `skills.toolPolicy` | `enforce` | How hard a skill's `allowed-tools` bites, for the turn it is loaded in. `off` disables it and says nothing about it to the model; `warn` runs the tool and reports it; `enforce` refuses. Unlike `skills.integrity`, a false positive here means a skill AUTHOR under-declared their own skill — which `aragon skills doctor` reports before it can bite. |
3192
+ | `skills.triage` | `true` | Put a short `<skill_candidates>` list in the system prompt for each new task, so "is an installed skill relevant?" is a step the agent is required to take instead of one it has to remember. Default on; turning it off restores the pre-feature prompt byte for byte. Matching is local over in-memory skill records — no model call, no file read. Explain any decision with `/skills route`, flip it live with `/skills triage off`. |
2670
3193
  | `log.level` | `info` | `silent` · `error` · `warn` · `info` · `debug` · `trace`. See [Files & logs](#files--logs) for what each level records. |
2671
3194
  | `log.toFile` | `true` | Write records to a file at all. `false` here is respected; `--log-file` overrides it for one run. |
2672
3195
  | `log.dir` | `""` | Log directory. Empty means `<home>/logs`. Only **absolute** paths are accepted — a relative one would mean a different directory for every working directory you launch from. |
@@ -3154,6 +3677,16 @@ VS Code 用户设置添加 `"terminal.integrated.copyOnSelection": false`,快
3154
3677
 
3155
3678
  编辑方案时可以当场验证:**Test connection**(Ctrl+R)用草稿里的服务商、模型、地址和解析后的密钥做一次真实往返,成功显示 `OK 812 ms` 与用量,失败给出分类结论和第二行“怎么办”;**Discover models**(Ctrl+F)列出该密钥实际可访问的模型供选择,只写回模型字段,不动上下文窗口与凭据。两者都作用于未保存的草稿,`Discover` 零 token;探测期间 Esc 只取消探测而不丢弃表单,修改服务商、模型或密钥后旧结论立刻回到未测试。
3156
3679
 
3680
+ ## 子代理扇出宽度与 `/subagent`
3681
+
3682
+ 子代理扇出的默认值现在是**每次派单 8 个、同时最多 8 个在跑**(原为 5 和 3)。用户感知的“最多只有 3 个”来自并发上限而不是派单上限——只把派单数改大而并发不动,观感不会有任何变化,所以两个数一起改。硬上限仍是 10:8 是默认值,10 是天花板,中间由用户自己决定。
3683
+
3684
+ 在 `/settings` 的第 4 行 **Subagent fan-out** 里可以改这三个值:**Max subagents**、**Run at once**、**Supervisor**,用左右方向键调整。页内只改草稿,回到根设置页按 Ctrl+S 才真正落盘(本页页脚如实写着这一点,因为子页上根本没有 Ctrl+S 监听者)。把扇出调窄时并发会跟着下调,并且这个跟随**会写进补丁**:再把扇出调宽时并发停在你选的位置,不会被悄悄拉回。只改宽度**不会**释放会话 kit 给的子代理模型,只有显式改子代理模型才会。
3685
+
3686
+ 磁盘上仍带着旧默认值 5/3 的配置会在首次启动时被一次性迁移到 8/8,并写下 `team.fanoutMigrated` 标记;之后你手工改回 5/3 会一直保持,不会被再次迁移。其他任何组合(2/2、5/5、10/10)都不迁移。
3687
+
3688
+ 想绕开“模型自己决定要不要派单”时用 **`/subagent`**:它把你的意图编译成一条用户回合,要求 lead 在同一条 `task` 路径上一次性派出指定数量的子代理,因此 Esc、报告、费用记账、并发锁全部沿用既有实现。`-n 6` 指定数量,`--parts api,ui,docs` 指定拆分维度,`--read-only` 让每个子代理都不能写文件也不能跑 shell。不带参数时打印容量、子代理模型、监察开关与 team 开关,`--no-team` 会话里也照实回答。参数被收窄一定伴随披露(`asked for 12; team.maxSubagents is 8`),`-n 0` 直接报错而不是静默变成 1;提示语说的是“下一轮会派单”,不会假装子代理已经开始。并发报 429 时把 **Run at once** 调低即可。
3689
+
3157
3690
  **Current custom** 保留旧式连接,可继续编辑原连接字段。创建方案时从磁盘上的旧式配置预填,不复制环境变量或 `--api-key` 的运行时密钥。专属密钥可保持、替换或切换为共享/环境凭据;修改服务商或地址后,必须明确选择保留原密钥,或重新输入。空地址在方案中表示服务商默认地址,快速方案不会继承主网关。
3158
3691
 
3159
3692
  启动优先级为 CLI 参数、环境变量、所选方案、旧式字段、默认值。界面显示启动覆盖;明确重新选择方案可对本次会话立即生效,重启仍遵循启动参数。专属密钥仅对对应角色和目标地址生效;主模型启动密钥不会跟随切换发往另一个网关。