Configure 10xgraph.json

In shortTask guide for setting the common 10xgraph.json keys, wiring a checkpointer, store and auth, and keeping separate configs per environment.

  • 9 min read
  • 15 sections
  • Updated
  • v0.10.0
  • Markdown

The 10xgraph.json file tells the API server which graph to load, how to persist state, who can access it, and how to manage resources. This guide covers the keys you need to configure for a working deployment. For a complete list of every key, type and default, see the configuration reference.

Minimal configuration

Only the agent key is required. It is a module:attribute path that resolves to a compiled graph:

JSON
{
  "agent": "graph.react:app",
  "env": ".env"
}

The module is imported when the server starts. The app variable must be the result of calling state_graph.compile(). If the import fails, the server refuses to start with a clear error. Test your module path by hand before deploying:

Terminal
python -c "from graph.react import app; print(type(app))"

The env key names a dotenv file that the server loads before importing your graph module. Use it for local development secrets (API keys, database URLs). Never commit .env files to version control. In production, pass variables through your container or orchestration platform instead, so sensitive values never touch the filesystem.

Enable thread persistence with a checkpointer

The API server persists conversation history in a checkpointer that you wire into your graph at compile time. Pass it to compile() in your graph module:

Python
# graph/dependencies.py
from tenxgraph.storage.checkpointer import PgCheckpointer

my_checkpointer = PgCheckpointer(
    postgres_dsn="postgresql://user:password@localhost/10xgraph",
    redis_url="redis://localhost:6379/0",
)
Python
# graph/react.py
from graph.dependencies import my_checkpointer

app = state_graph.compile(checkpointer=my_checkpointer)

The server uses the compiled graph’s checkpointer; the checkpointer key in 10xgraph.json is recognized but not applied, so you can remove it if it is present.

Without an explicit checkpointer, compile() falls back to InMemoryCheckpointer, which keeps threads in memory while the process runs. This is fine for local development and testing, but in production it means:

  • Conversations are lost when the process restarts.
  • Each replica maintains its own separate history; the same user hitting a different pod sees a different thread history.

For production, use PgCheckpointer (Postgres + Redis). It requires pip install "10xgraph[pg_checkpoint]" and maintains thread history durably in Postgres while using Redis as a fast cache layer. See Set up checkpointing for the full setup, including SQLite for single-machine deployments.

Set up the memory store

