Design patterns, tips, and guidelines for building effective OpenAgentFlow workflows.
Each agent should have a single, clear responsibility. Avoid agents that try to do everything.
Good:
agent Analyzer {
instructions: "Analyze sentiment. Return: positive, negative, or neutral."
outputs: [sentiment]
}
Avoid:
agent DoEverything {
instructions: "Analyze sentiment, categorize the feedback, extract key issues, and draft a response."
outputs: [sentiment, category, key_issues, response]
}
Use descriptive, role-based names that communicate what the agent does:
| Good | Avoid |
|---|---|
SentimentAnalyzer |
Agent1 |
ResponseDrafter |
Step3 |
QualityReviewer |
Processor |
Before writing agents, plan your state variables. Think about:
state {
// Inputs
raw_text: string @required @description("Text to process")
// Intermediates
analysis: string
entities: list[string]
// Outputs
summary: string
confidence_score: float
}
@required — for inputs that must be provided before workflow starts@default(value) — for optional fields with sensible defaults@description("text") — for documentation and future tooling@reducer("append") — when multiple agents may contribute to the same listLLMs perform better with clear, structured instructions:
Good:
agent Categorizer {
instructions: """
Categorize the customer feedback into exactly one category:
- bug_report: Technical issues or errors
- feature_request: New feature suggestions
- praise: Positive feedback
- complaint: Negative experience
- question: Asking for help
Return ONLY the category name, nothing else.
"""
}
Avoid:
agent Categorizer {
instructions: "Categorize the feedback"
}
When agents need to produce structured data, explicitly describe the expected format:
agent DataExtractor {
instructions: """
Extract the following from the document:
- title: The document title
- date: The publication date (ISO 8601 format)
- keywords: A list of 3-5 keywords
Return a JSON object with these fields:
{
"title": "...",
"date": "YYYY-MM-DD",
"keywords": ["...", "..."]
}
"""
outputs: [title, date, keywords]
}
This is especially important for multi-output agents, where the generated code attempts to parse JSON.
Long instructions can dilute the model’s focus. If instructions get too long:
Choose temperature based on the task type:
| Temperature | Use Case | Example Agents |
|---|---|---|
| 0.0 – 0.2 | Classification, extraction, factual analysis | SentimentAnalyzer, DataValidator |
| 0.2 – 0.5 | Structured analysis, categorization | Categorizer, Architect |
| 0.5 – 0.8 | General-purpose, balanced | Summarizer, Planner |
| 0.8 – 1.2 | Creative writing, brainstorming | Writer, IdeaGenerator |
Pattern: In a pipeline, use lower temperatures upstream (analysis) and higher temperatures downstream (content generation).
agent Analyst { temperature: 0.2 } // Deterministic analysis
agent Planner { temperature: 0.4 } // Structured planning
agent Writer { temperature: 0.8 } // Creative output
GOOGLE_API_KEY is availableprovider: "openai" overrideOPENAI_API_KEY is available but Gemini is notYou can use different models for different agents:
agent FastClassifier {
model: "gemini-2.0-flash" // Fast, cheap
temperature: 0.1
}
agent DeepAnalyst {
model: "gpt-4" // More capable
temperature: 0.3
}
Linear pipelines are the simplest and most predictable:
flow {
start -> A
A -> B
B -> C
C -> end
}
When tasks don’t depend on each other:
flow {
start -> Classifier
Classifier -> SentimentAnalyzer
Classifier -> TopicExtractor
SentimentAnalyzer -> end
TopicExtractor -> end
}
endA common mistake is creating dead-end agents:
// BAD — Logger has no path to end
flow {
start -> Processor
Processor -> Logger
Processor -> end
}
// Fix: Add Logger -> end
Only create state variables that are actually read or written by agents. The validator will warn about unused variables.
state {
// Good
customer_feedback: string
sentiment_classification: string
// Avoid
input: string
result: string
}
@requiredIf a variable must be provided for the workflow to function:
state {
source_text: string @required
query: string @required
summary: string // Generated by agents
}
Always validate first — it’s instant and free:
oaf validate my-workflow.oaf
Start with minimal test data to verify the pipeline works:
{
"source_text": "A short test sentence."
}
Inspect the generated Python before running:
oaf compile my-workflow.oaf -t langgraph -o preview.py
Check that:
If you modify a workflow, compile to IR and compare against previous output:
oaf compile my-workflow.oaf > new-ir.json
diff old-ir.json new-ir.json
Validation errors include precise source locations:
[ERROR] workflow.oaf:15:5 — Undefined state variable in agent "Writer": "summry"
The line and column point you directly to the issue.
Warnings don’t block compilation but often indicate real problems:
[WARNING] State variable "old_field" is never referenced
[WARNING] Agent "Processor" does not declare inputs or outputs
If flow validation fails, visualize the graph to understand the topology:
oaf graph my-workflow.oaf
Keep test data in JSON files alongside your workflows:
my-project/
├── workflows/
│ ├── analysis.oaf
│ └── report.oaf
├── data/
│ ├── analysis-input.json
│ └── report-input.json
└── output/
├── analysis.py
└── report.py
Don’t rely on run for production use. Compile to Python files and manage them like any other code:
oaf compile workflow.oaf -t langgraph -o workflow.py
# Then deploy workflow.py alongside your Python application