@event4u/agent-config 7.0.2 → 7.2.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 +9 -1
- package/CHANGELOG.md +117 -0
- package/README.md +4 -2
- package/dist/agent-src/commands/council/default.md +28 -20
- package/dist/agent-src/commands/evals/fix.json +5 -1
- package/dist/agent-src/commands/fix/comments.md +101 -0
- package/dist/agent-src/commands/fix.md +5 -2
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +65 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +68 -0
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +68 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +62 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +59 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +8 -3
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +56 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +57 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +54 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +71 -0
- package/dist/agent-src/contexts/execution/verify-budget.md +50 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +10 -0
- package/dist/agent-src/rules/delegation-policy.md +85 -0
- package/dist/agent-src/rules/design-fidelity.md +98 -0
- package/dist/agent-src/rules/domain-safety-pii.md +22 -0
- package/dist/agent-src/rules/legal-safety-floor.md +182 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +25 -22
- package/dist/agent-src/rules/output-discipline.md +85 -0
- package/dist/agent-src/rules/telegraph-speak.md +4 -1
- package/dist/agent-src/rules/token-budget-discipline.md +94 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +102 -15
- package/dist/agent-src/scripts/update_roadmap_progress.ts +30 -3
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +20 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +28 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +9 -0
- package/dist/agent-src/skills/contract-review/SKILL.md +125 -0
- package/dist/agent-src/skills/contract-review/evals/triggers.json +17 -0
- package/dist/agent-src/skills/dashboard-design/SKILL.md +21 -5
- package/dist/agent-src/skills/design-intelligence/SKILL.md +96 -0
- package/dist/agent-src/skills/design-review/SKILL.md +83 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +234 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +1 -0
- package/dist/agent-src/skills/dpa-review/SKILL.md +130 -0
- package/dist/agent-src/skills/dpa-review/evals/triggers.json +17 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +15 -4
- package/dist/agent-src/skills/fe-design/SKILL.md +98 -16
- package/dist/agent-src/skills/flux/SKILL.md +8 -0
- package/dist/agent-src/skills/iconography/SKILL.md +1 -0
- package/dist/agent-src/skills/laravel/SKILL.md +1 -0
- package/dist/agent-src/skills/legal-intake-triage/SKILL.md +128 -0
- package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +17 -0
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +101 -0
- package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +17 -0
- package/dist/agent-src/skills/livewire/SKILL.md +8 -0
- package/dist/agent-src/skills/nda-triage/SKILL.md +114 -0
- package/dist/agent-src/skills/nda-triage/evals/triggers.json +17 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +21 -9
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +3 -1
- package/dist/agent-src/skills/recursive-verification/SKILL.md +186 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +17 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +18 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +11 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +17 -7
- package/dist/agent-src/skills/typography-system/SKILL.md +14 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
- package/dist/agent-src/templates/agent-settings.md +64 -19
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +362 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +13 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +56 -11
- package/dist/discovery/trust-report.md +7 -5
- package/dist/discovery/workspaces.json +38 -7
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/routes/wizard.js +4 -3
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/schemas/settings.js +23 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-CoQo90sz.js → index-UjcTG_9O.js} +2 -2
- package/dist/ui/assets/index-UjcTG_9O.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/RELEASE_STORY_TEMPLATE.md +37 -0
- package/docs/architecture.md +4 -4
- package/docs/benchmark.md +46 -0
- package/docs/catalog.md +18 -6
- package/docs/command-flows.md +7 -1
- package/docs/contracts/ai-council-config.md +17 -10
- package/docs/contracts/audit-log-v1.md +7 -0
- package/docs/contracts/auto-orchestration-v1.md +70 -0
- package/docs/contracts/capability-boundary.md +64 -0
- package/docs/contracts/command-clusters.md +1 -1
- package/docs/contracts/discovery-manifest.schema.json +2 -2
- package/docs/contracts/package-self-orientation.md +3 -0
- package/docs/contracts/subagent-boundary.md +54 -0
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +32 -0
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +13 -6
- package/docs/decisions/ADR-049-configuration-trust-boundary.md +11 -1
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +10 -4
- package/docs/decisions/ADR-104-ai-council-config-global-only.md +119 -0
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +102 -0
- package/docs/decisions/ADR-106-recursive-verification-benchmark-gate.md +126 -0
- package/docs/decisions/ADR-107-legal-domain-pack-adoption.md +70 -0
- package/docs/decisions/ADR-108-open-source-forever-no-commercial-tier.md +46 -0
- package/docs/decisions/INDEX.md +5 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
- package/docs/guidelines/agent-infra/domain-pack-architecture.md +56 -0
- package/docs/guidelines/design-antipatterns-triggers.json +17 -0
- package/docs/guidelines/design-antipatterns.md +170 -0
- package/docs/guidelines/design-modes.md +97 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +103 -3
- package/src/config/discovery/packs.yml +24 -0
- package/src/config/discovery/workspaces.yml +7 -0
- package/src/scripts/_lib/auto_dispatch.ts +95 -0
- package/src/scripts/_lib/host_capability.ts +59 -0
- package/src/scripts/_lib/orchestration_gate.ts +98 -0
- package/src/scripts/_lib/subagent_bundle.ts +154 -0
- package/src/scripts/_lib/subagent_response.ts +82 -0
- package/src/scripts/_lib/subagent_routing.ts +81 -0
- package/src/scripts/_lib/subagent_spawn.ts +97 -0
- package/src/scripts/_lib/subagent_steering.ts +64 -0
- package/src/scripts/_lib/verify_budget.ts +54 -0
- package/src/scripts/ai_council/advisors.ts +4 -2
- package/src/scripts/ai_council/clients.ts +7 -1
- package/src/scripts/ai_council/config.ts +22 -17
- package/src/scripts/bench_ab_v2_run.ts +127 -0
- package/src/scripts/bench_ab_v2_stats.ts +39 -0
- package/src/scripts/check_council_config_location.ts +6 -5
- package/src/scripts/check_trunk_drift.ts +67 -0
- package/src/scripts/lint_design_quality.ts +321 -0
- package/src/scripts/lint_discovery_vocabulary.ts +6 -0
- package/src/scripts/lint_framework_leakage_allowlist.json +21 -0
- package/src/scripts/lint_legal_pack.ts +197 -0
- package/src/scripts/lint_output_slop.ts +223 -0
- package/src/scripts/lint_pack_risk_class.ts +79 -0
- package/src/scripts/probe_projection_fidelity.ts +7 -1
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/skill.schema.json +5 -0
- package/src/scripts/skill_linter.ts +3 -0
- package/dist/ui/assets/index-CoQo90sz.js.map +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-capability manifest normalizer.
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O helper that takes a partial / unknown object (typically the
|
|
5
|
+
* `subagents.host_capabilities` override from `.agent-settings.yml`, or the
|
|
6
|
+
* agent's own host-knowledge object) and returns a fully-populated manifest.
|
|
7
|
+
*
|
|
8
|
+
* Contract — see `src/agent-src/contexts/execution/host-capability-manifest.md`:
|
|
9
|
+
*
|
|
10
|
+
* - Safe default is ALL boolean fields `false` — an unknown host is assumed to
|
|
11
|
+
* have no subagent primitive.
|
|
12
|
+
* - A missing or non-boolean field resolves to `false`, never `true`.
|
|
13
|
+
* - `schema_version` is always forced to `1`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Resolved host-capability manifest. All booleans, `schema_version` fixed at 1. */
|
|
17
|
+
export interface HostCapabilityManifest {
|
|
18
|
+
schema_version: 1;
|
|
19
|
+
subagent_spawn: boolean;
|
|
20
|
+
parallel_spawn: boolean;
|
|
21
|
+
status_polling: boolean;
|
|
22
|
+
separate_quota_pool: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Safe default — unknown host assumes no subagent primitive. */
|
|
26
|
+
const SAFE_DEFAULT: HostCapabilityManifest = {
|
|
27
|
+
schema_version: 1,
|
|
28
|
+
subagent_spawn: false,
|
|
29
|
+
parallel_spawn: false,
|
|
30
|
+
status_polling: false,
|
|
31
|
+
separate_quota_pool: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Coerce one field: only a strict boolean `true` survives; everything else is `false`. */
|
|
35
|
+
function asBool(value: unknown): boolean {
|
|
36
|
+
return value === true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Normalize a partial / unknown object into a full {@link HostCapabilityManifest}.
|
|
41
|
+
*
|
|
42
|
+
* Any missing or invalidly-typed field falls back to the all-`false` safe
|
|
43
|
+
* default; `schema_version` is always forced to `1`.
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeHostManifest(input: unknown): HostCapabilityManifest {
|
|
46
|
+
if (input === null || typeof input !== 'object') {
|
|
47
|
+
return { ...SAFE_DEFAULT };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const src = input as Record<string, unknown>;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
schema_version: 1,
|
|
54
|
+
subagent_spawn: asBool(src.subagent_spawn),
|
|
55
|
+
parallel_spawn: asBool(src.parallel_spawn),
|
|
56
|
+
status_polling: asBool(src.status_polling),
|
|
57
|
+
separate_quota_pool: asBool(src.separate_quota_pool),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-flip decision gate (Phase 6).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. Encodes the Phase-6 falsification gate: the SHIPPED default for
|
|
5
|
+
* `subagents.auto` flips toward `on` ONLY when a real benchmark shows a net
|
|
6
|
+
* token-or-time win at held quality, and only on hosts with a subagent
|
|
7
|
+
* primitive. Honest-null exit (no win, or quality regressed) keeps the
|
|
8
|
+
* conservative default. Contract:
|
|
9
|
+
* `src/agent-src/contexts/execution/orchestration-benchmark-gate.md`.
|
|
10
|
+
*
|
|
11
|
+
* This helper decides; it never runs the benchmark. The measurement is the
|
|
12
|
+
* empirical bench:ab step, authorised + run out of band.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface BenchmarkResult {
|
|
16
|
+
/** Net token OR wall-clock win on the delegable-task subset. */
|
|
17
|
+
net_win: boolean;
|
|
18
|
+
/** Output quality held at or above the single-agent baseline. */
|
|
19
|
+
quality_held: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type GateVerdict = 'pass' | 'fail';
|
|
23
|
+
export type ShippedDefault = 'on' | 'ask' | 'off';
|
|
24
|
+
|
|
25
|
+
/** Gate verdict: pass requires BOTH a net win AND held quality. */
|
|
26
|
+
export function gateVerdict(r: BenchmarkResult): GateVerdict {
|
|
27
|
+
return r.net_win && r.quality_held ? 'pass' : 'fail';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the shipped default for `subagents.auto` after a benchmark.
|
|
32
|
+
*
|
|
33
|
+
* - Gate fail (no win or quality regressed) → keep conservative: `ask` on a
|
|
34
|
+
* subagent-capable host, `off` elsewhere (honest-null exit).
|
|
35
|
+
* - Gate pass → `on` on a subagent-capable host, `off` elsewhere.
|
|
36
|
+
*
|
|
37
|
+
* `host_subagent_spawn` gates the result so the default is never `on`/`ask`
|
|
38
|
+
* where the host cannot spawn at all.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveShippedDefault(r: BenchmarkResult, host_subagent_spawn: boolean): ShippedDefault {
|
|
41
|
+
if (!host_subagent_spawn) return 'off';
|
|
42
|
+
return gateVerdict(r) === 'pass' ? 'on' : 'ask';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Recursive-verification gate (ADR-106).
|
|
47
|
+
*
|
|
48
|
+
* Decides whether `verification.recursive` ships on for a (host, family) cell.
|
|
49
|
+
* Pure, no-I/O — the measurement is the empirical bench:ab step. Encodes the
|
|
50
|
+
* council-locked two-branch rule: capability-only is NOT the gate, because it
|
|
51
|
+
* assumes (without measuring) that recursion's discipline lift is redundant
|
|
52
|
+
* with the always-on rules. The `D₂ − D₁` (recursion over rules-only) baseline
|
|
53
|
+
* makes redundancy a measured fact, gated behind a concrete cost ceiling and a
|
|
54
|
+
* human-preference pre-test. Contract:
|
|
55
|
+
* `docs/decisions/ADR-106-recursive-verification-benchmark-gate.md`.
|
|
56
|
+
*/
|
|
57
|
+
export interface RecursiveBenchmarkResult {
|
|
58
|
+
/** Capability-axis lift is significant (McNemar/scorer p<0.05, effect≥0.5). */
|
|
59
|
+
capability_lift_significant: boolean;
|
|
60
|
+
/** Novel discipline lift `D₂ − D₁` (recursion over rules-only) exceeds ε_disc. */
|
|
61
|
+
novel_discipline_lift_significant: boolean;
|
|
62
|
+
/** Per-task cost is within the concrete ceiling (token-multiplier / $). */
|
|
63
|
+
cost_within_ceiling: boolean;
|
|
64
|
+
/** Phase 3a-pre human-preference rate (fraction preferring the recursion output). */
|
|
65
|
+
human_preference_rate: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Human-preference floor from ADR-106: below this the discipline branch cannot pass. */
|
|
69
|
+
export const RECURSIVE_HUMAN_PREFERENCE_FLOOR = 0.6;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Gate verdict for recursive verification (ADR-106 two-branch rule):
|
|
73
|
+
* pass iff capability_lift_significant
|
|
74
|
+
* OR (novel_discipline_lift_significant AND cost_within_ceiling
|
|
75
|
+
* AND human_preference_rate > floor)
|
|
76
|
+
* The human-preference test gates the discipline branch only — a capability
|
|
77
|
+
* lift passes on its own.
|
|
78
|
+
*/
|
|
79
|
+
export function recursiveGateVerdict(r: RecursiveBenchmarkResult): GateVerdict {
|
|
80
|
+
if (r.capability_lift_significant) return 'pass';
|
|
81
|
+
const disciplineBranch =
|
|
82
|
+
r.novel_discipline_lift_significant &&
|
|
83
|
+
r.cost_within_ceiling &&
|
|
84
|
+
r.human_preference_rate > RECURSIVE_HUMAN_PREFERENCE_FLOOR;
|
|
85
|
+
return disciplineBranch ? 'pass' : 'fail';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the shipped default for `verification.recursive`.
|
|
90
|
+
*
|
|
91
|
+
* Default stays `off` until a cell passes (ADR-106) — an honest-null keeps it
|
|
92
|
+
* `off`, never `ask`. `host_supported` gates the result so a host where
|
|
93
|
+
* recursion is not viable always resolves `off`.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveRecursiveDefault(r: RecursiveBenchmarkResult, host_supported: boolean): ShippedDefault {
|
|
96
|
+
if (!host_supported) return 'off';
|
|
97
|
+
return recursiveGateVerdict(r) === 'pass' ? 'on' : 'off';
|
|
98
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent bundle resolver (Phase 2 / A2).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. Maps a task slice to a concrete subagent bundle by selecting
|
|
5
|
+
* from the EXISTING surfaces — role-modes, the `judge-*` review-lens skills,
|
|
6
|
+
* personas, knowledge-card refs, and an ADR-035 tier — instead of a new parallel
|
|
7
|
+
* registry. Feeds `composeSpawnBrief` (subagent_spawn.ts) real values.
|
|
8
|
+
*
|
|
9
|
+
* Contract: `src/agent-src/contexts/execution/subagent-spawn-contract.md`.
|
|
10
|
+
*
|
|
11
|
+
* ADR-100 guard: proprietary knowledge-card refs never enter a cross-project
|
|
12
|
+
* subagent bundle. The resolver drops them and records the drop.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { RoleMode, SpawnSelection } from './subagent_spawn.js';
|
|
16
|
+
|
|
17
|
+
export type Tier = 'lite' | 'medium' | 'high' | 'inherit';
|
|
18
|
+
|
|
19
|
+
/** The well-known delegable slice kinds and their lens/role/tier mapping. */
|
|
20
|
+
export type SliceKind =
|
|
21
|
+
| 'review'
|
|
22
|
+
| 'security'
|
|
23
|
+
| 'tests'
|
|
24
|
+
| 'bug-hunt'
|
|
25
|
+
| 'research'
|
|
26
|
+
| 'docs'
|
|
27
|
+
| 'refactor'
|
|
28
|
+
| 'plan';
|
|
29
|
+
|
|
30
|
+
interface KindProfile {
|
|
31
|
+
/** Reuse a `judge-*` skill as a ready-made subagent role-profile, or null. */
|
|
32
|
+
judge_lens: string | null;
|
|
33
|
+
role_mode: RoleMode | null;
|
|
34
|
+
tier: Tier;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Slice-kind → bundle profile. The `judge-*` skills are reused as subagent
|
|
39
|
+
* role-profiles (the package already ships them as review lenses).
|
|
40
|
+
*/
|
|
41
|
+
const KIND_PROFILE: Record<SliceKind, KindProfile> = {
|
|
42
|
+
review: { judge_lens: 'judge-code-quality', role_mode: 'reviewer', tier: 'medium' },
|
|
43
|
+
security: { judge_lens: 'judge-security-auditor', role_mode: 'reviewer', tier: 'high' },
|
|
44
|
+
tests: { judge_lens: 'judge-test-coverage', role_mode: 'tester', tier: 'medium' },
|
|
45
|
+
'bug-hunt': { judge_lens: 'judge-bug-hunter', role_mode: 'developer', tier: 'medium' },
|
|
46
|
+
research: { judge_lens: null, role_mode: null, tier: 'lite' },
|
|
47
|
+
docs: { judge_lens: null, role_mode: null, tier: 'lite' },
|
|
48
|
+
refactor: { judge_lens: null, role_mode: 'developer', tier: 'high' },
|
|
49
|
+
plan: { judge_lens: null, role_mode: 'planner', tier: 'high' },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** A knowledge-card ref tagged with its sharing tier (ADR-100). */
|
|
53
|
+
export interface KnowledgeRef {
|
|
54
|
+
ref: string;
|
|
55
|
+
tier: 'public' | 'vendor' | 'proprietary';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SliceInput {
|
|
59
|
+
kind: SliceKind;
|
|
60
|
+
task: string;
|
|
61
|
+
/** Active profile id (e.g. 'developer'), inherited from the session unless overridden. */
|
|
62
|
+
profile?: string | null;
|
|
63
|
+
/** Persona ids cited by the task's skill. */
|
|
64
|
+
personas?: string[];
|
|
65
|
+
/** Candidate knowledge-card refs (tier-tagged) for this slice. */
|
|
66
|
+
knowledge?: KnowledgeRef[];
|
|
67
|
+
/** Will this bundle be visible across project boundaries? */
|
|
68
|
+
cross_project?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ResolvedBundle {
|
|
72
|
+
selection: SpawnSelection;
|
|
73
|
+
judge_lens: string | null;
|
|
74
|
+
tier: Tier;
|
|
75
|
+
dropped_proprietary: number;
|
|
76
|
+
reason: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* ADR-100 guard: keep refs whose tier is shareable in the bundle's visibility
|
|
81
|
+
* scope. In a cross-project bundle, `proprietary` refs are dropped.
|
|
82
|
+
*/
|
|
83
|
+
export function filterKnowledgeByPolicy(
|
|
84
|
+
knowledge: KnowledgeRef[],
|
|
85
|
+
cross_project: boolean,
|
|
86
|
+
): { kept: string[]; dropped: number } {
|
|
87
|
+
const kept: string[] = [];
|
|
88
|
+
let dropped = 0;
|
|
89
|
+
for (const k of knowledge) {
|
|
90
|
+
if (cross_project && k.tier === 'proprietary') {
|
|
91
|
+
dropped += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
kept.push(k.ref);
|
|
95
|
+
}
|
|
96
|
+
return { kept, dropped };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a task slice into a subagent bundle: role-mode + reused judge lens +
|
|
101
|
+
* personas + ADR-100-filtered knowledge refs + tier. The result's `selection`
|
|
102
|
+
* is ready for `composeSpawnBrief`.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveBundle(slice: SliceInput): ResolvedBundle {
|
|
105
|
+
const profile = KIND_PROFILE[slice.kind];
|
|
106
|
+
const cross = slice.cross_project === true;
|
|
107
|
+
const { kept, dropped } = filterKnowledgeByPolicy(slice.knowledge ?? [], cross);
|
|
108
|
+
|
|
109
|
+
// The reused judge lens rides as a persona-equivalent so the subagent loads it.
|
|
110
|
+
const personas = [...(slice.personas ?? [])];
|
|
111
|
+
if (profile.judge_lens && !personas.includes(profile.judge_lens)) {
|
|
112
|
+
personas.unshift(profile.judge_lens);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const selection: SpawnSelection = {
|
|
116
|
+
task: slice.task,
|
|
117
|
+
role_mode: profile.role_mode,
|
|
118
|
+
profile: slice.profile ?? null,
|
|
119
|
+
personas,
|
|
120
|
+
knowledge_refs: kept,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const reasonParts = [`kind=${slice.kind}`, `tier=${profile.tier}`];
|
|
124
|
+
if (profile.judge_lens) reasonParts.push(`lens=${profile.judge_lens}`);
|
|
125
|
+
if (dropped > 0) reasonParts.push(`dropped ${dropped} proprietary (cross-project)`);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
selection,
|
|
129
|
+
judge_lens: profile.judge_lens,
|
|
130
|
+
tier: profile.tier,
|
|
131
|
+
dropped_proprietary: dropped,
|
|
132
|
+
reason: reasonParts.join(' · '),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Auditable bundle signature for the orchestration-telemetry object — counts +
|
|
138
|
+
* ids only, never bodies (preserves the audit-log privacy floor).
|
|
139
|
+
*/
|
|
140
|
+
export function bundleAuditLine(b: ResolvedBundle): {
|
|
141
|
+
role_mode: RoleMode | null;
|
|
142
|
+
judge_lens: string | null;
|
|
143
|
+
tier: Tier;
|
|
144
|
+
knowledge_ref_count: number;
|
|
145
|
+
dropped_proprietary: number;
|
|
146
|
+
} {
|
|
147
|
+
return {
|
|
148
|
+
role_mode: b.selection.role_mode ?? null,
|
|
149
|
+
judge_lens: b.judge_lens,
|
|
150
|
+
tier: b.tier,
|
|
151
|
+
knowledge_ref_count: b.selection.knowledge_refs?.length ?? 0,
|
|
152
|
+
dropped_proprietary: b.dropped_proprietary,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured subagent-response envelope + synthesis helpers (Phase 3 / A3).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. Pins the body a subagent returns inside the 4-status envelope
|
|
5
|
+
* (the status stays the envelope; this is the body) and gives the orchestrator
|
|
6
|
+
* the synthesis duties: surface evidence gaps, and force the full cross-model
|
|
7
|
+
* judge when a mutating finding is low-confidence.
|
|
8
|
+
*
|
|
9
|
+
* Contract: `src/agent-src/contexts/execution/subagent-response-contract.md`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type Confidence = 'low' | 'medium' | 'high';
|
|
13
|
+
|
|
14
|
+
export interface Finding {
|
|
15
|
+
title: string;
|
|
16
|
+
/** Refs (file:line / id / path) backing this finding — never inline bodies. */
|
|
17
|
+
evidence_refs?: string[];
|
|
18
|
+
/** Does acting on this finding mutate files / state? */
|
|
19
|
+
mutating?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SubagentResponse {
|
|
23
|
+
summary: string;
|
|
24
|
+
findings: Finding[];
|
|
25
|
+
risks: string[];
|
|
26
|
+
confidence: Confidence;
|
|
27
|
+
handoff: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const CONFIDENCE: ReadonlySet<string> = new Set<Confidence>(['low', 'medium', 'high']);
|
|
31
|
+
|
|
32
|
+
export interface ValidationResult {
|
|
33
|
+
valid: boolean;
|
|
34
|
+
errors: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Validate the envelope shape. Counts/refs only — reject inline-body evidence. */
|
|
38
|
+
export function validateResponse(input: unknown): ValidationResult {
|
|
39
|
+
const errors: string[] = [];
|
|
40
|
+
if (input === null || typeof input !== 'object') {
|
|
41
|
+
return { valid: false, errors: ['response is not an object'] };
|
|
42
|
+
}
|
|
43
|
+
const r = input as Record<string, unknown>;
|
|
44
|
+
if (typeof r.summary !== 'string') errors.push('summary missing or not a string');
|
|
45
|
+
if (typeof r.handoff !== 'string') errors.push('handoff missing or not a string');
|
|
46
|
+
if (!CONFIDENCE.has(r.confidence as string)) errors.push(`confidence must be low|medium|high (got ${String(r.confidence)})`);
|
|
47
|
+
if (!Array.isArray(r.findings)) errors.push('findings missing or not an array');
|
|
48
|
+
if (!Array.isArray(r.risks)) errors.push('risks missing or not an array');
|
|
49
|
+
if (Array.isArray(r.findings)) {
|
|
50
|
+
for (const [i, f] of (r.findings as Finding[]).entries()) {
|
|
51
|
+
if (!f || typeof f.title !== 'string') errors.push(`finding[${i}] missing title`);
|
|
52
|
+
const refs = f?.evidence_refs;
|
|
53
|
+
if (refs && refs.some((x) => typeof x !== 'string' || x.includes('\n'))) {
|
|
54
|
+
errors.push(`finding[${i}] evidence_refs must be ref tokens, not bodies`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { valid: errors.length === 0, errors };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Synthesis gaps the orchestrator must resolve before adopting the response:
|
|
63
|
+
* a finding with no evidence ref is an unbacked claim to re-check.
|
|
64
|
+
*/
|
|
65
|
+
export function synthesisGaps(response: SubagentResponse): string[] {
|
|
66
|
+
const gaps: string[] = [];
|
|
67
|
+
for (const f of response.findings) {
|
|
68
|
+
if (!f.evidence_refs || f.evidence_refs.length === 0) {
|
|
69
|
+
gaps.push(`finding "${f.title}" has no evidence — re-check before adopting`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return gaps;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Confidence → verify-budget link. A mutating finding returned at `low`
|
|
77
|
+
* confidence forces the full cross-model judge (no deterministic-only pass).
|
|
78
|
+
* Returns true when the full judge is mandatory for this finding.
|
|
79
|
+
*/
|
|
80
|
+
export function forcesJudge(f: Finding, confidence: Confidence): boolean {
|
|
81
|
+
return f.mutating === true && confidence === 'low';
|
|
82
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent routing (Phase 2) — cost/speed downshift + optional quota arbitrage.
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O, VENDOR-NEUTRAL. Resolves which model a delegated sub-task runs
|
|
5
|
+
* on, from the sub-task's declared capability tier, the `subagents.*` settings,
|
|
6
|
+
* and the host-capability manifest. Contract:
|
|
7
|
+
* `src/agent-src/contexts/execution/subagent-routing.md`.
|
|
8
|
+
*
|
|
9
|
+
* Invariants:
|
|
10
|
+
* - The orchestrator stays on the session/high tier (this resolves SUB-tasks).
|
|
11
|
+
* - Downshift off → every sub-task runs on the session tier.
|
|
12
|
+
* - Downshift on → a sub-task runs on the lowest-capable tier it declares.
|
|
13
|
+
* - Quota arbitrage is an OPTIONAL bonus: it only prefers the separate pool
|
|
14
|
+
* when BOTH the setting and the host manifest allow it. Where unsupported,
|
|
15
|
+
* routing is identical minus the quota win — never a hard-coded vendor model.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type Tier = 'lite' | 'medium' | 'high' | 'inherit';
|
|
19
|
+
export type QuotaPool = 'separate' | 'shared';
|
|
20
|
+
|
|
21
|
+
/** Per-tier model alias overrides (empty string = use the tier's runtime default). */
|
|
22
|
+
export interface ModelMap {
|
|
23
|
+
lite?: string;
|
|
24
|
+
medium?: string;
|
|
25
|
+
high?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RoutingInputs {
|
|
29
|
+
/** The sub-task's declared capability tier. */
|
|
30
|
+
task_tier: Tier;
|
|
31
|
+
/** The orchestrator's session tier (used when downshift is off / tier is inherit). */
|
|
32
|
+
session_tier: Tier;
|
|
33
|
+
downshift: boolean;
|
|
34
|
+
quota_arbitrage: boolean;
|
|
35
|
+
model_map: ModelMap;
|
|
36
|
+
/** From the host-capability manifest. */
|
|
37
|
+
separate_quota_pool: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RoutingDecision {
|
|
41
|
+
/** Resolved tier the sub-task runs on. */
|
|
42
|
+
tier: Tier;
|
|
43
|
+
/** Model alias, or '' meaning "use the tier's runtime default" (no vendor name baked in). */
|
|
44
|
+
model: string;
|
|
45
|
+
/** Whether a separate quota pool is used (bonus) or the shared one. */
|
|
46
|
+
quota_pool: QuotaPool;
|
|
47
|
+
reason: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Resolve a model alias for a tier from the map; '' = tier default sentinel. */
|
|
51
|
+
function modelForTier(tier: Tier, map: ModelMap): string {
|
|
52
|
+
if (tier === 'lite') return map.lite ?? '';
|
|
53
|
+
if (tier === 'medium') return map.medium ?? '';
|
|
54
|
+
if (tier === 'high') return map.high ?? '';
|
|
55
|
+
return ''; // inherit → session default
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve routing for one delegated sub-task. Vendor-neutral: returns a tier +
|
|
60
|
+
* an optional model alias (never a hard-coded provider model) + the quota pool.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveSubagentRouting(inp: RoutingInputs): RoutingDecision {
|
|
63
|
+
// Downshift off, or an `inherit` task → run on the session tier.
|
|
64
|
+
const tier: Tier = !inp.downshift || inp.task_tier === 'inherit' ? inp.session_tier : inp.task_tier;
|
|
65
|
+
const downshifted = inp.downshift && inp.task_tier !== 'inherit' && tier !== inp.session_tier;
|
|
66
|
+
|
|
67
|
+
// Quota arbitrage: a bonus, only when BOTH the setting and the host allow it.
|
|
68
|
+
const useSeparate = inp.quota_arbitrage && inp.separate_quota_pool;
|
|
69
|
+
const quota_pool: QuotaPool = useSeparate ? 'separate' : 'shared';
|
|
70
|
+
|
|
71
|
+
const reasonParts: string[] = [];
|
|
72
|
+
reasonParts.push(downshifted ? `downshift to ${tier}` : `session tier ${tier}`);
|
|
73
|
+
reasonParts.push(useSeparate ? 'separate quota pool (bonus)' : 'shared quota pool');
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
tier,
|
|
77
|
+
model: modelForTier(tier, inp.model_map),
|
|
78
|
+
quota_pool,
|
|
79
|
+
reason: reasonParts.join(' · '),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent spawn-brief composition (Phase 3) — task-optimal configuration.
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. Composes a subagent's brief from the existing config seams —
|
|
5
|
+
* role-mode + profile + persona(s) + a MINIMAL knowledge slice — so each
|
|
6
|
+
* subagent is configured for its task. Contract:
|
|
7
|
+
* `src/agent-src/contexts/execution/subagent-spawn-contract.md`.
|
|
8
|
+
*
|
|
9
|
+
* Hard invariant (lethal-trifecta-guard): knowledge is passed as a small set of
|
|
10
|
+
* REFERENCES, never as inline bodies, and is capped. Never bulk-dump context
|
|
11
|
+
* into a subagent — keep the private-data leg narrow.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type RoleMode = 'developer' | 'reviewer' | 'tester' | 'po' | 'incident' | 'planner';
|
|
15
|
+
|
|
16
|
+
const ROLE_MODES: ReadonlySet<string> = new Set<RoleMode>([
|
|
17
|
+
'developer',
|
|
18
|
+
'reviewer',
|
|
19
|
+
'tester',
|
|
20
|
+
'po',
|
|
21
|
+
'incident',
|
|
22
|
+
'planner',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** Max knowledge slice refs handed to one subagent (minimal-slice cap). */
|
|
26
|
+
export const MAX_KNOWLEDGE_REFS = 5;
|
|
27
|
+
/** Max personas overlaid on one subagent. */
|
|
28
|
+
export const MAX_PERSONAS = 2;
|
|
29
|
+
|
|
30
|
+
export interface SpawnSelection {
|
|
31
|
+
task: string;
|
|
32
|
+
/** Role-mode contract the subagent runs under, or null for none. */
|
|
33
|
+
role_mode?: RoleMode | null;
|
|
34
|
+
/** Active profile id (e.g. 'developer', 'content_creator'), or null. */
|
|
35
|
+
profile?: string | null;
|
|
36
|
+
/** Persona ids (review lenses) cited by the task's skill, capped. */
|
|
37
|
+
personas?: string[];
|
|
38
|
+
/** Knowledge slice REFERENCES (ids / paths) — never inline bodies. */
|
|
39
|
+
knowledge_refs?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SpawnBrief {
|
|
43
|
+
task: string;
|
|
44
|
+
role_mode: RoleMode | null;
|
|
45
|
+
profile: string | null;
|
|
46
|
+
personas: string[];
|
|
47
|
+
knowledge_refs: string[];
|
|
48
|
+
warnings: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A ref is acceptable only if it looks like an id/path token, not a body. */
|
|
52
|
+
function isRefLike(s: unknown): s is string {
|
|
53
|
+
return typeof s === 'string' && s.length > 0 && s.length <= 200 && !s.includes('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compose a validated spawn brief. Invalid role-mode → dropped to null;
|
|
58
|
+
* personas capped at {@link MAX_PERSONAS}; knowledge refs filtered to
|
|
59
|
+
* ref-like tokens and capped at {@link MAX_KNOWLEDGE_REFS}. Anything dropped is
|
|
60
|
+
* recorded in `warnings` (surfaced, never silent).
|
|
61
|
+
*/
|
|
62
|
+
export function composeSpawnBrief(sel: SpawnSelection): SpawnBrief {
|
|
63
|
+
const warnings: string[] = [];
|
|
64
|
+
|
|
65
|
+
let role_mode: RoleMode | null = null;
|
|
66
|
+
if (sel.role_mode != null) {
|
|
67
|
+
if (ROLE_MODES.has(sel.role_mode)) role_mode = sel.role_mode;
|
|
68
|
+
else warnings.push(`unknown role_mode '${sel.role_mode}' dropped`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const personasIn = sel.personas ?? [];
|
|
72
|
+
let personas = personasIn.filter((p) => typeof p === 'string' && p.length > 0);
|
|
73
|
+
if (personas.length > MAX_PERSONAS) {
|
|
74
|
+
warnings.push(`personas capped ${personas.length} → ${MAX_PERSONAS}`);
|
|
75
|
+
personas = personas.slice(0, MAX_PERSONAS);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const refsIn = sel.knowledge_refs ?? [];
|
|
79
|
+
const bodyLike = refsIn.filter((r) => !isRefLike(r));
|
|
80
|
+
if (bodyLike.length > 0) {
|
|
81
|
+
warnings.push(`${bodyLike.length} non-ref knowledge entr${bodyLike.length === 1 ? 'y' : 'ies'} rejected (refs only, no bodies)`);
|
|
82
|
+
}
|
|
83
|
+
let knowledge_refs = refsIn.filter(isRefLike);
|
|
84
|
+
if (knowledge_refs.length > MAX_KNOWLEDGE_REFS) {
|
|
85
|
+
warnings.push(`knowledge refs capped ${knowledge_refs.length} → ${MAX_KNOWLEDGE_REFS}`);
|
|
86
|
+
knowledge_refs = knowledge_refs.slice(0, MAX_KNOWLEDGE_REFS);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
task: sel.task,
|
|
91
|
+
role_mode,
|
|
92
|
+
profile: sel.profile ?? null,
|
|
93
|
+
personas,
|
|
94
|
+
knowledge_refs,
|
|
95
|
+
warnings,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent steering & guardrails (Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. The guardrails that keep auto-dispatch from becoming a token
|
|
5
|
+
* sink: the N=3 per-target budget, the kill-switch, and the rollback
|
|
6
|
+
* guardrail-threshold checks. Contract:
|
|
7
|
+
* `src/agent-src/contexts/execution/subagent-steering.md`.
|
|
8
|
+
*
|
|
9
|
+
* Automatic cohort-level disable is OUT OF SCOPE — a config package runs no
|
|
10
|
+
* daemon. Breaches are surfaced for the maintainer/user to act on; the only
|
|
11
|
+
* automatic stop is the per-target N=3 budget.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Max consecutive failed attempts on one validation target before halting. */
|
|
15
|
+
export const MAX_ATTEMPTS_PER_TARGET = 3;
|
|
16
|
+
|
|
17
|
+
/** Rollback guardrail thresholds (council-defined). */
|
|
18
|
+
export const GUARDRAILS = {
|
|
19
|
+
/** Token spend vs single-agent baseline. */
|
|
20
|
+
token_blowup_ratio: 2,
|
|
21
|
+
/** Subagent spawn-failure rate. */
|
|
22
|
+
spawn_failure_rate: 0.1,
|
|
23
|
+
/** Fraction of dispatches that completed without a required verification. */
|
|
24
|
+
verify_skip_rate: 0.01,
|
|
25
|
+
/** Fraction of users manually disabling the layer. */
|
|
26
|
+
user_override_rate: 0.3,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export interface LayerState {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
auto: 'off' | 'ask' | 'on';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The kill-switch: layer is disabled when the master switch is off or auto is off. */
|
|
35
|
+
export function isLayerDisabled(state: LayerState): boolean {
|
|
36
|
+
return !state.enabled || state.auto === 'off';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** N=3 per-target budget: halt once consecutive failed attempts reach the cap. */
|
|
40
|
+
export function budgetHalt(consecutiveFailures: number): boolean {
|
|
41
|
+
return consecutiveFailures >= MAX_ATTEMPTS_PER_TARGET;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface GuardrailMetrics {
|
|
45
|
+
/** Observed token spend / single-agent baseline (e.g. 2.4 = 2.4x). */
|
|
46
|
+
token_ratio: number;
|
|
47
|
+
spawn_failure_rate: number;
|
|
48
|
+
verify_skip_rate: number;
|
|
49
|
+
user_override_rate: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Evaluate the rollback guardrails. Returns the list of breached threshold
|
|
54
|
+
* names (empty = all clear). The orchestrator surfaces a non-empty result —
|
|
55
|
+
* it does NOT auto-disable (no daemon); the maintainer/user decides.
|
|
56
|
+
*/
|
|
57
|
+
export function breachedGuardrails(m: GuardrailMetrics): string[] {
|
|
58
|
+
const breached: string[] = [];
|
|
59
|
+
if (m.token_ratio > GUARDRAILS.token_blowup_ratio) breached.push('token_blowup');
|
|
60
|
+
if (m.spawn_failure_rate > GUARDRAILS.spawn_failure_rate) breached.push('spawn_failure');
|
|
61
|
+
if (m.verify_skip_rate > GUARDRAILS.verify_skip_rate) breached.push('verify_skip');
|
|
62
|
+
if (m.user_override_rate > GUARDRAILS.user_override_rate) breached.push('user_override');
|
|
63
|
+
return breached;
|
|
64
|
+
}
|