Tasks: a contribution has no outer loop, and its arming comment answers its first gate
Derived from the approved
bugfix.md,design.mdandtesting-plan.md. A DAG, not a list: tasks with no edge between them are independent. Each_Test:_names a row of the testing plan.
flowchart TD
T1["T1 — selection.py: _asks_surface()<br/>render / parse / confirm / freeze"] --> T5["T5 — unit tests"]
T2["T2 — graphlink: GraphContext.loop<br/>+ the prompt branch"] --> T6["T6 — prompt & spawn tests"]
T3["T3 — GraphLink.on_spawn evaluates<br/>a human start node"] --> T6
T3 --> T4["T4 — dispatcher passes `routed`"]
T4 --> T7["T7 — dispatcher scenario"]
T5 --> T8["T8 — docs: CLI page, capability docs, skill"]
T6 --> T8
T7 --> T8
T8 --> T9["T9 — verification: run the plan,<br/>commit evidence"]Tasks
[x] T1 —
selection.py: the surface question becomes conditional._asks_surface(ctx)reads the compiled graph's name;_checklist_bodyrenders the row or a sentence saying where a contribution's conversation happens;classify_phase_selectionparses a surface only when the question was asked;_confirmationclaims one only when there is one;NO_SURFACE = ""documents the distinction the frozen record now carries. Requirements: R1.1, R1.2, R1.3, R1.4 · Test: T1[x] T2 —
graphlink.py: the prompt stops naming an outer loop for a contribution.GraphContext.loop, populated fromgraph-state.jsonin_context_from; one branch inrender_graph_contextbehind_is_contribution(loop). Requirements: R1.5 · Test: T4[x] T3 —
GraphLink.on_spawnevaluates a human start node once. Fresh entries only (Runtime.startreturned a report), human nodes only (_entered_a_human_gate, failing closed), withcomments_from(routed)attached — inside the existing lock and blanketexcept, after the session binding. Requirements: R2.1, R2.2, R2.3, R2.4, R2.5 · Test: T2, T4[x] T4 — the dispatcher hands the spawning event over.
_spawn_tmuxpassesrouted=routed; the respawn seam deliberately does not, because a respawn evaluates nothing anyway. Requirements: R2.1 · Test: T3[x] T5 — unit tests for the surface question.
test_graph_contribution.py: the contribution checklist omits the row and says where the conversation happens; the outer loop's still asks; a ticked token in a contribution's reply changes nothing (pointer,state.surface, frozen record, skips, confirmation). Requirements: R1.1, R1.2, R1.3, R1.4 · Test: T1, T10[x] T6 — tests for the spawn-time gate and the prompt.
test_graph_contribution.py: the arming comment reaches the goal gate at spawn; no goal parks with a reason; a respawn re-evaluates nothing.test_graph_drive.py: the contribution prompt line, the outer-loop prompt line, and an agent start node left unevaluated at spawn. Requirements: R1.5, R2.1, R2.2, R2.3, R2.4 · Test: T2, T4[x] T7 — dispatcher scenario.
test_graph_drive_integration.py: theRoutedEventthat spawned the session is the one handed toon_spawn; the existing link double accepts and records it. Requirements: R2.1 · Test: T3[x] T8 — documentation.
docs/cli/commands/graph.md(the checklist section),docs/capabilities/process-graph.md(the contribution-loop bullet, the ingress-drives-the-graph bullet, a history row),docs/capabilities/webhook-triggers.md(the control-keyword bullet, a history row),docs/capabilities/spec-workflow.md,skills/the-loop/SKILL.md,skills/the-loop/reference/workflow.md,skills/the-loop/reference/collaboration.md. Requirements: R3.1 · Test: T14[x] T9 — verification. Run every applicable row of the testing plan, record the results in
testing-plan.md§ Verification results, and commit the evidence. Requirements: all · Test: T1–T14
Unplanned changes
test_graph_drive_integration.py::_SeqLink.on_spawngained theroutedparameter. The double is a stand-in forGraphLinkat the dispatcher seam, so a new argument at that seam is a change to it. It records the value, which is what T7 asserts.