Skip to content

Run graph: read a node's text in a popup, and make the graph routes generic over strut runs - #5384

Merged
Evanfeenstra merged 2 commits into
masterfrom
claude/graph-node-popup-13804f
Oct 1, 2026
Merged

Evanfeenstra merged 2 commits into
masterfrom
claude/graph-node-popup-13804f

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Contributor

Why

Clicking a node in the run graph (OpenHealth benchmark → Graph) showed its name, type, namespace and id, and nothing you could read. The trace is hydrated with one bulk Cypher query that pulls identity only, so a Concept's docs never left the swarm.

Two asks from review of the design: read the body without going through jarvis, and stop making the graph viewer OpenHealth's — every benchmark will run off struts doing graph walks over Concepts.

What

The popup. The panel asks for a node's body when it is first picked (cached per node), previews the first few lines of its first text property, and a Read button opens it whole in a dialog: the text as markdown, the remaining properties as attributes. The markdown renders through the run report's escaped renderer, never an HTML sink, since graph content is agent- and ingest-written. A failure shows the graph's reason with Ask again; a node the graph no longer holds asks for nothing.

The read. readRunGraphNode in lib/strut-run-graph/hydrate.ts goes through the same 7799 /api/hive/query passthrough the trace uses. The projection is written here:

MATCH (n:Data_Bank {ref_id: '<id>'})
RETURN labels(n) AS labels,
       [k IN keys(n) WHERE NOT k IN ['embeddings','text_embeddings'] | [k, n[k]]] AS props

so the vectors stay on the swarm, and the existing runner gives the timeout and the "did not answer" wording for free. A ref id that is not id-shaped never reaches a query (the route refuses it with a 400 before any lookup). Nothing to deploy in stakgraph or repo2graph.

Generic routes. The trace and the node read live at

  • GET /api/workspaces/[slug]/strut/runs/[runId]/graph
  • GET /api/workspaces/[slug]/strut/runs/[runId]/graph/nodes/[refId]

membership-gated (as the Graph Explorer's read-only queries of the same graph), the row found by id + workspace with no kind filter — a benchmark run, a climb, a job turn, a strut chat. The two OpenHealth graph routes are removed; both OpenHealth viewers point at the new path. A future benchmark viewer mounts StrutRunGraph with its run id and gets the trace and the reader with no new server code.

One text rule. lib/strut-run-graph/node-text.ts decides what of a node is prose (docs, then the deprecated documentation, then description, definition, body, content, text, summary) and what is an attribute. The run report's NodePeek now imports that list instead of carrying its own copy.

Tests

  • hydrate.test.ts: the node read (whole, without vectors; gone vs. unread; an unshaped id never sent) and isRunGraphRefId.
  • node-text.test.ts: the text rules.
  • strut-runs-graph-route.test.ts: both routes — member gate (401/403/404 and public viewer), workspace scoping, 400 on a non-id, 404 gone vs. 502 unread, 502 when strut/swarm cannot be read.
  • StrutRunGraph.test.tsx (jsdom): pick a node → body fetched once → preview → Read opens the dialog with a rendered heading and emphasis, identity keys kept out of attributes; retry after a failure; no fetch for an unresolved node.
  • The OpenHealth climbs and run-files route tests lose their graph sections; the climb viewer test asserts the new endpoint.

Not run against a live swarm — the dialog has only been exercised in jsdom.

…eneric over strut runs

Clicking a node in the run graph showed its name, type, namespace and id,
but never its text — the trace is hydrated with one bulk query that pulls
only identity, so a Concept's docs never left the swarm. Now the panel asks
for the node's body when it is first picked (cached per node), previews the
first few lines, and a Read button opens it whole in a dialog: the text as
escaped markdown, the other properties as attributes.

The body is read through the same 7799 Cypher passthrough the trace uses,
with the projection written here so the two vector properties stay on the
swarm. No jarvis read, nothing to deploy upstream.

The routes are no longer OpenHealth's: a run of any kind that walked the
graph gets the same viewer. The trace and the node read live at
/api/workspaces/[slug]/strut/runs/[runId]/graph and .../graph/nodes/[refId],
membership-gated, the row found by id and workspace with no kind filter.
The two OpenHealth graph routes are removed and both viewers point at the
new path.

The text rules (docs first, then the deprecated documentation, then
description, body, ...) are one pure helper, which the run report's node
peek now shares.
@Evanfeenstra
Evanfeenstra merged commit 3cd267e into master Oct 1, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant