Skip to content

orchestration: spawn-path Completed->Failed correction is status-only, does not propagate to dependents/graph status #6396

Description

@bug-ops

Description

Follow-up from #6380 (PR #6395). The fix for #6380 adds a post-hoc Completed -> Failed correction for spawn-dispatched orchestration tasks whose tool calls all failed (DagScheduler::correct_completed_to_failed_if_all_tool_calls_failed, crates/zeph-orchestration/src/scheduler/tick/mod.rs). This correction is deliberately status-only: it flips TaskStatus and annotates TaskResult::output, but does not call dag::propagate_failure, does not touch cascade_detector/lineage_chains, and does not recompute overall graph status.

This is a legitimate, deliberate tradeoff (avoids double-counting CascadeDetector::RegionHealth, since the task was already recorded as a success moments earlier via record_outcome(true) and downstream tasks were already unblocked to Ready). It is not a regression — pre-fix, the same all-failed spawn task was Completed with the same downstream behavior; post-fix, only the task's own status row improved.

However, this creates a real behavioral divergence: two functionally-identical "all tool calls failed" tasks produce different plan-level outcomes depending purely on dispatch path:

  • RunInline path: total failure routes through handle_failed_outcome, which calls propagate_failure (cancels downstream dependents) and can drive graph.status = GraphStatus::Failed.
  • Spawn path (the more common real-world path — .zeph/agents/*.md sub-agents): downstream dependents that were already unblocked before the correction lands continue to run and consume the failed task's (empty) output; GraphStatus can stay Completed even though a constituent task is Failed.

Expected Behavior

Spawn-path total-failure correction should have the option to also propagate to dependents (cancel or otherwise handle them) and/or be reflected in overall GraphStatus, giving parity with the RunInline path — or, if full propagation is judged not worth the RegionHealth double-counting risk, the divergence should be a deliberate, documented product decision rather than an accidental side effect of how the fix was staged.

Reproduction Steps

  1. Configure [orchestration] enabled = true, default verify_completeness = false.
  2. Trigger a spawn-dispatched sub-agent task whose write-type tool calls are all denied by policy (see orchestration /plan task marked completed despite policy-blocked tool failures (silent, no verification) #6380's repro: skill-matcher-fallback → quarantined trust floor → write-tool policy block).
  3. Observe: the task's own PlanView row correctly shows Failed (post-orchestration /plan task marked completed despite policy-blocked tool failures (silent, no verification) #6380 fix).
  4. Observe: any downstream task depending on it still ran (was unblocked to Ready before the correction), and /status's overall plan summary can still report success (GraphStatus::Completed).

Environment

Logs / Evidence

See PR #6395's impl-critic review (.local/handoff/2026-07-17T15-51-55-critic.md, finding S1) and the reviewer's confirmation (.local/handoff/2026-07-17T16-26-35-review.md). Independently reproduced at the integration-test level in the same PR's tester handoff (.local/handoff/2026-07-17T16-04-02-testing.md) — a test's initial (wrong) assertion expected GraphStatus::Failed and had to be corrected to document the actual (accepted) behavior.

Metadata

Metadata

Assignees

Labels

P2High value, medium complexitybugSomething isn't workingorchestrationTask orchestration / DAG scheduling

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions