@event4u/agent-config 13.0.0 → 14.1.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 (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +195 -315
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/evals/roadmap.json +8 -0
  5. package/dist/agent-src/commands/fix/ci.md +39 -1
  6. package/dist/agent-src/commands/pr/create.md +65 -4
  7. package/dist/agent-src/commands/roadmap/next.md +28 -5
  8. package/dist/agent-src/commands/work.md +16 -0
  9. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
  10. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
  11. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
  12. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
  13. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
  14. package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
  15. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
  16. package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
  17. package/dist/agent-src/rules/design-review-after-ui-write.md +25 -8
  18. package/dist/agent-src/rules/ui-audit-gate.md +25 -8
  19. package/dist/agent-src/scripts/gate_execute.ts +395 -0
  20. package/dist/agent-src/scripts/resume_probe.ts +422 -0
  21. package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
  22. package/dist/agent-src/scripts/update_roadmap_progress.ts +53 -2
  23. package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
  24. package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
  25. package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
  26. package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
  27. package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
  28. package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
  29. package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
  30. package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
  31. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  32. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
  33. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  34. package/dist/agent-src/templates/roadmaps.md +43 -3
  35. package/dist/cli/registry.js +1 -0
  36. package/dist/cli/registry.js.map +1 -1
  37. package/dist/cli-delegate/{chunk-N7FEST6D.js → chunk-7PZ4FSZB.js} +351 -174
  38. package/dist/cli-delegate/{chunk-3B72LSZL.js → chunk-F3YUF7SA.js} +1 -1
  39. package/dist/cli-delegate/cmd_conformance.js +336 -112
  40. package/dist/cli-delegate/cmd_doctor.js +2 -2
  41. package/dist/cli-delegate/cmd_handoff.js +1 -1
  42. package/dist/discovery/deprecation-report.md +1 -1
  43. package/dist/discovery/discovery-manifest.json +40 -18
  44. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  45. package/dist/discovery/discovery-manifest.summary.md +3 -3
  46. package/dist/discovery/dormancy-report.md +1 -1
  47. package/dist/discovery/orphan-report.md +1 -1
  48. package/dist/discovery/packs.json +6 -5
  49. package/dist/discovery/trust-report.md +2 -2
  50. package/dist/discovery/workspaces.json +5 -4
  51. package/dist/hooks/dispatch.js +2128 -1326
  52. package/dist/mcp/registry-manifest.json +3 -3
  53. package/dist/mcp/server.json +2 -2
  54. package/dist/mcp/server.mjs +1764 -899
  55. package/dist/router.json +1 -1
  56. package/dist/scripts/condense.js +1 -1
  57. package/dist/scripts/condense.js.map +1 -1
  58. package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
  59. package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
  60. package/dist/ui/index.html +1 -1
  61. package/package.json +1 -1
  62. package/src/agent-src/scripts/gate_execute.ts +395 -0
  63. package/src/agent-src/scripts/resume_probe.ts +422 -0
  64. package/src/agent-src/scripts/roadmap_gates.ts +138 -4
  65. package/src/agent-src/scripts/update_roadmap_progress.ts +53 -2
  66. package/src/config/ci-local-parity.yml +13 -0
  67. package/src/config/evaluator-budgets.json +4 -4
  68. package/src/config/gate-coverage.yml +111 -5
  69. package/src/config/gate-violation-baselines.json +5 -5
  70. package/src/config/rule-activation-census.json +41 -0
  71. package/src/config/rule-stub-ceilings.json +52 -0
  72. package/src/scripts/_dispatch.bash +4 -0
  73. package/src/scripts/_lib/evaluator_contract.ts +159 -0
  74. package/src/scripts/_lib/gate_ledger.ts +5 -1
  75. package/src/scripts/_lib/gh_transient.ts +80 -0
  76. package/src/scripts/_lib/release_material.ts +16 -5
  77. package/src/scripts/_lib/review_independence.ts +111 -0
  78. package/src/scripts/_lib/session_register.ts +14 -0
  79. package/src/scripts/_lib/skill_catalogue.ts +20 -0
  80. package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
  81. package/src/scripts/_lib/turn_end_refusals.ts +482 -0
  82. package/src/scripts/ai_council/_py_json.ts +96 -0
  83. package/src/scripts/ai_council/cli_call_budget.ts +357 -0
  84. package/src/scripts/ai_council/clients.ts +80 -143
  85. package/src/scripts/ai_council/config.ts +25 -11
  86. package/src/scripts/ai_council/probe_store.ts +196 -0
  87. package/src/scripts/ai_council/qualification.ts +425 -0
  88. package/src/scripts/ai_council/qualification_wiring.ts +243 -0
  89. package/src/scripts/ai_council/quorum_wiring.ts +211 -0
  90. package/src/scripts/ai_team/team_dispatch.ts +40 -6
  91. package/src/scripts/build_archive_index.ts +62 -3
  92. package/src/scripts/capture_skill_catalogue.ts +264 -4
  93. package/src/scripts/check_evaluator_schema.ts +249 -0
  94. package/src/scripts/check_gate_coverage.ts +93 -0
  95. package/src/scripts/check_pr_ci_current.ts +492 -0
  96. package/src/scripts/check_review_schema.ts +195 -0
  97. package/src/scripts/check_rule_activation_census.ts +451 -0
  98. package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
  99. package/src/scripts/condense.ts +1 -1
  100. package/src/scripts/council_cli.ts +141 -191
  101. package/src/scripts/dispatch_r2_reviewer.ts +8 -1
  102. package/src/scripts/git_authorization_hook.ts +186 -1
  103. package/src/scripts/hook_manifest.yaml +72 -5
  104. package/src/scripts/hooks/block_unauthorized_git.ts +62 -6
  105. package/src/scripts/hooks/concern_registry.ts +4 -0
  106. package/src/scripts/hooks/dispatch_hook.ts +43 -1
  107. package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
  108. package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
  109. package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
  110. package/src/scripts/hooks_doctor.ts +112 -0
  111. package/src/scripts/interruption_report.ts +443 -0
  112. package/src/scripts/lint_evidence_artifacts.ts +359 -0
  113. package/src/scripts/lint_roadmap_blockers.ts +65 -0
  114. package/src/scripts/lint_roadmap_complexity.ts +7 -1
  115. package/src/scripts/release.ts +38 -53
  116. package/src/scripts/rule_activation_census.ts +292 -0
  117. package/src/scripts/schemas/evaluator-output.schema.json +60 -0
  118. package/src/scripts/schemas/review-findings.schema.json +31 -0
  119. package/src/scripts/self_review_gate.ts +6 -0
  120. package/src/scripts/session_register_hook.ts +30 -1
  121. package/src/scripts/sync_pr_branch.ts +271 -0
  122. package/src/scripts/test_council_qualification.ts +213 -0
  123. package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
@@ -180,11 +180,11 @@ class RoadmapStats {
180
180
  }
181
181
 
182
182
  get open_blockers(): Blocker[] {
183
- return this.blockers.filter((b) => b.status !== 'resolved');
183
+ return this.blockers.filter((b) => !blocker_is_resolved(b));
184
184
  }
185
185
 
186
186
  get resolved_blockers(): Blocker[] {
187
- return this.blockers.filter((b) => b.status === 'resolved');
187
+ return this.blockers.filter((b) => blocker_is_resolved(b));
188
188
  }
189
189
 
190
190
  get done(): number {
@@ -365,6 +365,49 @@ interface Blocker {
365
365
  * costs one enum entry and turns a silent swallow into a rendered field.
366
366
  */
367
367
  question: string;
368
+ /**
369
+ * Gate taxonomy: `0` auto-run · `1` budget-preauthorized · `2`
370
+ * consent-once · `3` human-only. Empty string when the entry declares
371
+ * none, and an absent class READS AS 3 at every consumer.
372
+ *
373
+ * The default is the safe end on purpose. An agent that inferred the class
374
+ * at runtime could promote a Hard-Floor gate into something it executes;
375
+ * authoring it into the file means a misclassification is a reviewed edit,
376
+ * and `lint_roadmap_blockers` refuses a runnable class that names no
377
+ * command.
378
+ */
379
+ blockerClass: string;
380
+ /** The command a class-0/1 entry is resolved by running. */
381
+ run: string;
382
+ /** Spend estimate for class 1 — recorded, never enforced by the lint. */
383
+ budget: string;
384
+ }
385
+
386
+ /**
387
+ * Is this blocker settled?
388
+ *
389
+ * A PREFIX test, not equality, and the difference was measured: authors write
390
+ * the resolution into the status line — `**Status:** RESOLVED 2026-08-17 —
391
+ * **option (b)**, narrowed to two rules` — and an equality check matches
392
+ * neither `open` nor `resolved`, so the entry falls through to open. Found
393
+ * 2026-08-17 in `road-to-mixed-trigger-activation-cost`: a decision taken two
394
+ * days earlier was still being rendered by `agent-config gates` as a live one
395
+ * the reader owed an answer to, and it inflated every blocker count in the
396
+ * dashboard by one.
397
+ *
398
+ * `lint_roadmap_blockers` already read the same field as a prefix
399
+ * (`/^-\s*\*\*Status:\*\*\s*resolved/i`), so the two surfaces disagreed about
400
+ * what "resolved" means — the lint exempted the entry from its decidability
401
+ * ratchet while the dashboard counted it open.
402
+ */
403
+ function blocker_is_resolved(b: Blocker): boolean {
404
+ return /^resolved\b/.test(_strip(b.status).toLowerCase());
405
+ }
406
+
407
+ /** Class of a blocker, with the absent-field default applied. */
408
+ function blocker_class(b: Blocker): '0' | '1' | '2' | '3' {
409
+ const c = _strip(b.blockerClass).split(/[\s—–-]/)[0] as string;
410
+ return c === '0' || c === '1' || c === '2' ? c : '3';
368
411
  }
369
412
 
370
413
  // Strip fenced code blocks before blocker detection — a roadmap that shows the
@@ -508,6 +551,9 @@ function parse_blockers(raw_text: string): Blocker[] {
508
551
  recommendation: _blockerField(body, 'Recommendation') ?? '',
509
552
  ifNothing: _blockerField(body, 'If you do nothing') ?? '',
510
553
  question: _blockerField(body, 'Question') ?? '',
554
+ blockerClass: _blockerField(body, 'Class') ?? '',
555
+ run: _blockerField(body, 'Run') ?? '',
556
+ budget: _blockerField(body, 'Budget') ?? '',
511
557
  });
512
558
  }
