@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.
- package/CHANGELOG.md +263 -0
- package/README.md +544 -11
- package/dist/agent/controller.d.ts +245 -10
- package/dist/agent/controller.js +367 -22
- package/dist/agent/model-profile-settings.js +12 -1
- package/dist/agent/plan-prompt.d.ts +1 -3
- package/dist/agent/plan-prompt.js +13 -7
- package/dist/agent/reducer.d.ts +70 -2
- package/dist/agent/reducer.js +59 -6
- package/dist/agent/skill-triage-prompt.d.ts +42 -0
- package/dist/agent/skill-triage-prompt.js +74 -0
- package/dist/agent/system-prompt.d.ts +28 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +106 -32
- package/dist/commands/builtins.js +24 -3
- package/dist/commands/keys-command.d.ts +23 -0
- package/dist/commands/keys-command.js +38 -0
- package/dist/commands/registry.d.ts +39 -0
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +25 -0
- package/dist/commands/subagent-command.js +242 -0
- package/dist/commands/task-command.d.ts +39 -0
- package/dist/commands/task-command.js +102 -0
- package/dist/compaction/command.js +16 -3
- package/dist/compaction/compactor.d.ts +25 -0
- package/dist/compaction/compactor.js +111 -10
- package/dist/compaction/digest.d.ts +24 -1
- package/dist/compaction/digest.js +139 -17
- package/dist/compaction/limits.d.ts +21 -0
- package/dist/compaction/limits.js +21 -0
- package/dist/compaction/reason-text.d.ts +36 -0
- package/dist/compaction/reason-text.js +78 -0
- package/dist/compaction/types.d.ts +82 -14
- package/dist/compaction/wiring.d.ts +13 -8
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +23 -0
- package/dist/config/cli-commands.js +36 -0
- package/dist/config/env.js +17 -4
- package/dist/config/load.js +55 -3
- package/dist/config/schema.d.ts +125 -10
- package/dist/config/schema.js +138 -14
- package/dist/config/store.js +23 -3
- package/dist/diagnostics/doctor.d.ts +9 -0
- package/dist/diagnostics/doctor.js +34 -0
- package/dist/diagnostics/info.d.ts +71 -0
- package/dist/diagnostics/info.js +91 -5
- package/dist/exec/bridge/caller-tools.d.ts +96 -0
- package/dist/exec/bridge/caller-tools.js +213 -0
- package/dist/exec/bridge/decisions.d.ts +78 -0
- package/dist/exec/bridge/decisions.js +116 -0
- package/dist/exec/bridge/human-answers.d.ts +38 -0
- package/dist/exec/bridge/human-answers.js +164 -0
- package/dist/exec/bridge/limits.d.ts +68 -0
- package/dist/exec/bridge/limits.js +68 -0
- package/dist/exec/bridge/ndjson.d.ts +17 -0
- package/dist/exec/bridge/ndjson.js +83 -0
- package/dist/exec/bridge/pending.d.ts +70 -0
- package/dist/exec/bridge/pending.js +132 -0
- package/dist/exec/bridge/protocol.d.ts +346 -0
- package/dist/exec/bridge/protocol.js +256 -0
- package/dist/exec/bridge/server.d.ts +142 -0
- package/dist/exec/bridge/server.js +502 -0
- package/dist/exec/bridge/source.d.ts +58 -0
- package/dist/exec/bridge/source.js +145 -0
- package/dist/exec/bridge/writer.d.ts +26 -0
- package/dist/exec/bridge/writer.js +156 -0
- package/dist/exec/cli-commands.js +30 -2
- package/dist/exec/emitter.d.ts +16 -2
- package/dist/exec/emitter.js +26 -6
- package/dist/exec/events.d.ts +465 -10
- package/dist/exec/events.js +47 -1
- package/dist/exec/hooks/dispatch.d.ts +76 -0
- package/dist/exec/hooks/dispatch.js +169 -0
- package/dist/exec/hooks/index.d.ts +83 -0
- package/dist/exec/hooks/index.js +205 -0
- package/dist/exec/hooks/limits.d.ts +56 -0
- package/dist/exec/hooks/limits.js +49 -0
- package/dist/exec/hooks/registry.d.ts +49 -0
- package/dist/exec/hooks/registry.js +91 -0
- package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
- package/dist/exec/hooks/tool-wrapper.js +154 -0
- package/dist/exec/hooks/turn.d.ts +61 -0
- package/dist/exec/hooks/turn.js +59 -0
- package/dist/exec/hooks/validate.d.ts +85 -0
- package/dist/exec/hooks/validate.js +310 -0
- package/dist/exec/index.d.ts +130 -4
- package/dist/exec/index.js +501 -10
- package/dist/exec/options.d.ts +124 -2
- package/dist/exec/options.js +413 -6
- package/dist/exec/permission.d.ts +9 -1
- package/dist/exec/permission.js +7 -1
- package/dist/exec/request/limits.d.ts +51 -0
- package/dist/exec/request/limits.js +49 -0
- package/dist/exec/request/scope.d.ts +106 -0
- package/dist/exec/request/scope.js +149 -0
- package/dist/exec/result/limits.d.ts +74 -0
- package/dist/exec/result/limits.js +72 -0
- package/dist/exec/result/prompt.d.ts +28 -0
- package/dist/exec/result/prompt.js +74 -0
- package/dist/exec/result/schema.d.ts +99 -0
- package/dist/exec/result/schema.js +464 -0
- package/dist/exec/result/sink.d.ts +56 -0
- package/dist/exec/result/sink.js +50 -0
- package/dist/exec/result/tool.d.ts +37 -0
- package/dist/exec/result/tool.js +118 -0
- package/dist/exec/runner.d.ts +395 -1
- package/dist/exec/runner.js +860 -8
- package/dist/exec/threads/aggregate.d.ts +34 -0
- package/dist/exec/threads/aggregate.js +30 -0
- package/dist/exec/threads/capacity.d.ts +23 -0
- package/dist/exec/threads/capacity.js +38 -0
- package/dist/exec/threads/dispatch-source.d.ts +60 -0
- package/dist/exec/threads/dispatch-source.js +106 -0
- package/dist/exec/threads/execute-threads.d.ts +53 -0
- package/dist/exec/threads/execute-threads.js +234 -0
- package/dist/exec/threads/gate.d.ts +42 -0
- package/dist/exec/threads/gate.js +81 -0
- package/dist/exec/threads/identity.d.ts +36 -0
- package/dist/exec/threads/identity.js +67 -0
- package/dist/exec/threads/interrupt.d.ts +29 -0
- package/dist/exec/threads/interrupt.js +79 -0
- package/dist/exec/threads/limits.d.ts +58 -0
- package/dist/exec/threads/limits.js +35 -0
- package/dist/exec/threads/outcome.d.ts +47 -0
- package/dist/exec/threads/outcome.js +26 -0
- package/dist/exec/threads/registry.d.ts +81 -0
- package/dist/exec/threads/registry.js +427 -0
- package/dist/exec/threads/runtime.d.ts +107 -0
- package/dist/exec/threads/runtime.js +523 -0
- package/dist/exec/threads/thread-source.d.ts +20 -0
- package/dist/exec/threads/thread-source.js +134 -0
- package/dist/fast/limits.d.ts +54 -0
- package/dist/fast/limits.js +54 -0
- package/dist/fast/summary-call.d.ts +72 -0
- package/dist/fast/summary-call.js +133 -0
- package/dist/fast/summary-prompt.d.ts +106 -0
- package/dist/fast/summary-prompt.js +320 -0
- package/dist/fast/task-summary-types.d.ts +66 -0
- package/dist/fast/task-summary-types.js +17 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/limits.d.ts +7 -0
- package/dist/proc/limits.js +7 -0
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- package/dist/proc/output-streams.d.ts +50 -0
- package/dist/proc/output-streams.js +54 -0
- package/dist/skills/service.d.ts +41 -0
- package/dist/skills/service.js +62 -0
- package/dist/skills/triage.d.ts +194 -0
- package/dist/skills/triage.js +393 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/limits.d.ts +21 -1
- package/dist/team/limits.js +21 -1
- package/dist/team/normalize.d.ts +15 -0
- package/dist/team/normalize.js +21 -4
- package/dist/team/prompt.d.ts +6 -3
- package/dist/team/prompt.js +9 -5
- package/dist/team/runtime.d.ts +9 -0
- package/dist/team/runtime.js +8 -0
- package/dist/team/subagent.d.ts +15 -0
- package/dist/team/subagent.js +45 -1
- package/dist/team/task-tool.js +11 -7
- package/dist/tools/bash-tool.d.ts +10 -0
- package/dist/tools/bash-tool.js +71 -6
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/index.d.ts +93 -1
- package/dist/tools/index.js +112 -3
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/tools/tool-gate.d.ts +100 -0
- package/dist/tools/tool-gate.js +87 -0
- package/dist/ui/App.js +238 -12
- package/dist/ui/Header.d.ts +28 -16
- package/dist/ui/Header.js +21 -19
- package/dist/ui/StatusBar.d.ts +1 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +11 -1
- package/dist/ui/entries/CompactionCard.js +65 -4
- package/dist/ui/interaction-hints.js +6 -0
- package/dist/ui/layout/OverlayFrame.d.ts +18 -0
- package/dist/ui/layout/OverlayFrame.js +19 -4
- package/dist/ui/layout/header-hints.d.ts +55 -62
- package/dist/ui/layout/header-hints.js +59 -166
- package/dist/ui/layout/header-left.d.ts +115 -0
- package/dist/ui/layout/header-left.js +206 -0
- package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
- package/dist/ui/layout/shortcut-panel-layout.js +321 -0
- package/dist/ui/layout/status-detail-layout.js +5 -1
- package/dist/ui/layout/status-layout.d.ts +10 -0
- package/dist/ui/layout/status-layout.js +18 -1
- package/dist/ui/layout/task-summary-cell.d.ts +61 -0
- package/dist/ui/layout/task-summary-cell.js +65 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
- package/dist/ui/overlays/HelpOverlay.js +29 -2
- package/dist/ui/overlays/SettingsScreen.js +29 -5
- package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
- package/dist/ui/overlays/ShortcutPanel.js +106 -0
- package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
- package/dist/ui/overlays/TeamFanoutPage.js +78 -0
- package/dist/ui/overlays/model-profile-state.d.ts +19 -1
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
- package/dist/ui/overlays/team-fanout-state.js +97 -0
- package/dist/ui/shortcuts.d.ts +81 -0
- package/dist/ui/shortcuts.js +221 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -305,7 +305,7 @@ printf '%s\n' \
|
|
|
305
305
|
|
|
306
306
|
### Limits
|
|
307
307
|
|
|
308
|
-
|
|
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
|
|
1656
|
-
default
|
|
1657
|
-
and no config file can raise it
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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]
|
|
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
|
-
|
|
|
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` | `
|
|
2639
|
-
| `team.maxConcurrent` | `
|
|
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 参数、环境变量、所选方案、旧式字段、默认值。界面显示启动覆盖;明确重新选择方案可对本次会话立即生效,重启仍遵循启动参数。专属密钥仅对对应角色和目标地址生效;主模型启动密钥不会跟随切换发往另一个网关。
|