@event4u/agent-config 9.28.0 → 9.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +6 -6
  3. package/CHANGELOG.md +172 -5
  4. package/README.md +2 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/analyze/inbox.md +22 -1
  7. package/dist/agent-src/commands/cost/report.md +2 -2
  8. package/dist/agent-src/commands/explain-run.md +31 -1
  9. package/dist/agent-src/commands/team/adversarial.md +8 -7
  10. package/dist/agent-src/commands/team/delegate.md +14 -12
  11. package/dist/agent-src/commands/team/review.md +9 -8
  12. package/dist/agent-src/commands/team/status.md +9 -8
  13. package/dist/agent-src/commands/team.md +29 -19
  14. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  15. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  16. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  17. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +22 -1
  18. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
  19. package/dist/agent-src/contexts/execution/subagent-response-contract.md +65 -1
  20. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  21. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  22. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  23. package/dist/agent-src/rules/delegation-policy.md +11 -7
  24. package/dist/agent-src/rules/preservation-guard.md +9 -0
  25. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  26. package/dist/agent-src/rules/token-budget-discipline.md +1 -1
  27. package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
  28. package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
  29. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  30. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  31. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  32. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  33. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +17 -5
  34. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  35. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  36. package/dist/agent-src/templates/AGENTS.md +4 -4
  37. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  38. package/dist/agent-src/templates/roadmaps.md +23 -0
  39. package/dist/cli/registry.js +2 -0
  40. package/dist/cli/registry.js.map +1 -1
  41. package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
  42. package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-BTJNRUX3.js} +22 -35
  43. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-DDUNNFYX.js} +24 -17
  44. package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-EQY4FLJB.js} +3 -3
  45. package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-IYTGIG2Y.js} +155 -91
  46. package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
  47. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  48. package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
  49. package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
  50. package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
  51. package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
  52. package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
  53. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  54. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  55. package/dist/cli-delegate/cmd_conformance.js +2803 -137
  56. package/dist/cli-delegate/cmd_doctor.js +10 -5
  57. package/dist/cli-delegate/cmd_explain.js +2 -2
  58. package/dist/cli-delegate/cmd_export.js +4 -4
  59. package/dist/cli-delegate/cmd_handoff.js +88 -8
  60. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  61. package/dist/cli-delegate/cmd_migrate.js +2 -2
  62. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  63. package/dist/cli-delegate/cmd_prune.js +4 -4
  64. package/dist/cli-delegate/cmd_route_audit.js +3 -2
  65. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  66. package/dist/cli-delegate/cmd_session_recycle.js +177 -0
  67. package/dist/cli-delegate/cmd_settings_get.js +2 -8
  68. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  69. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  70. package/dist/cli-delegate/cmd_sync.js +4 -4
  71. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  72. package/dist/cli-delegate/cmd_update.js +1 -1
  73. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  74. package/dist/cli-delegate/cmd_validate.js +4 -4
  75. package/dist/cli-delegate/cmd_versions.js +1 -1
  76. package/dist/discovery/deprecation-report.md +1 -1
  77. package/dist/discovery/discovery-manifest.json +72 -23
  78. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  79. package/dist/discovery/discovery-manifest.summary.md +3 -2
  80. package/dist/discovery/orphan-report.md +1 -1
  81. package/dist/discovery/packs.json +39 -2
  82. package/dist/discovery/trust-report.md +2 -2
  83. package/dist/discovery/workspaces.json +12 -4
  84. package/dist/hooks/dispatch.js +2278 -611
  85. package/dist/install/install.mjs +56 -17
  86. package/dist/mcp/registry-manifest.json +3 -3
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +4122 -699
  89. package/dist/router.json +1 -1
  90. package/dist/scripts/_lib/agent_settings.js +54 -0
  91. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  92. package/dist/scripts/condense.js +147 -2
  93. package/dist/scripts/condense.js.map +1 -1
  94. package/dist/server/schemas/settings.js +12 -7
  95. package/dist/server/schemas/settings.js.map +1 -1
  96. package/dist/shared/settingsCarveOut.js +0 -9
  97. package/dist/shared/settingsCarveOut.js.map +1 -1
  98. package/dist/shared/settingsClasses.js +11 -9
  99. package/dist/shared/settingsClasses.js.map +1 -1
  100. package/dist/ui/assets/index-B5qGYOJo.js +26 -0
  101. package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
  102. package/dist/ui/index.html +1 -1
  103. package/docs/contracts/settings-classes.md +58 -12
  104. package/package.json +1 -1
  105. package/src/agent-src/scripts/roadmap_gates.ts +312 -0
  106. package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
  107. package/src/agent-src/templates/AGENTS.md +4 -4
  108. package/src/config/agent-settings.template.yml +88 -66
  109. package/src/config/discovery/packs.yml +11 -0
  110. package/src/config/discovery/workspaces.yml +1 -1
  111. package/src/config/dispatch-economy-metrics.json +61 -0
  112. package/src/config/evaluator-budgets.json +4 -4
  113. package/src/config/gate-violation-baselines.json +3 -3
  114. package/src/config/hook-token-budget.json +91 -0
  115. package/src/config/recycle-threshold-budget.json +12 -0
  116. package/src/config/rule-enforcement-baseline.json +86 -0
  117. package/src/scripts/_cli/cmd_conformance.ts +31 -44
  118. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  119. package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
  120. package/src/scripts/_dispatch.bash +23 -0
  121. package/src/scripts/_lib/agent_settings.ts +58 -0
  122. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  123. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  124. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  125. package/src/scripts/_lib/host_capability.ts +63 -6
  126. package/src/scripts/_lib/judgment_ladder.ts +634 -0
  127. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  128. package/src/scripts/_lib/orchestration_record.ts +78 -10
  129. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  130. package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
  131. package/src/scripts/_lib/self_repair.ts +354 -13
  132. package/src/scripts/_lib/self_repair_store.ts +28 -0
  133. package/src/scripts/_lib/session_eol.ts +230 -0
  134. package/src/scripts/_lib/session_role.ts +42 -0
  135. package/src/scripts/_lib/subagent_capsule.ts +341 -0
  136. package/src/scripts/_lib/subagent_response.ts +94 -0
  137. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  138. package/src/scripts/_lib/subagent_steering.ts +9 -4
  139. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  140. package/src/scripts/_lib/worker_budget.ts +37 -2
  141. package/src/scripts/ai_council/clients.ts +126 -55
  142. package/src/scripts/ai_council/config.ts +94 -6
  143. package/src/scripts/ai_council/handoff.ts +113 -0
  144. package/src/scripts/ai_council/modes.ts +19 -4
  145. package/src/scripts/ai_council/orchestrator.ts +105 -0
  146. package/src/scripts/ai_council/quorum.ts +84 -0
  147. package/src/scripts/ai_council/session.ts +44 -0
  148. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  149. package/src/scripts/ai_team/availability.ts +110 -0
  150. package/src/scripts/ai_team/config.ts +34 -14
  151. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  152. package/src/scripts/ask_transport.ts +221 -0
  153. package/src/scripts/bench_critic_protocol.ts +396 -0
  154. package/src/scripts/bench_hook_injection.ts +234 -0
  155. package/src/scripts/check_adr_frontmatter.ts +84 -40
  156. package/src/scripts/check_branch_freshness.ts +1 -0
  157. package/src/scripts/check_corpus_staleness.ts +18 -4
  158. package/src/scripts/check_detector_corpus.ts +219 -0
  159. package/src/scripts/check_gitignore_freshness.ts +42 -8
  160. package/src/scripts/check_hook_bundle_freshness.ts +24 -1
  161. package/src/scripts/check_kernel_prefix_stability.ts +1 -0
  162. package/src/scripts/check_standing_rule_delivery.ts +73 -32
  163. package/src/scripts/check_static_layer_stability.ts +154 -0
  164. package/src/scripts/check_suppression_hygiene.ts +48 -0
  165. package/src/scripts/condense.ts +162 -2
  166. package/src/scripts/council_cli.ts +401 -25
  167. package/src/scripts/dispatch_economy_report.ts +448 -0
  168. package/src/scripts/dispatch_r2_reviewer.ts +15 -2
  169. package/src/scripts/explain_run.ts +206 -4
  170. package/src/scripts/forensics_report.ts +333 -0
  171. package/src/scripts/generate_settings_reference.ts +1 -0
  172. package/src/scripts/handoff_context_hook.ts +112 -4
  173. package/src/scripts/hook_manifest.yaml +117 -10
  174. package/src/scripts/hooks/concern_registry.ts +8 -0
  175. package/src/scripts/hooks/delegation_nudge_hook.ts +106 -35
  176. package/src/scripts/hooks/dispatch_hook.ts +39 -2
  177. package/src/scripts/hooks/edit_shape_hook.ts +303 -0
  178. package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
  179. package/src/scripts/hooks/reread_guard_hook.ts +314 -0
  180. package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
  181. package/src/scripts/hooks/session_eol_hook.ts +268 -0
  182. package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
  183. package/src/scripts/install-hooks.sh +1 -1
  184. package/src/scripts/language_mirror_hook.ts +89 -0
  185. package/src/scripts/lint_decision_impact.ts +14 -1
  186. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  187. package/src/scripts/lint_hook_manifest.ts +82 -0
  188. package/src/scripts/lint_never_silent.ts +276 -0
  189. package/src/scripts/lint_no_activation_gates.ts +271 -0
  190. package/src/scripts/lint_readme_serial_comma.ts +1 -0
  191. package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
  192. package/src/scripts/measure_turn_end_gate.ts +194 -0
  193. package/src/scripts/orchestration_record.ts +12 -1
  194. package/src/scripts/rebuild_hook_bundle.ts +178 -0
  195. package/src/scripts/release.ts +74 -2
  196. package/src/scripts/report_conformance_funnel.ts +284 -0
  197. package/src/scripts/report_imperative_density.ts +1 -0
  198. package/src/scripts/routing_doctor.ts +118 -36
  199. package/src/scripts/score_skill_selection.ts +19 -5
  200. package/src/scripts/self_repair_cli.ts +337 -59
  201. package/src/scripts/session_eol_report.ts +261 -0
  202. package/src/scripts/team_events_spike.ts +271 -0
  203. package/src/scripts/team_review_gate_hook.ts +31 -9
  204. package/src/server/schemas/settings.ts +24 -17
  205. package/src/shared/settingsCarveOut.ts +0 -10
  206. package/src/shared/settingsClasses.ts +11 -9
  207. package/src/templates/compact-instructions.md +40 -0
  208. package/dist/ui/assets/index-DmR0mbk4.js +0 -26
  209. package/dist/ui/assets/index-DmR0mbk4.js.map +0 -1
