Streaming, declarative multi-agent workflows for Deep Agents.
Project description
deepflow
Streaming, declarative multi-agent orchestration for Deep Agents.
One idea, two modes: keep the orchestrator's context tiny while many sub-agents do the work — and stream the whole thing live.
- 🔀 Workflow mode — the agent authors a phase/step plan in a single call. Independent steps fan out in parallel, later steps fan in by consuming earlier results, and each step runs in its own isolated sub-agent.
- 🗂️ Task-list mode — when a job explodes into hundreds or thousands of to-dos, a deterministic dispatcher fans them out to workers in disjoint batches. Each worker sees only its slice, so the store never has to fit in any prompt.
🔀 workflow 🗂️ task-list (500 to-dos)
Phase 1 · Build (3 in ∥) plan 500 pending · batch 50 → 10 workers (not 500 agents)
b1 b2 b3 ┌ w0 sees ONLY its slice (50 of 500)
Phase 2 · Verify │ read_todos → 50 the other 450 are invisible to it
v1 ⇐ b1,b2,b3 │ ✓✓✓ … ✓
✓ done └ … w9
done=500 ← all the orchestrator gets back
Why
A normal agent runs every step in one conversation and re-reads the whole history each turn — tokens climb and the model drifts on long jobs. deepflow lets the agent commit to a structure and run the work in fresh, focused sub-agents:
- the orchestrator stays
O(small)— it sees a plan or a status rollup, never the full work; - each worker sees only its step or its batch;
- everything streams as it happens.
It's built on top of Deep Agents (not a fork): every sub-agent is a full Deep Agent (filesystem, shell, tools), and each mode is just an AgentMiddleware you can drop into any agent.
Install
pip install deepflow-agents # pulls in deepagents
# or
uv add deepflow-agents
Requires Python 3.11+. The install name is deepflow-agents (the bare deepflow is taken on PyPI); you still import deepflow.
Quickstart
Give the agent one objective; it authors a DAG of sub-agents and runs it. Stream the run to watch the plan and every step as they happen:
from deepflow import create_workflow_agent
agent = create_workflow_agent("openai:gpt-5.5")
for mode, chunk in agent.stream(
{"messages": "Research PostgreSQL, SQLite and DuckDB in parallel, then recommend one for a local analytics CLI."},
stream_mode=["updates", "custom"],
):
if mode == "custom" and "deepflow" in chunk:
ev = chunk["deepflow"]
print(ev["event"], {k: v for k, v in ev.items() if k != "event"})
plan {'phase_count': 2, 'step_count': 4}
phase_start {'index': 0, 'title': 'Research'}
step_start {'id': 'postgres', 'subagent': 'general-purpose'} ┐
step_start {'id': 'sqlite', 'subagent': 'general-purpose'} │ run in parallel
step_start {'id': 'duckdb', 'subagent': 'general-purpose'} ┘
step_done {'id': 'duckdb'} …
phase_start {'index': 1, 'title': 'Recommend'}
step_done {'id': 'recommendation'} # ⇐ depends on all three (fan-in)
workflow_done {}
The two ways to use it follow — workflows, and a workflow that also drives a large task list. Runnable versions live in examples/.
Use cases
1 · Creating workflows
The agent authors a phase/step plan in a single call. It decides when — it stays a normal agent and only reaches for a workflow when work fans out into parallel parts that then combine.
from deepflow import create_workflow_agent
agent = create_workflow_agent(model="openai:gpt-5.5")
result = agent.invoke({"messages":
"Compare PostgreSQL, SQLite and DuckDB for a local analytics CLI — research each in parallel, then recommend one."
})
print(result["messages"][-1].content)
A workflow is an ordered list of phases; each phase has steps. Phases run sequentially; steps within a phase run in parallel; a later step consumes an earlier one's output via {{step_id}}:
{
"phases": [
{"title": "Research", "steps": [
{"id": "a", "subagent_type": "general-purpose", "description": "Research A", "prompt": "Research topic A."},
{"id": "b", "subagent_type": "general-purpose", "description": "Research B", "prompt": "Research topic B."}
]},
{"title": "Synthesize", "steps": [
{"id": "s", "subagent_type": "general-purpose", "description": "Synthesize", "depends_on": ["a", "b"],
"prompt": "Compare and synthesize:\n\nA: {{a}}\n\nB: {{b}}"}
]}
]
}
Invalid plans come back to the model as actionable messages ("Step 's' references {{a}} but does not list it in depends_on"), not opaque tool errors.
2 · A workflow with a task list
A workflow can also drive a large, long-running task list. One flag — enable_todos=True — gives the agent count_todos / add_todos / process_todos alongside workflow. Hand it a specified list of tasks, or let it create its own internal tasks with add_todos, and it works through them at scale:
from deepflow import create_workflow_agent, make_todos
agent = create_workflow_agent(model="openai:gpt-5.5", enable_todos=True, todo_batch_size=50)
# a specified task list (could be 5,000) — or let the agent build it with add_todos
result = agent.invoke({
"messages": "Work through every pending to-do; each worker writes a one-line result.",
"tasks": make_todos(tasks),
})
# the orchestrator only ever sees: "done=5000" — never the 5,000 items
Task management — the to-dos live in a store with status; process_todos partitions the pending ones into disjoint batches, runs them in parallel, and you re-run it to retry anything still pending/failed.
Context management — the orchestrator plans from counts, never contents (O(log N) — 10× the to-dos adds one digit, not 10× the tokens); each worker is handed only its batch (O(batch)); the store lives in state and never enters a prompt. That's what lets a single workflow drive thousands of tasks across a long-running job without its context exploding.
Workers are Deep Agents minus orchestration. Every worker has the full Deep Agent toolset — filesystem,
execute, summarization/compaction — but notaskand noworkflow: it drains its assigned slice and nothing more. Disjoint batches ⇒ no race, no double-processing.
Prefer a dedicated task-list agent without the workflow tool? create_tasklist_agent(model, batch_size=...) is the same store and dispatch on its own.
Streaming (the point)
Stream the run and render events as they happen — same for agent.stream(...) and agent.astream(...):
for mode, chunk in agent.stream(
{"messages": "...", "tasks": tasks},
stream_mode=["updates", "custom"],
):
if mode == "custom" and "deepflow" in chunk:
ev = chunk["deepflow"]
print(ev["event"], ev)
Workflow events
| event | when | fields |
|---|---|---|
plan |
before anything runs | phase_count, step_count, phases[…] |
phase_start / phase_done |
a phase starts / finishes | index, title |
step_start |
a step begins | id, subagent |
step_event |
live activity inside a running step | id, kind |
step_done |
a step settles (fires immediately, not batched) | id |
step_error |
a step failed (isolated) | id, error |
workflow_done |
the whole run finished | — |
Task-list events
| event | when | fields |
|---|---|---|
tasklist_plan |
before dispatch | total, pending, batch_size, worker_count |
batch_start |
a worker gets its slice | worker, size, todos[] |
worker_read |
a worker calls read_todos |
worker, returned, ids |
batch_done |
a worker finished its slice | worker, results[] |
tasklist_done |
dispatch finished | done, failed, pending, in_progress |
The names live in deepflow.events, so the code that emits them and any reader never drift.
Recipes
Run real commands — pass a sandbox/shell backend; workers share it with the orchestrator:
import tempfile
from deepagents.backends import LocalShellBackend
agent = create_workflow_agent(
model="openai:gpt-5.5",
backend=LocalShellBackend(root_dir=tempfile.mkdtemp(), inherit_env=True),
)
Cheaper workers — strong orchestrator, cheap/fast workers:
create_workflow_agent(model="openai:gpt-5.5", workflow_model="openai:gpt-5-mini")
create_tasklist_agent(model="openai:gpt-5.5", worker_model="openai:gpt-5-mini")
Custom workflow workers (a general-purpose worker is added automatically if you don't define one):
create_workflow_agent(
model="openai:gpt-5.5",
subagents=[
{"name": "researcher", "description": "Researches one topic.", "system_prompt": "Return 3 concise bullets."},
{"name": "writer", "description": "Writes a synthesis.", "system_prompt": "Combine inputs into a tight brief."},
],
)
Use the middleware directly — each mode is an AgentMiddleware:
from deepagents import create_deep_agent
from deepflow import WorkflowMiddleware, TaskListMiddleware
create_deep_agent(model="openai:gpt-5.5", middleware=[TaskListMiddleware(model="openai:gpt-5.5")])
Examples
| file | shows |
|---|---|
examples/workflow_demo.py |
1 · creating workflows — three databases researched in parallel, then a synthesis step fans in; the plan + every step streamed live |
examples/workflow_with_tasks_demo.py |
2 · a workflow with a task list — enable_todos=True, a specified list dispatched in disjoint batches with a per-worker visual of each slice, its read_todos, and its results |
examples/build_library_demo.py |
a workflow builds a small Python library in a real shell — plain Deep Agent vs. workflow, tokens/turns compared |
examples/explore_then_workflow_demo.py |
the agent does ordinary tool calls first, then authors a workflow once it knows what to fan out over |
OPENAI_API_KEY=… uv run python examples/workflow_demo.py
OPENAI_API_KEY=… uv run python examples/workflow_with_tasks_demo.py
How it relates to Deep Agents
deepflow depends on deepagents and only uses its public surface (create_deep_agent, the middleware= extension point). It doesn't fork or patch internals, so it rides along with upstream Deep Agents releases.
License
MIT — see LICENSE. Built on top of Deep Agents (MIT).
Contributing
See CONTRIBUTING.md.
Project details
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 deepflow_agents-0.4.0.tar.gz.
File metadata
- Download URL: deepflow_agents-0.4.0.tar.gz
- Upload date:
- Size: 40.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47cb36434f28eb70d3aa2df1d4722f9c32b0a4f3f4e9ac1316ddb9ea2447b5ec
|
|
| MD5 |
d40abe70e18c71b134f9f91cb78873b4
|
|
| BLAKE2b-256 |
91f997069e55483ec709ab0f7b17563b6e3457f6558a7ae9c8fff459bc56516b
|
File details
Details for the file deepflow_agents-0.4.0-py3-none-any.whl.
File metadata
- Download URL: deepflow_agents-0.4.0-py3-none-any.whl
- Upload date:
- Size: 29.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a45c18c950cdfad33dd4d90961a935589b2351671c29cf0d9cfd20e9b13dda3
|
|
| MD5 |
6d28462d494023082bb762973217cfc9
|
|
| BLAKE2b-256 |
4cd30f7dd698e67f517fc94cdd38c83e9601dbe23452e2f9f4f796f49d2fb575
|