Skip to content

Planned plans are flat: the LLM planner can't express the dependency graph the orchestrator already executes #20

Description

@minerva-sky

Hypothesis

Letting TaskPlanner emit dependency edges and output wiring would materially improve execution correctness for any multi-step goal. Today, every plan produced by the product's own front door executes as an unordered concurrent swarm in which no task can see any other task's output — the orchestrator's graph machinery (dependencies, needs: output wiring, topological scheduling) is reachable only from hand-written library code, never from a planned plan.

Evidence

  • The planner's LLM schema has no dependency or wiring field — tasks come back as a flat array (lib/agentic/task_planner.rb:43-60).
  • agentic plan --execute adds every task with orchestrator.add_task(task) — no dependencies, no needs: (lib/agentic/cli.rb:604-617 and 634-639).
  • agentic execute --plan file.json does the same via initialize_tasks (lib/agentic/cli.rb:930-944), and the plan JSON format (ExecutionPlan#to_h) has nowhere to put an edge.
  • Meanwhile PlanOrchestrator#add_task(task, dependencies, needs:) supports plain edges, named output wiring, rewire_task, and topological ordering (lib/agentic/plan_orchestrator.rb:82,138,433) — and examples/plan_roundtrip.rb hand-rolls its own serialize/deserialize because the library has no wire format for the graph.

Concrete failure: a goal like "research X, then write a report from the findings" plans into two tasks that run concurrently with concurrency_limit: 10; the Writer never receives the Researcher's output and completes from instructions alone. This is the silent version of the stranded-dependents problem in #10 — the plan looks fine and produces confident nonsense.

Why this is Direction, not scope creep

WORLD.md: "The plan is the product. Inspectable, editable, persistable plans are what separate this from 'call the API in a loop.'" A flat list executed concurrently is very nearly "call the API in a loop," ten at a time. Depth on plan quality is the named bet.

Experiment that would validate it

  1. Extend the planner schema additively: each task gains optional depends_on: [task ids/indexes] and needs: {name: task id}. Same for the plan JSON format and TaskDefinition (additive, semver-minor).
  2. Wire both CLI paths through add_task(task, deps, needs:).
  3. Bench: take 10 multi-step goals (the repo already has benchmark/), run each with flat vs. graph planning, and compare (a) whether downstream tasks reference upstream output at all, (b) wall-clock (graph planning should keep parallelism where tasks are genuinely independent).

Prediction I'd bet on: near-total failure of output flow in the flat mode on dependent-step goals, at equal or better wall-clock in graph mode.

Cost

  • Planner schema + TaskDefinition.from_hash + two CLI call sites + specs. No new dependencies. Additive plan-format change with a tolerant reader (old flat plans stay valid: no edges = current behavior).
  • Risk: the LLM emits cycles or dangling references — plan validation from Fail fast when a plan cannot finish as declared #11's execute-time validation is the natural place to catch both.

Not proposing a PR: this is class code-feature/public-api-change (plan format is a public contract), which is L0 per the autonomy policy. If the direction looks right I can stage it as (1) format + validation, (2) planner schema, (3) CLI wiring.


Origin: loop:self session 2026-08-21. Checked against open/closed issues; nearest neighbors are #10 (failure-path state) and #11 (execute-time validation) — both about executing plans as declared, neither about declaring the graph.

Metadata

Metadata

Assignees

No one assigned

    Labels

    loop:selfForward-thinking improvement loop (WORLD.md-aligned)status:analyzedAnalyzed, awaiting decision

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions