opencastle 0.22.0 → 0.23.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.
Files changed (63) hide show
  1. package/dist/cli/convoy/engine.d.ts +1 -0
  2. package/dist/cli/convoy/engine.d.ts.map +1 -1
  3. package/dist/cli/convoy/engine.js +1 -0
  4. package/dist/cli/convoy/engine.js.map +1 -1
  5. package/dist/cli/convoy/export.d.ts +1 -0
  6. package/dist/cli/convoy/export.d.ts.map +1 -1
  7. package/dist/cli/convoy/export.js +34 -0
  8. package/dist/cli/convoy/export.js.map +1 -1
  9. package/dist/cli/convoy/pipeline.d.ts +35 -0
  10. package/dist/cli/convoy/pipeline.d.ts.map +1 -0
  11. package/dist/cli/convoy/pipeline.js +353 -0
  12. package/dist/cli/convoy/pipeline.js.map +1 -0
  13. package/dist/cli/convoy/pipeline.test.d.ts +2 -0
  14. package/dist/cli/convoy/pipeline.test.d.ts.map +1 -0
  15. package/dist/cli/convoy/pipeline.test.js +778 -0
  16. package/dist/cli/convoy/pipeline.test.js.map +1 -0
  17. package/dist/cli/convoy/store.d.ts +14 -2
  18. package/dist/cli/convoy/store.d.ts.map +1 -1
  19. package/dist/cli/convoy/store.js +84 -5
  20. package/dist/cli/convoy/store.js.map +1 -1
  21. package/dist/cli/convoy/store.test.js +216 -7
  22. package/dist/cli/convoy/store.test.js.map +1 -1
  23. package/dist/cli/convoy/types.d.ts +15 -0
  24. package/dist/cli/convoy/types.d.ts.map +1 -1
  25. package/dist/cli/dashboard.d.ts.map +1 -1
  26. package/dist/cli/dashboard.js +1 -0
  27. package/dist/cli/dashboard.js.map +1 -1
  28. package/dist/cli/init.d.ts.map +1 -1
  29. package/dist/cli/init.js +8 -1
  30. package/dist/cli/init.js.map +1 -1
  31. package/dist/cli/run/schema.d.ts +5 -1
  32. package/dist/cli/run/schema.d.ts.map +1 -1
  33. package/dist/cli/run/schema.js +41 -8
  34. package/dist/cli/run/schema.js.map +1 -1
  35. package/dist/cli/run/schema.test.js +194 -5
  36. package/dist/cli/run/schema.test.js.map +1 -1
  37. package/dist/cli/run.d.ts.map +1 -1
  38. package/dist/cli/run.js +143 -3
  39. package/dist/cli/run.js.map +1 -1
  40. package/dist/cli/types.d.ts +3 -1
  41. package/dist/cli/types.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/cli/convoy/engine.ts +2 -0
  44. package/src/cli/convoy/export.ts +41 -0
  45. package/src/cli/convoy/pipeline.test.ts +939 -0
  46. package/src/cli/convoy/pipeline.ts +430 -0
  47. package/src/cli/convoy/store.test.ts +239 -7
  48. package/src/cli/convoy/store.ts +110 -7
  49. package/src/cli/convoy/types.ts +17 -0
  50. package/src/cli/dashboard.ts +1 -0
  51. package/src/cli/init.ts +9 -1
  52. package/src/cli/run/schema.test.ts +244 -5
  53. package/src/cli/run/schema.ts +49 -8
  54. package/src/cli/run.ts +142 -3
  55. package/src/cli/types.ts +3 -1
  56. package/src/dashboard/dist/_astro/{index.DyyaCW8L.css → index.Cq68OHaZ.css} +1 -1
  57. package/src/dashboard/dist/index.html +214 -2
  58. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  59. package/src/dashboard/src/pages/index.astro +230 -1
  60. package/src/dashboard/src/styles/dashboard.css +116 -0
  61. package/src/orchestrator/customizations/KNOWN-ISSUES.md +1 -1
  62. package/src/orchestrator/skills/decomposition/SKILL.md +1 -0
  63. package/src/orchestrator/skills/orchestration-protocols/SKILL.md +32 -1
