ai-runtime-engine 1.3.0 → 2.8.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/CHANGELOG.md +638 -0
- package/dist/agents/admit.d.ts +69 -0
- package/dist/agents/admit.js +129 -0
- package/dist/agents/definition.d.ts +36 -0
- package/dist/agents/definition.js +9 -0
- package/dist/agents/envelope.d.ts +53 -0
- package/dist/agents/envelope.js +68 -0
- package/dist/agents/finding.d.ts +79 -0
- package/dist/agents/finding.js +80 -0
- package/dist/agents/roles.d.ts +36 -0
- package/dist/agents/roles.js +44 -0
- package/dist/agents/synthesize.d.ts +44 -0
- package/dist/agents/synthesize.js +60 -0
- package/dist/agents/task.d.ts +112 -0
- package/dist/agents/task.js +48 -0
- package/dist/agents/worker.d.ts +91 -0
- package/dist/agents/worker.js +377 -0
- package/dist/capabilities/capability.d.ts +117 -0
- package/dist/capabilities/capability.js +66 -0
- package/dist/capabilities/registry.d.ts +139 -0
- package/dist/capabilities/registry.js +413 -0
- package/dist/capabilities/vocabulary.d.ts +32 -0
- package/dist/capabilities/vocabulary.js +34 -0
- package/dist/cli/cli.js +55 -4
- package/dist/cli/commands/cleanup.js +29 -27
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +38 -8
- package/dist/cli/commands/executions.js +34 -25
- package/dist/cli/commands/info.d.ts +1 -0
- package/dist/cli/commands/info.js +11 -9
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/inspect.d.ts +40 -1
- package/dist/cli/commands/inspect.js +157 -2
- package/dist/cli/commands/mcp.d.ts +45 -0
- package/dist/cli/commands/mcp.js +148 -0
- package/dist/cli/commands/route.js +21 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +21 -2
- package/dist/cli/commands/skills.d.ts +2 -0
- package/dist/cli/commands/skills.js +29 -7
- package/dist/cli/interactive/ansi.d.ts +41 -0
- package/dist/cli/interactive/ansi.js +43 -0
- package/dist/cli/interactive/complete.d.ts +10 -0
- package/dist/cli/interactive/complete.js +19 -0
- package/dist/cli/interactive/lanes.d.ts +69 -0
- package/dist/cli/interactive/lanes.js +181 -0
- package/dist/cli/interactive/repl.d.ts +3 -0
- package/dist/cli/interactive/repl.js +91 -13
- package/dist/cli/interactive/session.d.ts +8 -0
- package/dist/cli/interactive/session.js +73 -2
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +10 -0
- package/dist/cli/runtimeSession.d.ts +11 -0
- package/dist/cli/runtimeSession.js +17 -0
- package/dist/config/defaults.d.ts +3 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +2 -2
- package/dist/context/lossVerifier.d.ts +24 -0
- package/dist/context/lossVerifier.js +45 -0
- package/dist/context/summarize.d.ts +19 -0
- package/dist/context/summarize.js +53 -0
- package/dist/core/fallback/fallback.d.ts +5 -0
- package/dist/core/fallback/fallback.js +3 -1
- package/dist/core/router/router.d.ts +3 -0
- package/dist/core/router/router.js +1 -0
- package/dist/executions/agentTasks.d.ts +627 -0
- package/dist/executions/agentTasks.js +149 -0
- package/dist/executions/checkpoint.d.ts +5 -1
- package/dist/executions/checkpoint.js +13 -1
- package/dist/executions/execution.d.ts +36 -2
- package/dist/executions/store.d.ts +37 -0
- package/dist/executions/store.js +33 -0
- package/dist/generation/generateAdapter.d.ts +14 -0
- package/dist/generation/generateAdapter.js +38 -0
- package/dist/generation/generateSkill.d.ts +26 -0
- package/dist/generation/generateSkill.js +51 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.js +33 -2
- package/dist/mcp/client.d.ts +70 -0
- package/dist/mcp/client.js +221 -0
- package/dist/mcp/manager.d.ts +151 -0
- package/dist/mcp/manager.js +493 -0
- package/dist/mcp/protocol.d.ts +216 -0
- package/dist/mcp/protocol.js +149 -0
- package/dist/mcp/toolAdapter.d.ts +44 -0
- package/dist/mcp/toolAdapter.js +94 -0
- package/dist/mcp/transport.d.ts +109 -0
- package/dist/mcp/transport.js +383 -0
- package/dist/memory/embedders/hash.d.ts +12 -0
- package/dist/memory/embedders/hash.js +31 -0
- package/dist/memory/embedders/http.d.ts +25 -0
- package/dist/memory/embedders/http.js +48 -0
- package/dist/memory/memory.d.ts +19 -2
- package/dist/memory/memory.js +75 -11
- package/dist/memory/semantic.d.ts +17 -0
- package/dist/memory/semantic.js +29 -0
- package/dist/orchestration/budget.d.ts +30 -0
- package/dist/orchestration/budget.js +40 -0
- package/dist/orchestration/executor.d.ts +64 -1
- package/dist/orchestration/executor.js +104 -7
- package/dist/orchestration/orchestrator.d.ts +35 -1
- package/dist/orchestration/orchestrator.js +106 -8
- package/dist/orchestration/plan.d.ts +15 -1
- package/dist/orchestration/plan.js +23 -4
- package/dist/orchestration/planner.d.ts +19 -1
- package/dist/orchestration/planner.js +25 -5
- package/dist/plugin/ai.d.ts +4 -0
- package/dist/plugin/ai.js +9 -0
- package/dist/runtime/config.js +50 -6
- package/dist/runtime/intent/aiClassifier.d.ts +19 -0
- package/dist/runtime/intent/aiClassifier.js +74 -0
- package/dist/runtime/models/modelProfile.d.ts +61 -0
- package/dist/runtime/models/modelProfile.js +139 -0
- package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
- package/dist/runtime/planning/deriveCapabilities.js +146 -0
- package/dist/runtime/policy.d.ts +10 -0
- package/dist/runtime/policy.js +9 -2
- package/dist/runtime/runtime.d.ts +233 -0
- package/dist/runtime/runtime.js +1042 -60
- package/dist/runtime/types.d.ts +88 -2
- package/dist/security/redact.js +22 -10
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +24 -0
- package/dist/skills/registry.d.ts +16 -1
- package/dist/skills/registry.js +21 -1
- package/dist/skills/skill.d.ts +6 -1
- package/dist/store/area.d.ts +15 -1
- package/dist/store/area.js +19 -8
- package/dist/store/crypto.d.ts +21 -0
- package/dist/store/crypto.js +49 -0
- package/dist/store/paths.d.ts +5 -1
- package/dist/store/paths.js +6 -0
- package/dist/store/store.d.ts +15 -3
- package/dist/store/store.js +28 -7
- package/dist/telemetry/sinks/otlp.d.ts +31 -0
- package/dist/telemetry/sinks/otlp.js +76 -0
- package/dist/tools/builtins/filesystem.js +1 -0
- package/dist/tools/builtins/git.js +1 -0
- package/dist/tools/builtins/shell.js +1 -0
- package/dist/tools/permissions.d.ts +28 -0
- package/dist/tools/permissions.js +72 -0
- package/dist/tools/registry.d.ts +18 -2
- package/dist/tools/registry.js +22 -2
- package/dist/tools/tool.d.ts +4 -0
- package/dist/types.d.ts +5 -1
- package/dist/util/flatten.d.ts +11 -0
- package/dist/util/flatten.js +18 -0
- package/dist/util/hash.d.ts +19 -0
- package/dist/util/hash.js +39 -0
- package/dist/util/semaphore.d.ts +19 -0
- package/dist/util/semaphore.js +60 -0
- package/package.json +24 -9
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Finding ADMISSION PIPELINE (Phase 3.4) - the only way a Finding enters persistent or inter-agent
|
|
3
|
+
* state. Arbitrary agent output NEVER becomes a Finding.
|
|
4
|
+
*
|
|
5
|
+
* FOUR STAGES, in order, each with one rejection reason: extract -> output-contract validate ->
|
|
6
|
+
* Finding validate -> persist. A rejected candidate becomes a DIAGNOSTIC on the task record and is
|
|
7
|
+
* never threaded: not onto the observation, not into a prompt, not into a RuntimeResult.
|
|
8
|
+
*
|
|
9
|
+
* Findings carry no secret by construction: every string is a clamped derivative of tool/skill output.
|
|
10
|
+
* That clamp is the ONLY defense for a persisted finding - `src/security/redact.ts` covers telemetry and
|
|
11
|
+
* CLI output, not the execution store.
|
|
12
|
+
*/
|
|
13
|
+
import type { Finding, FindingEvidence } from './finding.js';
|
|
14
|
+
import type { AgentDefinition } from './definition.js';
|
|
15
|
+
import type { ArtifactRef } from '../runtime/types.js';
|
|
16
|
+
/** Clamp bounds. Each is a prompt- and record-facing string. */
|
|
17
|
+
export declare const TYPE_MAX = 40;
|
|
18
|
+
export declare const SUBJECT_MAX = 80;
|
|
19
|
+
export declare const CLAIM_MAX = 240;
|
|
20
|
+
export declare const VERDICT_MAX = 80;
|
|
21
|
+
/** Observations are persisted verbatim onto the Execution, so what rides along is bounded. */
|
|
22
|
+
export declare const FINDING_THREAD_MAX = 5;
|
|
23
|
+
/** The contract default when a definition declares one without a count. */
|
|
24
|
+
export declare const DEFAULT_MAX_FINDINGS = 10;
|
|
25
|
+
export interface AdmissionRejection {
|
|
26
|
+
index: number;
|
|
27
|
+
stage: 'extract' | 'output-contract' | 'finding-validate' | 'persist';
|
|
28
|
+
why: string;
|
|
29
|
+
}
|
|
30
|
+
export interface AdmissionResult {
|
|
31
|
+
admitted: Finding[];
|
|
32
|
+
rejected: AdmissionRejection[];
|
|
33
|
+
/** True when a DECLARED output contract was violated - the input to the task-failure rule. */
|
|
34
|
+
contractViolated: boolean;
|
|
35
|
+
/** Whether a contract was declared at all. Recorded HERE so `contractFailed` needs only this result:
|
|
36
|
+
* taking the contract twice would let a caller check the rule against a contract the pipeline never
|
|
37
|
+
* actually applied, and quietly get the wrong answer. */
|
|
38
|
+
contractDeclared: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface AdmitFindingsInput {
|
|
41
|
+
agentTaskId: string;
|
|
42
|
+
agentId: string;
|
|
43
|
+
/** Raw `data.findings` from the final inner step. Untrusted shape: anything at all. */
|
|
44
|
+
candidates: unknown;
|
|
45
|
+
contract?: AgentDefinition['outputContract'];
|
|
46
|
+
/** Deterministic execution coverage, injected - never computed from the candidates. */
|
|
47
|
+
coverage: number;
|
|
48
|
+
/** `SkillResult.data.confidence` from the final inner step, if it offered one. */
|
|
49
|
+
confidenceOverride?: unknown;
|
|
50
|
+
evidenceFor: (index: number) => FindingEvidence[];
|
|
51
|
+
artifactsFor: (index: number) => {
|
|
52
|
+
refs: ArtifactRef[];
|
|
53
|
+
unavailable: boolean;
|
|
54
|
+
};
|
|
55
|
+
sourceSteps: string[];
|
|
56
|
+
provenance: {
|
|
57
|
+
executionId?: string;
|
|
58
|
+
planVersion: number;
|
|
59
|
+
};
|
|
60
|
+
now: number;
|
|
61
|
+
}
|
|
62
|
+
export declare function admitFindings(input: AdmitFindingsInput): AdmissionResult;
|
|
63
|
+
/**
|
|
64
|
+
* THE TASK-FAILURE RULE. An agent task fails on its findings ONLY when the definition declared an
|
|
65
|
+
* output contract: then a violation, or admitting nothing at all, is a contract failure. With no
|
|
66
|
+
* declared contract findings are best-effort - rejections are diagnostics and the step's success is
|
|
67
|
+
* decided by its inner plan alone.
|
|
68
|
+
*/
|
|
69
|
+
export declare function contractFailed(result: AdmissionResult): boolean;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Finding ADMISSION PIPELINE (Phase 3.4) - the only way a Finding enters persistent or inter-agent
|
|
3
|
+
* state. Arbitrary agent output NEVER becomes a Finding.
|
|
4
|
+
*
|
|
5
|
+
* FOUR STAGES, in order, each with one rejection reason: extract -> output-contract validate ->
|
|
6
|
+
* Finding validate -> persist. A rejected candidate becomes a DIAGNOSTIC on the task record and is
|
|
7
|
+
* never threaded: not onto the observation, not into a prompt, not into a RuntimeResult.
|
|
8
|
+
*
|
|
9
|
+
* Findings carry no secret by construction: every string is a clamped derivative of tool/skill output.
|
|
10
|
+
* That clamp is the ONLY defense for a persisted finding - `src/security/redact.ts` covers telemetry and
|
|
11
|
+
* CLI output, not the execution store.
|
|
12
|
+
*/
|
|
13
|
+
import { flattenClamp } from '../util/flatten.js';
|
|
14
|
+
import { confidenceOf } from './finding.js';
|
|
15
|
+
/** Clamp bounds. Each is a prompt- and record-facing string. */
|
|
16
|
+
export const TYPE_MAX = 40;
|
|
17
|
+
export const SUBJECT_MAX = 80;
|
|
18
|
+
export const CLAIM_MAX = 240;
|
|
19
|
+
export const VERDICT_MAX = 80;
|
|
20
|
+
/** Observations are persisted verbatim onto the Execution, so what rides along is bounded. */
|
|
21
|
+
export const FINDING_THREAD_MAX = 5;
|
|
22
|
+
/** The contract default when a definition declares one without a count. */
|
|
23
|
+
export const DEFAULT_MAX_FINDINGS = 10;
|
|
24
|
+
export function admitFindings(input) {
|
|
25
|
+
const admitted = [];
|
|
26
|
+
const rejected = [];
|
|
27
|
+
let contractViolated = false;
|
|
28
|
+
// STAGE 1 - EXTRACT. The shape is whatever a skill happened to return.
|
|
29
|
+
if (!Array.isArray(input.candidates)) {
|
|
30
|
+
return { admitted, rejected: [{ index: -1, stage: 'extract', why: 'data.findings is not an array' }], contractViolated: input.contract !== undefined, contractDeclared: input.contract !== undefined };
|
|
31
|
+
}
|
|
32
|
+
const maxFindings = input.contract ? input.contract.maxFindings ?? DEFAULT_MAX_FINDINGS : Number.POSITIVE_INFINITY;
|
|
33
|
+
input.candidates.forEach((raw, index) => {
|
|
34
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
35
|
+
rejected.push({ index, stage: 'extract', why: 'candidate is not an object' });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const c = raw;
|
|
39
|
+
if (typeof c.type !== 'string' || !c.type.trim()) {
|
|
40
|
+
rejected.push({ index, stage: 'extract', why: 'missing type' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof c.claim !== 'string' || !c.claim.trim()) {
|
|
44
|
+
rejected.push({ index, stage: 'extract', why: 'missing claim' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// STAGE 2 - OUTPUT-CONTRACT VALIDATE. Only when the definition declared one.
|
|
48
|
+
if (input.contract) {
|
|
49
|
+
if (!input.contract.types.includes(c.type)) {
|
|
50
|
+
rejected.push({ index, stage: 'output-contract', why: `type '${flattenClamp(c.type, TYPE_MAX)}' is not in the declared output contract` });
|
|
51
|
+
contractViolated = true;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (admitted.length >= maxFindings) {
|
|
55
|
+
rejected.push({ index, stage: 'output-contract', why: 'exceeds maxFindings' });
|
|
56
|
+
contractViolated = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (input.contract.requireSubject && (typeof c.subject !== 'string' || !c.subject.trim())) {
|
|
60
|
+
rejected.push({ index, stage: 'output-contract', why: 'the contract requires a subject' });
|
|
61
|
+
contractViolated = true;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// STAGE 3 - FINDING VALIDATE. Build it fully, clamp every string, then assert the invariants.
|
|
66
|
+
const evidence = input.evidenceFor(index);
|
|
67
|
+
const artifacts = input.artifactsFor(index);
|
|
68
|
+
const confidence = confidenceOf(evidence, input.confidenceOverride);
|
|
69
|
+
const finding = {
|
|
70
|
+
id: `${input.agentTaskId}_f${admitted.length + 1}`,
|
|
71
|
+
agentId: input.agentId,
|
|
72
|
+
agentTaskId: input.agentTaskId,
|
|
73
|
+
type: flattenClamp(c.type, TYPE_MAX),
|
|
74
|
+
...(typeof c.subject === 'string' && c.subject.trim() ? { subject: flattenClamp(c.subject, SUBJECT_MAX) } : {}),
|
|
75
|
+
claim: flattenClamp(c.claim, CLAIM_MAX),
|
|
76
|
+
...(typeof c.verdict === 'string' && c.verdict.trim() ? { verdict: flattenClamp(c.verdict, VERDICT_MAX) } : {}),
|
|
77
|
+
executionCoverage: input.coverage,
|
|
78
|
+
confidence,
|
|
79
|
+
evidence,
|
|
80
|
+
artifacts: artifacts.refs,
|
|
81
|
+
sourceSteps: [...input.sourceSteps],
|
|
82
|
+
provenance: { ...input.provenance },
|
|
83
|
+
status: 'active',
|
|
84
|
+
createdAt: input.now,
|
|
85
|
+
};
|
|
86
|
+
const invalid = validateFinding(finding);
|
|
87
|
+
if (invalid) {
|
|
88
|
+
rejected.push({ index, stage: 'finding-validate', why: invalid });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// STAGE 4 - PERSIST/THREAD. Bounded, and an excess is a recorded rejection rather than a silent
|
|
92
|
+
// truncation: an observation rides on the persisted Execution, so unbounded data would grow the
|
|
93
|
+
// store without limit.
|
|
94
|
+
if (admitted.length >= FINDING_THREAD_MAX) {
|
|
95
|
+
rejected.push({ index, stage: 'persist', why: `exceeds the ${FINDING_THREAD_MAX}-finding thread bound` });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
admitted.push(finding);
|
|
99
|
+
});
|
|
100
|
+
return { admitted, rejected, contractViolated, contractDeclared: input.contract !== undefined };
|
|
101
|
+
}
|
|
102
|
+
/** The Finding-validate assertions. Returns the failing one's name, or undefined when it holds. */
|
|
103
|
+
function validateFinding(f) {
|
|
104
|
+
if (!(f.confidence >= 0 && f.confidence <= 1))
|
|
105
|
+
return 'confidence is not in [0,1]';
|
|
106
|
+
if (!(f.executionCoverage >= 0 && f.executionCoverage <= 1))
|
|
107
|
+
return 'executionCoverage is not in [0,1]';
|
|
108
|
+
if (f.sourceSteps.length === 0)
|
|
109
|
+
return 'sourceSteps is empty';
|
|
110
|
+
// A claim with no evidence may exist only if it also claims no belief.
|
|
111
|
+
if (f.evidence.length === 0 && f.confidence !== 0)
|
|
112
|
+
return 'evidence is empty but confidence is not 0';
|
|
113
|
+
if (!f.type)
|
|
114
|
+
return 'type is empty after clamping';
|
|
115
|
+
if (!f.claim)
|
|
116
|
+
return 'claim is empty after clamping';
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* THE TASK-FAILURE RULE. An agent task fails on its findings ONLY when the definition declared an
|
|
121
|
+
* output contract: then a violation, or admitting nothing at all, is a contract failure. With no
|
|
122
|
+
* declared contract findings are best-effort - rejections are diagnostics and the step's success is
|
|
123
|
+
* decided by its inner plan alone.
|
|
124
|
+
*/
|
|
125
|
+
export function contractFailed(result) {
|
|
126
|
+
if (!result.contractDeclared)
|
|
127
|
+
return false;
|
|
128
|
+
return result.contractViolated || result.admitted.length === 0;
|
|
129
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent definitions (Phase 3.4) — the OPERATOR-authored shape of a delegable unit of work.
|
|
3
|
+
*
|
|
4
|
+
* A definition is a CEILING, never a grant: its `tools`/`skills` are intersected with the parent's
|
|
5
|
+
* catalog, its `permissions` may only narrow, its caps may only lower, and its `routing` may only add
|
|
6
|
+
* exclusions. Everything that turns a definition into something runnable happens at exactly one place —
|
|
7
|
+
* `narrowEnvelope` in ./envelope.ts. Nothing else may derive an inner catalog or a permission set.
|
|
8
|
+
*/
|
|
9
|
+
import type { PermissionPolicy } from '../runtime/policy.js';
|
|
10
|
+
import type { CapabilityRequirement, RoutingPreferences } from '../types.js';
|
|
11
|
+
export interface AgentDefinition {
|
|
12
|
+
/** What this agent is for. Definition-authored, clamped before it reaches a prompt. */
|
|
13
|
+
objective: string;
|
|
14
|
+
/** Tool allowlist, INTERSECTED with the parent catalog — it can never add a tool. */
|
|
15
|
+
tools?: string[];
|
|
16
|
+
/** Skill allowlist, intersected with the parent catalog (and with what the envelope's tools support). */
|
|
17
|
+
skills?: string[];
|
|
18
|
+
/** May only NARROW the parent's permissions (see `clampPermissions`). */
|
|
19
|
+
permissions?: PermissionPolicy;
|
|
20
|
+
/** Per-task caps. A definition may only LOWER the configured default. */
|
|
21
|
+
maxToolCalls?: number;
|
|
22
|
+
maxDurationMs?: number;
|
|
23
|
+
/** The wave-gate reservation AND the worker's hard inner-model-call ceiling — one number, both jobs. */
|
|
24
|
+
maxInnerCalls?: number;
|
|
25
|
+
/** What the agent must produce for its findings to be admitted (see ./admit.ts). */
|
|
26
|
+
outputContract?: {
|
|
27
|
+
types: string[];
|
|
28
|
+
maxFindings?: number;
|
|
29
|
+
requireSubject?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/** Model constraints. `routing` may only narrow the parent's — never re-admit an excluded provider. */
|
|
32
|
+
model?: {
|
|
33
|
+
requirements?: CapabilityRequirement[];
|
|
34
|
+
routing?: RoutingPreferences;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent definitions (Phase 3.4) — the OPERATOR-authored shape of a delegable unit of work.
|
|
3
|
+
*
|
|
4
|
+
* A definition is a CEILING, never a grant: its `tools`/`skills` are intersected with the parent's
|
|
5
|
+
* catalog, its `permissions` may only narrow, its caps may only lower, and its `routing` may only add
|
|
6
|
+
* exclusions. Everything that turns a definition into something runnable happens at exactly one place —
|
|
7
|
+
* `narrowEnvelope` in ./envelope.ts. Nothing else may derive an inner catalog or a permission set.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONE ENFORCEMENT POINT for agent execution (Phase 3.4).
|
|
3
|
+
*
|
|
4
|
+
* `narrowEnvelope` is the single place an inner catalog, a narrowed permission set, the per-task caps,
|
|
5
|
+
* the budget reservation, and the no-nested-agents rule are derived. NEVER re-derive any of them at
|
|
6
|
+
* another call site — the same standing discipline as `Runtime.resolvedPermissions` and
|
|
7
|
+
* `clampMcpPermissions`. It has exactly one caller (`Runtime.agentEnvelopes`) and is deliberately not
|
|
8
|
+
* exported from the public API, so that property is checkable by grep.
|
|
9
|
+
*
|
|
10
|
+
* Everything here NARROWS. A definition is a ceiling: it can remove a tool but never add one, lower a
|
|
11
|
+
* cap but never raise it, drop a permission but never grant one, and add a routing exclusion but never
|
|
12
|
+
* re-admit an excluded provider.
|
|
13
|
+
*/
|
|
14
|
+
import type { PermissionPolicy } from '../runtime/policy.js';
|
|
15
|
+
import type { CapabilityRequirement, RoutingPreferences } from '../types.js';
|
|
16
|
+
import type { AgentDefinition } from './definition.js';
|
|
17
|
+
/** The definition-authored objective reaches a model prompt, so it is bounded like any other source text. */
|
|
18
|
+
export declare const OBJECTIVE_MAX = 240;
|
|
19
|
+
export interface AgentEnvelope {
|
|
20
|
+
agentId: string;
|
|
21
|
+
/** Definition-authored, already flattened and clamped. */
|
|
22
|
+
objective: string;
|
|
23
|
+
/** parent ∩ definition, sorted. Agents are NEVER a member — depth 1 is structural. */
|
|
24
|
+
tools: string[];
|
|
25
|
+
/** parent ∩ definition ∩ { skills whose declared tools are all inside `tools` }, sorted. */
|
|
26
|
+
skills: string[];
|
|
27
|
+
/** Fully explicit and ≤ the parent on every field, so a later merge cannot widen it. */
|
|
28
|
+
permissions: PermissionPolicy;
|
|
29
|
+
maxToolCalls: number;
|
|
30
|
+
maxDurationMs: number;
|
|
31
|
+
/** The wave-gate cost AND the worker's hard inner-model-call ceiling. One number, both jobs. */
|
|
32
|
+
reservation: number;
|
|
33
|
+
/** Only ever narrower than the parent's: exclusions union, preferences intersect. */
|
|
34
|
+
routing?: RoutingPreferences;
|
|
35
|
+
requirements?: CapabilityRequirement[];
|
|
36
|
+
}
|
|
37
|
+
export interface NarrowEnvelopeInput {
|
|
38
|
+
agentId: string;
|
|
39
|
+
definition: AgentDefinition;
|
|
40
|
+
parentTools: string[];
|
|
41
|
+
parentSkills: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
tools?: string[];
|
|
44
|
+
}>;
|
|
45
|
+
parentPermissions: PermissionPolicy;
|
|
46
|
+
parentRouting?: RoutingPreferences;
|
|
47
|
+
defaults: {
|
|
48
|
+
maxToolCalls: number;
|
|
49
|
+
maxDurationMs: number;
|
|
50
|
+
maxInnerCalls: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export declare function narrowEnvelope(input: NarrowEnvelopeInput): AgentEnvelope;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONE ENFORCEMENT POINT for agent execution (Phase 3.4).
|
|
3
|
+
*
|
|
4
|
+
* `narrowEnvelope` is the single place an inner catalog, a narrowed permission set, the per-task caps,
|
|
5
|
+
* the budget reservation, and the no-nested-agents rule are derived. NEVER re-derive any of them at
|
|
6
|
+
* another call site — the same standing discipline as `Runtime.resolvedPermissions` and
|
|
7
|
+
* `clampMcpPermissions`. It has exactly one caller (`Runtime.agentEnvelopes`) and is deliberately not
|
|
8
|
+
* exported from the public API, so that property is checkable by grep.
|
|
9
|
+
*
|
|
10
|
+
* Everything here NARROWS. A definition is a ceiling: it can remove a tool but never add one, lower a
|
|
11
|
+
* cap but never raise it, drop a permission but never grant one, and add a routing exclusion but never
|
|
12
|
+
* re-admit an excluded provider.
|
|
13
|
+
*/
|
|
14
|
+
import { clampPermissions } from '../tools/permissions.js';
|
|
15
|
+
import { flattenClamp } from '../util/flatten.js';
|
|
16
|
+
/** The definition-authored objective reaches a model prompt, so it is bounded like any other source text. */
|
|
17
|
+
export const OBJECTIVE_MAX = 240;
|
|
18
|
+
/** A cap a definition may only LOWER, never raise, and never below 1. */
|
|
19
|
+
function lowerOnly(deflt, requested) {
|
|
20
|
+
return Math.max(1, Math.min(deflt, requested ?? deflt));
|
|
21
|
+
}
|
|
22
|
+
const uniqSorted = (xs) => [...new Set(xs)].sort();
|
|
23
|
+
export function narrowEnvelope(input) {
|
|
24
|
+
const { definition: def, defaults } = input;
|
|
25
|
+
// (1) Tools: intersect with the parent. A definition entry naming something the parent does not have
|
|
26
|
+
// is simply absent — it can never ADD a tool.
|
|
27
|
+
const parentTools = new Set(input.parentTools);
|
|
28
|
+
const tools = uniqSorted((def.tools ?? input.parentTools).filter((t) => parentTools.has(t)));
|
|
29
|
+
// (2) Skills: intersect with the parent, then drop any skill that needs a tool outside the envelope.
|
|
30
|
+
// That second clause is load-bearing, not tidiness: a skill's own `callTool` resolves straight off the
|
|
31
|
+
// Runtime's registry with no allowlist check, so admitting a skill whose declared tools escape the
|
|
32
|
+
// envelope would be a hole. Excluding it is the structural fix; the worker's membership check is
|
|
33
|
+
// defense in depth.
|
|
34
|
+
const inner = new Set(tools);
|
|
35
|
+
const allowedSkills = def.skills ? new Set(def.skills) : undefined;
|
|
36
|
+
const skills = uniqSorted(input.parentSkills.filter((s) => (!allowedSkills || allowedSkills.has(s.id)) && (s.tools ?? []).every((t) => inner.has(t))).map((s) => s.id));
|
|
37
|
+
// (3) Permissions: minimum-merged and fully explicit (see `clampPermissions`).
|
|
38
|
+
const permissions = clampPermissions(input.parentPermissions, def.permissions);
|
|
39
|
+
// (6) Routing: exclusions only ever GROW, preferences only ever shrink, so an agent can never
|
|
40
|
+
// re-admit a provider the parent excluded, nor reach past a privacy or policy decision.
|
|
41
|
+
const pr = input.parentRouting;
|
|
42
|
+
const dr = def.model?.routing;
|
|
43
|
+
const routing = pr || dr
|
|
44
|
+
? {
|
|
45
|
+
...(pr?.excludeProviders || dr?.excludeProviders ? { excludeProviders: uniqSorted([...(pr?.excludeProviders ?? []), ...(dr?.excludeProviders ?? [])]) } : {}),
|
|
46
|
+
...(pr?.excludeModels || dr?.excludeModels ? { excludeModels: uniqSorted([...(pr?.excludeModels ?? []), ...(dr?.excludeModels ?? [])]) } : {}),
|
|
47
|
+
...(pr?.preferProviders ? { preferProviders: dr?.preferProviders ? pr.preferProviders.filter((x) => dr.preferProviders.includes(x)) : [...pr.preferProviders] } : dr?.preferProviders ? { preferProviders: [...dr.preferProviders] } : {}),
|
|
48
|
+
...(pr?.preferModels ? { preferModels: dr?.preferModels ? pr.preferModels.filter((x) => dr.preferModels.includes(x)) : [...pr.preferModels] } : dr?.preferModels ? { preferModels: [...dr.preferModels] } : {}),
|
|
49
|
+
}
|
|
50
|
+
: undefined;
|
|
51
|
+
return {
|
|
52
|
+
agentId: input.agentId,
|
|
53
|
+
objective: flattenClamp(def.objective, OBJECTIVE_MAX),
|
|
54
|
+
tools,
|
|
55
|
+
skills,
|
|
56
|
+
permissions,
|
|
57
|
+
// (4) Caps: a definition may only lower.
|
|
58
|
+
maxToolCalls: lowerOnly(defaults.maxToolCalls, def.maxToolCalls),
|
|
59
|
+
maxDurationMs: lowerOnly(defaults.maxDurationMs, def.maxDurationMs),
|
|
60
|
+
// (5) The reservation is the same shape of number, and doubles as the hard inner-call ceiling.
|
|
61
|
+
reservation: lowerOnly(defaults.maxInnerCalls, def.maxInnerCalls),
|
|
62
|
+
...(routing && Object.keys(routing).length ? { routing } : {}),
|
|
63
|
+
// Requirements ADD to the parent's — more requirements is a narrower candidate set.
|
|
64
|
+
...(def.model?.requirements?.length ? { requirements: [...def.model.requirements] } : {}),
|
|
65
|
+
};
|
|
66
|
+
// (7) There is no `agents` field, by construction: depth 1 is structural, not a runtime check. The
|
|
67
|
+
// worker plans with `agents: []`, so validatePlan rejects any nested agent step as `unknown agent`.
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Finding contract (Phase 3.4) - what an agent is allowed to tell the rest of the system.
|
|
3
|
+
*
|
|
4
|
+
* TWO NUMBERS, NEVER CONFLATED:
|
|
5
|
+
* - `executionCoverage` is how much of the agent's plan actually ran. Deterministic, read from step
|
|
6
|
+
* statuses, and NOT a belief about the claim.
|
|
7
|
+
* - `confidence` is evidence-based belief in the claim. Conflict resolution weighs ONLY this;
|
|
8
|
+
* coverage may filter or break a tie, never contribute to the weight.
|
|
9
|
+
*
|
|
10
|
+
* NAMING: the entry type is `FindingEvidence`, not `Evidence` - that name is already the router's
|
|
11
|
+
* capability-evidence RANK union (src/types.ts), and `EvidenceResult` belongs to comparison. The same
|
|
12
|
+
* class of collision as `Tool.provides` and `agentTaskId`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ArtifactRef } from '../runtime/types.js';
|
|
15
|
+
import type { PlanStep } from '../orchestration/plan.js';
|
|
16
|
+
export type FindingStatus = 'active' | 'superseded' | 'contradicted';
|
|
17
|
+
export type FindingEvidenceKind = 'validation' | 'observation' | 'tool-output' | 'artifact';
|
|
18
|
+
export interface FindingEvidence {
|
|
19
|
+
kind: FindingEvidenceKind;
|
|
20
|
+
/** The INNER step id this entry came from. */
|
|
21
|
+
stepId: string;
|
|
22
|
+
/** Clamped at construction. Fenced again at render; never stored fenced. */
|
|
23
|
+
detail: string;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
/** `validation` only: the check tally that backs the belief. */
|
|
26
|
+
checks?: {
|
|
27
|
+
passed: number;
|
|
28
|
+
total: number;
|
|
29
|
+
};
|
|
30
|
+
/** Set when the backing artifact could not be written - the DOWNGRADE marker. */
|
|
31
|
+
unavailable?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface Finding {
|
|
34
|
+
id: string;
|
|
35
|
+
agentId: string;
|
|
36
|
+
/** `agentTaskId`, NEVER `taskId` - that name belongs to the router. */
|
|
37
|
+
agentTaskId: string;
|
|
38
|
+
type: string;
|
|
39
|
+
subject?: string;
|
|
40
|
+
claim: string;
|
|
41
|
+
verdict?: string;
|
|
42
|
+
/** Deterministic plan completion, NOT belief. */
|
|
43
|
+
executionCoverage: number;
|
|
44
|
+
/** Evidence-based belief in the claim. */
|
|
45
|
+
confidence: number;
|
|
46
|
+
evidence: FindingEvidence[];
|
|
47
|
+
artifacts: ArtifactRef[];
|
|
48
|
+
sourceSteps: string[];
|
|
49
|
+
provenance: {
|
|
50
|
+
executionId?: string;
|
|
51
|
+
planVersion: number;
|
|
52
|
+
};
|
|
53
|
+
status: FindingStatus;
|
|
54
|
+
supersededBy?: string;
|
|
55
|
+
createdAt: number;
|
|
56
|
+
}
|
|
57
|
+
/** Two decimals, so a JSON round-trip is stable and an equality assertion means something. */
|
|
58
|
+
export declare function round2(n: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* How much of the agent's inner plan succeeded. Reads step STATUSES only - it never consults evidence
|
|
61
|
+
* and is never derived from confidence.
|
|
62
|
+
*/
|
|
63
|
+
export declare function executionCoverage(steps: PlanStep[]): number;
|
|
64
|
+
/**
|
|
65
|
+
* Evidence-based belief. The rubric, in order:
|
|
66
|
+
* - no evidence => 0, always. This guard precedes the override, so a finding with nothing behind it
|
|
67
|
+
* can never claim belief.
|
|
68
|
+
* - at least one usable `validation` entry => up to 1.0, scaled by validated/total CHECKS.
|
|
69
|
+
* - observation / tool-output only => capped at 0.5.
|
|
70
|
+
* - a final inner step MAY supply a structured override (`SkillResult.data.confidence`), clamped
|
|
71
|
+
* 0..1. That is a skill-authored judgment, still not model prose.
|
|
72
|
+
*/
|
|
73
|
+
export declare function confidenceOf(ev: FindingEvidence[], override?: unknown): number;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve conflicts among active findings, weighing ONLY `confidence`. Two findings conflict when they
|
|
76
|
+
* share a `type` + `subject`: a differing verdict makes the loser `contradicted`, and otherwise the
|
|
77
|
+
* loser is `superseded`. `executionCoverage` breaks a tie and never enters the weight.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveConflicts(findings: Finding[]): Finding[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Finding contract (Phase 3.4) - what an agent is allowed to tell the rest of the system.
|
|
3
|
+
*
|
|
4
|
+
* TWO NUMBERS, NEVER CONFLATED:
|
|
5
|
+
* - `executionCoverage` is how much of the agent's plan actually ran. Deterministic, read from step
|
|
6
|
+
* statuses, and NOT a belief about the claim.
|
|
7
|
+
* - `confidence` is evidence-based belief in the claim. Conflict resolution weighs ONLY this;
|
|
8
|
+
* coverage may filter or break a tie, never contribute to the weight.
|
|
9
|
+
*
|
|
10
|
+
* NAMING: the entry type is `FindingEvidence`, not `Evidence` - that name is already the router's
|
|
11
|
+
* capability-evidence RANK union (src/types.ts), and `EvidenceResult` belongs to comparison. The same
|
|
12
|
+
* class of collision as `Tool.provides` and `agentTaskId`.
|
|
13
|
+
*/
|
|
14
|
+
/** Two decimals, so a JSON round-trip is stable and an equality assertion means something. */
|
|
15
|
+
export function round2(n) {
|
|
16
|
+
return Math.round(n * 100) / 100;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How much of the agent's inner plan succeeded. Reads step STATUSES only - it never consults evidence
|
|
20
|
+
* and is never derived from confidence.
|
|
21
|
+
*/
|
|
22
|
+
export function executionCoverage(steps) {
|
|
23
|
+
if (steps.length === 0)
|
|
24
|
+
return 0;
|
|
25
|
+
return round2(steps.filter((s) => s.status === 'succeeded').length / steps.length);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Evidence-based belief. The rubric, in order:
|
|
29
|
+
* - no evidence => 0, always. This guard precedes the override, so a finding with nothing behind it
|
|
30
|
+
* can never claim belief.
|
|
31
|
+
* - at least one usable `validation` entry => up to 1.0, scaled by validated/total CHECKS.
|
|
32
|
+
* - observation / tool-output only => capped at 0.5.
|
|
33
|
+
* - a final inner step MAY supply a structured override (`SkillResult.data.confidence`), clamped
|
|
34
|
+
* 0..1. That is a skill-authored judgment, still not model prose.
|
|
35
|
+
*/
|
|
36
|
+
export function confidenceOf(ev, override) {
|
|
37
|
+
if (ev.length === 0)
|
|
38
|
+
return 0;
|
|
39
|
+
const validations = ev.filter((e) => e.kind === 'validation' && !e.unavailable && (e.checks?.total ?? 0) > 0);
|
|
40
|
+
let c;
|
|
41
|
+
if (validations.length >= 1) {
|
|
42
|
+
const passed = validations.reduce((n, e) => n + e.checks.passed, 0);
|
|
43
|
+
const total = validations.reduce((n, e) => n + e.checks.total, 0);
|
|
44
|
+
c = Math.min(1, passed / total);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
c = (ev.filter((e) => e.ok && !e.unavailable).length / ev.length) * 0.5;
|
|
48
|
+
}
|
|
49
|
+
if (typeof override === 'number' && Number.isFinite(override))
|
|
50
|
+
c = Math.min(1, Math.max(0, override));
|
|
51
|
+
return round2(c);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve conflicts among active findings, weighing ONLY `confidence`. Two findings conflict when they
|
|
55
|
+
* share a `type` + `subject`: a differing verdict makes the loser `contradicted`, and otherwise the
|
|
56
|
+
* loser is `superseded`. `executionCoverage` breaks a tie and never enters the weight.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveConflicts(findings) {
|
|
59
|
+
const groups = new Map();
|
|
60
|
+
for (const f of findings) {
|
|
61
|
+
if (!f.subject)
|
|
62
|
+
continue; // no conflict key => nothing to resolve against
|
|
63
|
+
const key = `${f.type}\u0000${f.subject}`;
|
|
64
|
+
groups.set(key, [...(groups.get(key) ?? []), f]);
|
|
65
|
+
}
|
|
66
|
+
const out = new Map(findings.map((f) => [f.id, { ...f }]));
|
|
67
|
+
for (const group of groups.values()) {
|
|
68
|
+
if (group.length < 2)
|
|
69
|
+
continue;
|
|
70
|
+
const ranked = [...group].sort((a, b) => b.confidence - a.confidence || b.executionCoverage - a.executionCoverage || (a.id < b.id ? -1 : 1));
|
|
71
|
+
const winner = ranked[0];
|
|
72
|
+
for (const loser of ranked.slice(1)) {
|
|
73
|
+
const row = out.get(loser.id);
|
|
74
|
+
const differingVerdict = loser.verdict !== undefined && winner.verdict !== undefined && loser.verdict !== winner.verdict;
|
|
75
|
+
row.status = differingVerdict ? 'contradicted' : 'superseded';
|
|
76
|
+
row.supersededBy = winner.id;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return [...out.values()];
|
|
80
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed role vocabulary for auto-decomposition (Phase 3.7).
|
|
3
|
+
*
|
|
4
|
+
* Auto-decomposition lets a goal produce agent steps with no operator-authored definition. The thing
|
|
5
|
+
* that makes it safe is what it does NOT do: no model-authored string ever becomes an objective, a tool
|
|
6
|
+
* id, or a permission. The model's only influence is choosing among the bounded agents offered to it —
|
|
7
|
+
* the same closed-set discipline `deriveCapabilitiesOffline` uses, applied to roles instead of ids.
|
|
8
|
+
*
|
|
9
|
+
* Every objective here is written in-tree by a human, because the objective is the one string that
|
|
10
|
+
* reaches the inner prompt UNFENCED (the step input beside it is fenced as untrusted). Every role is
|
|
11
|
+
* read-shaped, and adding a write-shaped one is a decision to record, not a line to slip in: the
|
|
12
|
+
* permission clamp would still deny it, but the catalog it is offered should not suggest otherwise.
|
|
13
|
+
*/
|
|
14
|
+
import type { AgentDefinition } from './definition.js';
|
|
15
|
+
export interface AgentRole {
|
|
16
|
+
id: string;
|
|
17
|
+
/** IN-TREE and human-written. Never derived, never model-authored. */
|
|
18
|
+
objective: string;
|
|
19
|
+
/** What the role is for, shown in the plan catalog so the planner can choose between roles. */
|
|
20
|
+
description: string;
|
|
21
|
+
/**
|
|
22
|
+
* An output contract is MANDATORY for a derived role, though optional for an authored definition.
|
|
23
|
+
* `contractFailed` returns false when no contract is declared and the finding cap becomes infinite —
|
|
24
|
+
* so an agent nobody wrote would be the one agent whose output could never fail admission, and the
|
|
25
|
+
* one whose finding count is unbounded.
|
|
26
|
+
*/
|
|
27
|
+
outputContract: NonNullable<AgentDefinition['outputContract']>;
|
|
28
|
+
}
|
|
29
|
+
/** Three roles ship in 3.0.0. All read-shaped; see the header before adding a fourth. */
|
|
30
|
+
export declare const AGENT_ROLES: readonly AgentRole[];
|
|
31
|
+
/** The id prefix reserved for synthesized agents, so one can never collide with an authored id. */
|
|
32
|
+
export declare const DERIVED_ID_PREFIX = "auto_";
|
|
33
|
+
/** The agent id a role synthesizes to. */
|
|
34
|
+
export declare function roleAgentId(roleId: string): string;
|
|
35
|
+
/** Whether an id belongs to the reserved synthesized namespace. */
|
|
36
|
+
export declare function isDerivedAgentId(id: string): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed role vocabulary for auto-decomposition (Phase 3.7).
|
|
3
|
+
*
|
|
4
|
+
* Auto-decomposition lets a goal produce agent steps with no operator-authored definition. The thing
|
|
5
|
+
* that makes it safe is what it does NOT do: no model-authored string ever becomes an objective, a tool
|
|
6
|
+
* id, or a permission. The model's only influence is choosing among the bounded agents offered to it —
|
|
7
|
+
* the same closed-set discipline `deriveCapabilitiesOffline` uses, applied to roles instead of ids.
|
|
8
|
+
*
|
|
9
|
+
* Every objective here is written in-tree by a human, because the objective is the one string that
|
|
10
|
+
* reaches the inner prompt UNFENCED (the step input beside it is fenced as untrusted). Every role is
|
|
11
|
+
* read-shaped, and adding a write-shaped one is a decision to record, not a line to slip in: the
|
|
12
|
+
* permission clamp would still deny it, but the catalog it is offered should not suggest otherwise.
|
|
13
|
+
*/
|
|
14
|
+
/** Three roles ship in 3.0.0. All read-shaped; see the header before adding a fourth. */
|
|
15
|
+
export const AGENT_ROLES = [
|
|
16
|
+
{
|
|
17
|
+
id: 'investigate',
|
|
18
|
+
objective: 'Investigate the question you are given by reading the workspace. Report what you found as structured findings, each naming the file or symbol it is about. Do not modify anything.',
|
|
19
|
+
description: 'reads the workspace to answer a question, and reports findings',
|
|
20
|
+
outputContract: { types: ['observation'], maxFindings: 5, requireSubject: true },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'verify',
|
|
24
|
+
objective: 'Check the specific claim you are given against the workspace. Report whether it holds, as structured findings that name what you checked. Do not modify anything.',
|
|
25
|
+
description: 'checks a specific claim against the workspace and reports whether it holds',
|
|
26
|
+
outputContract: { types: ['verification'], maxFindings: 5, requireSubject: true },
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'survey',
|
|
30
|
+
objective: 'Survey the area you are given and describe what is there: the files, their roles, and how they relate. Report structured findings. Do not modify anything.',
|
|
31
|
+
description: 'describes the shape of an area of the workspace',
|
|
32
|
+
outputContract: { types: ['survey'], maxFindings: 5, requireSubject: true },
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
/** The id prefix reserved for synthesized agents, so one can never collide with an authored id. */
|
|
36
|
+
export const DERIVED_ID_PREFIX = 'auto_';
|
|
37
|
+
/** The agent id a role synthesizes to. */
|
|
38
|
+
export function roleAgentId(roleId) {
|
|
39
|
+
return `${DERIVED_ID_PREFIX}${roleId}`;
|
|
40
|
+
}
|
|
41
|
+
/** Whether an id belongs to the reserved synthesized namespace. */
|
|
42
|
+
export function isDerivedAgentId(id) {
|
|
43
|
+
return id.startsWith(DERIVED_ID_PREFIX);
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesizing agents from the registry (Phase 3.7) — auto-decomposition.
|
|
3
|
+
*
|
|
4
|
+
* THE TOOL SET IS THE WHOLE PROBLEM. A capability's providers are NAMESPACED ids (`tool:filesystem`)
|
|
5
|
+
* while the tool registry holds bare ones (`filesystem`), and `narrowEnvelope` intersects against the
|
|
6
|
+
* bare set. Emitting provider ids directly yields an empty catalog for every derived agent — a feature
|
|
7
|
+
* that is inert while looking correct, and whose obvious tests ("a derived agent cannot name a tool
|
|
8
|
+
* outside the registry") pass vacuously on the empty array. So the prefix is stripped here, membership
|
|
9
|
+
* in the parent catalog is asserted here, and the tests assert a NON-EMPTY result.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IS NOT USED: BM25 relevance. It was measured to abstain on exactly the goals these roles exist
|
|
12
|
+
* for — "review the changed files", "find the bug", "audit the auth module" all derive nothing, because
|
|
13
|
+
* its thresholds were tuned for a different job (advisory capability hints, where a false positive
|
|
14
|
+
* merely over-advises). Gating whether an agent EXISTS on that calibration would make decomposition a
|
|
15
|
+
* silent no-op for the goals it is for. Roles are offered whenever they have tools; which one fits the
|
|
16
|
+
* goal is the planner's judgment, made from the role descriptions, which is exactly its job.
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentRole } from './roles.js';
|
|
19
|
+
import type { AgentDefinition } from './definition.js';
|
|
20
|
+
export interface DerivedAgent {
|
|
21
|
+
id: string;
|
|
22
|
+
roleId: string;
|
|
23
|
+
definition: AgentDefinition;
|
|
24
|
+
}
|
|
25
|
+
export interface SynthesizeInput {
|
|
26
|
+
/** Every capability the registry knows, with its effects and providers. */
|
|
27
|
+
capabilities: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
effects: readonly string[];
|
|
30
|
+
providers: string[];
|
|
31
|
+
}>;
|
|
32
|
+
/** The tool ids the parent actually holds — the set `narrowEnvelope` will intersect against. */
|
|
33
|
+
parentTools: string[];
|
|
34
|
+
roles?: readonly AgentRole[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Offer one bounded agent per role that has at least one usable tool.
|
|
38
|
+
*
|
|
39
|
+
* A role draws the parent's tools that provide at least one READ-effect capability. Effects are
|
|
40
|
+
* advisory metadata and never authorization (invariant 25) — the permission clamp in `narrowEnvelope`
|
|
41
|
+
* is what actually denies writing. Using them here only NARROWS what a machine-generated agent is
|
|
42
|
+
* offered, which fails safe: a tool with no declared effects is inferred write-shaped and excluded.
|
|
43
|
+
*/
|
|
44
|
+
export declare function synthesizeAgents(input: SynthesizeInput): DerivedAgent[];
|