Skip to main content

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 the app variable from graph/agent.py
  • "mypackage.graph:build_graph" — calls build_graph() synchronously
  • "mypackage.graph:async_build" — calls await 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:

VariableDescription
JWT_SECRET_KEYSigning secret (32+ characters recommended in production)
JWT_ALGORITHMAlgorithm 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:

ConditionError codeMessage
No tokenREVOKED_TOKEN"Invalid token, please login again"
Token expiredEXPIRED_TOKEN"Token has expired, please login again"
Invalid signatureINVALID_TOKEN"Invalid token, please login again"
user_id missingINVALID_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

FieldTypeRequiredDefaultDescription
enabledboolNotrueSet to false to disable rate limiting while keeping the config in place.
backendstringNo"memory"Storage backend: "memory", "redis", or "custom".
requestsintNo100Maximum requests allowed in the time window. Must be > 0.
windowintNo60Time window in seconds. Must be > 0.
bystringNo"ip"Key used for bucketing: "ip" (per-client) or "global" (all clients share one counter).
trusted_proxy_headersboolNofalseWhen 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_pathsstring[]No[]Request paths that bypass rate limiting entirely.
fail_openboolNotrueWhen 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:

FieldTypeDefaultDescription
urlstringRedis connection URL. Supports environment variable expansion: "$REDIS_URL".
prefixstring"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:

  1. The path passed via --config (default agentflow.json)
  2. agentflow.json in the current directory
  3. .agentflow.json in the current directory
  4. agentflow.config.json in the current directory

The first file found is used.


Complete field summary

FieldTypeRequiredDefaultPurpose
agentstringYesGraph import path (module:attribute)
envstring | nullNonull.env file to load
authnull | "jwt" | objectNonullAuthentication backend
authorizationstring | nullNonullRBAC authorization backend
checkpointerstring | nullNonullThread state persistence backend
injectqstring | nullNonullDependency injection container
storestring | nullNonullSemantic memory store backend
redisstring | nullNonullRedis URL for shared components
thread_name_generatorstring | nullNonullHuman-readable thread name generator
rate_limitobject | nullNonullRate limiting configuration