@@ -27,8 +27,9 @@ const base = import.meta.env.BASE_URL;
27
27
  <nav class="dash-sidebar" id="dash-sidebar">
28
28
  <ul class="dash-sidebar__list">
29
29
  <li><a class="dash-sidebar__link" href="#convoy-section" data-section="convoy-section">Convoy</a></li>
30
+ <li><a class="dash-sidebar__link" href="#convoy-pipeline-section" data-section="convoy-pipeline-section">Convoy Chain</a></li>
30
31
  <li><a class="dash-sidebar__link dash-sidebar__link--active" href="#kpi-row" data-section="kpi-row">Overview</a></li>
31
- <li><a class="dash-sidebar__link" href="#pipeline-section" data-section="pipeline-section">Pipeline</a></li>
32
+ <li><a class="dash-sidebar__link" href="#pipeline-section" data-section="pipeline-section">Task Flow</a></li>
32
33
  <li><a class="dash-sidebar__link" href="#agent-section" data-section="agent-section">Agents</a></li>
33
34
  <li><a class="dash-sidebar__link" href="#tier-section" data-section="tier-section">Tiers</a></li>
34
35
  <li><a class="dash-sidebar__link" href="#delegation-section" data-section="delegation-section">Delegations</a></li>
@@ -73,6 +74,12 @@ const base = import.meta.env.BASE_URL;
73
74
  <option value="">All convoys</option>
74
75
  </select>
75
76
  </div>
77
+ <div class="filter-group">
78
+ <label class="filter-label" for="filter-pipeline">Pipeline</label>
79
+ <select class="filter-select" id="filter-pipeline">
80
+ <option value="">All</option>
81
+ </select>
82
+ </div>
76
83
  <button class="dash-btn dash-btn--ghost filter-reset" id="filter-reset" type="button">Reset</button>
77
84
  </div>
78
85
 
@@ -86,6 +93,16 @@ const base = import.meta.env.BASE_URL;
86
93
  </div>
87
94
  </section>
88
95
 
96
+ <!-- Convoy Pipeline (Chaining) Section -->
97
+ <section class="chart-card" id="convoy-pipeline-section" data-nav-section style="display:none">
98
+ <div class="chart-card__header">
99
+ <h2 class="chart-card__title">Convoy Pipeline</h2>
100
+ <p class="chart-card__desc" id="convoy-pipeline-desc">Pipeline convoy chain progress</p>
101
+ </div>
102
+ <div class="chart-card__body" id="convoy-pipeline-body">
103
+ </div>
104
+ </section>
105
+
89
106
  <!-- KPI Row -->
90
107
  <section class="kpi-row" id="kpi-row" data-nav-section>
91
108
  <div class="kpi-card" id="kpi-sessions">
@@ -1178,6 +1195,7 @@ const base = import.meta.env.BASE_URL;
1178
1195
  let rawPanels = [];
1179
1196
  let rawReviews = [];
1180
1197
  let rawConvoys = [];
1198
+ let rawPipelines = [];
1181
1199
 
1182
1200
  function applyFilters() {
1183
1201
  const dateFrom = document.getElementById('filter-date-from').value;
@@ -1185,6 +1203,7 @@ const base = import.meta.env.BASE_URL;
1185
1203
  const agentFilter = document.getElementById('filter-agent').value;
1186
1204
  const outcomeFilter = document.getElementById('filter-outcome').value;
1187
1205
  const convoyFilter = document.getElementById('filter-convoy').value;
1206
+ const pipelineFilter = document.getElementById('filter-pipeline')?.value || '';
1188
1207
 
1189
1208
  function matchDate(ts) {
1190
1209
  const date = ts.slice(0, 10);
@@ -1214,6 +1233,18 @@ const base = import.meta.env.BASE_URL;
1214
1233
  return true;
1215
1234
  });
