llm-orchestrator 1.2.5 → 1.2.7
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 +6 -4
- package/bin/run.mjs +6 -2
- package/lib/flow-gate.mjs +65 -9
- package/package.json +1 -1
- package/policies/dispatch.md +7 -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.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bogdan-Gabriel Torcescu",
|
|
7
7
|
"url": "https://www.linkedin.com/in/bogdantorcescu/"
|
package/README.md
CHANGED
|
@@ -365,7 +365,8 @@ more instructions:
|
|
|
365
365
|
it, runs opened without a PlanShard count, runs that planned several shards but started no
|
|
366
366
|
subagents, and runs still open.
|
|
367
367
|
- Trivial is the narrow exception — a one-line change such as a typo or a version bump. A run
|
|
368
|
-
declared trivial that then edits a second file
|
|
368
|
+
declared trivial that then edits a second file, touches tests, or keeps working past eight calls
|
|
369
|
+
(an investigation is not trivial) gets one reminder to reopen it as
|
|
369
370
|
a typed run, and counts as `trivial_overreach` in the audit. Trivial runs close at the end of the
|
|
370
371
|
turn (`Stop` hook), so single-prompt sessions still reach the history. Typed runs stay open across
|
|
371
372
|
turns and close when the session ends (`SessionEnd`), so an unclosed run is never lost.
|
|
@@ -375,13 +376,14 @@ more instructions:
|
|
|
375
376
|
reports `role_dispatches`, `generic_dispatches` and `runs_without_roles`. The Claude Code plugin
|
|
376
377
|
ships the roles as agents (`llm-orchestrator:<role>`), so they are available without a project
|
|
377
378
|
install.
|
|
378
|
-
- A run opened with two or more shards whose main thread keeps doing the work — two work calls per planned shard, no
|
|
379
|
+
- 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 and the evidence is live — `ssh`, remote databases, cluster/cloud CLIs, HTTP; a few small local files are fine read inline), no
|
|
379
380
|
subagent started — gets one more sentence: dispatch the independent shards (searching for the
|
|
380
381
|
Agent tool if it is deferred), or declare the chain inline with
|
|
381
382
|
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Work is inline only while it holds
|
|
382
383
|
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
|
-
|
|
384
|
+
are never inline. The audit adds `inline_declared`, `inline_after_nudge` (an `--inline` declared only
|
|
385
|
+
after a dispatch reminder — a retroactive justification) and `below_fan_out` (incident, investigation
|
|
386
|
+
or research runs opened with fewer than two shards).
|
|
385
387
|
- Once the entrypoint is loaded, read-only discovery (reading files, `grep`, `git status`, tool
|
|
386
388
|
version checks) before `run start` is SKILL.md steps 2–3, not a deviation. An edit, a write, a
|
|
387
389
|
dispatch or any other shell command before the run is.
|
package/bin/run.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the project ledger; the command itself only validates and acknowledges, so it is
|
|
8
8
|
* safe to call with or without the hooks installed.
|
|
9
9
|
*/
|
|
10
|
-
import { parseRunArgs, TASK_TYPES } from '../lib/flow-gate.mjs';
|
|
10
|
+
import { INLINE_REASON, parseRunArgs, TASK_TYPES } from '../lib/flow-gate.mjs';
|
|
11
11
|
|
|
12
12
|
const USAGE = `Usage: llm-orchestrator run start --type <${TASK_TYPES.join('|')}> [--shards N] [--inline "stateful:<what>"]
|
|
13
13
|
llm-orchestrator run start --trivial "<reason>"
|
|
@@ -18,7 +18,11 @@ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
|
18
18
|
process.stdout.write(`${USAGE}\n`);
|
|
19
19
|
} else {
|
|
20
20
|
const parsed = parseRunArgs(args);
|
|
21
|
-
|
|
21
|
+
const inlineAt = args.indexOf('--inline');
|
|
22
|
+
if (!parsed && inlineAt !== -1 && !INLINE_REASON.test(args[inlineAt + 1] ?? '')) {
|
|
23
|
+
process.stderr.write('--inline must name the live state a subagent cannot inherit, as "stateful:<what state>" (a browser session mid-flow, an interactive shell). Independent reads are never inline — dispatch them.\n');
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
} else if (!parsed) {
|
|
22
26
|
process.stderr.write(`${USAGE}\n`);
|
|
23
27
|
process.exitCode = 1;
|
|
24
28
|
} else {
|
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';
|
|
@@ -47,6 +47,11 @@ export function overreachNudgeFor(cli = 'llm-orchestrator', files = 2) {
|
|
|
47
47
|
return `This task was declared trivial, but it now touches ${files} files or its tests. Reopen it as a typed run (\`${cli} run start --type <TYPE> --shards <n>\`) so it is classified, planned and verified like one.`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** A run declared trivial that has turned into an investigation. */
|
|
51
|
+
export function overreachWorkNudgeFor(cli = 'llm-orchestrator', calls = 9) {
|
|
52
|
+
return `This task was declared trivial, but it has now made ${calls} work calls — trivial is a one-line change, not an investigation. Reopen it as a typed run (\`${cli} run start --type <TYPE> --shards <n>\`) so it is classified, planned and verified like one.`;
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
// Edit-shaped tools, per harness. Only a short hash of each path is kept.
|
|
51
56
|
const EDIT_TOOLS = new Set(['edit', 'write', 'multiedit', 'notebookedit', 'str_replace_based_edit_tool', 'apply_patch', 'patch']);
|
|
52
57
|
const TEST_PATH = /(^|\/)(test|tests|__tests__|spec|specs)\/|[._-](test|spec)\.[a-z0-9]+$|(^|\/)test_[^/]+\.py$|_spec\.rb$/i;
|
|
@@ -67,6 +72,18 @@ function pathHash(path) {
|
|
|
67
72
|
// Main-thread work calls tolerated per planned shard before the dispatch nudge: a
|
|
68
73
|
// two-shard task that is finished inline in three calls never reached a fixed six.
|
|
69
74
|
export const DISPATCH_CALLS_PER_SHARD = 2;
|
|
75
|
+
// Evidence flows: the reads are the shards, so waiting two calls per shard lets the
|
|
76
|
+
// main thread read everything before the reminder arrives. One call per shard there,
|
|
77
|
+
// and one firmer follow-up if the first reminder is ignored.
|
|
78
|
+
export const EVIDENCE_CALLS_PER_SHARD = 1;
|
|
79
|
+
const MAX_DISPATCH_NUDGES = 2;
|
|
80
|
+
|
|
81
|
+
/** The follow-up for an evidence flow whose first dispatch reminder was ignored. */
|
|
82
|
+
export function dispatchFollowupFor(cli = 'llm-orchestrator', planned = 2, taskType = null) {
|
|
83
|
+
const roles = (EVIDENCE_ROLES[taskType] ?? []).slice(0, 3);
|
|
84
|
+
const who = roles.length > 0 ? ` (${roles.join(', ')})` : '';
|
|
85
|
+
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>"\`).`;
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
// Evidence-gathering flows: their first phase fans out across independent sources.
|
|
72
89
|
const EVIDENCE_TYPES = new Set(['INCIDENT', 'INVESTIGATION', 'RESEARCH']);
|
|
@@ -85,8 +102,12 @@ const ROLE_IDS = new Set([
|
|
|
85
102
|
...(readRegistry('registries/agent-roles.json')?.roles ?? []).map((role) => role.id),
|
|
86
103
|
...(readRegistry('registries/preferred-tools.json')?.tools ?? []).filter((tool) => tool.kind === 'agent_role').map((tool) => tool.id),
|
|
87
104
|
]);
|
|
88
|
-
const
|
|
105
|
+
const TASK_FLOWS = readRegistry('registries/routing-matrix.json')?.task_flows ?? {};
|
|
106
|
+
const FLOW_ROLES = Object.fromEntries(Object.entries(TASK_FLOWS)
|
|
89
107
|
.map(([type, flow]) => [type, [...new Set(flow.phases.flatMap((phase) => phase.roles ?? []))].filter((role) => role !== 'orchestrator')]));
|
|
108
|
+
// The evidence phase of each flow: who gathers, as opposed to who fixes later.
|
|
109
|
+
const EVIDENCE_ROLES = Object.fromEntries(Object.entries(TASK_FLOWS)
|
|
110
|
+
.map(([type, flow]) => [type, (flow.phases[0]?.roles ?? []).filter((role) => role !== 'orchestrator')]));
|
|
90
111
|
|
|
91
112
|
/** 'role' for an orchestrator role (plugin-namespaced ids too), 'generic' otherwise, null when unknown. */
|
|
92
113
|
export function roleKind(agentType) {
|
|
@@ -97,6 +118,18 @@ export function roleKind(agentType) {
|
|
|
97
118
|
|
|
98
119
|
const MAX_REASON = 200;
|
|
99
120
|
|
|
121
|
+
export const INLINE_REASON = /^stateful:\s*\S/;
|
|
122
|
+
|
|
123
|
+
// A: a trivial run is a one-line change, not an investigation.
|
|
124
|
+
export const TRIVIAL_WORK_LIMIT = 8;
|
|
125
|
+
|
|
126
|
+
// C: sources whose reads cost real time and live remotely — the reads that pay to fan out.
|
|
127
|
+
const LIVE_SOURCE = /(^|[\s;&|(])(rtk\s+)?(ssh|scp|psql|mysql|mongosh|mongo|redis-cli|kubectl|docker\s+(exec|logs)|aws|gcloud|az|curl|wget|http|httpie)(\s|$)/;
|
|
128
|
+
|
|
129
|
+
export function isLiveSource(command) {
|
|
130
|
+
return typeof command === 'string' && LIVE_SOURCE.test(command);
|
|
131
|
+
}
|
|
132
|
+
|
|
100
133
|
// Reading the orchestration instructions is the intended first step, so it must
|
|
101
134
|
// never count as starting work without a run.
|
|
102
135
|
const INSTRUCTION_PATH = /(^|\/)(SKILL|AGENTS|CLAUDE|protocol)\.md$|\/orchestrate-core\/|(^|\/)(policies|workflows|registries)\/[^/]+\.(md|json)$/;
|
|
@@ -192,10 +225,12 @@ export function parseRunArgs(args) {
|
|
|
192
225
|
if (flag === '--type' && value) { type = value.toUpperCase(); index += 1; }
|
|
193
226
|
else if (flag === '--shards' && value) { shards = Number.parseInt(value, 10); index += 1; }
|
|
194
227
|
else if (flag === '--trivial' && value !== undefined) { trivial = value.slice(0, MAX_REASON); index += 1; }
|
|
195
|
-
else if (flag === '--inline' && value) { inline = value.slice(0, MAX_REASON); index += 1; }
|
|
228
|
+
else if (flag === '--inline' && value !== undefined) { inline = value.slice(0, MAX_REASON); index += 1; }
|
|
196
229
|
}
|
|
197
230
|
if (trivial !== null) return { action: 'start', trivial: true, reason: trivial || null, type: null, shards: null, inline: null };
|
|
198
231
|
if (!TASK_TYPES.includes(type)) return null;
|
|
232
|
+
// Inline is for live state a subagent cannot inherit; the reason has to name it.
|
|
233
|
+
if (inline !== null && !INLINE_REASON.test(inline)) return null;
|
|
199
234
|
return { action: 'start', trivial: false, reason: null, type, shards: Number.isInteger(shards) && shards > 0 ? shards : null, inline };
|
|
200
235
|
}
|
|
201
236
|
|
|
@@ -241,6 +276,7 @@ export function normalizePayload(raw) {
|
|
|
241
276
|
nonWork: NON_WORK_TOOLS.has(toolName.toLowerCase()),
|
|
242
277
|
loadsEntrypoint,
|
|
243
278
|
readOnly,
|
|
279
|
+
live: isLiveSource(command),
|
|
244
280
|
edits: editedPaths(toolName, input).map((editedPath) => ({ hash: pathHash(editedPath), test: TEST_PATH.test(editedPath) })),
|
|
245
281
|
};
|
|
246
282
|
}
|
|
@@ -267,6 +303,8 @@ function historyLine(session, fields, now) {
|
|
|
267
303
|
inline_reason: fields.inline_reason ?? null,
|
|
268
304
|
dispatch_nudged: Boolean(fields.dispatch_nudged),
|
|
269
305
|
overreach: Boolean(fields.overreach_nudged),
|
|
306
|
+
inline_after_nudge: Boolean(fields.inline_after_nudge),
|
|
307
|
+
live_calls: fields.live_calls ?? 0,
|
|
270
308
|
role_dispatches: fields.role_dispatches ?? 0,
|
|
271
309
|
generic_dispatches: fields.generic_dispatches ?? 0,
|
|
272
310
|
closed_by: fields.closed_by,
|
|
@@ -369,6 +407,9 @@ export function decide(previous, event, now) {
|
|
|
369
407
|
subagent_ids: [],
|
|
370
408
|
role_dispatches: 0,
|
|
371
409
|
generic_dispatches: 0,
|
|
410
|
+
dispatch_nudges: 0,
|
|
411
|
+
live_calls: 0,
|
|
412
|
+
inline_after_nudge: Boolean(event.run.inline && session.run?.dispatch_nudged),
|
|
372
413
|
};
|
|
373
414
|
// The work already done is accounted for on the run itself now.
|
|
374
415
|
session.worked_without_run = false;
|
|
@@ -390,16 +431,29 @@ export function decide(previous, event, now) {
|
|
|
390
431
|
if (current.trivial) {
|
|
391
432
|
current.edited_files = [...new Set([...(current.edited_files ?? []), ...event.edits.map((entry) => entry.hash)])];
|
|
392
433
|
current.touched_tests = Boolean(current.touched_tests) || event.edits.some((entry) => entry.test);
|
|
393
|
-
|
|
434
|
+
const grewByEdits = current.edited_files.length >= 2 || current.touched_tests;
|
|
435
|
+
const grewByWork = current.main_work_calls > TRIVIAL_WORK_LIMIT;
|
|
436
|
+
if (!current.overreach_nudged && (grewByEdits || grewByWork)) {
|
|
394
437
|
current.overreach_nudged = true;
|
|
395
|
-
output =
|
|
438
|
+
output = grewByEdits
|
|
439
|
+
? { additionalContext: overreachNudgeFor(undefined, current.edited_files.length), kind: 'overreach', files: current.edited_files.length }
|
|
440
|
+
: { additionalContext: overreachWorkNudgeFor(undefined, current.main_work_calls), kind: 'overreach', calls: current.main_work_calls };
|
|
396
441
|
}
|
|
397
442
|
return { session, output, history };
|
|
398
443
|
}
|
|
444
|
+
if (event.live) current.live_calls = (current.live_calls ?? 0) + 1;
|
|
445
|
+
const evidence = EVIDENCE_TYPES.has(current.task_type);
|
|
446
|
+
const step = (evidence ? EVIDENCE_CALLS_PER_SHARD : DISPATCH_CALLS_PER_SHARD) * (current.planned_shards ?? 0);
|
|
447
|
+
const nudges = current.dispatch_nudges ?? (current.dispatch_nudged ? 1 : 0);
|
|
448
|
+
// The follow-up is for live evidence; a few small local files are fine read inline.
|
|
449
|
+
const allowed = evidence && (current.live_calls ?? 0) > 0 ? MAX_DISPATCH_NUDGES : 1;
|
|
399
450
|
if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
|
|
400
|
-
&&
|
|
451
|
+
&& nudges < allowed && current.main_work_calls >= step * (nudges + 1)) {
|
|
401
452
|
current.dispatch_nudged = true;
|
|
402
|
-
|
|
453
|
+
current.dispatch_nudges = nudges + 1;
|
|
454
|
+
output = nudges === 0
|
|
455
|
+
? { additionalContext: dispatchNudgeFor(undefined, current.planned_shards, current.task_type), kind: 'dispatch', planned: current.planned_shards, taskType: current.task_type }
|
|
456
|
+
: { additionalContext: dispatchFollowupFor(undefined, current.planned_shards, current.task_type), kind: 'dispatch_followup', planned: current.planned_shards, taskType: current.task_type };
|
|
403
457
|
}
|
|
404
458
|
return { session, output, history };
|
|
405
459
|
}
|
|
@@ -538,7 +592,8 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
|
|
|
538
592
|
if (!result.output) return null;
|
|
539
593
|
// decide() speaks in the default CLI spelling; the hook swaps in the runnable path.
|
|
540
594
|
const text = result.output.kind === 'dispatch' ? dispatchNudgeFor(cli, result.output.planned, result.output.taskType)
|
|
541
|
-
: result.output.kind === '
|
|
595
|
+
: result.output.kind === 'dispatch_followup' ? dispatchFollowupFor(cli, result.output.planned, result.output.taskType)
|
|
596
|
+
: result.output.kind === 'overreach' ? (result.output.calls ? overreachWorkNudgeFor(cli, result.output.calls) : overreachNudgeFor(cli, result.output.files))
|
|
542
597
|
: nudgeFor(cli);
|
|
543
598
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
|
|
544
599
|
}
|
|
@@ -584,11 +639,12 @@ export function adherenceSummary(lines) {
|
|
|
584
639
|
planned_but_not_dispatched: lines.filter((line) => (line.planned_shards ?? 0) > 1 && line.subagents_started === 0).length,
|
|
585
640
|
runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
|
|
586
641
|
inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
|
|
642
|
+
inline_after_nudge: lines.filter((line) => line.inline_after_nudge).length,
|
|
587
643
|
trivial_overreach: lines.filter((line) => line.trivial && line.overreach).length,
|
|
588
644
|
role_dispatches: lines.reduce((sum, line) => sum + (line.role_dispatches ?? 0), 0),
|
|
589
645
|
generic_dispatches: lines.reduce((sum, line) => sum + (line.generic_dispatches ?? 0), 0),
|
|
590
646
|
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
|
|
647
|
+
below_fan_out: lines.filter((line) => EVIDENCE_TYPES.has(line.task_type) && !line.inline_reason && (line.live_calls ?? 0) > 0
|
|
592
648
|
&& (line.planned_shards ?? 0) < 2 && (line.subagents_started ?? 0) < 2).length,
|
|
593
649
|
};
|
|
594
650
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
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": {
|
package/policies/dispatch.md
CHANGED
|
@@ -294,12 +294,18 @@ Everything around that chain is still sharded:
|
|
|
294
294
|
- Independent reads are never inline: log, metric and database queries, code search, config
|
|
295
295
|
lookups. An SSH query that only reads is a stateless command, not a session — five of them are
|
|
296
296
|
five W-tier evidence shards, not one inline chain.
|
|
297
|
+
- Size the fan-out to the evidence, not to the task type. **Live or large sources** — anything
|
|
298
|
+
behind `ssh`, a remote database, a cluster or cloud CLI, an HTTP API, or logs too long to read
|
|
299
|
+
whole — are where parallel W-tier collectors pay, and where the incident/investigation/research
|
|
300
|
+
minimums apply. A handful of small local files is read faster inline than dispatched; plan it as
|
|
301
|
+
one shard and do not pad the fan-out to look thorough.
|
|
297
302
|
- "Cheaper inline" is not a reason. The main thread runs at the flow's highest tier; the same reads
|
|
298
303
|
on a W-tier subagent cost less per token and keep the orchestrator's context for synthesis.
|
|
299
304
|
- A tool that is not visible is not absent. On Claude Code the Agent tool can be deferred — search
|
|
300
305
|
for it (`tool.discovery`) before concluding dispatch is unavailable.
|
|
301
306
|
- Declare the inline chain when opening the run: `run start --type <T> --shards <n> --inline
|
|
302
|
-
"stateful:<what state>"`.
|
|
307
|
+
"stateful:<what state>"`. The reason must name the live state; `run start` rejects any other
|
|
308
|
+
reason, and an `--inline` declared only after a dispatch reminder is recorded as retroactive. Undeclared, a planned multi-shard run whose main thread keeps working
|
|
303
309
|
with no subagent started gets one reminder from the flow hooks, and the audit counts it.
|
|
304
310
|
- Name the real seam you will split at, e.g. iOS simulator vs Android emulator: independent devices
|
|
305
311
|
with independent state are parallel shards even when each one is inline inside.
|
|
@@ -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", "live_calls"],
|
|
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] },
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"planned_shards": { "type": ["integer", "null"], "minimum": 1 },
|
|
19
19
|
"subagents_started": { "type": "integer", "minimum": 0 },
|
|
20
20
|
"started_outside_flow": { "type": "boolean" },
|
|
21
|
-
"inline_reason": { "type": ["string", "null"], "description": "Why the shards stay inline, e.g. stateful:browser — a live state no subagent can inherit." },
|
|
21
|
+
"inline_reason": { "type": ["string", "null"], "pattern": "^stateful:\\s*\\S", "description": "Why the shards stay inline, e.g. stateful:browser — a live state no subagent can inherit." },
|
|
22
22
|
"main_work_calls": { "type": "integer", "minimum": 0 },
|
|
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." },
|
|
@@ -26,7 +26,10 @@
|
|
|
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." },
|
|
32
|
+
"live_calls": { "type": "integer", "minimum": 0, "description": "Main-thread calls that read a live source (ssh, remote databases, cluster/cloud CLIs, HTTP clients)." }
|
|
30
33
|
}
|
|
31
34
|
},
|
|
32
35
|
"session": {
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
"historyLine": {
|
|
49
52
|
"type": "object",
|
|
50
53
|
"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"],
|
|
54
|
+
"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", "live_calls", "role_dispatches", "generic_dispatches", "closed_by"],
|
|
52
55
|
"properties": {
|
|
53
56
|
"session": { "type": "string", "minLength": 1 },
|
|
54
57
|
"task_id": { "type": ["string", "null"] },
|
|
@@ -65,6 +68,8 @@
|
|
|
65
68
|
"inline_reason": { "type": ["string", "null"] },
|
|
66
69
|
"dispatch_nudged": { "type": "boolean" },
|
|
67
70
|
"overreach": { "type": "boolean", "description": "A trivial run that edited two or more files or its tests." },
|
|
71
|
+
"inline_after_nudge": { "type": "boolean" },
|
|
72
|
+
"live_calls": { "type": "integer", "minimum": 0 },
|
|
68
73
|
"role_dispatches": { "type": "integer", "minimum": 0 },
|
|
69
74
|
"generic_dispatches": { "type": "integer", "minimum": 0 },
|
|
70
75
|
"closed_by": { "enum": ["run_close", "next_prompt", "succession", "turn_end", "session_end"] }
|