@claude-flow/cli 3.54.1 → 3.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/.claude/settings.json +9 -9
- package/bin/cli.js +1 -1
- package/bin/mcp-server.js +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/autopilot.js +2 -2
- package/dist/src/commands/daemon.js +15 -3
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/hive-mind.js +7 -0
- package/dist/src/commands/mcp.js +35 -1
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/policy.js +10 -2
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.js +96 -4
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -4
- package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +95 -61
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +71 -13
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/production/retry.js +1 -1
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/bounded-worker-pool.js +3 -0
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/config-file-manager.d.ts +3 -0
- package/dist/src/services/config-file-manager.js +24 -7
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/policy-ledger-anchor.d.ts +42 -0
- package/dist/src/services/policy-ledger-anchor.js +163 -0
- package/dist/src/services/policy-runtime.d.ts +11 -5
- package/dist/src/services/policy-runtime.js +70 -15
- package/dist/src/services/worker-daemon.d.ts +5 -2
- package/dist/src/services/worker-daemon.js +26 -6
- package/dist/src/services/worker-queue.js +11 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +60 -47
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +11 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
- package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -101,7 +101,9 @@ export async function callAnthropicMessages(input) {
|
|
|
101
101
|
// key-presence inference, kept below as the last-resort fallback.
|
|
102
102
|
const explicitProvider = (input.provider || process.env.RUFLO_PROVIDER || '').toLowerCase();
|
|
103
103
|
const ollamaKey = process.env.OLLAMA_API_KEY;
|
|
104
|
-
const
|
|
104
|
+
const anthropicAuthToken = process.env.ANTHROPIC_AUTH_TOKEN;
|
|
105
|
+
// A gateway bearer token is also an Anthropic credential for provider inference.
|
|
106
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY || anthropicAuthToken;
|
|
105
107
|
// #2042 — OpenRouter is an OpenAI-compat endpoint that fronts dozens of
|
|
106
108
|
// providers. Reporter (@ummcke00) had `providers.openrouter.apiKey` in
|
|
107
109
|
// their config.yaml but agent_execute hardcoded Anthropic. Detect via
|
|
@@ -109,7 +111,7 @@ export async function callAnthropicMessages(input) {
|
|
|
109
111
|
// when no Anthropic key is available (same precedence as the Ollama
|
|
110
112
|
// branch above).
|
|
111
113
|
const openrouterKey = process.env.OPENROUTER_API_KEY;
|
|
112
|
-
const useOpenRouter = explicitProvider === 'openrouter' || (!anthropicKey && !!openrouterKey);
|
|
114
|
+
const useOpenRouter = explicitProvider === 'openrouter' || (!explicitProvider && !anthropicKey && !!openrouterKey);
|
|
113
115
|
// #2962 — only consult the persisted config when a candidate is actually
|
|
114
116
|
// relevant (explicit choice, or no env key found anywhere), so a normal
|
|
115
117
|
// ANTHROPIC_API_KEY-only setup never pays a config-file read.
|
|
@@ -117,7 +119,7 @@ export async function callAnthropicMessages(input) {
|
|
|
117
119
|
? getPersistedProviderConfig('ollama')
|
|
118
120
|
: undefined;
|
|
119
121
|
const persistedOpenRouter = explicitProvider === 'openrouter' && !openrouterKey ? getPersistedProviderConfig('openrouter') : undefined;
|
|
120
|
-
const useOllama = explicitProvider === 'ollama' || (!anthropicKey && !openrouterKey && (!!ollamaKey || !!persistedOllama));
|
|
122
|
+
const useOllama = explicitProvider === 'ollama' || (!explicitProvider && !anthropicKey && !openrouterKey && (!!ollamaKey || !!persistedOllama));
|
|
121
123
|
if (useOpenRouter) {
|
|
122
124
|
const apiKey = openrouterKey || persistedOpenRouter?.apiKey;
|
|
123
125
|
if (apiKey) {
|
|
@@ -133,6 +135,7 @@ export async function callAnthropicMessages(input) {
|
|
|
133
135
|
defaultModel: process.env.OPENROUTER_DEFAULT_MODEL || persistedOpenRouter?.model || 'anthropic/claude-sonnet-4-6',
|
|
134
136
|
});
|
|
135
137
|
}
|
|
138
|
+
return { success: false, error: 'OpenRouter was selected but no API key is configured.' };
|
|
136
139
|
}
|
|
137
140
|
if (useOllama) {
|
|
138
141
|
// #2962 — retire the undocumented OLLAMA_API_KEY=local sentinel
|
|
@@ -151,22 +154,26 @@ export async function callAnthropicMessages(input) {
|
|
|
151
154
|
model: input.model || persistedOllama?.model,
|
|
152
155
|
});
|
|
153
156
|
}
|
|
157
|
+
return { success: false, error: 'Ollama was selected but no API key or self-hosted endpoint is configured.' };
|
|
154
158
|
}
|
|
155
159
|
if (!anthropicKey) {
|
|
156
160
|
return {
|
|
157
161
|
success: false,
|
|
158
|
-
error: 'No LLM provider configured. Set ANTHROPIC_API_KEY (Tier-3), OPENROUTER_API_KEY (#2042), or OLLAMA_API_KEY (Tier-2 — #1725).',
|
|
162
|
+
error: 'No LLM provider configured. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN (Tier-3), OPENROUTER_API_KEY (#2042), or OLLAMA_API_KEY (Tier-2 — #1725).',
|
|
159
163
|
};
|
|
160
164
|
}
|
|
161
165
|
const model = input.model || DEFAULT_ANTHROPIC_MODEL;
|
|
162
166
|
const startedAt = Date.now();
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
163
169
|
try {
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
170
|
+
const baseUrl = (process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com').replace(/\/+$/, '');
|
|
171
|
+
const res = await fetch(`${baseUrl}/v1/messages`, {
|
|
167
172
|
method: 'POST',
|
|
168
173
|
headers: {
|
|
169
|
-
|
|
174
|
+
...(anthropicAuthToken
|
|
175
|
+
? { Authorization: `Bearer ${anthropicAuthToken}` }
|
|
176
|
+
: { 'x-api-key': anthropicKey }),
|
|
170
177
|
'anthropic-version': '2023-06-01',
|
|
171
178
|
'content-type': 'application/json',
|
|
172
179
|
},
|
|
@@ -191,7 +198,6 @@ export async function callAnthropicMessages(input) {
|
|
|
191
198
|
}),
|
|
192
199
|
signal: controller.signal,
|
|
193
200
|
});
|
|
194
|
-
clearTimeout(timer);
|
|
195
201
|
if (!res.ok) {
|
|
196
202
|
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
197
203
|
return { success: false, model, error: `Anthropic API error ${res.status}: ${errText.slice(0, 400)}` };
|
|
@@ -223,6 +229,9 @@ export async function callAnthropicMessages(input) {
|
|
|
223
229
|
durationMs: Date.now() - startedAt,
|
|
224
230
|
};
|
|
225
231
|
}
|
|
232
|
+
finally {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
}
|
|
226
235
|
}
|
|
227
236
|
/**
|
|
228
237
|
* Ollama Cloud / OpenAI-compat provider — Tier-2 routing per ADR-026 + #1725.
|
|
@@ -252,9 +261,9 @@ async function callOllamaCompat(input) {
|
|
|
252
261
|
// (the daemon binds to 11434 with no auth by default), but Ollama Cloud
|
|
253
262
|
// does. Send the bearer when the key is non-empty AND looks cloud-shaped.
|
|
254
263
|
const sendAuth = !!input.apiKey && input.apiKey !== 'local';
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
255
266
|
try {
|
|
256
|
-
const controller = new AbortController();
|
|
257
|
-
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
258
267
|
const res = await fetch(url, {
|
|
259
268
|
method: 'POST',
|
|
260
269
|
headers: {
|
|
@@ -274,7 +283,6 @@ async function callOllamaCompat(input) {
|
|
|
274
283
|
}),
|
|
275
284
|
signal: controller.signal,
|
|
276
285
|
});
|
|
277
|
-
clearTimeout(timer);
|
|
278
286
|
if (!res.ok) {
|
|
279
287
|
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
280
288
|
return { success: false, model, error: `Ollama API error ${res.status} at ${url}: ${errText.slice(0, 400)}` };
|
|
@@ -304,6 +312,9 @@ async function callOllamaCompat(input) {
|
|
|
304
312
|
durationMs: Date.now() - startedAt,
|
|
305
313
|
};
|
|
306
314
|
}
|
|
315
|
+
finally {
|
|
316
|
+
clearTimeout(timer);
|
|
317
|
+
}
|
|
307
318
|
}
|
|
308
319
|
/**
|
|
309
320
|
* Generic OpenAI-compat caller for OpenRouter and other OpenAI-shaped
|
|
@@ -319,9 +330,9 @@ async function callOpenAICompat(input) {
|
|
|
319
330
|
const startedAt = Date.now();
|
|
320
331
|
const base = input.baseUrl.replace(/\/+$/, '');
|
|
321
332
|
const url = `${base}/v1/chat/completions`;
|
|
333
|
+
const controller = new AbortController();
|
|
334
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
322
335
|
try {
|
|
323
|
-
const controller = new AbortController();
|
|
324
|
-
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
325
336
|
const messages = [];
|
|
326
337
|
if (input.systemPrompt)
|
|
327
338
|
messages.push({ role: 'system', content: input.systemPrompt });
|
|
@@ -344,7 +355,6 @@ async function callOpenAICompat(input) {
|
|
|
344
355
|
}),
|
|
345
356
|
signal: controller.signal,
|
|
346
357
|
});
|
|
347
|
-
clearTimeout(timer);
|
|
348
358
|
if (!res.ok) {
|
|
349
359
|
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
350
360
|
return { success: false, model, error: `${input.providerLabel} API error ${res.status}: ${errText.slice(0, 400)}` };
|
|
@@ -374,6 +384,9 @@ async function callOpenAICompat(input) {
|
|
|
374
384
|
durationMs: Date.now() - startedAt,
|
|
375
385
|
};
|
|
376
386
|
}
|
|
387
|
+
finally {
|
|
388
|
+
clearTimeout(timer);
|
|
389
|
+
}
|
|
377
390
|
}
|
|
378
391
|
function resolveOpenAICompatModel(input, fallback) {
|
|
379
392
|
if (!input)
|
|
@@ -418,6 +431,8 @@ export function resolveAnthropicModel(input) {
|
|
|
418
431
|
return input.slice('anthropic:'.length);
|
|
419
432
|
return input;
|
|
420
433
|
}
|
|
434
|
+
// Tracks overlapping calls in this process; persisted state remains authoritative.
|
|
435
|
+
const activeExecutions = new Map();
|
|
421
436
|
export async function executeAgentTask(input) {
|
|
422
437
|
const store = loadAgentStore();
|
|
423
438
|
const agent = store.agents[input.agentId];
|
|
@@ -465,194 +480,235 @@ export async function executeAgentTask(input) {
|
|
|
465
480
|
`You are a ${agent.agentType} agent operating as part of a Ruflo swarm. ` +
|
|
466
481
|
`Agent ID: ${input.agentId}. Domain: ${agent.domain ?? 'general'}. ` +
|
|
467
482
|
`Respond directly and stay focused on the task. If you need information you don't have, state that explicitly.`;
|
|
483
|
+
const initialModelId = agent.modelId;
|
|
484
|
+
const initialModel = agent.model;
|
|
468
485
|
agent.status = 'busy';
|
|
469
486
|
agent.taskCount = (agent.taskCount || 0) + 1;
|
|
470
487
|
saveAgentStore(store);
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
model
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
for
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
excludeIds.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
488
|
+
const executionKey = JSON.stringify([getAgentPath(), input.agentId, agent.createdAt]);
|
|
489
|
+
activeExecutions.set(executionKey, (activeExecutions.get(executionKey) || 0) + 1);
|
|
490
|
+
try {
|
|
491
|
+
const startedAt = Date.now();
|
|
492
|
+
// #2042 — delegate to callAnthropicMessages so the v3 provider router
|
|
493
|
+
// (Anthropic / Ollama / OpenRouter) governs which backend is hit.
|
|
494
|
+
// #2962 — forward the agent's own explicit provider (set at spawn time
|
|
495
|
+
// from --provider / persisted config) so it outranks env-var inference.
|
|
496
|
+
// Only the first-attempt call; the ADR-149 fallback-retry call below is
|
|
497
|
+
// driven by the cost-optimal neural router picking model-id alternatives,
|
|
498
|
+
// an orthogonal mechanism left as-is to avoid unintended interaction.
|
|
499
|
+
let result = await callAnthropicMessages({
|
|
500
|
+
model: anthropicModel,
|
|
501
|
+
prompt: input.prompt,
|
|
502
|
+
systemPrompt,
|
|
503
|
+
maxTokens: input.maxTokens,
|
|
504
|
+
temperature: input.temperature,
|
|
505
|
+
timeoutMs: input.timeoutMs,
|
|
506
|
+
provider: agent.provider,
|
|
507
|
+
});
|
|
508
|
+
// ADR-149 iter 7 — fallback chain on retryable failures (429, 5xx,
|
|
509
|
+
// timeout). When the cost-optimal neural backend picked a specific
|
|
510
|
+
// model id and that model fails for a transient reason, fall back to
|
|
511
|
+
// the next-cheapest candidate that clears the quality bar. Budget is
|
|
512
|
+
// bounded by CLAUDE_FLOW_ROUTER_FALLBACK_MAX_RETRIES (default 1) so
|
|
513
|
+
// upstream outages don't cause retry storms.
|
|
514
|
+
const configuredFallbackBudget = parseInt(process.env.CLAUDE_FLOW_ROUTER_FALLBACK_MAX_RETRIES ?? '1', 10);
|
|
515
|
+
const fallbackBudget = Number.isNaN(configuredFallbackBudget) ? 1 : Math.max(0, configuredFallbackBudget);
|
|
516
|
+
const fallbackHistory = [];
|
|
517
|
+
// Keep feedback for each completed attempt before switching model identity.
|
|
518
|
+
const failedAttempts = [];
|
|
519
|
+
if (!result.success && agent.modelId && fallbackBudget > 0) {
|
|
520
|
+
const isRetryable = /\b(429|500|502|503|504|timeout|ECONNRESET|ETIMEDOUT)\b/i.test(result.error ?? '');
|
|
521
|
+
if (isRetryable) {
|
|
522
|
+
try {
|
|
523
|
+
const { nextCostOptimalAlternative } = await import('../ruvector/neural-router.js');
|
|
524
|
+
// ADR-149 iter 9 — delegate to the shared task-embedder LRU. The
|
|
525
|
+
// pipeline + cache are shared with agent-tools.ts, so the embedding
|
|
526
|
+
// for this prompt is almost always already cached from the initial
|
|
527
|
+
// routing decision (no extra inference cost in steady state).
|
|
528
|
+
const { embedTaskWithCache } = await import('../ruvector/task-embedder.js');
|
|
529
|
+
const embedding = await embedTaskWithCache(input.prompt);
|
|
530
|
+
if (embedding) {
|
|
531
|
+
const excludeIds = [agent.modelId];
|
|
532
|
+
for (let attempt = 0; attempt < fallbackBudget && !result.success; attempt++) {
|
|
533
|
+
fallbackHistory.push({ modelId: excludeIds[excludeIds.length - 1], error: result.error ?? '' });
|
|
534
|
+
const alt = await nextCostOptimalAlternative(embedding, excludeIds);
|
|
535
|
+
if (!alt || !alt.modelId)
|
|
536
|
+
break;
|
|
537
|
+
excludeIds.push(alt.modelId);
|
|
538
|
+
const altResult = await callAnthropicMessages({
|
|
539
|
+
model: alt.modelId,
|
|
540
|
+
prompt: input.prompt,
|
|
541
|
+
systemPrompt,
|
|
542
|
+
maxTokens: input.maxTokens,
|
|
543
|
+
temperature: input.temperature,
|
|
544
|
+
timeoutMs: input.timeoutMs,
|
|
545
|
+
});
|
|
546
|
+
// Record the model that ACTUALLY answered (or errored). On success,
|
|
547
|
+
// update agent.modelId so downstream observers see the retry winner.
|
|
548
|
+
failedAttempts.push({ model: agent.model ?? 'sonnet', modelId: agent.modelId });
|
|
549
|
+
agent.modelId = alt.modelId;
|
|
550
|
+
agent.model = alt.model;
|
|
551
|
+
result = altResult;
|
|
552
|
+
}
|
|
527
553
|
}
|
|
528
554
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
555
|
+
catch {
|
|
556
|
+
// Fallback chain is best-effort — preserve the original error result.
|
|
557
|
+
}
|
|
532
558
|
}
|
|
533
559
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
// ADR-149 — also write to the shadow per-modelId priors when the cost-
|
|
553
|
-
// optimal neural backend picked a concrete model id. Selection logic
|
|
554
|
-
// still uses tier priors, but the per-modelId data accumulates so a
|
|
555
|
-
// future refactor can switch the selector over.
|
|
556
|
-
if (agent.modelId) {
|
|
557
|
-
recordModelOutcomeByModelId(input.prompt, agent.modelId, outcome);
|
|
558
|
-
}
|
|
559
|
-
// ADR-149 iter 17 — close the production-data side of the feedback loop.
|
|
560
|
-
// The trajectory recorder (CLAUDE_FLOW_ROUTER_TRAJECTORY=1) writes
|
|
561
|
-
// per-decision rows to .swarm/model-router-trajectories.jsonl from the
|
|
562
|
-
// model-router itself. Now we ALSO write outcome rows here so future
|
|
563
|
-
// training (scripts/train-from-trajectories.mjs, follow-up) can pair
|
|
564
|
-
// decision+outcome by task_hash and produce DRACO-shaped retraining
|
|
565
|
-
// rows from real production traffic. quality = 1.0 on success, 0.0 on
|
|
566
|
-
// failure (coarse signal; finer-grained quality from user ratings or
|
|
567
|
-
// regression-detection is a separate hook).
|
|
568
|
-
if (process.env.CLAUDE_FLOW_ROUTER_TRAJECTORY === '1') {
|
|
569
|
-
try {
|
|
570
|
-
const { recordTrajectoryOutcome } = await import('../ruvector/router-trajectory.js');
|
|
571
|
-
const scores = agent.modelId
|
|
572
|
-
? { [agent.modelId]: outcome === 'success' ? 1.0 : 0.0 }
|
|
573
|
-
: undefined;
|
|
574
|
-
// iter 31 — pass token usage + modelId so the recorder can compute
|
|
575
|
-
// USD spend at write time. Consumers (`router decisions`, cost-
|
|
576
|
-
// savings reports) get cost without their own price table.
|
|
577
|
-
recordTrajectoryOutcome({
|
|
578
|
-
task: input.prompt,
|
|
579
|
-
quality: outcome === 'success' ? 1.0 : 0.0,
|
|
580
|
-
scores,
|
|
581
|
-
source: 'agent-execute',
|
|
582
|
-
tokens: result.usage ? { input: result.usage.inputTokens, output: result.usage.outputTokens } : undefined,
|
|
583
|
-
modelId: agent.modelId,
|
|
584
|
-
});
|
|
560
|
+
// ADR-149 — close the bandit feedback loop. `recordModelOutcome` updates
|
|
561
|
+
// the Beta(α,β) prior for the agent's tier so the Thompson sampler learns
|
|
562
|
+
// from production traffic instead of staying frozen at install-day priors.
|
|
563
|
+
// This is best-effort: any error here must NOT break the agent execution.
|
|
564
|
+
// For now, "success" = the model returned a response without an API error.
|
|
565
|
+
// A finer-grained signal (user-accepted output / regression-detected) is a
|
|
566
|
+
// follow-up; this commit closes the bandit's basic learning loop.
|
|
567
|
+
try {
|
|
568
|
+
const { recordModelOutcome, recordModelOutcomeByModelId } = await import('../ruvector/model-router.js');
|
|
569
|
+
// Bandit priors are keyed on the 3 canonical tiers (haiku/sonnet/opus/inherit);
|
|
570
|
+
// collapse opus-4.7 → opus before recording so the bandit's per-tier Beta
|
|
571
|
+
// updates correctly.
|
|
572
|
+
const tier = agent.model === 'opus-4.7' ? 'opus' :
|
|
573
|
+
agent.model ?? 'sonnet';
|
|
574
|
+
const outcome = result.success ? 'success' : 'failure';
|
|
575
|
+
for (const attempt of failedAttempts) {
|
|
576
|
+
recordModelOutcome(input.prompt, attempt.model === 'opus-4.7' ? 'opus' : attempt.model, 'failure');
|
|
577
|
+
recordModelOutcomeByModelId(input.prompt, attempt.modelId, 'failure');
|
|
585
578
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
579
|
+
recordModelOutcome(input.prompt, tier, outcome);
|
|
580
|
+
// ADR-149 — also write to the shadow per-modelId priors when the cost-
|
|
581
|
+
// optimal neural backend picked a concrete model id. Selection logic
|
|
582
|
+
// still uses tier priors, but the per-modelId data accumulates so a
|
|
583
|
+
// future refactor can switch the selector over.
|
|
584
|
+
if (agent.modelId) {
|
|
585
|
+
recordModelOutcomeByModelId(input.prompt, agent.modelId, outcome);
|
|
586
|
+
}
|
|
587
|
+
// ADR-149 iter 17 — close the production-data side of the feedback loop.
|
|
588
|
+
// The trajectory recorder (CLAUDE_FLOW_ROUTER_TRAJECTORY=1) writes
|
|
589
|
+
// per-decision rows to .swarm/model-router-trajectories.jsonl from the
|
|
590
|
+
// model-router itself. Now we ALSO write outcome rows here so future
|
|
591
|
+
// training (scripts/train-from-trajectories.mjs, follow-up) can pair
|
|
592
|
+
// decision+outcome by task_hash and produce DRACO-shaped retraining
|
|
593
|
+
// rows from real production traffic. quality = 1.0 on success, 0.0 on
|
|
594
|
+
// failure (coarse signal; finer-grained quality from user ratings or
|
|
595
|
+
// regression-detection is a separate hook).
|
|
596
|
+
if (process.env.CLAUDE_FLOW_ROUTER_TRAJECTORY === '1') {
|
|
597
|
+
try {
|
|
598
|
+
const { recordTrajectoryOutcome } = await import('../ruvector/router-trajectory.js');
|
|
599
|
+
const scores = agent.modelId
|
|
600
|
+
? { [agent.modelId]: outcome === 'success' ? 1.0 : 0.0 }
|
|
601
|
+
: undefined;
|
|
602
|
+
// iter 31 — pass token usage + modelId so the recorder can compute
|
|
603
|
+
// USD spend at write time. Consumers (`router decisions`, cost-
|
|
604
|
+
// savings reports) get cost without their own price table.
|
|
605
|
+
recordTrajectoryOutcome({
|
|
606
|
+
task: input.prompt,
|
|
607
|
+
quality: outcome === 'success' ? 1.0 : 0.0,
|
|
608
|
+
scores,
|
|
609
|
+
source: 'agent-execute',
|
|
610
|
+
tokens: result.usage ? { input: result.usage.inputTokens, output: result.usage.outputTokens } : undefined,
|
|
611
|
+
modelId: agent.modelId,
|
|
612
|
+
});
|
|
607
613
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
614
|
+
catch { /* never break execution */ }
|
|
615
|
+
}
|
|
616
|
+
// ADR-150 weight-eft capture seam — the ONLY place a run's full outcome is
|
|
617
|
+
// known here: prompt (issue), assistant transcript, model, tier, and a
|
|
618
|
+
// resolved PROXY. Gated behind CLAUDE_FLOW_RUN_TRANSCRIPTS=1 (off by
|
|
619
|
+
// default; PII/retention surface, mirrors the router trajectory recorder).
|
|
620
|
+
// HONESTY: `resolved` here is the WEAKEST proxy — 'api-success' means only
|
|
621
|
+
// that the model returned without an API error, NOT that the output is
|
|
622
|
+
// correct (ruflo has no SWE-bench gold oracle). model_patch is '' because
|
|
623
|
+
// this single-shot execute path produces no unified diff. Both facts are
|
|
624
|
+
// stamped so no downstream weight-eft export mistakes this for gold data.
|
|
625
|
+
if (process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS === '1') {
|
|
626
|
+
try {
|
|
627
|
+
const { recordRunTranscript, tierForModel } = await import('../ruvector/run-transcript-recorder.js');
|
|
628
|
+
const modelId = agent.modelId ?? String(agent.model ?? 'unknown');
|
|
629
|
+
const messages = [];
|
|
630
|
+
if (systemPrompt)
|
|
631
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
632
|
+
messages.push({ role: 'user', content: input.prompt });
|
|
633
|
+
if (result.success && typeof result.output === 'string') {
|
|
634
|
+
messages.push({ role: 'assistant', content: result.output });
|
|
635
|
+
}
|
|
636
|
+
recordRunTranscript({
|
|
637
|
+
task: input.prompt,
|
|
638
|
+
model: modelId,
|
|
639
|
+
tier: tierForModel(modelId),
|
|
640
|
+
resolved: outcome === 'success',
|
|
641
|
+
resolvedSource: 'api-success',
|
|
642
|
+
messages,
|
|
643
|
+
source: 'agent-execute',
|
|
644
|
+
tokens: result.usage ? { input: result.usage.inputTokens, output: result.usage.outputTokens } : undefined,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
catch { /* never break execution */ }
|
|
618
648
|
}
|
|
619
|
-
catch { /* never break execution */ }
|
|
620
649
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
650
|
+
catch {
|
|
651
|
+
// Silent — bandit feedback must never block routing.
|
|
652
|
+
}
|
|
653
|
+
const remaining = (activeExecutions.get(executionKey) || 1) - 1;
|
|
654
|
+
const persistCompletion = (out) => {
|
|
655
|
+
// Awaited provider/feedback work may have overlapped registration, updates,
|
|
656
|
+
// termination or deletion. Never replace that newer registry snapshot.
|
|
657
|
+
const latest = loadAgentStore();
|
|
658
|
+
const current = latest.agents[input.agentId];
|
|
659
|
+
if (!current || current.createdAt !== agent.createdAt)
|
|
660
|
+
return;
|
|
661
|
+
if (current.status === 'busy')
|
|
662
|
+
current.status = remaining > 0 ? 'busy' : 'idle';
|
|
663
|
+
if (agent.modelId !== initialModelId && current.modelId === initialModelId) {
|
|
664
|
+
// A fallback may also change its tier. Carry that change with the model
|
|
665
|
+
// ID, but preserve any selection the user edited during the request.
|
|
666
|
+
if (agent.model !== initialModel && current.model === initialModel)
|
|
667
|
+
current.model = agent.model;
|
|
668
|
+
current.modelId = agent.modelId;
|
|
669
|
+
}
|
|
670
|
+
if (out)
|
|
671
|
+
current.lastResult = out;
|
|
672
|
+
saveAgentStore(latest);
|
|
673
|
+
};
|
|
674
|
+
if (result.success) {
|
|
675
|
+
const out = {
|
|
676
|
+
success: true,
|
|
677
|
+
agentId: input.agentId,
|
|
678
|
+
messageId: result.messageId,
|
|
679
|
+
model: result.model,
|
|
680
|
+
stopReason: result.stopReason,
|
|
681
|
+
output: result.output,
|
|
682
|
+
usage: result.usage,
|
|
683
|
+
durationMs: result.durationMs ?? Date.now() - startedAt,
|
|
684
|
+
...(fallbackHistory.length > 0 ? { fallbackHistory } : {}),
|
|
685
|
+
};
|
|
686
|
+
persistCompletion(out);
|
|
687
|
+
return out;
|
|
688
|
+
}
|
|
689
|
+
persistCompletion();
|
|
690
|
+
// No-provider-configured error → surface the same actionable message
|
|
691
|
+
// the router built, with a #2042-aware remediation pointer.
|
|
692
|
+
const noProvider = (result.error || '').includes('No LLM provider configured');
|
|
693
|
+
return {
|
|
694
|
+
success: false,
|
|
628
695
|
agentId: input.agentId,
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
stopReason: result.stopReason,
|
|
632
|
-
output: result.output,
|
|
633
|
-
usage: result.usage,
|
|
696
|
+
model: anthropicModel,
|
|
697
|
+
error: result.error || 'agent_execute failed',
|
|
634
698
|
durationMs: result.durationMs ?? Date.now() - startedAt,
|
|
635
699
|
...(fallbackHistory.length > 0 ? { fallbackHistory } : {}),
|
|
700
|
+
...(noProvider && {
|
|
701
|
+
remediation: 'Set one of ANTHROPIC_API_KEY, OPENROUTER_API_KEY (+ optional OPENROUTER_BASE_URL), or OLLAMA_API_KEY. ' +
|
|
702
|
+
'Or set RUFLO_PROVIDER=openrouter|ollama to force a specific provider.',
|
|
703
|
+
}),
|
|
636
704
|
};
|
|
637
|
-
agent.lastResult = out;
|
|
638
|
-
saveAgentStore(store);
|
|
639
|
-
return out;
|
|
640
705
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
model: anthropicModel,
|
|
649
|
-
error: result.error || 'agent_execute failed',
|
|
650
|
-
durationMs: result.durationMs ?? Date.now() - startedAt,
|
|
651
|
-
...(fallbackHistory.length > 0 ? { fallbackHistory } : {}),
|
|
652
|
-
...(noProvider && {
|
|
653
|
-
remediation: 'Set one of ANTHROPIC_API_KEY, OPENROUTER_API_KEY (+ optional OPENROUTER_BASE_URL), or OLLAMA_API_KEY. ' +
|
|
654
|
-
'Or set RUFLO_PROVIDER=openrouter|ollama to force a specific provider.',
|
|
655
|
-
}),
|
|
656
|
-
};
|
|
706
|
+
finally {
|
|
707
|
+
const pending = (activeExecutions.get(executionKey) || 1) - 1;
|
|
708
|
+
if (pending > 0)
|
|
709
|
+
activeExecutions.set(executionKey, pending);
|
|
710
|
+
else
|
|
711
|
+
activeExecutions.delete(executionKey);
|
|
712
|
+
}
|
|
657
713
|
}
|
|
658
714
|
//# sourceMappingURL=agent-execute-core.js.map
|