From fa437343335a75d8602a940fa34f2c916cc6872f Mon Sep 17 00:00:00 2001
From: Jason-Vaughan <95194903+Jason-Vaughan@users.noreply.github.com>
Date: Thu, 18 Jun 2026 22:56:53 -0700
Subject: [PATCH] Surface the delegation tree in the knob panel (GUI parity)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The per-parent-task tree shipped in --stats (CLI) but the panel's
"Delegated sub-tasks" card ignored by_parent. Add a "Linked to" stat
(N parent task(s), with any unlinked sub-calls noted) mirroring the CLI
rollup. Read-only, no new endpoint — the data already rides view_stats's
rollup payload.
---
CHANGELOG.md | 4 ++++
tanglebrain/gui/static/index.html | 8 ++++++++
tests/test_gui.py | 13 +++++++++++++
3 files changed, 25 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1256f6..8814ca7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/tanglebrain/gui/static/index.html b/tanglebrain/gui/static/index.html
index ea6a977..53cd300 100644
--- a/tanglebrain/gui/static/index.html
+++ b/tanglebrain/gui/static/index.html
@@ -119,10 +119,18 @@
Pricing reference
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 += `Delegated sub-tasks (offloaded by orchestrators)
+
Est. tokens (in / out)
${(dg.in_tokens_est||0).toLocaleString()} / ${(dg.out_tokens_est||0).toLocaleString()}
Cloud-equiv
${money(dg.cloud_equiv_usd)}
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 4c8020a..c42efa9 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -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):