fix(runner): completion persists as its own batch; consumed child errors are not failure evidence - #52
Merged
Merged
Conversation
…tcomes A tool.call or delegated child whose error the parent agent consumes as an observation now carries an error_observed declaration, written in the same durable commit as the observation itself. get_failed_node_errors skips declared-consumed failures, so a handled child error is never reported as the dataflow's failure cause; workflow status continues to derive only from terminal outcomes. Tests: - agent_tool_failure_test: a failing tool consumed by the agent leaves the aggregate completed and the agent node driven to its own terminal status; an unhandled agent failure is attributed to the agent node, not the consumed tool child - delegation_handler_test: a consumed delegation failure is declared on the child and excluded from workflow failure evidence
A failed transaction leaves its command batch queued for retry; appending COMPLETE_WORKFLOW to that retained batch violated the persist layer's batch-order contract, the rejected completion cost the run its owner, and the overseer terminalized a healthy run as failed. workflow_state:queue_completion inserts the completion command at the batch head so retained commands persist behind the generation fence in one transaction, applied only when the fence wins. On fence loss the orchestrator rebuilds workflow state from durable rows before rescheduling, so re-evaluation never acts on outcomes that were never persisted. Exit-path persist failures are logged; the batch remains queued for retry. Tests: orchestrator_completion_flush_test drives the real orchestrator, workflow state, scheduler and persistence with a simulated transaction abort; covers completion retry of the retained batch and durable re-derivation after losing the completion fence.
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.
Production evidence
Dataflow
a production dataflow instanceon a production deployment:dataflows.statusflipped tofailedwhile the agent node stayedrunningmid-run (26 tool calls, 229k tokens). The dataflow row'smetadata.runtime_failureshows the killer:The run was healthy; the status lied to every consumer.
Three defects fixed
1. COMPLETE_WORKFLOW batch ordering loses the owner (
src/runner/orchestrator.lua,src/runner/workflow_state.lua)A failed transaction leaves its command batch queued for retry (workflow_state contract).
handle_complete_workflowthen appended COMPLETE_WORKFLOW to that retained batch; the persist validator rejected the batch ("COMPLETE_WORKFLOW must be the first command"), the orchestrator exited, the overseer saw owner loss and terminalized the healthy run asfailed— leaving the node row a zombierunning.Fix:
workflow_state:queue_completioninserts the completion command at the batch head, so retained commands persist behind the generation fence in one transaction — exactly what the persist layer'scompletion_blockedmachinery is built for (fence first; details applied only when the fence wins; dropped when a newer activation owns the workflow). On fence loss the orchestrator rebuilds workflow state from durable rows before rescheduling, so re-evaluation never acts on outcomes that were never persisted. Exit-path persist failures are now logged instead of silently swallowed.Reproduced end-to-end: driving the real orchestrator + workflow state + scheduler + persistence with one simulated transaction abort yields the exact production error string pre-fix.
2. Consumed child errors counted as workflow failure evidence (
src/node/agent/*,src/runner/workflow_state.lua)get_failed_node_errorscounted everyCOMPLETED_FAILUREnode — includingtool.callviz children and delegated children whose error the parent agent consumed as an observation and continued past. Any workflow failure was then attributed to the handled tool child (the false trace read off the production run). The parent now declareserror_observedon the child in the same durable commit as the delivered observation, and failure evidence derives only from unhandled failures. Aggregate status derivation was verified correct (a failed tool child never flipsdataflows.status) and is locked by regression tests.3. Postgres terminal lifecycle lock order (
src/persist/*, pre-existing commit on this branch)Terminal lifecycle transactions upgraded the workflow row lock after activation/wake writes, deadlocking with concurrent commits holding FK KEY SHARE locks on postgres — the class of transient transaction abort that triggers defect 1. Locks now follow the canonical parent-first order.
Tests
orchestrator_completion_flush_test(new): completion retries the retained batch as a fenced batch and completes; fence-loss rebuilds from durable state and re-derives the outcome (real orchestrator/state/scheduler/persist, one simulated tx abort).agent_tool_failure_test(new): a failing tool consumed by the agent leaves the aggregatecompletedwith the agent node driven to its own terminal status; an unhandled agent failure is attributed to the agent node, never the consumed tool child.delegation_handler_test(extended): consumed delegation failures are declared on the child and excluded from failure evidence.Reviews
Codex read-only reviews across three iterations; final verdict: APPROVE — no blocking issues, "queue_completion correctly prepends the fence; fence loss reloads durable state; no compensation or weakened assertions found."