The store key points at a BaseStore instance (not a class). Without it, the /v1/store/* endpoints report that no store is configured. The store powers long-term memory for your agents:

Python
# graph/dependencies.py
from tenxgraph.storage.store import QdrantStore
from tenxgraph.storage.store.embedding import OpenAIEmbedding

# needs: pip install "10xgraph[qdrant,openai]"
my_store = QdrantStore(embedding=OpenAIEmbedding(), path="./qdrant_data")
JSON
{
  "agent": "graph.react:app",
  "store": "graph.dependencies:my_store"
}

The loader validates that the attribute is a BaseStore instance and fails at startup with a clear error if not. For tests, use tenxgraph.qa.testing.InMemoryStore, which satisfies the same interface.

Wire up dependency injection

The injectq key points at an InjectQ container. Use it to inject services at startup that your nodes and tools need, such as a database session factory, an HTTP client, or configuration objects:

Python
# graph/dependencies.py
import os

from injectq import InjectQ


class MyDatabase:
    def __init__(self, dsn: str) -> None:
        self.dsn = dsn


container = InjectQ()
container.bind_instance(MyDatabase, MyDatabase(dsn=os.environ["DATABASE_URL"]))
JSON
{
  "agent": "graph.react:app",
  "injectq": "graph.dependencies:container"
}

The server loads your container at startup and makes it the global instance. Your services land in the same container as the server’s internal bindings, so they integrate seamlessly. This is also where you bind a custom BaseRateLimitBackend if you use "backend": "custom" for rate limiting.

Generate readable thread names

New threads are assigned UUIDs by default. To give them human-readable names instead, set thread_name_generator to a module:ClassName path. The server calls this generator when a new thread is created:

Python
# graph/thread_name_generator.py
from tenxgraph_api.src.app.utils.thread_name_generator import ThreadNameGenerator

class MyThreadNameGenerator(ThreadNameGenerator):
    async def generate_name(self, messages: list[str]) -> str:
        # Generate a name based on the first message, an LLM call, or your own logic.
        return "thoughtful-conversation"
JSON
{
  "agent": "graph.react:app",
  "thread_name_generator": "graph.thread_name_generator:MyThreadNameGenerator"
}

Thread names appear in logs, dashboards, and the /v1/threads API response, making it easier to identify conversations at a glance.

Enable authentication and authorization

These features have their own guides and require coordination:

Feature Guide What it controls
auth Authentication Who can call the server (JWT, custom)
authorization Authentication Who can access each thread (ownership, RBAC)
rate_limit Rate limiting How many requests per window

A minimally secured production config looks like this:

JSON
{
  "agent": "graph.react:app",
  "auth": "jwt",
  "authorization": "ownership",
  "rate_limit": {
    "enabled": true,
    "backend": "redis",
    "requests": 100,
    "window": 60,
    "by": "user",
    "redis": { "url": "${REDIS_URL}" }
  }
}
  • "auth": "jwt" requires JWT_SECRET_KEY (at least 32 characters; shorter keys are rejected in production) and JWT_ALGORITHM (for example HS256) in the environment. See Authentication for custom auth.
  • "authorization": "ownership" makes threads private to their creator. With allow_all, any authenticated user who knows a thread ID can read it.
  • The rate limit backend: "redis" shares the limit across all replicas. The "memory" backend counts per process, so three replicas allow three times the configured limit, making it unsuitable for production.

Share a Redis connection for the thread cache

The top-level redis key is a URL string used by the optional shared thread-ownership cache. This is separate from rate_limit.redis:

JSON
{
  "agent": "graph.react:app",
  "redis": "redis://localhost:6379/0",
  "rate_limit": { "backend": "redis", "redis": { "url": "redis://localhost:6379/1" } }
}

If the redis key is not set, the server falls back to the REDIS_URL environment variable. With neither, the ownership cache is per-process. Without a shared cache, each replica maintains its own view of which user owns each thread, leading to inconsistent access control across replicas.

Set test and evaluation defaults

The optional test and evaluation blocks supply defaults for the 10xgraph test and 10xgraph eval commands. CLI flags always override these defaults:

JSON
{
  "agent": "graph.react:app",
  "test": {
    "path": "tests",
    "coverage": true,
    "coverage_threshold": 80
  },
  "evaluation": {
    "directory": "evals",
    "output_dir": "eval_reports",
    "threshold": 0.9
  }
}

With this configuration, 10xgraph test runs tests from tests/ with coverage reporting. Pass a different path to override:

Terminal
10xgraph test tests/unit/            # uses tests/unit/ instead
10xgraph test --coverage             # enables coverage (default is on here)
10xgraph eval --output ci_reports/   # overrides output_dir
10xgraph eval --threshold 0.95       # overrides threshold

Test criteria come from your test files. Evaluation criteria come from a confeval.py file in your evals directory. See Run evals for details.

Enable AG-UI for client-side agents

The ag_ui key exposes an AG-UI endpoint so frameworks like CopilotKit can run your agent in a browser or desktop context:

JSON
{
  "agent": "graph.react:app",
  "ag_ui": {
    "enabled": true,
    "allow_client_tools": true
  }
}

When enabled, POST /v1/ag-ui streams AG-UI events. This feature requires pip install "10xgraph-api[ag-ui]". Set allow_client_tools to false to accept only the tools you explicitly declare in remote_tools (tools the client sends in each request are rejected). By default, it is true, allowing clients to offer ad-hoc tools as long as they fit size limits.

Control WebSocket connections

The websocket key manages connection limits for /v1/graph/ws (streaming) and /v1/graph/live (realtime):

JSON
{
  "agent": "graph.react:app",
  "websocket": {
    "max_connections": 1000,
    "max_connections_per_user": 10,
    "realtime_models": ["gemini-2.5-flash-live"]
  }
}
  • max_connections caps the concurrent WebSocket connections this server process accepts (default 1000). Multiple replicas each have this limit.
  • max_connections_per_user prevents a single authenticated user from hogging all slots (default 10). Set to 0 or null for unlimited.
  • realtime_models lists the models a /v1/graph/live client may request (empty by default, meaning clients cannot choose). Limits are per process, so size them based on your expected worker count.

Environment variable expansion in Redis URLs

Only the rate_limit.redis configuration supports environment variable expansion. Both $VAR and ${VAR} forms work:

JSON
{
  "agent": "graph.react:app",
  "rate_limit": {
    "backend": "redis",
    "redis": { "url": "${REDIS_URL}" }
  }
}

If a variable is missing, the server fails at startup with an error rather than silently starting without rate limiting. This is deliberate: a misconfigured server is worse than one that refuses to start.

All other configuration keys in 10xgraph.json are read literally. Keep credentials out of the file: use the environment, .env files, or your orchestration platform.

Configure per environment

Use separate config files and select one at startup:

plaintext
config/
  dev.json
  staging.json
  prod.json
Terminal
# Development: auto-reload, all features open
10xgraph api --config config/dev.json

# Production-like local run: no reload, hardened settings
# (for real deployments use `10xgraph build`, see the deploy guide)
MODE=production 10xgraph api --config config/prod.json --no-reload

A typical dev config:

JSON
{
  "agent": "graph.agent:app",
  "env": ".env",
  "authorization": "allow_all"
}

A typical prod config (see production checklist below):

JSON
{
  "agent": "graph.agent:app",
  "env": ".env",
  "auth": "jwt",
  "authorization": "ownership",
  "store": "graph.dependencies:store",
  "redis": "redis://redis:6379/0",
  "rate_limit": {
    "enabled": true,
    "backend": "redis",
    "requests": 100,
    "window": 60,
    "by": "user",
    "redis": { "url": "${REDIS_URL}", "prefix": "10xgraph:rate-limit" },
    "trusted_proxy_headers": true,
    "trusted_proxy_hops": 1,
    "fail_open": true
  }
}

Production decisions checklist

Before deploying to production, make five explicit decisions:

1. Turn on authentication

With auth unset or null, every request is accepted. This is fine for internal development. In production, set "auth": "jwt" with JWT_SECRET_KEY (at least 32 characters) and JWT_ALGORITHM in the environment, or point at a custom BaseAuth subclass. See Authentication.

2. Set authorization policy

If authorization is unset, the server uses ownership when MODE=production and allow_all otherwise. With allow_all, any authenticated user who knows a thread ID can read that conversation. In production, set "authorization": "ownership" so threads are visible only to their creator. Alternatively, implement a custom AuthorizationBackend for role-based access control. In development, "allow_all" is fine. Set it explicitly rather than relying on the MODE default.

3. Use a shared, durable checkpointer

InMemoryCheckpointer loses all threads on restart and is invisible to other replicas. In production, pass PgCheckpointer to compile() in your graph module. This ensures thread history survives restarts and is consistent across all replicas.

4. Set rate limit backend to Redis with multiple replicas

The "memory" backend counts per process, so with three workers you allow three times the configured limit. With multiple replicas, use "backend": "redis" and ensure all replicas point to the same Redis instance. Also set trusted_proxy_hops correctly: it counts from the right of the X-Forwarded-For header, so the value must match how many proxies sit in front of your server. Set it too high and a client can forge its own address to bypass limits.

5. Wire observability

A production agent you cannot trace is one you cannot debug. At minimum, set MODE=production and IS_DEBUG=false in the environment, and configure logging. See Observability.

Verify before you deploy

Run the audit command to check your setup:

Terminal
10xgraph audit

This checks the Python interpreter, the installed 10xgraph-api and 10xgraph packages, evaluation API compatibility, 10xgraph.json (valid JSON, an agent value in module:attribute form, valid remote_tools), and whether port 8000 is free. It does not import your graph. It exits with status 0 if only warnings appear, or 1 if errors are found.

Then start the server and verify each control:

Terminal
# 1. Config loads and graph imports
10xgraph api --no-reload &

# 2. The health endpoint works
curl http://127.0.0.1:8000/ping

# 3. Auth is on: an unauthenticated call must be rejected (expect 401, not 200)
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8000/v1/graph/invoke -d '{}'

# 4. Rate limiting is on: after 100 requests in 60 seconds, you get 429
for i in $(seq 1 120); do
  curl -s -o /dev/null -w "%{http_code} " http://127.0.0.1:8000/ping
done

Finally, verify that persistence survives a restart: start a thread, restart the server, and read the thread back via /v1/threads/{thread_id}.

Common configuration issues

Symptom Solution
ModuleNotFoundError or import fails The module path is spelled correctly and exists in your project. Test with python -c "from module import attr".
AttributeError: module has no attribute graph.react:app expects an app variable in the graph/react.py file. Check the name and that it is a compiled graph.
Checkpointer connection fails The Postgres DSN is correct and the database is reachable. Test: psql <dsn>. The Redis URL is reachable: redis-cli -u <url> ping.
JWT_SECRET_KEY and JWT_ALGORITHM must be set Export both variables, or set "env": ".env" and add them there. The secret needs at least 32 characters.
Rate limiting not working on multi-replica setup Set rate_limit.backend to "redis", not "memory". Ensure all replicas point to the same Redis instance.
Threads visible to users who did not create them Set "authorization": "ownership" or implement a custom AuthorizationBackend. Check that all replicas use the same setting.

For a complete list of configuration keys, defaults, and types, see the configuration reference.

Last updated for v0.10.0Edit this page on GitHubReport an issue