Flow-driven CLI runtime for Codex agent sessions.
Project description
flow
flow runs agents through flowchart-like workflows in the background. You can watch them and help if they need it. An simple flow:
flow:
name: agi-watcher
mode: workspace-write
args:
site:
help: news site to monitor
check-news:
start: true
thinking: low
prompt: Check {{site}} to see if there's a story about AGI being achieved
transitions:
- if: there is news that AGI has been acheived
go: investigate
- if: there are no stories about AGI being achieved
wait: 60m
go: check-news
investigate:
thinking: xhigh
prompt: |
Read the article, comments and any sources you can find.
Decide whether AGI really has been achieved or if this is just hype.
transitions:
- if: AGI really has been acheived
go: its-over
- if: AGI has probably not been achieved
go: check-news
its-over:
mode: yolo
prompt: |
Use pushover to send the user a short summary of the situation.
Then send another reminding them to go outside, lie on the grass and enjoy the sun.
end: true
Use it like this:
$ flow start agi-watcher.yaml --site news.ycombinator.com
Monitor the situation:
$ flow catalog
$ flow list
$ flow list --top
$ flow top
Runtime active | uptime 00:18:01 | active agents 3 | total agents 4 | cumulative agent time 00:11:18
agi-watcher
check-news
#6 waiting 00:42:32 ~/work/agent-flows site=news.ycombinator.com
#7 waiting 00:42:43 ~/work/agent-flows site=reddit.com/r/locallama
#8 working 00:00:19 ~/work/agent-flows site=https://karpathy.github.io
Check what a specific agent has been up to:
$ flow show 6
$ flow show 6 --json
$ flow show 6 --top
agi-watcher in ~/work/agent-flows (started 23:57 on Apr 1 | 0h 0m running, 0h 6m waiting)
State check-news | Substate normal | Phase waiting
Status Waiting until 2026-04-01T22:58:16Z
site: news.ycombinator.com
Events
23:57 on Apr 1 (0h 0m): check-news started
23:58 on Apr 1 (0h 0m): check-news -> check-news "Checked the live Hacker News front page and relevant HN search results; no current story claims AGI has been achieved."
23:58 on Apr 1 (0h 0m): check-news wait for 60m until 00:58 on Apr 2
View and interact with any codex session directly in your terminal:
$ flow view 6
View many agents in lots of little windows:
$ flow view --all
You have complete control at all times, including pausing and resuming automation for an agent, interrupting it, moving to another state and more. Read the CLI overview for the details.
The main idea is simple:
- a flow is a graph of named states
- each state can give the agent a prompt
- each non-end state has outgoing transitions
- after a turn, the agent chooses the next transition or terminal action in JSON
- the runtime moves the agent, waits, pauses, or asks for help as needed
- every agent is running in a tmux session you can attach to and view or interact with if you have to
flow is built for asynchronous work. You start agents, the runtime keeps them moving through a flowchart in the background, and you inspect or intervene only when you want to.
Principles
- Each agent is always in exactly one state.
- Flows are plain YAML, meant to be easy for both humans and agents to read and write.
- Starting an agent snapshots the flow file. Later edits only affect new agents.
- The runtime is persistent. Agent state lives in
~/.flowby default. - Each agent gets its own tmux session and long-lived Codex process.
- Codex uses your normal shared
~/.codexhome, so your usual config, auth, and skills still apply. - Waiting, pausing, interruption, and recovery are first-class runtime concepts.
Agent Skills
This repository includes local Codex skills in .agents/skills/. They are guidance for agents working with this repo, not runtime configuration.
flows-composedescribes the standard pattern for delegating work to child flows withflow startandwait-for-child.flow-devrecords the repo-specific development commands, test environment, and release steps.
When you maintain flow definitions, prefer putting reusable operating patterns in skills and documentation instead of repeating long mechanical instructions in every YAML prompt. For agents running in another workspace, make sure the relevant skill is available in that workspace or in the user's global Codex skills.
Requirements
- Python 3.10+
tmux- a working Codex CLI setup
Installation
Install the published package:
python -m pip install flow-like-a-river
The name flow was taken. Can you believe that?
This installs the flow CLI command.
Development setup in a fresh virtual environment:
python -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
pytest
Runtime model
The runtime runs as a detached background process.
flow initstarts it if neededflow start ...also starts it automaticallyflow restartgracefully stops it and starts it againflow shutdownlets agents finish their current turn and then stops the runtimeflow shutdown nowkills agents and tmux sessions immediately
State is stored in:
~/.flow/runtime.sqlite3~/.flow/logs/daemon.log~/.flow/scratchpads/
You can override the home directory with FLOW_HOME.
Scratchpads
Each agent gets a lightweight scratchpad file at ~/.flow/scratchpads/agent-<id>/scratchpad.md.
- It is separate from conversation history.
- It is meant for durable working state, not a log or checklist.
- Agents read and edit it directly when there is something genuinely worth preserving across future states or turns.
- The runtime grants sandboxed agents write access to their scratchpad directory with
--add-dir.
Because editable scratchpads are a core part of the harness, read-only is intentionally unsupported. Use workspace-write when you want a tighter sandbox than yolo.
Flow files
A flow file has:
- one top-level
flow:header block - one block per state, e.g.
my-state:
Top-level flow: fields:
name: flow namedescription: human-readable summary shown in UI headers andflow start <file> --help(optional)version: of the flow file format (optional, currently always1)path: initial working directory for new agents (optional, defaults to the current working directory whereflow startis run)mode: default Codex permissions mode (optional, defaults toyolo, other options aredanger-full-access,full-auto, andworkspace-write)thinking: default flow reasoning effort (optional, defaultxhigh, other options arehigh,mediumandlow)fast: default Codex fast mode toggle (optional, defaultfalse)args: named CLI arguments for placeholders and their help/defaults (optional if the flow uses no placeholders)
State fields:
start: true: marks a start state (optional)end: true: marks a terminal state (optional)wait: default delay before the state runs (optional)prompt: text sent to the agent on entry (optional; promptless non-end states move directly to transition questions, and promptless end states finish immediately)mode: per-state mode override (optional default set inflow:header)thinking: per-state thinking override (optional, default set inflow:header)fast: per-state fast mode override (optional, default set inflow:header)transitions: list of outgoing transitions (required unlessend: true, and forbidden on end states)
Transition fields:
if: natural-language condition, e.g. "the CI tests have all passed"wait: optional delay before entering the target state, e.g. "10m"go: target state name
Placeholders like {{repo}} can appear in strings. Every placeholder must be declared in flow.args, and those declarations become CLI arguments at flow start time.
Catalog and Status JSON
flow catalog exposes the flows an agent can discover and reuse.
By default it searches, in order:
$FLOW_PATHif set~/flows~/.flow/flows./flows
Directories are searched recursively for *.yaml and *.yml files. Only flows that pass validation appear in the default output.
Useful forms:
flow catalog
flow catalog --format json
flow catalog --broken
flow show 17 --json
flow show --json emits a compact machine-readable status snapshot, including the current phase, args, scratchpad path, latest event, and child-wait details when the agent is parked on child flows.
JSON contract
The flow show --json shape is fixed. Machine clients should rely on these rules:
phaseis one ofenter_state,resume_state,continue_state,evaluate_transition,evaluate_terminal,submitting,working,waiting,waiting_children,needs_help,interaction,finished,stopped.end_stateis populated only when the agent finished by reaching a flow-declared end state. It isnullwhen the agent is still running, paused, or was stopped.terminated_reasonis"finished"when the agent reached an end state,"stopped"when it was stopped, andnullotherwise.waiting_on.finished[]entries carrystatus:"finished"when the child reached an end state,"stopped"when it was stopped,"unknown"when the id does not resolve to any agent.end_statein those entries follows the same null-when-not-a-flow-end-state rule.
The transition-evaluation JSON the runtime expects from an agent is also a fixed single shape:
{"choice": "<name>", "reason": "<short explanation>"}
When choice is wait-for-child, also include "child_ids": [17, 18]. No other top-level keys are read. In particular, action is not accepted as a synonym for choice.
Composing Flows
Flows can launch other flows without any YAML composition syntax.
The intended pattern is:
- Run
flow catalogto discover an existing flow that matches the long-running subtask. - Check your scratchpad or current agent status for an already-active child doing the same work, and avoid starting duplicates.
- Run
flow start ...from the agent turn and capture the child agent id. - Record the child id, flow name, purpose, and key args in the parent scratchpad.
- When the runtime asks for a transition or terminal action and the parent needs the result before continuing, return
wait-for-childwith one or more child ids inchild_ids. - The parent parks in
waiting_childrenand wakes in the same state once every named child reaches an end state, is stopped, or becomes unknown. - On wake, inspect the child end state and scratchpad path, copy durable facts into the parent scratchpad, and then choose the next transition.
The runtime does not add hierarchy-specific YAML or special child-agent semantics beyond that. Children are still ordinary agents you can inspect, pause, move, stop, or delete with the normal CLI.
Children do not automatically belong to a state or flow. You can start a child in one state and wait for it in a later state, but only if the parent preserves the child id and later supplies it in child_ids. The wait is local to the state where wait-for-child is chosen: the parent parks and wakes in that same current state.
Codex agents in this repository can also use the local flows-compose skill in .agents/skills/flows-compose/SKILL.md, which captures this delegation pattern so it does not need to be spelled out in every flow prompt.
Example:
flow:
name: check-ci
description: Poll a GitHub Actions run until it passes or fails.
path: ~/project
args:
run_url:
help: GitHub Actions run URL
check:
start: true
prompt: |
Inspect the CI run at {{run_url}}.
transitions:
- if: still running
wait: 10m
go: check
- if: passed
go: notify-pass
- if: failed
go: investigate
notify-pass:
prompt: |
Send a success notification.
transitions:
- go: done
investigate:
prompt: |
Investigate the failure and write a short report.
transitions:
- go: done
done:
end: true
How a state runs
When an agent enters a normal non-end state:
- Flow sends the state prompt to Codex (optionally after a wait period).
- Codex works until its turn completes.
- Flow asks Codex to choose one transition in strict JSON.
- Flow follows that transition.
When an agent enters an end state:
- Flow waits first if the state defines
wait. - If the end state has no prompt, Flow finishes the agent immediately.
- If the end state has a prompt, Flow sends it to Codex.
- Flow then asks Codex to choose one terminal action in strict JSON.
- Flow either finishes, keeps working in the same end state, or pauses for help.
There are also implicit choices that change the status of an agent without changing its state:
keep-working: stays in the same state and tells Codex to continue workingneeds-help: stops automation for this agent and waits for someone to assist it
Prompted end states also have:
finish: ends the agent successfully from the current end state
If a state has no prompt and exactly one unconditional transition, Flow auto-advances without asking Codex anything. This is useful for pure wait states.
Waiting
wait can appear on:
- a state: default delay when entering that state
- a transition: override delay for that specific entry
Internally, waits become an absolute ready_at timestamp.
Useful patterns:
- poll every 10 minutes by looping back to the same state with
wait: 10m - define a pure wait state with no prompt and one unconditional transition
If you want to cancel a wait early:
flow wake <agent-id>
wake only clears the timer. It does not resume an agent that is paused in interaction or needs-help.
CLI overview
Check that the installed flow/Codex/tmux integration can run a tiny end-to-end agent flow using your current user environment:
flow self-test
Validate one or more flow files:
flow validate examples/agi-watcher.yaml examples/ci-notify.yaml
Start an agent:
flow start examples/agi-watcher.yaml --site news.ycombinator.com
If the flow has more than one start state:
flow start my-flow.yaml start-state-name --path ~/work/repo
List active and archived agents:
flow list
flow list agi-watcher
flow list --top
Open the live dashboard of active and recently finished agents:
flow top
flow top agi-watcher
flow top --recent 4h
Show one agent in detail:
flow show 12
flow show 12 --top
flow show displays:
- flow name and working path
- start time
- total running time
- total waiting time
- scratchpad path
- Codex thread id when known
- a
codex resume ...hint for finished agents when possible - args
- a timestamped event log
flow top shows the flow list summary for active agents plus agents that finished recently, with a Recent Events section underneath. By default the recent window is 1h.
With --top, flow list and flow show, and with flow top, the screen clears and redraws every five seconds. Press space to refresh immediately and q to exit.
If flow top is run without a terminal on stdin or stdout, it prints one full dashboard snapshot and exits instead of entering the live redraw loop.
View live tmux sessions:
flow view 12
flow view 12 15 18
flow view --all
With multiple ids, flow view opens a tiled tmux dashboard with one read-only pane per agent.
Pause, interrupt, and resume automation:
flow pause 12
flow interrupt 12
flow resume 12
flow pause: pause automation without sendingCtrl-C; if Codex is already working on a turn, that turn is allowed to finish naturallyflow interrupt: pause automation and also sendCtrl-Cto the live Codex sessionflow resume: leaveinteractionorneeds-helpand let automation continue
Move or stop an agent:
flow move 12 investigate
flow resume 12
flow stop 12
flow stop 12 done
flow move: move the agent to another state and leave it paused; runflow resumewhen you want the target state to start. Moving a finished agent resurrects it from the selected state.flow stop: stop the agent immediately, optionally marking it as a specific terminal state
Delete an archived agent entirely:
flow delete 12
Manage the runtime:
flow init
flow restart
flow shutdown
flow shutdown now
Agent states you will see
Normal runtime state:
- the agent is in a flow state and automation is active
Special substates:
interaction: you paused or interrupted the agent, and automation is pausedneeds-help: the agent asked for human help and automation is paused
Other useful runtime phases:
waiting: waiting forready_atsubmitting: prompt submission is recorded locally while Codex acknowledges the turnworking: Codex is still working on the current promptfinished: the agent reached an end state
Diagnostics
flow list includes runtime diagnostics before the state list when relevant.
It can show:
- daemon crash details if the runtime exited with an error
- new runtime warnings and errors since the last time you ran
flow list - agent-level
errorandneeds-helpevents
This is driven by structured runtime diagnostics, not just raw log scraping.
Example files
examples/agi-watcher.yamlexamples/ci-notify.yaml
The examples cover:
- placeholders
- polling with
wait - success and failure transitions
- push-notification follow-up states
- a simple realistic monitoring flow
A typical session
Validate a flow:
flow validate examples/agi-watcher.yaml
Start an agent:
flow start examples/agi-watcher.yaml --site news.ycombinator.com
Watch progress:
flow list
flow list --top
flow show 1
flow show 1 --top
flow view 1
Intervene if needed:
flow pause 1
flow interrupt 1
flow resume 1
flow wake 1
flow move 1 investigate-failure
flow resume 1
Restart the runtime after code changes:
flow restart
Stop everything cleanly:
flow shutdown
Notes
- Reserved state names are
stopped,needs-help,needs_help, andinteraction. - End states cannot define
transitions. - A state can only have one unconditional transition, and it must be last.
- States without transitions must set
end: trueexplicitly. - Relative paths and
~inflow.pathare expanded to absolute paths. - Absolute and relative times in
flow showuse your local timezone for display.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flow_like_a_river-0.2.17.tar.gz.
File metadata
- Download URL: flow_like_a_river-0.2.17.tar.gz
- Upload date:
- Size: 104.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96cc0b02533a0726a66b336716388bb8c21e70401bdbc49a8d5cc7610b93ad33
|
|
| MD5 |
0fccb13892da84d0884f63a91467d4ba
|
|
| BLAKE2b-256 |
01cdcfdcd75d49ebf73c30b70fb7187839e457df6b116412935f403216f89940
|
Provenance
The following attestation bundles were made for flow_like_a_river-0.2.17.tar.gz:
Publisher:
workflow.yml on yieldthought/flow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flow_like_a_river-0.2.17.tar.gz -
Subject digest:
96cc0b02533a0726a66b336716388bb8c21e70401bdbc49a8d5cc7610b93ad33 - Sigstore transparency entry: 1802327474
- Sigstore integration time:
-
Permalink:
yieldthought/flow@ba039a9e0994206090c26865fedf230cf7f7bc1d -
Branch / Tag:
refs/tags/v0.2.17 - Owner: https://github.com/yieldthought
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@ba039a9e0994206090c26865fedf230cf7f7bc1d -
Trigger Event:
push
-
Statement type:
File details
Details for the file flow_like_a_river-0.2.17-py3-none-any.whl.
File metadata
- Download URL: flow_like_a_river-0.2.17-py3-none-any.whl
- Upload date:
- Size: 72.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c83e48b5de95a02bedacee77280012fa2701826deaee1d91ded5301ee197e8c9
|
|
| MD5 |
2e095614dc007a23083e98043c4ed997
|
|
| BLAKE2b-256 |
7835bf5e8e2d2b04eb940dba55f6ae979f1d2257e2c20a9598617668728e9526
|
Provenance
The following attestation bundles were made for flow_like_a_river-0.2.17-py3-none-any.whl:
Publisher:
workflow.yml on yieldthought/flow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flow_like_a_river-0.2.17-py3-none-any.whl -
Subject digest:
c83e48b5de95a02bedacee77280012fa2701826deaee1d91ded5301ee197e8c9 - Sigstore transparency entry: 1802327501
- Sigstore integration time:
-
Permalink:
yieldthought/flow@ba039a9e0994206090c26865fedf230cf7f7bc1d -
Branch / Tag:
refs/tags/v0.2.17 - Owner: https://github.com/yieldthought
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@ba039a9e0994206090c26865fedf230cf7f7bc1d -
Trigger Event:
push
-
Statement type: