agentxchain 2.53.0 → 2.55.0
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/bin/agentxchain.js +1 -0
- package/dashboard/components/run-history.js +14 -0
- package/package.json +1 -1
- package/src/commands/history.js +8 -3
- package/src/commands/run.js +21 -0
- package/src/commands/status.js +4 -0
- package/src/lib/dispatch-bundle.js +11 -0
- package/src/lib/export.js +1 -0
- package/src/lib/governed-state.js +1 -0
- package/src/lib/report.js +7 -0
- package/src/lib/run-context-inheritance.js +161 -0
- package/src/lib/run-history.js +44 -0
- package/src/lib/run-loop.js +3 -0
package/bin/agentxchain.js
CHANGED
|
@@ -407,6 +407,7 @@ program
|
|
|
407
407
|
.option('--no-report', 'Suppress automatic governance report after run completes')
|
|
408
408
|
.option('--continue-from <run_id>', 'Continue from a prior terminal run (sets trigger=continuation)')
|
|
409
409
|
.option('--recover-from <run_id>', 'Recover from a prior blocked run (sets trigger=recovery)')
|
|
410
|
+
.option('--inherit-context', 'Inherit read-only summary context from the parent run (requires --continue-from or --recover-from)')
|
|
410
411
|
.action(runCommand);
|
|
411
412
|
|
|
412
413
|
program
|
|
@@ -68,6 +68,14 @@ function truncateId(id, len = 12) {
|
|
|
68
68
|
return id.length > len ? id.slice(0, len) + '…' : id;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function isInheritable(entry) {
|
|
72
|
+
const snap = entry?.inheritance_snapshot;
|
|
73
|
+
if (!snap) return false;
|
|
74
|
+
const hasDecisions = Array.isArray(snap.recent_decisions) && snap.recent_decisions.length > 0;
|
|
75
|
+
const hasTurns = Array.isArray(snap.recent_accepted_turns) && snap.recent_accepted_turns.length > 0;
|
|
76
|
+
return hasDecisions || hasTurns;
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
function renderRow(entry, index) {
|
|
72
80
|
const rowClass = entry.status === 'blocked'
|
|
73
81
|
? ' style="border-left:3px solid var(--yellow)"'
|
|
@@ -83,10 +91,15 @@ function renderRow(entry, index) {
|
|
|
83
91
|
? `<div class="blocked-hint" style="font-size:0.85em;color:var(--yellow);margin-top:2px">${esc(typeof entry.blocked_reason === 'string' ? entry.blocked_reason : entry.blocked_reason?.detail || entry.blocked_reason?.category || '')}</div>`
|
|
84
92
|
: '';
|
|
85
93
|
|
|
94
|
+
const ctxIndicator = isInheritable(entry)
|
|
95
|
+
? `<span title="Has inheritance snapshot — usable by child runs" style="color:var(--green)">✓</span>`
|
|
96
|
+
: `<span style="color:var(--text-dim)">—</span>`;
|
|
97
|
+
|
|
86
98
|
return `<tr${rowClass}>
|
|
87
99
|
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
88
100
|
<td class="mono" title="${esc(entry.run_id)}">${esc(truncateId(entry.run_id))}</td>
|
|
89
101
|
<td>${statusBadge(entry.status)}${blockedInfo}</td>
|
|
102
|
+
<td>${ctxIndicator}</td>
|
|
90
103
|
<td>${phases}</td>
|
|
91
104
|
<td>${entry.total_turns ?? '—'}</td>
|
|
92
105
|
<td>${formatCost(entry.total_cost_usd)}</td>
|
|
@@ -125,6 +138,7 @@ export function render({ runHistory }) {
|
|
|
125
138
|
<th>#</th>
|
|
126
139
|
<th>Run ID</th>
|
|
127
140
|
<th>Status</th>
|
|
141
|
+
<th>Ctx</th>
|
|
128
142
|
<th>Phases</th>
|
|
129
143
|
<th>Turns</th>
|
|
130
144
|
<th>Cost</th>
|
package/package.json
CHANGED
package/src/commands/history.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { resolve } from 'path';
|
|
8
8
|
import { existsSync, readFileSync } from 'fs';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
import { queryRunHistory, queryRunLineage } from '../lib/run-history.js';
|
|
10
|
+
import { queryRunHistory, queryRunLineage, isInheritable } from '../lib/run-history.js';
|
|
11
11
|
import { getRunTriggerLabel, summarizeRunProvenance } from '../lib/run-provenance.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -46,11 +46,12 @@ export async function historyCommand(opts) {
|
|
|
46
46
|
const turns = `${entry.total_turns || 0} turns`;
|
|
47
47
|
const cost = entry.total_cost_usd != null ? `$${entry.total_cost_usd.toFixed(2)}` : '';
|
|
48
48
|
const trigger = getRunTriggerLabel(entry.provenance);
|
|
49
|
+
const ctxMarker = isInheritable(entry) ? ' [ctx]' : '';
|
|
49
50
|
const parentNote = entry.provenance?.parent_run_id
|
|
50
51
|
? ` from ${entry.provenance.parent_run_id.slice(0, 12)}`
|
|
51
52
|
: '';
|
|
52
53
|
const prefix = i === 0 ? ' ' : ' └─ ';
|
|
53
|
-
console.log(`${prefix}${runId} ${status} ${pad(phases, 20)} ${pad(turns, 10)} ${pad(cost, 8)} (${trigger}${parentNote})`);
|
|
54
|
+
console.log(`${prefix}${runId} ${status} ${pad(phases, 20)} ${pad(turns, 10)} ${pad(cost, 8)} (${trigger}${parentNote})${ctxMarker}`);
|
|
54
55
|
});
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
@@ -63,7 +64,8 @@ export async function historyCommand(opts) {
|
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
if (opts.json) {
|
|
66
|
-
|
|
67
|
+
const enriched = entries.map(e => ({ ...e, inheritable: isInheritable(e) }));
|
|
68
|
+
console.log(JSON.stringify(enriched, null, 2));
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -81,6 +83,7 @@ export async function historyCommand(opts) {
|
|
|
81
83
|
pad('Run ID', 14),
|
|
82
84
|
pad('Status', 11),
|
|
83
85
|
pad('Trigger', 14),
|
|
86
|
+
pad('Ctx', 4),
|
|
84
87
|
pad('Phases', 8),
|
|
85
88
|
pad('Turns', 6),
|
|
86
89
|
pad('Cost', 10),
|
|
@@ -96,6 +99,7 @@ export async function historyCommand(opts) {
|
|
|
96
99
|
const runId = (entry.run_id || '—').slice(0, 12);
|
|
97
100
|
const status = formatStatus(entry.status);
|
|
98
101
|
const trigger = getRunTriggerLabel(entry.provenance);
|
|
102
|
+
const ctx = isInheritable(entry) ? '✓' : '—';
|
|
99
103
|
const phases = String(entry.phases_completed?.length || 0);
|
|
100
104
|
const turns = String(entry.total_turns || 0);
|
|
101
105
|
const cost = entry.total_cost_usd != null
|
|
@@ -113,6 +117,7 @@ export async function historyCommand(opts) {
|
|
|
113
117
|
pad(runId, 14),
|
|
114
118
|
pad(status, 11),
|
|
115
119
|
pad(trigger, 14),
|
|
120
|
+
pad(ctx, 4),
|
|
116
121
|
pad(phases, 8),
|
|
117
122
|
pad(turns, 6),
|
|
118
123
|
pad(cost, 10),
|
package/src/commands/run.js
CHANGED
|
@@ -33,6 +33,7 @@ import { runHooks } from '../lib/hook-runner.js';
|
|
|
33
33
|
import { finalizeDispatchManifest } from '../lib/dispatch-manifest.js';
|
|
34
34
|
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
35
35
|
import { resolveGovernedRole } from '../lib/role-resolution.js';
|
|
36
|
+
import { buildInheritedContext } from '../lib/run-context-inheritance.js';
|
|
36
37
|
import {
|
|
37
38
|
getDispatchAssignmentPath,
|
|
38
39
|
getDispatchContextPath,
|
|
@@ -91,6 +92,25 @@ export async function executeGovernedRun(context, opts = {}) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// ── Inherit-context validation ─────────────────────────────────────────
|
|
96
|
+
const inheritContext = !!opts.inheritContext;
|
|
97
|
+
let inheritedContext = null;
|
|
98
|
+
if (inheritContext) {
|
|
99
|
+
if (!continueFrom && !recoverFrom) {
|
|
100
|
+
log(chalk.red('--inherit-context requires --continue-from or --recover-from'));
|
|
101
|
+
log(chalk.dim('Usage: agentxchain run --continue-from <run_id> --inherit-context'));
|
|
102
|
+
return { exitCode: 1, result: null };
|
|
103
|
+
}
|
|
104
|
+
const parentId = continueFrom || recoverFrom;
|
|
105
|
+
const inheritance = buildInheritedContext(root, parentId);
|
|
106
|
+
inheritedContext = inheritance.inherited_context;
|
|
107
|
+
if (inheritance.warnings?.length) {
|
|
108
|
+
for (const w of inheritance.warnings) {
|
|
109
|
+
log(chalk.yellow(` ⚠ Inheritance: ${w}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
const maxTurns = opts.maxTurns || 50;
|
|
95
115
|
const autoApprove = !!opts.autoApprove;
|
|
96
116
|
const verbose = !!opts.verbose;
|
|
@@ -375,6 +395,7 @@ export async function executeGovernedRun(context, opts = {}) {
|
|
|
375
395
|
startNewRunFromBlocked: opts.allowBlockedRestart ?? Boolean(provenance),
|
|
376
396
|
};
|
|
377
397
|
if (provenance) runLoopOpts.provenance = provenance;
|
|
398
|
+
if (inheritedContext) runLoopOpts.inheritedContext = inheritedContext;
|
|
378
399
|
const result = await runLoop(root, config, callbacks, runLoopOpts);
|
|
379
400
|
|
|
380
401
|
// ── Summary ─────────────────────────────────────────────────────────────
|
package/src/commands/status.js
CHANGED
|
@@ -92,6 +92,7 @@ function renderGovernedStatus(context, opts) {
|
|
|
92
92
|
config,
|
|
93
93
|
state,
|
|
94
94
|
provenance: state?.provenance || null,
|
|
95
|
+
inherited_context: state?.inherited_context || null,
|
|
95
96
|
continuity,
|
|
96
97
|
connector_health: connectorHealth,
|
|
97
98
|
workflow_kit_artifacts: workflowKitArtifacts,
|
|
@@ -113,6 +114,9 @@ function renderGovernedStatus(context, opts) {
|
|
|
113
114
|
if (provenanceSummary) {
|
|
114
115
|
console.log(` ${chalk.dim('Origin:')} ${chalk.magenta(provenanceSummary)}`);
|
|
115
116
|
}
|
|
117
|
+
if (state?.inherited_context?.parent_run_id) {
|
|
118
|
+
console.log(` ${chalk.dim('Inherits:')} ${chalk.magenta(`parent ${state.inherited_context.parent_run_id} (${state.inherited_context.parent_status || 'unknown'})`)}`);
|
|
119
|
+
}
|
|
116
120
|
if (state?.accepted_integration_ref) {
|
|
117
121
|
console.log(` ${chalk.dim('Accepted:')} ${state.accepted_integration_ref}`);
|
|
118
122
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync } from 'fs';
|
|
18
18
|
import { join } from 'path';
|
|
19
19
|
import { getActiveTurn, getActiveTurns } from './governed-state.js';
|
|
20
|
+
import { renderInheritedContextMarkdown } from './run-context-inheritance.js';
|
|
20
21
|
import {
|
|
21
22
|
DISPATCH_INDEX_PATH,
|
|
22
23
|
getDispatchAssignmentPath,
|
|
@@ -511,6 +512,16 @@ function renderContext(state, config, root, turn, role) {
|
|
|
511
512
|
}
|
|
512
513
|
lines.push('');
|
|
513
514
|
|
|
515
|
+
// Inherited context from parent run (when --inherit-context was used)
|
|
516
|
+
if (state.inherited_context) {
|
|
517
|
+
// First turn gets the full rendering; subsequent turns get compact
|
|
518
|
+
const isFirstTurn = !state.last_completed_turn_id;
|
|
519
|
+
const inheritedMd = renderInheritedContextMarkdown(state.inherited_context, !isFirstTurn);
|
|
520
|
+
if (inheritedMd) {
|
|
521
|
+
lines.push(inheritedMd);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
514
525
|
// Last accepted turn summary
|
|
515
526
|
if (state.last_completed_turn_id) {
|
|
516
527
|
const lastTurn = readLastHistoryEntry(root, warnings);
|
package/src/lib/export.js
CHANGED
|
@@ -304,6 +304,7 @@ export function buildRunExport(startDir = process.cwd()) {
|
|
|
304
304
|
status: state?.status || null,
|
|
305
305
|
phase: state?.phase || null,
|
|
306
306
|
provenance: normalizeRunProvenance(state?.provenance),
|
|
307
|
+
inherited_context: state?.inherited_context || null,
|
|
307
308
|
active_turn_ids: activeTurns,
|
|
308
309
|
retained_turn_ids: retainedTurns,
|
|
309
310
|
history_entries: countJsonl(files, '.agentxchain/history.jsonl'),
|
|
@@ -1856,6 +1856,7 @@ export function initializeGovernedRun(root, config, options = {}) {
|
|
|
1856
1856
|
remaining_usd: config.budget?.per_run_max_usd ?? null
|
|
1857
1857
|
},
|
|
1858
1858
|
provenance,
|
|
1859
|
+
inherited_context: options.inherited_context || null,
|
|
1859
1860
|
};
|
|
1860
1861
|
|
|
1861
1862
|
writeState(root, updatedState);
|
package/src/lib/report.js
CHANGED
|
@@ -641,6 +641,7 @@ function buildRunSubject(artifact) {
|
|
|
641
641
|
blocked_on: artifact.state?.blocked_on || null,
|
|
642
642
|
blocked_reason: artifact.state?.blocked_reason || null,
|
|
643
643
|
provenance: normalizeRunProvenance(artifact.summary?.provenance || artifact.state?.provenance),
|
|
644
|
+
inherited_context: artifact.summary?.inherited_context || artifact.state?.inherited_context || null,
|
|
644
645
|
active_turn_count: activeTurns.length,
|
|
645
646
|
retained_turn_count: retainedTurns.length,
|
|
646
647
|
active_turn_ids: activeTurns,
|
|
@@ -904,6 +905,9 @@ export function formatGovernanceReportText(report) {
|
|
|
904
905
|
if (summarizeRunProvenance(run.provenance)) {
|
|
905
906
|
lines.push(`Provenance: ${summarizeRunProvenance(run.provenance)}`);
|
|
906
907
|
}
|
|
908
|
+
if (run.inherited_context?.parent_run_id) {
|
|
909
|
+
lines.push(`Inherited from: ${run.inherited_context.parent_run_id} (${run.inherited_context.parent_status || 'unknown'})`);
|
|
910
|
+
}
|
|
907
911
|
|
|
908
912
|
lines.push(
|
|
909
913
|
`History entries: ${artifacts.history_entries}`,
|
|
@@ -1308,6 +1312,9 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1308
1312
|
if (summarizeRunProvenance(run.provenance)) {
|
|
1309
1313
|
lines.push(`- Provenance: \`${summarizeRunProvenance(run.provenance)}\``);
|
|
1310
1314
|
}
|
|
1315
|
+
if (run.inherited_context?.parent_run_id) {
|
|
1316
|
+
lines.push(`- Inherited from: \`${run.inherited_context.parent_run_id}\` (${run.inherited_context.parent_status || 'unknown'})`);
|
|
1317
|
+
}
|
|
1311
1318
|
|
|
1312
1319
|
lines.push(
|
|
1313
1320
|
`- History entries: ${artifacts.history_entries}`,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Context Inheritance — read-only summary bridge from parent to child run.
|
|
3
|
+
*
|
|
4
|
+
* When a child run uses `--inherit-context` with `--continue-from` or
|
|
5
|
+
* `--recover-from`, this module extracts a read-only summary from the
|
|
6
|
+
* selected parent run-history record and attaches it to the child's
|
|
7
|
+
* governed state.
|
|
8
|
+
*
|
|
9
|
+
* The inherited summary is:
|
|
10
|
+
* - read-only (never mutated by the child run)
|
|
11
|
+
* - a summary (not a full replay of the parent)
|
|
12
|
+
* - observable in status --json, report, export, and CONTEXT.md
|
|
13
|
+
*
|
|
14
|
+
* DEC-RUN-CONTEXT-INHERIT-001
|
|
15
|
+
* Spec: .planning/RUN_CONTEXT_INHERITANCE_SPEC.md
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, existsSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
|
|
21
|
+
const RUN_HISTORY_PATH = '.agentxchain/run-history.jsonl';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build an inherited context summary from a parent run.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} root - project root directory
|
|
27
|
+
* @param {string} parentRunId - the parent run_id
|
|
28
|
+
* @returns {{ ok: boolean, inherited_context?: object, warnings?: string[] }}
|
|
29
|
+
*/
|
|
30
|
+
export function buildInheritedContext(root, parentRunId) {
|
|
31
|
+
const warnings = [];
|
|
32
|
+
|
|
33
|
+
// 1. Read parent run-history entry
|
|
34
|
+
const parentEntry = findRunHistoryEntry(root, parentRunId);
|
|
35
|
+
if (!parentEntry) {
|
|
36
|
+
warnings.push(`Parent run ${parentRunId} not found in run-history.jsonl`);
|
|
37
|
+
return {
|
|
38
|
+
ok: true,
|
|
39
|
+
inherited_context: buildPartialContext(parentRunId, null, [], [], warnings),
|
|
40
|
+
warnings,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const snapshot = parentEntry.inheritance_snapshot;
|
|
45
|
+
const recentDecisions = Array.isArray(snapshot?.recent_decisions) ? snapshot.recent_decisions : [];
|
|
46
|
+
const acceptedTurns = Array.isArray(snapshot?.recent_accepted_turns) ? snapshot.recent_accepted_turns : [];
|
|
47
|
+
|
|
48
|
+
if (!snapshot) {
|
|
49
|
+
warnings.push('Parent run has no inheritance snapshot in run-history.jsonl — inherited context is metadata only');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const inherited_context = {
|
|
53
|
+
schema_version: '0.1',
|
|
54
|
+
parent_run_id: parentRunId,
|
|
55
|
+
parent_status: parentEntry.status,
|
|
56
|
+
parent_completed_at: parentEntry.completed_at || null,
|
|
57
|
+
parent_phases_completed: parentEntry.phases_completed || [],
|
|
58
|
+
parent_roles_used: parentEntry.roles_used || [],
|
|
59
|
+
parent_blocked_reason: parentEntry.blocked_reason || null,
|
|
60
|
+
recent_decisions: recentDecisions,
|
|
61
|
+
recent_accepted_turns: acceptedTurns,
|
|
62
|
+
inherited_at: new Date().toISOString(),
|
|
63
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return { ok: true, inherited_context, warnings };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render the inherited context as a markdown section for CONTEXT.md.
|
|
71
|
+
*/
|
|
72
|
+
export function renderInheritedContextMarkdown(inheritedContext, compact = false) {
|
|
73
|
+
if (!inheritedContext) return '';
|
|
74
|
+
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push('## Inherited Run Context');
|
|
77
|
+
lines.push('');
|
|
78
|
+
lines.push('> **This is a fresh run, not a resumed parent.** The context below is a read-only summary from the parent run to provide continuity.');
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push(`- **Parent run:** ${inheritedContext.parent_run_id}`);
|
|
81
|
+
lines.push(`- **Parent status:** ${inheritedContext.parent_status || 'unknown'}`);
|
|
82
|
+
if (inheritedContext.parent_completed_at) {
|
|
83
|
+
lines.push(`- **Completed at:** ${inheritedContext.parent_completed_at}`);
|
|
84
|
+
}
|
|
85
|
+
if (inheritedContext.parent_blocked_reason) {
|
|
86
|
+
lines.push(`- **Blocked reason:** ${inheritedContext.parent_blocked_reason}`);
|
|
87
|
+
}
|
|
88
|
+
if (inheritedContext.parent_phases_completed?.length) {
|
|
89
|
+
lines.push(`- **Phases completed:** ${inheritedContext.parent_phases_completed.join(', ')}`);
|
|
90
|
+
}
|
|
91
|
+
if (inheritedContext.parent_roles_used?.length) {
|
|
92
|
+
lines.push(`- **Roles used:** ${inheritedContext.parent_roles_used.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
lines.push('');
|
|
95
|
+
|
|
96
|
+
if (!compact && inheritedContext.recent_decisions?.length) {
|
|
97
|
+
lines.push('### Recent Decisions');
|
|
98
|
+
lines.push('');
|
|
99
|
+
for (const d of inheritedContext.recent_decisions) {
|
|
100
|
+
const id = d.id || '(no id)';
|
|
101
|
+
const stmt = d.statement || '(no statement)';
|
|
102
|
+
lines.push(`- **${id}:** ${stmt}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!compact && inheritedContext.recent_accepted_turns?.length) {
|
|
108
|
+
lines.push('### Recent Accepted Turns');
|
|
109
|
+
lines.push('');
|
|
110
|
+
for (const t of inheritedContext.recent_accepted_turns) {
|
|
111
|
+
const role = t.role || '(unknown)';
|
|
112
|
+
const summary = t.summary || '(no summary)';
|
|
113
|
+
lines.push(`- **${role}** (${t.turn_id || '?'}): ${summary}`);
|
|
114
|
+
}
|
|
115
|
+
lines.push('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (inheritedContext.warnings?.length) {
|
|
119
|
+
lines.push('### Inheritance Warnings');
|
|
120
|
+
lines.push('');
|
|
121
|
+
for (const w of inheritedContext.warnings) {
|
|
122
|
+
lines.push(`- ⚠ ${w}`);
|
|
123
|
+
}
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Internal ────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function findRunHistoryEntry(root, runId) {
|
|
133
|
+
const filePath = join(root, RUN_HISTORY_PATH);
|
|
134
|
+
if (!existsSync(filePath)) return null;
|
|
135
|
+
try {
|
|
136
|
+
const content = readFileSync(filePath, 'utf8').trim();
|
|
137
|
+
if (!content) return null;
|
|
138
|
+
const entries = content.split('\n').filter(Boolean).map(line => {
|
|
139
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
140
|
+
}).filter(Boolean);
|
|
141
|
+
return entries.find(e => e.run_id === runId) || null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildPartialContext(parentRunId, parentEntry, decisions, turns, warnings) {
|
|
148
|
+
return {
|
|
149
|
+
schema_version: '0.1',
|
|
150
|
+
parent_run_id: parentRunId,
|
|
151
|
+
parent_status: parentEntry?.status || null,
|
|
152
|
+
parent_completed_at: parentEntry?.completed_at || null,
|
|
153
|
+
parent_phases_completed: parentEntry?.phases_completed || [],
|
|
154
|
+
parent_roles_used: parentEntry?.roles_used || [],
|
|
155
|
+
parent_blocked_reason: parentEntry?.blocked_reason || null,
|
|
156
|
+
recent_decisions: decisions,
|
|
157
|
+
recent_accepted_turns: turns,
|
|
158
|
+
inherited_at: new Date().toISOString(),
|
|
159
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
package/src/lib/run-history.js
CHANGED
|
@@ -16,6 +16,8 @@ const HISTORY_PATH = '.agentxchain/history.jsonl';
|
|
|
16
16
|
const LEDGER_PATH = '.agentxchain/decision-ledger.jsonl';
|
|
17
17
|
const SCHEMA_VERSION = '0.1';
|
|
18
18
|
const WRITABLE_TERMINAL_STATUSES = new Set(['completed', 'blocked']);
|
|
19
|
+
const MAX_INHERITANCE_DECISIONS = 5;
|
|
20
|
+
const MAX_INHERITANCE_TURNS = 3;
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Record a run's summary into the persistent run-history ledger.
|
|
@@ -81,6 +83,10 @@ export function recordRunHistory(root, state, config, status) {
|
|
|
81
83
|
connector_used: connectorUsed,
|
|
82
84
|
model_used: modelUsed,
|
|
83
85
|
provenance: normalizeRunProvenance(state?.provenance),
|
|
86
|
+
inheritance_snapshot: {
|
|
87
|
+
recent_decisions: buildRecentDecisionSnapshot(ledgerEntries),
|
|
88
|
+
recent_accepted_turns: buildRecentAcceptedTurnSnapshot(historyEntries),
|
|
89
|
+
},
|
|
84
90
|
recorded_at: new Date().toISOString(),
|
|
85
91
|
};
|
|
86
92
|
|
|
@@ -243,6 +249,21 @@ export function validateParentRun(root, runId) {
|
|
|
243
249
|
return { ok: true, entry };
|
|
244
250
|
}
|
|
245
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Check whether a run-history entry has a usable inheritance snapshot
|
|
254
|
+
* (at least one decision or one accepted turn available for child runs).
|
|
255
|
+
*
|
|
256
|
+
* @param {object} entry - a run-history record
|
|
257
|
+
* @returns {boolean}
|
|
258
|
+
*/
|
|
259
|
+
export function isInheritable(entry) {
|
|
260
|
+
const snap = entry?.inheritance_snapshot;
|
|
261
|
+
if (!snap) return false;
|
|
262
|
+
const hasDecisions = Array.isArray(snap.recent_decisions) && snap.recent_decisions.length > 0;
|
|
263
|
+
const hasTurns = Array.isArray(snap.recent_accepted_turns) && snap.recent_accepted_turns.length > 0;
|
|
264
|
+
return hasDecisions || hasTurns;
|
|
265
|
+
}
|
|
266
|
+
|
|
246
267
|
/**
|
|
247
268
|
* Get the path to the run-history file.
|
|
248
269
|
*/
|
|
@@ -265,3 +286,26 @@ function readJsonlSafe(root, relPath) {
|
|
|
265
286
|
return [];
|
|
266
287
|
}
|
|
267
288
|
}
|
|
289
|
+
|
|
290
|
+
function buildRecentDecisionSnapshot(entries) {
|
|
291
|
+
return entries
|
|
292
|
+
.slice(-MAX_INHERITANCE_DECISIONS)
|
|
293
|
+
.map((entry) => ({
|
|
294
|
+
id: entry.id || entry.decision_id || null,
|
|
295
|
+
statement: entry.statement || entry.description || entry.text || null,
|
|
296
|
+
decided_by: entry.decided_by || entry.role || null,
|
|
297
|
+
phase: entry.phase || null,
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function buildRecentAcceptedTurnSnapshot(entries) {
|
|
302
|
+
return entries
|
|
303
|
+
.filter((entry) => entry.status === 'accepted')
|
|
304
|
+
.slice(-MAX_INHERITANCE_TURNS)
|
|
305
|
+
.map((entry) => ({
|
|
306
|
+
turn_id: entry.turn_id || null,
|
|
307
|
+
role: entry.role || null,
|
|
308
|
+
summary: entry.summary || null,
|
|
309
|
+
phase: entry.phase || null,
|
|
310
|
+
}));
|
|
311
|
+
}
|
package/src/lib/run-loop.js
CHANGED
|
@@ -62,6 +62,9 @@ export async function runLoop(root, config, callbacks, options = {}) {
|
|
|
62
62
|
const shouldRestartBlocked = state?.status === 'blocked' && options.startNewRunFromBlocked === true;
|
|
63
63
|
if (!state || state.status === 'idle' || shouldRestartCompleted || shouldRestartBlocked) {
|
|
64
64
|
const initOpts = options.provenance ? { provenance: options.provenance } : {};
|
|
65
|
+
if (options.inheritedContext) {
|
|
66
|
+
initOpts.inherited_context = options.inheritedContext;
|
|
67
|
+
}
|
|
65
68
|
if (shouldRestartCompleted || shouldRestartBlocked) {
|
|
66
69
|
initOpts.allow_terminal_restart = true;
|
|
67
70
|
}
|