llm-orchestrator 1.2.0 → 1.2.1
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/marketplace.json +1 -0
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +7 -0
- package/bin/run.mjs +2 -2
- package/lib/flow-gate.mjs +94 -22
- package/package.json +1 -1
- package/policies/dispatch.md +21 -0
- package/protocol.md +5 -0
- package/schemas/flow-ledger.schema.json +8 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tbogdan",
|
|
3
|
+
"description": "llm-orchestrator: write /task once and the agent plans, shards across parallel subagents, gates every phase and verifies before claiming done.",
|
|
3
4
|
"owner": {
|
|
4
5
|
"name": "Bogdan-Gabriel Torcescu",
|
|
5
6
|
"url": "https://www.linkedin.com/in/bogdantorcescu/"
|
|
@@ -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.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bogdan-Gabriel Torcescu",
|
|
7
7
|
"url": "https://www.linkedin.com/in/bogdantorcescu/"
|
package/README.md
CHANGED
|
@@ -348,6 +348,13 @@ more instructions:
|
|
|
348
348
|
`flow.adherence`: runs, trivial declarations, tasks that skipped the flow, runs started outside
|
|
349
349
|
it, runs opened without a PlanShard count, runs that planned several shards but started no
|
|
350
350
|
subagents, and runs still open.
|
|
351
|
+
- A run opened with two or more shards whose main thread keeps doing the work — six work calls, no
|
|
352
|
+
subagent started — gets one more sentence: dispatch the independent shards (searching for the
|
|
353
|
+
Agent tool if it is deferred), or declare the chain inline with
|
|
354
|
+
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Work is inline only while it holds
|
|
355
|
+
live state a subagent cannot inherit (a browser mid-flow, an interactive shell); independent reads
|
|
356
|
+
are never inline. The audit adds `inline_declared` and `below_fan_out` (incident, investigation or
|
|
357
|
+
research runs opened with fewer than two shards).
|
|
351
358
|
- Once the entrypoint is loaded, read-only discovery (reading files, `grep`, `git status`, tool
|
|
352
359
|
version checks) before `run start` is SKILL.md steps 2–3, not a deviation. An edit, a write, a
|
|
353
360
|
dispatch or any other shell command before the run is.
|
package/bin/run.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { parseRunArgs, TASK_TYPES } from '../lib/flow-gate.mjs';
|
|
11
11
|
|
|
12
|
-
const USAGE = `Usage: llm-orchestrator run start --type <${TASK_TYPES.join('|')}> [--shards N]
|
|
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>"
|
|
14
14
|
llm-orchestrator run close`;
|
|
15
15
|
|
|
@@ -22,6 +22,6 @@ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
|
22
22
|
process.stderr.write(`${USAGE}\n`);
|
|
23
23
|
process.exitCode = 1;
|
|
24
24
|
} else {
|
|
25
|
-
process.stdout.write(`${JSON.stringify({ run: parsed.action, ...(parsed.action === 'start' ? { type: parsed.type, trivial: parsed.trivial, shards: parsed.shards, reason: parsed.reason } : {}) })}\n`);
|
|
25
|
+
process.stdout.write(`${JSON.stringify({ run: parsed.action, ...(parsed.action === 'start' ? { type: parsed.type, trivial: parsed.trivial, shards: parsed.shards, reason: parsed.reason, inline: parsed.inline } : {}) })}\n`);
|
|
26
26
|
}
|
|
27
27
|
}
|
package/lib/flow-gate.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* tool inputs or file contents. `doctor` reads it back as the adherence audit.
|
|
15
15
|
*/
|
|
16
16
|
import { createHash, randomUUID } from 'node:crypto';
|
|
17
|
-
import { appendFile, mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { appendFile, mkdir, readdir, readFile, rename, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -28,6 +28,20 @@ export function nudgeFor(cli = 'llm-orchestrator') {
|
|
|
28
28
|
|
|
29
29
|
export const NUDGE = nudgeFor();
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The second, and last, sentence the model can get per run: the plan has shards,
|
|
33
|
+
* none went to a subagent, and the main thread keeps doing the work itself.
|
|
34
|
+
*/
|
|
35
|
+
export function dispatchNudgeFor(cli = 'llm-orchestrator', planned = 2) {
|
|
36
|
+
return `This run planned ${planned} shards and none has been dispatched to a subagent; the main thread is doing the work itself. Dispatch the independent shards (Claude Code: the Agent tool — search for it if it is deferred; Codex: spawn_agent; OpenCode/Kilo: task), or declare why this must stay inline (\`${cli} run start --type <TYPE> --inline "stateful:<what state>"\`).`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Main-thread work calls tolerated after run start before the dispatch nudge.
|
|
40
|
+
export const DISPATCH_THRESHOLD = 6;
|
|
41
|
+
|
|
42
|
+
// Evidence-gathering flows: their first phase fans out across independent sources.
|
|
43
|
+
const EVIDENCE_TYPES = new Set(['INCIDENT', 'INVESTIGATION', 'RESEARCH']);
|
|
44
|
+
|
|
31
45
|
export const TASK_TYPES = ['INCIDENT', 'FEATURE', 'BUG_FIX', 'REFACTOR', 'INVESTIGATION', 'DEPLOY', 'CONFIG', 'REVIEW', 'RESEARCH'];
|
|
32
46
|
|
|
33
47
|
export const LEDGER_DIRECTORY = '.orchestrator-run';
|
|
@@ -46,7 +60,7 @@ const RUN_COMMAND = /(^|[\s/"'])llm-orchestrator(?:\.mjs)?["']?\s+run\s+(start|c
|
|
|
46
60
|
// come before the run is opened at step 4. Once the entrypoint is loaded, these do not
|
|
47
61
|
// count as starting work; edits, writes, dispatches and other shell commands still do.
|
|
48
62
|
const READ_ONLY_TOOLS = new Set(['read', 'grep', 'glob', 'ls', 'view', 'list', 'notebookread', 'webfetch', 'websearch']);
|
|
49
|
-
const READ_ONLY_COMMAND = /^\s*(rtk\s+)?(cat|head|tail|less|bat|ls|tree|find|grep|rg|ag|wc|file|stat|pwd|which|type|command\s+-v|git\s+(status|diff|log|show|ls-files|branch|rev-parse|remote))(\s|$)|--version\b/;
|
|
63
|
+
const READ_ONLY_COMMAND = /^\s*(rtk\s+)?(cat|head|tail|less|bat|ls|tree|find|grep|rg|ag|wc|file|stat|pwd|which|type|echo|printf|sort|uniq|command\s+-v|git\s+(status|diff|log|show|ls-files|branch|rev-parse|remote))(\s|$)|--version\b/;
|
|
50
64
|
const LOAD_SKILL = /(^|\/)orchestrate(-core)?(\/SKILL\.md)?$/;
|
|
51
65
|
|
|
52
66
|
/** Every segment of a compound command reads; nothing is redirected into a file. */
|
|
@@ -122,16 +136,18 @@ export function parseRunArgs(args) {
|
|
|
122
136
|
let type = null;
|
|
123
137
|
let shards = null;
|
|
124
138
|
let trivial = null;
|
|
139
|
+
let inline = null;
|
|
125
140
|
for (let index = 0; index < rest.length; index += 1) {
|
|
126
141
|
const flag = rest[index];
|
|
127
142
|
const value = rest[index + 1];
|
|
128
143
|
if (flag === '--type' && value) { type = value.toUpperCase(); index += 1; }
|
|
129
144
|
else if (flag === '--shards' && value) { shards = Number.parseInt(value, 10); index += 1; }
|
|
130
145
|
else if (flag === '--trivial' && value !== undefined) { trivial = value.slice(0, MAX_REASON); index += 1; }
|
|
146
|
+
else if (flag === '--inline' && value) { inline = value.slice(0, MAX_REASON); index += 1; }
|
|
131
147
|
}
|
|
132
|
-
if (trivial !== null) return { action: 'start', trivial: true, reason: trivial || null, type: null, shards: null };
|
|
148
|
+
if (trivial !== null) return { action: 'start', trivial: true, reason: trivial || null, type: null, shards: null, inline: null };
|
|
133
149
|
if (!TASK_TYPES.includes(type)) return null;
|
|
134
|
-
return { action: 'start', trivial: false, reason: null, type, shards: Number.isInteger(shards) && shards > 0 ? shards : null };
|
|
150
|
+
return { action: 'start', trivial: false, reason: null, type, shards: Number.isInteger(shards) && shards > 0 ? shards : null, inline };
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
function keyOf(eventName, value) {
|
|
@@ -196,6 +212,8 @@ function historyLine(session, fields, now) {
|
|
|
196
212
|
subagents_started: fields.subagents_started ?? 0,
|
|
197
213
|
started_outside_flow: Boolean(fields.started_outside_flow),
|
|
198
214
|
skipped_flow: Boolean(fields.skipped_flow),
|
|
215
|
+
inline_reason: fields.inline_reason ?? null,
|
|
216
|
+
dispatch_nudged: Boolean(fields.dispatch_nudged),
|
|
199
217
|
closed_by: fields.closed_by,
|
|
200
218
|
};
|
|
201
219
|
}
|
|
@@ -206,7 +224,7 @@ function closeRun(session, now, closedBy) {
|
|
|
206
224
|
|
|
207
225
|
/**
|
|
208
226
|
* Pure state transition: (session, event, now) → { session, output, history }.
|
|
209
|
-
* `output` is
|
|
227
|
+
* `output` is null or `{ additionalContext, kind }` — a steering sentence, never a decision.
|
|
210
228
|
*/
|
|
211
229
|
export function decide(previous, event, now) {
|
|
212
230
|
const session = structuredClone(previous);
|
|
@@ -259,6 +277,9 @@ export function decide(previous, event, now) {
|
|
|
259
277
|
planned_shards: event.run.shards,
|
|
260
278
|
subagents_started: 0,
|
|
261
279
|
started_outside_flow: session.worked_without_run,
|
|
280
|
+
inline_reason: event.run.inline,
|
|
281
|
+
main_work_calls: 0,
|
|
282
|
+
dispatch_nudged: false,
|
|
262
283
|
};
|
|
263
284
|
// The work already done is accounted for on the run itself now.
|
|
264
285
|
session.worked_without_run = false;
|
|
@@ -271,7 +292,17 @@ export function decide(previous, event, now) {
|
|
|
271
292
|
}
|
|
272
293
|
|
|
273
294
|
if (event.loadsEntrypoint) session.entrypoint_loaded = true;
|
|
274
|
-
if (event.isSubagent || event.instruction || event.orchestratorCall || event.nonWork || event.loadsEntrypoint
|
|
295
|
+
if (event.isSubagent || event.instruction || event.orchestratorCall || event.nonWork || event.loadsEntrypoint) {
|
|
296
|
+
return { session, output, history };
|
|
297
|
+
}
|
|
298
|
+
if (session.run) {
|
|
299
|
+
const current = session.run;
|
|
300
|
+
current.main_work_calls = (current.main_work_calls ?? 0) + 1;
|
|
301
|
+
if ((current.planned_shards ?? 0) >= 2 && current.subagents_started === 0 && !current.inline_reason
|
|
302
|
+
&& !current.dispatch_nudged && current.main_work_calls >= DISPATCH_THRESHOLD) {
|
|
303
|
+
current.dispatch_nudged = true;
|
|
304
|
+
output = { additionalContext: dispatchNudgeFor(undefined, current.planned_shards), kind: 'dispatch', planned: current.planned_shards };
|
|
305
|
+
}
|
|
275
306
|
return { session, output, history };
|
|
276
307
|
}
|
|
277
308
|
// Following the entrypoint: discovery reads before the run opens are step 2–3, not a skip.
|
|
@@ -280,7 +311,7 @@ export function decide(previous, event, now) {
|
|
|
280
311
|
session.worked_without_run = true;
|
|
281
312
|
if (!session.nudged) {
|
|
282
313
|
session.nudged = true;
|
|
283
|
-
output = { additionalContext: NUDGE };
|
|
314
|
+
output = { additionalContext: NUDGE, kind: 'start' };
|
|
284
315
|
}
|
|
285
316
|
return { session, output, history };
|
|
286
317
|
}
|
|
@@ -326,6 +357,40 @@ export async function projectUsesOrchestrator(project) {
|
|
|
326
357
|
return false;
|
|
327
358
|
}
|
|
328
359
|
|
|
360
|
+
const LOCK_WAIT_MS = 2000;
|
|
361
|
+
const LOCK_STALE_MS = 5000;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Claude Code runs matching hooks in parallel, so a plugin install and a CLI
|
|
365
|
+
* install both handle every event at the same moment. Serialise per session with
|
|
366
|
+
* an atomic mkdir lock; a lock older than LOCK_STALE_MS belongs to a crashed
|
|
367
|
+
* handler and is taken over. Failing to lock in time throws — the gate fails open.
|
|
368
|
+
*/
|
|
369
|
+
async function withSessionLock(path, work) {
|
|
370
|
+
const lock = `${path}.lock`;
|
|
371
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
372
|
+
while (true) {
|
|
373
|
+
try {
|
|
374
|
+
await mkdir(lock);
|
|
375
|
+
break;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error.code !== 'EEXIST') throw error;
|
|
378
|
+
const held = await stat(lock).then((info) => Date.now() - info.mtimeMs).catch(() => 0);
|
|
379
|
+
if (held > LOCK_STALE_MS) {
|
|
380
|
+
await rmdir(lock).catch(() => {});
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (Date.now() > deadline) throw new Error('ledger session is locked');
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, 5 + Math.random() * 20));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
return await work();
|
|
389
|
+
} finally {
|
|
390
|
+
await rmdir(lock).catch(() => {});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
329
394
|
export async function handleHook({ payload, project, now = Date.now(), cli = 'llm-orchestrator' }) {
|
|
330
395
|
const event = normalizePayload(payload);
|
|
331
396
|
if (event.kind === 'other') return null;
|
|
@@ -333,22 +398,27 @@ export async function handleHook({ payload, project, now = Date.now(), cli = 'll
|
|
|
333
398
|
if (!event.run && !(await projectUsesOrchestrator(project))) return null;
|
|
334
399
|
const directory = await ensureLedger(project);
|
|
335
400
|
const path = join(directory, 'sessions', sessionFileName(event.session));
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
401
|
+
const result = await withSessionLock(path, async () => {
|
|
402
|
+
let session;
|
|
403
|
+
try {
|
|
404
|
+
session = JSON.parse(await readFile(path, 'utf8'));
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error.code !== 'ENOENT') throw error;
|
|
407
|
+
session = emptySession(event.session);
|
|
408
|
+
}
|
|
409
|
+
const next = decide(session, event, now);
|
|
410
|
+
const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
411
|
+
await writeFile(temporary, `${JSON.stringify(next.session)}\n`);
|
|
412
|
+
await rename(temporary, path);
|
|
413
|
+
if (next.history.length > 0) {
|
|
414
|
+
await appendFile(join(directory, 'history.jsonl'), next.history.map((line) => `${JSON.stringify(line)}\n`).join(''));
|
|
415
|
+
}
|
|
416
|
+
return next;
|
|
417
|
+
});
|
|
350
418
|
if (!result.output) return null;
|
|
351
|
-
|
|
419
|
+
// decide() speaks in the default CLI spelling; the hook swaps in the runnable path.
|
|
420
|
+
const text = result.output.kind === 'dispatch' ? dispatchNudgeFor(cli, result.output.planned) : nudgeFor(cli);
|
|
421
|
+
return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text } };
|
|
352
422
|
}
|
|
353
423
|
|
|
354
424
|
export async function readHistory(project) {
|
|
@@ -391,5 +461,7 @@ export function adherenceSummary(lines) {
|
|
|
391
461
|
started_outside_flow: lines.filter((line) => line.started_outside_flow).length,
|
|
392
462
|
planned_but_not_dispatched: lines.filter((line) => (line.planned_shards ?? 0) > 1 && line.subagents_started === 0).length,
|
|
393
463
|
runs_without_plan: lines.filter((line) => !line.skipped_flow && !line.trivial && line.planned_shards === null).length,
|
|
464
|
+
inline_declared: lines.filter((line) => Boolean(line.inline_reason)).length,
|
|
465
|
+
below_fan_out: lines.filter((line) => EVIDENCE_TYPES.has(line.task_type) && !line.inline_reason && (line.planned_shards ?? 0) < 2).length,
|
|
394
466
|
};
|
|
395
467
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
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
|
@@ -282,3 +282,24 @@ Parallel work requires disjoint ownership and a real critical-path reduction; do
|
|
|
282
282
|
beyond the minimums to satisfy an appearance of breadth, and do not fall below them when independent
|
|
283
283
|
scopes exist. A serial fallback is valid only where no required independence is lost — a reviewer's
|
|
284
284
|
independence is never negotiable.
|
|
285
|
+
|
|
286
|
+
### A shard ends where state ends
|
|
287
|
+
|
|
288
|
+
"No independent work exists" is the claim that most often excuses skipping dispatch, so it has a
|
|
289
|
+
definition. Work is **inline** only while it holds live state a fresh subagent cannot inherit — a
|
|
290
|
+
browser session mid-flow (cookies, a half-filled form, an open modal), an interactive SSH shell with
|
|
291
|
+
context, a REPL, a booted simulator — **and** each step depends on the result of the one before.
|
|
292
|
+
Everything around that chain is still sharded:
|
|
293
|
+
|
|
294
|
+
- Independent reads are never inline: log, metric and database queries, code search, config
|
|
295
|
+
lookups. An SSH query that only reads is a stateless command, not a session — five of them are
|
|
296
|
+
five W-tier evidence shards, not one inline chain.
|
|
297
|
+
- "Cheaper inline" is not a reason. The main thread runs at the flow's highest tier; the same reads
|
|
298
|
+
on a W-tier subagent cost less per token and keep the orchestrator's context for synthesis.
|
|
299
|
+
- A tool that is not visible is not absent. On Claude Code the Agent tool can be deferred — search
|
|
300
|
+
for it (`tool.discovery`) before concluding dispatch is unavailable.
|
|
301
|
+
- Declare the inline chain when opening the run: `run start --type <T> --shards <n> --inline
|
|
302
|
+
"stateful:<what state>"`. Undeclared, a planned multi-shard run whose main thread keeps working
|
|
303
|
+
with no subagent started gets one reminder from the flow hooks, and the audit counts it.
|
|
304
|
+
- Name the real seam you will split at, e.g. iOS simulator vs Android emulator: independent devices
|
|
305
|
+
with independent state are parallel shards even when each one is inline inside.
|
package/protocol.md
CHANGED
|
@@ -36,6 +36,11 @@ can target it.
|
|
|
36
36
|
No dispatch, edit or shell command may precede this object. Opening the run
|
|
37
37
|
(`llm-orchestrator run start --type <TASK_TYPE>`) follows it immediately.
|
|
38
38
|
|
|
39
|
+
**Inline shards.** A run whose shards must stay in the main thread because they hold live state
|
|
40
|
+
(a browser session, an interactive shell, a simulator) says so when it opens:
|
|
41
|
+
`run start --type <T> --shards <n> --inline "stateful:<what>"`. Independent reads around that state
|
|
42
|
+
are still dispatched — see "A shard ends where state ends" in [dispatch](policies/dispatch.md).
|
|
43
|
+
|
|
39
44
|
**Trivial tasks.** A one-line, obviously scoped change (a typo, a version bump) may skip the full
|
|
40
45
|
flow, but only by declaring it: `llm-orchestrator run start --trivial "<reason>"`. The
|
|
41
46
|
declaration and its reason are recorded; an undeclared skip is recorded as a skipped flow.
|
|
@@ -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"],
|
|
11
|
+
"required": ["task_id", "task_type", "trivial", "reason", "opened_at", "planned_shards", "subagents_started", "started_outside_flow", "inline_reason", "main_work_calls", "dispatch_nudged"],
|
|
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] },
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"opened_at": { "type": "number" },
|
|
18
18
|
"planned_shards": { "type": ["integer", "null"], "minimum": 1 },
|
|
19
19
|
"subagents_started": { "type": "integer", "minimum": 0 },
|
|
20
|
-
"started_outside_flow": { "type": "boolean" }
|
|
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." },
|
|
22
|
+
"main_work_calls": { "type": "integer", "minimum": 0 },
|
|
23
|
+
"dispatch_nudged": { "type": "boolean" }
|
|
21
24
|
}
|
|
22
25
|
},
|
|
23
26
|
"session": {
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
"historyLine": {
|
|
40
43
|
"type": "object",
|
|
41
44
|
"additionalProperties": false,
|
|
42
|
-
"required": ["session", "task_id", "task_type", "trivial", "reason", "opened_at", "closed_at", "duration_s", "planned_shards", "subagents_started", "started_outside_flow", "skipped_flow", "closed_by"],
|
|
45
|
+
"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", "closed_by"],
|
|
43
46
|
"properties": {
|
|
44
47
|
"session": { "type": "string", "minLength": 1 },
|
|
45
48
|
"task_id": { "type": ["string", "null"] },
|
|
@@ -53,6 +56,8 @@
|
|
|
53
56
|
"subagents_started": { "type": "integer", "minimum": 0 },
|
|
54
57
|
"started_outside_flow": { "type": "boolean" },
|
|
55
58
|
"skipped_flow": { "type": "boolean" },
|
|
59
|
+
"inline_reason": { "type": ["string", "null"] },
|
|
60
|
+
"dispatch_nudged": { "type": "boolean" },
|
|
56
61
|
"closed_by": { "enum": ["run_close", "next_prompt", "succession"] }
|
|
57
62
|
}
|
|
58
63
|
}
|