llm-orchestrator 1.2.4 → 1.2.5

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "llm-orchestrator",
3
3
  "description": "Write /task once — it plans the work, shards it across parallel subagents, gates every phase and verifies before claiming done. Claude Code, Codex, OpenCode, Kilo.",
4
- "version": "1.2.4",
4
+ "version": "1.2.5",
5
5
  "author": {
6
6
  "name": "Bogdan-Gabriel Torcescu",
7
7
  "url": "https://www.linkedin.com/in/bogdantorcescu/"
package/README.md CHANGED
@@ -369,7 +369,12 @@ more instructions:
369
369
  a typed run, and counts as `trivial_overreach` in the audit. Trivial runs close at the end of the
370
370
  turn (`Stop` hook), so single-prompt sessions still reach the history. Typed runs stay open across
371
371
  turns and close when the session ends (`SessionEnd`), so an unclosed run is never lost.
372
- - Subagents are counted once each: resuming one (SendMessage) is not a new dispatch.
372
+ - Subagents are counted once each: resuming one (SendMessage) is not a new dispatch. Each is also
373
+ classified by its agent type — one of the orchestrator's roles, or a generic agent such as
374
+ `general-purpose` — and the dispatch reminder names the roles of the task's flow. The audit
375
+ reports `role_dispatches`, `generic_dispatches` and `runs_without_roles`. The Claude Code plugin
376
+ ships the roles as agents (`llm-orchestrator:<role>`), so they are available without a project
377
+ install.
373
378
  - A run opened with two or more shards whose main thread keeps doing the work — two work calls per planned shard, no
374
379
  subagent started — gets one more sentence: dispatch the independent shards (searching for the
375
380
  Agent tool if it is deferred), or declare the chain inline with
@@ -26,7 +26,7 @@ description: ${role.description}
26
26
  ---
27
27
  ${MD_MARKER}
28
28
 
29
- Mandatory — before acting, load and follow \`.agents/skills/orchestrate/SKILL.md\`.
29
+ Mandatory — before acting, load and follow the \`orchestrate-core\` skill (\`.agents/skills/orchestrate/SKILL.md\` in a project install). You work inside the parent's run: never open or close one.
30
30
  ${profileLine}
31
31
  Best for: ${role.best_for}
32
32
  Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: adversarial-skeptic
3
+ description: Independently challenges a conclusion, diagnosis, or diff before it ships.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: Money, auth, migration, and frozen-build-shaped review seats.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: backend-fixer
3
+ description: Implements bounded backend changes: a bug fix behind a validated hypothesis, or a feature/config change behind a failing test.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Backend implementation shards — bug fixes with a validated hypothesis, and the build phase of a feature or config flow.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: code-reviewer
3
+ description: Performs the review pass at the task's review risk floor.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: The review seat on every review-gated task.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: code-simplifier
3
+ description: Simplifies and clarifies recently changed code without changing behavior.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Post-implementation cleanup passes.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: db-concurrency-specialist
3
+ description: Reviews transactional/locking correctness and concurrency-sensitive schema/code.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: Race conditions, stale claims, lock ordering, transactional boundaries.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: db-migration-author
3
+ description: Sole authority for authoring SQL schema migrations.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Any new migration file; never hand-write one outside this role.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: explore
3
+ description: Read-only breadth search across a codebase: where something is defined, what calls it, which files are involved.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: Broad read-only location work before a decision; never edits.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: frontend-fixer
3
+ description: Implements bounded frontend changes: a bug fix behind a validated hypothesis, or a feature change behind a failing test.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Frontend/UI implementation shards — bug fixes with a validated hypothesis, and the build phase of a feature flow.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: frontend-specialist
3
+ description: Implements frontend changes that touch shared state, realtime or a native bridge, respecting shipped-client compatibility.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Complex frontend state, realtime surfaces and native-bridge implementation work.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: general
3
+ description: General-purpose bounded worker for tasks that fit no specialist role.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Bounded work with no specialist owner; escalate rather than widen scope.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: orchestrator
3
+ description: Plans work, resolves capabilities, dispatches bounded shards, integrates results.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: ORCHESTRATOR — Plans, dispatches, and integrates; owns the overall session state.
9
+ Best for: Any nontrivial task needing more than one shard or a risk-floor review seat.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: production-telemetry-collector
3
+ description: Collects production logs/metrics/traces before an incident hypothesis is formed.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: Incident evidence gathering; never forms a fix on its own.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: provider-webhook-specialist
3
+ description: Implements and reviews payment/webhook provider integrations (Stripe, Apple, Google).
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Webhook signature/idempotency, provider state reconciliation, refund delivery.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: route-data-flow-tracer
3
+ description: Traces a request/data path across layers (frontend, API, DB, provider) read-only.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RO — Read-only. Investigation, evidence collection, review, telemetry.
9
+ Best for: Symptoms that cross architectural layers.
10
+ Never bypass a mandatory capability without declaring the gap first.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: test-engineer
3
+ description: Writes and maintains behavioral/regression tests.
4
+ ---
5
+ <!-- llm-orchestrator · created by Bogdan-Gabriel Torcescu · https://www.linkedin.com/in/bogdantorcescu/ · keep this credit when copying or deriving -->
6
+
7
+ Mandatory — before acting, load and follow the `orchestrate-core` skill (`.agents/skills/orchestrate/SKILL.md` in a project install). You work inside the parent's run: never open or close one.
8
+ Permission profile: RW — Read-write within an assigned bounded shard.
9
+ Best for: Coverage gaps, regression tests for bug fixes, refactor safety nets.
10
+ Never bypass a mandatory capability without declaring the gap first.
package/lib/flow-gate.mjs CHANGED
@@ -15,7 +15,9 @@
15
15
  */
16
16
  import { createHash, randomUUID } from 'node:crypto';
17
17
  import { appendFile, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
18
- import { join } from 'node:path';
18
+ import { readFileSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
19
21
 
20
22
  /**
21
23
  * The one sentence the model gets. `cli` is how to invoke this package from the
@@ -32,8 +34,12 @@ export const NUDGE = nudgeFor();
32
34
  * The second, and last, sentence the model can get per run: the plan has shards,
33
35
  * none went to a subagent, and the main thread keeps doing the work itself.
34
36
  */
35
- export function dispatchNudgeFor(cli = 'llm-orchestrator', planned = 2) {
36
- return `This run planned ${planned} shards and none has been dispatched to a subagent; the main thread is doing the work itself. Dispatch the independent shards (Claude Code: the Agent tool — search for it if it is deferred; Codex: spawn_agent; OpenCode/Kilo: task), or declare why this must stay inline (\`${cli} run start --type <TYPE> --inline "stateful:<what state>"\`).`;
37
+ export function dispatchNudgeFor(cli = 'llm-orchestrator', planned = 2, taskType = null) {
38
+ const roles = (FLOW_ROLES[taskType] ?? []).slice(0, 4);
39
+ const to = roles.length > 0
40
+ ? ` to this flow's roles (${roles.join(', ')}), not a general-purpose agent`
41
+ : ' to the orchestrator\'s roles, not a general-purpose agent';
42
+ return `This run planned ${planned} shards and none has been dispatched to a subagent; the main thread is doing the work itself. Dispatch the independent shards${to} (Claude Code: the Agent tool — search for it if it is deferred; Codex: spawn_agent; OpenCode/Kilo: task), or declare why this must stay inline (\`${cli} run start --type <TYPE> --inline "stateful:<what state>"\`).`;
37
43
  }
38
44
 
39
45
  /** A run declared trivial has outgrown the declaration. */
@@ -69,6 +75,26 @@ export const TASK_TYPES = ['INCIDENT', 'FEATURE', 'BUG_FIX', 'REFACTOR', 'INVEST
69
75
 
70
76
  export const LEDGER_DIRECTORY = '.orchestrator-run';
71
77
 
78
+ // The orchestrator's own roles, and the roles each task flow dispatches to, read from
79
+ // the registries this runtime ships with. A missing registry only weakens the audit.
80
+ const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
81
+ function readRegistry(path) {
82
+ try { return JSON.parse(readFileSync(join(PACKAGE_ROOT, path), 'utf8')); } catch { return null; }
83
+ }
84
+ const ROLE_IDS = new Set([
85
+ ...(readRegistry('registries/agent-roles.json')?.roles ?? []).map((role) => role.id),
86
+ ...(readRegistry('registries/preferred-tools.json')?.tools ?? []).filter((tool) => tool.kind === 'agent_role').map((tool) => tool.id),
87
+ ]);
88
+ const FLOW_ROLES = Object.fromEntries(Object.entries(readRegistry('registries/routing-matrix.json')?.task_flows ?? {})
89
+ .map(([type, flow]) => [type, [...new Set(flow.phases.flatMap((phase) => phase.roles ?? []))].filter((role) => role !== 'orchestrator')]));
90
+
91
+ /** 'role' for an orchestrator role (plugin-namespaced ids too), 'generic' otherwise, null when unknown. */
92
+ export function roleKind(agentType) {
93
+ if (typeof agentType !== 'string' || !agentType) return null;
94
+ // `plugin:role` (Claude Code plugin agents) and `plugin-role` (preferred-tools ids) both resolve.
95
+ return ROLE_IDS.has(agentType.split(':').pop()) || ROLE_IDS.has(agentType.replaceAll(':', '-')) ? 'role' : 'generic';
96
+ }
97
+
72
98
  const MAX_REASON = 200;
73
99
 
74
100
  // Reading the orchestration instructions is the intended first step, so it must
@@ -189,7 +215,7 @@ export function normalizePayload(raw) {
189
215
  // plugin and a CLI install; the harness's own event id lets the second be ignored.
190
216
  const id = (value) => (typeof value === 'string' && value ? value : null);
191
217
  if (eventName === 'UserPromptSubmit') return { kind: 'prompt', session, isSubagent, key: keyOf(eventName, id(payload.prompt_id) ?? id(payload.turn_id)) };
192
- if (eventName === 'SubagentStart') return { kind: 'subagent', session, isSubagent, key: keyOf(eventName, id(payload.agent_id)), agent: id(payload.agent_id) };
218
+ if (eventName === 'SubagentStart') return { kind: 'subagent', session, isSubagent, key: keyOf(eventName, id(payload.agent_id)), agent: id(payload.agent_id), role: roleKind(payload.agent_type) };
193
219
  if (eventName === 'Stop') return { kind: 'stop', session, isSubagent };
194
220
  if (eventName === 'SessionEnd') return { kind: 'session_end', session, isSubagent };
195
221
  if (eventName !== 'PreToolUse') return { kind: 'other', session, isSubagent };
@@ -241,6 +267,8 @@ function historyLine(session, fields, now) {
241
267
  inline_reason: fields.inline_reason ?? null,
242
268
  dispatch_nudged: Boolean(fields.dispatch_nudged),
243
269
  overreach: Boolean(fields.overreach_nudged),
270
+ role_dispatches: fields.role_dispatches ?? 0,
271
+ generic_dispatches: fields.generic_dispatches ?? 0,
244
272
  closed_by: fields.closed_by,
245
273
  };
246
274
  }
@@ -312,6 +340,8 @@ export function decide(previous, event, now) {
312
340
  if (!agentHash || !ids.includes(agentHash)) {
313
341
  session.run.subagents_started += 1;
314
342
  if (agentHash) session.run.subagent_ids = [...ids, agentHash];
343
+ if (event.role === 'role') session.run.role_dispatches = (session.run.role_dispatches ?? 0) + 1;
344
+ if (event.role === 'generic') session.run.generic_dispatches = (session.run.generic_dispatches ?? 0) + 1;
315
345
  }
316
346
  } else session.subagents_without_run += 1;
317
347
  return { session, output, history };
@@ -337,6 +367,8 @@ export function decide(previous, event, now) {
337
367
  touched_tests: false,
338
368
  overreach_nudged: false,
339
369
  subagent_ids: [],
370
+ role_dispatches: 0,
371
+ generic_dispatches: 0,
340
372
  };
341
373
  // The work already done is accounted for on the run itself now.
342
374
  session.worked_without_run = false;
@@ -367,7 +399,7 @@ export function decide(previous, event, now) {
367
399
  if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
368
400
  && !current.dispatch_nudged && current.main_work_calls >= DISPATCH_CALLS_PER_SHARD * current.planned_shards) {
369
401
  current.dispatch_nudged = true;
370
- output = { additionalContext: dispatchNudgeFor(undefined, current.planned_shards), kind: 'dispatch', planned: current.planned_shards };
402
+ output = { additionalContext: dispatchNudgeFor(undefined, current.planned_shards, current.task_type), kind: 'dispatch', planned: current.planned_shards, taskType: current.task_type };
371
403
  }
372
404
  return { session, output, history };
373
405
  }
@@ -505,7 +537,7 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
505
537
  });
