agentflow.json configuration
agentflow.json is the single configuration file that controls every behavior of the AgentFlow server. The agentflow api command reads it at startup. This page documents every supported field.
Minimal example
{
"agent": "graph.agent:app",
"env": ".env",
"auth": null,
"thread_name_generator": null
}
Full example
{
"agent": "graph.agent:app",
"env": ".env",
"auth": {
"method": "jwt"
},
"authorization": "graph.agent:MyAuthorizationBackend",
"checkpointer": null,
"injectq": "graph.agent:container",
"store": null,
"redis": null,
"thread_name_generator": "graph.thread_name_generator:MyNameGenerator",
"rate_limit": {
"enabled": true,
"backend": "redis",
"requests": 100,
"window": 60,
"by": "ip",
"trusted_proxy_headers": true,
"exclude_paths": ["/health", "/docs", "/redoc", "/openapi.json"],
"redis": {
"url": "redis://localhost:6379/0",
"prefix": "agentflow:rate-limit"
},
"fail_open": true
}
}
Field reference
agent (required)
Type: string
Import path to your compiled graph, in module:attribute format.
"agent": "graph.agent:app"
The server imports the module and retrieves the attribute. The attribute must be a CompiledGraph instance, a callable that returns one, or an async callable that returns one.
"graph.agent:app"— retrieves theappvariable fromgraph/agent.py"mypackage.graph:build_graph"— callsbuild_graph()synchronously"mypackage.graph:async_build"— callsawait async_build()asynchronously
The module must be importable from the working directory where agentflow api is started. The loader inserts the project root into sys.path automatically.
env
Type: string | null — Default: null
Path to a .env file. Loaded with python-dotenv before the graph module is imported, so environment variables are available during graph initialization.
"env": ".env"
Environment variable references in the value are expanded. If the file does not exist, it is silently skipped. Setting null or omitting the field disables .env loading.
auth
Type: null | "jwt" | object — Default: null
Controls authentication. When null, all requests are accepted without credentials (skip the entire auth layer).
No authentication
"auth": null
All endpoints pass through without credential checks. Safe for internal networks or local development. Not recommended for public deployments.
JWT authentication
"auth": "jwt"
or equivalently:
"auth": {"method": "jwt"}
Validates Authorization: Bearer <token> using PyJWT. Requires two environment variables to be set before the server starts:
| Variable | Description |
|---|---|
JWT_SECRET_KEY | Signing secret (32+ characters recommended in production) |
JWT_ALGORITHM | Algorithm used to sign tokens (default HS256) |
If either variable is missing the server will refuse to start.
The JWT payload must contain a user_id field. The decoded payload is merged into the graph's run config and available to nodes as config["user_id"].
Error responses:
| Condition | Error code | Message |
|---|---|---|
| No token | REVOKED_TOKEN | "Invalid token, please login again" |
| Token expired | EXPIRED_TOKEN | "Token has expired, please login again" |
| Invalid signature | INVALID_TOKEN | "Invalid token, please login again" |
user_id missing | INVALID_TOKEN | "Invalid token, user_id missing" |
Install PyJWT:
pip install "10xscale-agentflow-cli[jwt]"
Custom authentication
"auth": {
"method": "custom",
"path": "auth.agent_auth:AgentAuth"
}
Loads and instantiates the class at the given import path. The class must subclass BaseAuth and implement the authenticate method:
from agentflow_cli import BaseAuth
from fastapi import Request, Response
from fastapi.security import HTTPAuthorizationCredentials
class AgentAuth(BaseAuth):
def authenticate(
self,
request: Request,
response: Response,
credential: HTTPAuthorizationCredentials,
) -> dict | None:
# Validate credential.credentials
# Return dict with at least "user_id" on success
# Raise an exception on failure
return {"user_id": "user-123", "role": "admin"}
The return value is merged into the graph's run config. The server checks that user_id is present; if not, a warning is logged but the request proceeds.
authorization
Type: string | null — Default: null
Import path to a custom AuthorizationBackend class or instance, in module:attribute format.
"authorization": "graph.agent:MyAuthorizationBackend"
When null, the built-in DefaultAuthorizationBackend is used, which allows all requests as long as a valid user_id is present.
To implement custom RBAC:
from agentflow_cli.src.app.core.auth.authorization import AuthorizationBackend
class MyAuthorizationBackend(AuthorizationBackend):
async def authorize(
self,
user: dict,
resource: str,
action: str,
resource_id: str | None = None,
**context,
) -> bool:
if user.get("role") == "admin":
return True
if resource == "graph" and action == "invoke":
return True
return False
The resource and action values match the permission reference in the API docs.
checkpointer
Type: string | null — Default: null
Import path to a BaseCheckpointer instance, in module:attribute format.
"checkpointer": "graph.agent:my_checkpointer"
When null, threads are stateless. Thread endpoints (/v1/threads/...) require a checkpointer to be configured.
The server loads the object at the path and binds it as a BaseCheckpointer in the dependency injection container. Both InMemoryCheckpointer (dev) and PgCheckpointer (production) implement this interface.
See Checkpointing for setup instructions.
injectq
Type: string | null — Default: null
Import path to an InjectQ container instance, in module:attribute format.
"injectq": "graph.agent:container"
When set, the server uses your container as the global DI container, inheriting all your bindings (database connections, API clients, custom services, etc.). The container must be an InjectQ instance.
When null, the server creates a default InjectQ container automatically.
# graph/agent.py
from injectq import InjectQ
from my_services import DatabaseService, ApiClient
container = InjectQ()
container.bind(DatabaseService)
container.bind_instance(ApiClient, ApiClient(api_key=os.environ["MY_API_KEY"]))
store
Type: string | null — Default: null
Import path to a BaseStore instance, in module:attribute format.
"store": "graph.agent:my_store"
When set, enables the /v1/store/... endpoints for semantic memory storage and retrieval. The object must implement BaseStore.
When null, store endpoints are mounted but return errors because no backend is bound.
redis
Type: string | null — Default: null
Redis connection URL. Used as a fallback Redis URL for features that need Redis (e.g. checkpointer, publisher).
"redis": "redis://localhost:6379/0"
Note: the rate_limit block has its own redis sub-key that takes precedence for rate limiting. This top-level redis key is for other components.
thread_name_generator
Type: string | null — Default: null
Import path to a ThreadNameGenerator class or instance, in module:attribute format.
"thread_name_generator": "graph.thread_name_generator:MyNameGenerator"
When set, the server calls the generator when a new thread is created to produce a human-readable name (e.g. "thoughtful-dialogue").
The class must subclass ThreadNameGenerator:
from agentflow_cli.src.app.utils.thread_name_generator import ThreadNameGenerator
class MyNameGenerator(ThreadNameGenerator):
def generate(self) -> str:
# Return any string
return "session-" + str(int(time.time()))
When null, threads get IDs but no human-readable names.
rate_limit
Type: object | null — Default: null
When present and enabled: true, applies rate limiting to all incoming requests.
"rate_limit": {
"enabled": true,
"backend": "memory",
"requests": 100,
"window": 60,
"by": "ip",
"trusted_proxy_headers": false,
"exclude_paths": ["/health", "/docs", "/redoc", "/openapi.json"],
"fail_open": true
}
rate_limit fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | bool | No | true | Set to false to disable rate limiting while keeping the config in place. |
backend | string | No | "memory" | Storage backend: "memory", "redis", or "custom". |
requests | int | No | 100 | Maximum requests allowed in the time window. Must be > 0. |
window | int | No | 60 | Time window in seconds. Must be > 0. |
by | string | No | "ip" | Key used for bucketing: "ip" (per-client) or "global" (all clients share one counter). |
trusted_proxy_headers | bool | No | false | When true, reads the client IP from X-Forwarded-For / X-Real-IP headers. Enable only when the server is behind a trusted reverse proxy. |
exclude_paths | string[] | No | [] | Request paths that bypass rate limiting entirely. |
fail_open | bool | No | true | When the backend is unavailable: true = allow the request, false = deny it (429). |
Memory backend (default)
"rate_limit": {
"enabled": true,
"backend": "memory",
"requests": 100,
"window": 60,
"by": "ip"
}
Uses an in-process sliding window counter. Counters are lost on server restart. Not shared across multiple server instances. Suitable for single-process development and testing.
Redis backend (production)
"rate_limit": {
"enabled": true,
"backend": "redis",
"requests": 200,
"window": 60,
"by": "ip",
"trusted_proxy_headers": true,
"exclude_paths": ["/health", "/docs", "/redoc", "/openapi.json"],
"redis": {
"url": "redis://localhost:6379/0",
"prefix": "agentflow:rate-limit"
},
"fail_open": true
}
The redis sub-key accepts:
| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | Redis connection URL. Supports environment variable expansion: "$REDIS_URL". |
prefix | string | "agentflow:rate-limit" | Key prefix for all rate-limit keys in Redis. |
The url field also accepts a bare string as shorthand: "redis": "redis://localhost:6379/0".
Redis rate limits survive server restarts and are shared across all server instances behind a load balancer.
Custom backend
"rate_limit": {
"enabled": true,
"backend": "custom",
"requests": 100,
"window": 60,
"by": "ip"
}
When backend is "custom", bind a BaseRateLimitBackend instance in your InjectQ container before the server starts.
Environment variable expansion
String values in agentflow.json support environment variable expansion. Use standard shell syntax:
"redis": {
"url": "$REDIS_URL"
}
or with braces:
"redis": {
"url": "${REDIS_URL}"
}
The server raises a ValueError at startup if a referenced variable is not set in the environment.
Config discovery
The agentflow api command looks for the config file in this order:
- The path passed via
--config(defaultagentflow.json) agentflow.jsonin the current directory.agentflow.jsonin the current directoryagentflow.config.jsonin the current directory
The first file found is used.
Complete field summary
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
agent | string | Yes | — | Graph import path (module:attribute) |
env | string | null | No | null | .env file to load |
auth | null | "jwt" | object | No | null | Authentication backend |
authorization | string | null | No | null | RBAC authorization backend |
checkpointer | string | null | No | null | Thread state persistence backend |
injectq | string | null | No | null | Dependency injection container |
store | string | null | No | null | Semantic memory store backend |
redis | string | null | No | null | Redis URL for shared components |
thread_name_generator | string | null | No | null | Human-readable thread name generator |
rate_limit | object | null | No | null | Rate limiting configuration |