513
559
  }
@@ -542,6 +588,9 @@ function parse_blockers(raw_text: string): Blocker[] {
542
588
  recommendation: '',
543
589
  ifNothing: '',
544
590
  question: '',
591
+ blockerClass: '',
592
+ run: '',
593
+ budget: '',
545
594
  });
546
595
  }
547
596
  return blockers;
@@ -1187,6 +1236,8 @@ export {
1187
1236
  is_roadmap_candidate,
1188
1237
  count_checkboxes,
1189
1238
  parse_blockers,
1239
+ blocker_class,
1240
+ blocker_is_resolved,
1190
1241
  blocker_needs_user,
1191
1242
  parse_roadmap,
1192
1243
  bar,
@@ -17,6 +17,67 @@ packs:
17
17
 
18
18
  # ai-council
19
19
 
20
+ ## Council-first — try the council BEFORE the user, on these classes
21
+
22
+ ```
23
+ A DESIGN DECISION THE AGENT CANNOT SETTLE FROM THE TREE GOES TO THE COUNCIL
24
+ FIRST, NOT TO THE USER FIRST. THE USER IS THE ESCALATION, NOT THE DEFAULT.
25
+ NEVER PRESENT A COUNCIL VERDICT THAT WAS NOT REACHED. AN INCONCLUSIVE RUN
26
+ IS REPORTED AS INCONCLUSIVE, AND WHATEVER YOU DECIDE INSTEAD IS LABELLED
27
+ OWN ANALYSIS.
28
+ ```
29
+
30
+ Interrupting the user is the expensive path: it costs a round trip and their
31
+ attention, and on a question two models could have settled it buys nothing they
32
+ wanted to be asked. So the ordering is **tree → council → user**, and the
33
+ council leg is the one that gets skipped.
34
+
35
+ **Classes that go to the council first** — each is a judgement with a real
36
+ trade-off and no answer in the tree:
37
+
38
+ | Class | Example |
39
+ |---|---|
40
+ | Competing implementations of one feature | two sessions shipped the same phase; discard, overwrite, or merge |
41
+ | Which of N designs, all defensible | four declared types with a derived fifth, or five declared |
42
+ | Reopening a recorded decision | an ADR or honest-null blocks a change that now looks net-positive |
43
+ | Scope re-cut inside an authorized task | this phase splits into two, or absorbs the next |
44
+ | Is the evidence sufficient to conclude | n=1 measurement, a proxy metric, an unreplicated finding |
45
+
46
+ **Classes that still go straight to the user, and the council never substitutes
47
+ for them:** anything behind the Hard Floor
48
+ ([`non-destructive-by-default`](../../rules/non-destructive-by-default.md)) ·
49
+ spend · a scope EXPANSION beyond what was authorized · a decision whose inputs
50
+ only the user holds (a preference, a deadline, a business constraint) · the
51
+ `high_impact` and `user_required` classes, which
52
+ [`ask-when-uncertain`](../../rules/ask-when-uncertain.md) routes to the user by
53
+ Iron Law. A council cannot consent on the user's behalf, and asking it to is not
54
+ autonomy, it is laundering.
55
+
56
+ ### When the council cannot answer
57
+
58
+ Measured 2026-08-17: a run returned **0 of 2 seats**, both `cli_quota_exhausted`,
59
+ with the counters at 72/50 and 99/50. The CLI reported it correctly as
60
+ `INCONCLUSIVE — DEGRADED` and did not print a quorum it had not reached. Three
61
+ things follow, and the first is the one under pressure:
62
+
63
+ 1. **Never dress your own verdict as the council's.** A fan-out, a solo read, or
64
+ your own judgement is a legitimate substitute **only when named as such** —
65
+ the boundary [`council-availability`](../../rules/council-availability.md)
66
+ states.
67
+ 2. **Do not bounce to the user just because the council failed.** An
68
+ unreachable council does not upgrade a decidable question into a
69
+ user-required one. Decide it, label it own analysis, state the reason the
70
+ council was unavailable, and name what would change the answer.
71
+ 3. **Escalate only if it is genuinely undecidable without them** — the two
72
+ options are close AND the cost of being wrong is high AND nothing in the tree
73
+ separates them. Then say the council was attempted and why it failed, so the
74
+ user is not asked to arbitrate something they will assume was already tried.
75
+
76
+ `agent-config council:status` is free and answers availability; `council_cli
77
+ estimate <file>` is free and answers parse-and-cost. Run both before concluding
78
+ the council is not an option — and note the question goes in a **file**, since
79
+ prose passed as an argument overflows the argument limit.
80
+
20
81
  ## When to use
21
82
 
22
83
  * The host agent has drafted a roadmap, plan, or design and wants an
@@ -0,0 +1,116 @@
1
+ ---
2
+ model_tier: inherit
3
+ name: experiment-loop
4
+ description: "Use to drive a scalar metric down or up across bounded iterations — keep on strict improvement, revert otherwise, state on disk. Triggers 'minimize X', 'optimize until it stops improving'."
5
+ domain: process
6
+ workspaces:
7
+ - agent-config-maintainer
8
+ packs:
9
+ - meta
10
+ ---
11
+
12
+ # experiment-loop
13
+
14
+ > A bounded **change → commit → evaluate → keep-or-revert** cycle against a
15
+ > **scalar metric**, where the decision input is an
16
+ > [evaluator-output](../../../docs/contracts/evaluator-output.md) verdict and the
17
+ > loop's entire state lives in an append-only register on disk.
18
+
19
+ This file **routes**. The protocol, the register format, and the pivot ladder are
20
+ reference files loaded on demand — a loop doctrine read in full on every session
21
+ that merely mentions optimization is a payload nobody asked for.
22
+
23
+ | Need | Load |
24
+ |---|---|
25
+ | The iteration protocol and its two exit conditions | [`references/protocol.md`](references/protocol.md) |
26
+ | The register's row shape and write ordering | [`references/register-format.md`](references/register-format.md) |
27
+ | What to do when the metric stops moving | [`references/pivot-ladder.md`](references/pivot-ladder.md) |
28
+
29
+ ## When to use
30
+
31
+ * The goal is expressible as **"minimize X"** or **"maximize X"** against a
32
+ mechanical verifier — bundle size, violation count, query count, runtime.
33
+ * A verifier already emits, or can be wrapped to emit, the evaluator-output
34
+ contract. Wrapping is usually free: three of this repo's verifiers were wrapped
35
+ with zero changes to any of them.
36
+
37
+ Do **NOT** use when:
38
+
39
+ * The target is a **known end state**, not a number — "make the tests pass" has a
40
+ destination, so use [`verify-repair-loop`](../verify-repair-loop/SKILL.md),
41
+ which converges toward it. This skill has no destination; it has a direction.
42
+ * The verdict is **subjective craft** — no scalar, nothing to optimize.
43
+ * The metric is a **proxy nobody agreed on**. Optimizing an unowned proxy is how
44
+ a loop makes a system worse while its number improves.
45
+
46
+ ## The Iron Law
47
+
48
+ ```
49
+ THE METRIC IS NEVER SOVEREIGN. `pass` IS.
50
+ KEEP ONLY ON `pass && score > baseline`. A CHANGE THAT IMPROVES THE NUMBER
51
+ AND BREAKS BEHAVIOUR IS REVERTED — MEASURED, NOT ASSUMED.
52
+ COMMIT BEFORE YOU EVALUATE, SO EVERY REVERT IS A GIT OPERATION.
53
+ THE REGISTER IS WRITTEN BEFORE THE ACTION IT RECORDS, NEVER AFTER.
54
+ STATE LIVES ON DISK AND IS RE-READ EACH CYCLE — NEVER IN THE CONVERSATION.
55
+ ```
56
+
57
+ Each line is a measured failure, not a preference. Spike s01 ran a change that
58
+ improved its metric by 67 % and broke the behaviour it measured; only `pass`
59
+ reverted it. The same run crashed between its commit and its register append,
60
+ twice, leaving the branch one iteration ahead of its own record.
61
+
62
+ ## Procedure
63
+
64
+ 1. **Inspect the verifier before trusting it.** Run the evaluator once on the
65
+ unchanged tree and check its verdict: `pass` must be true and `metric_state`
66
+ must be `present`. A baseline taken from an evaluator that is already red, or
67
+ whose metric is unreadable, is not a baseline — every later comparison
68
+ inherits the defect silently.
69
+ 2. **Fix the metric and the verifier** before the first change. Name the
70
+ evaluator, its direction, and the baseline score.
71
+ 3. **Declare the bound** — a maximum iteration count, written into the register's
72
+ first row. An unbounded loop is not this skill.
73
+ 4. **Iterate** per [`references/protocol.md`](references/protocol.md): one focused
74
+ change, commit, evaluate, keep or revert, append the outcome.
75
+ 5. **Exit** on either condition (both machine-checkable, never prose): the bound
76
+ is reached, or the explicit exit signal fires.
77
+ 6. **Report** from the register, not from memory. If the register cannot
78
+ reconstruct the run without the transcript, the run is not reportable.
79
+
80
+ ## Validation
81
+
82
+ * The register reconstructs the run with the transcript discarded — every kept
83
+ change traceable to a row, every row to a commit.
84
+ * Both branches ran at least once. **A loop whose revert branch never executed
85
+ is a one-branch loop with a comment**; spike s01 found its revert broken on
86
+ first use after four green iterations.
87
+ * The final tree passes the evaluator with `pass: true`.
88
+
89
+ ## Output format
90
+
91
+ 1. **The register path**, and the iteration count actually run against the bound.
92
+ 2. **A metric line**: baseline → final, and the number of keeps versus reverts.
93
+ 3. **The exit condition that fired**, named — bound reached, or exit signal.
94
+ 4. **Every reverted iteration listed** with its reason. A revert is a result the
95
+ next run needs, not noise to summarise away.
96
+
97
+ ## Gotcha
98
+
99
+ * **A metric that improves while the system degrades.** The reason `pass` is
100
+ sovereign. Never widen a verifier to make an iteration keep.
101
+ * **A revert path that has never run.** Exercise it deliberately once.
102
+ * **A register appended after the mutation.** A crash in between leaves the
103
+ branch ahead of the record; write the row first, then update its outcome.
104
+ * **Loop state in the conversation.** It decays silently and the loop keeps
105
+ comparing against a baseline it no longer holds.
106
+ * **Iterating past the bound because the number is still moving.** The bound is
107
+ the contract; extending it is a new run with a new baseline.
108
+
109
+ ## Do NOT
110
+
111
+ * Do NOT keep a change on `score` alone.
112
+ * Do NOT edit the verifier to move the metric.
113
+ * Do NOT batch several changes into one iteration — a keep you cannot attribute
114
+ is a keep you cannot revert.
115
+ * Do NOT delete a register row. Rows are append-only, including the embarrassing
116
+ ones.
@@ -0,0 +1,27 @@
1
+ # Behavior-schema contract for `experiment-loop`.
2
+ #
3
+ # Locks the sections a refactor could quietly drop while the skill still
4
+ # reads plausibly. Each one is a measured failure from spike s01
5
+ # (agents/evidence/eval-findings/metric-loop-s01.md), not a style
6
+ # preference:
7
+ #
8
+ # The Iron Law — `pass` sovereign over `score`; a change that improved
9
+ # the metric 67% and broke behaviour was reverted only
10
+ # because of it.
11
+ # Validation — the both-branches-ran check; s01's revert path was
12
+ # broken and four green iterations never noticed.
13
+ # Output format — the register-derived report; a report from memory is
14
+ # the in-conversation state the register exists to replace.
15
+ # Do NOT — the four keeps that must not happen.
16
+ #
17
+ # Enforced by `skill_linter` (`lint_output_schema`, matches `^## <header>$`) —
18
+ # a missing section is a hard error.
19
+
20
+ version: 1
21
+
22
+ required_headers:
23
+ - "The Iron Law"
24
+ - "Procedure"
25
+ - "Validation"
26
+ - "Output format"
27
+ - "Do NOT"
@@ -0,0 +1,17 @@
1
+ {
2
+ "skill": "experiment-loop",
3
+ "last_eval": "2026-08-17",
4
+ "description": "5 should-trigger + 5 should-not-trigger. Should-trigger covers the bounded keep-or-revert loop against a SCALAR metric (direction, no destination). Should-not-trigger covers the near-miss neighbours the roadmap's gap-table names: iterate-to-a-known-end-state (verify-repair-loop), subjective craft with no scalar, a one-shot measurement, a one-shot review, and asking what a metric currently is.",
5
+ "queries": [
6
+ {"q": "minimize the bundle size, keep changes that help and revert the rest", "trigger": true},
7
+ {"q": "optimize this until it stops improving, bounded to ten tries", "trigger": true},
8
+ {"q": "drive the query count down across iterations and record what you kept", "trigger": true},
9
+ {"q": "run a keep-or-revert loop against the lint violation count", "trigger": true},
10
+ {"q": "reduce this metric as far as it goes without breaking the tests", "trigger": true},
11
+ {"q": "keep fixing until the tests pass", "trigger": false, "note": "a known end state, not a direction → verify-repair-loop"},
12
+ {"q": "iterate this change to green, run the suite each time", "trigger": false, "note": "converges toward a destination → verify-repair-loop"},
13
+ {"q": "is this diff clean — naming, structure, conventions?", "trigger": false, "note": "subjective craft, no scalar to optimize"},
14
+ {"q": "how big is the bundle right now?", "trigger": false, "note": "a single measurement, no loop"},
15
+ {"q": "review my changes before I open the PR", "trigger": false, "note": "one-shot review → /review-changes"}
16
+ ]
17
+ }
@@ -0,0 +1,54 @@
1
+ # experiment-loop — the pivot ladder
2
+
3
+ Loaded on demand by [`experiment-loop`](../SKILL.md), when the exit signal
4
+ `consecutive_reverts >= N` fires. The metric has stopped moving. The question is
5
+ whether that is a fact about the system or about the hypotheses being tried.
6
+
7
+ Climb one rung at a time. Each rung is a **new run** with a fresh register and a
8
+ re-declared baseline — never a quiet continuation of the old one, because a run
9
+ whose hypothesis class changed mid-flight cannot be read afterwards.
10
+
11
+ ## Rung 1 — a different hypothesis class, same metric
12
+
13
+ The reverts share a shape: every attempt tried to shave the same structure. Pick
14
+ a class the run has not touched — a different module, a different layer, a
15
+ different kind of change — and rerun with the current score as the baseline.
16
+
17
+ Stop climbing here if the first iteration of the new class keeps.
18
+
19
+ ## Rung 2 — check the metric measures what you meant
20
+
21
+ Before concluding the system is optimal, confirm the number is the number. The
22
+ measured failure mode: a diff-volume metric that looked like shipping volume was
23
+ counting the repository's own review bookkeeping — a committed copy of the diff
24
+ being measured — so the ranking it produced was of the wrong thing entirely.
25
+ Excluding the bookkeeping moved the p90 by more than half and reordered the top
26
+ five.
27
+
28
+ Ask: does the metric include artefacts the change did not author? Generated
29
+ files, snapshots, vendored trees, its own output? Correcting that is a
30
+ **measurement fix** and legitimate.
31
+
32
+ ## Rung 3 — decide whether tuning is the honest move
33
+
34
+ Rung 2's correction has a twin that is not legitimate: adjusting the metric until
35
+ the answer is pleasant. The discriminator is stated once, plainly:
36
+
37
+ > **Removing an input that was never part of the quantity is a correction.
38
+ > Moving a threshold or growing an exclusion set until the result improves is
39
+ > tuning.** A correction can be justified without reference to its effect on the
40
+ > outcome. Tuning cannot.
41
+
42
+ If the only available move needs the outcome to justify it, stop. The run's
43
+ answer is that the metric does not move under changes of this kind, and that is
44
+ a real result worth recording.
45
+
46
+ ## Rung 4 — publish the null
47
+
48
+ No rung produced a keep. Write the run up: the metric, the bound, the hypothesis
49
+ classes tried, the register path, and the conclusion that the metric did not
50
+ move. A published null is why the next person does not re-run this.
51
+
52
+ **Do not climb back down.** Re-running rung 1 with slightly different wording is
53
+ the shape that turns a bounded loop into an unbounded one, and it burns budget
54
+ producing rows that all say the same thing.
@@ -0,0 +1,61 @@
1
+ # experiment-loop — the iteration protocol
2
+
3
+ Loaded on demand by [`experiment-loop`](../SKILL.md). One iteration, in order.
4
+
5
+ ## The cycle
6
+
7
+ 1. **Read the register from disk.** Not from the conversation, not from a
8
+ variable held since the last cycle. The baseline for this iteration is the
9
+ `score` of the most recent `keep` row, or the declared baseline if there is
10
+ none.
11
+ 2. **Make ONE focused change.** One hypothesis per iteration. A batched
12
+ iteration produces a keep you cannot attribute and therefore cannot revert.
13
+ 3. **Append the register row** with `decision: "pending"` — before the commit,
14
+ before the evaluation. See [`register-format.md`](register-format.md) for why
15
+ the ordering is this way round.
16
+ 4. **Commit.** The revert is then a git operation rather than a manual undo, and
17
+ the branch carries one commit per iteration whether it survives or not.
18
+ 5. **Evaluate** — run the evaluator, read its
19
+ [evaluator-output](../../../../../docs/contracts/evaluator-output.md) verdict.
20
+ 6. **Decide**, and update the row:
21
+
22
+ | Verdict | Decision |
23
+ |---|---|
24
+ | `pass: true` and `score > baseline` | **keep** |
25
+ | `pass: true` and `score <= baseline` | **revert** — no strict improvement |
26
+ | `pass: false` | **revert** — the tree is red |
27
+ | non-zero exit · absent JSON · timeout | **revert** — the experiment failed, the measurement did not happen |
28
+ | `metric_state: "unreadable"` | **revert** — an unread metric is not a zero |
29
+
30
+ 7. **Revert if the decision says so**, then loop.
31
+
32
+ ## The two exit conditions
33
+
34
+ Both are **machine-checkable predicates**. Neither is prose, and that is
35
+ deliberate: a stop condition written loosely rots, and the rot is invisible
36
+ because the loop keeps running.
37
+
38
+ **Condition A — the bound.** `iterations_run >= max_iterations`, where
39
+ `max_iterations` was written into the register's first row before the first
40
+ change. Reaching it is a normal exit, not a failure.
41
+
42
+ **Condition B — the exit signal.** A predicate declared at the start, evaluated
43
+ against the register each cycle. Two usable shapes:
44
+
45
+ - `consecutive_reverts >= N` — the metric has stopped moving under this
46
+ hypothesis class. Route to [`pivot-ladder.md`](pivot-ladder.md).
47
+ - `score >= target` — the run had a destination after all, and reached it.
48
+
49
+ **Why two and not one.** A bound alone burns the whole budget on a metric that
50
+ stopped moving at iteration three. An exit signal alone is an unbounded loop
51
+ wearing a condition: if the signal never fires — and a subtly wrong predicate
52
+ never fires — nothing stops it. Each guards a different failure direction, so
53
+ neither is the redundant one.
54
+
55
+ ## What ends a run that is not an exit condition
56
+
57
+ Nothing. Specifically **not**: the number looking good enough, the change
58
+ starting to feel risky, or the register getting long. Those are judgements, and
59
+ a loop that stops on a judgement produces a result nobody can reproduce. If the
60
+ run should stop for a reason the predicates do not cover, that is a finding
61
+ about the predicates — record it and fix them for the next run.
@@ -0,0 +1,66 @@
1
+ # experiment-loop — the register
2
+
3
+ Loaded on demand by [`experiment-loop`](../SKILL.md). The register is the run:
4
+ if it cannot reconstruct what happened with the transcript discarded, the run
5
+ was not recorded.
6
+
7
+ ## Shape
8
+
9
+ An **append-only JSONL** file, one row per iteration, **untracked** (the run is
10
+ evidence, not source). Re-read from disk at the top of every cycle.
11
+
12
+ Row 0 declares the run:
13
+
14
+ ```json
15
+ {"row": 0, "evaluator": "bundle-size", "direction": "minimize",
16
+ "baseline_score": -142000, "max_iterations": 12,
17
+ "exit_signal": "consecutive_reverts >= 3"}
18
+ ```
19
+
20
+ Each iteration:
21
+
22
+ ```json
23
+ {"row": 1, "label": "drop the polyfill for evergreen targets", "head": "a1b2c3d",
24
+ "baseline_score": -142000, "score": -138400, "metric": 138400,
25
+ "metric_state": "present", "pass": true, "decision": "keep",
26
+ "reason": "strict improvement"}
27
+ ```
28
+
29
+ `decision` is one of `pending` · `keep` · `revert`. `reason` is one of
30
+ `strict improvement` · `no strict improvement` · `test red` ·
31
+ `evaluator failed` · `metric unreadable`.
32
+
33
+ ## Write the row BEFORE the action it records
34
+
35
+ This is the ordering that costs a run to learn. The obvious sequence — commit,
36
+ evaluate, revert, then append — leaves a window in which the branch has advanced
37
+ and the register has not. Spike s01 hit it twice: the revert threw, the process
38
+ died, and the row was never written, so the register described a run one
39
+ iteration shorter than the branch.
40
+
41
+ So: append the row with `decision: "pending"` first, then act, then rewrite that
42
+ row's decision. A crash then leaves a visible `pending` — a row that says "an
43
+ iteration started here and we do not know how it ended", which is recoverable —
44
+ instead of silence, which is not.
45
+
46
+ **"Append-only" and "rewrite the last row" are not in conflict.** Append-only
47
+ governs *history*: no row is ever deleted and no earlier row is ever edited. The
48
+ pending row is the write frontier, not history, until its outcome lands.
49
+
50
+ ## Why on disk and not in the conversation
51
+
52
+ Two independent references converged on files-as-truth after documenting silent
53
+ degradation of in-conversation loop state, and the mechanism is not exotic: a
54
+ baseline held in context drifts as the context is summarised, and the loop keeps
55
+ comparing against a number that is no longer the one it kept. Nothing errors.
56
+ The run just quietly stops meaning anything.
57
+
58
+ Re-reading from disk each cycle costs one file read and removes the entire
59
+ class.
60
+
61
+ ## Reporting from the register
62
+
63
+ The final report is derived, never remembered: iterations run against the bound,
64
+ baseline → final score, keeps versus reverts, the exit condition that fired, and
65
+ every reverted iteration with its reason. A revert is the most informative row in
66
+ the file — it is the hypothesis the metric rejected, and the next run needs it.
@@ -226,8 +226,8 @@ unfamiliar codebases.
226
226
 
227
227
  ### Executing a roadmap
228
228
 
229
- 0. Read `execution.mode` from the roadmap frontmatter
230
- (`autonomous` | `phase-checkpoints` | `interactive`; absent =
229
+ 0. Resolve the mode per `roadmap-process-loop` § 3a: suffix, frontmatter
230
+ `execution.mode`, invocation form (absent = derived, not
231
231
  `interactive`). Under `/roadmap:process-*` the mode selects the
232
232
  interaction pattern via the run-start execution contract — see
233
233
  [`roadmap-execution-contract`](../../contexts/execution/roadmap-execution-contract.md).
@@ -124,7 +124,7 @@ Every new roadmap declares how a later `/roadmap:process-*` run should
124
124
  interact, via `execution.mode:` in frontmatter — `autonomous` (one
125
125
  run-start execution-contract confirmation, then uninterrupted except
126
126
  safety floors), `phase-checkpoints` (halt + compact status per phase
127
- boundary), or `interactive` (absent-field default; omit the field).
127
+ boundary), or `interactive` (declare it — an omitted field is derived).
128
128
  Semantics: [`templates/roadmaps.md` rule 18](../../templates/roadmaps.md);
129
129
  run mechanics:
130
130
  [`roadmap-execution-contract`](../../contexts/execution/roadmap-execution-contract.md).
@@ -59,14 +59,19 @@
59
59
  },