506
538
  if (!result.output) return null;
507
539
  // decide() speaks in the default CLI spelling; the hook swaps in the runnable path.
508
- const text = result.output.kind === 'dispatch' ? dispatchNudgeFor(cli, result.output.planned)
540
+ const text = result.output.kind === 'dispatch' ? dispatchNudgeFor(cli, result.output.planned, result.output.taskType)
509
541
  : result.output.kind === 'overreach' ? overreachNudgeFor(cli, result.output.files)
510
542
  : nudgeFor(cli);
511
543
  return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
@@ -553,6 +585,9 @@ export function adherenceSummary(lines) {
553
585
  runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
554
586
  inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
555
587
  trivial_overreach: lines.filter((line) => line.trivial && line.overreach).length,
588
+ role_dispatches: lines.reduce((sum, line) => sum + (line.role_dispatches ?? 0), 0),
589
+ generic_dispatches: lines.reduce((sum, line) => sum + (line.generic_dispatches ?? 0), 0),
590
+ runs_without_roles: lines.filter((line) => (line.subagents_started ?? 0) > 0 && line.role_dispatches === 0 && (line.generic_dispatches ?? 0) > 0).length,
556
591
  below_fan_out: lines.filter((line) => EVIDENCE_TYPES.has(line.task_type) && !line.inline_reason
557
592
  && (line.planned_shards ?? 0) < 2 && (line.subagents_started ?? 0) < 2).length,
558
593
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-orchestrator",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Write /task once — it plans the work, shards it across parallel subagents, gates every phase and verifies before claiming done. Claude Code, Codex, OpenCode, Kilo.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -48,6 +48,7 @@
48
48
  "NOTICE",
49
49
  "skills",
50
50
  "hooks",
51
+ "agents",
51
52
  ".claude-plugin"
52
53
  ],
