Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
TangleBrain-enforced guarantee, so a delegate that loses the env degrades safely to `unlinked` (never
an error). This was the deferred half of the scatter-gather epic whose entry criterion was a
live-verification spike — now done.
- **Knob panel surfaces the delegation tree.** The panel's "Delegated sub-tasks" card now shows a
**Linked to** stat (`N parent task(s)`, with any `unlinked` sub-calls noted) — GUI parity with the
`tanglebrain --stats` rollup, so the per-parent-task linkage is visible in the panel, not just the
CLI. Read-only; no new endpoint (the data already rides `view_stats`'s rollup payload).

### Fixed

Expand Down
8 changes: 8 additions & 0 deletions tanglebrain/gui/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,18 @@ <h2>Pricing reference</h2>
const dg = s.delegates || {};
if (dg.count) {
const backends = Object.entries(dg.by_backend || {}).map(([k, v]) => `${esc(k)} ${v.count || 0}`).join(", ") || "—";
// Parent-task tree: how many top-level tasks the sub-calls link back to. Mirrors the CLI's
// "Linked to: N parent task(s)" line; sub-calls run outside a propagated task are "unlinked".
const byParent = dg.by_parent || {};
const linked = Object.keys(byParent).filter((k) => k !== "unlinked").length;
const unlinked = (byParent.unlinked || {}).count || 0;
let tree = linked ? `${linked} parent task(s)` : `${unlinked} unlinked`;
if (linked && unlinked) tree += `, ${unlinked} unlinked`;
html += `<h3 style="margin:1rem 0 .4rem">Delegated sub-tasks <span class="muted">(offloaded by orchestrators)</span></h3>
<div class="stat-grid">
<div class="stat"><div class="label">Sub-tasks</div><div class="value">${dg.count}</div></div>
<div class="stat"><div class="label">By backend</div><div class="value mono" style="font-size:.95rem">${backends}</div></div>
<div class="stat"><div class="label">Linked to</div><div class="value mono" style="font-size:.95rem">${esc(tree)}</div></div>
<div class="stat"><div class="label">Est. tokens (in / out)</div><div class="value mono" style="font-size:.95rem">${(dg.in_tokens_est||0).toLocaleString()} / ${(dg.out_tokens_est||0).toLocaleString()}</div></div>
<div class="stat"><div class="label">Cloud-equiv</div><div class="value mono" style="font-size:.95rem">${money(dg.cloud_equiv_usd)}</div></div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions tests/test_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ def test_includes_delegate_breakdown(self):
self.assertEqual(delegates["count"], 1)
self.assertEqual(delegates["by_backend"]["local-x"]["count"], 1)

def test_includes_parent_task_tree(self):
# The panel's delegate card renders the by_parent tree, so view_stats must carry it through.
recs = [
{"kind": "delegate", "model": "local-x", "parent_task_id": "p1"},
{"kind": "delegate", "model": "local-x", "parent_task_id": "p2"},
{"kind": "delegate", "model": "local-x"},
]
with patch("tanglebrain.gui.views.read_records", return_value=recs):
out = views.view_stats()
by_parent = out["summary"]["delegates"]["by_parent"]
self.assertEqual({k for k in by_parent if k != "unlinked"}, {"p1", "p2"})
self.assertEqual(by_parent["unlinked"]["count"], 1)


class RunPromptTest(unittest.TestCase):
def test_happy_path_reports_served(self):
Expand Down
Loading