Your First Flow¶
This guide walks through building a flow step by step, explaining each concept as we go.
What is a Flow?¶
A Flow is just an async Python function that orchestrates agent calls:
There's no special syntax or DSL. It's regular Python.
Step 1: Create an Agent¶
import agentic_flow as af
assistant = af.Agent(
name="assistant",
instructions="You are a helpful assistant.",
model="gpt-5.2",
)
Agent is a thin wrapper around the OpenAI Agents SDK's Agent. All SDK arguments pass through unchanged.
Step 2: Call the Agent¶
This is the Call-Spec discipline in action. Calling an agent returns a specification, not a result.
Step 3: Execute with await¶
Only await triggers execution. This makes it clear exactly where side effects occur.
Step 4: Add Modifiers¶
Modifiers configure execution behavior without triggering it:
# Streaming mode
result = await assistant("Hello").stream()
# Silent mode (no UI output)
result = await assistant("Hello").silent()
# Isolated mode (no session context)
result = await assistant("Hello").isolated()
# Limit execution turns
result = await assistant("Hello").max_turns(5)
# Combine them (order doesn't matter)
result = await assistant("Hello").stream().silent()
result = await assistant("Hello").stream().max_turns(10)
Step 5: Add Phases¶
Phases group related agent calls and manage boundaries:
import agentic_flow as af
async def my_flow(user_message: str) -> str:
async with af.phase("Thinking"):
thought = await assistant("Think about: " + user_message).stream()
async with af.phase("Responding", persist=True):
return await assistant(f"Based on: {thought}, respond").stream()
What phase() does:
- Creates a semantic boundary for UI display
- Manages a temporary context for agent calls within the phase
- Automatically cleans up when the phase ends
- With
persist=True, saves the last exchange to the session
Step 6: Add a Runner¶
The Runner provides session management and executes the flow:
import agentic_flow as af
from agents import SQLiteSession
runner = af.Runner(
flow=my_flow,
session=SQLiteSession("chat.db"),
)
# Execute
result = await runner("Hello!")
Step 7: Add a Handler (Optional)¶
For CLI or custom output, add a handler:
def my_handler(event):
# Handle streaming events
if hasattr(event, "data") and hasattr(event.data, "delta"):
print(event.data.delta, end="", flush=True)
runner = af.Runner(
flow=my_flow,
session=SQLiteSession("chat.db"),
handler=my_handler,
)
Complete Example¶
import agentic_flow as af
from agents import SQLiteSession
# Agent
assistant = af.Agent(
name="assistant",
instructions="You are a thoughtful assistant.",
model="gpt-5.2",
)
# Flow
async def thoughtful_flow(user_message: str) -> str:
# Internal thinking (not persisted)
async with af.phase("Thinking"):
thought = await assistant(
f"Think step by step about: {user_message}"
).stream()
# User-facing response (persisted)
async with af.phase("Response", persist=True):
return await assistant(
f"Based on your thinking:\n{thought}\n\nProvide a clear response."
).stream()
# Handler for CLI output
def print_handler(event):
if hasattr(event, "data") and hasattr(event.data, "delta"):
print(event.data.delta, end="", flush=True)
# Runner
runner = af.Runner(
flow=thoughtful_flow,
session=SQLiteSession("chat.db"),
handler=print_handler,
)
# Run
if __name__ == "__main__":
result = runner.run_sync("Explain why the sky is blue.")
print() # Newline after streaming output
Summary¶
| Concept | Purpose |
|---|---|
Agent |
Wraps SDK Agent, makes it callable |
agent(prompt) |
Creates ExecutionSpec (no execution) |
await spec |
Executes the agent |
.stream() / .silent() / .isolated() |
Modifiers (no execution) |
phase() |
Semantic boundary with automatic cleanup |
Runner |
Executes flows with session injection |
Next: Call-Spec Discipline