53
54
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  "run": {
9
9
  "type": "object",
10
10
  "additionalProperties": false,
11
- "required": ["task_id", "task_type", "trivial", "reason", "opened_at", "planned_shards", "subagents_started", "started_outside_flow", "inline_reason", "main_work_calls", "dispatch_nudged", "edited_files", "touched_tests", "overreach_nudged", "subagent_ids"],
11
+ "required": ["task_id", "task_type", "trivial", "reason", "opened_at", "planned_shards", "subagents_started", "started_outside_flow", "inline_reason", "main_work_calls", "dispatch_nudged", "edited_files", "touched_tests", "overreach_nudged", "subagent_ids", "role_dispatches", "generic_dispatches"],
12
12
  "properties": {
13
13
  "task_id": { "type": "string", "minLength": 1 },
14
14
  "task_type": { "type": ["string", "null"], "enum": ["INCIDENT", "FEATURE", "BUG_FIX", "REFACTOR", "INVESTIGATION", "DEPLOY", "CONFIG", "REVIEW", "RESEARCH", null] },
@@ -24,7 +24,9 @@
24
24
  "edited_files": { "type": "array", "items": { "type": "string", "pattern": "^[0-9a-f]{12}$" }, "description": "Short hashes of edited paths — never the paths themselves." },
25
25
  "touched_tests": { "type": "boolean" },
26
26
  "overreach_nudged": { "type": "boolean" },
27
- "subagent_ids": { "type": "array", "items": { "type": "string", "pattern": "^[0-9a-f]{12}$" }, "description": "Short hashes of the distinct subagents started under this run." }
27
+ "subagent_ids": { "type": "array", "items": { "type": "string", "pattern": "^[0-9a-f]{12}$" }, "description": "Short hashes of the distinct subagents started under this run." },
28
+ "role_dispatches": { "type": "integer", "minimum": 0, "description": "Subagents started as one of the orchestrator's roles." },
29
+ "generic_dispatches": { "type": "integer", "minimum": 0, "description": "Subagents started as a generic agent type (general-purpose, Explore, …)." }
28
30
  }
29
31
  },