60
60
  "quota": {
61
61
  "type": "object",
62
- "required": ["used", "ceiling"],
62
+ "required": ["provider", "used", "ceiling"],
63
63
  "additionalProperties": false,
64
- "description": "Shared cli_call_budget openai-bucket visibility after this run.",
64
+ "description": "Shared cli_call_budget bucket visibility after this run. The bucket is named rather than assumed — `provider` says which one `used` counts.",
65
65
  "properties": {
66
+ "provider": {
67
+ "type": "string",
68
+ "minLength": 1,
69
+ "description": "The shared-bucket key this run books into, read from the dispatching client rather than hardcoded. Was implicitly always `openai`; naming it keeps the report from drifting away from the bucket if the team transport moves."
70
+ },
66
71
  "used": {
67
72
  "type": "integer",
68
73
  "minimum": 0,
69
- "description": "Today's openai-bucket call count (UTC-day, shared with the council)."
74
+ "description": "Today's call count in the `provider` bucket (UTC-day, shared with the council)."
70
75
  },
71
76
  "ceiling": {
72
77
  "type": "integer",
@@ -39,7 +39,7 @@ schema_version: 1
39
39
  # CI guard: a release bump of `package.json` must update this value
40
40
  # in lockstep — see scripts/check_template_pin_drift.ts (road-to-
41
41
  # portable-runtime-and-update-check P3.3).
42
- agent_config_version: "13.0.0"
42
+ agent_config_version: "14.1.0"
43
43
 
44
44
  # --- Project identity ---
45
45
  project:
@@ -86,8 +86,13 @@ Templates for roadmap files stored in `agents/roadmaps/` or `{module_root}/{Modu
86
86
  execution:
87
87
  mode: autonomous # autonomous | phase-checkpoints | interactive
88
88
  ```
89
- - `interactive` (default when the field is absent) — today's behavior:
90
- every gate fires as authored by its owning rule.
89
+ - `interactive` — every gate fires as authored by its owning rule.
90
+ **Not what an absent field means.** When the field is absent the mode is
91
+ DERIVED from the invocation form (`roadmap-process-loop § 3a`):
92
+ `process-full` and `/roadmap:next` derive `autonomous`, `process-phase`
93
+ derives `phase-checkpoints`, `process-step` runs without a contract. The
94
+ contract screen is still shown once and still carries "run interactive
95
+ instead", so declaring nothing costs a keystroke, never control.
91
96
  - `phase-checkpoints` — the run halts at each phase boundary with a
92
97
  compact status + continue prompt; inside a phase it behaves like
93
98
  `autonomous`.
@@ -138,6 +143,13 @@ Templates for roadmap files stored in `agents/roadmaps/` or `{module_root}/{Modu
138
143
  - **Owner:** user <!-- user | maintainer | external -->
139
144
  - **Blocks:** Phase N — {phase name}
140
145
  - **Question:** {optional — the one line saying what is being decided.}
146
+ <!-- Class is optional and absent means 3. Add Run: ONLY with Class 0 or 1,
147
+ where it is required — a class-3 entry advertising a command reads as
148
+ actionable and is not. Budget: is class 1 only.
149
+ - **Class:** 0
150
+ - **Run:** `{command}`
151
+ - **Budget:** {estimate} (class 1 only)
152
+ -->
141
153
  - **Recommendation:** {which option, and the one sentence why.}
142
154
  - **If you do nothing:** {what the non-decision costs, concretely.}
143
155
  - **What to do:**
@@ -181,11 +193,39 @@ Templates for roadmap files stored in `agents/roadmaps/` or `{module_root}/{Modu
181
193
  offers to walk them through it step by step in the same reply. The
182
194
  owner deciding does not mean the owner executing.
183
195
 
196
+ **`Class:` — is a human the point of this gate, or its courier?**
197
+ Most gates are not decisions; they are commands waiting for someone
198
+ to type them. The four classes name the difference, and only the
199
+ last one genuinely needs a person:
200
+
201
+ | Class | Meaning | What clears it |
202
+ |---|---|---|
203
+ | `0` | auto-run — deterministic, free, reversible | the agent runs `Run:`; the output IS the unblock |
204
+ | `1` | budget-preauthorized — billable but reversible | the agent runs `Run:` under a standing budget and logs a receipt |
205
+ | `2` | consent-once — a real preference call, reversible | one line with the recommendation and a default |
206
+ | `3` | human-only — irreversible, legal, or Hard Floor | unchanged: the human IS the content |
207
+
208
+ Three properties are load-bearing. **`Class:` is optional and its
209
+ absence means 3**, so nothing becomes executable because an author
210
+ forgot a field. **A class-0 or class-1 entry MUST carry `Run:`** —
211
+ `lint_roadmap_blockers` fails otherwise, because a gate that claims
212
+ to be runnable without naming the command reads as actionable and is
213
+ not. And **class is authored, never inferred**: promoting a gate is
214
+ a reviewed edit, which is what keeps a Hard-Floor gate
215
+ (`non-destructive-by-default`) out of reach of a runtime judgment.
216
+
217
+ Only the leading token of `Class:` is read, so
218
+ `- **Class:** 1 — budget-preauthorized` is valid; the taxonomy name
219
+ is there for the reader.
220
+
184
221
  `lint_roadmap_blockers` enforces the field set and probes `What to
185
222
  do` for executable substance. The 14-entry backlog is recorded as a
186
223
  ratchet baseline (`gate-violation-baselines.json`), so existing
187
224
  entries stay legal while any **new** one that skips a field or ships
188
- a command-free `What to do` fails.
225
+ a command-free `What to do` fails. The `Class:` / `Run:` pair is a
226
+ HARD check rather than a ratcheted one — it is opt-in, so it fires
227
+ on nothing until an author declares a class and there is no backlog
228
+ to grandfather.
189
229
 
190
230
  **Legacy fallback.** A body-level `> Blocked until <condition>`
191
231
  note (the follow-up-roadmap convention from rule 17) is parsed by
@@ -105,6 +105,7 @@ export const REGISTRY = [
105
105
  { name: 'council:run', disposition: 'delegate', synopsis: 'Run the AI council (requires --confirm to spend).' },
106
106
  { name: 'council:render', disposition: 'delegate', synopsis: 'Re-render a saved council responses JSON to markdown.' },
107
107
  { name: 'council:status', disposition: 'delegate', synopsis: 'Is a council configured, and from which file — zero spend, no inference.' },
108
+ { name: 'council:quota', disposition: 'delegate', synopsis: "Today's per-provider CLI-call usage against the enforced cap — zero spend." },
108
109
  { name: 'eval:record', disposition: 'native', synopsis: 'Record a live trigger-eval result into a corpus manifest (corpus-refresh DoD, ADR-061 §6).' },
109
110
  { name: 'self-repair:status', disposition: 'delegate', synopsis: 'List queued agent-config defect records (read-only).' },
110
111
  { name: 'self-repair:release', disposition: 'delegate', synopsis: 'Publish one defect record as a PR, or an issue when a PR is impossible (Hard-Floor gated).' },