@aragon-agent/cli 0.6.21 → 0.6.23

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/README.md CHANGED
@@ -1148,15 +1148,34 @@ than a fact: the file list is derived from `write_file` / `edit_file`, so a
1148
1148
  write made through `bash` is invisible to it.
1149
1149
 
1150
1150
  **The dispatch supervisor.** A time ceiling on a subagent is an inspection
1151
- trigger, not a death sentence — as long as the fast tier is enabled. Two soft
1152
- timers per child (event silence and a wall clock) hand a bounded snapshot to the
1153
- fast model, which decides: keep waiting, steer a nudge in, rebuild the child
1154
- with an amended brief, or abandon it with a recorded reason. The budgets are
1155
- structural, not settings — 4 inspections and 1 replacement per child per
1156
- dispatch, after which the child runs under its own hard ceilings and the
1157
- supervisor goes quiet. Every applied decision is recorded in the report's
1158
- Supervisor section; `/team overseer off` (or no fast tier) restores the
1159
- pre-supervisor regime exactly.
1151
+ trigger, not a death sentence. Every dispatch gets its own supervisor on the
1152
+ fast model: a per-child silence window for suspected stalls, plus a periodic
1153
+ cadence check over the WHOLE roster — one bounded call that carries every live
1154
+ subagent and returns one decision per child: keep waiting, steer a nudge in,
1155
+ rebuild the child with an amended brief, or abandon it with a recorded reason.
1156
+ The cadence grows geometrically (5 min base, ×1.6, capped at 15 min), so a
1157
+ healthy slow subagent is checked logarithmically in its run time rather than
1158
+ every five minutes forever. The budgets are structural, not settings — 12
1159
+ looks, 3 nudges, 1 replacement and 1 abandon per child per dispatch. When the
1160
+ look budget is spent the supervisor says so once (`quiet` in the report) and
1161
+ stops waking that child; failed inspection calls cost a look but never an
1162
+ action. Every applied decision is recorded in the report's Supervisor section
1163
+ with the supervisor's own fast-tier spend, and replace/abandon also surface as
1164
+ a transcript notice while the dispatch runs.
1165
+
1166
+ **No kill by default.** While `team.overseer` is on (the default),
1167
+ `team.subagentTimeoutMs` never aborts anything — a positive value is only the
1168
+ supervisor's first cadence check. If the fast tier is missing (no key,
1169
+ `/fast off`), a due check degrades to an announced *unassisted wait*: the
1170
+ children keep running, the report notes how many checks ran degraded, and
1171
+ nothing is killed. A subagent still ends on the user's Esc, the explicit
1172
+ `team.dispatchTimeoutMs` / `team.maxTurnsPerSubagent` ceilings, its own idle
1173
+ watchdog (lengthened 4× while supervised), or the supervisor's own
1174
+ replace/abandon verdict. One boundary to know: a session with NO fast wiring
1175
+ at all and `team.overseer: true` runs children with no wall clock whatsoever
1176
+ — they finish, or die on their own un-lengthened watchdog. `/team overseer
1177
+ off` is the escape hatch back to the legacy regime where
1178
+ `team.subagentTimeoutMs` is a hard abort again.
1160
1179
 
1161
1180
  **Talking to each other.** Subagents get two extra tools, `team_send` and
1162
1181
  `team_wait`. Messages are delivered by attaching them to the recipient's next
@@ -2001,10 +2020,11 @@ env / `.env` → CLI flags**.
2001
2020
  | `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. |
2002
2021
  | `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. |
2003
2022
  | `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. |
2004
- | `team.subagentTimeoutMs` | `0` | One subagent's wall clock, clamped to `[0, 1800000]`. With the supervisor off it is a hard abort; with it on (the default) a positive value is the **first wall-clock inspection** — the supervisor looks and decides instead of killing. `0` (the default) = no ceiling — a wedged subagent still dies on its own idle watchdog, exactly like the lead. |
2023
+ | `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. |
2005
2024
  | `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. |
2006
2025
  | `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. |
2007
- | `team.overseer` | `true` | The dispatch supervisor: time ceilings on subagents become inspection triggers for a fast-tier supervisor that decides wait / nudge / replace / abandon instead of aborting. Needs the fast tier (`fast.enabled`); with the tier off or this `false`, children run under the legacy hard ceilings exactly. `/team overseer on\|off` flips it live, and it is read per dispatch. |
2026
+ | `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. |
2027
+ | `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. |
2008
2028
  | `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. |
2009
2029
  | `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. |