@@ -36,24 +36,26 @@ THE SAME MINUS THE QUOTA WIN.
36
36
  the tier/model choice is unchanged. This is the only place the
37
37
  "Sonnet-has-its-own-allowance" idea lives, and it lives as a runtime-detected
38
38
  flag, never as portable prose.
39
- 4. **Budget routing** (`subagents.budget_routing != off`,
39
+ 4. **Budget routing** (design:
40
40
  [`budget-routing` contract](../../../../docs/contracts/budget-routing.md)) →
41
- AFTER the tier resolves per 1–3, apply the budget relation via
42
- `pickTier` (`src/scripts/_lib/tier_budget_routing.ts`): cheapest
41
+ AFTER the tier resolves per 1–3, the budget relation via `pickTier`
42
+ (`src/scripts/_lib/tier_budget_routing.ts`) would apply: cheapest
43
43
  classifier-adequate tier WITH available budget; that tier exhausted or
44
44
  cooling → next tier up with budget; all unavailable → session model +
45
45
  surfaced one-line notice. Work is never blocked to save money. Before
46
46
  the dispatch is created, acquire the atomic reserve
47
47
  (`acquireBudgetPermit`; check state via `node src/scripts/cost/budget.mjs
48
48
  tier <t>`); a 429/quota error trips `tripCooldown` for that tier and
49
- falls back per the relation — never a retry loop. Under
50
- `budget_routing: ask`, the FIRST budget-motivated downshift of a session
51
- is confirmed once, in the shape
52
- [`settings-ask-protocol`](../../rules/settings-ask-protocol.md) fixes and
53
- without persisting the answer (`subagents.budget_routing` is class C);
54
- `auto` proceeds silently. Every budget-routed
55
- dispatch emits one `orchestration_record` line (tier + provenance-tagged
56
- token delta) so realized savings are measured, not asserted.
49
+ falls back per the relation — never a retry loop.
50
+ `subagents.budget_routing` was removed as a settings key (always-on
51
+ orchestration, road-to-always-on-orchestration Phase 1: no per-layer
52
+ on/off switch survives) — `pickTier` has NO production caller today, so
53
+ this point documents the DESIGNED relation, not a currently wired one;
54
+ wiring it is a later phase of that roadmap (council-side
55
+ `cli_call_budget`/`cost_budget` are the caps that replace the settings
56
+ gate). Every budget-routed dispatch, once wired, would emit one
57
+ `orchestration_record` line (tier + provenance-tagged token delta) so
58
+ realized savings are measured, not asserted.
57
59
 
58
60
  **Cache trade-off (road-to-cache-economy Phase 4).** A tier downshift changes
59
61
  the sub-task's model, and the prompt cache is keyed by `(model, prefix)` — so
@@ -64,6 +64,31 @@ ADR-109 response contract — it replaces nothing. Live evidence
64
64
  (2026-07-28): four lookup-class workers burned 280–327k tokens each on tasks
65
65
  a deterministic primitive answers for <1k.
66
66
 
67
+ ### Capsule watermark — the line below the kill line
68
+
69
+ The budget now carries **two** lines. `capsule_watermark` sits at
70
+ `CAPSULE_WATERMARK_FRACTION` (0.8) of `max_tokens_per_worker` and is **derived**
71
+ from it in `composeSpawnBrief`, never set independently — a watermark that could
72
+ drift away from the budget it sits under is a watermark nobody can reason about.
73
+ Reaching it means: emit a `CHECKPOINT` capsule
74
+ ([response contract](subagent-response-contract.md#checkpoint-capsule--the-generation-handoff))
75
+ **while there is still headroom to write one**. A worker at 100 % of its budget
76
+ cannot summarise itself; that headroom is the entire mechanism.
77
+
78
+ `evaluateWorkerBudget` reports the band between the two lines as
79
+ `watermark_hit` — the only window in which a capsule can be written at all.
80
+
81
+ **Shadow only, today.** The worker still runs on to the stop-loss and still
82
+ returns the `BLOCKED` partial result exactly as before; the capsule is recorded,
83
+ never acted on. Behaviour is unchanged on purpose, so the Phase-1 measurement is
84
+ not confounded by the mechanism it is measuring. A second candidate trigger —
85
+ novelty-per-step saturation
86
+ ([`capsule_trigger.ts`](../../../../src/scripts/_lib/capsule_trigger.ts),
87
+ term-frequency, no embeddings) — is logged alongside it, and the paired
88
+ comparison is pre-registered as `worker-capsule-trigger-arm` in
89
+ [`docs/CLAIMS.md`](../../../../docs/CLAIMS.md) with both arms losing as an
90
+ explicitly publishable outcome.
91
+
67
92
  ## Bundle resolver — bind to the existing surfaces
68
93
 
69
94
  The brief is **resolved**, not hand-built. `resolveBundle(slice)` maps a task
@@ -144,11 +144,14 @@ dashboards alone look fine while quality regresses.
144
144
  ## Kill-switch
145
145
 
146
146
  ```
147
- subagents.enabled: false (or subagents.auto: off)
147
+ emergency.orchestration_halt: true
148
148
  ```
149
149
 
150
- A single, no-deploy settings flip fully disables the layer (`isLayerDisabled()`).
151
- This is the canonical disable — no code change, effective on the next run.
150
+ A single, no-deploy settings flip fully disables the layer (`isLayerDisabled()`)
151
+ for the duration of an incident — the one audited switch that survives
152
+ always-on orchestration (road-to-always-on-orchestration Phase 1;
153
+ `docs/contracts/settings-classes.md` § "The one exception"). Disarming it
154
+ requires a non-empty `emergency.orchestration_halt_justification`.
152
155
 
153
156
  ## Reference implementation
154
157
 
@@ -46,16 +46,18 @@ Only when the activation gate from
46
46
  [`auto-orchestration-activation`](../contexts/execution/auto-orchestration-activation.md)
47
47
  clears — all of:
48
48
 
49
- - `subagents.enabled` **and** `subagents.auto != off`,
49
+ - `emergency.orchestration_halt` is not set,
50
50
  - the host-capability manifest reports `subagent_spawn: true`,
51
51
  - the task is classified **delegable** per
52
52
  [`auto-dispatch-classification`](../contexts/execution/auto-dispatch-classification.md)
53
53
  (≥1 independent, well-specified slice above the size floor).
54
54
 
55
- `subagents.auto: ask` → ask once before dispatching, in the shape
56
- [`settings-ask-protocol`](settings-ask-protocol.md) fixes (the answer is not
57
- persisted — `subagents.auto` is class C); `auto: on` → surface the chosen mode
58
- + per-subtask tiers in one line. Any gate failing, or `auto: off`, or no host
55
+ Always-on orchestration (road-to-always-on-orchestration Phase 1) removed the
56
+ `subagents.enabled`/`subagents.auto` settings this used to gate on — there is
57
+ no more per-layer on/off setting. A matched delegable signal → dispatch,
58
+ surfacing the chosen mode + per-subtask tiers in one line, never silent. An
59
+ AMBIGUOUS verdict (no enumerated signal matched) → **ask**, always — a verdict
60
+ to the user, never a speculative spawn. The emergency halt, or no host
59
61
  primitive → run in-session (clean no-op).
60
62
 
61
63
  ## What it makes binding
@@ -100,8 +102,10 @@ no-review session — verified; its advisory line reaches the dispatcher
100
102
  output, but host-side forwarding of stop-slot context to the model is
101
103
  unverified, so the model-facing end-review carrier is the AGENTS.md line
102
104
  plus this telemetry). The
103
- capability gate itself resolves from the committed host registry in
104
- `src/scripts/_lib/host_capability.ts` (settings override wins). What stays
105
+ capability gate itself resolves from the committed host registry merged with a
106
+ live environment probe in `src/scripts/_lib/host_capability.ts`
107
+ (`probeHostCapabilities` — capability is a fact about the host, never a
108
+ settings override). What stays
105
109
  model-carried, honestly: the decomposition itself, the per-return
106
110
  verification, and every dispatch on hosts without hook slots. The nudges are
107
111
  advisory by design — whether they change behaviour is measured by the
@@ -26,6 +26,15 @@ the result must be **at least as strong** as the original.
26
26
 
27
27
  ## Iron Laws — every passage stays, telegraph is fine
28
28
 
29
+ ```
30
+ EVERY PASSAGE STAYS — PARAGRAPH FOR PARAGRAPH, BULLET FOR BULLET, FENCE FOR FENCE.
31
+ IRON LAW HEADINGS SURVIVE VERBATIM AT THEIR LEVEL. FENCED BLOCKS SURVIVE BYTE-FOR-BYTE.
32
+ NEGATION CLAUSES (NO X / NEVER Y / NOT Z) STAY IN.
33
+ NEVER WEAKEN: MUST STAYS MUST. ROUTING AND DECISION LOGIC STAY AS STRONG.
34
+ NEVER BROADEN SCOPE BY MERGING UNRELATED WORKFLOWS.
35
+ TELEGRAPH THE WORDING AS HARD AS YOU WANT — CONDENSE, NEVER DELETE, NEVER SOFTEN.
36
+ ```
37
+
29
38
  Sections marked **Iron Law** (any heading matching `# Iron Law`, `# Iron Laws`,
30
39
  `# The Iron Law`, at any heading level, including numbered variants like
31
40
  `Iron Law 1`, `Iron Law 2`) are **non-negotiable** and require the strictest
@@ -7,7 +7,6 @@ triggers:
7
7
  - keyword: "settings:set"
8
8
  - keyword: "canary_name"
9
9
  - keyword: "rich_skills"
10
- - keyword: "budget_routing"
11
10
  - keyword: "adversarial_council"
12
11
  - keyword: "learn_on_session_end"
13
12
  - keyword: "open_edited_files"
@@ -34,7 +33,7 @@ obligation_frequency: "per-task"
34
33
  # Settings-Ask Protocol
35
34
 
36
35
  A settings question is the one ask where the agent poses the question *and* owns
37
- where the answer goes. Nine keys can produce one, in two mechanisms that look
36
+ where the answer goes. Seven keys can produce one, in two mechanisms that look
38
37
  identical from outside and differ completely underneath.
39
38
 
40
39
  ## The Iron Law
@@ -70,7 +69,7 @@ How long it stays cached in working state is the key's own business, and several
70
69
  keys state their own scope. Reading the C path as "re-ask every execution" would
71
70
  turn a fence against illegal writes into a source of extra questions.
72
71
 
73
- ## The nine keys
72
+ ## The seven keys
74
73
 
75
74
  **Class B — persist once**: `personal.canary_name` ·
76
75
  `personal.open_edited_files` · `memory.learn_on_session_end`. Each ships a
@@ -78,10 +77,12 @@ conservative default (`""`, `false`, `false`), so absent is indistinguishable
78
77
  from *no* and never from *yes*.
79
78
 
80
79
  **Class C carrying an `ask` value in its own enum** — a runtime question whose
81
- answer dies with the run: `subagents.budget_routing` and `worktrees.mode` (both
82
- **ship** as `ask`), plus `tokens.rich_skills`, `subagents.auto`,
83
- `subagents.adversarial_council`, `decision_engine.on_block` (which can be set to
84
- `ask` by a human but do not ship that way).
80
+ answer dies with the run: `worktrees.mode` (**ships** as `ask`), plus
81
+ `tokens.rich_skills`, `subagents.adversarial_council`, `decision_engine.on_block`
82
+ (which can be set to `ask` by a human but do not ship that way). Always-on
83
+ orchestration (road-to-always-on-orchestration Phase 1) deleted
84
+ `subagents.auto` and `subagents.budget_routing` — the layer no longer carries a
85
+ setting for this protocol to route.
85
86
 
86
87
  ## The budget
87
88
 
@@ -139,7 +139,7 @@ its obligation on line 300.
139
139
 
140
140
  ## The size band is measured, and only its ceiling is gated
141
141
 
142
- The `rich` band is **2,000–3,500 tokens** ([ADR-217](../docs/decisions/ADR-217-rich-class-band-measured-and-enforced.md)).
142
+ The `rich` band is **2,000–3,500 tokens** (ADR-217, `docs/decisions/ADR-217-rich-class-band-measured-and-enforced.md`).
143
143
  It was 2,000–5,000 and enforced by nothing until that record: measured with the
144
144
  exact BPE tokenizer, the largest rich artifact in the tree is 3,331 tokens, so
145
145
  the old ceiling described no artifact that existed. An unused permission costs
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * `agent-config gates` — the open decisions that need the user, as actions.
4
+ *
5
+ * The dashboard (`update_roadmap_progress`) already parses every `## Blockers`
6
+ * entry and already counts them per roadmap. What it does not do is separate
7
+ * the blockers the *user* must resolve from the ones the maintainer or an
8
+ * external party owns: the count links into a per-roadmap breakdown, so
9
+ * answering "what do I have to do" means opening every anchor and filtering
10
+ * `Owner:` by hand across the whole file. The fields exist, are CI-enforced by
11
+ * `lint_roadmap_blockers`, and never reach the user in a form they can act on.
12
+ *
13
+ * This command is that missing projection, and nothing more: same parser
14
+ * (`parse_blockers` / `collect`, imported — never re-implemented), filtered by
15
+ * owner, ordered by how much each blocker unblocks, rendered action-first.
16
+ *
17
+ * Deliberately NOT a gate: it exits 0 whether or not decisions are pending.
18
+ * Blocking a pipeline on "the user has an open decision" would turn a
19
+ * visibility aid into a second thing to fight.
20
+ *
21
+ * Invocation (from project root):
22
+ * ./agent-config gates # decisions owned by the user
23
+ * ./agent-config gates --all # every open blocker, grouped by owner
24
+ * ./agent-config gates --json # machine-readable
25
+ */
26
+
27
+ import * as path from 'node:path';
28
+ import { fileURLToPath, pathToFileURL } from 'node:url';
29
+ import * as fs from 'node:fs';
30
+ import { spawnSync } from 'node:child_process';
31
+
32
+ import {
33
+ collect,
34
+ blocker_needs_user as needsUser,
35
+ type Blocker,
36
+ } from './update_roadmap_progress.js';
37
+
38
+ const _HERE = fileURLToPath(import.meta.url);
39
+
40
+ /** Hard wrap for the rendered body — keeps output readable in a narrow pane. */
41
+ const WIDTH = 78;
42
+
43
+ interface Entry {
44
+ blocker: Blocker;
45
+ roadmapRel: string;
46
+ /** Open steps in the roadmap this blocker sits in — the "unblocks" weight. */
47
+ openSteps: number;
48
+ }
49
+
50
+ /**
51
+ * Rejoin wrapped continuation lines into one step each.
52
+ *
53
+ * `parse_blockers` returns `todo` as raw stripped lines, so a step that wraps
54
+ * over three source lines arrives as three entries. A new step starts at an
55
+ * ordered marker (`1.`, `2)`) or a bullet; everything else continues the one
56
+ * before it.
57
+ */
58
+ function regroupTodo(todo: readonly string[]): string[] {
59
+ const steps: string[] = [];
60
+ for (const line of todo) {
61
+ const startsStep = /^(\d+[.)]|[-*+])\s/.test(line);
62
+ if (startsStep || steps.length === 0) {
63
+ steps.push(line);
64
+ } else {
65
+ steps[steps.length - 1] = `${steps[steps.length - 1] as string} ${line}`;
66
+ }
67
+ }
68
+ return steps;
69
+ }
70
+
71
+ /** Wrap `text` to `width`, indenting every line after the first by `indent`. */
72
+ function wrap(text: string, width: number, indent: string): string[] {
73
+ const words = text.split(/\s+/).filter(Boolean);
74
+ if (words.length === 0) {
75
+ return [];
76
+ }
77
+ const lines: string[] = [];
78
+ let cur = '';
79
+ for (const w of words) {
80
+ const candidate = cur === '' ? w : `${cur} ${w}`;
81
+ if (candidate.length > width && cur !== '') {
82
+ lines.push(cur);
83
+ cur = w;
84
+ } else {
85
+ cur = candidate;
86
+ }
87
+ }
88
+ if (cur !== '') {
89
+ lines.push(cur);
90
+ }
91
+ return lines.map((l, i) => (i === 0 ? l : indent + l));
92
+ }
93
+
94
+ /**
95
+ * Column width for the label gutter — sized to the longest label in use
96
+ * (`Blocked until:`, 14 chars) plus one separating space, so no value ever
97
+ * runs flush against its own label.
98
+ */
99
+ const LABEL_W = 15;
100
+
101
+ /** A labelled block: ` Label: value`, wrapped and hanging-indented. */
102
+ function field(label: string, value: string): string[] {
103
+ const pad = ' ' + label.padEnd(LABEL_W);
104
+ const indent = ' '.repeat(pad.length);
105
+ const lines = wrap(value, WIDTH - pad.length, indent);
106
+ if (lines.length === 0) {
107
+ return [];
108
+ }
109
+ return [pad + lines[0], ...lines.slice(1)];
110
+ }
111
+
112
+ function collectEntries(roadmapRoot: string): Entry[] {
113
+ const entries: Entry[] = [];
114
+ for (const r of collect(roadmapRoot)) {
115
+ for (const b of r.open_blockers) {
116
+ entries.push({ blocker: b, roadmapRel: r.rel, openSteps: r.open_ });
117
+ }
118
+ }
119
+ // Most-unblocking first; ties broken deterministically so two runs on an
120
+ // unchanged tree print byte-identical output.
121
+ entries.sort(
122
+ (a, b) =>
123
+ b.openSteps - a.openSteps ||
124
+ a.roadmapRel.localeCompare(b.roadmapRel) ||
125
+ a.blocker.id.localeCompare(b.blocker.id),
126
+ );
127
+ return entries;
128
+ }
129
+
130
+ /**
131
+ * The parser's legacy fallback synthesises one blocker per `> Blocked until …`
132
+ * note under the fixed id `legacy`. That id is an implementation detail and
133
+ * says nothing to a reader, and its text is a *condition* ("blocked until X"),
134
+ * not an imperative — so both the heading and the field label are renamed for
135
+ * this one parser-defined case rather than dressed up as a step list.
136
+ */
137
+ function isLegacy(b: Blocker): boolean {
138
+ return b.id === 'legacy';
139
+ }
140
+
141
+ function renderEntry(e: Entry, index: number): string[] {
142
+ const head = `${index} · ${isLegacy(e.blocker) ? 'blocked-until note' : e.blocker.id}`;
143
+ const tail =
144
+ e.openSteps > 0 ? `unblocks: ${e.openSteps} step${e.openSteps !== 1 ? 's' : ''}` : '';
145
+ const dashes = Math.max(3, WIDTH - head.length - tail.length - 6);
146
+ const out: string[] = [
147
+ '',
148
+ `── ${head} ${'─'.repeat(dashes)} ${tail}`.trimEnd(),
149
+ ...field('Roadmap:', e.roadmapRel),
150
+ ...field('Blocks:', e.blocker.blocks),
151
+ ];
152
+
153
+ const doLabel = isLegacy(e.blocker) ? 'Blocked until:' : 'Do this:';
154
+ const steps = regroupTodo(e.blocker.todo);
155
+ if (steps.length === 0) {
156
+ out.push(
157
+ ...field(
158
+ doLabel,
159
+ '(no steps recorded — the blocker entry in the roadmap needs a ' +
160
+ '**What to do:** list)',
161
+ ),
162
+ );
163
+ } else {
164
+ out.push(...field(doLabel, steps[0] as string));
165
+ for (const s of steps.slice(1)) {
166
+ // Steps 2..n have no label of their own — align them under the
167
+ // first step's text, i.e. the same gutter `field()` produces.
168
+ const indent = ' '.repeat(LABEL_W + 2);
169
+ const lines = wrap(s, WIDTH - LABEL_W - 2, indent);
170
+ out.push(...lines.map((l, i) => (i === 0 ? indent + l : l)));
171
+ }
172
+ }
173
+ out.push(...field('Done when:', e.blocker.resolvedWhen));
174
+ return out;
175
+ }
176
+
177
+ function render(entries: readonly Entry[], all: boolean): string {
178
+ const mine = entries.filter((e) => needsUser(e.blocker.owner));
179
+ const others = entries.filter((e) => !needsUser(e.blocker.owner));
180
+ const shown = all ? entries : mine;
181
+ const lines: string[] = [];
182
+
183
+ if (mine.length === 0) {
184
+ lines.push(
185
+ others.length === 0
186
+ ? 'No open blockers at all.'
187
+ : `Nothing is waiting on you. ${others.length} open blocker` +
188
+ `${others.length !== 1 ? 's' : ''} sit with maintainer/external` +
189
+ `${all ? '' : ' — see --all'}.`,
190
+ );
191
+ } else {
192
+ const head =
193
+ `${mine.length} decision${mine.length !== 1 ? 's' : ''} need${mine.length === 1 ? 's' : ''} you`;
194
+ const rest =
195
+ others.length > 0
196
+ ? ` · ${others.length} more with maintainer/external${all ? '' : ' (--all)'}`
197
+ : '';
198
+ lines.push(head + rest);
199
+ }
200
+
201
+ let n = 0;
202
+ for (const e of shown) {
203
+ n += 1;
204
+ lines.push(...renderEntry(e, n));
205
+ }
206
+ if (shown.length > 0) {
207
+ lines.push('');
208
+ }
209
+ return lines.join('\n') + '\n';
210
+ }
211
+
212
+ function renderJson(entries: readonly Entry[], all: boolean): string {
213
+ const pick = all ? entries : entries.filter((e) => needsUser(e.blocker.owner));
214
+ return (
215
+ JSON.stringify(
216
+ {
217
+ needsYou: entries.filter((e) => needsUser(e.blocker.owner)).length,
218
+ other: entries.filter((e) => !needsUser(e.blocker.owner)).length,
219
+ blockers: pick.map((e) => ({
220
+ id: e.blocker.id,
221
+ roadmap: e.roadmapRel,
222
+ owner: e.blocker.owner,
223
+ needsYou: needsUser(e.blocker.owner),
224
+ blocks: e.blocker.blocks,
225
+ unblocksSteps: e.openSteps,
226
+ todo: regroupTodo(e.blocker.todo),
227
+ resolvedWhen: e.blocker.resolvedWhen,
228
+ })),
229
+ },
230
+ null,
231
+ 2,
232
+ ) + '\n'
233
+ );
234
+ }
235
+
236
+ function _isDir(p: string): boolean {
237
+ try {
238
+ return fs.statSync(p).isDirectory();
239
+ } catch {
240
+ return false;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Same repo-root resolution as the dashboard: honour the cwd when it holds
246
+ * `agents/roadmaps`, otherwise fall back to the git toplevel (monorepo
247
+ * sub-project support).
248
+ */
249
+ function _resolveRepoRoot(start: string): string {
250
+ if (_isDir(path.join(start, 'agents', 'roadmaps'))) {
251
+ return start;
252
+ }
253
+ try {
254
+ const r = spawnSync('git', ['rev-parse', '--show-toplevel'], {
255
+ cwd: start,
256
+ encoding: 'utf-8',
257
+ timeout: 10_000,
258
+ });
259
+ const top = (r.stdout || '').trim();
260
+ if (r.status === 0 && top !== '' && _isDir(path.join(top, 'agents', 'roadmaps'))) {
261
+ return top;
262
+ }
263
+ } catch {
264
+ /* not a git repo / git missing — keep the cwd default */
265
+ }
266
+ return start;
267
+ }
268
+
269
+ function main(argv?: readonly string[]): number {
270
+ const args = argv ?? process.argv.slice(2);
271
+ const all = args.includes('--all');
272
+ const json = args.includes('--json');
273
+
274
+ const repoRoot = _resolveRepoRoot(process.cwd());
275
+ const roadmapRoot = path.join(repoRoot, 'agents', 'roadmaps');
276
+ if (!_isDir(roadmapRoot)) {
277
+ process.stdout.write(
278
+ json ? '{"needsYou":0,"other":0,"blockers":[]}\n' : 'No roadmaps directory — nothing to report.\n',
279
+ );
280
+ return 0;
281
+ }
282
+
283
+ const entries = collectEntries(roadmapRoot);
284
+ process.stdout.write(json ? renderJson(entries, all) : render(entries, all));
285
+ return 0;
286
+ }
287
+
288
+ function _isCliEntry(): boolean {
289
+ if (process.argv[1] === undefined) {
290
+ return false;
291
+ }
292
+ const argvPath = path.resolve(process.argv[1]);
293
+ if (import.meta.url === pathToFileURL(argvPath).href) {
294
+ return true;
295
+ }
296
+ // A symlinked invocation (`.augment/scripts` → `dist/agent-src/scripts`, or
297
+ // macOS /var → /private/var) makes the raw URLs differ: import.meta.url is
298
+ // the resolved real path while argv[1] keeps the symlink. Compare realpaths
299
+ // so the entry guard still fires through the projection.
300
+ try {
301
+ return fs.realpathSync(_HERE) === fs.realpathSync(argvPath);
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+
307
+ if (_isCliEntry()) {
308
+ process.exitCode = main();
309
+ }
310
+
311
+ export { main, needsUser, regroupTodo, wrap, collectEntries, render, renderJson };
312
+ export type { Entry };
@@ -398,14 +398,32 @@ function _blockerField(slice: string, label: string): string | null {
398
398
  return parts.length ? parts.join(' ') : null;
399
399
  }
400
400
 
401
- /** Lines under `- **What to do:**` up to the next `- **Field:**` marker. */
401
+ /**
402
+ * The `- **What to do:**` text: the remainder of the marker line, plus every
403
+ * line under it up to the next `- **Field:**` marker.
404
+ *
405
+ * The marker line's own remainder is load-bearing and used to be dropped.
406
+ * Authors write both shapes — a numbered list under the marker, and prose that
407
+ * starts inline (`- **What to do:** the build work is done; only real …`) —
408
+ * and every *other* blocker field reads the inline part via `_blockerField`.
409
+ * Skipping it here truncated instructions mid-sentence in both the dashboard
410
+ * and `agent-config gates`, which is precisely the content those surfaces
411
+ * exist to deliver.
412
+ */
402
413
  function _blockerTodo(slice: string): string[] {
403
414
  const lines = _splitlines(slice);
404
- const startIdx = lines.findIndex((l) => /^-[ \t]*\*\*What to do:\*\*/i.test(_strip(l)));
415
+ const markerRe = /^-[ \t]*\*\*What to do:\*\*[ \t]*(.*)$/i;
416
+ const startIdx = lines.findIndex((l) => markerRe.test(_strip(l)));
405
417
  if (startIdx === -1) {
406
418
  return [];
407
419
  }
408
420
  const out: string[] = [];
421
+ const inline = _stripComment(
422
+ (markerRe.exec(_strip(lines[startIdx] as string))?.[1] as string) ?? '',
423
+ );
424
+ if (inline) {
425
+ out.push(inline);
426
+ }
409
427
  for (let i = startIdx + 1; i < lines.length; i++) {
410
428
  const trimmed = _strip(lines[i] as string);
411
429
  if (BLOCKER_FIELD_RE.test(trimmed) || trimmed.startsWith('#')) {
@@ -458,18 +476,56 @@ function parse_blockers(raw_text: string): Blocker[] {
458
476
  }
459
477
  const legacyMatch = LEGACY_BLOCKED_UNTIL_RE.exec(text);
460
478
  if (legacyMatch) {
479
+ // The legacy note is a blockquote and routinely wraps over several
480
+ // `> ` lines. The regex captures one line, so the continuation used to
481
+ // be dropped and the instruction ended mid-sentence — the same defect
482
+ // `_blockerTodo` carried above. Walk the following quoted lines until
483
+ // the quote block ends.
484
+ const parts = [_strip(legacyMatch[1] as string)];
485
+ const allLines = _splitlines(text);
486
+ const noteIdx = allLines.findIndex((l) => LEGACY_BLOCKED_UNTIL_RE.test(_strip(l)));
487
+ for (let i = noteIdx + 1; noteIdx !== -1 && i < allLines.length; i++) {
488
+ const quoted = /^>[ \t]?(.*)$/.exec(_strip(allLines[i] as string));
489
+ if (!quoted) {
490
+ break;
491
+ }
492
+ const cont = _strip(quoted[1] as string);
493
+ if (cont === '') {
494
+ break;
495
+ }
496
+ parts.push(cont);
497
+ }
461
498
  blockers.push({
462
499
  id: 'legacy',
463
500
  status: 'open',
464
501
  owner: 'user',
465
502
  blocks: 'entire roadmap',
466
- todo: [_strip(legacyMatch[1] as string)],
503
+ todo: [parts.join(' ')],
467
504
  resolvedWhen: 'condition described above clears',
468
505
  });
469
506
  }
470
507
  return blockers;
471
508
  }
472
509
 
510
+ /**
511
+ * Does this blocker need the human at the keyboard, rather than the maintainer
512
+ * or an external party?
513
+ *
514
+ * Owner values in the wild are not the clean `user | maintainer | external`
515
+ * enum the template comment suggests — real entries carry qualifiers
516
+ * (`user (billable spend)`, `user / maintainer`, `maintainer (security role)`).
517
+ * Word-boundary matching on `user` keeps the qualified forms and rejects
518
+ * `maintainer`. A shared `user / maintainer` blocker deliberately counts as the
519
+ * user's: showing one decision too many costs a line, hiding one costs the wait.
520
+ *
521
+ * Lives here rather than in `roadmap_gates` because it is owner *semantics*,
522
+ * not presentation — the dashboard header and `agent-config gates` must agree
523
+ * on the split, and two copies of this regex would drift.
524
+ */
525
+ function blocker_needs_user(owner: string): boolean {
526
+ return /\buser\b/i.test(owner);
527
+ }
528
+
473
529
  function parse_roadmap(p: string, roadmap_root: string): RoadmapStats | null {
474
530
  const text = fs.readFileSync(p, { encoding: 'utf-8' });
475
531
  const phase_matches: Array<{ start: number; end: number; g2: string; g3: string | undefined }> =
@@ -674,6 +730,14 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
674
730
  const pending = pending_iron_law_3(roadmaps);
675
731
  const gated = merge_gated_pending(roadmaps);
676
732
  const total_open_blockers = roadmaps.reduce((s, r) => s + r.open_blockers.length, 0);
733
+ // The count alone answers "is something blocked", never "is it on me" —
734
+ // and the per-roadmap anchors make the owner split a manual read across the
735
+ // whole file. Name the user's share here and point at the command that
736
+ // renders those entries as actions.
737
+ const user_open_blockers = roadmaps.reduce(
738
+ (s, r) => s + r.open_blockers.filter((b) => blocker_needs_user(b.owner)).length,
739
+ 0,
740
+ );
677
741
  const lines: string[] = [];
678
742
  lines.push('# Roadmap Progress\n');
679
743
  const header_meta =
@@ -682,7 +746,10 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
682
746
  ' · [roadmaps/](roadmaps/) · [archive/](roadmaps/archive/) · ' +
683
747
  '[skipped/](roadmaps/skipped/) · [later/](roadmaps/later/)' +
684
748
  (total_open_blockers > 0
685
- ? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}`
749
+ ? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}` +
750
+ (user_open_blockers > 0
751
+ ? `, **${user_open_blockers}** need you → \`agent-config gates\``
752
+ : '')
686
753
  : '') +
687
754
  '\n';
688
755
  lines.push(
@@ -1074,6 +1141,7 @@ export {
1074
1141
  is_roadmap_candidate,
1075
1142
  count_checkboxes,
1076
1143
  parse_blockers,
1144
+ blocker_needs_user,
1077
1145
  parse_roadmap,
1078
1146
  bar,
1079
1147
  collect,