Repository navigation
Peers: tagged nodes across a call, and a resumed caller reattaches instead of relaunching - #128
Merged
Merged
Conversation
…esumed caller reattaches instead of relaunching strut/run-workflow (plans/federation.md §2.2, decided 2026-10-09): - Provenance across the call. The step folds the peer run's step.end.nodes off the tail it already reads, dedupes by ref_id and tags each with the peer's id (AccessedNode.peer — the handle rule, never a URL), then marks its own output. A ref is meaningful only against the graph that recorded it, so no reader resolves a tagged ref locally: the projector skips it (no edge, not counted unresolved), the run flyout shows it under an @peer badge and opens nothing. - Resume. The handle is journaled right after the launch as a synthetic step.end at <path>#launch (no stepType, so stepCounts/verify/projector see no step execution). A re-executed step — the caller restarted and resumed the run — finds it in ctx.journal and tails the same peer run from the start instead of launching a second one. In-process a child dies with its parent, so relaunch is right there; across struts the child survives, so reattach is right. - A stale peer run no longer hangs the caller. TailOpts.live, passed by the stream route as controllers.has(key), closes a tail at EOF when the log has no terminal event and no producer, and the done frame says "stale". tailPeerRun retries a stale done with backoff (its own counter — the failed-open counter resets on every successful open) and then fails peer_run_stale:; a summary-less done (the finalize race) reattaches at once. Streaming an id with no log and no live run now ends at once with done stale rather than waiting for a log to appear. Tests: the two-strut suite checks the tagged nodes and the #launch record, resumes a hand-written stale caller log through POST …/resume and sees the same peer run's result with one run on the peer, and streams a stale peer log to done stale; tailPeerRun covers the stale retry/cap and the summary race; conformance covers `live` on both tails; the pure projector and the web fold cover the tag. Docs: federation.md (§2.2 Provenance + Resume, Decided rows, validation, step order, a dated status note that steps 1–2 are merged), API.md §4 + §10, RUN_CONTROL_SPEC tail terminality, AGENTS.md.
Evanfeenstra
added a commit
to stakwork/hive
that referenced
this pull request
Oct 9, 2026
…#5466) A strut/run-workflow step folds the peer run's step.end.nodes onto its own step.end, each tagged peer: <id> (stakwork/strut#128, plans/federation.md §2.2). That ref_id names a node in the peer's graph, so resolving it against this workspace's graph would show a node that is not there. nodesOf now skips it, the same rule strut's projector and run flyout follow.
This was referenced Oct 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two gaps in
strut/run-workflow(#123), both ruled on 2026-10-09 and folded intoplans/federation.md§2.2:nodes, but the step tailed without reading them. The caller's log had the handle and the output and nothing about which graph nodes the peer's agent read.step.end. A caller that crashed mid-call and auto-resumed re-executed the step and launched a second run on the peer, while the first kept running, orphaned and never cancelled. Separately, tailing a peer run that had died without a terminal event polled forever.What
step.end.nodesoff the tail it already reads, dedupes byref_id, tags each with the caller's peer id (AccessedNode.peer, never a URL: a URL is a second identity and goes stale on re-register), and marks its own output. A ref only means something against the graph that recorded it, so nothing resolves a tagged one locally: the projector skips it (no edge, not counted as unresolved), and the run flyout lists it under an@peerbadge and opens nothing. Once read-through lands it opens through/peers/:id/graph/nodes/:ref_id.step.endat<path>#launch. It has nostepType, so step counts, verify and the projector never see a step execution. A re-executed step finds it inctx.journaland tails the same peer run from the start instead of launching another. The rule: in-process a child dies with its parent, so relaunch is right; across struts the child survives, so reattach is right.TailOpts.live(controllers.has). A log with no terminal event and no producer now closes at EOF withdone { status: "stale" }.tailPeerRunretries a stale done with backoff on its own counter, then failspeer_run_stale:. A summary-less done (the finalize race) reattaches at once.Verified
1376 unit tests pass. New coverage:
#launchrecord, a hand-written stale caller log resumed throughPOST …/resumereturning the same peer run's result with one run on the peer, and a stale peer log streamed todone staletailPeerRun: the stale retry/cap and the summary raceliveon both tailsAlso run live on 2026-10-09: a local strut calling swarm38's
exploreover this path.Not here
nodesOfstill needs a one-line skip of tagged refs.lab:peer+resolveScope(federation.md §3, step 4) is next. Until then a peer token is the peer's whole key.