2010
2030
  | `todo.followThrough` | `"notify"` | What happens when a run ends with steps left. `notify` says so and stops; `auto` continues the plan after a 3-second grace window, bounded by two structural limits (one fruitless attempt, 25 continuations per plan) that are deliberately **not** settings; `off` is silent. An unrecognized value falls back to `notify`. |
@@ -2015,7 +2035,7 @@ env / `.env` → CLI flags**.
2015
2035
  | `retry.multiplier` | `2` | Growth factor, clamped to `[1, 5]`. The one **float** key in this section: `2.5` is a legitimate factor and is stored as typed. |
2016
2036
  | `retry.jitter` | `true` | Equal jitter (`d/2 + rand·d/2`), which de-correlates concurrent subagents hitting the same 429 while keeping the announced countdown within a factor of two of the truth. Full jitter can fire 40 ms after announcing 30 s, which reads as a bug. |
2017
2037
  | `retry.respectRetryAfter` | `true` | Honour `Retry-After` (and the provider rate-limit reset headers) as a **floor**, never a replacement — a `Retry-After: 1` on the ninth retry does not undo eight retries of backoff. A stated wait above 60 s is surfaced immediately instead of waited out. |
2018
- | `retry.maxElapsedMs` | `240000` | Wall-clock budget from the first failure, clamped to `[10000, 1800000]`. When `team.subagentTimeoutMs` is positive, keep this below it: an equal budget means a subagent is killed at the same instant its ladder ends, and the user is then told "subagent timed out" for what was a provider outage. |
2038
+ | `retry.maxElapsedMs` | `240000` | Wall-clock budget from the first failure, clamped to `[10000, 1800000]`. Under the legacy regime (`team.overseer: false`) with `team.subagentTimeoutMs` positive, keep this below it: an equal budget means a subagent is killed at the same instant its ladder ends, and the user is then told "subagent timed out" for what was a provider outage. |
2019
2039
  | `retry.onPartialStream` | `true` | Allow a restart after content has already streamed to the transcript. The view is rewound and the discarded tool cards removed; engine history is provably clean at that point, because it is only written on a completed turn. `false` refuses the whole path. |
2020
2040
  | `skills.enabled` | `true` | Master switch for the skill system. |
2021
2041
  | `skills.requireApproval` | `true` | Require a human to approve `skill_install` / `skill_create`. |
@@ -519,6 +519,19 @@ export class AgentController {
519
519
  // Every child runs the lead's own model, so the lead's cost table
520
520
  // is the one honest price for the dispatch total.
521
521
  modelCost: () => this.getModelInfo().cost,
522
+ // The FAST table for the supervisor's own spend line
523
+ // (subagent-overseer-v2 D-7 / R-P1-2). Reuses the tier
524
+ // resolution and the unknown-pricing predicate the fast
525
+ // reviewer already uses: undefined prices as `pricing
526
+ // unknown`, never as $0.00 (C-11 / RV-4).
527
+ fastModelCost: () => {
528
+ const tier = this.fast?.getTier();
529
+ if (!tier || !tier.ok)
530
+ return undefined;
531
+ return this.isPricedModel(tier.ref)
532
+ ? this.getModelInfoFor(tier.ref).cost
533
+ : undefined;
534
+ },
522
535
  withPausedWatchdog: (fn) => this.withPausedWatchdog(fn),
523
536
  }),
524
537
  ]
@@ -621,7 +634,15 @@ export class AgentController {
621
634
  // `dispatchTimeoutMs: 0` (the default) reaches the executor as an
622
635
  // override of `0` = NO TIMER AT ALL: the dispatch lasts as long as its
623
636
  // slowest child, and only an Esc ends it early (main-agent parity).
624
- ...(this.teamRegistered ? { task: config.team.dispatchTimeoutMs } : {}),
637
+ // D-4, THE SECOND ARMING SITE: the runtime arms its own dispatch
638
+ // timer under the same gate, because the executor timeout being
639
+ // cooperative means this override must never be the ONLY ceiling.
640
+ // With the supervisor gate open the ceiling disarms HERE too, so
641
+ // team.overseer can never leave two kill paths alive behind one
642
+ // config. Read once at construction, exactly as before.
643
+ ...(this.teamRegistered
644
+ ? { task: config.team.overseer ? 0 : config.team.dispatchTimeoutMs }
645
+ : {}),
625
646
  },
