@aragon-agent/cli 0.6.39 → 0.6.42

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 (83) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +126 -20
  3. package/dist/agent/controller.d.ts +139 -14
  4. package/dist/agent/controller.js +299 -40
  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 +29 -1
  9. package/dist/agent/reducer.js +34 -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 +33 -2
  13. package/dist/agent/system-prompt.js +12 -4
  14. package/dist/cli.js +21 -5
  15. package/dist/commands/builtins.js +28 -185
  16. package/dist/commands/skills.js +86 -1
  17. package/dist/commands/subagent-command.d.ts +30 -0
  18. package/dist/commands/subagent-command.js +39 -0
  19. package/dist/commands/team-command.d.ts +49 -0
  20. package/dist/commands/team-command.js +259 -0
  21. package/dist/compaction/command.js +16 -3
  22. package/dist/compaction/compactor.d.ts +25 -0
  23. package/dist/compaction/compactor.js +111 -10
  24. package/dist/compaction/digest.d.ts +24 -1
  25. package/dist/compaction/digest.js +139 -17
  26. package/dist/compaction/limits.d.ts +21 -0
  27. package/dist/compaction/limits.js +21 -0
  28. package/dist/compaction/reason-text.d.ts +36 -0
  29. package/dist/compaction/reason-text.js +78 -0
  30. package/dist/compaction/types.d.ts +82 -14
  31. package/dist/compaction/wiring.d.ts +13 -8
  32. package/dist/compaction/wiring.js +1 -0
  33. package/dist/config/load.js +50 -3
  34. package/dist/config/model-kits.d.ts +2 -1
  35. package/dist/config/schema.d.ts +82 -6
  36. package/dist/config/schema.js +107 -9
  37. package/dist/config/store.js +10 -4
  38. package/dist/proc/log-mirror.d.ts +21 -4
  39. package/dist/proc/log-mirror.js +29 -7
  40. package/dist/skills/service.d.ts +41 -0
  41. package/dist/skills/service.js +62 -0
  42. package/dist/skills/triage.d.ts +194 -0
  43. package/dist/skills/triage.js +393 -0
  44. package/dist/team/dispatch-gate.d.ts +86 -0
  45. package/dist/team/dispatch-gate.js +75 -0
  46. package/dist/team/forced-dispatch.d.ts +95 -0
  47. package/dist/team/forced-dispatch.js +208 -0
  48. package/dist/team/forced-mode.d.ts +116 -0
  49. package/dist/team/forced-mode.js +156 -0
  50. package/dist/team/forced-prompt.d.ts +48 -0
  51. package/dist/team/forced-prompt.js +84 -0
  52. package/dist/team/limits.d.ts +61 -1
  53. package/dist/team/limits.js +69 -1
  54. package/dist/team/model.d.ts +5 -5
  55. package/dist/team/model.js +5 -5
  56. package/dist/team/normalize.d.ts +15 -0
  57. package/dist/team/normalize.js +21 -4
  58. package/dist/team/prompt.d.ts +6 -3
  59. package/dist/team/prompt.js +9 -5
  60. package/dist/team/task-tool.js +11 -7
  61. package/dist/tools/human-input.d.ts +35 -2
  62. package/dist/tools/human-input.js +38 -0
  63. package/dist/tools/plan-tools.d.ts +20 -12
  64. package/dist/tools/plan-tools.js +24 -17
  65. package/dist/ui/App.js +130 -11
  66. package/dist/ui/StatusBar.d.ts +15 -0
  67. package/dist/ui/StatusBar.js +2 -0
  68. package/dist/ui/Transcript.js +1 -1
  69. package/dist/ui/entries/CompactionCard.d.ts +11 -1
  70. package/dist/ui/entries/CompactionCard.js +65 -4
  71. package/dist/ui/layout/status-layout.d.ts +27 -0
  72. package/dist/ui/layout/status-layout.js +27 -1
  73. package/dist/ui/overlays/HelpOverlay.js +8 -4
  74. package/dist/ui/overlays/SettingsScreen.d.ts +14 -0
  75. package/dist/ui/overlays/SettingsScreen.js +39 -5
  76. package/dist/ui/overlays/TeamFanoutPage.d.ts +51 -0
  77. package/dist/ui/overlays/TeamFanoutPage.js +90 -0
  78. package/dist/ui/overlays/kit-settings-state.js +3 -2
  79. package/dist/ui/overlays/model-profile-state.d.ts +20 -2
  80. package/dist/ui/overlays/model-profile-state.js +12 -0
  81. package/dist/ui/overlays/team-fanout-state.d.ts +106 -0
  82. package/dist/ui/overlays/team-fanout-state.js +155 -0
  83. package/package.json +81 -81
package/CHANGELOG.md CHANGED
@@ -8,6 +8,74 @@ were written.
8
8
  ## Unreleased
9
9
 
10
10
  ### Added
