@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
|
@@ -1052,9 +1052,12 @@ export class SubprocessError extends Error {
|
|
|
1052
1052
|
* `~/.event4u/agent-config/cli-calls.json` and resets on UTC date rollover.
|
|
1053
1053
|
*
|
|
1054
1054
|
* Subclass contract:
|
|
1055
|
-
* - `name
|
|
1056
|
-
*
|
|
1057
|
-
* `binary:` field is not set.
|
|
1055
|
+
* - `super(opts, { name, default_binary })`: `name` is the provider key
|
|
1056
|
+
* (`anthropic`, `openai`, `gemini`, …); `default_binary` is the executable
|
|
1057
|
+
* resolved via PATH when the member-level `binary:` field is not set. Both
|
|
1058
|
+
* are passed explicitly to `super()` — not read off subclass instance
|
|
1059
|
+
* fields — because a subclass's own field initializers run only AFTER this
|
|
1060
|
+
* `super()` call returns.
|
|
1058
1061
|
* - `_build_command(system_prompt, user_prompt, max_tokens)`: return the argv.
|
|
1059
1062
|
* - `_parse_output(stdout, stderr)`: return a partial `CouncilResponse`.
|
|
1060
1063
|
*
|
|
@@ -1097,8 +1100,22 @@ export abstract class CliClient extends ExternalAIClient {
|
|
|
1097
1100
|
'usage limit',
|
|
1098
1101
|
];
|
|
1099
1102
|
|
|
1100
|
-
|
|
1103
|
+
/**
|
|
1104
|
+
* `identity` carries the subclass constants (`name`, `default_binary`)
|
|
1105
|
+
* that construction validates against. They are passed explicitly rather
|
|
1106
|
+
* than read off `this.name` / `this.default_binary` because subclass
|
|
1107
|
+
* instance-field initializers (`override default_binary = 'claude'`,
|
|
1108
|
+
* declared in the subclass body) run only AFTER this `super()` call
|
|
1109
|
+
* returns — reading them here, before that happens, would always see the
|
|
1110
|
+
* base class's own field defaults (`''`), not the subclass override.
|
|
1111
|
+
*/
|
|
1112
|
+
constructor(
|
|
1113
|
+
opts: CliClientOptions & { model: string },
|
|
1114
|
+
identity: { name: string; default_binary: string },
|
|
1115
|
+
) {
|
|
1101
1116
|
super();
|
|
1117
|
+
this.name = identity.name;
|
|
1118
|
+
this.default_binary = identity.default_binary;
|
|
1102
1119
|
this.model = opts.model;
|
|
1103
1120
|
this.timeout_seconds = opts.timeout_seconds ?? DEFAULT_CLI_TIMEOUT_SECONDS;
|
|
1104
1121
|
this.max_calls_per_day = opts.max_calls_per_day ?? null;
|
|
@@ -1353,19 +1370,20 @@ export abstract class CliClient extends ExternalAIClient {
|
|
|
1353
1370
|
* collides with argv length limits.
|
|
1354
1371
|
*/
|
|
1355
1372
|
export class AnthropicCliClient extends CliClient {
|
|
1356
|
-
override name = 'anthropic';
|
|
1357
|
-
override default_binary = 'claude';
|
|
1358
1373
|
override subscription_label = 'claude-pro';
|
|
1359
1374
|
|
|
1360
1375
|
constructor(opts: CliClientOptions = {}) {
|
|
1361
|
-
super(
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1376
|
+
super(
|
|
1377
|
+
{
|
|
1378
|
+
model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL,
|
|
1379
|
+
binary: opts.binary,
|
|
1380
|
+
timeout_seconds: opts.timeout_seconds,
|
|
1381
|
+
max_calls_per_day: opts.max_calls_per_day,
|
|
1382
|
+
warn_at: opts.warn_at,
|
|
1383
|
+
cli_calls_path: opts.cli_calls_path,
|
|
1384
|
+
},
|
|
1385
|
+
{ name: 'anthropic', default_binary: 'claude' },
|
|
1386
|
+
);
|
|
1369
1387
|
}
|
|
1370
1388
|
|
|
1371
1389
|
protected override _build_command(
|
|
@@ -1446,8 +1464,6 @@ export class AnthropicCliClient extends CliClient {
|
|
|
1446
1464
|
* token usage in `usage.input_tokens` / `usage.output_tokens`.
|
|
1447
1465
|
*/
|
|
1448
1466
|
export class OpenAICliClient extends CliClient {
|
|
1449
|
-
override name = 'openai';
|
|
1450
|
-
override default_binary = 'codex';
|
|
1451
1467
|
override subscription_label = 'chatgpt-plus';
|
|
1452
1468
|
|
|
1453
1469
|
static override _AUTH_FAILURE_PATTERNS: readonly string[] = [
|
|
@@ -1458,14 +1474,17 @@ export class OpenAICliClient extends CliClient {
|
|
|
1458
1474
|
];
|
|
1459
1475
|
|
|
1460
1476
|
constructor(opts: CliClientOptions = {}) {
|
|
1461
|
-
super(
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1477
|
+
super(
|
|
1478
|
+
{
|
|
1479
|
+
model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL,
|
|
1480
|
+
binary: opts.binary,
|
|
1481
|
+
timeout_seconds: opts.timeout_seconds,
|
|
1482
|
+
max_calls_per_day: opts.max_calls_per_day,
|
|
1483
|
+
warn_at: opts.warn_at,
|
|
1484
|
+
cli_calls_path: opts.cli_calls_path,
|
|
1485
|
+
},
|
|
1486
|
+
{ name: 'openai', default_binary: 'codex' },
|
|
1487
|
+
);
|
|
1469
1488
|
}
|
|
1470
1489
|
|
|
1471
1490
|
protected override _build_command(
|
|
@@ -1569,8 +1588,6 @@ export class OpenAICliClient extends CliClient {
|
|
|
1569
1588
|
* stdin to dodge argv limits.
|
|
1570
1589
|
*/
|
|
1571
1590
|
export class GeminiCliClient extends CliClient {
|
|
1572
|
-
override name = 'gemini';
|
|
1573
|
-
override default_binary = 'gemini';
|
|
1574
1591
|
override subscription_label = 'gemini-pro';
|
|
1575
1592
|
|
|
1576
1593
|
static override _AUTH_FAILURE_PATTERNS: readonly string[] = [
|
|
@@ -1581,14 +1598,17 @@ export class GeminiCliClient extends CliClient {
|
|
|
1581
1598
|
];
|
|
1582
1599
|
|
|
1583
1600
|
constructor(opts: CliClientOptions = {}) {
|
|
1584
|
-
super(
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1601
|
+
super(
|
|
1602
|
+
{
|
|
1603
|
+
model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL,
|
|
1604
|
+
binary: opts.binary,
|
|
1605
|
+
timeout_seconds: opts.timeout_seconds,
|
|
1606
|
+
max_calls_per_day: opts.max_calls_per_day,
|
|
1607
|
+
warn_at: opts.warn_at,
|
|
1608
|
+
cli_calls_path: opts.cli_calls_path,
|
|
1609
|
+
},
|
|
1610
|
+
{ name: 'gemini', default_binary: 'gemini' },
|
|
1611
|
+
);
|
|
1592
1612
|
}
|
|
1593
1613
|
|
|
1594
1614
|
protected override _build_command(
|
|
@@ -1686,8 +1706,6 @@ export class GeminiCliClient extends CliClient {
|
|
|
1686
1706
|
* heuristically (chars / 4) for the audit-trail.
|
|
1687
1707
|
*/
|
|
1688
1708
|
export class XAICliClient extends CliClient {
|
|
1689
|
-
override name = 'xai';
|
|
1690
|
-
override default_binary = 'grok';
|
|
1691
1709
|
override billable = true; // community CLI consumes an API key — billable applies
|
|
1692
1710
|
|
|
1693
1711
|
static override _AUTH_FAILURE_PATTERNS: readonly string[] = [
|
|
@@ -1698,14 +1716,17 @@ export class XAICliClient extends CliClient {
|
|
|
1698
1716
|
];
|
|
1699
1717
|
|
|
1700
1718
|
constructor(opts: CliClientOptions = {}) {
|
|
1701
|
-
super(
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1719
|
+
super(
|
|
1720
|
+
{
|
|
1721
|
+
model: opts.model ?? DEFAULT_XAI_MODEL,
|
|
1722
|
+
binary: opts.binary,
|
|
1723
|
+
timeout_seconds: opts.timeout_seconds,
|
|
1724
|
+
max_calls_per_day: opts.max_calls_per_day,
|
|
1725
|
+
warn_at: opts.warn_at,
|
|
1726
|
+
cli_calls_path: opts.cli_calls_path,
|
|
1727
|
+
},
|
|
1728
|
+
{ name: 'xai', default_binary: 'grok' },
|
|
1729
|
+
);
|
|
1709
1730
|
}
|
|
1710
1731
|
|
|
1711
1732
|
protected override _build_command(
|
|
@@ -1752,8 +1773,6 @@ export class XAICliClient extends CliClient {
|
|
|
1752
1773
|
* Invokes `perplexity -p <prompt>`. Output is plain text — no JSON envelope.
|
|
1753
1774
|
*/
|
|
1754
1775
|
export class PerplexityCliClient extends CliClient {
|
|
1755
|
-
override name = 'perplexity';
|
|
1756
|
-
override default_binary = 'perplexity';
|
|
1757
1776
|
override billable = true; // community CLI consumes an API key — billable applies
|
|
1758
1777
|
|
|
1759
1778
|
static override _AUTH_FAILURE_PATTERNS: readonly string[] = [
|
|
@@ -1764,14 +1783,17 @@ export class PerplexityCliClient extends CliClient {
|
|
|
1764
1783
|
];
|
|
1765
1784
|
|
|
1766
1785
|
constructor(opts: CliClientOptions = {}) {
|
|
1767
|
-
super(
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1786
|
+
super(
|
|
1787
|
+
{
|
|
1788
|
+
model: opts.model ?? DEFAULT_PERPLEXITY_MODEL,
|
|
1789
|
+
binary: opts.binary,
|
|
1790
|
+
timeout_seconds: opts.timeout_seconds,
|
|
1791
|
+
max_calls_per_day: opts.max_calls_per_day,
|
|
1792
|
+
warn_at: opts.warn_at,
|
|
1793
|
+
cli_calls_path: opts.cli_calls_path,
|
|
1794
|
+
},
|
|
1795
|
+
{ name: 'perplexity', default_binary: 'perplexity' },
|
|
1796
|
+
);
|
|
1775
1797
|
}
|
|
1776
1798
|
|
|
1777
1799
|
protected override _build_command(
|
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* a locally-installed CLI under subscription auth (free for
|
|
22
22
|
* first-party CLIs, billable for community wrappers); `auto` = pick
|
|
23
23
|
* per provider per invocation (cli → api → unavailable), resolved by
|
|
24
|
-
* `transport_resolver.ts`. `auto` is
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* `transport_resolver.ts`. `auto` is the SHIPPED DEFAULT
|
|
25
|
+
* (road-to-always-on-orchestration Phase 3.1 — CLI-first is the
|
|
26
|
+
* owner-set transport doctrine: ride the vendor CLI under subscription
|
|
27
|
+
* auth first, fall to the metered API only where no CLI resolves).
|
|
28
|
+
* `manual` and a pinned `api`/`cli` remain valid explicit per-member or
|
|
29
|
+
* per-invocation overrides — `auto` never displaces one.
|
|
27
30
|
* 3. `members.<name>` keys are restricted to the known provider set.
|
|
28
31
|
* 4. `cost_budget.*` numeric fields are >= 0.
|
|
29
32
|
* 5. Enabled members carry a non-empty `model` and `api_key_ref` when
|
|
@@ -37,7 +40,18 @@
|
|
|
37
40
|
* 8. `binary:` is only valid when the member's effective mode is `cli` or
|
|
38
41
|
* `auto` (auto may resolve to the cli rung);
|
|
39
42
|
* `cli_call_budget.max_calls_per_day.<provider>` keys must be valid
|
|
40
|
-
* providers.
|
|
43
|
+
* providers. `max_calls_per_day` SHIPS POPULATED with a generous
|
|
44
|
+
* per-provider default (50/day, road-to-always-on-orchestration
|
|
45
|
+
* Phase 3.4) for every known provider — a guard against a silent
|
|
46
|
+
* always-on pass exhausting a subscription's plan quota, not a brake;
|
|
47
|
+
* an explicit per-provider entry overrides just that provider's default.
|
|
48
|
+
* 9. `quorum` ∈ {`"majority"`, a positive integer} (default `"majority"`,
|
|
49
|
+
* road-to-always-on-orchestration Phase 3.3). `"majority"` at n members
|
|
50
|
+
* resolves to `ceil(n / 2)` — a SIMPLE majority, deliberately NOT
|
|
51
|
+
* "more than half": at n=2 that is 1-of-2, because 2-of-2 turns any
|
|
52
|
+
* single absent member into a deadlocked release gate (council-verified
|
|
53
|
+
* 2026-08-09). See `quorum.ts` for the resolver a caller applies to its
|
|
54
|
+
* own present/total member counts.
|
|
41
55
|
*
|
|
42
56
|
* Parity notes (intentional, documented):
|
|
43
57
|
* - YAML is parsed with `yaml` (npm) at `version: '1.1'`, matching
|
|
@@ -562,6 +576,15 @@ export interface CliCallBudgetConfig {
|
|
|
562
576
|
readonly warn_at: number;
|
|
563
577
|
}
|
|
564
578
|
|
|
579
|
+
/**
|
|
580
|
+
* `"majority"` (default) or a positive integer k — the number of members a
|
|
581
|
+
* pass needs to conclude (road-to-always-on-orchestration Phase 3.3). See
|
|
582
|
+
* `quorum.ts::resolveQuorumThreshold` for how a caller turns this into a
|
|
583
|
+
* concrete threshold against its own total-member count, and the module
|
|
584
|
+
* docstring rule 9 for why `"majority"` is `ceil(n / 2)`, not `> n / 2`.
|
|
585
|
+
*/
|
|
586
|
+
export type QuorumSetting = 'majority' | number;
|
|
587
|
+
|
|
565
588
|
export interface CouncilConfig {
|
|
566
589
|
readonly enabled: boolean;
|
|
567
590
|
readonly defaults: DefaultsConfig;
|
|
@@ -570,6 +593,7 @@ export interface CouncilConfig {
|
|
|
570
593
|
readonly advisors: ReadonlyMap<string, AdvisorConfig>;
|
|
571
594
|
readonly consensus_scoring: ConsensusScoringConfig;
|
|
572
595
|
readonly cli_call_budget: CliCallBudgetConfig;
|
|
596
|
+
readonly quorum: QuorumSetting;
|
|
573
597
|
readonly necessity_classifier: NecessityClassifierConfig;
|
|
574
598
|
readonly model_downgrade: ModelDowngradeConfig;
|
|
575
599
|
readonly debate: DebateConfig;
|
|
@@ -578,6 +602,7 @@ export interface CouncilConfig {
|
|
|
578
602
|
readonly chairman: ChairmanConfig;
|
|
579
603
|
readonly debate_gates: DebateGatesConfig;
|
|
580
604
|
readonly restate: RestateConfig;
|
|
605
|
+
readonly critic_protocol: CriticProtocol;
|
|
581
606
|
readonly decision_resolution: DecisionResolutionConfig;
|
|
582
607
|
readonly routing: RoutingConfig;
|
|
583
608
|
readonly low_impact: LowImpactConfig;
|
|
@@ -796,6 +821,7 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
796
821
|
const cli_call_budget = _build_cli_call_budget(
|
|
797
822
|
_asDict(_getOr(raw, 'cli_call_budget', {})),
|
|
798
823
|
);
|
|
824
|
+
const quorum = _build_quorum(_get(raw, 'quorum', 'majority'));
|
|
799
825
|
const necessity_classifier = _build_necessity_classifier(
|
|
800
826
|
_asDict(_getOr(raw, 'necessity_classifier', {})),
|
|
801
827
|
);
|
|
@@ -821,6 +847,7 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
821
847
|
'debate_gates',
|
|
822
848
|
);
|
|
823
849
|
const restate = _build_restate(_asDict(_getOr(raw, 'restate', {})), 'restate');
|
|
850
|
+
const critic_protocol = _build_critic_protocol(_getOr(raw, 'critic_protocol', 'legacy'));
|
|
824
851
|
const decision_resolution = _build_decision_resolution(
|
|
825
852
|
_asDict(_getOr(raw, 'decision_resolution', {})),
|
|
826
853
|
);
|
|
@@ -843,6 +870,7 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
843
870
|
advisors,
|
|
844
871
|
consensus_scoring: consensus,
|
|
845
872
|
cli_call_budget,
|
|
873
|
+
quorum,
|
|
846
874
|
necessity_classifier,
|
|
847
875
|
model_downgrade,
|
|
848
876
|
debate,
|
|
@@ -851,6 +879,7 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
851
879
|
chairman,
|
|
852
880
|
debate_gates,
|
|
853
881
|
restate,
|
|
882
|
+
critic_protocol,
|
|
854
883
|
decision_resolution,
|
|
855
884
|
routing,
|
|
856
885
|
low_impact,
|
|
@@ -1115,6 +1144,27 @@ function _build_restate(d: Dict, scope: string): RestateConfig {
|
|
|
1115
1144
|
return { enabled: Boolean(enabled) };
|
|
1116
1145
|
}
|
|
1117
1146
|
|
|
1147
|
+
/**
|
|
1148
|
+
* `critic_protocol` (road-to-judgment-and-forensic-evidence Phase 2) — which
|
|
1149
|
+
* critic posture adversarial/skeptic review passes use. `legacy` is the
|
|
1150
|
+
* shipped free-hunt skeptic; `load_bearing` is the fixed protocol that must
|
|
1151
|
+
* name the single load-bearing assumption and may return "holds". Default
|
|
1152
|
+
* `legacy` regardless of the A/B outcome (promotion is a separate decision).
|
|
1153
|
+
*/
|
|
1154
|
+
export type CriticProtocol = 'legacy' | 'load_bearing';
|
|
1155
|
+
|
|
1156
|
+
const _VALID_CRITIC_PROTOCOLS: ReadonlySet<string> = new Set(['legacy', 'load_bearing']);
|
|
1157
|
+
|
|
1158
|
+
function _build_critic_protocol(v: unknown): CriticProtocol {
|
|
1159
|
+
if (v === undefined || v === null) return 'legacy';
|
|
1160
|
+
if (!(_isStr(v) && _VALID_CRITIC_PROTOCOLS.has(v))) {
|
|
1161
|
+
throw new CouncilConfigError(
|
|
1162
|
+
`\`critic_protocol\`=${_pyRepr(v)} not in ${_sortedListRepr(_VALID_CRITIC_PROTOCOLS)}.`,
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
return v as CriticProtocol;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1118
1168
|
const _VALID_RESOLUTION_MODES: ReadonlySet<string> = new Set([
|
|
1119
1169
|
'agent',
|
|
1120
1170
|
'council',
|
|
@@ -1427,7 +1477,11 @@ function _build_defaults(d: Dict): DefaultsConfig {
|
|
|
1427
1477
|
if (!_isDict(d)) {
|
|
1428
1478
|
throw new CouncilConfigError('`defaults` must be a mapping.');
|
|
1429
1479
|
}
|
|
1430
|
-
|
|
1480
|
+
// road-to-always-on-orchestration Phase 3.1: CLI-first is the shipped
|
|
1481
|
+
// default (was `api`) — see the module docstring rule 2. `manual` and a
|
|
1482
|
+
// pinned `api`/`cli` remain valid overrides at every layer resolve_mode
|
|
1483
|
+
// checks; this only changes what a config that never sets `mode` gets.
|
|
1484
|
+
const mode = _get(d, 'mode', 'auto');
|
|
1431
1485
|
if (!(_isStr(mode) && _VALID_MODES.has(mode))) {
|
|
1432
1486
|
throw new CouncilConfigError(
|
|
1433
1487
|
`defaults.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_MODES)}.`,
|
|
@@ -1630,7 +1684,13 @@ function _build_cost_budget(d: Dict): CostBudgetConfig {
|
|
|
1630
1684
|
function _build_member(
|
|
1631
1685
|
name: string,
|
|
1632
1686
|
cfg: Dict,
|
|
1633
|
-
|
|
1687
|
+
// road-to-always-on-orchestration Phase 3.1: kept in sync with
|
|
1688
|
+
// `_build_defaults`'s `auto` default for consistency. The sole call site
|
|
1689
|
+
// (`_build_config`) always passes `defaults.mode` explicitly, so this
|
|
1690
|
+
// parameter default is unreachable on the live path today; it exists so
|
|
1691
|
+
// a future caller that omits the third argument observes the same
|
|
1692
|
+
// doctrine rather than silently reverting to the retired `api` default.
|
|
1693
|
+
default_mode = 'auto',
|
|
1634
1694
|
): MemberConfig {
|
|
1635
1695
|
if (!_VALID_PROVIDERS.has(name)) {
|
|
1636
1696
|
throw new CouncilConfigError(
|
|
@@ -1775,6 +1835,18 @@ function _build_member(
|
|
|
1775
1835
|
};
|
|
1776
1836
|
}
|
|
1777
1837
|
|
|
1838
|
+
/**
|
|
1839
|
+
* Generous per-provider guard for `mode: cli` / `mode: auto` members
|
|
1840
|
+
* (road-to-always-on-orchestration Phase 3.4). Before this default the map
|
|
1841
|
+
* shipped empty and an unlisted provider ran uncapped — a plan-quota guard
|
|
1842
|
+
* that only existed if the user remembered to opt in. Every known provider
|
|
1843
|
+
* now carries this floor unless a config entry overrides it; sized as a
|
|
1844
|
+
* GUARD against silent quota exhaustion on an always-on pass, not a brake on
|
|
1845
|
+
* normal use (see the template's own worked-example sizing for the
|
|
1846
|
+
* reasoning behind a TIGHTER per-provider number).
|
|
1847
|
+
*/
|
|
1848
|
+
const _DEFAULT_CLI_CALLS_PER_DAY = 50;
|
|
1849
|
+
|
|
1778
1850
|
function _build_cli_call_budget(d: Dict): CliCallBudgetConfig {
|
|
1779
1851
|
if (!_isDict(d)) {
|
|
1780
1852
|
throw new CouncilConfigError('`cli_call_budget` must be a mapping.');
|
|
@@ -1786,6 +1858,9 @@ function _build_cli_call_budget(d: Dict): CliCallBudgetConfig {
|
|
|
1786
1858
|
);
|
|
1787
1859
|
}
|
|
1788
1860
|
const caps = new Map<string, number>();
|
|
1861
|
+
for (const provider of _VALID_PROVIDERS) {
|
|
1862
|
+
caps.set(provider, _DEFAULT_CLI_CALLS_PER_DAY);
|
|
1863
|
+
}
|
|
1789
1864
|
for (const [provider, value] of Object.entries(raw_caps)) {
|
|
1790
1865
|
if (!_VALID_PROVIDERS.has(provider)) {
|
|
1791
1866
|
throw new CouncilConfigError(
|
|
@@ -1817,6 +1892,19 @@ function _build_cli_call_budget(d: Dict): CliCallBudgetConfig {
|
|
|
1817
1892
|
return { max_calls_per_day: caps, warn_at };
|
|
1818
1893
|
}
|
|
1819
1894
|
|
|
1895
|
+
function _build_quorum(raw: Json): QuorumSetting {
|
|
1896
|
+
if (raw === 'majority') {
|
|
1897
|
+
return 'majority';
|
|
1898
|
+
}
|
|
1899
|
+
if (_isInt(raw) && !_isBool(raw) && (raw as number) >= 1) {
|
|
1900
|
+
return raw as number;
|
|
1901
|
+
}
|
|
1902
|
+
throw new CouncilConfigError(
|
|
1903
|
+
`\`quorum\`=${_pyRepr(raw)} must be 'majority' or an integer >= 1 ` +
|
|
1904
|
+
`(got ${_pyTypeName(raw)}).`,
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1820
1908
|
function _build_advisor(name: string, cfg: Dict): AdvisorConfig {
|
|
1821
1909
|
if (!_isDict(cfg)) {
|
|
1822
1910
|
throw new CouncilConfigError(`advisors.${name}: must be a mapping.`);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verdict → handoff envelope (Phase 4.1 of road-to-always-on-orchestration).
|
|
3
|
+
*
|
|
4
|
+
* A council pass produces a verdict a human currently has to read and
|
|
5
|
+
* re-type into whatever executes next. This module is the machine-readable
|
|
6
|
+
* bridge: an additive `HandoffEnvelope` — decision, rejected alternatives
|
|
7
|
+
* with reasons, binding constraints — injectable as the dispatched work
|
|
8
|
+
* order of an implementing subagent, so deliberation output becomes
|
|
9
|
+
* execution input without a human transcription step.
|
|
10
|
+
*
|
|
11
|
+
* ## The honesty constraint
|
|
12
|
+
*
|
|
13
|
+
* A council pass has exactly ONE place in this codebase where "the
|
|
14
|
+
* decision" is a discrete, machine-parsed value rather than free-text prose
|
|
15
|
+
* a human (or an NLP guess) would have to interpret: the option-level
|
|
16
|
+
* stance tally (`stance_tally.ts`, opt-in via `ai_council.stance_tally`).
|
|
17
|
+
* Every other synthesis path — the default template, a chairman's prose
|
|
18
|
+
* verdict, consensus scoring's qualitative Strong/Findings/Minority
|
|
19
|
+
* buckets — has no comparable discrete "decision" field to extract from
|
|
20
|
+
* without parsing prose, which is exactly the kind of confident-looking
|
|
21
|
+
* fabrication this module refuses to do.
|
|
22
|
+
*
|
|
23
|
+
* So: `buildHandoffFromStanceTally` is the ONLY builder this module ships.
|
|
24
|
+
* No tally (the common case — the flag defaults off) or a tally that split
|
|
25
|
+
* (no option cleared the consensus threshold) → every field is `null`,
|
|
26
|
+
* never a guessed decision. A future builder over a different structured
|
|
27
|
+
* source (a chairman synthesis that itself emits structured fields, a
|
|
28
|
+
* consensus-scoring extension) is additive — it does not change this one.
|
|
29
|
+
*
|
|
30
|
+
* @see docs/contracts/ai-council-config.md § Handoff envelope
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { StanceTallyResult } from './stance_tally.js';
|
|
34
|
+
|
|
35
|
+
/** One option the tally counted but did not conclude on. */
|
|
36
|
+
export interface RejectedAlternative {
|
|
37
|
+
readonly option: string;
|
|
38
|
+
/**
|
|
39
|
+
* A factual, derived-from-the-tally sentence — never invented prose.
|
|
40
|
+
* States the option's own backer count and weight against the
|
|
41
|
+
* threshold the winner had to clear, nothing else.
|
|
42
|
+
*/
|
|
43
|
+
readonly reason: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The machine-readable work order a council verdict hands to whatever
|
|
48
|
+
* executes next. Every field is independently nullable: `null` means "no
|
|
49
|
+
* structured source existed to populate this field this pass", never a
|
|
50
|
+
* fabricated placeholder. A consumer checks for `null`, not for an empty
|
|
51
|
+
* array — an empty array would claim "checked, found none", which this
|
|
52
|
+
* module never asserts when it never actually checked.
|
|
53
|
+
*/
|
|
54
|
+
export interface HandoffEnvelope {
|
|
55
|
+
readonly decision: string | null;
|
|
56
|
+
readonly rejected_alternatives: readonly RejectedAlternative[] | null;
|
|
57
|
+
/**
|
|
58
|
+
* Binding constraints the decision carries. No structured source for
|
|
59
|
+
* this exists anywhere in the current synthesis pipeline (a
|
|
60
|
+
* dealbreaker count describes objection PRESSURE on an option, not a
|
|
61
|
+
* named constraint on the winner) — always `null` until one does.
|
|
62
|
+
*/
|
|
63
|
+
readonly constraints: readonly string[] | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** An envelope with every field `null` — the honest "nothing to hand off" shape. */
|
|
67
|
+
export const EMPTY_HANDOFF: HandoffEnvelope = {
|
|
68
|
+
decision: null,
|
|
69
|
+
rejected_alternatives: null,
|
|
70
|
+
constraints: null,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** True when every field is `null` — nothing worth rendering or dispatching. */
|
|
74
|
+
export function isEmptyHandoff(h: HandoffEnvelope): boolean {
|
|
75
|
+
return h.decision === null && h.rejected_alternatives === null && h.constraints === null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build a `HandoffEnvelope` from a stance tally, or the honest empty
|
|
80
|
+
* envelope when there is nothing structured to extract:
|
|
81
|
+
*
|
|
82
|
+
* - `tally` is `null` — stance tally never ran this pass (the default,
|
|
83
|
+
* opt-in-off case).
|
|
84
|
+
* - `tally.consensus` is `null` — the tally ran but split; no option
|
|
85
|
+
* cleared the two-thirds threshold, so there is no decision to report,
|
|
86
|
+
* only a split (which is quorum's/the render's job to surface, not this
|
|
87
|
+
* module's).
|
|
88
|
+
*
|
|
89
|
+
* Otherwise: `decision` is the winning option's display label;
|
|
90
|
+
* `rejected_alternatives` lists every OTHER non-abstain option the tally
|
|
91
|
+
* counted, each with a reason sentence built strictly from that option's
|
|
92
|
+
* own tally numbers (backer count, weight, the threshold the winner
|
|
93
|
+
* cleared) — never a narrative guess at WHY members backed it.
|
|
94
|
+
*/
|
|
95
|
+
export function buildHandoffFromStanceTally(tally: StanceTallyResult | null): HandoffEnvelope {
|
|
96
|
+
if (tally === null || tally.consensus === null) {
|
|
97
|
+
return EMPTY_HANDOFF;
|
|
98
|
+
}
|
|
99
|
+
const winner = tally.consensus;
|
|
100
|
+
const rejected: RejectedAlternative[] = tally.options
|
|
101
|
+
.filter((o) => o !== winner)
|
|
102
|
+
.map((o) => ({
|
|
103
|
+
option: o.label,
|
|
104
|
+
reason:
|
|
105
|
+
`backed by ${o.backers.length} member(s), weight ${o.weight.toFixed(2)} of ` +
|
|
106
|
+
`${tally.threshold.toFixed(2)} needed to conclude`,
|
|
107
|
+
}));
|
|
108
|
+
return {
|
|
109
|
+
decision: winner.label,
|
|
110
|
+
rejected_alternatives: rejected.length > 0 ? rejected : null,
|
|
111
|
+
constraints: null,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -33,21 +33,36 @@
|
|
|
33
33
|
* conflated them. They are now named separately:
|
|
34
34
|
*
|
|
35
35
|
* - **Loader default** — when a config file omits `defaults.mode` entirely,
|
|
36
|
-
* `config.ts::_build_defaults` fills `
|
|
37
|
-
*
|
|
36
|
+
* `config.ts::_build_defaults` fills `auto` (road-to-always-on-orchestration
|
|
37
|
+
* Phase 3.1 — CLI-first is now the owner-set transport doctrine; before this
|
|
38
|
+
* flip the loader default was `api`). This is what every real config
|
|
39
|
+
* observes, because the loader always populates the key.
|
|
38
40
|
* - **Built-in fallback** — when NO layer supplies a mode at all (a settings
|
|
39
41
|
* dict handed straight to the resolver, no config file involved), the answer
|
|
40
42
|
* is `manual`, below. That is the fail-closed direction the no-silent-spend
|
|
41
43
|
* Iron Law requires: a caller who supplied no transport preference has not
|
|
42
44
|
* asked to spend money, so the free transport wins.
|
|
45
|
+
*
|
|
46
|
+
* ### Why `DEFAULT_MODE` did NOT flip to `auto` alongside the loader default
|
|
47
|
+
*
|
|
48
|
+
* `auto` still resolves to a paying rung (the api fallback) when no CLI is
|
|
49
|
+
* usable — it is a routing rule, not a free transport. `DEFAULT_MODE` is only
|
|
50
|
+
* reached by a caller that bypasses the config loader entirely (no
|
|
51
|
+
* `.ai-council.yml`, a hand-built settings dict handed straight to
|
|
52
|
+
* `resolve_mode`/`resolve_modes`). Flipping it to `auto` would let such a
|
|
53
|
+
* caller spend money on a config that never asked for `auto` in the first
|
|
54
|
+
* place — the same no-silent-spend violation the built-in fallback exists to
|
|
55
|
+
* prevent. Every real, file-backed config always has `defaults.mode`
|
|
56
|
+
* populated by the loader (now `auto`), so `DEFAULT_MODE` is never consulted
|
|
57
|
+
* on that path — verified in modes.test.ts and config.test.ts.
|
|
43
58
|
*/
|
|
44
59
|
|
|
45
60
|
export const VALID_MODES: ReadonlySet<string> = new Set(['api', 'manual', 'cli', 'auto']);
|
|
46
61
|
|
|
47
62
|
/**
|
|
48
63
|
* Built-in fallback when no layer supplies a mode. `manual` — free, no key,
|
|
49
|
-
* no spend. NOT the same as the loader's `defaults.mode` default (`
|
|
50
|
-
* the module header.
|
|
64
|
+
* no spend. NOT the same as the loader's `defaults.mode` default (`auto`
|
|
65
|
+
* since road-to-always-on-orchestration Phase 3.1); see the module header.
|
|
51
66
|
*/
|
|
52
67
|
export const DEFAULT_MODE = 'manual';
|
|
53
68
|
|