626
647
  },
627
648
  });
@@ -183,18 +183,20 @@ export function reduceEvent(event, cost, patches) {
183
183
  }
184
184
  /**
185
185
  * Tools that render their own transcript entry, so the generic tool card would
186
- * be a duplicate. EXACTLY ONE MEMBER; read todo-plan-execution §3.8 before
187
- * adding a second.
186
+ * be a duplicate. `task` is the second member (F-8 A'3): every dispatch opens
187
+ * a TeamCard immediately after, so the pair read as TWO dispatches - the first
188
+ * failed, the second succeeded - when there was only ever one. `todo_write`'s
189
+ * reasons (todo-plan-execution §3.8 / D-10) carry over unchanged.
188
190
  *
189
191
  * SUPPRESSING ONLY `tool_call_start` IS SUFFICIENT, AND THAT IS THE WHOLE REASON
190
192
  * THIS IS SAFE (D-10). Every later handler — `toolCallDelta` / `toolCallEnd` /
191
193
  * `toolExecStart` / `toolExecEnd` — resolves its target through
192
194
  * `findToolEntryId`, which returns `undefined` when no entry was created, and
193
- * every one of them already early-returns on that. There is no fifth handler. A
194
- * second suppression rule would be four more places to get the id matching
195
- * wrong, for nothing.
195
+ * every one of them already early-returns on that. There is no fifth handler.
196
+ * Blast radius verified: headless and `exec` writers key on EVENTS, not tool
197
+ * names, and `transcript-text` renders `kind:'team'` on its own.
196
198
  */
197
- const SELF_RENDERING_TOOLS = new Set(['todo_write']);
199
+ const SELF_RENDERING_TOOLS = new Set(['todo_write', 'task']);
198
200
  function reduceStreamEvent(se) {
199
201
  switch (se.type) {
200
202
  case 'thinking_start':
@@ -294,7 +294,8 @@ const COMMANDS = [
294
294
  ? `\nRunning: ${snapshot.runs.map((r) => `${r.label} (${r.phase})`).join(', ')}`
295
295
  : '';
296
296
  ctx.notify('info', `Team mode: ${state}. Max subagents: ${cfg.maxSubagents}, ` +
297
- `concurrent: ${cfg.maxConcurrent}, supervisor: ${cfg.overseer ? 'on' : 'off'} (needs the fast tier).${roster}`);
297
+ `concurrent: ${cfg.maxConcurrent}, supervisor: ${cfg.overseer ? 'on' : 'off'}` +
298
+ `${cfg.overseer ? ' (never kills; checks the fast tier each tick)' : ' (legacy hard timeouts)'}.${roster}`);
298
299
  return;
299
300
  }
300
301
  if (verb === 'on' || verb === 'off') {
@@ -20,7 +20,7 @@ export declare const LOG_CONFIG_SET_KEYS: readonly ["log.level", "log.toFile", "
20
20
  * switch falls through every case, writes nothing, and still prints
21
21
  * `Set team.enabled = false`.
22
22
  */
23
- export declare const TEAM_CONFIG_SET_KEYS: readonly ["team.enabled", "team.maxSubagents", "team.maxConcurrent", "team.subagentTimeoutMs", "team.dispatchTimeoutMs", "team.maxTurnsPerSubagent", "team.overseer"];
23
+ export declare const TEAM_CONFIG_SET_KEYS: readonly ["team.enabled", "team.maxSubagents", "team.maxConcurrent", "team.subagentTimeoutMs", "team.dispatchTimeoutMs", "team.maxTurnsPerSubagent", "team.overseer", "team.overseerIntervalMs"];
24
24
  /**
25
25
  * Config keys under `retry.` that `aragon config set` accepts
26
26
  * (llm-api-retry-backoff §8.2).
@@ -44,6 +44,7 @@ export const TEAM_CONFIG_SET_KEYS = [
44
44
  'team.dispatchTimeoutMs',
45
45
  'team.maxTurnsPerSubagent',
46
46
  'team.overseer',
47
+ 'team.overseerIntervalMs',
47
48
  ];
48
49
  /**
49
50
  * Config keys under `retry.` that `aragon config set` accepts
@@ -202,6 +203,8 @@ export function applyTeamConfigSet(key, value) {
202
203
  return patch(clampOne('maxTurnsPerSubagent'));
203
204
  case 'team.overseer':
204
205
  return patch({ overseer: isTrue(value) });
206
+ case 'team.overseerIntervalMs':
207
+ return patch(clampOne('overseerIntervalMs'));
205
208
  default:
206
209
  return null;
207
210
  }
@@ -6,10 +6,10 @@
6
6
  * legitimately long single tool run can never be watchdog-killed.
7
7
  */
8
8
  import process from 'node:process';
9
- import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, parseThresholdInput, } from './schema.js';
9
+ import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, legacyTeamCeilingPatch, parseThresholdInput, } from './schema.js';
10
10
  import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
11
- import { readConfigFile } from './store.js';
12
- import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey } from './model-profile-resolution.js';
11
+ import { readConfigFile, updatePersistedConfig } from './store.js';
12
+ import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey, } from './model-profile-resolution.js';
13
13
  import { getConfigPath } from './app-paths.js';
