The execution kernel, typed wire, subflows, fan-out, Coding Agent backends, and local scheduling are shipped with interpreter/codegen parity. The next phases return to Flow's product purpose: one Git-native workflow artifact authored by humans in a TUI/Web UI or generated by Coding Agents. P9 builds those authoring and inspection surfaces; P10 adds deterministic companion tests with typed node-output mocks. Distributed execution remains a deliberate non-goal.
- Done: NodeDef carries a RetryPolicy(max, backoff); the executor retries a failed node (script or agent) up to max times with a per-attempt backoff, then re-raises — preserving fail-fast when retries are exhausted.
- Removes the 'one LLM hiccup fails the whole run' failure mode.
- Notably, this was implemented by flow itself: the tools/autoenrich workflow (build → gate → validate, with a bounded fix loop) wrote and self-reviewed it.
- Done: a CheckpointStore protocol (with a JSONFileCheckpointStore) persists coherent frontier-batch snapshots keyed by run id; resume reconstructs graph progress and skips already-committed work with batch-level at-least-once semantics.
- Turns long agent runs from all-or-nothing into recoverable. Codegen honours it too via FLOW_RUN_ID / FLOW_CHECKPOINT_DIR, so both run paths agree.
- Also implemented by the tools/autoenrich workflow itself.
- Done: typed NodeStarted / NodeFinished / NodeFailed events carry per-node wall-clock duration and (for agent nodes) token usage; the executor delivers them via an on_event callback, and the generated module logs the same lifecycle to the flow.generated.events logger.
- The foundation for observability and live TUI/web progress. Also implemented by the tools/autoenrich workflow itself.
- Done: a node marked on_error:"isolate" whose branch fails is captured (its error recorded in runtime["failed"]) and its downstream sub-tree skipped, while independent sibling branches still run to completion. The default on_error:"fail" keeps the fail-fast semantics.
- Implemented by the tools/autoenrich workflow itself; codegen mirrors it.
- Done: a workflow (or execute() override) can cap how many nodes run at once via an asyncio.Semaphore, so a wide fan-out can't burst past a provider's rate limits. Unlimited by default.
- Implemented by the tools/autoenrich workflow itself; codegen mirrors it.
- Done: a human node awaits a named signal — absent, the run checkpoints and raises WorkflowPaused (the generated module exits with a PAUSED line); deliver the signal and resume with the same run id to pass the gate. Real stop-for-approval built on P2 checkpointing.
- Implemented by the tools/autoenrich workflow itself; codegen mirrors it.
- Done: a node marked deterministic:true memoises its output keyed by (node id, input hash), so a retry or resume with the same input reuses the result instead of repeating a side-effect. Non-deterministic nodes (the default) always run.
- Implemented by the tools/autoenrich workflow itself; codegen mirrors it.
- Done: the wire format is type-native — port values are live int/float/bool/list/dict, not stringified — so structure flows between nodes intact and an agent's structured result stays a real object.
- Done: ports carry a JSON Schema (scalar or nested), with one `required` flag replacing the old `optional`; an agent's structured-output contract is derived from its output ports (no separate output_schema) and validated by fastjsonschema; an agent can fan its result across several typed output ports.
- Done: interpolation, conditions and edge maps use JSONPath (`{{ $.plan.tasks[0] }}`, `"$.verdict.within_budget"`) via one shared jsonpath-ng evaluator; edges are type-checked end to end, including a sub-field's type.
- Done: real cycle detection (an unbounded cycle fails validation with its path); `entry` is optional (derived from the $in frontier); the generated module honours FLOW_INPUTS / FLOW_PROVIDER overrides.
- Every item shipped with interpreter + codegen + a cross-engine parity test, so interpret == compile holds throughout. See docs/expressiveness.md.
- Done: numeric condition ops (gt/gte/lt/lte) so a loop can branch on a score, not just a string match; and strict interpolation — because ports are declared, every `{{ $.key }}` in a prompt or condition is checked against the declared ports at load time, turning a silent typo into a fail-fast. A loop with no `when` guard now warns.
- Done: a typed workflow signature. $in carries an optional in_schema (else it's inferred from how each seed is consumed — the consumer's type, not the seed's value); the $output signature is derived from the sink edges. This gives a workflow a checkable I/O type at both ends.
- Done: sub-workflows — a type:"subflow" node runs another workflow as one OPAQUE node (not inlined), authored inline or as a `./child.json` path ref. Its ports are derived from the child's signature; both engines call the same execute() on the child, so interpret == compile holds by construction. A generated module that uses one imports flow (vendored by --portable). See docs/subflow.md.
- Done: dynamic fan-out — a fan_out edge maps a node over a runtime-sized array and a fan_in edge gathers the results into an index-ordered list, with a dedicated fan_max_concurrency cap. The fan group stays ONE scheduler node, so the static graph stays static. See docs/fan-out.md.
- Every item shipped with interpreter + codegen + a cross-engine parity test, and the builder/graph views render subflow nodes and the typed signature.
- Done: an agent node can set backend:"claude-cli"/"codex-cli" to run one turn by shelling out to a coding-agent CLI instead of the in-process SDK. A CLI agent node needs NO provider (the CLI owns auth); its allowed_tools narrows the CLI's own toolset; its mcp_servers spec is format-converted into the CLI's MCP config with ${ENV} secret interpolation. Both engines shell the same command, so interpret == compile holds.
- Done: the SDK block (agent/ai imports + tool registry + _run_agent) is emitted only when a workflow has an SDK agent node — so a pure-CLI/script generated module imports no agent/ai and its --portable bundle drops that vendoring.
- Done: a flow skill (SKILL.md + example pack) installable into claude/codex, so a CLI can crystallize a recurring process into a workflow and run it. See docs/cli-agent.md.
- Done: a top-level schedule block. mode:"timer" fires on an interval (every:"15m") or a cron expression (translated to a systemd OnCalendar); mode:"hook" fires when an external event (http/file) delivers a signal to a fresh run — reusing the human-node pause/resume primitive.
- Done: xdog-flow scheduling install builds the portable bundle and installs a systemd user timer (crontab fallback) or shared hook-listener route; scheduling list/uninstall manage installs through a local registry, and --dry-run previews changes.
- The scheduler wraps the built bundle — every firing is a fresh python <bundle> run, so the engine and interpret == compile are untouched. Linux/systemd first. See docs/scheduling.md.
- Build a local Web UI as a Workflow JSON IDE: graph canvas, typed forms, JSON preview, validation, run/generate/scheduling actions, and structured results. It edits the same Git file as the TUI and Coding Agent skill — no second database model.
- Make Agent authoring machine-friendly with schema export and JSON validation errors carrying stable path/code/message fields, enabling create → validate → repair → review.
- Grow a high-quality template library, led by Flow Release Radar: local repo collection, SDK-tool audits, dynamic fan-out, deterministic scoring, report subflow, and scheduling.
- xdog-flow test <target> [--case NAME] runs a workflow's companion <name>.test.json suite — a workflow file, a suite file, or a whole directory. Test data stays out of the production workflow definition.
- Stubs cover only the boundaries a test cannot reason about: agent turns (SDK and CLI backends alike), human signals, and whole subflow nodes. Script nodes need an explicit --allow-script-stub, because stubbing deterministic logic hides the thing under test.
- Stubs are injected at the provider call, so prompt interpolation runs for real and the stubbed value is validated by the node's own required-field check and coercion — the same code that validates a live model response. No provider is ever constructed, so a test cannot reach the network by accident.
- Rules select deterministically under concurrency: when (deep-subset match on inputs), index (fan array position), round (activation ordinal). Assertions are one outcome — success, error substring, or paused-at-node — plus a deep-subset output match and a calls map that covers executed/skipped nodes, fan instances, and loop iterations.
- Deeper host-integration examples: run a flow graph as one activity inside a durable engine (e.g. Temporal) for cross-machine scale.
- Richer diagram/observability surfaces built on the P3 event stream.
- Distributed execution stays a deliberate non-goal — see Design for why the single-machine, interpret==compile kernel is the point.