@event4u/agent-config 9.28.0 → 9.29.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +66 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +13 -12
- package/dist/agent-src/commands/team/review.md +9 -8
- package/dist/agent-src/commands/team/status.md +9 -8
- package/dist/agent-src/commands/team.md +29 -19
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +18 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
- package/dist/agent-src/rules/delegation-policy.md +11 -7
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/token-budget-discipline.md +1 -1
- package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +4 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +23 -0
- package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
- package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
- package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
- package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-NZ5N2XQL.js} +3 -3
- package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-OJM3MBJM.js} +1 -1
- package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-RCIA67XN.js} +111 -90
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +6 -43
- package/dist/cli-delegate/cmd_doctor.js +9 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_settings_get.js +1 -7
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +69 -20
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -2
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +39 -2
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +12 -4
- package/dist/hooks/dispatch.js +373 -35
- package/dist/install/install.mjs +42 -17
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +651 -262
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +54 -0
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +147 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +6 -7
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +0 -9
- package/dist/shared/settingsCarveOut.js.map +1 -1
- package/dist/shared/settingsClasses.js +11 -9
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
- package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +39 -9
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +4 -4
- package/src/config/agent-settings.template.yml +52 -66
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/scripts/_cli/cmd_conformance.ts +14 -42
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_lib/agent_settings.ts +58 -0
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
- package/src/scripts/_lib/auto_dispatch.ts +23 -18
- package/src/scripts/_lib/capsule_trigger.ts +124 -0
- package/src/scripts/_lib/host_capability.ts +63 -6
- package/src/scripts/_lib/judgment_ladder.ts +382 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +37 -0
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/self_repair.ts +160 -13
- package/src/scripts/_lib/self_repair_store.ts +28 -0
- package/src/scripts/_lib/subagent_capsule.ts +182 -0
- package/src/scripts/_lib/subagent_response.ts +19 -0
- package/src/scripts/_lib/subagent_spawn.ts +13 -0
- package/src/scripts/_lib/subagent_steering.ts +9 -4
- package/src/scripts/_lib/tier_budget_routing.ts +6 -1
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +76 -54
- package/src/scripts/ai_council/config.ts +94 -6
- package/src/scripts/ai_council/handoff.ts +113 -0
- package/src/scripts/ai_council/modes.ts +19 -4
- package/src/scripts/ai_council/orchestrator.ts +105 -0
- package/src/scripts/ai_council/quorum.ts +84 -0
- package/src/scripts/ai_council/session.ts +44 -0
- package/src/scripts/ai_council/transport_resolver.ts +127 -1
- package/src/scripts/ai_team/availability.ts +110 -0
- package/src/scripts/ai_team/config.ts +34 -14
- package/src/scripts/ai_team/team_dispatch.ts +71 -29
- package/src/scripts/bench_critic_protocol.ts +396 -0
- package/src/scripts/check_detector_corpus.ts +202 -0
- package/src/scripts/check_hook_bundle_freshness.ts +4 -1
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/council_cli.ts +401 -25
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +69 -35
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/orchestration_record.ts +8 -0
- package/src/scripts/rebuild_hook_bundle.ts +177 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/routing_doctor.ts +21 -36
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +287 -57
- package/src/scripts/team_events_spike.ts +271 -0
- package/src/scripts/team_review_gate_hook.ts +31 -9
- package/src/server/schemas/settings.ts +10 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- package/src/shared/settingsClasses.ts +11 -9
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judgment ladder (Phase 2, road-to-always-on-orchestration) — the ONE
|
|
3
|
+
* resolver that decides which of the five dispatch rungs (0-4), or the
|
|
4
|
+
* silent ∅, a task resolves to. Committed table + rationale:
|
|
5
|
+
* `src/agent-src/contexts/execution/auto-dispatch-classification.md`
|
|
6
|
+
* § Judgment ladder.
|
|
7
|
+
*
|
|
8
|
+
* This WRAPS `classifyTask` rather than replacing it — rungs 1 and 2 ARE
|
|
9
|
+
* `classifyTask`'s existing dispatch verdict (single-slice vs multi-slice);
|
|
10
|
+
* rungs 0, 3, and 4 are new signal layers checked around it, in a FIXED
|
|
11
|
+
* priority order (documented on {@link classifyLadder} itself, because the
|
|
12
|
+
* order IS the contract: two signals matching the same text must resolve
|
|
13
|
+
* deterministically, never on evaluation-order accident).
|
|
14
|
+
*
|
|
15
|
+
* Three previously-scattered classification surfaces (the delegable-signal
|
|
16
|
+
* rules in `auto_dispatch.ts`, ad-hoc "should this be a team" judgment
|
|
17
|
+
* calls, and the absence of any council-routing signal on the TASK side)
|
|
18
|
+
* get ONE committed name and ONE resolver here — never a fourth parallel
|
|
19
|
+
* classifier bolted on beside it.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately independent of `ai_council/necessity.ts`: that module is the
|
|
22
|
+
* council's OWN necessity gate (its `NECESSARY_TRIGGERS` vocabulary and its
|
|
23
|
+
* `off|educate|block|warn-only` modes stay the council-side surface, per the
|
|
24
|
+
* roadmap's 2.2 note). This resolver's rung-4 signals are a NEW, narrower
|
|
25
|
+
* set — the three shapes the roadmap names explicitly (design decision,
|
|
26
|
+
* security downgrade, release-gate escalation) — kept as separate,
|
|
27
|
+
* regex-only constants rather than an import so this resolver carries no
|
|
28
|
+
* runtime dependency on the council module.
|
|
29
|
+
*/
|
|
30
|
+
import {
|
|
31
|
+
classifyLookup,
|
|
32
|
+
classifyTask,
|
|
33
|
+
SIZE_FLOOR,
|
|
34
|
+
type ActivationInputs,
|
|
35
|
+
type DispatchMode,
|
|
36
|
+
type TaskSignals,
|
|
37
|
+
} from './auto_dispatch.js';
|
|
38
|
+
|
|
39
|
+
/** The five dispatch rungs, or `null` for the silent ∅ (in-session / ask). */
|
|
40
|
+
export type LadderRung = 0 | 1 | 2 | 3 | 4 | null;
|
|
41
|
+
|
|
42
|
+
export type LadderVerdict =
|
|
43
|
+
| 'script' // rung 0 — deterministic transform/primitive, no spawn at all
|
|
44
|
+
| 'subagent' // rung 1/2 — one, or several downshifted, subagent dispatch(es)
|
|
45
|
+
| 'team' // rung 3 — communicating slices, dispatched via the host teams primitive
|
|
46
|
+
| 'council' // rung 4 — contested judgment
|
|
47
|
+
| 'ask' // ∅ — ambiguous; a VERDICT to the user, never a speculative spawn
|
|
48
|
+
| 'in-session'; // ∅ — halted, no host primitive, trivial, approval-required, or recursive guard
|
|
49
|
+
|
|
50
|
+
export interface LadderResult {
|
|
51
|
+
rung: LadderRung;
|
|
52
|
+
verdict: LadderVerdict;
|
|
53
|
+
reason: string;
|
|
54
|
+
/**
|
|
55
|
+
* Present only when a HIGHER rung's signal fired but its precondition
|
|
56
|
+
* failed (today: rung 3's `agentTeams` host-capability check) and
|
|
57
|
+
* resolution fell back to a lower rung/verdict. Names the rung the
|
|
58
|
+
* signal originally pointed at, never the resolved one.
|
|
59
|
+
*/
|
|
60
|
+
degraded_from?: LadderRung;
|
|
61
|
+
/**
|
|
62
|
+
* Carried through from `classifyTask` when the resolved rung is 1 or 2
|
|
63
|
+
* — the concrete `subagent-orchestration` mode a caller dispatches on.
|
|
64
|
+
* `null` for rung 1 (a single bounded slice has no do-in-steps /
|
|
65
|
+
* do-in-parallel shape of its own) and for every non-subagent verdict.
|
|
66
|
+
* Not part of the roadmap's minimal `{rung, verdict, reason,
|
|
67
|
+
* degraded_from?}` shape — added because the delegation-nudge hook
|
|
68
|
+
* (2.4) needs the concrete mode to build its injected line.
|
|
69
|
+
*/
|
|
70
|
+
mode?: DispatchMode | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface LadderInputs {
|
|
74
|
+
/**
|
|
75
|
+
* Raw task/prompt text — regex-only signal extraction for rungs 0, 1, 3,
|
|
76
|
+
* and 4, in the same discipline as `delegation_nudge_hook.ts`'s
|
|
77
|
+
* extractors (cheap, no LLM call; false-positive/negative trade-offs
|
|
78
|
+
* documented per pattern below).
|
|
79
|
+
*/
|
|
80
|
+
taskText: string;
|
|
81
|
+
/** Structural signals `classifyTask` already consumes — rungs 1/2. */
|
|
82
|
+
signals: TaskSignals;
|
|
83
|
+
/** Emergency halt + subagent_spawn primitive — gates every rung above 0. */
|
|
84
|
+
activation: ActivationInputs;
|
|
85
|
+
/**
|
|
86
|
+
* Host-capability manifest's `agent_teams` field
|
|
87
|
+
* (`host_capability.ts::probeHostCapabilities`) — rung 3's precondition.
|
|
88
|
+
* Absent/`false` degrades a matched rung-3 signal to rung 2.
|
|
89
|
+
*/
|
|
90
|
+
agentTeams: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Caller states this step needs an explicit human decision before
|
|
93
|
+
* anything may run (an approval gate, a Hard-Floor action, …) — NEVER
|
|
94
|
+
* inferred from `taskText`. Checked first; overrides every other signal.
|
|
95
|
+
*/
|
|
96
|
+
interactiveApprovalRequired?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Recursive-dispatch guard (2.3). See {@link classifyLadder}'s doc
|
|
99
|
+
* comment for the honest scope of what this suite can and cannot
|
|
100
|
+
* detect on its own — this field is a CALLER-SUPPLIED fact, never
|
|
101
|
+
* something this resolver probes for itself.
|
|
102
|
+
*/
|
|
103
|
+
insideSubagentSession?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Rung 0 — mechanical-transform signals (regex-only) ──────────────────
|
|
107
|
+
//
|
|
108
|
+
// `classifyLookup` (auto_dispatch.ts, road-to-lean-agent-init L0) already
|
|
109
|
+
// covers the READ-ONLY half of "deterministic script, no spawn" (definition/
|
|
110
|
+
// references/string-existence/report-run lookups). This adds the WRITE half
|
|
111
|
+
// the roadmap names explicitly: mechanical transforms with no semantics to
|
|
112
|
+
// judge — a rename, a codemod, a formatter run, a bulk search-and-replace.
|
|
113
|
+
// Each pattern requires the verb AND its object shape (e.g. "rename X to Y",
|
|
114
|
+
// not bare "rename") so a sentence that merely MENTIONS renaming in passing
|
|
115
|
+
// ("we renamed this last week") does not false-positive into rung 0.
|
|
116
|
+
const RENAME_RE = /\brename\s+\S+\s+to\s+\S+\b/i;
|
|
117
|
+
const CODEMOD_RE = /\bcodemod\b/i;
|
|
118
|
+
const FORMATTER_RUN_RE =
|
|
119
|
+
/\bauto-?format(?:ting)?\b|\breformat(?:ting)?\b|\brun(?:ning)?\s+prettier\b|\brun(?:ning)?\s+eslint\s*--fix\b|\bapply\s+eslint\s*--fix\b/i;
|
|
120
|
+
const SEARCH_REPLACE_RE = /\bsearch-and-replace\b|\bfind-and-replace\b|\bbulk\s+rename\b/i;
|
|
121
|
+
|
|
122
|
+
export function detectMechanicalTransform(text: string): { matched: boolean; reason: string } {
|
|
123
|
+
const rename = RENAME_RE.exec(text);
|
|
124
|
+
if (rename) {
|
|
125
|
+
return { matched: true, reason: `rename signal ("${rename[0]}") — deterministic script, no spawn` };
|
|
126
|
+
}
|
|
127
|
+
const codemod = CODEMOD_RE.exec(text);
|
|
128
|
+
if (codemod) {
|
|
129
|
+
return { matched: true, reason: `codemod signal ("${codemod[0]}") — deterministic script, no spawn` };
|
|
130
|
+
}
|
|
131
|
+
const formatter = FORMATTER_RUN_RE.exec(text);
|
|
132
|
+
if (formatter) {
|
|
133
|
+
return { matched: true, reason: `formatter-run signal ("${formatter[0]}") — deterministic script, no spawn` };
|
|
134
|
+
}
|
|
135
|
+
const searchReplace = SEARCH_REPLACE_RE.exec(text);
|
|
136
|
+
if (searchReplace) {
|
|
137
|
+
return {
|
|
138
|
+
matched: true,
|
|
139
|
+
reason: `search-and-replace/bulk-rename signal ("${searchReplace[0]}") — deterministic script, no spawn`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { matched: false, reason: 'no mechanical-transform signal' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Rung 1 — single bounded read-heavy slice (regex-only) ───────────────
|
|
146
|
+
//
|
|
147
|
+
// The one rung `classifyTask` structurally cannot see: its delegable rules
|
|
148
|
+
// all require >=2 slices, an ordered plan, or a declared `parallelizable`
|
|
149
|
+
// frontmatter value. A single "read this whole file and summarize it" task
|
|
150
|
+
// is real, non-trivial work that still benefits from a lite-tier subagent
|
|
151
|
+
// (session-context savings) but has no multi-slice shape at all. Requires a
|
|
152
|
+
// read-heavy VERB plus a bounded, singular TARGET phrase — a bare "read the
|
|
153
|
+
// code" without a stated target is too weak a signal to spawn on.
|
|
154
|
+
const SINGLE_SLICE_READHEAVY_RE =
|
|
155
|
+
/\b(?:read|review|summarize|summarise|inventory|audit|investigate|research|inspect|dig into|look into)\b[^.!?\n]{0,80}\b(?:this file|the file|this codebase|the codebase|this transcript|the transcript|this log|the log|this document|the document|this diff|the diff)\b/i;
|
|
156
|
+
|
|
157
|
+
export function detectBoundedReadHeavySlice(text: string): { matched: boolean; reason: string } {
|
|
158
|
+
const m = SINGLE_SLICE_READHEAVY_RE.exec(text);
|
|
159
|
+
return m
|
|
160
|
+
? {
|
|
161
|
+
matched: true,
|
|
162
|
+
reason: `single bounded read-heavy slice ("${m[0]}") — one lite-tier subagent instead of session-context burn`,
|
|
163
|
+
}
|
|
164
|
+
: { matched: false, reason: 'no single-slice read-heavy signal' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Rung 3 — communication-need signals (team) ───────────────────────────
|
|
168
|
+
//
|
|
169
|
+
// "Slices that must communicate" per the committed table: cross-layer work,
|
|
170
|
+
// an in-flight review-with-challenge shape, or an explicit shared work
|
|
171
|
+
// queue — signals a single implementer-then-judge handoff, or a set of
|
|
172
|
+
// independent parallel slices, cannot express, because the workers need to
|
|
173
|
+
// see each other's state DURING the run, not just at handoff.
|
|
174
|
+
const CROSS_LAYER_RE =
|
|
175
|
+
/\bcross[- ]layer\b|\bcross[- ]cutting\b|\bback[- ]?end\s+and\s+front[- ]?end\b|\bfront[- ]?end\s+and\s+back[- ]?end\b/i;
|
|
176
|
+
const REVIEW_WITH_CHALLENGE_RE =
|
|
177
|
+
/\breview[- ]with[- ]challenge\b|\bchallenge[- ]and[- ]implement\b|\bimplementer\s+and\s+(?:a\s+)?(?:judge|reviewer)\s+(?:working\s+)?together\b|\bsecond\s+opinion\s+while\s+(?:building|implementing)\b/i;
|
|
178
|
+
const SHARED_TASK_LIST_RE =
|
|
179
|
+
/\bshared\s+(?:task\s+list|todo\s+list|backlog)\b|\bcoordinat(?:e|ed|ing)\s+(?:with|between|across)\s+(?:multiple\s+|several\s+)?(?:agents|workers|subagents|teammates)\b/i;
|
|
180
|
+
|
|
181
|
+
export function detectCommunicationNeed(text: string): { matched: boolean; reason: string } {
|
|
182
|
+
const crossLayer = CROSS_LAYER_RE.exec(text);
|
|
183
|
+
if (crossLayer) {
|
|
184
|
+
return { matched: true, reason: `cross-layer communication signal ("${crossLayer[0]}")` };
|
|
185
|
+
}
|
|
186
|
+
const reviewChallenge = REVIEW_WITH_CHALLENGE_RE.exec(text);
|
|
187
|
+
if (reviewChallenge) {
|
|
188
|
+
return { matched: true, reason: `review-with-challenge signal ("${reviewChallenge[0]}")` };
|
|
189
|
+
}
|
|
190
|
+
const sharedList = SHARED_TASK_LIST_RE.exec(text);
|
|
191
|
+
if (sharedList) {
|
|
192
|
+
return { matched: true, reason: `shared-task-list signal ("${sharedList[0]}")` };
|
|
193
|
+
}
|
|
194
|
+
return { matched: false, reason: 'no communication-need signal' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Rung 4 — contested-judgment signals (council) ────────────────────────
|
|
198
|
+
//
|
|
199
|
+
// "Judgment under disagreement" per the committed table: a design decision,
|
|
200
|
+
// a proposed security downgrade, or an escalation at a release gate — the
|
|
201
|
+
// three shapes the roadmap names explicitly. Deliberately its OWN, narrower
|
|
202
|
+
// vocabulary rather than an import of `ai_council/necessity.ts`'s broader
|
|
203
|
+
// `NECESSARY_TRIGGERS` (architecture/tradeoff/ambiguity/strategic): that
|
|
204
|
+
// module stays the council's own necessity gate; this resolver only needs
|
|
205
|
+
// the narrow signal that should route a TASK to rung 4 in the first place.
|
|
206
|
+
// A bare `\badr\b` (no decision-context anchor) matched "update the ADR
|
|
207
|
+
// index" and "fix the release-gate CI script typo" — routine maintenance
|
|
208
|
+
// prose that names the artefact/gate but decides nothing — and suppressed
|
|
209
|
+
// the correct rung-2 nudge on both. Both alternatives below now require an
|
|
210
|
+
// explicit decision/escalation VERB within 40 chars of the artefact/gate
|
|
211
|
+
// token, on either side, so the bare noun alone never fires.
|
|
212
|
+
const DESIGN_DECISION_RE =
|
|
213
|
+
/\bdesign\s+decision\b|\barchitectur(?:e|al)\s+decision\b|\b(?:record(?:ing)?|decide|deciding|decision|accept(?:ed|ing)?|approv(?:e|ed|ing|al)|challeng(?:e|ed|ing)|choos(?:e|ing)|reject(?:ed|ing)?)\b[^.!?\n]{0,40}\bADR\b|\bADR\b[^.!?\n]{0,40}\b(?:record(?:ing)?|decide|deciding|decision|accept(?:ed|ing)?|approv(?:e|ed|ing|al)|challeng(?:e|ed|ing)|choos(?:e|ing)|reject(?:ed|ing)?)\b/i;
|
|
214
|
+
const SECURITY_DOWNGRADE_RE = /\bsecurity\s+downgrade\b|\bdowngrade\s+(?:the\s+)?security\b|\bsecurity\s+exception\b/i;
|
|
215
|
+
const RELEASE_GATE_ESCALATION_RE =
|
|
216
|
+
/\bgate\s+escalation\b|\bescalat(?:e|ion)\s+to\s+(?:the\s+)?council\b|\b(?:escalat(?:e|ing|ion)|contest(?:ed|ing)?|dispute|disputing|override|overriding|waiv(?:e|ing)|bypass(?:ed|ing)?|challeng(?:e|ed|ing))\b[^.!?\n]{0,40}\brelease[- ]gate\b|\brelease[- ]gate\b[^.!?\n]{0,40}\b(?:escalat(?:e|ing|ion)|contest(?:ed|ing)?|dispute|disputing|override|overriding|waiv(?:e|ing)|bypass(?:ed|ing)?|challeng(?:e|ed|ing))\b/i;
|
|
217
|
+
|
|
218
|
+
export function detectContestedJudgment(text: string): { matched: boolean; reason: string } {
|
|
219
|
+
const design = DESIGN_DECISION_RE.exec(text);
|
|
220
|
+
if (design) {
|
|
221
|
+
return { matched: true, reason: `design-decision signal ("${design[0]}")` };
|
|
222
|
+
}
|
|
223
|
+
const security = SECURITY_DOWNGRADE_RE.exec(text);
|
|
224
|
+
if (security) {
|
|
225
|
+
return { matched: true, reason: `security-downgrade signal ("${security[0]}")` };
|
|
226
|
+
}
|
|
227
|
+
const releaseGate = RELEASE_GATE_ESCALATION_RE.exec(text);
|
|
228
|
+
if (releaseGate) {
|
|
229
|
+
return { matched: true, reason: `release-gate-escalation signal ("${releaseGate[0]}")` };
|
|
230
|
+
}
|
|
231
|
+
return { matched: false, reason: 'no contested-judgment signal' };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the judgment-ladder verdict for one task.
|
|
236
|
+
*
|
|
237
|
+
* FIXED priority order (the order is the contract):
|
|
238
|
+
*
|
|
239
|
+
* 1. `interactiveApprovalRequired` — caller-stated, overrides everything.
|
|
240
|
+
* 2. Rung 0 (`classifyLookup` primitive match OR a mechanical-transform
|
|
241
|
+
* signal) — never spawns, so neither the recursive guard nor the
|
|
242
|
+
* activation gate applies to it.
|
|
243
|
+
* 3. Recursive-dispatch guard (2.3) — `insideSubagentSession` resolves ∅
|
|
244
|
+
* for every rung below this point.
|
|
245
|
+
* 4. Activation gate (`halted` / no `subagent_spawn`) — nothing below this
|
|
246
|
+
* point can spawn without it.
|
|
247
|
+
* 5. Rung 4 (contested-judgment signal) — council.
|
|
248
|
+
* 6. Rung 3 (communication-need signal) — team when `agentTeams`; otherwise
|
|
249
|
+
* degrades to whatever `classifyTask` resolves on the SAME signals
|
|
250
|
+
* (usually rung 2 — slices that must communicate are usually plural
|
|
251
|
+
* slices — but this honestly follows `classifyTask`'s real verdict
|
|
252
|
+
* rather than fabricating a slice count nothing confirmed).
|
|
253
|
+
* 7. Rungs 1/2 — `classifyTask`'s dispatch verdict. `do-in-steps` and
|
|
254
|
+
* `do-in-parallel` both resolve rung 2 in this v1: the roadmap's rung
|
|
255
|
+
* 1-vs-2 split is single-slice vs multi-slice, and `classifyTask`
|
|
256
|
+
* structurally never dispatches on a single slice (its rules require
|
|
257
|
+
* >=2 slices, an ordered plan, or a declared `parallelizable` value) —
|
|
258
|
+
* so every `classifyTask` dispatch verdict is inherently multi-slice.
|
|
259
|
+
* Its `ask` / size-floor `in-session` outcomes get one more check
|
|
260
|
+
* first: rung 1's single-bounded-slice signal, the one shape
|
|
261
|
+
* `classifyTask`'s own rules cannot see.
|
|
262
|
+
*
|
|
263
|
+
* ## Recursive-dispatch guard — honest scope (2.3)
|
|
264
|
+
*
|
|
265
|
+
* No verified host discriminator exists for "is this classification running
|
|
266
|
+
* inside a subagent/teammate session". This roadmap's own
|
|
267
|
+
* `point-of-action-carrier` blocker records that the upstream PreToolUse
|
|
268
|
+
* agent-identity request is closed NOT_PLANNED, and nothing in this repo's
|
|
269
|
+
* hook envelope (`event` / `payload` / `platform` / `workspace_root` —
|
|
270
|
+
* `delegation_nudge_hook.ts`'s own `main()`) carries a session-lineage
|
|
271
|
+
* field. `insideSubagentSession` is therefore a CALLER-SUPPLIED fact: the
|
|
272
|
+
* code path that dispatched the subagent/teammate loop is the one place
|
|
273
|
+
* that genuinely knows. This resolver does NOT probe `process.env` for
|
|
274
|
+
* it — inventing an unverified `CLAUDE_AGENT_*`-shaped variable name here
|
|
275
|
+
* would be exactly the hallucinated-field failure `source-discovery-gate`
|
|
276
|
+
* exists to stop. Documented as a gap, not silently assumed away: on a
|
|
277
|
+
* host/caller that never sets this field, the guard is a no-op and rungs
|
|
278
|
+
* 1-4 resolve normally even inside a subagent — the same gap the
|
|
279
|
+
* `point-of-action-carrier` blocker already names as its pre-registered
|
|
280
|
+
* null ("no discriminator" is publishable and does not block this roadmap).
|
|
281
|
+
*/
|
|
282
|
+
export function classifyLadder(inp: LadderInputs): LadderResult {
|
|
283
|
+
if (inp.interactiveApprovalRequired === true) {
|
|
284
|
+
return {
|
|
285
|
+
rung: null,
|
|
286
|
+
verdict: 'in-session',
|
|
287
|
+
reason: 'interactive-approval-required — caller-stated, needs a human decision before anything may run',
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Rung 0 — never spawns, so checked before the recursive guard and the
|
|
292
|
+
// activation gate: neither one is a precondition for a script that
|
|
293
|
+
// never dispatches anything.
|
|
294
|
+
const lookup = classifyLookup(inp.taskText);
|
|
295
|
+
if (lookup.route === 'primitive') {
|
|
296
|
+
return { rung: 0, verdict: 'script', reason: `lookup-class ${lookup.lookup_class} — ${lookup.reason}` };
|
|
297
|
+
}
|
|
298
|
+
const mechanical = detectMechanicalTransform(inp.taskText);
|
|
299
|
+
if (mechanical.matched) {
|
|
300
|
+
return { rung: 0, verdict: 'script', reason: mechanical.reason };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (inp.insideSubagentSession === true) {
|
|
304
|
+
return {
|
|
305
|
+
rung: null,
|
|
306
|
+
verdict: 'in-session',
|
|
307
|
+
reason:
|
|
308
|
+
'recursive-dispatch guard: this classification runs inside a subagent/teammate session — ' +
|
|
309
|
+
'rungs 1-4 resolve in-session',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (inp.activation.halted) {
|
|
314
|
+
return { rung: null, verdict: 'in-session', reason: 'emergency.orchestration_halt is set' };
|
|
315
|
+
}
|
|
316
|
+
if (!inp.activation.subagent_spawn) {
|
|
317
|
+
return { rung: null, verdict: 'in-session', reason: 'host has no subagent_spawn primitive' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const contested = detectContestedJudgment(inp.taskText);
|
|
321
|
+
if (contested.matched) {
|
|
322
|
+
return { rung: 4, verdict: 'council', reason: contested.reason };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const communication = detectCommunicationNeed(inp.taskText);
|
|
326
|
+
if (communication.matched) {
|
|
327
|
+
if (inp.agentTeams) {
|
|
328
|
+
return { rung: 3, verdict: 'team', reason: communication.reason };
|
|
329
|
+
}
|
|
330
|
+
const fallback = classifyTask(inp.signals, inp.activation);
|
|
331
|
+
if (fallback.action === 'dispatch' && fallback.mode !== null) {
|
|
332
|
+
return {
|
|
333
|
+
rung: 2,
|
|
334
|
+
verdict: 'subagent',
|
|
335
|
+
mode: fallback.mode,
|
|
336
|
+
degraded_from: 3,
|
|
337
|
+
reason: `${communication.reason} — no agent_teams host capability, degraded to rung 2 (${fallback.reason})`,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
rung: null,
|
|
342
|
+
verdict: fallback.action === 'ask' ? 'ask' : 'in-session',
|
|
343
|
+
degraded_from: 3,
|
|
344
|
+
reason: `${communication.reason} — no agent_teams host capability, degraded below rung 2 (${fallback.reason})`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const classification = classifyTask(inp.signals, inp.activation);
|
|
349
|
+
if (classification.action === 'dispatch' && classification.mode !== null) {
|
|
350
|
+
return { rung: 2, verdict: 'subagent', mode: classification.mode, reason: classification.reason };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// A task below the size floor never delegates — not even via rung 1's
|
|
354
|
+
// narrower single-slice regex. `classifyTask` already enforces this
|
|
355
|
+
// floor for rung 2 (its own `inSession('task below size floor …')`
|
|
356
|
+
// verdict); checked directly on `size_estimate` here — rather than by
|
|
357
|
+
// string-matching `classification.reason` — so this guard survives a
|
|
358
|
+
// future reword of that message, and so it fires regardless of WHICH of
|
|
359
|
+
// classifyTask's `in-session` branches produced the non-dispatch verdict.
|
|
360
|
+
// Before this check, a floor-worthy prompt like "review this diff" (no
|
|
361
|
+
// enumerated slices → size_estimate 0) still matched rung 1's read-heavy
|
|
362
|
+
// regex and dispatched a subagent anyway, silently bypassing the floor
|
|
363
|
+
// `classifyTask` had just enforced one line above.
|
|
364
|
+
if (inp.signals.size_estimate <= SIZE_FLOOR) {
|
|
365
|
+
return {
|
|
366
|
+
rung: null,
|
|
367
|
+
verdict: classification.action === 'ask' ? 'ask' : 'in-session',
|
|
368
|
+
reason: classification.reason,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const single = detectBoundedReadHeavySlice(inp.taskText);
|
|
373
|
+
if (single.matched) {
|
|
374
|
+
return { rung: 1, verdict: 'subagent', mode: null, reason: single.reason };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
rung: null,
|
|
379
|
+
verdict: classification.action === 'ask' ? 'ask' : 'in-session',
|
|
380
|
+
reason: classification.reason,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* This helper decides; it never runs the measurement. The signal is realized
|
|
16
16
|
* orchestration telemetry, aggregated + evaluated out of band.
|
|
17
|
+
*
|
|
18
|
+
* **Amendment (road-to-always-on-orchestration Phase 1).** `subagents.auto`
|
|
19
|
+
* was DELETED as a settings key — always-on orchestration carries no
|
|
20
|
+
* per-layer on/off setting to demote. `resolveShippedDefault` (the
|
|
21
|
+
* `subagents.auto`-specific half below) has no production caller today and
|
|
22
|
+
* is kept as a historical record of the demotion mechanism, not a live
|
|
23
|
+
* decision path; `resolveRecursiveDefault` (the `verification.recursive`
|
|
24
|
+
* half) is unaffected by this deletion.
|
|
17
25
|
*/
|
|
18
26
|
|
|
19
27
|
export interface BenchmarkResult {
|
|
@@ -28,6 +28,8 @@ export type TierSource = 'static' | 'inferred' | 'inherit';
|
|
|
28
28
|
export type Band = 'low' | 'medium' | 'high';
|
|
29
29
|
export type LinePhase = 'refine' | 'memory' | 'analyze' | 'plan' | 'implement' | 'test' | 'verify' | 'report';
|
|
30
30
|
export type LineOutcome = 'success' | 'blocked' | 'skipped' | 'error';
|
|
31
|
+
/** Which capsule-emission trigger arm fired first (Phase 1 shadow comparison). */
|
|
32
|
+
export type TriggerArm = 'watermark' | 'saturation' | 'tie';
|
|
31
33
|
/** The orchestration form the form-gate selected (road-to-opt-subagent-harvest P2). */
|
|
32
34
|
export type DispatchModeId =
|
|
33
35
|
| 'do-and-judge'
|
|
@@ -96,6 +98,20 @@ export interface RecordInput {
|
|
|
96
98
|
/** Rules the worker actually applied/cited (count of the envelope's
|
|
97
99
|
* `rules_applied`-equivalent on the worker side). Counts only. */
|
|
98
100
|
rules_used?: number | null | undefined;
|
|
101
|
+
// ── capsule shadow-measurement fields (road-to-worker-generation-recycling
|
|
102
|
+
// Phase 1.2). SHADOW ONLY: the worker still runs to stop-loss, nothing
|
|
103
|
+
// reads a capsule. All counts / enums — a capsule's CONTENT never
|
|
104
|
+
// reaches telemetry, only its shape. ──
|
|
105
|
+
/** A CHECKPOINT capsule was emitted at the watermark. */
|
|
106
|
+
capsule_emitted?: boolean | undefined;
|
|
107
|
+
/** Total entries across the capsule's arrays — a size proxy, never content. */
|
|
108
|
+
capsule_entries?: number | null | undefined;
|
|
109
|
+
/** 1-based step at which the token-watermark arm fired (null = never). */
|
|
110
|
+
watermark_step?: number | null | undefined;
|
|
111
|
+
/** 1-based step at which the novelty-saturation arm fired (null = never). */
|
|
112
|
+
saturation_step?: number | null | undefined;
|
|
113
|
+
/** Which arm fired first on this dispatch — the paired-comparison datum. */
|
|
114
|
+
trigger_arm_earlier?: TriggerArm | null | undefined;
|
|
99
115
|
// Audit-log envelope (sensible defaults for a dispatch record)
|
|
100
116
|
phase?: LinePhase | undefined;
|
|
101
117
|
outcome?: LineOutcome | undefined;
|
|
@@ -123,6 +139,7 @@ const BANDS: readonly Band[] = ['low', 'medium', 'high'];
|
|
|
123
139
|
const PHASES: readonly LinePhase[] = ['refine', 'memory', 'analyze', 'plan', 'implement', 'test', 'verify', 'report'];
|
|
124
140
|
const LOOKUP_CLASSES: readonly LookupClass[] = ['definition', 'references', 'string-existence', 'report-run'];
|
|
125
141
|
const ROUTES_TAKEN: readonly RouteTaken[] = ['primitive', 'subagent'];
|
|
142
|
+
const TRIGGER_ARMS: readonly TriggerArm[] = ['watermark', 'saturation', 'tie'];
|
|
126
143
|
/** Hex-only payload hash — a hash can never smuggle content (privacy by construction). */
|
|
127
144
|
const PAYLOAD_HASH_RE = /^[a-f0-9]{8,64}$/i;
|
|
128
145
|
/** Id-shaped origin tag — enum-ish, never free-form prose. */
|
|
@@ -233,6 +250,20 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
|
|
|
233
250
|
errors.push('rules_used cannot exceed rules_carried (a worker cannot apply a rule it was not given)');
|
|
234
251
|
}
|
|
235
252
|
|
|
253
|
+
if (input.capsule_emitted !== undefined && typeof input.capsule_emitted !== 'boolean') {
|
|
254
|
+
errors.push('capsule_emitted must be a boolean or omitted');
|
|
255
|
+
}
|
|
256
|
+
for (const [key, v] of [
|
|
257
|
+
['capsule_entries', input.capsule_entries],
|
|
258
|
+
['watermark_step', input.watermark_step],
|
|
259
|
+
['saturation_step', input.saturation_step],
|
|
260
|
+
] as const) {
|
|
261
|
+
if (v != null && (!isInt(v) || v < 0)) errors.push(`${key} must be a non-negative integer count`);
|
|
262
|
+
}
|
|
263
|
+
if (input.trigger_arm_earlier != null && !TRIGGER_ARMS.includes(input.trigger_arm_earlier)) {
|
|
264
|
+
errors.push(`trigger_arm_earlier must be one of ${TRIGGER_ARMS.join(' | ')} (or omitted / null when neither arm fired)`);
|
|
265
|
+
}
|
|
266
|
+
|
|
236
267
|
if (!input.ts) errors.push('ts (ISO-8601 UTC) is required');
|
|
237
268
|
if (!input.id) errors.push('id (ULID or content hash) is required');
|
|
238
269
|
|
|
@@ -267,6 +298,12 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
|
|
|
267
298
|
origin: input.origin ?? null,
|
|
268
299
|
rules_carried: input.rules_carried ?? null,
|
|
269
300
|
rules_used: input.rules_used ?? null,
|
|
301
|
+
// capsule shadow-measurement — readers ignore unknowns per audit-log-v1
|
|
302
|
+
capsule_emitted: input.capsule_emitted ?? null,
|
|
303
|
+
capsule_entries: input.capsule_entries ?? null,
|
|
304
|
+
watermark_step: input.watermark_step ?? null,
|
|
305
|
+
saturation_step: input.saturation_step ?? null,
|
|
306
|
+
trigger_arm_earlier: input.trigger_arm_earlier ?? null,
|
|
270
307
|
};
|
|
271
308
|
|
|
272
309
|
const line: Record<string, unknown> = {
|
|
@@ -212,7 +212,7 @@ export function aggregateOrchestrationSavings(
|
|
|
212
212
|
|
|
213
213
|
// Honest caveats — always attached so a consumer never over-reads the number.
|
|
214
214
|
if (report.dispatches === 0) {
|
|
215
|
-
report.notes.push('No orchestration telemetry yet (0 dispatches). Data accrues as
|
|
215
|
+
report.notes.push('No orchestration telemetry yet (0 dispatches). Data accrues as delegable slices dispatch (always-on orchestration; recorded via orchestration_record).');
|
|
216
216
|
} else {
|
|
217
217
|
report.notes.push('Token counts are MEASURED but blind to the downshift rate win (same tokens on a cheaper tier ≈ same count). The MODELED cost-% below captures that axis.');
|
|
218
218
|
if (report.by_provenance.estimated.dispatches > 0) {
|