11
+ - **Automatic skill triage, and no round limit on questions.** Two changes that
12
+ share one idea: replace a hard limit with guidance the agent can act on.
13
+ `ask_user` is no longer capped at four rounds per turn — the cap, its prompt
14
+ wording (`you may ask up to 4 rounds`), its tool-result counters
15
+ (`roundsUsed` / `roundsRemaining`) and its `/plan status` readout are all gone,
16
+ because an agent that is told it is running out of questions behaves like it.
17
+ What replaced it is a de-duplication guard: a question batch you have already
18
+ **answered** in this turn returns a note instead of reopening the overlay, while
19
+ a **cancelled** one records nothing, so an accidental `Esc` never retires a
20
+ question. Steering does not reset the guard, because a steer resumes the same
21
+ turn. `planModeMaxAskRounds` is retired rather than defaulted to a large number:
22
+ `config set` names the reason and exits `2`, and the key disappears from
23
+ `config.json` at the next write. The single-wait ceiling
24
+ (`planModeHumanTimeoutMs`) is untouched — it protects an unattended terminal, not
25
+ the agent's vocabulary.
26
+ Skill use now begins with a step instead of a hope. Every task gets a
27
+ `<skill_triage>` protocol fixing *when* to decide (after the project guidance and
28
+ the directory listing, before any plan or edit) plus up to three ranked
29
+ `<skill_candidates>` computed locally from your message against the installed
30
+ records — no model call, no file read, no `SKILL.md` body, so the disclosure
31
+ ladder keeps its lower levels untouched. When a strongly-matching skill was
32
+ available and the turn did real work without loading it, the run ends with one
33
+ `info` line and a pointer to `/skills route`; that fires at most once per skill
34
+ per session, because a line printed every turn is a line you learn to skip.
35
+ `/skills route <text>` explains any decision (including the runner-up and the
36
+ threshold on a miss), `/skills triage on|off` flips the feature live, and
37
+ `skills.triage` (default `true`) makes it permanent — off restores the previous
38
+ prompt byte for byte. Subagents are deliberately not triaged: a child has no
39
+ `skill` tool, so candidates would advertise a capability it does not have.
40
+ - **`/team <brief>` forces team mode and starts the work in one line.** Typing
41
+ `/team write today's AI briefing` no longer answers `Unknown argument` — it arms a
42
+ SESSION-level forced mode and submits that brief as an ordinary turn. The lead
43
+ analyses and plans it, then has to call `task`: three layers say so, and only the
44
+ third is mechanical. The submitted directive (unchanged bytes) covers the first
45
+ turn, a new `<forced_team_mode>` prompt block covers every later turn, and an
46
+ end-of-turn gate covers a model that did neither — when a turn used tools and
47
+ dispatched nothing, the app asks once in the transcript
48
+ (`Forced team mode: no dispatch last turn - asking the agent to dispatch now.`)
49
+ and re-prompts with the armed count. The gate is bounded on three sides, because
50
+ a helpful nudge that fires for ever is a bill: at most one nudge per user
51
+ request (a human submit refills the budget, a turn never does), never for a pure
52
+ question that used no tools, and never after Esc or an error. A turn that
53
+ already tried `task` — even one the tool refused — counts as an attempt, so a
54
+ closed capability cannot be mistaken for a disobedient model. While the session
55
+ is armed the status bar carries `Team 4` (`T4` when narrow), the only
56
+ persistent signal that `/team` happened. Forced mode is never persisted: no
57
+ new `config.json` key, no session-file field, and `aragon exec` is unchanged.
58
+ `/team on` arms without starting, `/team off` leaves, `/team` reports, and a
59
+ quoted `"/team \"off\""` is a brief named `off` rather than the keyword.
60
+
61
+ - **`/subagent`: force a parallel fan-out with one command.** The escape hatch for
62
+ "I want a team on this, not the model's judgement": `/subagent -n 6 review the error
63
+ handling in packages/cli/src/team`, `/subagent --parts api,ui,docs audit the request
64
+ path`, `/subagent --read-only profile the parser`. With no argument it reports live
65
+ capacity, the subagent model, the supervisor and the team switch, and it answers even in
66
+ a `--no-team` session rather than pretending. The command compiles your intent into ONE
67
+ user turn that tells the lead to call `task` once, so it shares the single dispatch path
68
+ (same Esc, same report, same spend accounting) - the forcing is instruction-level, and
69
+ the notice says what the next turn will do instead of claiming children have started.
70
+ A count above `team.maxSubagents` is narrowed AND disclosed (`asked for 12;
71
+ team.maxSubagents is 8`), a parts list is narrowed by both the structural 8 and the
72
+ configured width, and `-n 0` is an error rather than a silent 1.
73
+ - **`/settings` → `Subagent fan-out`.** A fourth root row holding `Max subagents`,
74
+ `Run at once` and `Supervisor`, adjusted with `Left` / `Right` and saved by the same
75
+ single transaction as every other settings row (`Ctrl+S` on the main page; the fan-out
76
+ page's footer says so instead of promising a key it does not listen for). Narrowing the
77
+ population narrows the pool with it, and the follow-down is committed, so widening again
78
+ does not silently restore a pool you did not choose.
11
79
  - **`--threads`: one process, N isolated conversations.** A headless `aragon exec`
12
80
  process used to be one conversation: the seventh tenant's messages appeared in the
13
81
  third tenant's history, one `todo` list served everybody, and `interrupt` ended the
@@ -55,6 +123,38 @@ were written.
55
123
  spawning anything.
56
124
 
57
125
  ### Changed
126
+ - **`/team` is now an action, and its configuration verbs are gone.** `status` and
127
+ `on` / `off` survive as SESSION control words (enter, leave, report), and `off`
128
+ no longer saves anything: **`/team on|off` used to write `config.json` and no
129
+ longer does**, so a mode you switch for this conversation is not silently a mode
130
+ for every later one. The four settings that used to live on the command moved to
131
+ `Settings > Team`, which is now the only persistent writer: `Team mode` (new —
132
+ the capability switch, previously only reachable as `/team on|off`), `Max
133
+ subagents`, `Run at once` and `Supervisor`. `max <n>`, `overseer on|off` and
134
+ `model <name>` are not verbs any more — `/team max 6` is a brief that starts work
135
+ on the text "max 6", because a command that both changes configuration and
136
+ executes work leaves you unable to predict what it did. `/subagent` remains as a
137
+ DEPRECATED ALIAS of `/team` (same arguments, one extra toast); nothing about its
138
+ behaviour changed except that a team-off session is now turned on for the session
139
+ in place instead of being refused. The settings page is retitled `Team` (it was
140
+ `Subagent fan-out`) and its root row prints `off - no subagents` while the
141
+ capability is off, rather than two numbers that describe nothing you can spend.
142
+ A session started with `--no-team` shows `on (next launch)` on the `Team mode`
143
+ row instead of claiming that saving will change the running session — the tool
144
+ array is immutable, and the row says so. One internal truth was deleted rather
145
+ than patched: `AgentController` no longer keeps a private `teamEnabled` copy of
146
+ `config.team.enabled`, so a settings save takes effect on the next turn instead of
147
+ at the next launch.
148
+ - **The subagent fan-out default is now 8 per dispatch and 8 in flight at once** (it was
149
+ 5 and 3). The perceived "only 3 subagents" was the concurrency ceiling, not the
150
+ population - changing only the population would have shown nothing, because children
151
+ 4..8 would simply queue. `maxConcurrent` stays a separate knob precisely so a single API
152
+ key collecting 429s can be fixed by lowering `Run at once`; the settings row and the
153
+ README both say which number to lower. The hard ceiling is unchanged at 10: 8 is a
154
+ default you can move, 10 is the limit you cannot. **A persisted `5`/`3` pair from an
155
+ older install is migrated to `8`/`8` once**, with a `team.fanoutMigrated` marker so a
156
+ deliberate return to 5/3 sticks instead of being widened again on every launch. A
157
+ configuration whose values are anything else is never touched.
58
158
  - `makeExecController` now separates PROCESS-LEVEL log mounts from PER-CONTROLLER ones:
59
159
  `run_exec`, the `onFailure` handler and the untrusted-skill notices happen once per
60
160
  process (selected by the new `ExecControllerDeps.processLevelMounts`, which only the
@@ -68,6 +168,15 @@ were written.
68
168
  the same bytes it wrote before.
69
169
 
70
170
  ### Fixed
171
+ - Changing only the fan-out width no longer drops the subagent model a session kit
172
+ installed. The settings save read "the `team` section was written" as "the user
173
+ re-decided the subagent slot", so adjusting `Max subagents` silently released a slot the
174
+ kit held - with every value on screen still correct. Only an explicit `model` /
175
+ `modelProfileId` decision releases the slot now, and the fan-out page can express
176
+ nothing else.
177
+ - The `task` tool's description no longer states a fixed width (`2-5 independent
178
+ subagents`, `hard maximum 10`), which contradicted the configured maximum in the very
179
+ same request. It points at `<team_mode>`, which is generated from the live config.
71
180
  - A process-level signal now has exactly ONE owner in thread mode (it used to be
72
181
  installed by every runner into a single process-wide slot, so the last one won): the
73
182
  terminator fans out to all threads, `result` is written before the process exits, and a
package/README.md CHANGED
@@ -942,6 +942,7 @@ ignores them rather than rejecting the library):
942
942
  | `/thinking <level>` | Set the thinking level |
943
943
  | `/max-tokens [<n>\|auto\|default]` | Output token cap; with no argument, reports the setting **and** the effective cap for the live model |
944
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 |
945
946
  | `/tools` | List active tools |
946
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 |
947
948
  | `/reset` | Alias of `/clear` - one implementation, so the two cannot drift apart |
@@ -950,7 +951,7 @@ ignores them rather than rejecting the library):
950
951
  | `/resume [file]` | Load a saved session. This session moves to a NEW id, and the spend counter restarts |
951
952
  | `/session [copy\|pin]` | Report this session's id, file and branch parent; `copy` puts `aragon --resume <id>` on the clipboard; `pin` makes `sessions prune` skip the file forever |
952
953
  | `/plan [on\|off\|status]` | Toggle plan mode - the keyboard-free equivalent of `Shift+Tab` |
953
- | `/team [on\|off\|max <n>\|overseer on\|off\|model [main\|fast\|<profile-name>]]` | Team subagents: status and the live roster, the on/off switch, the fan-out width, the dispatch supervisor |
954
+ | `/team [on\|off\|status]` / `/team [-n <n>] [--parts a,b,c] [--read-only] <brief>` | **Force team mode and start now.** `/team <brief>` arms forced team mode and submits that work as a normal turn: the lead analyses and plans it, then calls `task` — the directive, the `<forced_team_mode>` prompt block and a mechanical end-of-turn gate all say so. `/team on` arms without submitting, `/team off` leaves, `/team` reports. The configuration it used to carry (team switch, width, concurrency, supervisor, subagent model) lives in `Settings > Team` |
954
955
  | `/todo [on\|off\|panel on\|off\|follow <mode>\|acceptance on\|off\|reset\|clear\|continue]` | Todo planning: status, the on/off switch, the rail, follow-through, acceptance criteria, and picking an unfinished plan back up |
955
956
  | `/retry [show\|on\|off\|max <n>]` | API retry: the effective ladder, the on/off switch, the retry count. Takes effect **in the running session**, children included |
956
957
  | `/fast [on\|off\|model <id>\|provider <id>\|same\|review <n\|off>]` | Fast model tier: status and this session's totals, the on/off switch, which model, the review cadence |
@@ -1591,6 +1592,16 @@ questions.
1591
1592
  Cancelling either one is not an error. The agent is told to proceed on stated
1592
1593
  assumptions rather than to retry.
1593
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
+
1594
1605
  **Dismissing a plan is not how you stop.** `Esc` on a plan card asks for a
1595
1606
  *better* plan — the agent is told to refine it and submit again — so a model that
1596
1607
  keeps submitting keeps getting cards. To stop the run itself, close the card with `Esc`, then **press `Esc`
@@ -2071,14 +2082,61 @@ that existed before this feature.
2071
2082
  **Which model a subagent runs on is your setting, not the model's.** The
2072
2083
  default (`team.model: 'main'`) keeps main-agent parity: every child runs the
2073
2084
  lead's own connection. Switch it to the **fast tier** or a **saved profile**
2074
- from the settings screen's *Subagent model* row or with
2075
- `/team model main|fast|<profile-name>` — useful when a fan-out of read-only
2076
- scouts does not need the main model. The choice is re-resolved on every
2085
+ from the settings screen's *Subagent model* row — useful when a fan-out of
2086
+ read-only scouts does not need the main model. The choice is re-resolved on every
2077
2087
  dispatch, and an unusable one (tier off, profile deleted, key missing) falls
2078
2088
  back to the main model with a disclosed line in the dispatch report instead of
2079
2089
  failing the dispatch. The model itself still cannot downgrade its own
2080
2090
  delegation: a stale `model` key in a `task` call is ignored, exactly as before.
2081
2091
 
2092
+ **How wide a fan-out is also your setting.** `/settings` → `Team` (row four,
2093
+ directly under *Subagent model*) holds four values: `Team mode`, `Max subagents`,
2094
+ `Run at once` and `Supervisor`, adjusted with `Left` / `Right` (`Enter` toggles
2095
+ the two on/off rows). Nothing is written until you press `Ctrl+S` **on the main
2096
+ settings page** — the arrows edit a draft, and `Esc` on the `Team` page returns
2097
+ to the main page rather than saving. `Team mode` is the capability switch that
2098
+ used to be `/team on|off`, and it is the only place that saves it; a session
2099
+ started with `--no-team` shows `on (next launch)` rather than pretending the
2100
+ tool can still be added. The numbers are also settable without the screen:
2101
+ `aragon config set team.maxSubagents <n>`.
2102
+
2103
+ Narrowing `Max subagents` also narrows `Run at once`, because a pool wider than
2104
+ its own population is meaningless; the follow-down is committed, so widening the
2105
+ population again does not silently restore a pool you did not ask for. And the
2106
+ width change never touches the model a session kit installed: only an explicit
2107
+ model choice releases that slot.
2108
+
2109
+ **Or bypass the model's judgement entirely: `/team <brief>`.** The command plus
2110
+ the work in one line compiles your intent into a single user turn that tells the
2111
+ lead to call `task` once with that many children, so it shares the one dispatch
2112
+ path — same Esc, same report, same spend accounting — and then stays on:
2113
+
2114
+ ```
2115
+ /team -n 6 review the error handling in packages/cli/src/team
2116
+ /team --parts api,ui,docs audit the request path
2117
+ /team --read-only --parts perf,memory profile the parser
2118
+ ```
2119
+
2120
+ Forced mode is SESSION state, not a saved setting: the next message of any kind
2121
+ dispatches too, until `/team off`. It is enforced three ways, and the third one
2122
+ does not depend on the model's cooperation — the submitted directive, a
2123
+ `<forced_team_mode>` block in every later system prompt, and an end-of-turn gate
2124
+ that asks once (`Forced team mode: no dispatch last turn - asking the agent to
2125
+ dispatch now.`) when a turn used tools and dispatched nothing. The gate never
2126
+ fires twice for one request: a human submit refills the budget, a turn does not.
2127
+ While it is armed the status bar carries `Team 4` (`T4` on a narrow terminal).
2128
+
2129
+ `/team` with no argument reports the forced state, capacity, the subagent model
2130
+ and where the switches live — and answers even in a `--no-team` session, where
2131
+ the honest answer is that this session has no team capability. Narrowing is
2132
+ always disclosed (`asked for 12; team.maxSubagents is 8`), `-n 0` is refused
2133
+ rather than silently becoming 1, and the notice says what the **next turn** will
2134
+ do rather than claiming children have started. `"off"` in quotes is a brief
2135
+ named `off`, not the keyword.
2136
+
2137
+ `/subagent` is a **deprecated alias** of `/team`: same arguments, same
2138
+ behaviour, plus one toast saying so.
2139
+
2082
2140
  ```
2083
2141
  > read the auth middleware, map the route table, and check the migration
2084
2142
 
@@ -2093,12 +2151,18 @@ tools and the same working directory — but no memory of your conversation, so
2093
2151
  its brief has to be self-contained. It cannot delegate further (depth is capped
2094
2152
  at one by construction) and it cannot ask you anything.
2095
2153
 
2096
- **Delegate wide, not deep.** Two to four subagents is the usual size. The
2097
- default cap is 5 per dispatch and 3 in flight at once; the hard maximum is 10
2098
- and no config file can raise it. `maxConcurrent` is separate from `maxSubagents`
2099
- on purpose: five simultaneous streams against one API key is a reliable way to
2100
- collect rate-limit errors, and each one would land in the report as a failed
2101
- subagent.
2154
+ **Delegate wide, not deep.** Two to four subagents is usually enough. The
2155
+ default is **8 per dispatch and 8 in flight at once** (it was 5 and 3 before
2156
+ subagent-fanout); the hard maximum is 10 and no config file can raise it, so 8
2157
+ is a default you can move in either direction and 10 is the ceiling you cannot.
2158
+ Both numbers live in `/settings` (`Subagent fan-out`) and in
2159
+ `team.maxSubagents` / `team.maxConcurrent`.
2160
+
2161
+ `maxConcurrent` is separate from `maxSubagents` on purpose: eight simultaneous
2162
+ streams against one API key is a reliable way to collect rate-limit errors, and
2163
+ each one would land in the report as a failed subagent. **If you see rate-limit
2164
+ errors, lower `Run at once`** — that is the one number to change, and it is the
2165
+ only reason the two knobs are separate.
2102
2166
 
2103
2167
  **The report.** One text result: a header with the ok/failed counts, aggregate
2104
2168
  tokens and cost, then one section per subagent with its status, timing, the
@@ -2134,9 +2198,9 @@ nothing is killed. A subagent still ends on the user's Esc, the explicit
2134
2198
  watchdog (lengthened 4× while supervised), or the supervisor's own
2135
2199
  replace/abandon verdict. One boundary to know: a session with NO fast wiring
2136
2200
  at all and `team.overseer: true` runs children with no wall clock whatsoever
2137
- — they finish, or die on their own un-lengthened watchdog. `/team overseer
2138
- off` is the escape hatch back to the legacy regime where
2139
- `team.subagentTimeoutMs` is a hard abort again.
2201
+ — they finish, or die on their own un-lengthened watchdog. `Settings > Team` →
2202
+ `Supervisor` (or `aragon config set team.overseer false`) is the escape hatch
2203
+ back to the legacy regime where `team.subagentTimeoutMs` is a hard abort again.
2140
2204
 
2141
2205
  **Talking to each other.** Subagents get two extra tools, `team_send` and
2142
2206
  `team_wait`. Messages are delivered by attaching them to the recipient's next
@@ -2752,6 +2816,7 @@ This is the part that makes skills cheap enough to install a lot of:
2752
2816
  | Level | What the model gets | When |
2753
2817
  | --- | --- | --- |
2754
2818
  | 1 | Name, scope, and a one-line "what + when" | Always, in the system prompt. Whole catalog capped at 6 000 bytes. |
2819
+ | 1.5 | `<skill_triage>` protocol + up to 3 ranked `<skill_candidates>` | Always (protocol), rebuilt per task (candidates). ≤ 1 500 + ≤ 900 bytes. |
2755
2820
  | 2 | The full `SKILL.md` body + a list of bundled files | Only when the model calls `skill(name=…)` |
2756
2821
  | 3 | The bundled files themselves | Only when it reads them with `read_file` / `bash` |
2757
2822
 
@@ -2759,6 +2824,34 @@ A skill costs a line in the prompt rather than its whole body, so installing
2759
2824
  many of them stays cheap. The model decides on its own whether a task matches a
2760
2825
  skill — you do not have to name one.
2761
2826
 
2827
+ **Level 1.5 is what turns that from a claim into a step.** Reading a catalog is
2828
+ not the same as being told to read it: the catalog's own preamble is a statement
2829
+ ("if a task matches one, load it"), and a statement the model has seen forty times
2830
+ is the one it skims. So the prompt now carries a `<skill_triage>` protocol that
2831
+ fixes *when* the decision happens — after the project guidance and the directory
2832
+ listing, before any plan or edit — plus a short candidate list computed from your
2833
+ message against the in-memory skill records (name, description, keywords). Both are
2834
+ local string work: no model call, no file read, no body loaded, so "which skills
2835
+ could apply?" costs zero context bytes beyond the block itself.
2836
+
2837
+ Two consequences worth knowing:
2838
+
2839
+ - **It is a guess, not a verdict.** The block says so in as many words, and the
2840
+ model is told to load nothing when nothing fits. It never loads a skill for you.
2841
+ - **Silence on success, one line on failure.** When a strongly-matching skill was
2842
+ available and the turn did real work without loading it, the run ends with one
2843
+ `info` line naming the skill and pointing at `/skills route`. That happens at
2844
+ most once per skill per session — a message printed every turn is a message you
2845
+ learn to skip.
2846
+
2847
+ `/skills route <text>` shows the scoring for any phrasing you type (or with no
2848
+ argument, for the last real task), including the runner-up and the threshold when
2849
+ nothing qualified. `/skills triage off` turns the whole level off for the session
2850
+ and `skills.triage=false` makes it permanent; either way the prompt returns to its
2851
+ pre-feature bytes. **Subagents are not triaged** — a child has no `skill` tool, so
2852
+ proposing skills to it would describe a capability it does not have; name the skill
2853
+ in the `task` description instead.
2854
+
2762
2855
  At Level 2 the file list is split into what to read and what to run, and carries
2763
2856
  the skill's absolute root so the model can build real paths. The accompanying
2764
2857
  guidance is **platform-specific**: on Windows the `bash` tool runs `cmd.exe`, so
@@ -2834,7 +2927,8 @@ keeping `installedAt` and recording `updatedAt` + `previousVersion`.
2834
2927
 
2835
2928
  Interactively the same lives under `/skills …` (`/skills update <name> [--force]`,
2836
2929
  `/skills update --all`, `/skills list --sort=recent`, `/skills policy [mode]`,
2837
- `/skills unload`, `/skills usage [--reset]`). Every usable skill also becomes a
2930
+ `/skills unload`, `/skills usage [--reset]`, `/skills triage [on|off]`,
2931
+ `/skills route [text]`). Every usable skill also becomes a
2838
2932
  slash command:
2839
2933
 
2840
2934
  ```
@@ -3071,19 +3165,20 @@ env / `.env` → CLI flags**.
3071
3165
  | `renderGovernor` | `true` | Widen the streaming frame interval when a frame is genuinely expensive. See [Render performance](#render-performance). |
3072
3166
  | `maxRenderIntervalMs` | `320` | Ceiling of the governor ladder, clamped to `[33, 1000]`. `33` flattens it to the pre-0.6 cadence without disabling the mechanism. |
3073
3167
  | `planModeDefault` | `false` | Start every session in plan mode. `--no-plan` still overrides it for a single run. |
3074
- | `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. |
3168
+ | ~~`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. |
3075
3169
  | `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. |
3076
3170
  | `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. |
3077
3171
  | `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). |
