Skip to content

Dependents of failed tasks finish the plan stranded in :pending with no execution record #10

Description

@minerva-sky

Observed

When a task fails permanently (retries exhausted or non-retryable), its dependents never run — correct — but they end the plan still in :pending, with no entry in results and nothing recording why they never ran. Reproduced on main:

o = Agentic::PlanOrchestrator.new(retry_policy: {max_retries: 0})
o.add_task(f = failing_task, [], agent: ->(t) { raise "boom" })
o.add_task(d = dependent_task, [f], agent: ->(t) { {ok: true} })
r = o.execute_plan
r.status                          # => :partial_failure (fine)
o.execution_state[:pending]       # => still contains d.id
o.results.key?(d.id)              # => false — no record at all

The existing spec "doesn't execute dependent tasks when dependencies fail" pins the not running part, but the execution history is silent about the skip. For a plan with a deep graph, the answer to "why didn't task X run?" is currently "diff the task list against results and infer."

Why it matters (WORLD.md: Direction)

"The plan is the product... better failure recovery, better execution history." A terminal state that says skipped because dependency Y failed is execution history; :pending-forever is its absence. It also matters for tooling: overall_status works today, but any UI/journal rendering of a finished plan shows skipped tasks as if they might still run.

Proposal (L0 — needs maintainer direction, this is public API surface)

Add a :skipped terminal state to execution_state, entered when a task's dependency reaches :failed (or :canceled) with no retry remaining. Record a TaskExecutionResult failure (or a dedicated skip record) naming the failed dependency. Open design questions:

  • Is :skipped distinct from :canceled, or is cascading-cancel the same concept? (I'd argue distinct: canceled = someone chose; skipped = the graph decided.)
  • Does overall_status change? (:partial_failure already covers it; probably no.)
  • PlanExecutionResult#task_result currently returns nil for these — nil→record is observable behavior; consumers may nil-check today.

Not proposing a PR: state-machine additions ripple into to_h, journal, CLI rendering, and semver. If the direction gets a 👍 I can follow up with a scoped PR.

(Filed by the quality loop. Intended labels applied: loop:quality, status:analyzed.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    loop:qualityCode quality / refactoring loopstatus:analyzedAnalyzed, awaiting decision

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions