Skip to main content

agentflow.json configuration

agentflow.json is the configuration file the CLI reads at startup. Place it in your project root (next to your graph/ folder).

Minimal example

{
"agent": "graph.react:app"
}

Full example

{
"agent": "graph.react:app",
"checkpointer": "graph.dependencies:my_checkpointer",
"store": "graph.dependencies:my_store",
"injectq": "graph.dependencies:container",
"thread_name_generator": "graph.thread_name_generator:MyNameGenerator",
"authorization": "graph.auth:my_authorization_backend",
"redis": "redis://localhost:6379/0",
"env": ".env",
"auth": "jwt",
"rate_limit": {
"enabled": true,
"backend": "memory",
"requests": 100,
"window": 60,
"by": "ip",
"exclude_paths": ["/health", "/docs", "/redoc", "/openapi.json"]
},
"websocket": {
"max_connections": 100
},
"observability": {
"level": "standard",
"logfire": {"enabled": true, "service_name": "my-agent"},
"langsmith": {"enabled": false, "project": "my-agent"}
}
}

Fields

agent (required)

The import path to your compiled CompiledGraph, in the format module.path:variable.

"agent": "graph.react:app"
  • graph.react — the Python module path (relative to the project root)
  • app — the variable name that holds the compiled graph

The CLI imports this module at startup and calls the variable as the graph for every request.


checkpointer

Import path to a BaseCheckpointer instance.

"checkpointer": "graph.dependencies:my_checkpointer"

If omitted, the graph uses no checkpointer and each request is stateless.

In graph/dependencies.py:

from agentflow.storage.checkpointer import InMemoryCheckpointer

my_checkpointer = InMemoryCheckpointer()

store

Import path to a BaseStore instance.

"store": "graph.dependencies:my_store"

