step.18.diff.txt -- narrative retro for step.18 (U3.5: EXECUTING loop -- scheduling from the execution DAG's ready frontier; tool acting + evidence recorded into the step file AND node kv state; Q-15-B resolved) Executed: 2026-07-21, same session as the draft (single pass, the step.7/9/10/11/14-17 convention -- executing.py, the two new error classes, the shell.py env scrub, the compiler ASCII fix, the mock execution fixture, the `execute` CLI subcommand, the 32+4 test additions, the design.md/end_to_end.md/tool_surface.md/ orchestrator_state_machine.md edits, and the step file all built together). Companion artifact: step.18.gdiff.txt (automated, scripts/step_gdiff). Per the operator's explicit per-UoW checkpoint rule (HARD), this session STOPS after U3.5 -- U3.6 is not started. ## Plan vs actual ``` +---+----------------------------------+------------------------------------------+ | # | Planned (brief) | Actual | +---+----------------------------------+------------------------------------------+ | 1 | Decide where instances live for | steps/.execution.dag BESIDE the | | | a target workspace (steps/ | skeleton -- the U3.4 amendments-file | | | sibling or a dag/ dir), check | precedent; §2.12's "file trees for ... | | | §2.12's wording | workflow DAG instances" + §4's "the | | | | step's evidence graph" read as per-step | | | | workspace content. A workspace dag/ dir | | | | rejected: in the one real workspace | | | | (this repo) dag/ IS the substrate -- | | | | name collision with the grammar fork. | | 2 | Check whether bin/dagModel is | IT IS BUILT AND WORKS -- checked live | | | available; design honestly | before designing (from_dag -> | | | around absence if not | to_dag_text byte-identical on | | | | execution_example.dag). So instances | | | | are round-trip-verified at write + | | | | completion, droppings cleaned; absence | | | | still handled honestly BOTH ways | | | | (named "skipped" outcome for writes; | | | | loud refusal for RESUME -- reading | | | | back IS parsing). | | 3 | Map each V0 node kind to REAL | context_assembly = U3.3's bounded probe | | | actions using ONLY what | (intent = title + §(2)); provider_run = | | | exists; decide provider_run's | one PAL call, first entry "EXECUTE | | | prompt shape and tool_call's | : " (Q-4-B routing kept), | | | "the work" | proposal = path line + complete content | | | | (the ask arc widened to let the | | | | provider pick the file); human_gate = | | | | in-loop diff review, injectable I/O, | | | | --yes bypass; tool_call = the gated | | | | write_file apply (C-4-B AT the write); | | | | oracle_gate = run_gate on the | | | | scripts/oracle_gate.sh convention, only | | | | when the node exists, exit-as-data. | | 4 | Keep the loop generic over the | run_ready_frontier() is shape-agnostic: | | | DAG; test with a synthetic | ready = precedes-preds all done; other | | | branching DAG | edge kinds are annotations. Proven on a | | | | diamond, a multi-root join, an | | | | annotates back-edge (would be a cycle | | | | if it constrained), a real cycle | | | | (refuses), an unknown kind (refuses). | | 5 | Q-15-B MUST be answered here | RESOLVED: scrubbed child env, | | | | deny-by-pattern on secret-shaped names | | | | (no bare AUTH -- SSH_AUTH_SOCK is a | | | | socket path, not a credential). In | | | | _execute, so run_shell AND run_gate | | | | share it. Proven with real python3 -c | | | | children printing ABSENT. | | 6 | Decide the end-of-loop state and | Completion -> VERIFYING with | | | V0 failure semantics | verification ABSENT (U3.6's job; the | | | | RATIFIED-sat-until-U3.5 mirror). | | | | Failure/decline: halt loudly, node kvs | | | | persisted, state STAYS EXECUTING, | | | | re-run resumes -- NOT d9's budget. | | 7 | Evidence into the step file AND | Node kvs on every change (running | | | node kv state | first -- crash-honest); §(0) ENGINE | | | | EXECUTION RECORD stamped ONCE on | | | | completion (the step.16/17 pattern + | | | | re-parse + double-stamp guards). A halt | | | | stamps nothing in §(0) -- the DAG is | | | | the LIVE surface (§2.13), the step file | | | | the durable ledger fact (Q-18-A). | +---+----------------------------------+------------------------------------------+ ``` ## Surprises / genuine findings 1. **The section-sign character broke the instance round-trip** -- the one real mid-build defect. U3.1's compiler wrote "design.md §4" into generated node descriptions; tools.write_file writes utf-8 while the substrate reads/writes .dag as latin-1 bytes, so the first roundtrip_verify FAILED (utf-8 "§" reread as latin-1 "§"). The hand-authored execution_example.dag turned out to already avoid the character ("design.md section 4") -- upstream knew. Fixed UPSTREAM in compiler.py (ASCII "section 4", with the why as a comment), plus a BYTES-level comparison in roundtrip_verify (the honest invariant: what is on disk vs what the substrate would rewrite). 2. **The egress trail doubles as the resume source, for free.** A resumed human_gate/tool_call needs the proposal, which lives in no engine state -- but B-5's unconditional egress record (C-4-C) already holds the full response text, keyed by the run_id the provider_run node's own kv preserves. The resume-after-decline test passes a MustNotBeCalledAdapter: zero provider calls, identical applied result. Evidence discipline paying rent, not costing it. 3. **`running` written BEFORE the handler** fell out of the files-as-truth posture rather than being planned: if the instance is the live progress surface, a crash mid-node must be visible in the file. It also made the resume set (pending/failed/declined/running) self-evident. 4. NOT a surprise, verified as hoped: the generic frontier engine needed no special-casing for the linear template -- the template is just the degenerate DAG, exactly the point of scheduling from a frontier. ## Scope discipline U3.6/U3.7/U3.8, Q-17-A's auto re-draft, Track B, and content-aware compilation: none started (G8). No VERIFYING-tier code exists; no VERIFYING->RETRO or VERIFYING->EXECUTING call site anywhere. The operator's explicit stop-after-U3.5 boundary is honored -- control returns after the retro pair. ## Numbers 443 passed / 11 skipped before -> 479 passed / 11 skipped after the build (+32 tests/test_orchestrator_executing.py, +4 Q-15-B tests in tests/test_tools_shell.py) -> 481 passed / 11 skipped with this step's files in the corpus (+2 self-inclusive pickups). Zero regressions; zero existing tests edited (the stale-docstring updates touched no pinned strings -- checked by grep before editing). End of retro.