llm-orchestrator 1.2.3 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -2
- package/adapters/agents.mjs +1 -1
- package/adapters/hooks.mjs +3 -1
- package/agents/adversarial-skeptic.md +10 -0
- package/agents/backend-fixer.md +10 -0
- package/agents/code-reviewer.md +10 -0
- package/agents/code-simplifier.md +10 -0
- package/agents/db-concurrency-specialist.md +10 -0
- package/agents/db-migration-author.md +10 -0
- package/agents/explore.md +10 -0
- package/agents/frontend-fixer.md +10 -0
- package/agents/frontend-specialist.md +10 -0
- package/agents/general.md +10 -0
- package/agents/orchestrator.md +10 -0
- package/agents/production-telemetry-collector.md +10 -0
- package/agents/provider-webhook-specialist.md +10 -0
- package/agents/route-data-flow-tracer.md +10 -0
- package/agents/test-engineer.md +10 -0
- package/hooks/hooks.json +11 -0
- package/lib/flow-gate.mjs +68 -12
- package/package.json +2 -1
- package/schemas/flow-ledger.schema.json +9 -4
|
@@ -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.5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bogdan-Gabriel Torcescu",
|
|
7
7
|
"url": "https://www.linkedin.com/in/bogdantorcescu/"
|
package/README.md
CHANGED
|
@@ -367,8 +367,15 @@ more instructions:
|
|
|
367
367
|
- Trivial is the narrow exception — a one-line change such as a typo or a version bump. A run
|
|
368
368
|
declared trivial that then edits a second file or touches tests gets one reminder to reopen it as
|
|
369
369
|
a typed run, and counts as `trivial_overreach` in the audit. Trivial runs close at the end of the
|
|
370
|
-
turn (`Stop` hook), so single-prompt sessions still reach the history.
|
|
371
|
-
|
|
370
|
+
turn (`Stop` hook), so single-prompt sessions still reach the history. Typed runs stay open across
|
|
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. 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.
|
|
378
|
+
- A run opened with two or more shards whose main thread keeps doing the work — two work calls per planned shard, no
|
|
372
379
|
subagent started — gets one more sentence: dispatch the independent shards (searching for the
|
|
373
380
|
Agent tool if it is deferred), or declare the chain inline with
|
|
374
381
|
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Work is inline only while it holds
|
package/adapters/agents.mjs
CHANGED
|
@@ -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.
|
package/adapters/hooks.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { relative, isAbsolute } from 'node:path';
|
|
|
11
11
|
/** Marks the hook entries this package owns inside a user's hooks JSON. */
|
|
12
12
|
export const FLOW_MARKER = 'orchestrate-core:flow';
|
|
13
13
|
|
|
14
|
-
export const FLOW_EVENTS = ['UserPromptSubmit', 'PreToolUse', 'SubagentStart', 'Stop'];
|
|
14
|
+
export const FLOW_EVENTS = ['UserPromptSubmit', 'PreToolUse', 'SubagentStart', 'Stop', 'SessionEnd'];
|
|
15
15
|
|
|
16
16
|
/** Spell the runtime path through $HOME when it lives there, so committed settings stay portable. */
|
|
17
17
|
export function runtimeCliPath(runtimeRoot) {
|
|
@@ -166,6 +166,8 @@ export const OrchestrateFlow = async ({ directory }) => {
|
|
|
166
166
|
}
|
|
167
167
|
const idle = event?.properties?.sessionID;
|
|
168
168
|
if (event?.type === "session.idle" && idle && !parentOf.has(idle)) gate({ hook_event_name: "Stop", session_id: idle });
|
|
169
|
+
const ended = info?.id;
|
|
170
|
+
if (event?.type === "session.deleted" && ended && !parentOf.has(ended)) gate({ hook_event_name: "SessionEnd", session_id: ended });
|
|
169
171
|
} catch {}
|
|
170
172
|
},
|
|
171
173
|
"chat.message": async (input) => {
|
|
@@ -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/hooks/hooks.json
CHANGED
|
@@ -44,6 +44,17 @@
|
|
|
44
44
|
}
|
|
45
45
|
]
|
|
46
46
|
}
|
|
47
|
+
],
|
|
48
|
+
"SessionEnd": [
|
|
49
|
+
{
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/bin/llm-orchestrator.mjs\" gate 2>/dev/null || true # orchestrate-core:flow",
|
|
54
|
+
"timeout": 3
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
47
58
|
]
|
|
48
59
|
}
|
|
49
60
|
}
|
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 {
|
|
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
|
-
|
|
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. */
|
|
@@ -58,8 +64,9 @@ function pathHash(path) {
|
|
|
58
64
|
return createHash('sha256').update(path).digest('hex').slice(0, 12);
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
// Main-thread work calls tolerated
|
|
62
|
-
|
|
67
|
+
// Main-thread work calls tolerated per planned shard before the dispatch nudge: a
|
|
68
|
+
// two-shard task that is finished inline in three calls never reached a fixed six.
|
|
69
|
+
export const DISPATCH_CALLS_PER_SHARD = 2;
|
|
63
70
|
|
|
64
71
|
// Evidence-gathering flows: their first phase fans out across independent sources.
|
|
65
72
|
const EVIDENCE_TYPES = new Set(['INCIDENT', 'INVESTIGATION', 'RESEARCH']);
|
|
@@ -68,6 +75,26 @@ export const TASK_TYPES = ['INCIDENT', 'FEATURE', 'BUG_FIX', 'REFACTOR', 'INVEST
|
|
|
68
75
|
|
|
69
76
|
export const LEDGER_DIRECTORY = '.orchestrator-run';
|
|
70
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
|
+
|
|
71
98
|
const MAX_REASON = 200;
|
|
72
99
|
|
|
73
100
|
// Reading the orchestration instructions is the intended first step, so it must
|
|
@@ -188,8 +215,9 @@ export function normalizePayload(raw) {
|
|
|
188
215
|
// plugin and a CLI install; the harness's own event id lets the second be ignored.
|
|
189
216
|
const id = (value) => (typeof value === 'string' && value ? value : null);
|
|
190
217
|
if (eventName === 'UserPromptSubmit') return { kind: 'prompt', session, isSubagent, key: keyOf(eventName, id(payload.prompt_id) ?? id(payload.turn_id)) };
|
|
191
|
-
if (eventName === 'SubagentStart') return { kind: 'subagent', session, isSubagent, key: keyOf(eventName, 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) };
|
|
192
219
|
if (eventName === 'Stop') return { kind: 'stop', session, isSubagent };
|
|
220
|
+
if (eventName === 'SessionEnd') return { kind: 'session_end', session, isSubagent };
|
|
193
221
|
if (eventName !== 'PreToolUse') return { kind: 'other', session, isSubagent };
|
|
194
222
|
|
|
195
223
|
const toolName = String(payload.tool_name ?? '');
|
|
@@ -239,6 +267,8 @@ function historyLine(session, fields, now) {
|
|
|
239
267
|
inline_reason: fields.inline_reason ?? null,
|
|
240
268
|
dispatch_nudged: Boolean(fields.dispatch_nudged),
|
|
241
269
|
overreach: Boolean(fields.overreach_nudged),
|
|
270
|
+
role_dispatches: fields.role_dispatches ?? 0,
|
|
271
|
+
generic_dispatches: fields.generic_dispatches ?? 0,
|
|
242
272
|
closed_by: fields.closed_by,
|
|
243
273
|
};
|
|
244
274
|
}
|
|
@@ -283,6 +313,15 @@ export function decide(previous, event, now) {
|
|
|
283
313
|
return { session, output, history };
|
|
284
314
|
}
|
|
285
315
|
|
|
316
|
+
if (event.kind === 'session_end') {
|
|
317
|
+
// Typed runs outlive turns but not the session: close them so they reach history.
|
|
318
|
+
if (session.run) {
|
|
319
|
+
history.push(closeRun(session, now, 'session_end'));
|
|
320
|
+
session.run = null;
|
|
321
|
+
}
|
|
322
|
+
return { session, output, history };
|
|
323
|
+
}
|
|
324
|
+
|
|
286
325
|
if (event.kind === 'stop') {
|
|
287
326
|
// A trivial run lasts one turn; closing it here keeps single-prompt sessions in history.
|
|
288
327
|
if (session.run?.trivial && !event.isSubagent) {
|
|
@@ -293,8 +332,18 @@ export function decide(previous, event, now) {
|
|
|
293
332
|
}
|
|
294
333
|
|
|
295
334
|
if (event.kind === 'subagent') {
|
|
296
|
-
if (session.run)
|
|
297
|
-
|
|
335
|
+
if (session.run) {
|
|
336
|
+
// Count distinct agents: a resumed subagent fires SubagentStart again, possibly
|
|
337
|
+
// long after its first start has left the seen-window.
|
|
338
|
+
const ids = Array.isArray(session.run.subagent_ids) ? session.run.subagent_ids : [];
|
|
339
|
+
const agentHash = event.agent ? pathHash(event.agent) : null;
|
|
340
|
+
if (!agentHash || !ids.includes(agentHash)) {
|
|
341
|
+
session.run.subagents_started += 1;
|
|
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;
|
|
345
|
+
}
|
|
346
|
+
} else session.subagents_without_run += 1;
|
|
298
347
|
return { session, output, history };
|
|
299
348
|
}
|
|
300
349
|
|
|
@@ -317,6 +366,9 @@ export function decide(previous, event, now) {
|
|
|
317
366
|
edited_files: [],
|
|
318
367
|
touched_tests: false,
|
|
319
368
|
overreach_nudged: false,
|
|
369
|
+
subagent_ids: [],
|
|
370
|
+
role_dispatches: 0,
|
|
371
|
+
generic_dispatches: 0,
|
|
320
372
|
};
|
|
321
373
|
// The work already done is accounted for on the run itself now.
|
|
322
374
|
session.worked_without_run = false;
|
|
@@ -345,9 +397,9 @@ export function decide(previous, event, now) {
|
|
|
345
397
|
return { session, output, history };
|
|
346
398
|
}
|
|
347
399
|
if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
|
|
348
|
-
&& !current.dispatch_nudged && current.main_work_calls >=
|
|
400
|
+
&& !current.dispatch_nudged && current.main_work_calls >= DISPATCH_CALLS_PER_SHARD * current.planned_shards) {
|
|
349
401
|
current.dispatch_nudged = true;
|
|
350
|
-
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 };
|
|
351
403
|
}
|
|
352
404
|
return { session, output, history };
|
|
353
405
|
}
|
|
@@ -485,7 +537,7 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
|
|
|
485
537
|
});
|
|
486
538
|
if (!result.output) return null;
|
|
487
539
|
// decide() speaks in the default CLI spelling; the hook swaps in the runnable path.
|
|
488
|
-
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)
|
|
489
541
|
: result.output.kind === 'overreach' ? overreachNudgeFor(cli, result.output.files)
|
|
490
542
|
: nudgeFor(cli);
|
|
491
543
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
|
|
@@ -533,6 +585,10 @@ export function adherenceSummary(lines) {
|
|
|
533
585
|
runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
|
|
534
586
|
inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
|
|
535
587
|
trivial_overreach: lines.filter((line) => line.trivial && line.overreach).length,
|
|
536
|
-
|
|
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,
|
|
591
|
+
below_fan_out: lines.filter((line) => EVIDENCE_TYPES.has(line.task_type) && !line.inline_reason
|
|
592
|
+
&& (line.planned_shards ?? 0) < 2 && (line.subagents_started ?? 0) < 2).length,
|
|
537
593
|
};
|
|
538
594
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
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"],
|
|
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] },
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"dispatch_nudged": { "type": "boolean" },
|
|
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
|
-
"overreach_nudged": { "type": "boolean" }
|
|
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." },
|
|
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, …)." }
|
|
27
30
|
}
|
|
28
31
|
},
|
|
29
32
|
"session": {
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"historyLine": {
|
|
46
49
|
"type": "object",
|
|
47
50
|
"additionalProperties": false,
|
|
48
|
-
"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"],
|
|
49
52
|
"properties": {
|
|
50
53
|
"session": { "type": "string", "minLength": 1 },
|
|
51
54
|
"task_id": { "type": ["string", "null"] },
|
|
@@ -62,7 +65,9 @@
|
|
|
62
65
|
"inline_reason": { "type": ["string", "null"] },
|
|
63
66
|
"dispatch_nudged": { "type": "boolean" },
|
|
64
67
|
"overreach": { "type": "boolean", "description": "A trivial run that edited two or more files or its tests." },
|
|
65
|
-
"
|
|
68
|
+
"role_dispatches": { "type": "integer", "minimum": 0 },
|
|
69
|
+
"generic_dispatches": { "type": "integer", "minimum": 0 },
|
|
70
|
+
"closed_by": { "enum": ["run_close", "next_prompt", "succession", "turn_end", "session_end"] }
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|