14
14
  import { setHistoryEnabled } from './prompt-history.js';
15
15
  import { setEntryRetain } from '../agent/entry-limits.js';
@@ -439,6 +439,44 @@ function resolveContextWindow(env, file) {
439
439
  }
440
440
  return DEFAULT_CONFIG.contextWindow;
441
441
  }
442
+ /**
443
+ * Rewrite the cli<=0.6.16 persisted team ceiling defaults to 0 and persist the
444
+ * change (F-8 A'4). ONE-SHOT and IDEMPOTENT: after the first rewrite the file
445
+ * carries 0, which matches nothing, so nothing is written again.
446
+ *
447
+ * THE FILE LAYER ONLY, deliberately: env and flags are this-launch intent and
448
+ * survive untouched, and with the D-4 semantics a positive surviving value is
449
+ * a supervisor cadence hint, never a kill. The raw file object is patched IN
450
+ * PLACE (the same precedent `readConfigFile` sets with `fullscreen`) so the
451
+ * `file` layer this function's caller resolves from already reads 0; the
452
+ * write goes through `updatePersistedConfig`, the one atomic write path every
453
+ * other settings change uses.
454
+ *
455
+ * A FAILED WRITE MUST NOT BLOCK STARTUP: the values are already neutralized
456
+ * in memory, and the next launch retries — which is why the catch is empty
457
+ * and the migration is logged instead of surfaced.
458
+ */
459
+ function migrateLegacyTeamCeilings(raw) {
460
+ const patch = legacyTeamCeilingPatch(raw?.team);
461
+ if (patch === null)
462
+ return;
463
+ // THE FULL SECTION, not the bare patch: the write port types `team` as a
464
+ // whole `TeamConfig` (its merge then deep-merges over what is on disk), and
465
+ // the section the file already carries plus the zeroed keys IS the section
466
+ // we want on disk.
467
+ const migratedTeam = { ...raw.team, ...patch };
468
+ Object.assign(raw.team, patch);
469
+ try {
470
+ updatePersistedConfig({ team: migratedTeam });
471
+ }
472
+ catch {
473
+ // Unwritable config dir: in-memory values are already migrated; retry
474
+ // next launch (idempotent).
475
+ }
476
+ getLogger().warn('config', 'team_legacy_ceilings_migrated', {
477
+ keys: Object.keys(patch).join(','),
478
+ });
479
+ }
442
480
  /**
443
481
  * Resolve the effective config by merging all layers, then derive the runtime
444
482
  * (non-persisted) fields and enforce the timeout invariant.
@@ -448,6 +486,7 @@ export function loadConfig(flags = {}, options = {}) {
448
486
  // `.env` must be loaded before reading env overrides.
449
487
  loadDotenv(cwd);
450
488
  const read = readConfigFile();
489
+ migrateLegacyTeamCeilings(read.config);
451
490
  const projection = projectModelProfiles(read.config ?? {});
452
491
  const file = projection.file;
453
492
  const env = readEnvConfig();
@@ -309,10 +309,11 @@ export interface TeamConfig {
309
309
  * the lead, whose only in-run ceiling is the event-silence idle watchdog
310
310
  * a child already keeps. A wedged child still dies on its OWN watchdog.
311
311
  *
312
- * WITH `team.overseer` ON (the default) this is the FIRST wall-clock
313
- * INSPECTION point, not a hard abort: the supervisor looks and decides
314
- * (wait / nudge / replace / abandon), so a slow-but-healthy child is no
315
- * longer killed at an arbitrary wall clock.
312
+ * WITH `team.overseer` ON (the default) this NEVER aborts anything
313
+ * (subagent-overseer-v2 D-4): a positive value is only a COMPATIBILITY
314
+ * SOURCE for the supervisor's first cadence check, honouring users who
315
+ * already treated it as a trigger point. The only hard abort left is
316
+ * the legacy regime (`team.overseer: false`).
316
317
  */
