infinicode 2.3.2 → 2.3.3
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/dist/cli.js +2 -1
- package/dist/commands/mcp.js +2 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +36 -1
- package/dist/kernel/capability-map.d.ts +18 -0
- package/dist/kernel/capability-map.js +84 -0
- package/dist/kernel/checkpoint-engine.js +9 -0
- package/dist/kernel/command-system.d.ts +3 -0
- package/dist/kernel/command-system.js +315 -10
- package/dist/kernel/config-schema.d.ts +11 -0
- package/dist/kernel/config-schema.js +1 -0
- package/dist/kernel/federation/event-bridge.js +3 -1
- package/dist/kernel/federation/federation.d.ts +10 -0
- package/dist/kernel/federation/federation.js +28 -9
- package/dist/kernel/free-providers.d.ts +23 -0
- package/dist/kernel/free-providers.js +73 -1
- package/dist/kernel/index.d.ts +4 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/kernel.d.ts +12 -0
- package/dist/kernel/kernel.js +29 -2
- package/dist/kernel/mission-engine.js +14 -0
- package/dist/kernel/orchestrator.d.ts +3 -0
- package/dist/kernel/orchestrator.js +74 -2
- package/dist/kernel/planner.d.ts +42 -0
- package/dist/kernel/planner.js +174 -0
- package/dist/kernel/recovery-manager.d.ts +9 -0
- package/dist/kernel/recovery-manager.js +64 -3
- package/dist/kernel/router.d.ts +12 -1
- package/dist/kernel/router.js +36 -1
- package/dist/kernel/scheduler.d.ts +17 -0
- package/dist/kernel/scheduler.js +97 -1
- package/dist/kernel/setup.d.ts +9 -0
- package/dist/kernel/setup.js +38 -0
- package/dist/kernel/types.d.ts +65 -2
- package/dist/kernel/verification-engine.js +83 -27
- package/dist/kernel/verification-exec.d.ts +14 -0
- package/dist/kernel/verification-exec.js +65 -0
- package/dist/kernel/worker-runtime.js +3 -2
- package/package.json +1 -1
|
@@ -23,6 +23,9 @@ export interface DispatchTask {
|
|
|
23
23
|
prompt: string;
|
|
24
24
|
role?: string;
|
|
25
25
|
context?: Record<string, unknown>;
|
|
26
|
+
/** When true, the receiving node auto-plans the goal into phases (a phased
|
|
27
|
+
* build) instead of running it as one atomic task. */
|
|
28
|
+
autoPlan?: boolean;
|
|
26
29
|
}
|
|
27
30
|
export interface FederationOptions {
|
|
28
31
|
version: string;
|
|
@@ -100,6 +103,13 @@ export declare class Federation {
|
|
|
100
103
|
runId: string;
|
|
101
104
|
record: RunRecord;
|
|
102
105
|
} | null>;
|
|
106
|
+
/** Runs received + executing on THIS node (async handoff), newest last. */
|
|
107
|
+
localRuns(): RunRecord[];
|
|
108
|
+
/** Runs WE dispatched to peers → { runId, peerId } for monitoring. */
|
|
109
|
+
outboundRuns(): Array<{
|
|
110
|
+
runId: string;
|
|
111
|
+
peerId: string;
|
|
112
|
+
}>;
|
|
103
113
|
/** Query a dispatched run's current record (from whichever peer holds it). */
|
|
104
114
|
followRun(runId: string): Promise<{
|
|
105
115
|
record: RunRecord;
|
|
@@ -174,15 +174,26 @@ export class Federation {
|
|
|
174
174
|
const localRole = loadRole();
|
|
175
175
|
const preamble = rolePreamble(localRole);
|
|
176
176
|
const prompt = preamble ? `${preamble}\n\n${task.prompt}` : task.prompt;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
177
|
+
// Auto-planned dispatch → the remote kernel decomposes the goal into phases
|
|
178
|
+
// (architecture → implementation → verification …), each routed to its own
|
|
179
|
+
// model tier. The phase/task events stream back to the dispatcher for
|
|
180
|
+
// monitoring. Otherwise run it as a single atomic task.
|
|
181
|
+
const mission = task.autoPlan
|
|
182
|
+
? await this.deps.kernel.execute({
|
|
183
|
+
description: task.description,
|
|
184
|
+
goal: task.prompt || task.description,
|
|
185
|
+
autoPlan: true,
|
|
186
|
+
metadata: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context },
|
|
187
|
+
})
|
|
188
|
+
: await this.deps.kernel.execute({
|
|
189
|
+
description: task.description,
|
|
190
|
+
goal: task.description,
|
|
191
|
+
tasks: [{
|
|
192
|
+
description: task.description,
|
|
193
|
+
capabilities: task.capabilities.length ? task.capabilities : ['reasoning'],
|
|
194
|
+
input: { prompt, context: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context } },
|
|
195
|
+
}],
|
|
196
|
+
});
|
|
186
197
|
return {
|
|
187
198
|
missionId: mission.id,
|
|
188
199
|
status: mission.status,
|
|
@@ -238,6 +249,14 @@ export class Federation {
|
|
|
238
249
|
}
|
|
239
250
|
return { peerId: target.nodeId, runId, record };
|
|
240
251
|
}
|
|
252
|
+
/** Runs received + executing on THIS node (async handoff), newest last. */
|
|
253
|
+
localRuns() {
|
|
254
|
+
return [...this.runs.values()];
|
|
255
|
+
}
|
|
256
|
+
/** Runs WE dispatched to peers → { runId, peerId } for monitoring. */
|
|
257
|
+
outboundRuns() {
|
|
258
|
+
return [...this.dispatchedRuns.entries()].map(([runId, peerId]) => ({ runId, peerId }));
|
|
259
|
+
}
|
|
241
260
|
/** Query a dispatched run's current record (from whichever peer holds it). */
|
|
242
261
|
async followRun(runId) {
|
|
243
262
|
const local = this.runs.get(runId);
|
|
@@ -9,6 +9,11 @@ import type { ProviderModel, WorkerType } from './types.js';
|
|
|
9
9
|
export interface FreeProviderPreset {
|
|
10
10
|
id: string;
|
|
11
11
|
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* Provider kind. 'local' presets register as local (offline-eligible)
|
|
14
|
+
* providers and require no API key. Defaults to 'cloud' when omitted.
|
|
15
|
+
*/
|
|
16
|
+
type?: 'local' | 'cloud';
|
|
12
17
|
baseURL: string;
|
|
13
18
|
headerName?: string;
|
|
14
19
|
/** Where to get the API key. */
|
|
@@ -24,5 +29,23 @@ export interface FreeProviderPreset {
|
|
|
24
29
|
notes?: string;
|
|
25
30
|
}
|
|
26
31
|
export declare const FREE_PROVIDERS: FreeProviderPreset[];
|
|
32
|
+
/**
|
|
33
|
+
* Local OpenAI-compatible servers the wizard can register. These speak the same
|
|
34
|
+
* /v1/chat/completions + /v1/models API as the cloud providers but run on the
|
|
35
|
+
* user's machine, so they need NO API key and register with type 'local' (which
|
|
36
|
+
* makes the router apply its localBonus and include them in 'offline' mode).
|
|
37
|
+
*
|
|
38
|
+
* knownModels are intentionally empty: every one of these servers exposes the
|
|
39
|
+
* currently loaded model(s) dynamically via /v1/models, so there is no fixed
|
|
40
|
+
* catalog to hard-code. Unreachable servers are marked unhealthy by the health
|
|
41
|
+
* check and simply skipped by the router.
|
|
42
|
+
*/
|
|
43
|
+
export declare const LOCAL_PROVIDERS: FreeProviderPreset[];
|
|
27
44
|
export declare function getProviderPreset(id: string): FreeProviderPreset | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Build ready-to-register config entries for every built-in local provider.
|
|
47
|
+
* Used to register LM Studio / vLLM / SGLang / llama.cpp out of the box when the
|
|
48
|
+
* user has not customized `config.localProviders`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function localProviderConfigs(): CloudProviderConfig[];
|
|
28
51
|
export declare function toCloudProviderConfig(preset: FreeProviderPreset, apiKey: string): CloudProviderConfig;
|
|
@@ -147,8 +147,80 @@ export const FREE_PROVIDERS = [
|
|
|
147
147
|
notes: 'Google Generative Language API. Free tier (15 RPM / 1500 RPD on Flash). Long context.',
|
|
148
148
|
},
|
|
149
149
|
];
|
|
150
|
+
/**
|
|
151
|
+
* Local OpenAI-compatible servers the wizard can register. These speak the same
|
|
152
|
+
* /v1/chat/completions + /v1/models API as the cloud providers but run on the
|
|
153
|
+
* user's machine, so they need NO API key and register with type 'local' (which
|
|
154
|
+
* makes the router apply its localBonus and include them in 'offline' mode).
|
|
155
|
+
*
|
|
156
|
+
* knownModels are intentionally empty: every one of these servers exposes the
|
|
157
|
+
* currently loaded model(s) dynamically via /v1/models, so there is no fixed
|
|
158
|
+
* catalog to hard-code. Unreachable servers are marked unhealthy by the health
|
|
159
|
+
* check and simply skipped by the router.
|
|
160
|
+
*/
|
|
161
|
+
export const LOCAL_PROVIDERS = [
|
|
162
|
+
{
|
|
163
|
+
id: 'lmstudio',
|
|
164
|
+
name: 'LM Studio',
|
|
165
|
+
type: 'local',
|
|
166
|
+
baseURL: 'http://localhost:1234/v1',
|
|
167
|
+
keyUrl: 'https://lmstudio.ai',
|
|
168
|
+
free: true,
|
|
169
|
+
recommended: {},
|
|
170
|
+
knownModels: [],
|
|
171
|
+
notes: 'LM Studio local server. Start it from the "Developer" tab. No API key required.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'vllm',
|
|
175
|
+
name: 'vLLM',
|
|
176
|
+
type: 'local',
|
|
177
|
+
baseURL: 'http://localhost:8000/v1',
|
|
178
|
+
keyUrl: 'https://docs.vllm.ai',
|
|
179
|
+
free: true,
|
|
180
|
+
recommended: {},
|
|
181
|
+
knownModels: [],
|
|
182
|
+
notes: 'vLLM OpenAI-compatible server (`vllm serve <model>`). No API key required.',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'sglang',
|
|
186
|
+
name: 'SGLang',
|
|
187
|
+
type: 'local',
|
|
188
|
+
baseURL: 'http://localhost:30000/v1',
|
|
189
|
+
keyUrl: 'https://docs.sglang.ai',
|
|
190
|
+
free: true,
|
|
191
|
+
recommended: {},
|
|
192
|
+
knownModels: [],
|
|
193
|
+
notes: 'SGLang OpenAI-compatible server (`python -m sglang.launch_server`). No API key required.',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'llamacpp',
|
|
197
|
+
name: 'llama.cpp',
|
|
198
|
+
type: 'local',
|
|
199
|
+
baseURL: 'http://localhost:8080/v1',
|
|
200
|
+
keyUrl: 'https://github.com/ggml-org/llama.cpp',
|
|
201
|
+
free: true,
|
|
202
|
+
recommended: {},
|
|
203
|
+
knownModels: [],
|
|
204
|
+
notes: 'llama.cpp server (`llama-server`). No API key required.',
|
|
205
|
+
},
|
|
206
|
+
];
|
|
150
207
|
export function getProviderPreset(id) {
|
|
151
|
-
return FREE_PROVIDERS.find(p => p.id === id);
|
|
208
|
+
return FREE_PROVIDERS.find(p => p.id === id) ?? LOCAL_PROVIDERS.find(p => p.id === id);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Build ready-to-register config entries for every built-in local provider.
|
|
212
|
+
* Used to register LM Studio / vLLM / SGLang / llama.cpp out of the box when the
|
|
213
|
+
* user has not customized `config.localProviders`.
|
|
214
|
+
*/
|
|
215
|
+
export function localProviderConfigs() {
|
|
216
|
+
return LOCAL_PROVIDERS.map((preset) => ({
|
|
217
|
+
id: preset.id,
|
|
218
|
+
name: preset.name,
|
|
219
|
+
type: 'local',
|
|
220
|
+
baseURL: preset.baseURL,
|
|
221
|
+
headerName: preset.headerName,
|
|
222
|
+
enabled: true,
|
|
223
|
+
}));
|
|
152
224
|
}
|
|
153
225
|
export function toCloudProviderConfig(preset, apiKey) {
|
|
154
226
|
return {
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export type { GeminiProviderOptions } from './providers/gemini-provider.js';
|
|
|
32
32
|
export { ALL_BUILTIN_WORKERS, createResearchWorker, createCodingWorker, createArchitectureWorker, createReviewWorker, createDocumentationWorker, createTranslationWorker, createTerminalWorker, createVerificationWorker, createVisionWorker, createFrontendWorker, createBrowserWorker, } from './workers/builtin-workers.js';
|
|
33
33
|
export { KernelCommandRegistry, DEFAULT_COMMANDS } from './command-system.js';
|
|
34
34
|
export type { KernelCommand, CommandContext } from './command-system.js';
|
|
35
|
+
export { MissionPlanner } from './planner.js';
|
|
36
|
+
export type { MissionPlannerDeps, PlanOptions } from './planner.js';
|
|
37
|
+
export { normalizeCapabilities, CANONICAL_CAPABILITIES } from './capability-map.js';
|
|
38
|
+
export { phaseBenchmarkFloor, phaseLabel } from './scheduler.js';
|
|
35
39
|
export * from './federation/index.js';
|
|
36
40
|
export { createMcpServer, serveMcpStdio } from './mcp/index.js';
|
|
37
41
|
export type { McpServerDeps } from './mcp/index.js';
|
package/dist/kernel/index.js
CHANGED
|
@@ -24,6 +24,9 @@ export { OpenAICompatibleProvider } from './providers/openai-compatible-provider
|
|
|
24
24
|
export { GeminiProvider } from './providers/gemini-provider.js';
|
|
25
25
|
export { ALL_BUILTIN_WORKERS, createResearchWorker, createCodingWorker, createArchitectureWorker, createReviewWorker, createDocumentationWorker, createTranslationWorker, createTerminalWorker, createVerificationWorker, createVisionWorker, createFrontendWorker, createBrowserWorker, } from './workers/builtin-workers.js';
|
|
26
26
|
export { KernelCommandRegistry, DEFAULT_COMMANDS } from './command-system.js';
|
|
27
|
+
export { MissionPlanner } from './planner.js';
|
|
28
|
+
export { normalizeCapabilities, CANONICAL_CAPABILITIES } from './capability-map.js';
|
|
29
|
+
export { phaseBenchmarkFloor, phaseLabel } from './scheduler.js';
|
|
27
30
|
// Federation — neutral device mesh (peers, telemetry, config sync, MOLTFED interop)
|
|
28
31
|
export * from './federation/index.js';
|
|
29
32
|
// MCP — north-facing control surface (spawn/dispatch/role/voice/map over the mesh)
|
package/dist/kernel/kernel.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { PolicyEngine } from './policies.js';
|
|
|
20
20
|
import { VerificationEngine } from './verification-engine.js';
|
|
21
21
|
import { RecoveryManager } from './recovery-manager.js';
|
|
22
22
|
import { KernelCommandRegistry } from './command-system.js';
|
|
23
|
+
import { MissionPlanner } from './planner.js';
|
|
24
|
+
import type { Federation } from './federation/federation.js';
|
|
23
25
|
import type { CommandResult, EventHandler, KernelEvent, KernelEventType, KernelLike, Logger, Mission, MissionInput, Plugin, Policy, ProviderInterface, WorkerDefinition, MissionStatus } from './types.js';
|
|
24
26
|
export interface KernelOptions {
|
|
25
27
|
logger?: Logger;
|
|
@@ -42,8 +44,18 @@ export declare class Kernel implements KernelLike {
|
|
|
42
44
|
readonly logger: Logger;
|
|
43
45
|
/** Canonical OpenKernel slash-command surface (/status, /workers, /goal …). */
|
|
44
46
|
readonly commands: KernelCommandRegistry;
|
|
47
|
+
/** Decomposes a goal into phases when a mission is run with `autoPlan`. */
|
|
48
|
+
readonly planner: MissionPlanner;
|
|
49
|
+
/**
|
|
50
|
+
* The mesh node this kernel is part of, once `infinicode serve`/`mcp` wires it
|
|
51
|
+
* in via {@link attachFederation}. Undefined when running stand-alone. Node
|
|
52
|
+
* commands (/nodes, /node, /activity …) read it off the command context.
|
|
53
|
+
*/
|
|
54
|
+
federation?: Federation;
|
|
45
55
|
private missions;
|
|
46
56
|
constructor(options?: KernelOptions);
|
|
57
|
+
/** Attach the federation mesh node so node/mesh slash-commands can reach it. */
|
|
58
|
+
attachFederation(federation: Federation): void;
|
|
47
59
|
execute(input: MissionInput): Promise<Mission>;
|
|
48
60
|
pause(missionId: string): Promise<void>;
|
|
49
61
|
resume(missionId: string): Promise<Mission>;
|
package/dist/kernel/kernel.js
CHANGED
|
@@ -22,6 +22,7 @@ import { RecoveryManager } from './recovery-manager.js';
|
|
|
22
22
|
import { ConsoleLogger } from './logger.js';
|
|
23
23
|
import { ALL_BUILTIN_WORKERS } from './workers/builtin-workers.js';
|
|
24
24
|
import { KernelCommandRegistry } from './command-system.js';
|
|
25
|
+
import { MissionPlanner } from './planner.js';
|
|
25
26
|
export class Kernel {
|
|
26
27
|
eventBus;
|
|
27
28
|
providerManager;
|
|
@@ -38,6 +39,14 @@ export class Kernel {
|
|
|
38
39
|
logger;
|
|
39
40
|
/** Canonical OpenKernel slash-command surface (/status, /workers, /goal …). */
|
|
40
41
|
commands;
|
|
42
|
+
/** Decomposes a goal into phases when a mission is run with `autoPlan`. */
|
|
43
|
+
planner;
|
|
44
|
+
/**
|
|
45
|
+
* The mesh node this kernel is part of, once `infinicode serve`/`mcp` wires it
|
|
46
|
+
* in via {@link attachFederation}. Undefined when running stand-alone. Node
|
|
47
|
+
* commands (/nodes, /node, /activity …) read it off the command context.
|
|
48
|
+
*/
|
|
49
|
+
federation;
|
|
41
50
|
missions = new Map();
|
|
42
51
|
constructor(options = {}) {
|
|
43
52
|
this.logger = options.logger ?? new ConsoleLogger();
|
|
@@ -89,17 +98,35 @@ export class Kernel {
|
|
|
89
98
|
});
|
|
90
99
|
this.plugins = new PluginManager(this.eventBus, this.logger);
|
|
91
100
|
this.commands = new KernelCommandRegistry(this);
|
|
101
|
+
this.planner = new MissionPlanner({
|
|
102
|
+
providerManager: this.providerManager,
|
|
103
|
+
router: this.router,
|
|
104
|
+
logger: this.logger,
|
|
105
|
+
});
|
|
92
106
|
if (options.loadBuiltinWorkers ?? true) {
|
|
93
107
|
for (const handler of ALL_BUILTIN_WORKERS) {
|
|
94
108
|
this.workerRuntime.registerHandler(handler);
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
}
|
|
112
|
+
/** Attach the federation mesh node so node/mesh slash-commands can reach it. */
|
|
113
|
+
attachFederation(federation) {
|
|
114
|
+
this.federation = federation;
|
|
115
|
+
}
|
|
98
116
|
async execute(input) {
|
|
99
|
-
|
|
117
|
+
// Auto-planning: decompose a bare goal into phases before execution when
|
|
118
|
+
// asked and no tasks/phases were supplied. Each phase → its own objective +
|
|
119
|
+
// model tier (see MissionPlanner + scheduler.planPhases).
|
|
120
|
+
let effective = input;
|
|
121
|
+
if (input.autoPlan && !input.tasks?.length && !input.phases?.length) {
|
|
122
|
+
const policy = this.policies.resolveRef(input.policy).policy;
|
|
123
|
+
const phases = await this.planner.plan(input.goal, { description: input.description, policy });
|
|
124
|
+
effective = { ...input, phases };
|
|
125
|
+
}
|
|
126
|
+
const mission = this.missionEngine.create(effective);
|
|
100
127
|
this.missions.set(mission.id, mission);
|
|
101
128
|
try {
|
|
102
|
-
await this.orchestrator.run(mission,
|
|
129
|
+
await this.orchestrator.run(mission, effective);
|
|
103
130
|
}
|
|
104
131
|
catch (err) {
|
|
105
132
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -38,6 +38,20 @@ export class MissionEngine {
|
|
|
38
38
|
mission.tasks = tasks;
|
|
39
39
|
this.transition(mission, 'READY');
|
|
40
40
|
this.deps.logger.info(`Mission planned: ${mission.id} — ${tasks.length} task(s)`);
|
|
41
|
+
// Broadcast the phase breakdown so harnesses (InfiniBot mesh map) can render
|
|
42
|
+
// the plan + each phase's capabilities and model tier.
|
|
43
|
+
this.emit(mission, 'PLAN_CREATED', {
|
|
44
|
+
missionId: mission.id,
|
|
45
|
+
goal: mission.goal,
|
|
46
|
+
phases: objectives.map(o => ({
|
|
47
|
+
id: o.id,
|
|
48
|
+
phase: o.phase,
|
|
49
|
+
description: o.description,
|
|
50
|
+
capabilities: o.capabilities,
|
|
51
|
+
minBenchmark: o.minBenchmark,
|
|
52
|
+
taskCount: o.taskIds.length,
|
|
53
|
+
})),
|
|
54
|
+
});
|
|
41
55
|
return mission;
|
|
42
56
|
}
|
|
43
57
|
start(mission) {
|
|
@@ -36,6 +36,9 @@ export declare class Orchestrator {
|
|
|
36
36
|
private taskOverrides;
|
|
37
37
|
/** Per-task forced worker type override set by the recovery manager. */
|
|
38
38
|
private taskWorkerOverrides;
|
|
39
|
+
/** How many times each task has been replanned (bounds the replan loop). */
|
|
40
|
+
private taskReplans;
|
|
41
|
+
private maxReplans;
|
|
39
42
|
constructor(deps: OrchestratorDeps);
|
|
40
43
|
run(mission: Mission, input: MissionInput): Promise<Mission>;
|
|
41
44
|
private runTasks;
|
|
@@ -7,6 +7,9 @@ export class Orchestrator {
|
|
|
7
7
|
taskOverrides = new Map();
|
|
8
8
|
/** Per-task forced worker type override set by the recovery manager. */
|
|
9
9
|
taskWorkerOverrides = new Map();
|
|
10
|
+
/** How many times each task has been replanned (bounds the replan loop). */
|
|
11
|
+
taskReplans = new Map();
|
|
12
|
+
maxReplans = 2;
|
|
10
13
|
constructor(deps) {
|
|
11
14
|
this.deps = deps;
|
|
12
15
|
}
|
|
@@ -68,6 +71,15 @@ export class Orchestrator {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
async executeTask(mission, task) {
|
|
74
|
+
// Per-phase model assignment: inherit this task's objective (phase) benchmark
|
|
75
|
+
// floor so demanding phases (architecture, verification) route to a more
|
|
76
|
+
// capable model tier than light phases — instead of one model for the whole
|
|
77
|
+
// mission. Escalation may later raise this floor further.
|
|
78
|
+
if (task.minBenchmark === undefined) {
|
|
79
|
+
const objective = mission.objectives.find(o => o.id === task.objectiveId);
|
|
80
|
+
if (objective?.minBenchmark)
|
|
81
|
+
task.minBenchmark = objective.minBenchmark;
|
|
82
|
+
}
|
|
71
83
|
const overrideType = this.taskWorkerOverrides.get(task.id);
|
|
72
84
|
let workerType = overrideType ?? this.deps.workerRuntime.matchWorkerType(task.capabilities);
|
|
73
85
|
if (!workerType) {
|
|
@@ -90,7 +102,8 @@ export class Orchestrator {
|
|
|
90
102
|
// Per-task verification via the Verification Engine pipeline.
|
|
91
103
|
const vresult = await this.deps.verification.verify(mission, task, mission.policy.policy);
|
|
92
104
|
if (!vresult.passed) {
|
|
93
|
-
//
|
|
105
|
+
// Count verification misses — repeated misses escalate the model tier.
|
|
106
|
+
task.verificationFailures = (task.verificationFailures ?? 0) + 1;
|
|
94
107
|
await this.handleRecovery(mission, task, `verification failed: ${vresult.summary}`, instance);
|
|
95
108
|
}
|
|
96
109
|
}
|
|
@@ -112,6 +125,7 @@ export class Orchestrator {
|
|
|
112
125
|
policy: mission.policy.policy,
|
|
113
126
|
currentProviderId: instance.providerId,
|
|
114
127
|
currentModelId: instance.modelId,
|
|
128
|
+
verificationFailures: task.verificationFailures,
|
|
115
129
|
});
|
|
116
130
|
const maxAttempts = mission.policy.policy.retry?.maxAttempts ?? 3;
|
|
117
131
|
switch (decision.action) {
|
|
@@ -144,6 +158,35 @@ export class Orchestrator {
|
|
|
144
158
|
task.error = message;
|
|
145
159
|
break;
|
|
146
160
|
}
|
|
161
|
+
case 'escalate-model': {
|
|
162
|
+
// Climb the benchmark ladder to a more capable model and raise this
|
|
163
|
+
// task's routing floor so it keeps escalating on further failures.
|
|
164
|
+
if (decision.rotateToProviderId && decision.rotateToModelId) {
|
|
165
|
+
this.taskOverrides.set(task.id, {
|
|
166
|
+
providerId: decision.rotateToProviderId,
|
|
167
|
+
modelId: decision.rotateToModelId,
|
|
168
|
+
});
|
|
169
|
+
if (decision.escalateToBenchmark) {
|
|
170
|
+
task.minBenchmark = Math.max(task.minBenchmark ?? 0, decision.escalateToBenchmark);
|
|
171
|
+
}
|
|
172
|
+
this.deps.eventBus.emit({
|
|
173
|
+
type: 'MODEL_CHANGED',
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
missionId: mission.id,
|
|
176
|
+
taskId: task.id,
|
|
177
|
+
data: {
|
|
178
|
+
from: { providerId: instance.providerId, modelId: instance.modelId },
|
|
179
|
+
to: { providerId: decision.rotateToProviderId, modelId: decision.rotateToModelId },
|
|
180
|
+
reason: decision.reason,
|
|
181
|
+
escalated: true,
|
|
182
|
+
minBenchmark: task.minBenchmark,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
task.status = 'PENDING';
|
|
187
|
+
task.error = message;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
147
190
|
case 'spawn-different-worker': {
|
|
148
191
|
if (decision.newWorkerType) {
|
|
149
192
|
this.taskWorkerOverrides.set(task.id, decision.newWorkerType);
|
|
@@ -153,7 +196,36 @@ export class Orchestrator {
|
|
|
153
196
|
task.error = message;
|
|
154
197
|
break;
|
|
155
198
|
}
|
|
156
|
-
case 'replan':
|
|
199
|
+
case 'replan': {
|
|
200
|
+
// Real replan: checkpoint, then restart the task with a CLEAN slate —
|
|
201
|
+
// drop the provider/model + worker overrides so it re-routes from
|
|
202
|
+
// scratch, and reset the attempt/verification counters. Bounded by
|
|
203
|
+
// maxReplans so an unsatisfiable task can't loop forever.
|
|
204
|
+
const replans = (this.taskReplans.get(task.id) ?? 0) + 1;
|
|
205
|
+
this.taskReplans.set(task.id, replans);
|
|
206
|
+
await this.deps.checkpoints.save(mission, this.collectWorkerStates(mission.id), 'crash');
|
|
207
|
+
if (replans > this.maxReplans) {
|
|
208
|
+
task.status = 'FAILED';
|
|
209
|
+
task.error = `${message} (replan limit reached)`;
|
|
210
|
+
this.deps.eventBus.emit({
|
|
211
|
+
type: 'TASK_FAILED',
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
missionId: mission.id,
|
|
214
|
+
taskId: task.id,
|
|
215
|
+
workerId: instance.id,
|
|
216
|
+
data: { error: task.error, attempts: task.attempts, replans },
|
|
217
|
+
});
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
this.taskOverrides.delete(task.id);
|
|
221
|
+
this.taskWorkerOverrides.delete(task.id);
|
|
222
|
+
task.verificationFailures = 0;
|
|
223
|
+
task.attempts = 0;
|
|
224
|
+
task.status = 'PENDING';
|
|
225
|
+
task.error = message;
|
|
226
|
+
this.deps.logger.info(`Task ${task.id}: replan #${replans} — fresh routing + counters`);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
157
229
|
case 'checkpoint': {
|
|
158
230
|
await this.deps.checkpoints.save(mission, this.collectWorkerStates(mission.id), 'crash');
|
|
159
231
|
task.status = 'PENDING';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — Mission Planner
|
|
3
|
+
*
|
|
4
|
+
* Decomposes a one-line goal into a sequence of PHASES (each becomes an
|
|
5
|
+
* objective), so a large build spreads across the right models by aspect
|
|
6
|
+
* (architecture → implementation → verification) instead of one model
|
|
7
|
+
* one-shotting the whole thing. Each phase carries the capabilities it needs;
|
|
8
|
+
* the scheduler turns those into a per-phase model benchmark floor.
|
|
9
|
+
*
|
|
10
|
+
* LLM-driven when a capable provider is available, with a deterministic
|
|
11
|
+
* heuristic fallback so planning never blocks on model availability.
|
|
12
|
+
*/
|
|
13
|
+
import type { Logger, MissionPhaseInput, Policy } from './types.js';
|
|
14
|
+
import type { ProviderManager } from './provider-manager.js';
|
|
15
|
+
import type { CapabilityRouter } from './router.js';
|
|
16
|
+
import { normalizeCapabilities } from './capability-map.js';
|
|
17
|
+
export interface MissionPlannerDeps {
|
|
18
|
+
providerManager: ProviderManager;
|
|
19
|
+
router: CapabilityRouter;
|
|
20
|
+
logger: Logger;
|
|
21
|
+
}
|
|
22
|
+
export interface PlanOptions {
|
|
23
|
+
description?: string;
|
|
24
|
+
policy?: Policy;
|
|
25
|
+
/** Max phases to request (default 6). */
|
|
26
|
+
maxPhases?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class MissionPlanner {
|
|
29
|
+
private deps;
|
|
30
|
+
constructor(deps: MissionPlannerDeps);
|
|
31
|
+
plan(goal: string, opts?: PlanOptions): Promise<MissionPhaseInput[]>;
|
|
32
|
+
private planWithLlm;
|
|
33
|
+
/** Extract + validate a phase array from a model's (possibly noisy) response. */
|
|
34
|
+
private parsePhases;
|
|
35
|
+
/**
|
|
36
|
+
* Deterministic fallback plan. Always yields buildable phases; adds a frontend
|
|
37
|
+
* phase for UI-shaped goals. Capabilities are natural — the scheduler
|
|
38
|
+
* normalizes them and derives each phase's model tier.
|
|
39
|
+
*/
|
|
40
|
+
private heuristicPhases;
|
|
41
|
+
}
|
|
42
|
+
export { normalizeCapabilities };
|