1216
1235
 
1236
+ // Pipeline filter: restrict events to convoy_ids within the selected pipeline
1237
+ if (pipelineFilter) {
1238
+ const activePipeline = rawPipelines.find((p) => p.id === pipelineFilter);
1239
+ const pipelineConvoyIds = new Set((activePipeline && activePipeline.convoy_ids) || []);
1240
+ if (pipelineConvoyIds.size > 0) {
1241
+ sessions = sessions.filter((s) => !s.convoy_id || pipelineConvoyIds.has(s.convoy_id));
1242
+ delegations = delegations.filter((d) => !d.convoy_id || pipelineConvoyIds.has(d.convoy_id));
1243
+ panels = panels.filter((p2) => !p2.convoy_id || pipelineConvoyIds.has(p2.convoy_id));
1244
+ reviews = reviews.filter((r) => !r.convoy_id || pipelineConvoyIds.has(r.convoy_id));
1245
+ }
1246
+ }
1247
+
1217
1248
  if (convoyFilter) {
1218
1249
  sessions = sessions.filter((s) => s.convoy_id === convoyFilter);
1219
1250
  delegations = delegations.filter((d) => d.convoy_id === convoyFilter);
@@ -1230,6 +1261,17 @@ const base = import.meta.env.BASE_URL;
1230
1261
  }
1231
1262
  }
1232
1263
 
1264
+ // Show/hide convoy pipeline section based on pipeline filter
1265
+ const pipelineSectionEl = document.getElementById('convoy-pipeline-section');
1266
+ if (pipelineSectionEl) {
1267
+ if (pipelineFilter) {
1268
+ const activePipeline = rawPipelines.find((p) => p.id === pipelineFilter);
1269
+ renderConvoyPipeline(activePipeline, rawConvoys);
1270
+ } else {
1271
+ pipelineSectionEl.style.display = 'none';
1272
+ }
1273
+ }
1274
+
1233
1275
  renderAll(sessions, delegations, panels, reviews);
1234
1276
  }
1235
1277
 
@@ -1416,9 +1458,186 @@ const base = import.meta.env.BASE_URL;
1416
1458
  bodyEl.innerHTML = html;
1417
1459
  }
1418
1460
 
