Skip to main content
OpenSRE deploys as a standard Python FastAPI application. Use the repository Dockerfile with Railway, EC2, ECS, Vercel, or another ASGI-capable host. For local containers, see Docker. The image supports three values of MODE:

Environment setup

  1. Deploy this repository using your host’s normal application workflow, or build the Dockerfile and set MODE to web or gateway.
  2. Set LLM_PROVIDER to your model provider (anthropic, openai, openrouter, trustedrouter, deepseek, or gemini).
  3. Set the corresponding API key:
    • ANTHROPIC_API_KEY for anthropic
    • OPENAI_API_KEY for openai
    • OPENROUTER_API_KEY for openrouter
    • TRUSTEDROUTER_API_KEY for trustedrouter
    • DEEPSEEK_API_KEY for deepseek
    • GEMINI_API_KEY for gemini
  4. Add any integration or storage environment variables required by your deployment, then verify health with GET /health or GET /ok.
Minimum configuration:
Provider keys and optional model overrides are listed in .env.example and Environment variables. For hosted deployments that need persistent storage, set DATABASE_URI and REDIS_URI. The async investigations API uses DATABASE_URL for its Postgres store; see HTTP API.

Local gateway

Start a local HTTP server for investigations (and optional chat transports):
The web app listens on 0.0.0.0 using the PORT environment variable (default 8000).
Example investigation request:
By default, /investigate and /alerts accept only loopback callers. To allow remote access, set OPENSRE_ALERT_LISTENER_TOKEN and send it as a bearer token:
Complete route reference: HTTP API.

Railway

  1. Provision Postgres and Redis in the Railway project.
  2. Set DATABASE_URI and REDIS_URI on the OpenSRE service to those connection strings.
  3. Optionally set OPENSRE_DEPLOYMENT_METHOD=railway for telemetry labeling.
  4. Deploy the service through your Railway project.

Horizontal scale-out

Run one gateway task and leave OPENSRE_SESSION_FILE_LOCK off. More than one gateway task sharing OPENSRE_HOME is not supported yet. Follow #5474 and add a second task only after that lands. To serve more concurrent conversations today, raise concurrency on that single task (next section). After #5474, put every task on the same session store — a shared mount such as S3 Files or EFS — and set:
Slack Events API tasks must also share DATABASE_URL. Without it, a Slack retry that lands on another replica runs the same turn twice. Socket Mode, Telegram, and Discord do not use that store. Do not set SLACK_GATEWAY_ALLOW_LOCAL_DEDUP=1 on more than one replica. Do not turn the lock on to make a multi-task fleet safe until then. Even after the fix, fcntl.flock is weak on some shared mounts (NFS, and EFS depending on configuration) — stay on a single task there, or use a mount that honors POSIX locks.

Concurrency per task

Each task has two caps, and the lower one wins. OPENSRE_MAX_CONCURRENT_TURNS limits every turn in the process (default 1 on SMALL). Slack, Telegram, and Discord each have their own pool (SLACK_GATEWAY_MAX_CONCURRENT, TELEGRAM_GATEWAY_MAX_CONCURRENT, DISCORD_GATEWAY_MAX_CONCURRENT), which also defaults to 1 on SMALL. Raising only the process cap leaves chat at one concurrent turn. Turns are I/O-bound — mostly waiting on the model — so a small task can run several; the ceiling is the task’s memory, since each concurrent turn holds its context resident. Raise both without changing the task size, using the transport vars for the chats you run:
Or set OPENSRE_SIZE_PROFILE=MEDIUM (2) or LARGE (4) to raise both defaults together. Read the gateway_turn_memory debug lines (delta_mb, peak_mb) from a real run to size this against the task’s memory limit before raising it. Queued investigations (from POST /investigate) run in a separate background pool that defaults to one at a time. Raise it to process more concurrently, still bounded by the turn limit above:

Dedicated scheduler service

By default the gateway process also runs the cron/loop scheduler. With more than one gateway task that would fire every scheduled task once per task. Run the scheduler as its own single service instead:
  • Deploy one MODE=scheduler task. It runs opensre cron start --service, idling until tasks exist rather than exiting.
  • Set OPENSRE_GATEWAY_HOST_SCHEDULER=0 on the gateway tasks so they stop hosting the scheduler in-process.
Run exactly one scheduler service, never one per gateway task. The scheduler service and every process that mutates tasks — the gateway, opensre cron, and /loops — must share the same task store: the same OPENSRE_HOME on a shared mount (S3 Files / EFS). The scheduler reconciles from that file, so a separate filesystem would leave it running a stale task set.

Optional: conversation affinity

Skip affinity until you run more than one gateway task (after #5474). Then it is a performance option: it keeps a conversation’s warm agent in one task’s memory instead of cold-rebuilding when a turn lands elsewhere. Affinity is a routing concern, not an app setting:
  • Behind an HTTP load balancer (Slack Events API), set the shared DATABASE_URL above, then enable consistent hashing on the conversation (Slack channel and thread) so a thread sticks to one task. Hashing does not replace event dedup.
  • With the pull transports (Slack Socket Mode, Telegram, Discord), each task connects independently and the provider spreads events across tasks, so there is no built-in affinity. Affinity would need an external dispatcher and is worth adding only when warm-agent reuse measurably helps.