317
318
  subagentTimeoutMs: number;
318
319
  /**
@@ -329,17 +330,32 @@ export interface TeamConfig {
329
330
  */
330
331
  maxTurnsPerSubagent: number;
331
332
  /**
332
- * The dispatch supervisor (team-overseer): time ceilings on children
333
- * become inspection triggers for a fast-tier supervisor that decides
334
- * wait / nudge / replace / abandon, instead of aborts. Default TRUE.
333
+ * The dispatch supervisor (team-overseer / subagent-overseer-v2): time
334
+ * ceilings on children become inspection triggers for a fast-tier
335
+ * supervisor that decides wait / nudge / replace / abandon, instead of
336
+ * aborts. Default TRUE.
335
337
  *
336
- * READ PER DISPATCH. With the supervisor off (or no fast tier), children
337
- * run exactly the pre-feature regime: `subagentTimeoutMs` is a hard abort
338
- * and silence is the child's own watchdog. With it ON,
339
- * `subagentTimeoutMs > 0` is the FIRST wall-clock inspection point, not
340
- * a death sentence.
338
+ * READ PER DISPATCH. The supervisor ticks on a cadence ladder over every
339
+ * live child, and NO timeout is a death sentence while this is on: with
340
+ * the fast tier missing a tick degrades to an announced unassisted wait,
341
+ * never a kill. With this FALSE, children run exactly the pre-feature
342
+ * legacy regime: `subagentTimeoutMs` is a hard abort and silence is the
343
+ * child's own watchdog.
341
344
  */
342
345
  overseer: boolean;
346
+ /**
347
+ * Supervisor cadence BASE, in milliseconds (subagent-overseer-v2 D-8).
348
+ * `0` (the default) = the structural default
349
+ * (`TEAM_LIMITS.overseerDefaultCheckMs`, 300 s).
350
+ *
351
+ * EFFECTIVE FIRST CHECK, derived read-only and never written back:
352
+ * `overseerIntervalMs > 0` wins; otherwise a positive
353
+ * `subagentTimeoutMs` is honoured (users who already treated it as a
354
+ * trigger point); otherwise the structural default. Later checks grow
355
+ * by `overseerCadenceRatio` (1.6), clamped to [60 s, 900 s]. Only
356
+ * meaningful while `team.overseer` is true.
357
+ */
358
+ overseerIntervalMs: number;
343
359
  }
344
360
  /**
345
361
  * The requirement's ceiling on the fan-out (R-g).
@@ -364,6 +380,31 @@ export declare const DEFAULT_TEAM_CONFIG: TeamConfig;
364
380
  * the two disagree would make the panel's `n/m` readout confusing for no gain.
365
381
  */
366
382
  export declare function clampTeamConfig(raw: unknown): TeamConfig;
