llm-orchestrator 1.2.4 → 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 +10 -4
- package/adapters/agents.mjs +1 -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/lib/flow-gate.mjs +70 -7
- package/package.json +2 -1
- package/schemas/flow-ledger.schema.json +10 -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
|
@@ -369,14 +369,20 @@ 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.
|
|
373
|
-
|
|
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 (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
|
|
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
|
|
376
381
|
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Work is inline only while it holds
|
|
377
382
|
live state a subagent cannot inherit (a browser mid-flow, an interactive shell); independent reads
|
|
378
|
-
are never inline. The audit adds `inline_declared
|
|
379
|
-
|
|
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).
|
|
380
386
|
- Once the entrypoint is loaded, read-only discovery (reading files, `grep`, `git status`, tool
|
|
381
387
|
version checks) before `run start` is SKILL.md steps 2–3, not a deviation. An edit, a write, a
|
|
382
388
|
dispatch or any other shell command before the run is.
|
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.
|
|
@@ -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 {
|
|
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 = (EVIDENCE_TYPES.has(taskType) ? EVIDENCE_ROLES[taskType] : 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. */
|
|
@@ -61,6 +67,18 @@ function pathHash(path) {
|
|
|
61
67
|
// Main-thread work calls tolerated per planned shard before the dispatch nudge: a
|
|
62
68
|
// two-shard task that is finished inline in three calls never reached a fixed six.
|
|
63
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
|
+
}
|
|
64
82
|
|
|
65
83
|
// Evidence-gathering flows: their first phase fans out across independent sources.
|
|
66
84
|
const EVIDENCE_TYPES = new Set(['INCIDENT', 'INVESTIGATION', 'RESEARCH']);
|
|
@@ -69,6 +87,30 @@ export const TASK_TYPES = ['INCIDENT', 'FEATURE', 'BUG_FIX', 'REFACTOR', 'INVEST
|
|
|
69
87
|
|
|
70
88
|
export const LEDGER_DIRECTORY = '.orchestrator-run';
|
|
71
89
|
|
|
90
|
+
// The orchestrator's own roles, and the roles each task flow dispatches to, read from
|
|
91
|
+
// the registries this runtime ships with. A missing registry only weakens the audit.
|
|
92
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
93
|
+
function readRegistry(path) {
|
|
94
|
+
try { return JSON.parse(readFileSync(join(PACKAGE_ROOT, path), 'utf8')); } catch { return null; }
|
|
95
|
+
}
|
|
96
|
+
const ROLE_IDS = new Set([
|
|
97
|
+
...(readRegistry('registries/agent-roles.json')?.roles ?? []).map((role) => role.id),
|
|
98
|
+
...(readRegistry('registries/preferred-tools.json')?.tools ?? []).filter((tool) => tool.kind === 'agent_role').map((tool) => tool.id),
|
|
99
|
+
]);
|
|
100
|
+
const TASK_FLOWS = readRegistry('registries/routing-matrix.json')?.task_flows ?? {};
|
|
101
|
+
const FLOW_ROLES = Object.fromEntries(Object.entries(TASK_FLOWS)
|
|
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')]));
|
|
106
|
+
|
|
107
|
+
/** 'role' for an orchestrator role (plugin-namespaced ids too), 'generic' otherwise, null when unknown. */
|
|
108
|
+
export function roleKind(agentType) {
|
|
109
|
+
if (typeof agentType !== 'string' || !agentType) return null;
|
|
110
|
+
// `plugin:role` (Claude Code plugin agents) and `plugin-role` (preferred-tools ids) both resolve.
|
|
111
|
+
return ROLE_IDS.has(agentType.split(':').pop()) || ROLE_IDS.has(agentType.replaceAll(':', '-')) ? 'role' : 'generic';
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
const MAX_REASON = 200;
|
|
73
115
|
|
|
74
116
|
// Reading the orchestration instructions is the intended first step, so it must
|
|
@@ -189,7 +231,7 @@ export function normalizePayload(raw) {
|
|
|
189
231
|
// plugin and a CLI install; the harness's own event id lets the second be ignored.
|
|
190
232
|
const id = (value) => (typeof value === 'string' && value ? value : null);
|
|
191
233
|
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) };
|
|
234
|
+
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
235
|
if (eventName === 'Stop') return { kind: 'stop', session, isSubagent };
|
|
194
236
|
if (eventName === 'SessionEnd') return { kind: 'session_end', session, isSubagent };
|
|
195
237
|
if (eventName !== 'PreToolUse') return { kind: 'other', session, isSubagent };
|
|
@@ -241,6 +283,9 @@ function historyLine(session, fields, now) {
|
|
|
241
283
|
inline_reason: fields.inline_reason ?? null,
|
|
242
284
|
dispatch_nudged: Boolean(fields.dispatch_nudged),
|
|
243
285
|
overreach: Boolean(fields.overreach_nudged),
|
|
286
|
+
inline_after_nudge: Boolean(fields.inline_after_nudge),
|
|
287
|
+
role_dispatches: fields.role_dispatches ?? 0,
|
|
288
|
+
generic_dispatches: fields.generic_dispatches ?? 0,
|
|
244
289
|
closed_by: fields.closed_by,
|
|
245
290
|
};
|
|
246
291
|
}
|
|
@@ -312,6 +357,8 @@ export function decide(previous, event, now) {
|
|
|
312
357
|
if (!agentHash || !ids.includes(agentHash)) {
|
|
313
358
|
session.run.subagents_started += 1;
|
|
314
359
|
if (agentHash) session.run.subagent_ids = [...ids, agentHash];
|
|
360
|
+
if (event.role === 'role') session.run.role_dispatches = (session.run.role_dispatches ?? 0) + 1;
|
|
361
|
+
if (event.role === 'generic') session.run.generic_dispatches = (session.run.generic_dispatches ?? 0) + 1;
|
|
315
362
|
}
|
|
316
363
|
} else session.subagents_without_run += 1;
|
|
317
364
|
return { session, output, history };
|
|
@@ -337,6 +384,10 @@ export function decide(previous, event, now) {
|
|
|
337
384
|
touched_tests: false,
|
|
338
385
|
overreach_nudged: false,
|
|
339
386
|
subagent_ids: [],
|
|
387
|
+
role_dispatches: 0,
|
|
388
|
+
generic_dispatches: 0,
|
|
389
|
+
dispatch_nudges: 0,
|
|
390
|
+
inline_after_nudge: Boolean(event.run.inline && session.run?.dispatch_nudged),
|
|
340
391
|
};
|
|
341
392
|
// The work already done is accounted for on the run itself now.
|
|
342
393
|
session.worked_without_run = false;
|
|
@@ -364,10 +415,17 @@ export function decide(previous, event, now) {
|
|
|
364
415
|
}
|
|
365
416
|
return { session, output, history };
|
|
366
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;
|
|
367
422
|
if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
|
|
368
|
-
&&
|
|
423
|
+
&& nudges < allowed && current.main_work_calls >= step * (nudges + 1)) {
|
|
369
424
|
current.dispatch_nudged = true;
|
|
370
|
-
|
|
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 };
|
|
371
429
|
}
|
|
372
430
|
return { session, output, history };
|
|
373
431
|
}
|
|
@@ -505,7 +563,8 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
|
|
|
505
563
|
});
|
|
506
564
|
if (!result.output) return null;
|
|
507
565
|
// 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)
|
|
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)
|
|
509
568
|
: result.output.kind === 'overreach' ? overreachNudgeFor(cli, result.output.files)
|
|
510
569
|
: nudgeFor(cli);
|
|
511
570
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
|
|
@@ -552,7 +611,11 @@ export function adherenceSummary(lines) {
|
|
|
552
611
|
planned_but_not_dispatched: lines.filter((line) => (line.planned_shards ?? 0) > 1 && line.subagents_started === 0).length,
|
|
553
612
|
runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
|
|
554
613
|
inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
|
|
614
|
+
inline_after_nudge: lines.filter((line) => line.inline_after_nudge).length,
|
|
555
615
|
trivial_overreach: lines.filter((line) => line.trivial && line.overreach).length,
|
|
616
|
+
role_dispatches: lines.reduce((sum, line) => sum + (line.role_dispatches ?? 0), 0),
|
|
617
|
+
generic_dispatches: lines.reduce((sum, line) => sum + (line.generic_dispatches ?? 0), 0),
|
|
618
|
+
runs_without_roles: lines.filter((line) => (line.subagents_started ?? 0) > 0 && line.role_dispatches === 0 && (line.generic_dispatches ?? 0) > 0).length,
|
|
556
619
|
below_fan_out: lines.filter((line) => EVIDENCE_TYPES.has(line.task_type) && !line.inline_reason
|
|
557
620
|
&& (line.planned_shards ?? 0) < 2 && (line.subagents_started ?? 0) < 2).length,
|
|
558
621
|
};
|
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": {
|
|
@@ -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", "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] },
|
|
@@ -24,7 +24,11 @@
|
|
|
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, …)." },
|
|
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." }
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"session": {
|
|
@@ -46,7 +50,7 @@
|
|
|
46
50
|
"historyLine": {
|
|
47
51
|
"type": "object",
|
|
48
52
|
"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"],
|
|
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"],
|
|
50
54
|
"properties": {
|
|
51
55
|
"session": { "type": "string", "minLength": 1 },
|
|
52
56
|
"task_id": { "type": ["string", "null"] },
|
|
@@ -63,6 +67,9 @@
|
|
|
63
67
|
"inline_reason": { "type": ["string", "null"] },
|
|
64
68
|
"dispatch_nudged": { "type": "boolean" },
|
|
65
69
|
"overreach": { "type": "boolean", "description": "A trivial run that edited two or more files or its tests." },
|
|
70
|
+
"inline_after_nudge": { "type": "boolean" },
|
|
71
|
+
"role_dispatches": { "type": "integer", "minimum": 0 },
|
|
72
|
+
"generic_dispatches": { "type": "integer", "minimum": 0 },
|
|
66
73
|
"closed_by": { "enum": ["run_close", "next_prompt", "succession", "turn_end", "session_end"] }
|
|
67
74
|
}
|
|
68
75
|
}
|