Plan — #288 Conversational steering of workflow runs + incremental resume¶
The normative target is docs/workflows.md §10 (Steer-and-resume). This is the
implementation plan: flat integer phases, built via /tdd.
What this is¶
#288 is the steer-and-resume that #100 deferred. The incremental-resume
engine already exists (FS-as-journal input-hash skip, §9); #283 already made a run a
first-class object bound to its own chat. #288 adds the "say it in words" layer
on top of the §9 escape hatch ("stop → edit/delete step_* in the file UI → Run").
Flow: free-text in a run's chat → a read-only steerer LLM turn proposes a
SteerPlan (rewrite input files + invalidate steps) → human reviews a confirm card
with the blast radius → approve / reject / re-instruct → on approve a deterministic
step applies the edits + deletes the invalidated artifacts → the same run resumes
(re-spawn; completed steps skip → incremental).
Locked decisions (grilled)¶
- Mechanism = LLM translate + human confirm (produce → review → commit).
- Timing = the 3 points (mid-run / at-gate / post-cancel-or-error) collapse to one codepath: steer only acts on a not-running run; a mid-run instruction auto-Stops first. True live injection stays deferred.
- Vocabulary = edit input files + invalidate steps; downstream cascades via input-hash; no magic per-element skip.
- Continuity = same
run_id+ same chat (reusedecide's re-spawn); a terminal run flips back torunningon resume. - vs gate = coexist; the gate's
approve/reject/reviseis unchanged. - Generic = zero author code, any App; edit scope = any workspace file outside
/.workflow/+ invalidate any step. - Confirm = approve / reject / re-instruct (plan is not inline-editable).
Mechanism details (decided, not separately grilled): always-confirm; the steerer is
read-only + streamed + emits a structured SteerPlan; deterministic apply; blast
radius is an honest approximation (the invalidated steps + input diffs + "their
downstream re-runs" — a full pre-simulation is impossible past the first re-run, whose
output the downstream args depend on).
State machine¶
- Steerable run states:
awaiting_human(at a gate) ·done·error·cancelled.running/pending→steerStops it first, then proceeds. - New
WorkflowRun.pending_steer: SteerPlan | None. While a plan awaits confirm the run isawaiting_humanwithpending_steerset (andpending_decisionunset). The FE chooses the steer card vs. the gate card by which pending field is set;decide()still guards onpending_decision, so the two never collide. SteerPlan { rationale, input_edits: [{ path, content }], invalidate: [step_name] }— full-content writes (likeagent_write_step, #107, to dodge tool-arg unreliability).
Endpoints¶
POST /a/{slug}/items/{itemId}/runs/{runId}/steer { instruction, reasoning_effort? }→ (cancel if running) → read-only steerer turn in the run's chat (streamed) → parseSteerPlan→ setpending_steer, statusawaiting_human.202.POST /a/{slug}/items/{itemId}/runs/{runId}/steer/confirm { approve }→ approve: apply (write edits, delete invalidated artifacts, journal receipt) + resume (re-spawn). reject: clearpending_steer, restore the prior terminal/awaiting state. Re-instruct = callsteeragain.
Phases (flat)¶
- P1 — Docs: un-defer §10 in
docs/workflows.md+ this plan. (done) - P2 —
SteerPlanstruct +WorkflowRun.pending_steerfield; journal-relative steer receipt path helper. Resource + path tests. - P3 —
workflow/steer.pypropose_steer(wf, instruction, …): drive a read-only agent turn, parse aSteerPlanwith tolerant parse + retry-with-feedback. Unit-test with a fakedrive_turn. - P4 —
apply_steer(wf, plan, decided_by): writeinput_edits(guard: not under/.workflow/), delete invalidated step artifacts (guard: journal-only), journal an audit receipt. Unit-test incl. guard rejections. - P5 —
WorkflowOrchestrator.steer()/confirm_steer(): auto-Stop + propose + setpending_steer; apply + resume re-spawn / discard. Reuse_spawn/_read_inputs. Unit-test with fakes. - P6 — API endpoints + typed pydantic request/response models + route tests.
- P7 —
SteerProposedevent (+ any others) inapi/events.py/workflow/events.py; mirror inweb/src/events.ts. - P8 — FE:
SteerConfirmCard(rationale + file diffs + invalidate/re-run list + approve/reject) + run-chat composer reroute tosteerwhen the conversation has arun_id+ mid-run auto-Stop banner + pin the card +useWorkflowhooks +workflows.ts. FE TDD (vitest). - P9 — Live check (local Ollama): run a topic-hub workflow → Stop mid-run → steer "use the X collection" → confirm → verify only the affected step (ingest) re-runs. Full suite + 100% coverage gate + ruff/ty.
Non-goals¶
True live mid-run injection (a note into an already-running node without Stopping); inline plan editing; a structured-controls UI (the free-text steerer is the path).