383
+ /**
384
+ * The team ceilings cli <= 0.6.16 (7a6bf5630) persisted as its OWN defaults:
385
+ * every user who touched team mode back then still carries this exact triple
386
+ * in config.json, because `resolveTeamConfig` merges file values verbatim and
387
+ * no migration ever rewrote them. They are the kill ceilings behind F-8's
388
+ * "first block all failed" dispatches, so loading rewrites them to 0 (= no
389
+ * ceiling, the shipped default since 2ff15a9c3).
390
+ *
391
+ * EXACT-EQUALITY ONLY: a value that differs was a deliberate user choice and
392
+ * survives untouched. The price of that test is that a user who hand-types
393
+ * 300000 today is migrated to 0 too - the accepted trade, and the safe
394
+ * direction (no ceiling, not a cliff).
395
+ */
396
+ export declare const LEGACY_TEAM_CEILING_DEFAULTS: Readonly<{
397
+ subagentTimeoutMs: number;
398
+ dispatchTimeoutMs: number;
399
+ maxTurnsPerSubagent: number;
400
+ }>;
401
+ /**
402
+ * The `{ key: 0 }` patch for legacy-default-equal ceilings, or `null` when
403
+ * nothing matches. Pure on purpose: `loadConfig` decides WHERE the patch is
404
+ * applied and persisted; this function only answers WHICH keys match, so the
405
+ * equality test lives beside the constants it reads.
406
+ */
407
+ export declare function legacyTeamCeilingPatch(raw: unknown): Partial<TeamConfig> | null;
367
408
  export interface TodoConfig {
368
409
  /** Register `todo_write` at all. Default TRUE. */
369
410
  enabled: boolean;
@@ -455,6 +455,8 @@ export const DEFAULT_TEAM_CONFIG = {
455
455
  // The dispatch supervisor, default ON (team-overseer): ceilings become
456
456
  // inspections; the fast-tier supervisor decides what to do.
457
457
  overseer: true,
458
+ // 0 = the structural cadence default (300 s). See the field docstring.
459
+ overseerIntervalMs: 0,
458
460
  };
459
461
  const SUBAGENTS_RANGE = { min: 1, max: HARD_MAX_SUBAGENTS };
460
462
  const CONCURRENT_RANGE = { min: 1, max: HARD_MAX_SUBAGENTS };
@@ -463,6 +465,7 @@ const CONCURRENT_RANGE = { min: 1, max: HARD_MAX_SUBAGENTS };
463
465
  // ("my setting won't stick"), the exact trap `scrollResumeMs` records above.
464
466
  const SUBAGENT_TIMEOUT_RANGE = { min: 0, max: 1_800_000 };
465
467
  const DISPATCH_TIMEOUT_RANGE = { min: 0, max: 3_600_000 };
468
+ const OVERSEER_INTERVAL_RANGE = { min: 0, max: 3_600_000 };
466
469
  const TURNS_RANGE = { min: 0, max: 100 };
467
470
  /**
468
471
  * THE single gate for every team-config read AND write, mirroring
@@ -486,8 +489,47 @@ export function clampTeamConfig(raw) {
486
489
  dispatchTimeoutMs: clampIntAllowingZero(src.dispatchTimeoutMs, DEFAULT_TEAM_CONFIG.dispatchTimeoutMs, DISPATCH_TIMEOUT_RANGE),
487
490
  maxTurnsPerSubagent: clampIntAllowingZero(src.maxTurnsPerSubagent, DEFAULT_TEAM_CONFIG.maxTurnsPerSubagent, TURNS_RANGE),
488
491
  overseer: bool(src.overseer, DEFAULT_TEAM_CONFIG.overseer),
492
+ overseerIntervalMs: clampIntAllowingZero(src.overseerIntervalMs, DEFAULT_TEAM_CONFIG.overseerIntervalMs, OVERSEER_INTERVAL_RANGE),
489
493
  };
490
494
  }
495
+ /**
496
+ * The team ceilings cli <= 0.6.16 (7a6bf5630) persisted as its OWN defaults:
497
+ * every user who touched team mode back then still carries this exact triple
498
+ * in config.json, because `resolveTeamConfig` merges file values verbatim and
499
+ * no migration ever rewrote them. They are the kill ceilings behind F-8's
500
+ * "first block all failed" dispatches, so loading rewrites them to 0 (= no
501
+ * ceiling, the shipped default since 2ff15a9c3).
502
+ *
503
+ * EXACT-EQUALITY ONLY: a value that differs was a deliberate user choice and
504
+ * survives untouched. The price of that test is that a user who hand-types
505
+ * 300000 today is migrated to 0 too - the accepted trade, and the safe
506
+ * direction (no ceiling, not a cliff).
507
+ */
508
+ export const LEGACY_TEAM_CEILING_DEFAULTS = {
509
+ subagentTimeoutMs: 300_000,
510
+ dispatchTimeoutMs: 900_000,
511
+ maxTurnsPerSubagent: 24,
512
+ };
513
+ /**
514
+ * The `{ key: 0 }` patch for legacy-default-equal ceilings, or `null` when
515
+ * nothing matches. Pure on purpose: `loadConfig` decides WHERE the patch is
516
+ * applied and persisted; this function only answers WHICH keys match, so the
517
+ * equality test lives beside the constants it reads.
518
+ */
519
+ export function legacyTeamCeilingPatch(raw) {
520
+ if (!raw || typeof raw !== 'object')
521
+ return null;
522
+ const src = raw;
523
+ const patch = {};
524
+ let matched = false;
525
+ for (const key of Object.keys(LEGACY_TEAM_CEILING_DEFAULTS)) {
526
+ if (src[key] === LEGACY_TEAM_CEILING_DEFAULTS[key]) {
527
+ patch[key] = 0;
528
+ matched = true;
529
+ }
530
+ }
531
+ return matched ? patch : null;
532
+ }
491
533
  export const DEFAULT_TODO_CONFIG = {
492
534
  enabled: true,
493
535
  panel: true,
@@ -102,11 +102,12 @@ export declare const TEAM_LIMITS: {
102
102
  readonly maxColdStartRetries: 1;
103
103
  readonly retryBackoffMs: 2000;
104
104
  /**
105
- * Wall-clock budget of everything one inspection may carry. The digest holds
106
- * run facts, the supervisor's own memory and the tail of the child's live
107
- * history; unbounded, one look at a child that read five big files would
108
- * make the "cheap" supervision call cost more than the child (the argument
109
- * FAST_LIMITS.digestMaxChars makes, one level up).
105
+ * Wall-clock budget of everything one SINGLE inspection may carry (the
106
+ * silence-triggered path). The digest holds run facts, the supervisor's own
107
+ * memory and the tail of the child's live history; unbounded, one look at a
108
+ * child that read five big files would make the "cheap" supervision call
109
+ * cost more than the child (the argument FAST_LIMITS.digestMaxChars makes,
110
+ * one level up).
110
111
  */
111
112
  readonly overseerDigestBytes: 8000;
112
113
  /** Clamp on a `nudge` guidance text. Advice, not a second brief. */
@@ -114,11 +115,48 @@ export declare const TEAM_LIMITS: {
114
115
  /** Clamp on the decision `reason` recorded in the report. */
115
116
  readonly overseerReasonChars: 200;
116
117
  /**
117
- * Inspections PER CHILD per dispatch. An indecisive supervisor must stop
118
- * burning fast-tier calls; past the budget the child simply runs under its
119
- * hard ceilings and the supervisor goes quiet.
118
+ * Per-child slice of a BATCH digest (D-3 / R-P2-5). 6.7x tighter than the
119
+ * single-inspection budget above: a batch carries up to ten children in one
120
+ * call, and memory plus the history tail are the first to give way (the
121
+ * facts-first assembly order already puts them last).
120
122
  */
121
- readonly overseerMaxInspectionsPerChild: 4;
123
+ readonly overseerPerChildDigestBytes: 1200;
124
+ /** Whole-batch digest ceiling (D-3): 10 children x 1 200 B fits exactly. */
125
+ readonly overseerBatchDigestBytes: 12000;
126
+ /**
127
+ * Output budget of ONE batch inspection (R-P0-1). The single-inspection path
128
+ * keeps `FAST_LIMITS.reviewOutputTokens` (512); a batch must return up to
129
+ * ten decision objects, and 512 tokens cannot physically hold ten x
130
+ * (reason <= 200 + guidance <= 1200). A truncated batch reply fails
131
+ * "first `[` to last `]`" parsing and degrades to all-wait - exactly the
132
+ * failure this separate, larger budget exists to prevent.
133
+ */
134
+ readonly overseerBatchOutputTokens: 2048;
135
+ /** Tighter `reason` clamp inside the batch protocol (R-P0-1). */
136
+ readonly overseerBatchReasonChars: 80;
137
+ /** Tighter `guidance` clamp inside the batch protocol (R-P0-1). */
138
+ readonly overseerBatchGuidanceChars: 400;
139
+ /**
140
+ * LOOKS per child per dispatch (D-5). A look is cost, not mutation: every
141
+ * inspection of a child - silence-triggered, cadence tick or post-mortem -
142
+ * spends one, including a call that failed softly. Twelve with the cadence
143
+ * ladder covers hours of healthy slow work; past the budget the supervisor
144
+ * says so once (`trigger: 'quiet'`) and stops waking that child up.
145
+ */
146
+ readonly overseerMaxLooksPerChild: 12;
147
+ /**
148
+ * LOOKS per dispatch, the fool-proof total (R-P1-6):
149
+ * hardMaxSubagents x overseerMaxLooksPerChild = 120. A mathematical upper
150
+ * bound, not a tuning knob - the per-child budget always binds first; this
151
+ * one exists so a bug cannot loop calls forever. When it trips, the whole
152
+ * MonitorLoop stops with one aggregate notice.
153
+ */
154
+ readonly overseerMaxLooksPerDispatch: number;
155
+ /**
156
+ * NUDGES per child per dispatch (D-5). Advice is cheap but not free: a
157
+ * supervisor that steers the same child forever is one that never decided.
158
+ */
159
+ readonly overseerMaxNudgesPerChild: 3;
122
160
  /**
123
161
  * Rebuilds PER CHILD per dispatch. One: a replacement that also stalls dies
124
162
  * on its own watchdog rather than ping-ponging fresh children at a task the
@@ -126,13 +164,25 @@ export declare const TEAM_LIMITS: {
126
164
  */
127
165
  readonly overseerMaxReplacementsPerChild: 1;
128
166
  /**
129
- * First wall-clock check when `team.subagentTimeoutMs === 0` (the shipped
130
- * default): the supervisor still looks even when no ceiling was configured.
167
+ * Abandons PER CHILD per dispatch (D-5). One, so a supervisor that has
168
+ * already given up on a brief cannot keep re-killing bodies of it.
169
+ */
170
+ readonly overseerMaxAbandonsPerChild: 1;
171
+ /**
172
+ * Cadence ladder growth factor (D-2). Each no-action inspection schedules
173
+ * the next `previous * ratio` out, so a healthy slow child is looked at
174
+ * logarithmically in its run time rather than every five minutes.
175
+ */
176
+ readonly overseerCadenceRatio: 1.6;
177
+ /**
178
+ * First cadence check when neither `team.overseerIntervalMs` nor
179
+ * `team.subagentTimeoutMs` names one (the shipped defaults): the supervisor
180
+ * still looks even when no ceiling was configured.
131
181
  */
132
182
  readonly overseerDefaultCheckMs: 300000;
133
- /** Clamp on the model-chosen `nextCheckMs`, lower bound. */
183
+ /** Clamp on a ladder step / model-chosen `nextCheckMs`, lower bound. */
134
184
  readonly overseerNextCheckMinMs: 60000;
135
- /** Clamp on the model-chosen `nextCheckMs`, upper bound. */
185
+ /** Clamp on a ladder step / model-chosen `nextCheckMs`, upper bound. */
136
186
  readonly overseerNextCheckMaxMs: 900000;
137
187
  /**
138
188
  * A child's OWN idle watchdog, multiplied by this while the supervisor is
@@ -142,6 +192,16 @@ export declare const TEAM_LIMITS: {
142
192
  */
143
193
  readonly overseerWatchdogFactor: 4;
144
194
  };
195
+ /**
196
+ * The pseudo-label the runtime's AGGREGATE supervisor notices travel under
197
+ * (subagent-overseer-v2 D-5 / R-P1-6): the dispatch-total look-budget notice
198
+ * is one fact about the whole roster, not a row any child owns. RESERVED at
199
+ * normalization (`normalizeSubagentSpecs` seeds `dedupeLabel` with it), so a
200
+ * spec asking for the bare label `team` is handed `team-2` instead - the
201
+ * report ledger and the exec stream can then never attribute an aggregate
202
+ * notice to a real child, or swallow that child's own quiet note.
203
+ */
204
+ export declare const TEAM_AGGREGATE_LABEL = "team";
145
205
  /**
146
206
  * The two comm tools, which are POLICY-EXEMPT inside a child (§3.11).
147
207
  *