Required if you want to use the /v1/store/* endpoints.


injectq

Import path to an injectq dependency injection container.

"injectq": "graph.dependencies:container"

Use this when your graph nodes or tools depend on services that need to be resolved at startup.


thread_name_generator

Import path to a class that generates display names for threads.

"thread_name_generator": "graph.thread_name_generator:MyNameGenerator"

The class must subclass ThreadNameGenerator and implement async def generate_name(self, messages: list[str]) -> str. An already-created instance is also accepted. See thread name generator for the full interface.


authorization

Controls object-level access (thread ownership), per-endpoint scopes, and storage isolation. Accepted values:

ValueBehaviour
null (unset)Mode-based default: "ownership" in production, "allow_all" in development.
"ownership"Owner-only: a thread is accessible only to the user who created it.
"allow_all" (aliases "default", "none")Any authenticated user may do anything.
"module:attr"A custom AuthorizationBackend.
{ "backend": "rbac", ... }Role-based access control (below). "role_based" and "roles" select the same backend, and type is an accepted alias for backend.

An unrecognised string, or an object whose backend name is not one of the RBAC spellings, raises a ValueError at startup rather than falling back to a permissive default.

Built-in owner-only access (no code):

"authorization": "ownership"

Role-based access control — maps roles to scopes on top of owner-only isolation:

"authorization": {
"backend": "rbac",
"roles": {
"admin": ["*"],
"member": ["graph:invoke", "graph:stream", "graph:read", "checkpointer:read"]
},
"default_scopes": ["graph:read"],
"isolation": "owner"
}

See Auth for the scope catalog, the custom-backend interface, and how the isolation policy reaches the storage layer.


env

Path to a .env file that is loaded before the graph module is imported.

"env": ".env"

Variables in this file are available to all modules as os.environ values.


auth

Authentication method. Accepted values:

ValueDescription
nullNo authentication (default)
"jwt"JWT bearer token authentication
{"method": "custom", "path": "module:backend"}Custom auth backend

JWT auth:

"auth": "jwt"

Requires JWT_SECRET_KEY and JWT_ALGORITHM environment variables.

Custom auth:

"auth": {
"method": "custom",
"path": "graph.auth:MyAuthBackend"
}

See Auth reference for the custom backend interface.


rate_limit

Sliding-window rate limiter configuration.

"rate_limit": {
"enabled": true,
"backend": "memory",
"requests": 100,
"window": 60,
"by": "ip",
"exclude_paths": ["/health", "/docs", "/redoc", "/openapi.json"]
}

Omit this field (or set it to null) to disable rate limiting entirely.

Rate limiting also gates WebSocket handshakes on /v1/graph/ws and /v1/graph/live, sharing the same backend and bucket as REST requests.

For the full field reference (including by: "user" and trusted_proxy_hops), backend options, response headers, and the custom backend interface see Rate Limiting.


websocket

Per-process limits for the WebSocket endpoints.

"websocket": {
"max_connections": 100
}
FieldTypeDefaultDescription
max_connectionsinteger or nullnull (unlimited)Maximum concurrent WebSocket connections this server process accepts, counted across /v1/graph/ws and /v1/graph/live together. null or 0 means unlimited. Negative values raise a ValueError at startup.

Omit the block entirely to leave connections unlimited.

Exceeding the cap refuses the handshake before accept() with WebSocket close code 1013 (Try Again Later), so the client gets a clean rejection instead of a half-open socket. The slot is released when the handler returns or the client disconnects.

The counter is per process, like the in-memory rate-limit backend. With N workers the effective cluster-wide limit is max_connections x N, so size it per worker.


redis

A Redis connection URL used by the server itself, as a string.

"redis": "redis://localhost:6379/0"

It currently backs the L2 tier of the thread-ownership cache used by the ownership and rbac authorization backends. When it is unset the server falls back to the REDIS_URL environment variable; when neither is set, or the redis package is not installed, the cache runs in-process only and logs a warning at startup.

This is separate from rate_limit.redis, which configures the rate limiter's own connection. Set both if you want both features backed by Redis.


observability

Declarative tracing setup for Logfire and LangSmith.

"observability": {
"level": "standard",
"logfire": {"enabled": true, "service_name": "my-agent"},
"langsmith": {"enabled": true, "project": "my-agent", "endpoint": null}
}

The block is passed through to the core framework's observability setup. Secrets stay in the environment: LOGFIRE_TOKEN and LANGSMITH_API_KEY are read from there and are never read from this file.


test

Default settings for agentflow test. All fields are optional. CLI flags always take precedence.

"test": {
"path": "tests",
"coverage": true,
"coverage_threshold": 70
}
FieldTypeDefaultDescription
pathstringDefault path passed to pytest when no PATH argument is given on the CLI. Omit to let pytest auto-discover tests.
coveragebooleanfalseEnable coverage collection by default (equivalent to --coverage flag)
coverage_thresholdintegerMinimum coverage percentage required for a passing run. Adds --cov-fail-under=N to the pytest command. Omit to skip threshold enforcement.

Example — enforce 80 % coverage on every run:

{
"agent": "graph.react:app",
"test": {
"path": "tests",
"coverage": true,
"coverage_threshold": 80
}
}

evaluation

Default settings for agentflow eval. All fields are optional. CLI flags always take precedence.

"evaluation": {
"directory": "evals",
"output_dir": "eval_reports",
"threshold": 0.75,
"timestamp_files": true
}
FieldTypeDefaultDescription
directorystring"evals"Directory scanned for eval files when no TARGET argument is given
output_dirstring"eval_reports"Directory where HTML and JSON report files are written
thresholdfloatMinimum pass rate (0.0–1.0) required for a passing run. Omit to skip threshold enforcement.
timestamp_filesbooleantrueAppend a timestamp to report filenames so runs do not overwrite each other

Example — enforce 75 % pass rate and write reports to ci/reports/:

{
"agent": "graph.react:app",
"evaluation": {
"directory": "evals",
"output_dir": "ci/reports",
"threshold": 0.75
}
}

File discovery

The CLI resolves the config file in this order and uses the first one it finds:

  1. The path passed to --config
  2. agentflow.json
  3. .agentflow.json
  4. agentflow.config.json

If none exists, startup fails with a message listing those names.


Environment variable expansion

Expansion is not applied to every string in the file. It applies to the Redis URL in two places: the top-level redis value and rate_limit.redis.

{
"redis": {"url": "${REDIS_URL}"},
"rate_limit": {"redis": {"url": "$RATE_LIMIT_REDIS_URL"}}
}

Both $VAR and ${VAR} forms work, and the value may be given either as a bare string or as an object with a url key. If the variable is not set in the environment, startup fails with:

ValueError: Unresolved environment variable in value: ${REDIS_URL}

Every other secret belongs in the environment rather than in this file. Use the env key to point at a .env, and read the value from the environment in your own code.


Loading order

When the CLI starts:

  1. Reads the config file resolved above
  2. Loads .env if env is set
  3. Imports the module specified in agent and gets the compiled graph
  4. Imports and configures checkpointer, store, injectq, and authorization if set
  5. Starts the FastAPI server