1461
+ // ── Pipeline Filter Population ───────────────────────────
1462
+
1463
+ function populatePipelineFilter(pipelines) {
1464
+ const select = document.getElementById('filter-pipeline');
1465
+ if (!select) return;
1466
+ while (select.options.length > 1) select.remove(1);
1467
+ const sorted = pipelines.slice().sort((a, b) =>
1468
+ (b.created_at || '').localeCompare(a.created_at || '')
1469
+ );
1470
+ sorted.forEach((p) => {
1471
+ const opt = document.createElement('option');
1472
+ opt.value = p.id;
1473
+ opt.textContent = (p.name || p.id) + ' (' + (p.status || 'unknown') + ')';
1474
+ select.appendChild(opt);
1475
+ });
1476
+ }
1477
+
1478
+ // ── Convoy Pipeline (Chaining) Render ────────────────────
1479
+
1480
+ function renderConvoyPipeline(pipeline, convoys) {
1481
+ const sectionEl = document.getElementById('convoy-pipeline-section');
1482
+ const descEl = document.getElementById('convoy-pipeline-desc');
1483
+ const bodyEl = document.getElementById('convoy-pipeline-body');
1484
+ if (!sectionEl || !bodyEl) return;
1485
+
1486
+ if (!pipeline) {
1487
+ sectionEl.style.display = 'none';
1488
+ return;
1489
+ }
1490
+
1491
+ sectionEl.style.display = '';
1492
+ if (descEl) {
1493
+ descEl.textContent =
1494
+ (pipeline.name || pipeline.id) + ' \u2014 ' + (pipeline.branch || 'no branch');
1495
+ }
1496
+
1497
+ const convoyIds = pipeline.convoy_ids || [];
1498
+ const pipelineConvoys = convoyIds
1499
+ .map((id) => convoys.find((c) => c.id === id))
1500
+ .filter(Boolean);
1501
+
1502
+ const total = pipelineConvoys.length;
1503
+ const done = pipelineConvoys.filter((c) => c.status === 'done').length;
1504
+ const failed = pipelineConvoys.filter(
1505
+ (c) => c.status === 'failed' || c.status === 'gate-failed'
1506
+ ).length;
1507
+ const totalTasks = pipelineConvoys.reduce((sum, c) => {
1508
+ const s = c.summary || {};
1509
+ return sum + (s.total || (c.tasks ? c.tasks.length : 0));
1510
+ }, 0);
1511
+ const doneTasks = pipelineConvoys.reduce((sum, c) => {
1512
+ const s = c.summary || {};
1513
+ return sum + (s.done || 0);
1514
+ }, 0);
1515
+ const totalTokens = pipelineConvoys.reduce((sum, c) => sum + (c.total_tokens || 0), 0);
1516
+
1517
+ const pct =
1518
+ totalTasks > 0
1519
+ ? Math.round((doneTasks / totalTasks) * 100)
1520
+ : total > 0
1521
+ ? Math.round((done / total) * 100)
1522
+ : 0;
1523
+
1524
+ const statusClass =
1525
+ pipeline.status === 'done'
1526
+ ? 'success'
1527
+ : pipeline.status === 'failed' || pipeline.status === 'gate-failed'
1528
+ ? 'failed'
1529
+ : pipeline.status === 'running'
1530
+ ? 'partial'
1531
+ : '';
1532
+
1533
+ let html = '<div class="convoy-overview">';
1534
+ html +=
1535
+ '<div class="convoy-stat"><span class="convoy-stat__label">Status</span>' +
1536
+ '<span class="outcome-badge outcome-badge--' + statusClass + '">' +
1537
+ escapeHtml(pipeline.status || 'unknown') + '</span></div>';
1538
+ html +=
1539
+ '<div class="convoy-stat"><span class="convoy-stat__label">Branch</span>' +
1540
+ '<span class="convoy-stat__value">' + escapeHtml(pipeline.branch || '\u2014') + '</span></div>';
1541
+ html +=
1542
+ '<div class="convoy-stat"><span class="convoy-stat__label">Convoys</span>' +
1543
+ '<span class="convoy-stat__value">' + done + '/' + total + '</span></div>';
1544
+ if (totalTasks > 0) {
1545
+ html +=
1546
+ '<div class="convoy-stat"><span class="convoy-stat__label">Tasks</span>' +
1547
+ '<span class="convoy-stat__value">' + doneTasks + '/' + totalTasks + '</span></div>';
1548
+ }
1549
+ if (totalTokens > 0) {
1550
+ html +=
1551
+ '<div class="convoy-stat"><span class="convoy-stat__label">Tokens</span>' +
1552
+ '<span class="convoy-stat__value">' + formatTokens(totalTokens) + '</span></div>';
1553
+ }
1554
+ html +=
1555
+ '<div class="convoy-stat"><span class="convoy-stat__label">Started</span>' +
1556
+ '<span class="convoy-stat__value">' +
1557
+ (pipeline.started_at ? formatTime(pipeline.started_at) : '\u2014') + '</span></div>';
1558
+ if (pipeline.finished_at) {
1559
+ html +=
1560
+ '<div class="convoy-stat"><span class="convoy-stat__label">Finished</span>' +
1561
+ '<span class="convoy-stat__value">' + formatTime(pipeline.finished_at) + '</span></div>';
1562
+ }
1563
+ html += '</div>';
1564
+
1565
+ // Convoy chain visualization
1566
+ html += '<div class="convoy-chain">';
1567
+ pipelineConvoys.forEach((convoy, i) => {
1568
+ const cs = convoy.summary || {};
1569
+ const cDone = cs.done || 0;
1570
+ const cTotal = cs.total || (convoy.tasks ? convoy.tasks.length : 0);
1571
+ const cTokens = convoy.total_tokens || 0;
1572
+ const isActive =
1573
+ (pipeline.current_convoy_id && pipeline.current_convoy_id === convoy.id) ||
1574
+ convoy.status === 'running';
1575
+ const nodeStatusClass =
1576
+ convoy.status === 'done'
1577
+ ? 'done'
1578
+ : convoy.status === 'failed' || convoy.status === 'gate-failed'
1579
+ ? 'failed'
1580
+ : isActive
1581
+ ? 'active'
1582
+ : 'pending';
1583
+ const badgeClass =
1584
+ convoy.status === 'done'
1585
+ ? 'success'
1586
+ : convoy.status === 'failed' || convoy.status === 'gate-failed'
1587
+ ? 'failed'
1588
+ : convoy.status === 'running'
1589
+ ? 'partial'
1590
+ : '';
1591
+
1592
+ if (i > 0) {
1593
+ html += '<div class="convoy-chain__connector">\u2192</div>';
1594
+ }
1595
+ html +=
1596
+ '<div class="convoy-chain__node convoy-chain__node--' + nodeStatusClass +
1597
+ '" data-convoy-id="' + escapeHtml(convoy.id) + '" title="Click to filter to this convoy">';
1598
+ html += '<div class="convoy-chain__node-name">' + escapeHtml(convoy.name || convoy.id) + '</div>';
1599
+ html +=
1600
+ '<span class="outcome-badge outcome-badge--' + badgeClass + '">' +
1601
+ escapeHtml(convoy.status) + '</span>';
1602
+ if (cTotal > 0) {
1603
+ html += '<div class="convoy-chain__node-meta">' + cDone + '/' + cTotal + ' tasks</div>';
1604
+ }
1605
+ if (cTokens > 0) {
1606
+ html += '<div class="convoy-chain__node-meta">' + formatTokens(cTokens) + ' tokens</div>';
1607
+ }
1608
+ html += '</div>';
1609
+ });
1610
+ html += '</div>';
1611
+
1612
+ // Progress bar
1613
+ html += '<div class="convoy-progress">';
1614
+ html +=
1615
+ '<div class="convoy-progress__bar">' +
1616
+ '<div class="convoy-progress__fill" style="width:' + pct + '%"></div></div>';
1617
+ html +=
1618
+ '<span class="convoy-progress__label">' + pct + '% complete' +
1619
+ (failed > 0 ? ' \u00B7 ' + failed + ' failed' : '') + '</span>';
1620
+ html += '</div>';
1621
+
1622
+ bodyEl.innerHTML = html;
1623
+
1624
+ // Click handlers for convoy drill-down
1625
+ bodyEl.querySelectorAll('.convoy-chain__node').forEach((node) => {
1626
+ node.addEventListener('click', () => {
1627
+ const convoyId = node.dataset.convoyId;
1628
+ const sel = document.getElementById('filter-convoy');
1629
+ if (sel && convoyId) {
1630
+ sel.value = convoyId;
1631
+ applyFilters();
1632
+ }
1633
+ });
1634
+ });
1635
+ }
1636
+
1419
1637
  async function main() {
1420
1638
  const events = await loadNdjson(base + 'data/events.ndjson');
1421
1639
  const convoys = await loadNdjson(base + 'data/convoys.ndjson');
1640
+ const pipelines = await loadNdjson(base + 'data/pipelines.ndjson');
1422
1641
 
1423
1642
  const sessions = events.filter((e) => e.type === 'session');
1424
1643
  const delegations = events.filter((e) => e.type === 'delegation');
@@ -1430,9 +1649,11 @@ const base = import.meta.env.BASE_URL;
1430
1649
  rawPanels = panels;
1431
1650
  rawReviews = reviews;
1432
1651
  rawConvoys = convoys;
1652
+ rawPipelines = pipelines;
1433
1653
 
1434
1654
  populateAgentFilter(sessions, delegations, reviews);
1435
1655
  populateConvoyFilter(convoys);
1656
+ populatePipelineFilter(pipelines);
1436
1657
 
1437
1658
  // ── Read URL params ───────────────────────────────────
1438
1659
  const urlParams = new URLSearchParams(window.location.search);
@@ -1461,12 +1682,14 @@ const base = import.meta.env.BASE_URL;
1461
1682
  document.getElementById('filter-agent')?.addEventListener('change', applyFilters);
1462
1683
  document.getElementById('filter-outcome')?.addEventListener('change', applyFilters);
1463
1684
  document.getElementById('filter-convoy')?.addEventListener('change', applyFilters);
1685
+ document.getElementById('filter-pipeline')?.addEventListener('change', applyFilters);
1464
1686
  document.getElementById('filter-reset')?.addEventListener('click', () => {
1465
1687
  document.getElementById('filter-date-from').value = '';
1466
1688
  document.getElementById('filter-date-to').value = '';
1467
1689
  document.getElementById('filter-agent').value = '';
1468
1690
  document.getElementById('filter-outcome').value = '';
1469
1691
  document.getElementById('filter-convoy').value = '';
1692
+ document.getElementById('filter-pipeline').value = '';
1470
1693
  applyFilters();
1471
1694
  });
1472
1695
 
@@ -1477,15 +1700,21 @@ const base = import.meta.env.BASE_URL;
1477
1700
  refreshInterval = setInterval(async () => {
1478
1701
  const freshEvents = await loadNdjson(base + 'data/events.ndjson');
1479
1702
  const freshConvoys = await loadNdjson(base + 'data/convoys.ndjson');
1703
+ const freshPipelines = await loadNdjson(base + 'data/pipelines.ndjson');
1480
1704
  rawSessions = freshEvents.filter((e) => e.type === 'session');
1481
1705
  rawDelegations = freshEvents.filter((e) => e.type === 'delegation');
1482
1706
  rawPanels = freshEvents.filter((e) => e.type === 'panel');
1483
1707
  rawReviews = freshEvents.filter((e) => e.type === 'review');
1484
1708
  rawConvoys = freshConvoys;
1709
+ rawPipelines = freshPipelines;
1485
1710
  const currentValue = document.getElementById('filter-convoy')?.value;
1711
+ const currentPipelineValue = document.getElementById('filter-pipeline')?.value;
1486
1712
  populateConvoyFilter(freshConvoys);
1713
+ populatePipelineFilter(freshPipelines);
1487
1714
  const sel = document.getElementById('filter-convoy');
1488
1715
  if (sel && currentValue) sel.value = currentValue;
1716
+ const pSel = document.getElementById('filter-pipeline');
1717
+ if (pSel && currentPipelineValue) pSel.value = currentPipelineValue;
1489
1718
  applyFilters();
1490
1719
  }, 5000);
1491
1720
  }
