vaerion 0.1.15__py3-none-any.whl
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.
- vaerion/__init__.py +8 -0
- vaerion/cli.py +50 -0
- vaerion/engine/agents/executor.ts +227 -0
- vaerion/engine/agents/grants.ts +73 -0
- vaerion/engine/agents/index.ts +61 -0
- vaerion/engine/agents/metrics.ts +125 -0
- vaerion/engine/agents/planner.ts +248 -0
- vaerion/engine/agents/reasoning.ts +146 -0
- vaerion/engine/agents/research-port.ts +172 -0
- vaerion/engine/agents/runtime.ts +503 -0
- vaerion/engine/agents/tools.ts +365 -0
- vaerion/engine/api/index.ts +12 -0
- vaerion/engine/api/openapi.ts +170 -0
- vaerion/engine/api/routes.ts +340 -0
- vaerion/engine/api/run-registry.ts +1024 -0
- vaerion/engine/api/server.ts +516 -0
- vaerion/engine/broker/contracts/audit.ts +145 -0
- vaerion/engine/broker/contracts/capability.ts +82 -0
- vaerion/engine/broker/contracts/decision.ts +120 -0
- vaerion/engine/broker/contracts/events.ts +92 -0
- vaerion/engine/broker/contracts/gate.ts +52 -0
- vaerion/engine/broker/contracts/permission-graph.ts +82 -0
- vaerion/engine/broker/contracts/principal.ts +34 -0
- vaerion/engine/broker/contracts/review-diff.ts +55 -0
- vaerion/engine/broker/engine.ts +249 -0
- vaerion/engine/broker/refusal-log.ts +222 -0
- vaerion/engine/center/center.ts +190 -0
- vaerion/engine/cli/account.ts +162 -0
- vaerion/engine/cli/ai.ts +247 -0
- vaerion/engine/cli/catalog.ts +59 -0
- vaerion/engine/cli/commands.ts +3057 -0
- vaerion/engine/cli/completions.ts +249 -0
- vaerion/engine/cli/interactive.ts +114 -0
- vaerion/engine/cli/io.ts +30 -0
- vaerion/engine/cli/recovery.ts +246 -0
- vaerion/engine/cli/render.ts +207 -0
- vaerion/engine/cli/suggest.ts +56 -0
- vaerion/engine/cli/ui.ts +1654 -0
- vaerion/engine/cli/vae.ts +885 -0
- vaerion/engine/cli/workspace.ts +66 -0
- vaerion/engine/config/config.ts +564 -0
- vaerion/engine/config/templates.ts +588 -0
- vaerion/engine/evals/harness.ts +378 -0
- vaerion/engine/evals/index.ts +14 -0
- vaerion/engine/extensions/factory.ts +41 -0
- vaerion/engine/extensions/host.ts +430 -0
- vaerion/engine/extensions/index.ts +18 -0
- vaerion/engine/gateway/adapters/anthropic.ts +206 -0
- vaerion/engine/gateway/adapters/ollama.ts +102 -0
- vaerion/engine/gateway/adapters/openai.ts +180 -0
- vaerion/engine/gateway/adapters/sse.ts +145 -0
- vaerion/engine/gateway/breaker.ts +146 -0
- vaerion/engine/gateway/cassette.ts +102 -0
- vaerion/engine/gateway/metering.ts +84 -0
- vaerion/engine/gateway/mockbrain.ts +187 -0
- vaerion/engine/gateway/pricing.ts +103 -0
- vaerion/engine/gateway/secrets.ts +102 -0
- vaerion/engine/gateway/service.ts +436 -0
- vaerion/engine/gateway/transport.ts +77 -0
- vaerion/engine/gateway/types.ts +196 -0
- vaerion/engine/identity/identity.ts +231 -0
- vaerion/engine/index.ts +96 -0
- vaerion/engine/journal/export.ts +137 -0
- vaerion/engine/journal/hashchain.ts +69 -0
- vaerion/engine/journal/lock.ts +68 -0
- vaerion/engine/journal/ls.ts +45 -0
- vaerion/engine/journal/reader.ts +101 -0
- vaerion/engine/journal/records.ts +139 -0
- vaerion/engine/journal/recovery.ts +117 -0
- vaerion/engine/journal/replay.ts +115 -0
- vaerion/engine/journal/verify.ts +174 -0
- vaerion/engine/journal/writer.ts +208 -0
- vaerion/engine/kernel/canonical.ts +56 -0
- vaerion/engine/kernel/clock.ts +69 -0
- vaerion/engine/kernel/errors.ts +229 -0
- vaerion/engine/kernel/hash.ts +22 -0
- vaerion/engine/kernel/ids.ts +159 -0
- vaerion/engine/kernel/redact.ts +64 -0
- vaerion/engine/kernel/tar.ts +122 -0
- vaerion/engine/mcp/server.ts +261 -0
- vaerion/engine/package/build.ts +195 -0
- vaerion/engine/package/format.ts +358 -0
- vaerion/engine/package/index.ts +11 -0
- vaerion/engine/package/lock.ts +121 -0
- vaerion/engine/package/service.ts +218 -0
- vaerion/engine/package/verify.ts +231 -0
- vaerion/engine/perf/perf.ts +345 -0
- vaerion/engine/receipts/receipt.ts +170 -0
- vaerion/engine/repo/canonical.ts +110 -0
- vaerion/engine/repo/ci.ts +417 -0
- vaerion/engine/repo/constitution.ts +99 -0
- vaerion/engine/repo/git.ts +371 -0
- vaerion/engine/repo/index.ts +11 -0
- vaerion/engine/repo/release.ts +362 -0
- vaerion/engine/research/capability.ts +131 -0
- vaerion/engine/research/citation.ts +31 -0
- vaerion/engine/research/context.ts +202 -0
- vaerion/engine/research/evidence.ts +121 -0
- vaerion/engine/research/fencing.ts +135 -0
- vaerion/engine/research/fingerprint.ts +59 -0
- vaerion/engine/research/index.ts +20 -0
- vaerion/engine/research/local-index.ts +243 -0
- vaerion/engine/research/pipeline.ts +188 -0
- vaerion/engine/research/principal.ts +53 -0
- vaerion/engine/research/provenance.ts +85 -0
- vaerion/engine/research/replay.ts +93 -0
- vaerion/engine/research/scoring.ts +62 -0
- vaerion/engine/research/verification.ts +115 -0
- vaerion/engine/runtime/run.ts +505 -0
- vaerion/engine/spine/bus.ts +130 -0
- vaerion/engine/spine/envelope.ts +124 -0
- vaerion/engine/spine/event-types.ts +82 -0
- vaerion/engine/spine/persistence.ts +44 -0
- vaerion/engine/spine/serialization.ts +26 -0
- vaerion/engine/store/blob-cas.ts +93 -0
- vaerion/engine/workflow/dag.ts +111 -0
- vaerion/engine/workflow/engine.ts +322 -0
- vaerion/engine/workflow/index.ts +9 -0
- vaerion-0.1.15.dist-info/METADATA +69 -0
- vaerion-0.1.15.dist-info/RECORD +123 -0
- vaerion-0.1.15.dist-info/WHEEL +5 -0
- vaerion-0.1.15.dist-info/entry_points.txt +2 -0
- vaerion-0.1.15.dist-info/top_level.txt +1 -0
vaerion/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Vaerion — the AI-native development engine (PyPI delivery channel).
|
|
2
|
+
|
|
3
|
+
Python is the delivery channel, not the substrate: the engine is
|
|
4
|
+
TypeScript executed by the Bun runtime (ADR-0018). This package installs
|
|
5
|
+
the `vae` console launcher and the packaged engine source.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.15"
|
vaerion/cli.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""`vae` console launcher — resolves Bun, then hands argv to the engine.
|
|
2
|
+
|
|
3
|
+
Design laws:
|
|
4
|
+
- One entrypoint: the packaged engine's own main() (same exit codes as
|
|
5
|
+
every other distribution channel).
|
|
6
|
+
- Educated errors: a missing Bun runtime is a taught error (exit 2),
|
|
7
|
+
never a traceback.
|
|
8
|
+
- exec, not wrap: os.exec* replaces this process, so streaming, signals,
|
|
9
|
+
and exit codes pass through unchanged.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
_BUN_INSTALL = {
|
|
18
|
+
"darwin": "brew install oven-sh/bun/bun (or: curl -fsSL https://bun.sh/install | sh)",
|
|
19
|
+
"linux": "curl -fsSL https://bun.sh/install | sh",
|
|
20
|
+
"default": "see https://bun.sh (docs/INSTALL.md lists every method)",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _bun_hint() -> str:
|
|
25
|
+
if sys.platform == "darwin":
|
|
26
|
+
return _BUN_INSTALL["darwin"]
|
|
27
|
+
if sys.platform.startswith("linux"):
|
|
28
|
+
return _BUN_INSTALL["linux"]
|
|
29
|
+
return _BUN_INSTALL["default"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> int:
|
|
33
|
+
bun = shutil.which("bun")
|
|
34
|
+
if bun is None:
|
|
35
|
+
print("E1600 vae requires the Bun runtime (>= 1.2) to execute the engine.", file=sys.stderr)
|
|
36
|
+
print(f"Fix: install Bun -> {_bun_hint()}", file=sys.stderr)
|
|
37
|
+
print("Docs: docs/INSTALL.md (all installation methods)", file=sys.stderr)
|
|
38
|
+
return 2
|
|
39
|
+
|
|
40
|
+
cli = Path(__file__).resolve().parent / "engine" / "cli" / "vae.ts"
|
|
41
|
+
if not cli.exists():
|
|
42
|
+
print("E1900 packaged engine source is missing — the vaerion install is incomplete.", file=sys.stderr)
|
|
43
|
+
print("Fix: reinstall -> pip install --force-reinstall vaerion", file=sys.stderr)
|
|
44
|
+
return 1
|
|
45
|
+
|
|
46
|
+
os.execvp(bun, [bun, "run", str(cli), *sys.argv[1:]])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__": # pragma: no cover
|
|
50
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vaerion — the agent step executor (MS-4).
|
|
3
|
+
*
|
|
4
|
+
* One responsibility: run a single PlanStep through its constitutional path
|
|
5
|
+
* and return an honest outcome.
|
|
6
|
+
*
|
|
7
|
+
* model step → GatewayService (the single gate: decide → journal → act →
|
|
8
|
+
* meter); citation enforcement applies when the step asks
|
|
9
|
+
* for it and the run has prepared research context.
|
|
10
|
+
* tool step → ToolInvocationService (broker → journal → execute →
|
|
11
|
+
* receipts → replay).
|
|
12
|
+
* note step → ReasoningSession (persistent scratchpad, journaled).
|
|
13
|
+
* context step → the ResearchPort (One Context Path: declared capability →
|
|
14
|
+
* deterministic retrieval → evidence → citations → pack).
|
|
15
|
+
*
|
|
16
|
+
* The executor is stateless with respect to authority: gate prompts
|
|
17
|
+
* (GatewayGatePrompt / ToolGatePrompt) propagate — the supervisor owns the
|
|
18
|
+
* pause-and-resume law. Failures are RETURNED as outcomes (never swallowed),
|
|
19
|
+
* so the supervisor can retry or replan with the failure in the history.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { VaerionError } from "../kernel/errors.ts";
|
|
23
|
+
import type { Clock } from "../kernel/clock.ts";
|
|
24
|
+
import type { Actor } from "../spine/envelope.ts";
|
|
25
|
+
import type { Principal } from "../broker/contracts/principal.ts";
|
|
26
|
+
import type { PolicyContract } from "../broker/contracts/decision.ts";
|
|
27
|
+
import type { InvocationResult } from "../gateway/types.ts";
|
|
28
|
+
import { GatewayGatePrompt } from "../gateway/service.ts";
|
|
29
|
+
import type { GatewayService, BudgetGuard, GatewayHost } from "../gateway/service.ts";
|
|
30
|
+
import { ToolGatePrompt } from "./tools.ts";
|
|
31
|
+
import type { ToolInvocationService, ToolHost } from "./tools.ts";
|
|
32
|
+
import type { ReasoningSession } from "./reasoning.ts";
|
|
33
|
+
import type { PlanStep, PlannerHistoryItem } from "./planner.ts";
|
|
34
|
+
import type { Citation } from "../research/citation.ts";
|
|
35
|
+
|
|
36
|
+
/** The research port: the One Context Path behind `context` steps. */
|
|
37
|
+
export interface ResearchPort {
|
|
38
|
+
prepare(query: string, capability: string): Promise<{ pack_fingerprint: string; citation_ids: string[]; evidence_count: number; blocks: number; dropped: number; tokens_estimated: number }>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type StepOutcome =
|
|
42
|
+
| {
|
|
43
|
+
ok: true;
|
|
44
|
+
kind: PlanStep["kind"];
|
|
45
|
+
summary: string;
|
|
46
|
+
/** Model-backed step accounting (present for kind === "model"). */
|
|
47
|
+
model?: { model: string; inputTokens: number; outputTokens: number; costMicroUsd: number; latencyMs: number; attempts: number; textHash: string | null; text: string };
|
|
48
|
+
/** Tool-backed step accounting (present for kind === "tool"). */
|
|
49
|
+
tool?: { tool: string; resultHash: string; blobRef: string | null };
|
|
50
|
+
/** Citations referenced by the step's output when citation-checked. */
|
|
51
|
+
citations?: string[];
|
|
52
|
+
}
|
|
53
|
+
| { ok: false; kind: PlanStep["kind"]; error_code: string; message: string };
|
|
54
|
+
|
|
55
|
+
export interface StepExecutorOptions {
|
|
56
|
+
clock: Clock;
|
|
57
|
+
actor: Actor;
|
|
58
|
+
gateway: GatewayService | null;
|
|
59
|
+
/** The run host for gateway invocations (RunHarness satisfies it). */
|
|
60
|
+
gatewayHost: GatewayHost | null;
|
|
61
|
+
tools: ToolInvocationService | null;
|
|
62
|
+
/** The run host for tool pipelines (RunHarness satisfies it). */
|
|
63
|
+
toolHost: ToolHost | null;
|
|
64
|
+
reasoning: ReasoningSession;
|
|
65
|
+
research: ResearchPort | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const CITATION_RE = /cit_\d{4}/g;
|
|
69
|
+
|
|
70
|
+
/** Gate prompts are NOT failures: they propagate for the supervisor's pause law. */
|
|
71
|
+
function isGatePrompt(err: unknown): err is GatewayGatePrompt | ToolGatePrompt {
|
|
72
|
+
return err instanceof GatewayGatePrompt || err instanceof ToolGatePrompt;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class StepExecutor {
|
|
76
|
+
private readonly opts: StepExecutorOptions;
|
|
77
|
+
private readonly gatewayHostField: GatewayHost | null;
|
|
78
|
+
private readonly toolHostField: ToolHost | null;
|
|
79
|
+
/** Citation ids minted by context steps in THIS run (citation enforcement). */
|
|
80
|
+
private readonly availableCitations: string[] = [];
|
|
81
|
+
|
|
82
|
+
constructor(opts: StepExecutorOptions) {
|
|
83
|
+
this.opts = opts;
|
|
84
|
+
this.gatewayHostField = opts.gatewayHost;
|
|
85
|
+
this.toolHostField = opts.toolHost;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Citation enforcement input: the run's prepared citation ids. */
|
|
89
|
+
availableCitationIds(): readonly string[] {
|
|
90
|
+
return this.availableCitations;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async execute(
|
|
94
|
+
step: PlanStep,
|
|
95
|
+
ctx: { principal: Principal; policy: PolicyContract; goal: string; requestId: () => string; budget: () => BudgetGuard },
|
|
96
|
+
): Promise<StepOutcome> {
|
|
97
|
+
switch (step.kind) {
|
|
98
|
+
case "model":
|
|
99
|
+
return this.executeModel(step, ctx);
|
|
100
|
+
case "tool":
|
|
101
|
+
return this.executeTool(step, ctx);
|
|
102
|
+
case "note":
|
|
103
|
+
return this.executeNote(step);
|
|
104
|
+
case "context":
|
|
105
|
+
return this.executeContext(step, ctx);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async executeModel(
|
|
110
|
+
step: Extract<PlanStep, { kind: "model" }>,
|
|
111
|
+
ctx: { principal: Principal; policy: PolicyContract; goal: string; requestId: () => string; budget: () => BudgetGuard },
|
|
112
|
+
): Promise<StepOutcome> {
|
|
113
|
+
if (this.opts.gateway === null) {
|
|
114
|
+
return { ok: false, kind: "model", error_code: "E1600", message: "no gateway bound in this process — model steps are unavailable" };
|
|
115
|
+
}
|
|
116
|
+
let result: InvocationResult;
|
|
117
|
+
try {
|
|
118
|
+
result = await this.opts.gateway.invoke(this.gatewayHostField as GatewayHost, {
|
|
119
|
+
request: { op: "chat", model: step.model, messages: step.messages, seed: step.seed, maxOutputTokens: step.maxOutputTokens },
|
|
120
|
+
principal: ctx.principal,
|
|
121
|
+
policy: ctx.policy,
|
|
122
|
+
requestId: ctx.requestId(),
|
|
123
|
+
intent: `agent model step for goal: ${ctx.goal}`,
|
|
124
|
+
budget: ctx.budget(),
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (isGatePrompt(err)) throw err; // human authority is not a failure
|
|
128
|
+
return { ok: false, kind: "model", error_code: err instanceof VaerionError ? err.code : "E1900", message: (err as Error).message };
|
|
129
|
+
}
|
|
130
|
+
// Citation enforcement (E1806): an answer step over prepared research
|
|
131
|
+
// content must reference at least one prepared citation id.
|
|
132
|
+
let citations: string[] | undefined;
|
|
133
|
+
if (step.requiresCitations === true) {
|
|
134
|
+
if (this.availableCitations.length === 0) {
|
|
135
|
+
return { ok: false, kind: "model", error_code: "E1806", message: "answer step requires citations but the run prepared no research context" };
|
|
136
|
+
}
|
|
137
|
+
const cited = new Set(result.text.match(CITATION_RE) ?? []);
|
|
138
|
+
const used = this.availableCitations.filter((c) => cited.has(c));
|
|
139
|
+
if (used.length === 0) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
kind: "model",
|
|
143
|
+
error_code: "E1806",
|
|
144
|
+
message: `answer does not reference any prepared citation (${this.availableCitations.slice(0, 4).join(", ")}${this.availableCitations.length > 4 ? "…" : ""})`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
citations = used;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
kind: "model",
|
|
152
|
+
summary: result.text.slice(0, 120),
|
|
153
|
+
model: {
|
|
154
|
+
model: result.model,
|
|
155
|
+
inputTokens: result.usage?.inputTokens ?? 0,
|
|
156
|
+
outputTokens: result.usage?.outputTokens ?? 0,
|
|
157
|
+
costMicroUsd: result.cost?.totalMicroUsd ?? 0,
|
|
158
|
+
latencyMs: result.latencyMs,
|
|
159
|
+
attempts: result.attempts,
|
|
160
|
+
textHash: result.textHash,
|
|
161
|
+
text: result.text,
|
|
162
|
+
},
|
|
163
|
+
...(citations !== undefined ? { citations } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async executeTool(
|
|
168
|
+
step: Extract<PlanStep, { kind: "tool" }>,
|
|
169
|
+
ctx: { principal: Principal; policy: PolicyContract; goal: string; requestId: () => string; budget: () => BudgetGuard },
|
|
170
|
+
): Promise<StepOutcome> {
|
|
171
|
+
if (this.opts.tools === null) {
|
|
172
|
+
return { ok: false, kind: "tool", error_code: "E1600", message: "no tool pipeline bound in this process — tool steps are unavailable" };
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const result = await this.opts.tools.invoke(this.toolHostField as ToolHost, {
|
|
176
|
+
tool: step.tool,
|
|
177
|
+
args: step.args,
|
|
178
|
+
principal: ctx.principal,
|
|
179
|
+
policy: ctx.policy,
|
|
180
|
+
requestId: ctx.requestId(),
|
|
181
|
+
intent: `agent tool step for goal: ${ctx.goal}`,
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
kind: "tool",
|
|
186
|
+
summary: `tool ${result.tool} completed (${result.resultHash.slice(0, 12)}…)`,
|
|
187
|
+
tool: { tool: result.tool, resultHash: result.resultHash, blobRef: result.blobRef?.hash ?? null },
|
|
188
|
+
};
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (isGatePrompt(err)) throw err; // human authority is not a failure
|
|
191
|
+
return { ok: false, kind: "tool", error_code: err instanceof VaerionError ? err.code : "E1900", message: (err as Error).message };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async executeNote(step: Extract<PlanStep, { kind: "note" }>): Promise<StepOutcome> {
|
|
196
|
+
const note = await this.opts.reasoning.note(step.text);
|
|
197
|
+
return { ok: true, kind: "note", summary: `note #${note.index}: ${note.text.slice(0, 80)}` };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async executeContext(
|
|
201
|
+
step: Extract<PlanStep, { kind: "context" }>,
|
|
202
|
+
ctx: { principal: Principal; policy: PolicyContract; goal: string; requestId: () => string; budget: () => BudgetGuard },
|
|
203
|
+
): Promise<StepOutcome> {
|
|
204
|
+
if (this.opts.research === null) {
|
|
205
|
+
return { ok: false, kind: "context", error_code: "E1600", message: "no research port bound in this process — context steps are unavailable" };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const prepared = await this.opts.research.prepare(step.query, step.capability);
|
|
209
|
+
this.availableCitations.push(...prepared.citation_ids);
|
|
210
|
+
return {
|
|
211
|
+
ok: true,
|
|
212
|
+
kind: "context",
|
|
213
|
+
summary: `context ${prepared.pack_fingerprint.slice(0, 12)}… (${prepared.blocks} blocks, ${prepared.evidence_count} evidence, ${prepared.citation_ids.length} citations)`,
|
|
214
|
+
};
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (isGatePrompt(err)) throw err; // human authority is not a failure
|
|
217
|
+
return { ok: false, kind: "context", error_code: err instanceof VaerionError ? err.code : "E1900", message: (err as Error).message };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Convert an outcome into a planner history item (newest-last fold input). */
|
|
223
|
+
export function historyItemOf(round: number, index: number, outcome: StepOutcome): PlannerHistoryItem {
|
|
224
|
+
return outcome.ok
|
|
225
|
+
? { round, index, kind: outcome.kind, ok: true, summary: outcome.summary }
|
|
226
|
+
: { round, index, kind: outcome.kind, ok: false, summary: outcome.message.slice(0, 160), error_code: outcome.error_code };
|
|
227
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vaerion — agent permission grants (MS-4 wiring).
|
|
3
|
+
*
|
|
4
|
+
* The agent principal acts inside the permission-graph ceiling like every
|
|
5
|
+
* other principal. Its grants are DERIVED from human declarations only:
|
|
6
|
+
*
|
|
7
|
+
* - tool.call: one grant over every DECLARED tool scope (config.tools) —
|
|
8
|
+
* declaring a tool in vaerion.yaml is the human's statement that this
|
|
9
|
+
* tool exists; the broker policy still decides allow/deny/prompt.
|
|
10
|
+
* - model.invoke: the DECLARED model scopes (enabled gateway providers'
|
|
11
|
+
* concrete `provider/model` ceilings) that a declared policy rule admits
|
|
12
|
+
* for agents — the human typed both the models and the rules; nothing
|
|
13
|
+
* is invented here.
|
|
14
|
+
*
|
|
15
|
+
* Fail-closed: declare nothing, grant nothing. Derived grants are concrete
|
|
16
|
+
* scope strings that sit INSIDE the standing ceilings (graphFromConfig
|
|
17
|
+
* enforces coverage), so grants only ever narrow.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { VaerionConfig } from "../config/config.ts";
|
|
21
|
+
import type { PolicyContract } from "../broker/contracts/decision.ts";
|
|
22
|
+
import type { ConfigGrantInput } from "../broker/engine.ts";
|
|
23
|
+
import type { Principal } from "../broker/contracts/principal.ts";
|
|
24
|
+
import { scopeMatches } from "../broker/contracts/capability.ts";
|
|
25
|
+
|
|
26
|
+
/** Does any declared rule admit `agentKind` for `domain` over `scope`? */
|
|
27
|
+
function policyAdmits(policy: PolicyContract, domain: string, scope: string, agentKind: Principal["kind"]): boolean {
|
|
28
|
+
return policy.rules.some((rule) => {
|
|
29
|
+
if (rule.domain !== domain && rule.domain !== "*") return false;
|
|
30
|
+
if (rule.effect === "deny") return false;
|
|
31
|
+
if (rule.principalKinds !== "all" && !rule.principalKinds.some((k) => k === agentKind)) return false;
|
|
32
|
+
return scopeMatches(rule.scope, scope);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Derive the agent's ceiling-internal grants from config + policy. */
|
|
37
|
+
export function agentGrants(config: VaerionConfig, policy: PolicyContract, agent: Principal): ConfigGrantInput[] {
|
|
38
|
+
const grants: ConfigGrantInput[] = [];
|
|
39
|
+
// Tool ceilings: declared tools + declared extensions only (declared-before-used
|
|
40
|
+
// law; an extension is reachable as a tool, so its name is its scope).
|
|
41
|
+
const toolScopes = [
|
|
42
|
+
...(config.tools ?? []).map((t) => t.scope ?? t.name),
|
|
43
|
+
...(config.extensions ?? []).map((e) => e.name),
|
|
44
|
+
];
|
|
45
|
+
if (toolScopes.length > 0) grants.push({ principalId: agent.id, domain: "tool.call", scopes: toolScopes });
|
|
46
|
+
// Model ceilings: concrete declared models admitted by declared policy.
|
|
47
|
+
const modelScopes: string[] = [];
|
|
48
|
+
for (const [provider, p] of Object.entries(config.gateway?.providers ?? {})) {
|
|
49
|
+
if (!p.enabled) continue;
|
|
50
|
+
for (const model of p.models ?? []) {
|
|
51
|
+
const scope = `${provider}/${model}`;
|
|
52
|
+
if (policyAdmits(policy, "model.invoke", scope, agent.kind)) modelScopes.push(scope);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (modelScopes.length > 0) grants.push({ principalId: agent.id, domain: "model.invoke", scopes: modelScopes });
|
|
56
|
+
return grants;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The builtin scopes the R-2 host bridge exposes to extensions. */
|
|
60
|
+
export const BRIDGEABLE_BUILTIN_SCOPES: ReadonlyArray<string> = ["echo", "clock.read"];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Derive the EXTENSION principal's ceiling-internal grants (MS-5, ADR-0009):
|
|
64
|
+
* an extension may bridge only to the declared builtins, and only where a
|
|
65
|
+
* declared policy rule admits `extension` for that scope. Declaring an
|
|
66
|
+
* extension grants nothing by itself — this mirrors agentGrants.
|
|
67
|
+
*/
|
|
68
|
+
export function extensionGrants(config: VaerionConfig, policy: PolicyContract): ConfigGrantInput[] {
|
|
69
|
+
if ((config.extensions ?? []).length === 0) return [];
|
|
70
|
+
const scopes = BRIDGEABLE_BUILTIN_SCOPES.filter((scope) => policyAdmits(policy, "tool.call", scope, "extension"));
|
|
71
|
+
if (scopes.length === 0) return [];
|
|
72
|
+
return (config.extensions ?? []).map((ext) => ({ principalId: `extension:${ext.name}`, domain: "tool.call", scopes: [...scopes] }));
|
|
73
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vaerion — agents subsystem barrel (MS-4).
|
|
3
|
+
*
|
|
4
|
+
* Agent runtime (supervisor over journaled decisions), tool invocation
|
|
5
|
+
* pipeline, planners, reasoning sessions, metrics, and the local research
|
|
6
|
+
* port. Everything here is L2: it may import L0/L1/L2, never L4.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
ToolHost,
|
|
11
|
+
ToolDeclaration,
|
|
12
|
+
ToolArgsSchema,
|
|
13
|
+
ToolExecutor,
|
|
14
|
+
ToolContext,
|
|
15
|
+
ToolInvokeInput,
|
|
16
|
+
ToolInvokeResult,
|
|
17
|
+
SearchHit,
|
|
18
|
+
SearchableIndex,
|
|
19
|
+
} from "./tools.ts";
|
|
20
|
+
export {
|
|
21
|
+
ToolRegistry,
|
|
22
|
+
ToolInvocationService,
|
|
23
|
+
ToolGatePrompt,
|
|
24
|
+
echoTool,
|
|
25
|
+
clockReadTool,
|
|
26
|
+
researchSearchTool,
|
|
27
|
+
} from "./tools.ts";
|
|
28
|
+
|
|
29
|
+
export type { PlanStep, Plan, Planner, PlannerInput, PlannerHistoryItem } from "./planner.ts";
|
|
30
|
+
export {
|
|
31
|
+
InlinePlanner,
|
|
32
|
+
ModelPlanner,
|
|
33
|
+
parsePlanText,
|
|
34
|
+
assertPlan,
|
|
35
|
+
type GatewayHostLike,
|
|
36
|
+
type ModelPlannerOptions,
|
|
37
|
+
type InlinePlannerOptions,
|
|
38
|
+
} from "./planner.ts";
|
|
39
|
+
|
|
40
|
+
export type { ReasoningNote, ReasoningFold, ReasoningState, ReasoningHost } from "./reasoning.ts";
|
|
41
|
+
export {
|
|
42
|
+
ReasoningSession,
|
|
43
|
+
initialReasoningState,
|
|
44
|
+
reasoningStateReducer,
|
|
45
|
+
unfoldedNotes,
|
|
46
|
+
foldSummary,
|
|
47
|
+
} from "./reasoning.ts";
|
|
48
|
+
|
|
49
|
+
export type { StepOutcome, ResearchPort } from "./executor.ts";
|
|
50
|
+
export { StepExecutor, historyItemOf } from "./executor.ts";
|
|
51
|
+
|
|
52
|
+
export type { AgentOutcome, AgentRunState, AgentRuntimeOptions, AgentRunInput, AgentRunResult } from "./runtime.ts";
|
|
53
|
+
export { AgentRuntime, initialAgentRunState, agentRunStateReducer, agentStateFromRecords } from "./runtime.ts";
|
|
54
|
+
|
|
55
|
+
export type { AgentMetrics } from "./metrics.ts";
|
|
56
|
+
export { agentMetricsFromRecords } from "./metrics.ts";
|
|
57
|
+
|
|
58
|
+
export type { LocalResearchPortOptions } from "./research-port.ts";
|
|
59
|
+
export { LocalResearchPort } from "./research-port.ts";
|
|
60
|
+
|
|
61
|
+
export { agentGrants } from "./grants.ts";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vaerion — agent metrics (MS-4).
|
|
3
|
+
*
|
|
4
|
+
* A pure fold over journal records — the ONLY accounting there is. Latency,
|
|
5
|
+
* cost, tokens, success, failures, retries, and tool activity all come from
|
|
6
|
+
* the spine events the runtime already journaled (R-RT2: state is a fold of
|
|
7
|
+
* the journal; no counters live anywhere else). Integer micro-USD throughout.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { JournalRecord } from "../journal/records.ts";
|
|
11
|
+
|
|
12
|
+
export interface AgentMetrics {
|
|
13
|
+
run: {
|
|
14
|
+
started: boolean;
|
|
15
|
+
outcome: "goal" | "step_limit" | "failed" | "awaiting_gate" | null;
|
|
16
|
+
steps: number;
|
|
17
|
+
failures: number;
|
|
18
|
+
retries: number;
|
|
19
|
+
};
|
|
20
|
+
model: {
|
|
21
|
+
invocations: number;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
costMicroUsd: number;
|
|
25
|
+
latencyMs: number;
|
|
26
|
+
attempts: number;
|
|
27
|
+
};
|
|
28
|
+
tools: {
|
|
29
|
+
requested: number;
|
|
30
|
+
completed: number;
|
|
31
|
+
denied: number;
|
|
32
|
+
failed: number;
|
|
33
|
+
};
|
|
34
|
+
context: {
|
|
35
|
+
packs: number;
|
|
36
|
+
notes: number;
|
|
37
|
+
folds: number;
|
|
38
|
+
};
|
|
39
|
+
gates: {
|
|
40
|
+
opened: number;
|
|
41
|
+
resolved: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Fold agent metrics from journal records (order-free, deterministic). */
|
|
46
|
+
export function agentMetricsFromRecords(records: ReadonlyArray<JournalRecord>): AgentMetrics {
|
|
47
|
+
const m: AgentMetrics = {
|
|
48
|
+
run: { started: false, outcome: null, steps: 0, failures: 0, retries: 0 },
|
|
49
|
+
model: { invocations: 0, inputTokens: 0, outputTokens: 0, costMicroUsd: 0, latencyMs: 0, attempts: 0 },
|
|
50
|
+
tools: { requested: 0, completed: 0, denied: 0, failed: 0 },
|
|
51
|
+
context: { packs: 0, notes: 0, folds: 0 },
|
|
52
|
+
gates: { opened: 0, resolved: 0 },
|
|
53
|
+
};
|
|
54
|
+
for (const rec of records) {
|
|
55
|
+
if (rec.k !== "evt") {
|
|
56
|
+
if (rec.k === "gate") {
|
|
57
|
+
if (rec.gate.state === "open") m.gates.opened++;
|
|
58
|
+
else if (rec.gate.state === "resolved") m.gates.resolved++;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const p = rec.env.payload as Record<string, unknown>;
|
|
63
|
+
const int = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? Math.max(0, Math.round(v)) : 0);
|
|
64
|
+
switch (rec.env.type) {
|
|
65
|
+
case "agent.run.started":
|
|
66
|
+
m.run.started = true;
|
|
67
|
+
break;
|
|
68
|
+
case "agent.run.completed":
|
|
69
|
+
m.run.outcome = (typeof p.outcome === "string" ? p.outcome : null) as AgentMetrics["run"]["outcome"];
|
|
70
|
+
break;
|
|
71
|
+
case "agent.step.recorded": {
|
|
72
|
+
m.run.steps++;
|
|
73
|
+
if (typeof p.attempt === "number" && p.attempt > 1) m.run.retries += p.attempt - 1;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "agent.step.failed": {
|
|
77
|
+
m.run.failures++;
|
|
78
|
+
const attempts = int(p.attempts);
|
|
79
|
+
if (attempts > 1) m.run.retries += attempts - 1;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "gateway.invoke.recorded": {
|
|
83
|
+
// THE metering truth: every model invocation crosses the single gate,
|
|
84
|
+
// so tokens/cost/latency fold from ITS records only (never from step
|
|
85
|
+
// events — that would double-count planner + step spend).
|
|
86
|
+
const usage = p.usage as { inputTokens?: unknown; outputTokens?: unknown } | null | undefined;
|
|
87
|
+
const cost = p.cost as { totalMicroUsd?: unknown } | null | undefined;
|
|
88
|
+
m.model.invocations++;
|
|
89
|
+
m.model.inputTokens += int(usage?.inputTokens);
|
|
90
|
+
m.model.outputTokens += int(usage?.outputTokens);
|
|
91
|
+
m.model.costMicroUsd += int(cost?.totalMicroUsd);
|
|
92
|
+
m.model.latencyMs += int(p.latency_ms);
|
|
93
|
+
m.model.attempts += int(p.attempts);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "gateway.invoke.failed":
|
|
97
|
+
m.model.invocations++;
|
|
98
|
+
m.model.attempts += int(p.attempts);
|
|
99
|
+
break;
|
|
100
|
+
case "tool.call.requested":
|
|
101
|
+
m.tools.requested++;
|
|
102
|
+
break;
|
|
103
|
+
case "tool.call.completed": {
|
|
104
|
+
m.tools.completed++;
|
|
105
|
+
if (p.ok === false) m.tools.failed++;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "tool.call.denied":
|
|
109
|
+
m.tools.denied++;
|
|
110
|
+
break;
|
|
111
|
+
case "research.context.prepared":
|
|
112
|
+
m.context.packs++;
|
|
113
|
+
break;
|
|
114
|
+
case "reasoning.note.recorded":
|
|
115
|
+
m.context.notes++;
|
|
116
|
+
break;
|
|
117
|
+
case "reasoning.folded":
|
|
118
|
+
m.context.folds++;
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return m;
|
|
125
|
+
}
|