3078
- | `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. |
3079
- | `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. |
3080
- | `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. |
3172
+ | `team.enabled` | `true` | Master switch for team subagents. `false` means the `task` tool is never registered. Written by the `Team mode` row in `Settings > Team` and nowhere else — `/team` never saves configuration. In a session already started with `--no-team` the row reads `on (next launch)`, rather than advertising a tool that is not there. |
3173
+ | `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). |
3174
+ | `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. |
3175
+ | `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". |
3081
3176
  | `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. |
3082
3177
  | `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. |
3083
3178
  | `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. |
3084
- | `team.overseer` | `true` | The dispatch supervisor: one per dispatch on the fast model, ticking on a geometric cadence over every live subagent and deciding wait / nudge / replace / abandon. With the fast tier missing a tick degrades to an announced unassisted wait — never a kill; with this `false`, children run under the legacy hard ceilings exactly. `/team overseer on\|off` flips it live, and it is read per dispatch. |
3179
+ | `team.overseer` | `true` | The dispatch supervisor: one per dispatch on the fast model, ticking on a geometric cadence over every live subagent and deciding wait / nudge / replace / abandon. With the fast tier missing a tick degrades to an announced unassisted wait — never a kill; with this `false`, children run under the legacy hard ceilings exactly. The `Supervisor` row in `Settings > Team` flips it live (a settings save rebuilds the prompt), and it is read per dispatch. |
3085
3180
  | `team.overseerIntervalMs` | `0` | Supervisor cadence base, clamped to `[0, 3600000]`. `0` (the default) = the structural 300 s; a positive `team.subagentTimeoutMs` is honoured first when this is `0` (compat). Later checks grow ×1.6, clamped to `[60 s, 900 s]`; any nudge/replace/abandon resets that child's cadence to the base. Only meaningful while `team.overseer` is true. |
3086
- | `team.model` | `"main"` | Which model every subagent runs on: `'main'` (the default - the lead's own connection), `'fast'` (the fast tier, re-resolved per dispatch), or `'profile'` (the entry named by `team.modelProfileId`). A **user-owned setting** — the model itself still cannot downgrade its own delegation, and an unusable choice falls back to the main model with a disclosed line in the dispatch report. Also settable via `/team model main\|fast\|<profile-name>` and the settings screen's **Subagent model** row. |
3181
+ | `team.model` | `"main"` | Which model every subagent runs on: `'main'` (the default - the lead's own connection), `'fast'` (the fast tier, re-resolved per dispatch), or `'profile'` (the entry named by `team.modelProfileId`). A **user-owned setting** — the model itself still cannot downgrade its own delegation, and an unusable choice falls back to the main model with a disclosed line in the dispatch report. Settable only through the settings screen's **Subagent model** row: `/team` no longer writes configuration. |
3087
3182
  | `team.modelProfileId` | `null` | The profile entry `team.model: 'profile'` binds to. Reference existence is not validated here — a dangling id degrades to the main model at resolution time, so deleting a profile never breaks a dispatch. |
3088
3183
  | `todo.enabled` | `true` | Master switch for todo planning. `false` means the `todo_write` tool is never registered — `/todo on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
