## Why `agent-runtime` (`apex-agent-mvp`, code-complete, unapplied) gives the runtime exactly one shape of work: a single flat goal driven by one Planner/Executor Observe-Think-Act-Observe loop until it succeeds, fails, or exceeds `max_steps`. Real device-automation usage is rarely one flat goal — it is a sequence of distinct sub-goals with control flow between them: open an app, send a message, **wait** for a reply to arrive, take a screenshot, then end; or run a locally-learned Skill (`skill-learning-runtime`, Milestone 7) instead of re-planning from scratch, only re-planning if the skill's assumptions don't hold. Nothing in the current runtime can express "do A, then B, then wait until C is true, then D," track where a multi-stage run is, or resume it if the process restarts mid-way. Workflow Runtime introduces `Workflow` as a first-class, persisted, resumable object composed of planner-derived steps, skill-invocation steps, wait-for-condition steps, and simple branch steps — sitting one layer above the existing single-goal loop, not replacing it. ## What Changes - Add a new `workflow/` package defining a `WorkflowDefinition` (an ordered, possibly-branching list of `WorkflowStep`s) as a discriminated union of four step kinds: a **planned-goal step** (delegates a sub-goal string to the existing `agent-runtime` Planner/Executor loop), a **skill-invocation step** (resolves parameters and executes a locally-synthesized `FlowTemplateSkill` from `skill-learning-runtime`, Milestone 7), a **wait-for-condition step** (polls a named condition — e.g. scene text present, a world variable equals a value, elapsed time — up to a timeout), and a **branch step** (evaluates a condition and jumps to a named step id instead of falling through sequentially). - Add a `WorkflowRun` — the persisted, mutable execution record for one execution of a `WorkflowDefinition`: status (`pending`/`running`/`waiting`/`completed`/`failed`/`cancelled`), `current_step_id`, workflow-scoped `variables`, and a per-step result log — checkpointed to storage after every completed step so a `WorkflowRunner.resume(run_id)` can continue from the last checkpoint instead of re-running from step 0, without re-executing already-completed mutating steps. - Add a `WorkflowRunner` orchestration component that composes (imports, never subclasses or edits) `runtime/task.py`'s existing `TaskRunner` for planned-goal steps, and drives skill-invocation/wait/branch steps itself. - Add a first, minimal skill-invocation execution path (`workflow/skill_exec.py`): resolve a `FlowTemplateSkill`'s declared parameters against a step's supplied argument values, validate against the skill's parameter schema, and drive the resolved tool calls through `tools/` — the "some future runner resolves parameters and drives `tools/`" gap `skill-learning-runtime` explicitly left open. - Add a `ConditionEvaluator` port + registry (`workflow/conditions.py`) for wait/branch conditions, mirroring the Driver Registry / `PerceptionProvider` extension-point pattern already established by `device-agent-runtime-foundation`, with a small starting set of condition kinds (`scene_contains_text`, `world_variable_equals`, `elapsed_seconds`, `step_result_success`). - Add a new, independently-owned SQLite-backed `WorkflowStore` (`workflow/store.py`) for `WorkflowDefinition`/`WorkflowRun` persistence, following the same connect-per-call `sqlite3` pattern as `storage/task_metadata.py` but in its own database file — not a schema change to `storage/`, which remains owned by the pending `task-memory` capability. - No changes to `runtime/task.py`, `runtime/planner.py`, `runtime/executor.py`, `runtime/context.py`, `storage/*`, `tools/*`, or any other existing module's public behavior — this change is purely additive composition on top of them. ## Capabilities ### New Capabilities - `workflow-orchestration`: A `Workflow` model (planned-goal / skill-invocation / wait-for-condition / branch step kinds), executed by a `WorkflowRunner` that composes the existing single-goal Planner/Executor loop and the not-yet-built skill-execution path, persisted via a task-memory-style store, and resumable from its last checkpoint if interrupted mid-workflow. ### Modified Capabilities (none — `openspec/specs/` has no applied baseline for `agent-runtime`, `task-memory`, `skill-authoring`, or `skill-embedding-retrieval` yet, so this change cannot and does not write a `MODIFIED Requirements` delta against any of them; it composes with their pending, unapplied designs in prose only, and does not alter their specified behavior.) ## Impact - **New package**: `workflow/` — `models.py` (`WorkflowDefinition`, `WorkflowStep` variants, `WorkflowRun`, `WorkflowStepResult`), `runner.py` (`WorkflowRunner`), `conditions.py` (`ConditionEvaluator` port + registry), `skill_exec.py` (parameter resolution + tool dispatch for skill-invocation steps), `store.py` (`WorkflowStore`, SQLite-backed), `config.py` (enable flags, default poll interval, default wait timeout). - **New storage**: a new `workflows/workflows.sqlite3` database file (own schema: `workflow_definitions`, `workflow_runs`, `workflow_step_results` tables), independent of `storage/task_metadata.py`'s `tasks/tasks.sqlite3`. - **Composed, not modified, dependencies**: `runtime/task.py`'s `TaskRunner` (planned-goal steps construct a `Task` and call `TaskRunner(...).run(task)`, reading back `task.status`/`task.failure_reason`), `skill-learning-runtime`'s `FlowTemplateSkill`/`Skill` dataclass shape (imported, not redefined, matching that change's own D6 precedent for composing with `skill-catalog-subscription`), and optionally `world-model-runtime`'s `WorldState.variables` (read-only, for `world_variable_equals` conditions — degrades to "condition never satisfied until timeout" if `WorldState` is absent, never raises). - **Config**: add `workflow*` to `pyproject.toml`'s `[tool.setuptools.packages.find].include` list; no new third-party dependency beyond the standard library `sqlite3` already used by `storage/task_metadata.py`. - **Out of scope**: no visual workflow editor/UI (a future `web-console` concern, not touched here); no distributed/multi-device workflow execution (Milestone 10, Cloud Runtime); does not replace or remove the existing single-goal Planner/Executor loop, which remains the right tool for simple one-shot tasks; no generic expression/scripting language for branch conditions (a closed, registrable set of condition kinds only); no changes to `skill-catalog-subscription`, `web-console`, or any other pending change's files.