llm-orchestrator 1.2.5 → 1.2.6
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-plugin/plugin.json +1 -1
- package/README.md +4 -3
- package/lib/flow-gate.mjs +32 -4
- package/package.json +1 -1
- package/schemas/flow-ledger.schema.json +6 -3
|
@@ -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
|
+
"version": "1.2.6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bogdan-Gabriel Torcescu",
|
|
7
7
|
"url": "https://www.linkedin.com/in/bogdantorcescu/"
|
package/README.md
CHANGED
|
@@ -375,13 +375,14 @@ more instructions:
|
|
|
375
375
|
reports `role_dispatches`, `generic_dispatches` and `runs_without_roles`. The Claude Code plugin
|
|
376
376
|
ships the roles as agents (`llm-orchestrator:<role>`), so they are available without a project
|
|
377
377
|
install.
|
|
378
|
-
- A run opened with two or more shards whose main thread keeps doing the work — two work calls per planned shard, no
|
|
378
|
+
- A run opened with two or more shards whose main thread keeps doing the work — two work calls per planned shard (one per shard in incident, investigation and research flows, whose reads *are* the shards, with one firmer follow-up if the first reminder is ignored), no
|
|
379
379
|
subagent started — gets one more sentence: dispatch the independent shards (searching for the
|
|
380
380
|
Agent tool if it is deferred), or declare the chain inline with
|
|
381
381
|
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Work is inline only while it holds
|
|
382
382
|
live state a subagent cannot inherit (a browser mid-flow, an interactive shell); independent reads
|
|
383
|
-
are never inline. The audit adds `inline_declared
|
|
384
|
-
|
|
383
|
+
are never inline. The audit adds `inline_declared`, `inline_after_nudge` (an `--inline` declared only
|
|
384
|
+
after a dispatch reminder — a retroactive justification) and `below_fan_out` (incident, investigation
|
|
385
|
+
or research runs opened with fewer than two shards).
|
|
385
386
|
- Once the entrypoint is loaded, read-only discovery (reading files, `grep`, `git status`, tool
|
|
386
387
|
version checks) before `run start` is SKILL.md steps 2–3, not a deviation. An edit, a write, a
|
|
387
388
|
dispatch or any other shell command before the run is.
|
package/lib/flow-gate.mjs
CHANGED
|
@@ -35,7 +35,7 @@ export const NUDGE = nudgeFor();
|
|
|
35
35
|
* none went to a subagent, and the main thread keeps doing the work itself.
|
|
36
36
|
*/
|
|
37
37
|
export function dispatchNudgeFor(cli = 'llm-orchestrator', planned = 2, taskType = null) {
|
|
38
|
-
const roles = (FLOW_ROLES[taskType] ?? []).slice(0, 4);
|
|
38
|
+
const roles = (EVIDENCE_TYPES.has(taskType) ? EVIDENCE_ROLES[taskType] : FLOW_ROLES[taskType] ?? []).slice(0, 4);
|
|
39
39
|
const to = roles.length > 0
|
|
40
40
|
? ` to this flow's roles (${roles.join(', ')}), not a general-purpose agent`
|
|
41
41
|
: ' to the orchestrator\'s roles, not a general-purpose agent';
|
|
@@ -67,6 +67,18 @@ function pathHash(path) {
|
|
|
67
67
|
// Main-thread work calls tolerated per planned shard before the dispatch nudge: a
|
|
68
68
|
// two-shard task that is finished inline in three calls never reached a fixed six.
|
|
69
69
|
export const DISPATCH_CALLS_PER_SHARD = 2;
|
|
70
|
+
// Evidence flows: the reads are the shards, so waiting two calls per shard lets the
|
|
71
|
+
// main thread read everything before the reminder arrives. One call per shard there,
|
|
72
|
+
// and one firmer follow-up if the first reminder is ignored.
|
|
73
|
+
export const EVIDENCE_CALLS_PER_SHARD = 1;
|
|
74
|
+
const MAX_DISPATCH_NUDGES = 2;
|
|
75
|
+
|
|
76
|
+
/** The follow-up for an evidence flow whose first dispatch reminder was ignored. */
|
|
77
|
+
export function dispatchFollowupFor(cli = 'llm-orchestrator', planned = 2, taskType = null) {
|
|
78
|
+
const roles = (EVIDENCE_ROLES[taskType] ?? []).slice(0, 3);
|
|
79
|
+
const who = roles.length > 0 ? ` (${roles.join(', ')})` : '';
|
|
80
|
+
return `The dispatch reminder was not acted on, and the main thread is still gathering evidence itself. In an evidence flow those reads are the ${planned} planned shards — independent, W-tier, meant for subagents${who}. Dispatch the remaining sources now, or declare why they must stay inline (\`${cli} run start --type <TYPE> --inline "stateful:<what state>"\`).`;
|
|
81
|
+
}
|
|
70
82
|
|
|
71
83
|
// Evidence-gathering flows: their first phase fans out across independent sources.
|
|
72
84
|
const EVIDENCE_TYPES = new Set(['INCIDENT', 'INVESTIGATION', 'RESEARCH']);
|
|
@@ -85,8 +97,12 @@ const ROLE_IDS = new Set([
|
|
|
85
97
|
...(readRegistry('registries/agent-roles.json')?.roles ?? []).map((role) => role.id),
|
|
86
98
|
...(readRegistry('registries/preferred-tools.json')?.tools ?? []).filter((tool) => tool.kind === 'agent_role').map((tool) => tool.id),
|
|
87
99
|
]);
|
|
88
|
-
const
|
|
100
|
+
const TASK_FLOWS = readRegistry('registries/routing-matrix.json')?.task_flows ?? {};
|
|
101
|
+
const FLOW_ROLES = Object.fromEntries(Object.entries(TASK_FLOWS)
|
|
89
102
|
.map(([type, flow]) => [type, [...new Set(flow.phases.flatMap((phase) => phase.roles ?? []))].filter((role) => role !== 'orchestrator')]));
|
|
103
|
+
// The evidence phase of each flow: who gathers, as opposed to who fixes later.
|
|
104
|
+
const EVIDENCE_ROLES = Object.fromEntries(Object.entries(TASK_FLOWS)
|
|
105
|
+
.map(([type, flow]) => [type, (flow.phases[0]?.roles ?? []).filter((role) => role !== 'orchestrator')]));
|
|
90
106
|
|
|
91
107
|
/** 'role' for an orchestrator role (plugin-namespaced ids too), 'generic' otherwise, null when unknown. */
|
|
92
108
|
export function roleKind(agentType) {
|
|
@@ -267,6 +283,7 @@ function historyLine(session, fields, now) {
|
|
|
267
283
|
inline_reason: fields.inline_reason ?? null,
|
|
268
284
|
dispatch_nudged: Boolean(fields.dispatch_nudged),
|
|
269
285
|
overreach: Boolean(fields.overreach_nudged),
|
|
286
|
+
inline_after_nudge: Boolean(fields.inline_after_nudge),
|
|
270
287
|
role_dispatches: fields.role_dispatches ?? 0,
|
|
271
288
|
generic_dispatches: fields.generic_dispatches ?? 0,
|
|
272
289
|
closed_by: fields.closed_by,
|
|
@@ -369,6 +386,8 @@ export function decide(previous, event, now) {
|
|
|
369
386
|
subagent_ids: [],
|
|
370
387
|
role_dispatches: 0,
|
|
371
388
|
generic_dispatches: 0,
|
|
389
|
+
dispatch_nudges: 0,
|
|
390
|
+
inline_after_nudge: Boolean(event.run.inline && session.run?.dispatch_nudged),
|
|
372
391
|
};
|
|
373
392
|
// The work already done is accounted for on the run itself now.
|
|
374
393
|
session.worked_without_run = false;
|
|
@@ -396,10 +415,17 @@ export function decide(previous, event, now) {
|
|
|
396
415
|
}
|
|
397
416
|
return { session, output, history };
|
|
398
417
|
}
|
|
418
|
+
const evidence = EVIDENCE_TYPES.has(current.task_type);
|
|
419
|
+
const step = (evidence ? EVIDENCE_CALLS_PER_SHARD : DISPATCH_CALLS_PER_SHARD) * (current.planned_shards ?? 0);
|
|
420
|
+
const nudges = current.dispatch_nudges ?? (current.dispatch_nudged ? 1 : 0);
|
|
421
|
+
const allowed = evidence ? MAX_DISPATCH_NUDGES : 1;
|
|
399
422
|
if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
|
|
400
|
-
&&
|
|
423
|
+
&& nudges < allowed && current.main_work_calls >= step * (nudges + 1)) {
|
|
401
424
|
current.dispatch_nudged = true;
|
|
402
|
-
|
|
425
|
+
current.dispatch_nudges = nudges + 1;
|
|
426
|
+
output = nudges === 0
|
|
427
|
+
? { additionalContext: dispatchNudgeFor(undefined, current.planned_shards, current.task_type), kind: 'dispatch', planned: current.planned_shards, taskType: current.task_type }
|
|
428
|
+
: { additionalContext: dispatchFollowupFor(undefined, current.planned_shards, current.task_type), kind: 'dispatch_followup', planned: current.planned_shards, taskType: current.task_type };
|
|
403
429
|
}
|
|
404
430
|
return { session, output, history };
|
|
405
431
|
}
|
|
@@ -538,6 +564,7 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
|
|
|
538
564
|
if (!result.output) return null;
|
|
539
565
|
// decide() speaks in the default CLI spelling; the hook swaps in the runnable path.
|
|
540
566
|
const text = result.output.kind === 'dispatch' ? dispatchNudgeFor(cli, result.output.planned, result.output.taskType)
|
|
567
|
+
: result.output.kind === 'dispatch_followup' ? dispatchFollowupFor(cli, result.output.planned, result.output.taskType)
|
|
541
568
|
: result.output.kind === 'overreach' ? overreachNudgeFor(cli, result.output.files)
|
|
542
569
|
: nudgeFor(cli);
|
|
543
570
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
|
|
@@ -584,6 +611,7 @@ export function adherenceSummary(lines) {
|
|
|
584
611
|
planned_but_not_dispatched: lines.filter((line) => (line.planned_shards ?? 0) > 1 && line.subagents_started === 0).length,
|
|
585
612
|
runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
|
|
586
613
|
inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
|
|
614
|
+
inline_after_nudge: lines.filter((line) => line.inline_after_nudge).length,
|
|
587
615
|
trivial_overreach: lines.filter((line) => line.trivial && line.overreach).length,
|
|
588
616
|
role_dispatches: lines.reduce((sum, line) => sum + (line.role_dispatches ?? 0), 0),
|
|
589
617
|
generic_dispatches: lines.reduce((sum, line) => sum + (line.generic_dispatches ?? 0), 0),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
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": {
|
|
@@ -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", "role_dispatches", "generic_dispatches"],
|
|
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", "dispatch_nudges", "inline_after_nudge"],
|
|
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] },
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"overreach_nudged": { "type": "boolean" },
|
|
27
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
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, …)." }
|
|
29
|
+
"generic_dispatches": { "type": "integer", "minimum": 0, "description": "Subagents started as a generic agent type (general-purpose, Explore, …)." },
|
|
30
|
+
"dispatch_nudges": { "type": "integer", "minimum": 0, "maximum": 2, "description": "Dispatch reminders sent: one, or two for evidence flows whose first was ignored." },
|
|
31
|
+
"inline_after_nudge": { "type": "boolean", "description": "This run replaced one that had already received a dispatch reminder, and declared --inline: a retroactive justification." }
|
|
30
32
|
}
|
|
31
33
|
},
|
|
32
34
|
"session": {
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
"historyLine": {
|
|
49
51
|
"type": "object",
|
|
50
52
|
"additionalProperties": false,
|
|
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"],
|
|
53
|
+
"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", "inline_after_nudge", "role_dispatches", "generic_dispatches", "closed_by"],
|
|
52
54
|
"properties": {
|
|
53
55
|
"session": { "type": "string", "minLength": 1 },
|
|
54
56
|
"task_id": { "type": ["string", "null"] },
|
|
@@ -65,6 +67,7 @@
|
|
|
65
67
|
"inline_reason": { "type": ["string", "null"] },
|
|
66
68
|
"dispatch_nudged": { "type": "boolean" },
|
|
67
69
|
"overreach": { "type": "boolean", "description": "A trivial run that edited two or more files or its tests." },
|
|
70
|
+
"inline_after_nudge": { "type": "boolean" },
|
|
68
71
|
"role_dispatches": { "type": "integer", "minimum": 0 },
|
|
69
72
|
"generic_dispatches": { "type": "integer", "minimum": 0 },
|
|
70
73
|
"closed_by": { "enum": ["run_close", "next_prompt", "succession", "turn_end", "session_end"] }
|