ai-runtime-engine 2.8.0 → 3.0.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.
@@ -13,13 +13,17 @@ import { actionCapabilityRows, renderActionCapabilities, renderCapabilityGaps }
13
13
  import { renderMcpList, renderMcpDetail } from '../commands/mcp.js';
14
14
  import { candidatesFrom, deriveCapabilitiesOffline } from '../../runtime/planning/deriveCapabilities.js';
15
15
  import { displaySafe } from '../render.js';
16
+ import { AGENT_TERMINAL } from '../../agents/task.js';
17
+ import { TERMINAL as EXEC_TERMINAL } from '../../executions/execution.js';
16
18
  /** Top-level slash commands, for REPL tab-completion (Phase 21b). Kept in sync with the `handle` dispatch. */
17
19
  export const SLASH_COMMANDS = [
18
20
  'help', 'status', 'info', 'doctor', 'cleanup', 'mode', 'compare', 'models', 'config', 'providers', 'tools', 'capabilities', 'mcp',
19
- 'skills', 'memory', 'conversations', 'executions', 'resume', 'resume-execution', 'pause', 'cancel', 'approve',
21
+ 'skills', 'memory', 'conversations', 'executions', 'agents', 'resume', 'resume-execution', 'pause', 'cancel', 'approve',
20
22
  'deny', 'learning', 'feedback', 'permissions', 'budget', 'stream', 'dry-run', 'clear', 'exit', 'quit',
21
23
  ];