3089
3184
  | `todo.panel` | `true` | Render the right-hand rail. Independent of `todo.enabled` on purpose: a screen-reader user wants the planning discipline without the column, and the system prompt varies one sentence accordingly. |
@@ -3108,6 +3203,7 @@ env / `.env` → CLI flags**.
3108
3203
  | `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. |
3109
3204
  | `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`. |
3110
3205
  | `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. |
3206
+ | `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`. |
3111
3207
  | `log.level` | `info` | `silent` · `error` · `warn` · `info` · `debug` · `trace`. See [Files & logs](#files--logs) for what each level records. |
3112
3208
  | `log.toFile` | `true` | Write records to a file at all. `false` here is respected; `--log-file` overrides it for one run. |
3113
3209
  | `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. |
@@ -3595,6 +3691,16 @@ VS Code 用户设置添加 `"terminal.integrated.copyOnSelection": false`,快
3595
3691
 
3596
3692
  编辑方案时可以当场验证:**Test connection**(Ctrl+R)用草稿里的服务商、模型、地址和解析后的密钥做一次真实往返,成功显示 `OK 812 ms` 与用量,失败给出分类结论和第二行“怎么办”;**Discover models**(Ctrl+F)列出该密钥实际可访问的模型供选择,只写回模型字段,不动上下文窗口与凭据。两者都作用于未保存的草稿,`Discover` 零 token;探测期间 Esc 只取消探测而不丢弃表单,修改服务商、模型或密钥后旧结论立刻回到未测试。
3597
3693
 
3694
+ ## 子代理扇出宽度与 `/subagent`
3695
+
3696
+ 子代理扇出的默认值现在是**每次派单 8 个、同时最多 8 个在跑**(原为 5 和 3)。用户感知的“最多只有 3 个”来自并发上限而不是派单上限——只把派单数改大而并发不动,观感不会有任何变化,所以两个数一起改。硬上限仍是 10:8 是默认值,10 是天花板,中间由用户自己决定。
3697
+
3698
+ 在 `/settings` 的第 4 行 **Subagent fan-out** 里可以改这三个值:**Max subagents**、**Run at once**、**Supervisor**,用左右方向键调整。页内只改草稿,回到根设置页按 Ctrl+S 才真正落盘(本页页脚如实写着这一点,因为子页上根本没有 Ctrl+S 监听者)。把扇出调窄时并发会跟着下调,并且这个跟随**会写进补丁**:再把扇出调宽时并发停在你选的位置,不会被悄悄拉回。只改宽度**不会**释放会话 kit 给的子代理模型,只有显式改子代理模型才会。
3699
+
3700
+ 磁盘上仍带着旧默认值 5/3 的配置会在首次启动时被一次性迁移到 8/8,并写下 `team.fanoutMigrated` 标记;之后你手工改回 5/3 会一直保持,不会被再次迁移。其他任何组合(2/2、5/5、10/10)都不迁移。
3701
+
3702
+ 想绕开“模型自己决定要不要派单”时用 **`/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** 调低即可。
3703
+
3598
3704
  **Current custom** 保留旧式连接,可继续编辑原连接字段。创建方案时从磁盘上的旧式配置预填,不复制环境变量或 `--api-key` 的运行时密钥。专属密钥可保持、替换或切换为共享/环境凭据;修改服务商或地址后,必须明确选择保留原密钥,或重新输入。空地址在方案中表示服务商默认地址,快速方案不会继承主网关。
3599
3705
 
3600
3706
  启动优先级为 CLI 参数、环境变量、所选方案、旧式字段、默认值。界面显示启动覆盖;明确重新选择方案可对本次会话立即生效,重启仍遵循启动参数。专属密钥仅对对应角色和目标地址生效;主模型启动密钥不会跟随切换发往另一个网关。
@@ -25,6 +25,7 @@ import type { ToolPermission } from '../exec/permission.js';
25
25
  import { type ToolOutputListener } from '../tools/tool-output-store.js';
26
26
  import type { FilePatch } from '../tools/patch.js';
27
27
  import type { SubagentModelResolution } from '../team/model.js';
28
+ import type { ForcedTeamState } from '../team/forced-mode.js';
28
29
  import type { TeamEvent, TeamSnapshot } from '../team/types.js';
29
30
  import { ProcSupervisor } from '../proc/supervisor.js';
30
31
  import type { ProcEventListener, ServiceSnapshot } from '../proc/types.js';
@@ -36,6 +37,7 @@ import type { CompactionEventListener, CompactionSnapshot, ContextUsageSnapshot
36
37
  import type { AcceptanceItem, TodoEventListener, TodoSnapshot } from '../todo/types.js';
37
38
  import { type HumanInputGate } from '../tools/human-input.js';
38
39
  import { SkillService, type ApprovalGate } from '../skills/service.js';
40
+ import type { SkillRoutingReport } from '../skills/triage.js';
39
41
  import { type ConversationResetOptions, type ConversationResetResult } from './conversation-reset.js';
40
42
  import type { AgentMode } from './agent-mode.js';
41
43
  import type { NoticeLevel } from './reducer.js';
@@ -183,16 +185,19 @@ export interface ControllerDeps {
183
185
  */
184
186
  modelWindows?: ModelWindows;
185
187
  }
186
- /** What `setAgentMode` actually adopted — see `AgentController.setAgentMode`. */
188
+ /**
189
+ * What `setAgentMode` actually adopted — see `AgentController.setAgentMode`.
190
+ *
191
+ * THIS IS ALSO WHAT `/plan status` REPORTS. A `PlanStatus extends AgentModeState`
192
+ * with `askRoundsUsed` / `maxAskRounds` existed until the round cap was deleted;
193
+ * keeping an empty sub-interface around would only leave a comment that every
194
+ * later reader has to work through to learn that nothing is missing.
195
+ */
187
196
  export interface AgentModeState {
188
197
  effective: AgentMode;
189
198
  /** Non-null only while a `plan -> build` switch is waiting for `agent_end`. */
190
199
  pending: AgentMode | null;
191
200
  }
192
- export interface PlanStatus extends AgentModeState {
193
- askRoundsUsed: number;
194
- maxAskRounds: number;
195
- }
196
201
  export interface PreflightResult {
197
202
  ok: boolean;
198
203
  message?: string;
@@ -235,6 +240,21 @@ export declare class AgentController {
235
240
  private readonly skills;
236
241
  private readonly skillsEnabled;
237
242
  private onSkillsChanged?;
243
+ /**
244
+ * This turn's routing result, or `null` when triage is off or nothing scored.
245
+ *
246
+ * It also carries the task it was computed for (`SkillRoutingReport.task`), so
247
+ * there is no separate copy of the turn's text to keep in step with it.
248
+ */
249
+ private turnRouting;
250
+ /** The previous turn's routing, for `/skills route` with no argument. */
251
+ private lastSkillRouting;
252
+ /** Did this turn call `skill(name)` successfully? The only proof of adoption. */
253
+ private skillLoadedThisTurn;
254
+ /** Tool calls this turn, EXCLUDING `skill` / `skill_find` (see §4.2.7). */
255
+ private toolCallsThisTurn;
256
+ /** Skills already reported as "not loaded" — a session says it once (I-T5). */
257
+ private readonly skillTriageNoticed;
238
258
  /** Mutable session cwd — tools resolve relative paths against this. */
239
259
  private cwd;
240
260
  /**
@@ -259,12 +279,25 @@ export declare class AgentController {
259
279
  private readonly structuredResultBlock;
260
280
  private effectiveMode;
261
281
  private pendingMode;
262
- private askRounds;
282
+ /**
283
+ * Fingerprints of the question SETS the user ANSWERED in this turn. Bounded,
284
+ * and the bound is storage hygiene rather than a refusal rule — see
285
+ * `ASK_GUARD_LIMITS`.
286
+ *
287
+ * Replaces a numeric round counter that capped `ask_user` at four calls. There
288
+ * is no cap now; the only thing this prevents is the same batch of questions
289
+ * being put to the user twice, which is noise rather than information.
290
+ *
291
+ * Cleared in `prepareUserTurn` for a NEW turn only. A steer continuation is the
292
+ * same turn — clearing there would disable the guard exactly when a long turn
293
+ * most needs it (§4.1.6).
294
+ */
295
+ private readonly askedFingerprints;
263
296
  /** Static: whether this session could ever render a question overlay. */
264
297
  private readonly planInteractive;
265
298
  private readonly teamRegistered;
266
- private teamEnabled;
267
299
  private readonly teamRuntime;
300
+ private forcedTeam;
268
301
  private readonly todoRegistered;
269
302
  private todoEnabled;
270
303
  private readonly todos;
@@ -445,6 +478,24 @@ export declare class AgentController {
445
478
  * semantics depend on whether a notice happened to be pending.
446
479
  */
447
480
  private flushPendingNotices;
481
+ /**
482
+ * THE BOUNDARY OF ONE TURN — and there are TWO call sites, which is the reason
483
+ * this takes an explicit flag instead of resetting everything unconditionally.
484
+ *
485
+ * `startPrompt` passes `{ steering: false }`: the user typed something new, so
486
+ * this really is a new turn. `continue()` passes `{ steering: true }` with the
487
+ * CONCATENATED queued steering text, which resumes the SAME turn.
488
+ *
489
+ * A steer continuation must not re-triage (the candidates would change mid-turn,
490
+ * and rebuilding the prompt aborts an idle compaction for no gain) and must not
491
+ * clear the answered-question guard (a long turn interrupted by one steer is
492
+ * exactly when a repeat question is most likely). Hence per-turn state is
493
+ * initialised in ONE branch.
494
+ *
495
+ * `steering` is REQUIRED and positional-second so each call site states which
496
+ * kind of turn it is starting, rather than letting a default make "new turn" the
497
+ * thoughtless option.
498
+ */
448
499
  private prepareUserTurn;
449
500
  /** Cancel pending startup as well as any engine run already in progress. */
450
501
  abort(): void;
@@ -583,9 +634,53 @@ export declare class AgentController {
583
634
  */
584
635
  applyPendingMode(): AgentModeState | null;
585
636
  /** Named read surface for `/plan status`, so no command reaches into internals. */
586
- getPlanStatus(): PlanStatus;
587
- /** Spend one `ask_user` round. Called from the tool, never from the UI. */
588
- private takeAskRound;
637
+ getPlanStatus(): AgentModeState;
638
+ /**
639
+ * READ-ONLY guard probe, called by `ask_user` BEFORE it opens the overlay.
640
+ *
641
+ * No side effect of any kind: a check that also writes cannot be placed before
642
+ * the question without recording an answer that has not happened yet, and the
643
+ * "a cancelled round stays askable" rule would then be unimplementable.
644
+ */
645
+ private hasAnswered;
646
+ /**
647
+ * Record an ANSWERED question set, called by `ask_user` after a real answer.
648
+ *
649
+ * Evicts the oldest entry past `ASK_GUARD_LIMITS.maxRemembered`. That eviction
650
+ * is memory hygiene and NOT a refusal rule — a bounded rejection would be the
651
+ * deleted round cap wearing a bigger number.
652
+ */
653
+ private noteAnswered;
654
+ /** `/skills route` with no argument: the LAST turn's real routing result. */
655
+ getSkillRouting(): SkillRoutingReport | null;
656
+ /**
657
+ * One line when a confidently-matched skill was NOT loaded — and silence every
658
+ * other time (D-8).
659
+ *
660
+ * THE DISCIPLINE IS "SILENT ON SUCCESS, VISIBLE ON FAILURE". Announcing the
661
+ * candidates every turn would be ignored by the third day; announcing nothing
662
+ * ever would make the whole feature unfalsifiable. "A skill covered this task,
663
+ * you did the work anyway, and you never opened it" is precisely the failure this
664
+ * feature exists to fix, so it is the one case that speaks — and it names the
665
+ * command that explains it.
666
+ *
667
+ * SEVEN CONDITIONS, and the first one is the one that is easy to get wrong.
668
+ *
669
+ * 0. This turn was NOT aborted and NOT ended by an error.
670
+ * `agent_end` is emitted unconditionally from a `finally` block in
671
+ * `core/src/engine/agent.ts` and its payload carries no reason, no `aborted`
672
+ * and no `error` field — so "did this turn finish normally?" has to be read
673
+ * from this class's own state. `stopEngine()` sets `abortRequested` before it
674
+ * calls `agent.abort()` and `agent_start` clears it, which makes it an exact
675
+ * answer. Without this condition an Esc mis-touch would produce a line
676
+ * criticising the model for not loading a skill: commentary in response to an
677
+ * interruption.
678
+ *
679
+ * Conditions 5 and 0 are not redundant: 0 decides what counts as the end of a
680
+ * turn, 5 decides whether the turn did any work. With only 5, a tool call made
681
+ * before an abort would still trigger the notice.
682
+ */
683
+ private maybeReportSkillTriage;
589
684
  /**
590
685
  * Run `fn` with the idle watchdog paused.
591
686
  *
@@ -610,17 +705,47 @@ export declare class AgentController {
610
705
  withHumanWait<T>(fn: () => Promise<T>): Promise<T>;
611
706
  /** Whether `task` is in `this.tools`. Fixed at construction; see the fields. */
612
707
  isTeamRegistered(): boolean;
613
- /** Whether `task` will actually dispatch. Flipped live by `/team on|off`. */
708
+ /**
709
+ * Whether `task` will actually dispatch - a DERIVED value.
710
+ *
711
+ * One truth (`config.team.enabled`) plus one immutable snapshot
712
+ * (`teamRegistered`). The previous version read a private copy that `/team
713
+ * on|off` hand-synchronised, which worked only while that command was the sole
714
+ * writer; the settings screen writes the config directly, so the copy would
715
+ * have kept saying `on` (P1-4 / AC-25).
716
+ */
614
717
  isTeamEnabled(): boolean;
615
718
  /**
616
719
  * Flip the live switch and re-render the prompt.
617
720
  *
721
+ * THE NAME IS KEPT (three call sites plus the settings path) but the body is
722
+ * one write: `setTeamConfig()` already clamps, bumps the revision and rebuilds,
723
+ * so there is no second place where a half-applied state can exist.
724
+ *
618
725
  * Turning it ON in a session that started with `--no-team` deliberately does
619
726
  * NOT register the tool (D-17): the array is immutable, and advertising an
620
727
  * unregistered tool is the dead end P0-2 is about. `/team` reports that
621
728
  * honestly by checking `isTeamRegistered()` first.
622
729
  */
623
730
  setTeamEnabled(enabled: boolean): void;
731
+ /**
732
+ * Arm forced team mode for this session (team-command-forced-dispatch §3.5).
733
+ *
734
+ * Returns the count actually adopted. The clamp is `[1, team.maxSubagents]`:
735
+ * `0`, a negative and a fraction are refused by the parser upstream (`-n 0` is
736
+ * a syntax error, not a clamp), so this is the boundary check of a value that
737
+ * crossed a module line - which is the one place `CLAUDE.md` asks for it, not a
738
+ * second validation of the same input.
739
+ *
740
+ * THE COUNT IS FROZEN for the rest of the session: `/team -n 2 <brief>` then a
741
+ * plain message keeps two children per turn, so the user's one expressed intent
742
+ * does not silently widen back to four.
743
+ */
744
+ armForcedTeam(count: number): number;
745
+ /** Disarm. IDEMPOTENT: disarming an unarmed session rewrites nothing. */
746
+ disarmForcedTeam(): void;
747
+ /** The armed state, read at render time by the chip and by `/team`'s status. */
748
+ getForcedTeam(): ForcedTeamState | null;
624
749
  getTeamConfig(): TeamConfig;
625
750
  /**
626
751
  * Adopt a new team config for this session and re-render the prompt (the block
@@ -634,8 +759,8 @@ export declare class AgentController {
634
759
  * One authority for the `task` report's disclosure line and its price
635
760
  * table: both read through here, so the header can never name one model
636
761
  * while pricing another. Re-resolved on every call for the reason
637
- * `resolveFastTier` records - `/fast off`, a settings save and `/team model`
638
- * all rewrite the inputs mid-session.
762
+ * `resolveFastTier` records - `/fast off`, a settings save and the `Subagent
763
+ * model` row all rewrite the inputs mid-session.
639
764
  */
640
765
  subagentModelResolution(): SubagentModelResolution;
641
766
  /** Refused mid-dispatch by `/team`; half a dispatch under two ceilings is not
@@ -677,7 +802,7 @@ export declare class AgentController {
677
802
  * THIS EXISTS BECAUSE `persistConfig` DOES NOT TOUCH THE RUNTIME (P1-2 /
678
803
  * AC-38). `App` reads `controller.getConfig()` on every render, and that
679
804
  * returns THIS object; the App's `persistConfig` only writes the file. So
680
- * `/todo panel off` must call both, exactly as `/team max` does — with only
805
+ * `/todo panel off` must call both, exactly as settings' `Team` page does — with
681
806
  * the persist call the command reports success and changes nothing until the
682
807
  * next launch.
683
808
  *