open-multi-agent-kit 0.79.3 → 0.80.2
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 +38 -0
- package/MATURITY.md +2 -2
- package/README.md +86 -39
- package/ROADMAP.md +20 -17
- package/dist/brand/night-city.theme.json +122 -24
- package/dist/brand/rust-forge.theme.json +1 -1
- package/dist/commands/chat/native-root-loop.d.ts +27 -3
- package/dist/commands/chat/native-root-loop.js +370 -49
- package/dist/commands/dag.js +45 -0
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/verify.js +59 -1
- package/dist/contracts/dag.d.ts +9 -1
- package/dist/evidence/graph-ontology-relation-gate.d.ts +10 -0
- package/dist/evidence/graph-ontology-relation-gate.js +46 -0
- package/dist/evidence/headroom-replay-verifier.d.ts +19 -0
- package/dist/evidence/headroom-replay-verifier.js +86 -0
- package/dist/memory/local-graph-memory-store.d.ts +75 -0
- package/dist/memory/local-graph-memory-store.js +570 -0
- package/dist/orchestration/dag.d.ts +22 -1
- package/dist/orchestration/evidence-gate.js +12 -0
- package/dist/orchestration/executor.js +111 -15
- package/dist/orchestration/loop-controller-headroom.d.ts +6 -0
- package/dist/orchestration/loop-controller-headroom.js +33 -0
- package/dist/orchestration/scheduler.d.ts +2 -0
- package/dist/orchestration/scheduler.js +3 -0
- package/dist/orchestration/task-graph.d.ts +17 -0
- package/dist/orchestration/task-graph.js +38 -0
- package/dist/providers/codex-cli-runner.js +17 -2
- package/dist/runtime/agent-runtime.d.ts +32 -3
- package/dist/runtime/agent-runtime.js +20 -7
- package/dist/runtime/authority-matrix.d.ts +23 -0
- package/dist/runtime/authority-matrix.js +171 -0
- package/dist/runtime/chat-advisory-runtime.js +3 -0
- package/dist/runtime/codex-cli-runtime.js +2 -0
- package/dist/runtime/codex-runtime.d.ts +4 -1
- package/dist/runtime/codex-runtime.js +34 -12
- package/dist/runtime/context-broker-converter.js +32 -2
- package/dist/runtime/context-broker.js +18 -0
- package/dist/runtime/contracts/evidence.d.ts +52 -0
- package/dist/runtime/contracts/evidence.js +138 -1
- package/dist/runtime/contracts/shared.d.ts +27 -0
- package/dist/runtime/data-retention-gate.d.ts +42 -0
- package/dist/runtime/data-retention-gate.js +123 -0
- package/dist/runtime/deepseek-runtime.d.ts +5 -1
- package/dist/runtime/deepseek-runtime.js +13 -7
- package/dist/runtime/external-cli-adapter.js +20 -7
- package/dist/runtime/freedomd-evidence-envelope.d.ts +57 -0
- package/dist/runtime/freedomd-evidence-envelope.js +110 -0
- package/dist/runtime/freedomd-exception.d.ts +32 -0
- package/dist/runtime/freedomd-exception.js +95 -0
- package/dist/runtime/freedomd-incidents.d.ts +23 -0
- package/dist/runtime/freedomd-incidents.js +103 -0
- package/dist/runtime/freedomd-policy.d.ts +35 -0
- package/dist/runtime/freedomd-policy.js +114 -0
- package/dist/runtime/freedomd-router.d.ts +66 -0
- package/dist/runtime/freedomd-router.js +433 -0
- package/dist/runtime/headroom-aware-loop-decision.d.ts +21 -0
- package/dist/runtime/headroom-aware-loop-decision.js +67 -0
- package/dist/runtime/headroom-loop-risk.d.ts +19 -0
- package/dist/runtime/headroom-loop-risk.js +43 -0
- package/dist/runtime/headroom-policy.d.ts +14 -1
- package/dist/runtime/headroom-policy.js +74 -12
- package/dist/runtime/kimi-api-runtime.d.ts +4 -1
- package/dist/runtime/kimi-api-runtime.js +35 -11
- package/dist/runtime/kimi-print-runtime.js +21 -0
- package/dist/runtime/kimi-wire-protocol-runtime.js +17 -1
- package/dist/runtime/local-llm-runtime.d.ts +5 -1
- package/dist/runtime/local-llm-runtime.js +14 -23
- package/dist/runtime/private-stderr.d.ts +13 -0
- package/dist/runtime/private-stderr.js +69 -0
- package/dist/runtime/prompt-envelope.d.ts +1 -1
- package/dist/runtime/provider-sovereignty.d.ts +81 -0
- package/dist/runtime/provider-sovereignty.js +285 -0
- package/dist/runtime/runtime-backed-task-runner.d.ts +1 -0
- package/dist/runtime/runtime-backed-task-runner.js +282 -10
- package/dist/runtime/runtime-bootstrap.d.ts +12 -0
- package/dist/runtime/runtime-bootstrap.js +54 -11
- package/dist/runtime/runtime-failure-classifier.d.ts +15 -0
- package/dist/runtime/runtime-failure-classifier.js +107 -0
- package/dist/runtime/runtime-health-probes.d.ts +30 -0
- package/dist/runtime/runtime-health-probes.js +102 -0
- package/dist/runtime/runtime-router.d.ts +6 -2
- package/dist/runtime/runtime-router.js +575 -53
- package/dist/runtime/structured-compaction.d.ts +107 -0
- package/dist/runtime/structured-compaction.js +414 -0
- package/dist/runtime/tool-dispatch-contracts.d.ts +7 -5
- package/dist/runtime/tool-dispatch-contracts.js +15 -4
- package/dist/runtime/tool-plane.d.ts +1 -1
- package/dist/runtime/tool-plane.js +49 -1
- package/dist/runtime/worker-manifest.js +1 -1
- package/dist/schema/evidence-bundle.schema.d.ts +8 -8
- package/dist/schema/proof-bundle.schema.d.ts +2 -2
- package/dist/util/hash.d.ts +2 -0
- package/dist/util/hash.js +13 -0
- package/dist/util/session.js +5 -4
- package/docs/2026-06-16/critical-issues.md +19 -0
- package/docs/2026-06-16/improvements.md +15 -0
- package/docs/2026-06-16/init-checklist.md +25 -0
- package/docs/2026-06-16/plan.md +20 -0
- package/docs/2026-06-17/critical-issues.md +19 -0
- package/docs/2026-06-17/improvements.md +15 -0
- package/docs/2026-06-17/init-checklist.md +25 -0
- package/docs/2026-06-17/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +2 -1
- package/docs/GSTACK_MIGRATION.md +1 -1
- package/docs/algorithm-hardening-playbook.md +219 -0
- package/docs/claims.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/native-root-runtime-hardening.md +42 -32
- package/docs/post-0793-hardening-roadmap.md +123 -0
- package/docs/provider-maturity.md +18 -16
- package/docs/versioning.md +3 -3
- package/docs/what-is-omk.md +1 -1
- package/package.json +8 -4
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +2 -0
- package/readmeasset/ASSET_PROVENANCE.md +40 -20
- package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +1 -1
- package/readmeasset/omk-control-surfaces.svg +1 -1
- package/readmeasset/omk-core-loop.svg +1 -1
- package/readmeasset/omk-freedomd-control-plane.svg +59 -0
- package/readmeasset/omk-freedomd-control-plane.webp +0 -0
- package/readmeasset/omk-init-control-loop.svg +1 -1
- package/readmeasset/omk-logo-mark.svg +1 -1
- package/readmeasset/omk-parallel-subagents.svg +1 -1
- package/readmeasset/omk-release-assertions.svg +1 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd runtime router.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the base RuntimeRouter with provider-sovereignty scoring, data-retention
|
|
5
|
+
* gating, cutoff-resilient fallback, degraded-mode planning, and local-first
|
|
6
|
+
* evidence envelopes. This is the control-plane boundary that keeps OMK loops
|
|
7
|
+
* alive when a provider becomes unavailable or policy-blocked.
|
|
8
|
+
*/
|
|
9
|
+
import { createRuntimeRouter } from "./runtime-router.js";
|
|
10
|
+
import { computeRuntimeCapabilityScore } from "./runtime-router.js";
|
|
11
|
+
import { compileFreedomdPolicy } from "./freedomd-policy.js";
|
|
12
|
+
import { buildProviderSovereigntyProfiles, computeProviderSovereigntyScore, lookupSovereigntyProfile, } from "./provider-sovereignty.js";
|
|
13
|
+
import { evaluateDataRetentionGate } from "./data-retention-gate.js";
|
|
14
|
+
import { buildLocalFirstEvidenceEnvelope } from "./freedomd-evidence-envelope.js";
|
|
15
|
+
import { runtimeProviderId, runtimeModeOf } from "./authority-matrix.js";
|
|
16
|
+
import { maskSensitiveText } from "../util/secret-mask.js";
|
|
17
|
+
import { loadProviderIncidents } from "./freedomd-incidents.js";
|
|
18
|
+
import { requestProviderException } from "./freedomd-exception.js";
|
|
19
|
+
function severityOrdinal(severity) {
|
|
20
|
+
if (severity === "block")
|
|
21
|
+
return 2;
|
|
22
|
+
if (severity === "warn")
|
|
23
|
+
return 1;
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
function hasLocalRuntime(runtimes) {
|
|
27
|
+
return runtimes.some((r) => runtimeProviderId(r) === "local-llm" || r.id.startsWith("local-"));
|
|
28
|
+
}
|
|
29
|
+
export function evaluateTaskSovereignty(task, policy, runtimes, incidents) {
|
|
30
|
+
const localAvailable = hasLocalRuntime(runtimes);
|
|
31
|
+
const candidateProviderIds = new Set(runtimes.map((r) => runtimeProviderId(r)));
|
|
32
|
+
const matchedIncidents = (incidents ?? []).filter((i) => candidateProviderIds.has(i.providerId));
|
|
33
|
+
const incident = matchedIncidents.sort((a, b) => severityOrdinal(b.severity) - severityOrdinal(a.severity))[0];
|
|
34
|
+
const cutoffRisk = incident?.severity === "block" ? 1.0 : incident?.severity === "warn" ? 0.5 : 0.0;
|
|
35
|
+
let retentionDecision = "allow";
|
|
36
|
+
if (policy.mode === "strict" && !localAvailable) {
|
|
37
|
+
retentionDecision = "block";
|
|
38
|
+
}
|
|
39
|
+
else if (!policy.allowExportRestrictedProvider && incident?.kind === "export-control") {
|
|
40
|
+
retentionDecision = "block";
|
|
41
|
+
}
|
|
42
|
+
let jurisdictionDecision = "allow";
|
|
43
|
+
if (incident?.kind === "jurisdiction" || incident?.kind === "export-control") {
|
|
44
|
+
jurisdictionDecision = incident.severity === "block" ? "block" : "downgrade";
|
|
45
|
+
}
|
|
46
|
+
const reasonParts = [`mode=${policy.mode}`];
|
|
47
|
+
if (incident)
|
|
48
|
+
reasonParts.push(`incident=${incident.kind}:${incident.severity}`);
|
|
49
|
+
if (!localAvailable)
|
|
50
|
+
reasonParts.push("no-local-fallback");
|
|
51
|
+
return {
|
|
52
|
+
mode: policy.mode === "off" ? "standard" : "freedomd",
|
|
53
|
+
dataBoundary: "internal",
|
|
54
|
+
retentionDecision,
|
|
55
|
+
jurisdictionDecision,
|
|
56
|
+
providerCutoffRisk: cutoffRisk,
|
|
57
|
+
localFallbackAvailable: localAvailable,
|
|
58
|
+
reason: reasonParts.join("; "),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function classifyFreedomdIntent(task, capsule) {
|
|
62
|
+
const text = `${task.context.nodeId} ${task.context.goal ?? ""} ${task.prompt} ${task.context.role ?? ""} ${capsule?.node?.role ?? ""}`.toLowerCase();
|
|
63
|
+
const role = (task.context.role ?? capsule?.node?.role ?? "").toLowerCase();
|
|
64
|
+
if (/debug|fix|error|failure|bug|trace/.test(text) || role === "debugger")
|
|
65
|
+
return "debugging";
|
|
66
|
+
if (/review|audit|check|validate|verify/.test(text) || role === "reviewer")
|
|
67
|
+
return "review";
|
|
68
|
+
if (/test|spec|coverage|assertion/.test(text) || role === "tester")
|
|
69
|
+
return "test-generation";
|
|
70
|
+
if (/refactor|optimize|clean|improve|simplify/.test(text) || role === "refactor")
|
|
71
|
+
return "refactor";
|
|
72
|
+
if (/research|investigate|explore|search|discover|analyze/.test(text) || role === "researcher")
|
|
73
|
+
return "research";
|
|
74
|
+
if (/plan|design|architect|strategy|roadmap/.test(text) || role === "planner")
|
|
75
|
+
return "planning";
|
|
76
|
+
if (/doc|readme|changelog|comment/.test(text) || role === "documenter")
|
|
77
|
+
return "documentation";
|
|
78
|
+
if (/shell|command|run|exec|script/.test(text) || role === "shell")
|
|
79
|
+
return "shell-operation";
|
|
80
|
+
return "coding";
|
|
81
|
+
}
|
|
82
|
+
function runtimePriorityScore(runtime) {
|
|
83
|
+
return Math.max(0, Math.min(1, runtime.priority / 100));
|
|
84
|
+
}
|
|
85
|
+
export function scoreRuntimesWithSovereignty(task, runtimes, policy, options = {}) {
|
|
86
|
+
const profiles = options.profiles ?? buildProviderSovereigntyProfiles();
|
|
87
|
+
const incidents = options.incidents ?? [];
|
|
88
|
+
const intent = classifyFreedomdIntent(task, options.capsule);
|
|
89
|
+
const scored = [];
|
|
90
|
+
for (const runtime of runtimes) {
|
|
91
|
+
const profile = lookupSovereigntyProfile(runtime, profiles);
|
|
92
|
+
if (!profile)
|
|
93
|
+
continue;
|
|
94
|
+
const sovereignty = computeProviderSovereigntyScore(runtime, task, {
|
|
95
|
+
profile,
|
|
96
|
+
userCountry: options.userCountry,
|
|
97
|
+
taskRisk: task.safety?.risk,
|
|
98
|
+
incidents,
|
|
99
|
+
localAvailable: runtimeProviderId(runtime) === "local-llm" || runtime.id.startsWith("local-"),
|
|
100
|
+
});
|
|
101
|
+
if (policy.mode !== "off") {
|
|
102
|
+
if (sovereignty.diagnostics.jurisdictionRisk > policy.maxJurisdictionRisk)
|
|
103
|
+
continue;
|
|
104
|
+
if (sovereignty.diagnostics.retentionRisk > policy.maxRetentionRisk)
|
|
105
|
+
continue;
|
|
106
|
+
if (sovereignty.diagnostics.cutoffRisk > policy.maxCutoffRisk)
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const retention = evaluateDataRetentionGate({
|
|
110
|
+
task,
|
|
111
|
+
runtime,
|
|
112
|
+
providerProfile: profile,
|
|
113
|
+
orgMaxRetentionDays: policy.maxRetentionDays,
|
|
114
|
+
allowRedaction: options.allowRedaction ?? false,
|
|
115
|
+
});
|
|
116
|
+
if (retention.decision === "block")
|
|
117
|
+
continue;
|
|
118
|
+
const capabilityScore = computeRuntimeCapabilityScore(runtime, intent);
|
|
119
|
+
const priorityScore = runtimePriorityScore(runtime);
|
|
120
|
+
let capabilityWeight;
|
|
121
|
+
let priorityWeight;
|
|
122
|
+
let sovereigntyWeight;
|
|
123
|
+
if (policy.mode === "strict") {
|
|
124
|
+
capabilityWeight = 0.3;
|
|
125
|
+
priorityWeight = 0.1;
|
|
126
|
+
sovereigntyWeight = 0.6;
|
|
127
|
+
const isLocal = runtimeProviderId(runtime) === "local-llm" || runtime.id.startsWith("local-");
|
|
128
|
+
if (isLocal) {
|
|
129
|
+
sovereigntyWeight = 0.5;
|
|
130
|
+
capabilityWeight = 0.4;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (policy.mode === "balanced") {
|
|
134
|
+
capabilityWeight = 0.5;
|
|
135
|
+
priorityWeight = 0.2;
|
|
136
|
+
sovereigntyWeight = 0.3;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
capabilityWeight = 0.7;
|
|
140
|
+
priorityWeight = 0.3;
|
|
141
|
+
sovereigntyWeight = 0.0;
|
|
142
|
+
}
|
|
143
|
+
const score = capabilityWeight * capabilityScore +
|
|
144
|
+
priorityWeight * priorityScore +
|
|
145
|
+
sovereigntyWeight * sovereignty.score;
|
|
146
|
+
scored.push({ runtime, score, sovereignty, retention });
|
|
147
|
+
}
|
|
148
|
+
return scored.sort((a, b) => b.score - a.score);
|
|
149
|
+
}
|
|
150
|
+
export function buildFreedomdDegradedPlan(task, reason, runtimes) {
|
|
151
|
+
const localAuthority = runtimes.find((r) => {
|
|
152
|
+
const pid = runtimeProviderId(r);
|
|
153
|
+
return (pid === "local-llm" || r.id.startsWith("local-")) && (r.capabilities?.write || r.capabilities?.shell);
|
|
154
|
+
});
|
|
155
|
+
if (localAuthority) {
|
|
156
|
+
return {
|
|
157
|
+
mode: "local-authority",
|
|
158
|
+
runtime: localAuthority,
|
|
159
|
+
allowedCapabilities: ["read", "write", "patch", "shell"],
|
|
160
|
+
reason,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const localRead = runtimes.find((r) => runtimeProviderId(r) === "local-llm" || r.id.startsWith("local-"));
|
|
164
|
+
if (localRead) {
|
|
165
|
+
return {
|
|
166
|
+
mode: "read-only-local-review",
|
|
167
|
+
runtime: localRead,
|
|
168
|
+
allowedCapabilities: ["read", "review"],
|
|
169
|
+
reason,
|
|
170
|
+
blockedOriginalAuthority: Object.entries(task.capabilities)
|
|
171
|
+
.filter(([, v]) => v === true)
|
|
172
|
+
.map(([k]) => k),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
mode: "blocked",
|
|
177
|
+
allowedCapabilities: [],
|
|
178
|
+
reason,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function agentResultToRunResult(result, runtimeId) {
|
|
182
|
+
return {
|
|
183
|
+
success: result.exitCode === 0,
|
|
184
|
+
exitCode: result.exitCode,
|
|
185
|
+
stdout: result.output,
|
|
186
|
+
stderr: "",
|
|
187
|
+
metadata: { ...result.metadata, runtime: runtimeId },
|
|
188
|
+
tokenUsage: result.tokenUsage,
|
|
189
|
+
toolCalls: result.toolCalls,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export async function createFreedomdRuntimeRouter(options = {}) {
|
|
193
|
+
const policy = compileFreedomdPolicy({
|
|
194
|
+
taskFlags: options.freedomdMode ? { mode: options.freedomdMode } : undefined,
|
|
195
|
+
});
|
|
196
|
+
const profiles = options.sovereigntyProfiles ?? buildProviderSovereigntyProfiles();
|
|
197
|
+
const loadedIncidents = options.autoLoadIncidents !== false ? await loadProviderIncidents({ projectRoot: options.projectRoot, feedUrls: policy.incidentFeedUrls }) : [];
|
|
198
|
+
const incidents = options.incidents ?? loadedIncidents;
|
|
199
|
+
const graphStore = options.graphStore;
|
|
200
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
201
|
+
const baseOptions = {
|
|
202
|
+
runtimes: options.runtimes,
|
|
203
|
+
fallbackChain: options.fallbackChain,
|
|
204
|
+
memoryPath: options.memoryPath,
|
|
205
|
+
};
|
|
206
|
+
const baseRouter = createRuntimeRouter(baseOptions);
|
|
207
|
+
async function materialize(runId, nodeId, route, envelope) {
|
|
208
|
+
if (!graphStore)
|
|
209
|
+
return;
|
|
210
|
+
try {
|
|
211
|
+
await graphStore.materializeFreedomdSovereignty({
|
|
212
|
+
runId,
|
|
213
|
+
nodeId,
|
|
214
|
+
providerId: route.selectedRuntime ? runtimeProviderId(route.selectedRuntime) : "none",
|
|
215
|
+
runtimeMode: route.selectedRuntime ? runtimeModeOf(route.selectedRuntime) : "none",
|
|
216
|
+
sovereignty: route.sovereignty,
|
|
217
|
+
degradedMode: route.degradedPlan?.mode,
|
|
218
|
+
incident: route.sovereignty.providerCutoffRisk >= 0.5
|
|
219
|
+
? { kind: "cutoff", severity: route.sovereignty.providerCutoffRisk >= 1 ? "block" : "warn", reason: route.sovereignty.reason }
|
|
220
|
+
: undefined,
|
|
221
|
+
});
|
|
222
|
+
if (envelope) {
|
|
223
|
+
await graphStore.materializeFreedomdEvidenceEnvelope({
|
|
224
|
+
runId,
|
|
225
|
+
nodeId,
|
|
226
|
+
envelopePath: `.omk/runs/${runId}/freedomd/${nodeId}-evidence-envelope.json`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Graph materialization is best-effort; do not block turn execution.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function freedomdRoute(task, capsule) {
|
|
235
|
+
const sovereignty = evaluateTaskSovereignty(task, policy, options.runtimes ?? [], incidents);
|
|
236
|
+
if (sovereignty.retentionDecision === "block" || sovereignty.jurisdictionDecision === "block") {
|
|
237
|
+
const degraded = buildFreedomdDegradedPlan(task, sovereignty.reason, options.runtimes ?? []);
|
|
238
|
+
return {
|
|
239
|
+
fallbackChain: [],
|
|
240
|
+
degradedPlan: degraded,
|
|
241
|
+
sovereignty,
|
|
242
|
+
diagnostics: `blocked; degraded=${degraded.mode}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const scored = scoreRuntimesWithSovereignty(task, options.runtimes ?? [], policy, {
|
|
246
|
+
profiles,
|
|
247
|
+
incidents,
|
|
248
|
+
userCountry: options.userCountry,
|
|
249
|
+
allowRedaction: options.allowRedaction,
|
|
250
|
+
capsule,
|
|
251
|
+
});
|
|
252
|
+
if (scored.length === 0) {
|
|
253
|
+
const degraded = buildFreedomdDegradedPlan(task, "NO_FREEDOMD_COMPATIBLE_RUNTIME", options.runtimes ?? []);
|
|
254
|
+
if (policy.allowProviderExceptions && options.isTty !== false) {
|
|
255
|
+
const blocked = options.runtimes?.[0];
|
|
256
|
+
if (blocked) {
|
|
257
|
+
const profile = lookupSovereigntyProfile(blocked, profiles);
|
|
258
|
+
const exception = await requestProviderException({
|
|
259
|
+
providerId: runtimeProviderId(blocked),
|
|
260
|
+
runtimeMode: runtimeModeOf(blocked),
|
|
261
|
+
blockedReason: "NO_FREEDOMD_COMPATIBLE_RUNTIME",
|
|
262
|
+
retentionDays: profile?.retentionDays ?? 0,
|
|
263
|
+
jurisdictionRisk: profile ? computeProviderSovereigntyScore(blocked, task, { profile, incidents }).diagnostics.jurisdictionRisk : 0,
|
|
264
|
+
localFallbackAvailable: hasLocalRuntime(options.runtimes ?? []),
|
|
265
|
+
taskSensitivity: task.safety?.risk ?? "unknown",
|
|
266
|
+
}, {
|
|
267
|
+
runId: task.context.runId,
|
|
268
|
+
nodeId: task.context.nodeId,
|
|
269
|
+
projectRoot,
|
|
270
|
+
graphStore,
|
|
271
|
+
isTty: options.isTty,
|
|
272
|
+
promptUser: options.promptUser,
|
|
273
|
+
});
|
|
274
|
+
if (exception.decision === "once" || exception.decision === "run") {
|
|
275
|
+
return {
|
|
276
|
+
selectedRuntime: blocked,
|
|
277
|
+
fallbackChain: [],
|
|
278
|
+
sovereignty,
|
|
279
|
+
diagnostics: `provider exception ${exception.decision} for ${blocked.id}`,
|
|
280
|
+
providerException: exception,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
fallbackChain: [],
|
|
287
|
+
degradedPlan: degraded,
|
|
288
|
+
sovereignty,
|
|
289
|
+
diagnostics: "no compatible runtime; degraded",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const selected = scored[0];
|
|
293
|
+
const fallbackChain = scored.slice(1).map((s) => s.runtime);
|
|
294
|
+
if (selected.retention.decision === "downgrade") {
|
|
295
|
+
const degraded = buildFreedomdDegradedPlan(task, selected.retention.reason, options.runtimes ?? []);
|
|
296
|
+
return {
|
|
297
|
+
selectedRuntime: selected.runtime,
|
|
298
|
+
fallbackChain,
|
|
299
|
+
degradedPlan: degraded,
|
|
300
|
+
sovereignty,
|
|
301
|
+
diagnostics: `retention downgrade; selected=${selected.runtime.id}; fallback=${fallbackChain.map((r) => r.id).join(",")}`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
selectedRuntime: selected.runtime,
|
|
306
|
+
fallbackChain,
|
|
307
|
+
sovereignty,
|
|
308
|
+
diagnostics: `selected=${selected.runtime.id}; sovereignty=${selected.sovereignty.score.toFixed(2)}; fallback=${fallbackChain.map((r) => r.id).join(",")}`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function executeTask(task, capsule, signal) {
|
|
312
|
+
const route = await freedomdRoute(task, capsule);
|
|
313
|
+
return runFreedomdRoute(task, capsule, route, signal);
|
|
314
|
+
}
|
|
315
|
+
async function runFreedomdRoute(task, capsule, route, signal) {
|
|
316
|
+
const runId = task.context.runId;
|
|
317
|
+
const nodeId = task.context.nodeId;
|
|
318
|
+
if (route.degradedPlan && !route.selectedRuntime) {
|
|
319
|
+
const result = {
|
|
320
|
+
success: false,
|
|
321
|
+
exitCode: 78,
|
|
322
|
+
stdout: "",
|
|
323
|
+
stderr: `Freedomd degraded mode: ${route.degradedPlan.mode}. ${route.degradedPlan.reason}`,
|
|
324
|
+
metadata: {
|
|
325
|
+
freedomd: true,
|
|
326
|
+
degradedMode: route.degradedPlan.mode,
|
|
327
|
+
sovereignty: route.sovereignty,
|
|
328
|
+
remediation: [
|
|
329
|
+
"Enable local-llm runtime",
|
|
330
|
+
"Configure an authority-capable CLI runtime",
|
|
331
|
+
"Lower task authority to read/review",
|
|
332
|
+
"Provide user-approved provider exception",
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
await materialize(runId, nodeId, route);
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
const primary = route.selectedRuntime;
|
|
340
|
+
const allCandidates = [primary, ...route.fallbackChain];
|
|
341
|
+
let lastError;
|
|
342
|
+
for (const runtime of allCandidates) {
|
|
343
|
+
if (signal.aborted) {
|
|
344
|
+
return {
|
|
345
|
+
success: false,
|
|
346
|
+
exitCode: 130,
|
|
347
|
+
stdout: "",
|
|
348
|
+
stderr: "Aborted before execution",
|
|
349
|
+
metadata: { runtime: runtime.id, aborted: true, sovereignty: route.sovereignty },
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const rawResult = runtime.execute
|
|
354
|
+
? await runtime.execute(task)
|
|
355
|
+
: await runtime.runNode(capsule, signal);
|
|
356
|
+
const result = "stdout" in rawResult ? rawResult : agentResultToRunResult(rawResult, runtime.id);
|
|
357
|
+
const safeResult = {
|
|
358
|
+
success: result.success,
|
|
359
|
+
exitCode: result.exitCode,
|
|
360
|
+
stdout: result.stdout,
|
|
361
|
+
stderr: maskSensitiveText(result.stderr),
|
|
362
|
+
metadata: {
|
|
363
|
+
...result.metadata,
|
|
364
|
+
selectedRuntime: runtime.id,
|
|
365
|
+
sovereignty: route.sovereignty,
|
|
366
|
+
fallbackChain: allCandidates.map((r) => r.id),
|
|
367
|
+
freedomd: true,
|
|
368
|
+
},
|
|
369
|
+
tokenUsage: result.tokenUsage,
|
|
370
|
+
toolCalls: result.toolCalls,
|
|
371
|
+
};
|
|
372
|
+
const envelope = await buildLocalFirstEvidenceEnvelope({
|
|
373
|
+
task,
|
|
374
|
+
selectedRuntime: runtime,
|
|
375
|
+
runContext: { runId, nodeId, projectRoot },
|
|
376
|
+
providerResponse: safeResult,
|
|
377
|
+
sovereignty: route.sovereignty,
|
|
378
|
+
});
|
|
379
|
+
await materialize(runId, nodeId, route, envelope);
|
|
380
|
+
if (safeResult.success) {
|
|
381
|
+
return {
|
|
382
|
+
...safeResult,
|
|
383
|
+
metadata: {
|
|
384
|
+
...safeResult.metadata,
|
|
385
|
+
freedomdEvidenceEnvelope: envelope.localArtifacts[envelope.localArtifacts.length - 1]?.path,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
lastError = safeResult;
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
const error = maskSensitiveText(String(err));
|
|
393
|
+
lastError = {
|
|
394
|
+
success: false,
|
|
395
|
+
exitCode: 1,
|
|
396
|
+
stdout: "",
|
|
397
|
+
stderr: error,
|
|
398
|
+
metadata: { runtime: runtime.id, error, sovereignty: route.sovereignty },
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const final = lastError ?? {
|
|
403
|
+
success: false,
|
|
404
|
+
exitCode: 1,
|
|
405
|
+
stdout: "",
|
|
406
|
+
stderr: "No Freedomd runtime available",
|
|
407
|
+
metadata: { attempted: allCandidates.map((r) => r.id), sovereignty: route.sovereignty },
|
|
408
|
+
};
|
|
409
|
+
const envelope = await buildLocalFirstEvidenceEnvelope({
|
|
410
|
+
task,
|
|
411
|
+
selectedRuntime: primary,
|
|
412
|
+
runContext: { runId, nodeId, projectRoot },
|
|
413
|
+
providerResponse: final,
|
|
414
|
+
sovereignty: route.sovereignty,
|
|
415
|
+
});
|
|
416
|
+
await materialize(runId, nodeId, route, envelope);
|
|
417
|
+
return final;
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
policy,
|
|
421
|
+
// baseRouter intentionally omitted from public return type to avoid leaking
|
|
422
|
+
// internal RuntimeRouter history types; use executeTask for routing.
|
|
423
|
+
baseRouter: baseRouter,
|
|
424
|
+
freedomdRoute,
|
|
425
|
+
scoreRuntimesWithSovereignty: (task) => scoreRuntimesWithSovereignty(task, options.runtimes ?? [], policy, {
|
|
426
|
+
profiles,
|
|
427
|
+
incidents,
|
|
428
|
+
userCountry: options.userCountry,
|
|
429
|
+
allowRedaction: options.allowRedaction,
|
|
430
|
+
}),
|
|
431
|
+
executeTask,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type HeadroomDecisionHistoryEntry, type HeadroomLoopRiskSignal } from "./headroom-loop-risk.js";
|
|
2
|
+
export type HeadroomLoopAction = "continue" | "block" | "replan" | "context-adjustment";
|
|
3
|
+
export interface HeadroomAwareLoopDecision {
|
|
4
|
+
readonly action: HeadroomLoopAction;
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
readonly confidence: number;
|
|
7
|
+
readonly contextAdjustment?: {
|
|
8
|
+
readonly dropLowPriorityGraphMemory: boolean;
|
|
9
|
+
readonly reduceFileSlices: boolean;
|
|
10
|
+
readonly forceStructuredFallback: boolean;
|
|
11
|
+
};
|
|
12
|
+
readonly risk?: {
|
|
13
|
+
readonly headroom: HeadroomLoopRiskSignal;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function evaluateHeadroomAwareLoopDecision(input: {
|
|
17
|
+
readonly baseAction: HeadroomLoopAction;
|
|
18
|
+
readonly baseReason: string;
|
|
19
|
+
readonly baseConfidence: number;
|
|
20
|
+
readonly headroomHistory: readonly HeadroomDecisionHistoryEntry[];
|
|
21
|
+
}): HeadroomAwareLoopDecision;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { computeHeadroomLoopRiskSignal } from "./headroom-loop-risk.js";
|
|
2
|
+
function isStrictGuardrailMode() {
|
|
3
|
+
const raw = process.env.OMK_STRICT_GUARDRAIL ?? "";
|
|
4
|
+
const normalized = raw.trim().toLowerCase();
|
|
5
|
+
return normalized === "1" || normalized === "true" || normalized === "on";
|
|
6
|
+
}
|
|
7
|
+
export function evaluateHeadroomAwareLoopDecision(input) {
|
|
8
|
+
const headroomRisk = computeHeadroomLoopRiskSignal(input.headroomHistory);
|
|
9
|
+
if (headroomRisk.kind === "none") {
|
|
10
|
+
return {
|
|
11
|
+
action: input.baseAction,
|
|
12
|
+
reason: input.baseReason,
|
|
13
|
+
confidence: input.baseConfidence,
|
|
14
|
+
risk: { headroom: headroomRisk },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (!isStrictGuardrailMode()) {
|
|
18
|
+
return {
|
|
19
|
+
action: "continue",
|
|
20
|
+
reason: `${input.baseReason}; headroom risk noted under agent-freedom mode: ${headroomRisk.reason}`,
|
|
21
|
+
confidence: Math.min(input.baseConfidence, headroomRisk.severity),
|
|
22
|
+
contextAdjustment: headroomRisk.recommendedAction === "summarize-or-drop-low-priority-context"
|
|
23
|
+
? {
|
|
24
|
+
dropLowPriorityGraphMemory: true,
|
|
25
|
+
reduceFileSlices: true,
|
|
26
|
+
forceStructuredFallback: true,
|
|
27
|
+
}
|
|
28
|
+
: undefined,
|
|
29
|
+
risk: { headroom: headroomRisk },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (headroomRisk.recommendedAction === "block") {
|
|
33
|
+
return {
|
|
34
|
+
action: "block",
|
|
35
|
+
reason: headroomRisk.reason,
|
|
36
|
+
confidence: Math.max(input.baseConfidence, headroomRisk.severity),
|
|
37
|
+
risk: { headroom: headroomRisk },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (headroomRisk.recommendedAction === "replan") {
|
|
41
|
+
return {
|
|
42
|
+
action: input.baseAction === "continue" || input.baseAction === "context-adjustment" ? "replan" : input.baseAction,
|
|
43
|
+
reason: `${input.baseReason}; ${headroomRisk.reason}`,
|
|
44
|
+
confidence: Math.max(input.baseConfidence, headroomRisk.severity),
|
|
45
|
+
risk: { headroom: headroomRisk },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (headroomRisk.recommendedAction === "summarize-or-drop-low-priority-context") {
|
|
49
|
+
return {
|
|
50
|
+
action: "continue",
|
|
51
|
+
reason: `${input.baseReason}; ${headroomRisk.reason}`,
|
|
52
|
+
confidence: Math.max(input.baseConfidence, headroomRisk.severity),
|
|
53
|
+
contextAdjustment: {
|
|
54
|
+
dropLowPriorityGraphMemory: true,
|
|
55
|
+
reduceFileSlices: true,
|
|
56
|
+
forceStructuredFallback: true,
|
|
57
|
+
},
|
|
58
|
+
risk: { headroom: headroomRisk },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
action: input.baseAction,
|
|
63
|
+
reason: input.baseReason,
|
|
64
|
+
confidence: input.baseConfidence,
|
|
65
|
+
risk: { headroom: headroomRisk },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type HeadroomLoopRiskKind = "none" | "compaction-contract-drift" | "headroom-no-apply" | "low-compaction-yield";
|
|
2
|
+
export type HeadroomLoopRecommendedAction = "continue" | "block" | "replan" | "summarize-or-drop-low-priority-context";
|
|
3
|
+
export interface HeadroomDecisionHistoryEntry {
|
|
4
|
+
readonly attempted?: boolean;
|
|
5
|
+
readonly applied?: boolean;
|
|
6
|
+
readonly validated?: boolean;
|
|
7
|
+
readonly compactedTextProduced?: boolean;
|
|
8
|
+
readonly beforeTokens?: number | null;
|
|
9
|
+
readonly afterTokens?: number | null;
|
|
10
|
+
readonly reason?: string;
|
|
11
|
+
readonly missingSections?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface HeadroomLoopRiskSignal {
|
|
14
|
+
readonly kind: HeadroomLoopRiskKind;
|
|
15
|
+
readonly severity: number;
|
|
16
|
+
readonly recommendedAction: HeadroomLoopRecommendedAction;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function computeHeadroomLoopRiskSignal(history: readonly HeadroomDecisionHistoryEntry[]): HeadroomLoopRiskSignal;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function computeHeadroomLoopRiskSignal(history) {
|
|
2
|
+
const recent = history.slice(-3);
|
|
3
|
+
if (recent.length < 2) {
|
|
4
|
+
return noHeadroomLoopRisk("not enough headroom decision history");
|
|
5
|
+
}
|
|
6
|
+
const failedApplyCount = recent.filter((entry) => entry.attempted === true && entry.applied !== true).length;
|
|
7
|
+
const validationFailureCount = recent.filter((entry) => entry.compactedTextProduced === true && entry.validated !== true).length;
|
|
8
|
+
const lowYieldCount = recent.filter((entry) => {
|
|
9
|
+
if (entry.applied !== true)
|
|
10
|
+
return false;
|
|
11
|
+
const before = Math.max(1, entry.beforeTokens ?? 0);
|
|
12
|
+
const after = Math.max(0, entry.afterTokens ?? 0);
|
|
13
|
+
return after / before > 0.85;
|
|
14
|
+
}).length;
|
|
15
|
+
if (validationFailureCount >= 2) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "compaction-contract-drift",
|
|
18
|
+
severity: 0.9,
|
|
19
|
+
recommendedAction: "block",
|
|
20
|
+
reason: "structured compaction repeatedly stripped or mismatched required contract sections",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (failedApplyCount >= 3) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "headroom-no-apply",
|
|
26
|
+
severity: 0.8,
|
|
27
|
+
recommendedAction: "replan",
|
|
28
|
+
reason: "headroom crossed the threshold repeatedly but no compacted capsule was applied",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (lowYieldCount >= 2) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "low-compaction-yield",
|
|
34
|
+
severity: 0.6,
|
|
35
|
+
recommendedAction: "summarize-or-drop-low-priority-context",
|
|
36
|
+
reason: "compaction applied repeatedly but did not reduce context enough",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return noHeadroomLoopRisk("headroom decisions show no repeated compaction risk");
|
|
40
|
+
}
|
|
41
|
+
function noHeadroomLoopRisk(reason) {
|
|
42
|
+
return { kind: "none", severity: 0, recommendedAction: "continue", reason };
|
|
43
|
+
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* a configurable threshold (default 90%).
|
|
7
7
|
*
|
|
8
8
|
* Prefers the external `headroom` CLI compressor when available;
|
|
9
|
-
* falls back to
|
|
9
|
+
* falls back to caller-provided structured text and finally to legacy
|
|
10
|
+
* side-effect optimizers when not.
|
|
10
11
|
*
|
|
11
12
|
* Deterministic except for the injectable `runHeadroom` runner.
|
|
12
13
|
*/
|
|
@@ -18,9 +19,20 @@ export interface HeadroomDecision {
|
|
|
18
19
|
readonly contextWindow: number;
|
|
19
20
|
readonly reason: string;
|
|
20
21
|
}
|
|
22
|
+
export type HeadroomBackend = "headroom" | "structured-fallback" | "side-effect-fallback" | "none";
|
|
21
23
|
export interface HeadroomCompactResult {
|
|
24
|
+
/** Backward-compatible summary: a backend produced compacted text or completed a legacy fallback. */
|
|
22
25
|
readonly compacted: boolean;
|
|
26
|
+
/** Backward-compatible backend family. */
|
|
23
27
|
readonly via: "headroom" | "fallback" | "none";
|
|
28
|
+
readonly compactedText?: string;
|
|
29
|
+
readonly reason?: string;
|
|
30
|
+
/** Whether a compaction attempt was made because the policy threshold was crossed. */
|
|
31
|
+
readonly attempted: boolean;
|
|
32
|
+
/** Specific backend that produced this result. */
|
|
33
|
+
readonly backend: HeadroomBackend;
|
|
34
|
+
/** Whether a textual backend produced non-empty compacted text. */
|
|
35
|
+
readonly compactedTextProduced: boolean;
|
|
24
36
|
}
|
|
25
37
|
export declare function resolveHeadroomThreshold(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): number;
|
|
26
38
|
export declare function isHeadroomEnabled(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): boolean;
|
|
@@ -34,4 +46,5 @@ export declare function maybeCompactWithHeadroom(args: {
|
|
|
34
46
|
text?: string;
|
|
35
47
|
runHeadroom?: (text: string) => Promise<string | null>;
|
|
36
48
|
fallback?: () => Promise<void>;
|
|
49
|
+
fallbackText?: () => string | null | Promise<string | null>;
|
|
37
50
|
}): Promise<HeadroomCompactResult>;
|