22
- const HELP = [
24
+ /** Exported so a test can prove every reachable command is documented — the three touch points below
25
+ * are synced by hand, and `/agents` shipped tab-completable but absent from this list. */
26
+ export const HELP = [
23
27
  'Commands:',
24
28
  ' /help show this help',
25
29
  ' /status workspace, mode, provider count',
@@ -49,6 +53,8 @@ const HELP = [
49
53
  ' /conversations list recent conversations',
50
54
  ' /resume <id> resume a conversation',
51
55
  ' /executions list persisted executions',
56
+ ' /agents list agent tasks (state, progress, spend, findings)',
57
+ ' /agents stop <id> stop one agent task',
52
58
  ' /resume-execution <id> resume an execution',
53
59
  ' /approve <id> approve an execution waiting for approval, then continue',
54
60
  ' /deny <id> deny an execution waiting for approval (cancels it)',
@@ -59,7 +65,7 @@ const HELP = [
59
65
  ' /stream toggle token-by-token streaming of answers',
60
66
  ' /budget show the call/cost budget (AI_MAX_CALLS / AI_MAX_COST_USD)',
61
67
  ' /clear clear the screen',
62
- ' /exit leave the session',
68
+ ' /exit, /quit leave the session',
63
69
  '',
64
70
  'Anything else is sent to the runtime as a request.',
65
71
  ];
@@ -209,6 +215,8 @@ export class ReplSession {
209
215
  return this.resume(args[0]);
210
216
  case 'executions':
211
217
  return this.executionsList();
218
+ case 'agents':
219
+ return args[0] === 'stop' ? this.agentStop(args[1]) : this.agentsList();
212
220
  case 'resume-execution':
213
221
  return this.resumeExecution(args[0]);
214
222
  case 'approve':
@@ -327,6 +335,32 @@ export class ReplSession {
327
335
  return { lines: ['no conversations yet.'] };
328
336
  return { lines: ['Recent conversations:', ...list.map((c, i) => ` ${i + 1}. ${c.id} ${c.title} (${c.turns} turns)`)] };
329
337
  }
338
+ /** Agent tasks across this project's executions, newest first. */
339
+ agentsList() {
340
+ if (!this.runtime.executionStore.enabled)
341
+ return { lines: ['agent tasks are not persisted (stateless mode).'] };
342
+ const tasks = this.runtime.agentTasks().slice(0, 12);
343
+ if (!tasks.length)
344
+ return { lines: ['no agent tasks yet.', '(agents run when `runtime.agents.enabled` is set and a plan delegates to one)'] };
345
+ // A waiting task's question goes on its own line: it is the one thing the user must read to act.
346
+ const lines = tasks.flatMap((t) => {
347
+ const steps = `${t.innerSteps.succeeded}/${t.innerSteps.total}`;
348
+ const spend = `${t.callsUsed}/${t.callsReserved} call(s), ${t.toolCallsUsed} tool call(s)`;
349
+ const why = t.interruption ? ` (${t.interruption.kind})` : '';
350
+ // `stepId` is planner-authored and `question` is model-authored: both go through displaySafe,
351
+ // like every other untrusted string this file renders.
352
+ const row = ` ${t.agentTaskId} [${t.state}] ${t.agentId} @ ${displaySafe(t.stepId, 40)} ${steps} inner step(s), ${spend}, ${t.findings} finding(s)${why}`;
353
+ return t.question ? [row, ` ? ${displaySafe(t.question, 200)}`] : [row];
354
+ });
355
+ return { lines: ['Agent tasks:', ...lines, '', 'stop one with /agents stop <agent-task-id>'] };
356
+ }
357
+ /** Stop one agent task. Every outcome is reported — a stop that looks like nothing happened is a bug. */
358
+ agentStop(id) {
359
+ if (!id)
360
+ return { lines: ['usage: /agents stop <agent-task-id>'] };
361
+ const r = this.runtime.stopAgentTask(id);
362
+ return { lines: [`${r.ok ? 'stopped' : 'not stopped'} ${id}: ${r.reason}${r.state ? ` (state: ${r.state})` : ''}`] };
363
+ }
330
364
  executionsList() {
331
365
  if (!this.runtime.executionStore.enabled)
332
366
  return { lines: ['executions are disabled (stateless mode).'] };
@@ -362,13 +396,31 @@ export class ReplSession {
362
396
  status() {
363
397
  const ws = this.runtime.workspaceInfo();
364
398
  const providers = this.runtime.ai.providers();
365
- return {
366
- lines: [
367
- `workspace: ${ws?.name ?? '(none)'}${ws?.git.branch ? ` @ ${ws.git.branch}` : ''}`,
368
- `mode: ${this.mode}`,
369
- `providers: ${providers.length} configured (${providers.filter((p) => p.enabled).length} enabled)`,
370
- ],
371
- };
399
+ const lines = [
400
+ `workspace: ${ws?.name ?? '(none)'}${ws?.git.branch ? ` @ ${ws.git.branch}` : ''}`,
401
+ `mode: ${this.mode}`,
402
+ `providers: ${providers.length} configured (${providers.filter((p) => p.enabled).length} enabled)`,
403
+ ];
404
+ // Phase 3.6: agent work is the one thing that can be UNFINISHED and invisible — a task left waiting
405
+ // or interrupted holds its execution up, so /status names it rather than leaving the user to think
406
+ // to run /agents. Absent entirely when nothing has ever delegated, so the flag-off output is
407
+ // unchanged.
408
+ // Only executions that are themselves still going: a non-terminal task on a finished execution is
409
+ // stale bookkeeping, and reporting it as "unfinished work" forever would train the user to ignore
410
+ // this line — which is the one line that has to be trusted when something IS waiting.
411
+ const live = this.runtime.executionStore.enabled ? this.runtime.executions().filter((e) => !EXEC_TERMINAL.has(e.status)) : [];
412
+ const tasks = live.flatMap((e) => this.runtime.agentTasks(e.id));
413
+ if (tasks.length) {
414
+ const unfinished = tasks.filter((t) => !AGENT_TERMINAL.has(t.state));
415
+ const waiting = unfinished.filter((t) => t.state === 'waiting_for_clarification' || t.state === 'waiting_for_input');
416
+ lines.push(`agents: ${tasks.length} task(s), ${unfinished.length} unfinished${waiting.length ? `, ${waiting.length} waiting for an answer` : ''}`);
417
+ for (const t of unfinished.slice(0, 3)) {
418
+ lines.push(` ${t.agentTaskId} [${t.state}] ${displaySafe(t.agentId, 24)} @ ${displaySafe(t.stepId, 16)} ${t.innerSteps.succeeded}/${t.innerSteps.total} step(s)`);
419
+ }
420
+ if (unfinished.length > 3)
421
+ lines.push(` … ${unfinished.length - 3} more (/agents)`);
422
+ }
423
+ return { lines };
372
424
  }
373
425
  setOrShowMode(next) {
374
426
  if (!next)
@@ -35,11 +35,11 @@ export declare const persistedAgentTask: z.ZodObject<{
35
35
  detail: z.ZodOptional<z.ZodString>;
36
36
  }, "strict", z.ZodTypeAny, {
37
37
  at: number;
38
- kind: "pause" | "crash" | "parent-cancel";
38
+ kind: "crash" | "pause" | "parent-cancel";
39
39
  detail?: string | undefined;
40
40
  }, {
41
41
  at: number;
42
- kind: "pause" | "crash" | "parent-cancel";
42
+ kind: "crash" | "pause" | "parent-cancel";
43
43
  detail?: string | undefined;
44
44
  }>>;
45
45
  provenance: z.ZodObject<{
@@ -361,42 +361,14 @@ export declare const persistedAgentTask: z.ZodObject<{
361
361
  }, "strict", z.ZodTypeAny, {
362
362
  v: 1;
363
363
  state: "running" | "failed" | "completed" | "waiting_for_clarification" | "cancelled" | "paused" | "waiting_for_input" | "created" | "queued";
364
- createdAt: number;
365
- updatedAt: number;
366
364
  stepId: string;
367
365
  agentTaskId: string;
368
366
  callsUsed: number;
369
- envelopeHash: string;
370
367
  agentId: string;
371
- provenance: {
372
- planVersion: number;
373
- executionId?: string | undefined;
374
- };
375
- agentDefHash: string;
376
- stepInputHash: string;
377
- attempt: number;
378
- innerCompletedSteps: string[];
379
- innerObservations: z.objectOutputType<{
380
- stepId: z.ZodString;
381
- skill: z.ZodOptional<z.ZodString>;
382
- tool: z.ZodOptional<z.ZodString>;
383
- agent: z.ZodOptional<z.ZodString>;
384
- agentTaskId: z.ZodOptional<z.ZodString>;
385
- ok: z.ZodBoolean;
386
- output: z.ZodOptional<z.ZodString>;
387
- error: z.ZodOptional<z.ZodString>;
388
- code: z.ZodOptional<z.ZodString>;
389
- data: z.ZodOptional<z.ZodUnknown>;
390
- artifacts: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
391
- callsUsed: z.ZodOptional<z.ZodNumber>;
392
- }, z.ZodTypeAny, "passthrough">[];
393
- innerObservationsOmitted: number;
394
368
  innerSteps: {
395
369
  succeeded: number;
396
370
  total: number;
397
371
  };
398
- callsReserved: number;
399
- callsRefunded: number;
400
372
  toolCallsUsed: number;
401
373
  findings: z.objectOutputType<{
402
374
  id: z.ZodString;
@@ -440,7 +412,42 @@ export declare const persistedAgentTask: z.ZodObject<{
440
412
  supersededBy: z.ZodOptional<z.ZodString>;
441
413
  createdAt: z.ZodNumber;
442
414
  }, z.ZodTypeAny, "passthrough">[];
415
+ createdAt: number;
416
+ updatedAt: number;
417
+ envelopeHash: string;
418
+ provenance: {
419
+ planVersion: number;
420
+ executionId?: string | undefined;
421
+ };
422
+ agentDefHash: string;
423
+ stepInputHash: string;
424
+ attempt: number;
425
+ innerCompletedSteps: string[];
426
+ innerObservations: z.objectOutputType<{
427
+ stepId: z.ZodString;
428
+ skill: z.ZodOptional<z.ZodString>;
429
+ tool: z.ZodOptional<z.ZodString>;
430
+ agent: z.ZodOptional<z.ZodString>;
431
+ agentTaskId: z.ZodOptional<z.ZodString>;
432
+ ok: z.ZodBoolean;
433
+ output: z.ZodOptional<z.ZodString>;
434
+ error: z.ZodOptional<z.ZodString>;
435
+ code: z.ZodOptional<z.ZodString>;
436
+ data: z.ZodOptional<z.ZodUnknown>;
437
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
438
+ callsUsed: z.ZodOptional<z.ZodNumber>;
439
+ }, z.ZodTypeAny, "passthrough">[];
440
+ innerObservationsOmitted: number;
441
+ callsReserved: number;
442
+ callsRefunded: number;
443
443
  diagnostics: unknown[];
444
+ interruption?: {
445
+ at: number;
446
+ kind: "crash" | "pause" | "parent-cancel";
447
+ detail?: string | undefined;
448
+ } | undefined;
449
+ startedAt?: number | undefined;
450
+ endedAt?: number | undefined;
444
451
  innerPlan?: {
445
452
  id: string;
446
453
  version: number;
@@ -458,13 +465,6 @@ export declare const persistedAgentTask: z.ZodObject<{
458
465
  goal: string;
459
466
  reason?: string | undefined;
460
467
  } | undefined;
461
- startedAt?: number | undefined;
462
- endedAt?: number | undefined;
463
- interruption?: {
464
- at: number;
465
- kind: "pause" | "crash" | "parent-cancel";
466
- detail?: string | undefined;
467
- } | undefined;
468
468
  innerPlanVersion?: number | undefined;
469
469
  innerCheckpoint?: z.objectOutputType<{
470
470
  at: z.ZodNumber;
@@ -488,42 +488,14 @@ export declare const persistedAgentTask: z.ZodObject<{
488
488
  }, {
489
489
  v: 1;
490
490
  state: "running" | "failed" | "completed" | "waiting_for_clarification" | "cancelled" | "paused" | "waiting_for_input" | "created" | "queued";
491
- createdAt: number;
492
- updatedAt: number;
493
491
  stepId: string;
494
492
  agentTaskId: string;
495
493
  callsUsed: number;
496
- envelopeHash: string;
497
494
  agentId: string;
498
- provenance: {
499
- planVersion: number;
500
- executionId?: string | undefined;
501
- };
502
- agentDefHash: string;
503
- stepInputHash: string;
504
- attempt: number;
505
- innerCompletedSteps: string[];
506
- innerObservations: z.objectInputType<{
507
- stepId: z.ZodString;
508
- skill: z.ZodOptional<z.ZodString>;
509
- tool: z.ZodOptional<z.ZodString>;
510
- agent: z.ZodOptional<z.ZodString>;
511
- agentTaskId: z.ZodOptional<z.ZodString>;
512
- ok: z.ZodBoolean;
513
- output: z.ZodOptional<z.ZodString>;
514
- error: z.ZodOptional<z.ZodString>;
515
- code: z.ZodOptional<z.ZodString>;
516
- data: z.ZodOptional<z.ZodUnknown>;
517
- artifacts: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
518
- callsUsed: z.ZodOptional<z.ZodNumber>;
519
- }, z.ZodTypeAny, "passthrough">[];
520
- innerObservationsOmitted: number;
521
495
  innerSteps: {
522
496
  succeeded: number;
523
497
  total: number;
524
498
  };
525
- callsReserved: number;
526
- callsRefunded: number;
527
499
  toolCallsUsed: number;
528
500
  findings: z.objectInputType<{
529
501
  id: z.ZodString;
@@ -567,7 +539,42 @@ export declare const persistedAgentTask: z.ZodObject<{
567
539
  supersededBy: z.ZodOptional<z.ZodString>;
568
540
  createdAt: z.ZodNumber;
569
541
  }, z.ZodTypeAny, "passthrough">[];
542
+ createdAt: number;
543
+ updatedAt: number;
544
+ envelopeHash: string;
545
+ provenance: {
546
+ planVersion: number;
547
+ executionId?: string | undefined;
548
+ };
549
+ agentDefHash: string;
550
+ stepInputHash: string;
551
+ attempt: number;
552
+ innerCompletedSteps: string[];
553
+ innerObservations: z.objectInputType<{
554
+ stepId: z.ZodString;
555
+ skill: z.ZodOptional<z.ZodString>;
556
+ tool: z.ZodOptional<z.ZodString>;
557
+ agent: z.ZodOptional<z.ZodString>;
558
+ agentTaskId: z.ZodOptional<z.ZodString>;
559
+ ok: z.ZodBoolean;
560
+ output: z.ZodOptional<z.ZodString>;
561
+ error: z.ZodOptional<z.ZodString>;
562
+ code: z.ZodOptional<z.ZodString>;
563
+ data: z.ZodOptional<z.ZodUnknown>;
564
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
565
+ callsUsed: z.ZodOptional<z.ZodNumber>;
566
+ }, z.ZodTypeAny, "passthrough">[];
567
+ innerObservationsOmitted: number;
568
+ callsReserved: number;
569
+ callsRefunded: number;
570
570
  diagnostics: unknown[];
571
+ interruption?: {
572
+ at: number;
573
+ kind: "crash" | "pause" | "parent-cancel";
574
+ detail?: string | undefined;
575
+ } | undefined;
576
+ startedAt?: number | undefined;
577
+ endedAt?: number | undefined;
571
578
  innerPlan?: {
572
579
  id: string;
573
580
  version: number;
@@ -585,13 +592,6 @@ export declare const persistedAgentTask: z.ZodObject<{
585
592
  goal: string;
586
593
  reason?: string | undefined;
587
594
  } | undefined;
588
- startedAt?: number | undefined;
589
- endedAt?: number | undefined;
590
- interruption?: {
591
- at: number;
592
- kind: "pause" | "crash" | "parent-cancel";
593
- detail?: string | undefined;
594
- } | undefined;
595
595
  innerPlanVersion?: number | undefined;
596
596
  innerCheckpoint?: z.objectInputType<{
597
597
  at: z.ZodNumber;
@@ -613,15 +613,16 @@ export declare const persistedAgentTask: z.ZodObject<{
613
613
  message: z.ZodString;
614
614
  }, z.ZodTypeAny, "passthrough"> | undefined;
615
615
  }>;
616
- export interface AgentTaskView {
616
+ /** The result of reading agent tasks back: what parsed, and how much did not. */
617
+ export interface AgentTasksRead {
617
618
  tasks: AgentTaskRecord[];
618
- /** Records that failed validation and were left out of the view (still on disk, untouched). */
619
+ /** Records that failed validation and were left out (still on disk, untouched). */
619
620
  dropped: number;
620
621
  }
621
622
  /**
622
623
  * Validate the agent tasks on an execution. Pure: it reads, it never writes. Callers that then persist
623
624
  * the execution are choosing to drop the unparseable records — reading alone never does.
624
625
  */
625
- export declare function parseAgentTasks(exec: Pick<Execution, 'agentTasks'>): AgentTaskView;
626
+ export declare function parseAgentTasks(exec: Pick<Execution, 'agentTasks'>): AgentTasksRead;
626
627
  /** One task by id, validated — the lookup every resume path uses. */
627
628
  export declare function findAgentTask(exec: Pick<Execution, 'agentTasks'>, agentTaskId: string): AgentTaskRecord | undefined;
package/dist/index.d.ts CHANGED
@@ -102,8 +102,9 @@ export type { AdmissionRejection, AdmissionResult } from './agents/admit.js';
102
102
  export type { StepObservationCode } from './orchestration/executor.js';
103
103
  export { PLAN_STEP_STATUSES } from './orchestration/plan.js';
104
104
  export { parseAgentTasks, findAgentTask } from './executions/agentTasks.js';
105
- export type { AgentTaskView } from './executions/agentTasks.js';
106
- export { AGENT_TERMINAL, AGENT_RESUMABLE } from './agents/task.js';
105
+ export type { AgentTasksRead } from './executions/agentTasks.js';
106
+ export { AGENT_TERMINAL, AGENT_RESUMABLE, agentTaskView } from './agents/task.js';
107
+ export type { AgentTaskView } from './agents/task.js';
107
108
  export type { ProgressSnapshot } from './orchestration/executor.js';
108
109
  export { mcpToolId } from './mcp/toolAdapter.js';
109
110
  export { MCP_PROTOCOL_VERSION } from './mcp/protocol.js';
package/dist/index.js CHANGED
@@ -80,7 +80,7 @@ export { PLAN_STEP_STATUSES } from './orchestration/plan.js';
80
80
  // trust records this version cannot parse. The state sets are exported with it because "is this task
81
81
  // finished?" must have one answer, not one per caller.
82
82
  export { parseAgentTasks, findAgentTask } from './executions/agentTasks.js';
83
- export { AGENT_TERMINAL, AGENT_RESUMABLE } from './agents/task.js';
83
+ export { AGENT_TERMINAL, AGENT_RESUMABLE, agentTaskView } from './agents/task.js';
84
84
  // ── MCP connectivity (Phase 3.2) — external servers as ordinary Runtime tools ──
85
85
  // The CONFIG + STATUS surface is public; the client, transports, and manager internals are not, so the
86
86
  // wire implementation stays free to change without a breaking release.
@@ -34,7 +34,11 @@ async function runStepInner(step, deps) {
34
34
  if (step.agent) {
35
35
  if (!deps.runAgent)
36
36
  return { stepId: step.id, agent: step.agent, ok: false, code: 'agent-not-enabled', error: 'agent execution is not enabled' };
37
- return deps.runAgent(step, { reservation: stepCalls(step, Number.POSITIVE_INFINITY, deps.reserve), ...(deps.signal ? { signal: deps.signal } : {}) });
37
+ // `return await`, NOT a bare return. In an async function a returned promise is ADOPTED, not
38
+ // caught, so a bare return here escapes this try/catch entirely: a throwing agent rejected the
39
+ // whole executePlan instead of failing its own step, and left its wave-mates running unawaited.
40
+ // The skill and tool branches above both await, which is why only agents had this hole.
41
+ return await deps.runAgent(step, { reservation: stepCalls(step, Number.POSITIVE_INFINITY, deps.reserve), ...(deps.signal ? { signal: deps.signal } : {}) });
38
42
  }
39
43
  return { stepId: step.id, ok: false, error: 'step names neither a skill nor a tool' };
40
44
  }
@@ -43,7 +43,8 @@ export interface OrchestrateInput {
43
43
  routing?: RoutingPreferences;
44
44
  /** Phase 22: run the phases that fit the call budget and pause resumably (vs. the default notify-and-wait). */
45
45
  partial?: boolean;
46
- /** Phase 3.1: pre-rendered action-capability snapshot for the planner prompt (opt-in). */
46
+ /** Phase 3.1: pre-rendered, fenced action-capability snapshot for the planner prompt. Present by
47
+ * default from 3.0.0; `runtime.capabilities.catalog: false` removes it. */
47
48
  capabilityCatalog?: string;
48
49
  /** Phase 3.3: pre-rendered "Required capabilities" block for the planner prompt (opt-in). */
49
50
  requiredCapabilities?: string;
@@ -18,7 +18,8 @@ export interface PlannerInput {
18
18
  reason?: string;
19
19
  /** Observations from a prior attempt, to inform a replan. */
20
20
  priorObservations?: string[];
21
- /** Phase 3.1: a pre-rendered, capped, fenced action-capability snapshot. Absent the prompt is
21
+ /** Phase 3.1: a pre-rendered, capped, fenced action-capability snapshot (fenced for real as of
22
+ * 3.0.0, and present by default from it). Absent ⇒ the prompt is
22
23
  * byte-identical to 2.3.0 (the catalog flag is off by default). */
23
24
  capabilityCatalog?: string;
24
25
  /** Phase 3.3: a pre-rendered, clamped block naming the capabilities the goal was derived to need and
@@ -40,7 +40,7 @@ const agentDefinition = z
40
40
  .strict();
41
41
  /** An agent definition id: the same prompt-safe shape an MCP server id must have. */
42
42
  const AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,32}$/;
43
- const runtimeSettings = z.object({ defaultMode: z.enum(RUNTIME_MODES).optional(), defaultStrategy: z.enum(STRATEGIES).optional(), context: z.object({ maxTokens: z.number().optional(), verifyLoss: z.boolean().optional(), summarize: z.boolean().optional() }).strict().optional(), skills: z.object({ paths: z.array(z.string()).optional(), packages: z.array(z.string()).optional(), autoload: z.boolean().optional() }).strict().optional(), embedding: z.object({ provider: z.enum(['local', 'openai-compatible']), baseUrl: z.string().optional(), apiKeyEnv: z.string().optional(), model: z.string().optional() }).strict().optional(), intent: z.object({ aiFallback: z.boolean().optional() }).strict().optional(), organization: z.string().optional(), storage: z.object({ encrypt: z.boolean(), keyEnv: z.string() }).strict().optional(), capabilities: z.object({ catalog: z.boolean().optional(), planning: z.boolean().optional(), aliases: z.record(z.string(), z.string()).optional(), pins: z.record(z.string(), z.string()).optional() }).strict().optional(), concurrency: z.object({ maxParallelSteps: z.number().optional(), perTool: z.record(z.string(), z.number()).optional(), perSkill: z.record(z.string(), z.number()).optional(), perProvider: z.record(z.string(), z.number()).optional(), perAgent: z.record(z.string(), z.number()).optional() }).strict().optional(), agents: z.object({ enabled: z.boolean().optional(), maxToolCalls: z.number().optional(), maxDurationMs: z.number().optional(), maxInnerCalls: z.number().optional(), definitions: z.record(z.string().regex(AGENT_ID_RE, 'an agent definition id must be lowercase kebab/snake (max 33 chars)'), agentDefinition).optional() }).strict().optional() }).strict();
43
+ const runtimeSettings = z.object({ defaultMode: z.enum(RUNTIME_MODES).optional(), defaultStrategy: z.enum(STRATEGIES).optional(), context: z.object({ maxTokens: z.number().optional(), verifyLoss: z.boolean().optional(), summarize: z.boolean().optional() }).strict().optional(), skills: z.object({ paths: z.array(z.string()).optional(), packages: z.array(z.string()).optional(), autoload: z.boolean().optional() }).strict().optional(), embedding: z.object({ provider: z.enum(['local', 'openai-compatible']), baseUrl: z.string().optional(), apiKeyEnv: z.string().optional(), model: z.string().optional() }).strict().optional(), intent: z.object({ aiFallback: z.boolean().optional() }).strict().optional(), organization: z.string().optional(), storage: z.object({ encrypt: z.boolean(), keyEnv: z.string() }).strict().optional(), capabilities: z.object({ catalog: z.boolean().optional(), planning: z.boolean().optional(), aliases: z.record(z.string(), z.string()).optional(), pins: z.record(z.string(), z.string()).optional() }).strict().optional(), concurrency: z.object({ maxParallelSteps: z.number().optional(), perTool: z.record(z.string(), z.number()).optional(), perSkill: z.record(z.string(), z.number()).optional(), perProvider: z.record(z.string(), z.number()).optional(), perAgent: z.record(z.string(), z.number()).optional() }).strict().optional(), agents: z.object({ enabled: z.boolean().optional(), decompose: z.boolean().optional(), maxToolCalls: z.number().optional(), maxDurationMs: z.number().optional(), maxInnerCalls: z.number().optional(), definitions: z.record(z.string().regex(AGENT_ID_RE, 'an agent definition id must be lowercase kebab/snake (max 33 chars)').refine((id) => !id.startsWith('auto_'), 'the `auto_` prefix is reserved for agents the runtime derives — pick another id'), agentDefinition).optional() }).strict().optional() }).strict();
44
44
  const learning = z.object({ enabled: z.boolean().optional() }).strict();
45
45
  const verification = z.object({ enabled: z.boolean().optional() }).strict();
46
46
  const budget = z.object({ maxCostUsd: z.number().optional(), maxCalls: z.number().optional() }).strict();
@@ -6,10 +6,15 @@
6
6
  * Token-streaming extension point: `response.delta` is reserved here (content-bearing, redacted like
7
7
  * everything else). Provider token streaming is NOT implemented in 1.0 — no delta is ever emitted yet —
8
8
  * but declaring the arm keeps the host/emitter architecture ready for it without a breaking change.
9
+ *
10
+ * EVOLUTION CONTRACT: this union GROWS. A new arm is additive at runtime — an existing consumer keeps
11
+ * receiving the events it knows — but it breaks an exhaustive `switch` at COMPILE time. Consumers must
12
+ * carry a default case. Arms added this way are announced in the CHANGELOG.
9
13
  */
10
14
  import type { Clock } from '../util/clock.js';
11
15
  import type { ErrorCategory } from '../types.js';
12
16
  import type { ExecutableMode, ModeSource, RuntimeMode, RuntimeStatus } from './types.js';
17
+ import type { AgentTaskState } from '../agents/task.js';
13
18
  export type RuntimeEvent = {
14
19
  type: 'runtime.started';
15
20
  ts: number;
@@ -54,6 +59,45 @@ export type RuntimeEvent = {
54
59
  ts: number;
55
60
  runId: string;
56
61
  text: string;
62
+ } | {
63
+ type: 'agent.task.started';
64
+ ts: number;
65
+ runId: string;
66
+ agentTaskId: string;
67
+ agentId: string;
68
+ stepId: string;
69
+ state: AgentTaskState;
70
+ } | {
71
+ type: 'agent.task.progress';
72
+ ts: number;
73
+ runId: string;
74
+ agentTaskId: string;
75
+ agentId: string;
76
+ stepId: string;
77
+ /** Per inner WAVE, never per inner step: a chatty agent would otherwise flood the ring buffer
78
+ * and push every other event out of it. */
79
+ innerSteps: {
80
+ total: number;
81
+ succeeded: number;
82
+ };
83
+ callsUsed: number;
84
+ toolCallsUsed: number;
85
+ } | {
86
+ type: 'agent.task.completed';
87
+ ts: number;
88
+ runId: string;
89
+ agentTaskId: string;
90
+ agentId: string;
91
+ stepId: string;
92
+ state: AgentTaskState;
93
+ innerSteps: {
94
+ total: number;
95
+ succeeded: number;
96
+ };
97
+ callsUsed: number;
98
+ toolCallsUsed: number;
99
+ /** How many findings were ADMITTED. Never the findings themselves. */
100
+ findings: number;
57
101
  };
58
102
  /** Distributive Omit so an event can be emitted without pre-stamping `ts`. */
59
103
  type WithoutTs<T> = T extends unknown ? Omit<T, 'ts'> : never;
@@ -6,6 +6,10 @@
6
6
  * Token-streaming extension point: `response.delta` is reserved here (content-bearing, redacted like
7
7
  * everything else). Provider token streaming is NOT implemented in 1.0 — no delta is ever emitted yet —
8
8
  * but declaring the arm keeps the host/emitter architecture ready for it without a breaking change.
9
+ *
10
+ * EVOLUTION CONTRACT: this union GROWS. A new arm is additive at runtime — an existing consumer keeps
11
+ * receiving the events it knows — but it breaks an exhaustive `switch` at COMPILE time. Consumers must
12
+ * carry a default case. Arms added this way are announced in the CHANGELOG.
9
13
  */
10
14
  import { systemClock } from '../util/clock.js';
11
15
  import { redact } from '../security/redact.js';
@@ -25,6 +25,7 @@ import type { SkillSource, LoadedSource } from '../skills/discovery.js';
25
25
  import type { AgentDefinition } from '../agents/definition.js';
26
26
  import { ExecutionStore } from '../executions/store.js';
27
27
  import type { Execution } from '../executions/execution.js';
28
+ import type { AgentTaskState, AgentTaskView } from '../agents/task.js';
28
29
  import { ArtifactStore } from '../artifacts/artifacts.js';
29
30
  import type { CompareInput } from '../comparison/comparator.js';
30
31
  import type { ComparisonResult } from '../comparison/comparison.js';
@@ -87,6 +88,8 @@ export declare class Runtime {
87
88
  /** Live runs, so a pause/cancel can abort the agent tasks actually in flight. Only ever populated
88
89
  * when agents are enabled, so pause/cancel are unchanged with the flag off. */
89
90
  private readonly liveRuns;
91
+ /** Phase 3.6: agent tasks running RIGHT NOW in this process, and how to stop each one on its own. */
92
+ private readonly liveAgentTasks;
90
93
  /** The config file's `budget:` ceilings, kept only so the 3.3 pre-pass can decline a model call. */
91
94
  private readonly _configBudget?;
92
95
  private readonly approval?;
@@ -175,6 +178,15 @@ export declare class Runtime {
175
178
  * This run's agent envelopes. THE ONLY call site of `narrowEnvelope` — never re-derive an inner
176
179
  * catalog, a permission clamp, or a reservation anywhere else (see the header of agents/envelope.ts).
177
180
  */
181
+ /**
182
+ * Agents synthesized from the registry for this goal (Phase 3.7). Empty unless
183
+ * `runtime.agents.decompose` is on — so with the flag off nothing about planning changes.
184
+ *
185
+ * Deterministic and offline: no model call, no clock, no randomness. That is a requirement, not a
186
+ * preference — a derived definition is hashed into `agentDefHash`, and a resume that synthesized
187
+ * even slightly differently would discard every persisted inner plan as stale.
188
+ */
189
+ private derivedAgents;
178
190
  private agentEnvelopes;
179
191
  /** The MCP server manager: `list()`, `status(id)`, `test(id)`, `addServer`, `removeServer`, `setEnabled`. */
180
192
  mcp(): McpManager;
@@ -285,14 +297,28 @@ export declare class Runtime {
285
297
  private runPersistence;
286
298
  /** Run `fn` while heartbeating the execution lease so a long run never lets the lease expire. */
287
299
  private withHeartbeat;
300
+ /**
301
+ * Build the OrchestrateInput. The optional half is an OBJECT, not a positional tail: this function
302
+ * grew to ten parameters and a caller that passed five of them silently got no sink, no signal and no
303
+ * provenance — a replan that persisted nothing and could not be cancelled. Named fields cannot be
304
+ * short-counted.
305
+ */
288
306
  private orchestrateInput;
289
307
  /** Record an EXECUTED orchestration outcome for learning. plan-only, dry-run, and waiting states are
290
308
  * skipped — no skill ran, so there is no success/failure to learn (recording them would teach noise). */
291
309
  private recordOrchestration;
292
310
  /**
293
- * A capped, FENCED action-capability snapshot for the planner prompt (Phase 3.1, opt-in). Untrusted
294
- * sources (anything not an in-tree builtin) have their descriptions fenced, and the block is bounded so
295
- * a large catalog can never dominate the prompt.
311
+ * A capped, FENCED action-capability snapshot for the planner prompt (Phase 3.1; ON by default from
312
+ * 3.0.0 set `runtime.capabilities.catalog: false` to remove it).
313
+ *
314
+ * The fence is real as of 3.0.0 and was not before: this block carries ids that come from MCP servers
315
+ * and third-party skills, and it renders them as trusted-looking prompt structure on every planning
316
+ * iteration of every run. Flattening (`promptSafe`) bounds their shape but says nothing about their
317
+ * provenance, so the whole block is wrapped as untrusted data. Three comments claimed "fenced" while
318
+ * no fence existed; shipping that ON by default would have made a false safety claim load-bearing.
319
+ *
320
+ * Both halves are bounded. The blocked-skill list had no cap at all — measured at ~24k characters
321
+ * with 300 blocked skills, silently, in every prompt.
296
322
  */
297
323
  private capabilityCatalogText;
298
324
  /** Any call/cost ceiling declared in the config file's `budget:` block (router-level, not policy). */
@@ -364,6 +390,26 @@ export declare class Runtime {
364
390
  * an agent step would have failed every one of those steps.
365
391
  */
366
392
  private orchestrateRunners;
393
+ /**
394
+ * Reconcile findings that contradict each other, across ALL of this execution's agent tasks
395
+ * (Phase 3.7).
396
+ *
397
+ * `resolveConflicts` has existed since 3.4 with no caller, so two agents reaching opposite
398
+ * conclusions about the same subject both stayed `active` — and both were rendered into the next
399
+ * planning prompt, as if the runtime had no opinion about which was better supported. It does: it
400
+ * weighs evidence-based `confidence`, with `executionCoverage` only as a tiebreak.
401
+ *
402
+ * Runs at the ONE point new findings can appear — a task reaching a terminal state — and writes the
403
+ * outcome back onto the owning records, so a supersession survives a restart rather than being
404
+ * recomputed (and possibly recomputed differently) on every read.
405
+ *
406
+ * EVERY finding is passed in, not just the active ones. Resolving over the active subset makes the
407
+ * result depend on the order tasks happen to finish: a finding that beat a weak rival in wave 1 can
408
+ * itself lose in wave 2, and the wave-1 loser is then left pointing at a superseded finding — a
409
+ * broken chain nothing heals. Re-resolving the whole set each round is order-independent and gives
410
+ * the same answer as one pass over the final set.
411
+ */
412
+ private resolveFindingConflicts;
367
413
  /**
368
414
  * A bounded, fenced brief of what the agents have already established (Phase 3.5).
369
415
  *
@@ -409,6 +455,27 @@ export declare class Runtime {
409
455
  * hand one step's completed inner work to a different step with the same id and a different input.
410
456
  */
411
457
  private agentResumeLookup;
458
+ /**
459
+ * Every agent task this Runtime can see, newest execution first (Phase 3.6). Live ones (running in
460
+ * this process) and persisted ones are the same list: a task's record IS its status, so there is no
461
+ * second source to disagree with.
462
+ */
463
+ agentTasks(executionId?: string): AgentTaskView[];
464
+ /**
465
+ * Stop one agent task. Four cases, and NONE of them is a silent no-op — a `stop` that appears to do
466
+ * nothing is indistinguishable from a bug:
467
+ *
468
+ * - running in this process: abort it, then let the worker record `cancelled` as it unwinds;
469
+ * - persisted and not finished (another process, or a dead one): write `cancelled` with a
470
+ * `parent-cancel` interruption, so the record stops claiming it is queued or running;
471
+ * - already finished: report that, and change nothing — a terminal state is sticky;
472
+ * - unknown id: say so.
473
+ */
474
+ stopAgentTask(agentTaskId: string): {
475
+ ok: boolean;
476
+ state?: AgentTaskState;
477
+ reason: string;
478
+ };
412
479
  /** Abort a run that is in flight, recording WHY so a task can tell a pause from a cancellation. */
413
480
  private abortLiveRun;
414
481
  /** Mark an execution paused (it can be resumed later). */