@@ -1593,3 +1593,119 @@ body {
1593
1593
  .convoy-tasks {
1594
1594
  margin-top: 8px;
1595
1595
  }
1596
+
1597
+ /* ---------- Convoy Chain / Pipeline Section ---------- */
1598
+ .convoy-chain {
1599
+ display: flex;
1600
+ align-items: stretch;
1601
+ gap: 0;
1602
+ overflow-x: auto;
1603
+ padding: 1rem 0 1.5rem;
1604
+ scrollbar-width: thin;
1605
+ scrollbar-color: var(--border-color) transparent;
1606
+ }
1607
+
1608
+ .convoy-chain::-webkit-scrollbar {
1609
+ height: 4px;
1610
+ }
1611
+
1612
+ .convoy-chain::-webkit-scrollbar-track {
1613
+ background: transparent;
1614
+ }
1615
+
1616
+ .convoy-chain::-webkit-scrollbar-thumb {
1617
+ background: var(--border-color);
1618
+ border-radius: 2px;
1619
+ }
1620
+
1621
+ .convoy-chain__connector {
1622
+ display: flex;
1623
+ align-items: center;
1624
+ padding: 0 0.5rem;
1625
+ color: var(--text-tertiary);
1626
+ font-size: 1.1rem;
1627
+ flex-shrink: 0;
1628
+ }
1629
+
1630
+ .convoy-chain__node {
1631
+ display: flex;
1632
+ flex-direction: column;
1633
+ align-items: center;
1634
+ gap: 6px;
1635
+ padding: 12px 16px;
1636
+ background: var(--bg-tertiary);
1637
+ border: 1px solid var(--border-color);
1638
+ border-radius: 10px;
1639
+ min-width: 140px;
1640
+ cursor: pointer;
1641
+ transition:
1642
+ background var(--transition-fast),
1643
+ border-color var(--transition-fast),
1644
+ transform var(--transition-fast),
1645
+ box-shadow var(--transition-fast);
1646
+ flex-shrink: 0;
1647
+ }
1648
+
1649
+ .convoy-chain__node:hover {
1650
+ background: var(--bg-card-hover);
1651
+ transform: translateY(-2px);
1652
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
1653
+ }
1654
+
1655
+ .convoy-chain__node-name {
1656
+ font-size: 0.8rem;
1657
+ font-weight: 600;
1658
+ color: var(--text-primary);
1659
+ text-align: center;
1660
+ word-break: break-word;
1661
+ max-width: 120px;
1662
+ }
1663
+
1664
+ .convoy-chain__node-meta {
1665
+ font-size: 0.72rem;
1666
+ color: var(--text-tertiary);
1667
+ text-align: center;
1668
+ }
1669
+
1670
+ .convoy-chain__node--active {
1671
+ border-color: var(--accent-purple);
1672
+ box-shadow: 0 0 0 1px var(--accent-purple), 0 0 12px rgba(167, 139, 250, 0.2);
1673
+ animation: convoy-pulse 2s ease-in-out infinite;
1674
+ }
1675
+
1676
+ .convoy-chain__node--done {
1677
+ border-color: rgba(34, 197, 94, 0.3);
1678
+ }
1679
+
1680
+ .convoy-chain__node--failed {
1681
+ border-color: rgba(239, 68, 68, 0.3);
1682
+ }
1683
+
1684
+ .convoy-chain__node--pending {
1685
+ opacity: 0.6;
1686
+ }
1687
+
1688
+ @keyframes convoy-pulse {
1689
+ 0%, 100% {
1690
+ box-shadow: 0 0 0 1px var(--accent-purple), 0 0 8px rgba(167, 139, 250, 0.15);
1691
+ }
1692
+ 50% {
1693
+ box-shadow: 0 0 0 1px var(--accent-purple), 0 0 18px rgba(167, 139, 250, 0.35);
1694
+ }
1695
+ }
1696
+
1697
+ @media (max-width: 768px) {
1698
+ .convoy-chain {
1699
+ flex-wrap: wrap;
1700
+ gap: 8px;
1701
+ }
1702
+
1703
+ .convoy-chain__connector {
1704
+ display: none;
1705
+ }
1706
+
1707
+ .convoy-chain__node {
1708
+ flex: 1 1 calc(50% - 4px);
1709
+ min-width: 120px;
1710
+ }
1711
+ }
@@ -14,7 +14,7 @@ Tracked issues, limitations, and accepted risks discovered during agent sessions
14
14
 
15
15
  | Issue ID | Status | Severity | Summary | Evidence | Root Cause | Solution Options |
16
16
  |----------|--------|----------|---------|----------|------------|------------------|
17
- | KI-001 | Open | Medium | _Example: Description of the issue_ | _Link or description of where/how the issue manifests_ | _Why it happens_ | _Possible fixes or workarounds_ |
17
+ | KI-001 | Open | Medium | Convoy engine run()/resume() don't catch unexpected errors from runConvoy() — convoy DB records can get stuck in 'running' status | `src/cli/convoy/engine.ts` lines 452-510 (run) and 520-570 (resume): if `runConvoy()` throws, the convoy record is never updated to 'failed' | The try/finally block exports and closes the store but doesn't catch to update convoy status | Add a catch block before finally that calls `store.updateConvoyStatus(convoyId, 'failed', ...)` before rethrowing |
18
18
 
19
19
  ### Status Values
20
20
 
@@ -47,6 +47,7 @@ What to build/change and why. 1-3 sentences max.
47
47
  ### Context
48
48
  - Key files to read first: [list]
49
49
  - Related patterns to follow: [file:line references]
50
+ - Prior phase output (compacted): [summary from Context Compaction protocol if this task depends on a prior phase]
50
51
  - Relevant lessons: [LES-XXX references from LESSONS-LEARNED.md]
51
52
 
52
53
  ### Constraints
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orchestration-protocols
3
- description: "Runtime orchestration patterns for the Team Lead: parallel research spawning, agent health monitoring, active steering, background agent management, and escalation paths."
3
+ description: "Runtime orchestration patterns for the Team Lead: parallel research spawning, agent health monitoring, active steering, background agent management, Context Compaction, Agent Circuit Breaker, and escalation paths."
4
4
  ---
5
5
 
6
6
  # Orchestration Protocols
@@ -89,6 +89,25 @@ When multiple background agents complete work simultaneously, batch similar revi
89
89
  - If multiple outputs share the same file partition boundary, review them sequentially to catch integration issues
90
90
  - For panel reviews, combine related artifacts into a single panel question when they share acceptance criteria
91
91
 
92
+ ## Context Compaction
93
+
94
+ Between phases, summarize prior agent output before passing it to the next agent. Never paste raw sub-agent results into a downstream prompt.
95
+
96
+ **When:** Multi-phase chains where the next agent only needs outcomes, not full reasoning traces. Skip for single-phase work or when raw detail is needed (e.g., code review).
97
+
98
+ **How:** After a sub-agent returns, extract only: files changed, key decisions, verification results (pass/fail), and blockers. Discard raw tool output, reasoning traces, and failed attempts.
99
+
100
+ **Template for delegation prompts:**
101
+
102
+ ```
103
+ ### Prior Phase Output
104
+ **Phase [N] — [Agent Name] — [Task Title]**
105
+ - Files changed: [list with one-line descriptions]
106
+ - Decisions: [key decisions that affect downstream work]
107
+ - Verification: [lint ✅ | types ✅ | tests ✅]
108
+ - Blockers: [none | list]
109
+ ```
110
+
92
111
  ## Agent Health-Check Protocol
93
112
 
94
113
  Monitor delegated agents for failure signals. Intervene early rather than waiting for completion.
@@ -148,3 +167,15 @@ Common failure modes and how to recover:
148
167
 
149
168
  **Symptom:** Tests pass individually but fail when multiple agent outputs are merged.
150
169
  **Recovery:** (1) Run affected tests to identify which projects break. (2) Check for import conflicts, duplicate definitions, or state pollution. (3) Delegate fix to the agent whose changes are most likely the cause.
170
+
171
+ ## Agent Circuit Breaker
172
+
173
+ Track per-agent failure counts across the session (not just per-task). If the same agent keeps failing, the problem is likely systemic.
174
+
175
+ | Threshold | Action |
176
+ |-----------|--------|
177
+ | **2 failures** | Warning — investigate: same error class? Model endpoint healthy? Prompt pattern issue? |
178
+ | **3 failures** | Open circuit — stop delegating to that agent. Reassign tasks to an overlapping agent, try a different model tier, or checkpoint and escalate to the user. |
179
+ | **Next session** | Half-open — circuit resets. If the agent fails again immediately, re-open and add a lesson via **self-improvement**. |
180
+
181
+ This is a judgment-based pattern, not a hard gate. 3 failures on similar tasks with the same error is more concerning than 3 unrelated failures.