Skip to content

fix(graph): an unpropagated delegates traversal should not consume the edge cap #1005

Description

@drewstone

Summary

A delegates edge re-traversal that delivers zero bytes still consumes a traversal against maxTraversals. When a keyed re-spawn deduplicates to an already-completed worker, the edge reports unpropagated with no-live-worker-bound, the traversal budget drains, and runGraph throws GraphEdgeCapError with no winner — discarding a run whose child seats had all delivered.

Measured on agent-runtime 0.140.0, container-hosted director fleet, 2026-08-23.

What happens

Two director runs died this way within 15 minutes of each other. Both had maxTraversals: 3 on director->lead-1 and director->lead-2.

h2-director-v2-surgery-automorphism-census-r1:

edge outcomes:
  <run>:s0 delivered        <run>:s3 delivered
  <run>:s1 delivered        <run>:s4 delivered
  <run>:s2 delivered        graph:lead-1 unpropagated x5
  ...                       graph:lead-2 unpropagated x2
traversal numbers seen: [1, 2, 3, 4]
distinct composed-byte sizes: 1313, 3485, 3486

Then:

GraphEdgeCapError: runGraph: edge traversal cap exhausted on
delegates:director->lead-1, delegates:director->lead-2 and the run delivered no winner
exhaustedEdges: [ 'delegates:director->lead-1', 'delegates:director->lead-2' ]

Each unpropagated carried:

no-live-worker-bound (spawn refused after the factory, or a keyed re-spawn deduplicated
to a completed result; 3486 composed bytes never crossed)

Why we think it is the dedup branch, not the refusal branch

The three composed-byte sizes differ across attempts (1313, 3485, 3486). The director composed a new message each time and it was still deduplicated to the completed lead. A key that ignores message content cannot distinguish "retry the same delegation" from "send this lead something new".

Why it matters beyond the error

The run was discarded whole, but its seats had delivered. h2-director-v2-qecdec-loss-aware-bposd-r1 failed identically and left ~50 KB of findings on disk; imported and graded independently they came to 11 verified, 1 contradicted, 5 uncheckable. The driver's verdict and the research verdict were unrelated.

Configuration is not the discriminator

Four runs of the same generation, byte-identical edge config (maxTraversals: 3, continuity: "resume", 6 edges). Two returned {"kind":"winner"} with 8 and 4 traversals; two exhausted the cap. Raising the cap only buys more no-op traversals.

Suggested fix

A traversal that crosses zero bytes is a no-op and should not be billed to maxTraversals. The cap exists as the cyclic-graph backstop, and a delegation that delivered nothing has not advanced the cycle it is there to bound. Either:

  1. do not count an unpropagated traversal against the edge cap, or
  2. include the composed payload digest in the re-spawn key, so a genuinely new delegation is not deduplicated to a completed result.

(1) alone stops runs being discarded; (2) addresses why the delegation was lost in the first place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions