Configure agentflow.json
agentflow.json is the single source of truth for how your API server behaves. This guide walks through each field with examples and explains when to use each option. For a complete reference, see agentflow.json Configuration Reference.
Basic structure
Every agentflow.json must have at least an agent field:
{
"agent": "graph.react:app"
}
This tells the server where to load your compiled graph from. All other fields are optional but recommended for production.
Core fields
agent (required)
The import path to your compiled graph, in the format module:attribute:
{
"agent": "graph.react:app"
}
This import happens when the server starts. The module graph.react is imported and the app attribute is retrieved. The app must be a CompiledGraph instance:
# graph/react.py
from agentflow.core.graph import StateGraph
state_graph = StateGraph(MyState)
# ... add nodes, edges ...
app = state_graph.compile() # app is the compiled graph
Why it matters: If this import fails, the server will not start. Common issues:
- The module does not exist (typo in the path)
- The attribute does not exist on the module
- The module has syntax errors
Debugging: Test the import manually:
python -c "from graph.react import app; print(type(app))"
env (optional)
Path to a .env file containing environment variables:
{
"agent": "graph.react:app",
"env": ".env"
}
The server uses python-dotenv to load this file before importing your graph module. This is useful for:
- Development secrets (API keys, database URLs) that should not be committed to git
- Local overrides of production variables for testing
Example .env:
# API keys
GOOGLE_API_KEY=sk-abc123...
OPENAI_API_KEY=sk-def456...
# Database connections
REDIS_URL=redis://localhost:6379/0
DATABASE_URL=postgresql://user:pass@localhost/mydb
# Feature flags
ENABLE_STREAMING=true
Your graph code accesses them:
import os
api_key = os.environ["GOOGLE_API_KEY"]
Important: Never commit .env files to version control. Add .env to .gitignore.
In production: Pass environment variables via container/process environment, not .env files:
export GOOGLE_API_KEY=...
export REDIS_URL=...
agentflow api --no-reload
Persistence: Checkpointer
Without a checkpointer
Each API request is stateless. There is no conversation history. The next request forgets all prior context.
With a checkpointer
Conversation state is saved after each step. By providing a thread_id, you can resume a conversation later:
# First request
curl -X POST http://localhost:8000/v1/graph/invoke \
-d '{
"messages": [{"role": "user", "content": "What is my name?"}],
"config": {"thread_id": "user-123"}
}'
# Second request (same thread, context is preserved)
curl -X POST http://localhost:8000/v1/graph/invoke \
-d '{
"messages": [{"role": "user", "content": "Remind me what I just asked."}],
"config": {"thread_id": "user-123"}
}'
Adding a checkpointer
Development (in-memory, lost on restart):
# graph/dependencies.py
from agentflow.storage.checkpointer import InMemoryCheckpointer
my_checkpointer = InMemoryCheckpointer()
{
"agent": "graph.react:app",
"checkpointer": "graph.dependencies:my_checkpointer"
}
Fast and easy, but state is lost when the server restarts.
Production (PostgreSQL + Redis):
Install the optional dependency:
pip install 10xscale-agentflow[pg_checkpoint]
Create the checkpointer:
# graph/dependencies.py
from agentflow.storage.checkpointer import PgCheckpointer
my_checkpointer = PgCheckpointer(
db_url="postgresql+asyncpg://user:password@localhost/agentflow",
redis_url="redis://localhost:6379/0",
table="state_checkpoints", # Optional: customize table name
)
{
"agent": "graph.react:app",
"checkpointer": "graph.dependencies:my_checkpointer"
}
This persists all state to a PostgreSQL database, enabling:
- Durable conversation history across server restarts
- Multi-server deployments (shared state)
- Data retention and compliance
Health check: Verify the checkpointer is accessible:
curl -X GET http://localhost:8000/v1/threads
If the database is unreachable, you get a 500 error.
Memory: Store
A store is for long-term semantic memory (embeddings, facts). It is separate from the checkpointer (which holds conversation history).
Adding a store
Example using Qdrant for vector search:
# graph/dependencies.py
from agentflow.storage.store import create_cloud_qdrant_store
my_store = create_cloud_qdrant_store(
url="https://your-qdrant-cloud-instance.com",
api_key="your-qdrant-api-key",
collection="agent-memories",
)
{
"agent": "graph.react:app",
"store": "graph.dependencies:my_store"
}
Your graph can then retrieve and store semantic information (via embeddings) that persists across conversations.
Thread name generation
When a new conversation thread is created, generate a human-readable name instead of a raw UUID:
# graph/thread_name_generator.py
from agentflow_cli.src.app.utils.thread_name_generator import ThreadNameGenerator
class MyThreadNameGenerator(ThreadNameGenerator):
async def generate_name(self, messages: list[str]) -> str:
# Optionally use message context or an LLM
return "thoughtful-conversation"
my_generator = MyThreadNameGenerator()
{
"agent": "graph.react:app",
"thread_name_generator": "graph.thread_name_generator:MyThreadNameGenerator"
}
When a new thread is created via the API, it automatically gets a readable name like exploring-ideas instead of 550e8400-e29b-41d4-a716-446655440000.
Authentication
No authentication (development only)
{
"agent": "graph.react:app",
"auth": null
}
Or omit the auth field entirely. All requests are accepted.
JWT authentication
{
"agent": "graph.react:app",
"auth": "jwt"
}
Add required environment variables:
export JWT_SECRET_KEY="your-long-random-secret-key-at-least-32-chars"
export JWT_ALGORITHM="HS256"
agentflow api
Clients must now provide a valid JWT token in every request:
curl -H "Authorization: Bearer <token>" http://localhost:8000/v1/graph/invoke
Requests without a valid token get a 401 Unauthorized response.
Custom authentication
For integration with external identity providers (OAuth, SAML, custom API keys):
# graph/auth.py
from agentflow_cli.src.app.core.auth.base_auth import BaseAuth
class MyCustomAuth(BaseAuth):
async def authenticate(self, request) -> dict | None:
# Check custom header or token
api_key = request.headers.get("X-API-Key")
if not api_key:
return None
# Verify against your system
user = await verify_api_key(api_key)
if not user:
return None
# Return user info for authorization later
return {"user_id": user.id, "role": user.role}
{
"agent": "graph.react:app",
"auth": {
"method": "custom",
"path": "graph.auth:MyCustomAuth"
}
}
Authorization (fine-grained permissions)
After authenticating, restrict which users can access which endpoints:
# graph/auth.py
class MyAuthorizationBackend:
async def authorize(self, user: dict, resource: str, action: str) -> bool:
role = user.get("role", "guest")
# Only admins can invoke graph
if resource == "graph" and action == "invoke":
return role == "admin"
# Only owners can view threads
if resource == "checkpointer" and action == "read":
return role in ("admin", "user")
return False
{
"agent": "graph.react:app",
"auth": "jwt",
"authorization": "graph.auth:MyAuthorizationBackend"
}
Environment-specific configs
Create separate config files for dev, staging, and production:
config/
dev.json
staging.json
prod.json
config/dev.json (local development, no persistence):
{
"agent": "graph.react:app",
"env": ".env.dev",
"auth": null
}
config/staging.json (pre-production, with checkpointer):
{
"agent": "graph.react:app",
"env": ".env.staging",
"checkpointer": "graph.dependencies:pg_checkpointer",
"store": "graph.dependencies:qdrant_store",
"auth": "jwt"
}
config/prod.json (production, all features):
{
"agent": "graph.react:app",
"env": ".env.prod",
"checkpointer": "graph.dependencies:pg_checkpointer_prod",
"store": "graph.dependencies:qdrant_store_prod",
"thread_name_generator": "graph.thread_name_generator:ProductionNameGenerator",
"auth": "jwt",
"authorization": "graph.auth:ProductionAuthorizationBackend"
}
Start the server with the appropriate config:
# Development
agentflow api --config config/dev.json
# Production
MODE=production agentflow api --config config/prod.json --no-reload
Validating your config
Before deploying, verify the config is valid:
python -c "
import json
with open('agentflow.json') as f:
config = json.load(f)
print('Config is valid JSON')
print(f'Agent path: {config.get(\"agent\")}')
"
Then test that the graph can be imported:
python -c "from graph.react import app; print(f'Graph loaded: {type(app)}')
If both succeed, your config is likely valid. Start the server and test an endpoint:
agentflow api &
curl http://127.0.0.1:8000/ping
Common config issues
"Module not found" error
- Check the module path is spelled correctly
- Verify the module exists in your project
- Try importing manually:
python -c "from graph.react import app"
"Attribute not found" error
- Verify the attribute name exists on the module
- In the example,
graph.react:apprequires anappvariable ingraph/react.py
"Checkpointer database connection failed"
- Verify the database URL is correct
- Verify the database is running and accessible
- Test the connection:
psql postgresql://user:pass@localhost/agentflow
"JWT_SECRET_KEY not found"
- Set the environment variable:
export JWT_SECRET_KEY=... - Or add it to your
.envfile and pointagentflow.jsonto it:"env": ".env"