30
32
  "session": {
@@ -46,7 +48,7 @@
46
48
  "historyLine": {
47
49
  "type": "object",
48
50
  "additionalProperties": false,
49
- "required": ["session", "task_id", "task_type", "trivial", "reason", "opened_at", "closed_at", "duration_s", "planned_shards", "subagents_started", "started_outside_flow", "skipped_flow", "inline_reason", "dispatch_nudged", "overreach", "closed_by"],
51
+ "required": ["session", "task_id", "task_type", "trivial", "reason", "opened_at", "closed_at", "duration_s", "planned_shards", "subagents_started", "started_outside_flow", "skipped_flow", "inline_reason", "dispatch_nudged", "overreach", "role_dispatches", "generic_dispatches", "closed_by"],
50
52
  "properties": {
51
53
  "session": { "type": "string", "minLength": 1 },
52
54
  "task_id": { "type": ["string", "null"] },
@@ -63,6 +65,8 @@
63
65
  "inline_reason": { "type": ["string", "null"] },
64
66
  "dispatch_nudged": { "type": "boolean" },
65
67
  "overreach": { "type": "boolean", "description": "A trivial run that edited two or more files or its tests." },
68
+ "role_dispatches": { "type": "integer", "minimum": 0 },
69
+ "generic_dispatches": { "type": "integer", "minimum": 0 },
66
70
  "closed_by": { "enum": ["run_close", "next_prompt", "succession", "turn_end", "session_end"] }
67
71
  }
68
72
  }