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
-
Deploy this repository using your host’s normal application workflow, or
build the
Dockerfileand setMODEtoweborgateway. -
Set
LLM_PROVIDERto your model provider (anthropic,openai,openrouter,trustedrouter,deepseek, orgemini). -
Set the corresponding API key:
ANTHROPIC_API_KEYforanthropicOPENAI_API_KEYforopenaiOPENROUTER_API_KEYforopenrouterTRUSTEDROUTER_API_KEYfortrustedrouterDEEPSEEK_API_KEYfordeepseekGEMINI_API_KEYforgemini
-
Add any integration or storage environment variables required by your
deployment, then verify health with
GET /healthorGET /ok.
.env.example and
Environment variables.
For hosted deployments that need persistent storage, set DATABASE_URI and
REDIS_URI. See HTTP API for the route reference.
Local gateway
Start a local HTTP server for health checks and alert intake (and optional chat transports):0.0.0.0 using the PORT environment variable
(default 8000).
/alerts accepts only loopback callers. To allow remote access, set
OPENSRE_ALERT_LISTENER_TOKEN and send it as a bearer token:
Railway
- Provision Postgres and Redis in the Railway project.
- Set
DATABASE_URIandREDIS_URIon the OpenSRE service to those connection strings. - Optionally set
OPENSRE_DEPLOYMENT_METHOD=railwayfor telemetry labeling. - Deploy the service through your Railway project.
Horizontal scale-out
Run one gateway task and leaveOPENSRE_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:
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:
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.
Dedicated scheduler service
Scheduled callbacks use a bounded worker pool. At most two distinct tasks run at once by default; setOPENSRE_SCHEDULER_MAX_CONCURRENT_RUNS to a positive
integer to change that limit. A task never overlaps its own previous run.
Runs waiting for a scheduler worker appear as pending in run history and
resume after a scheduler restart. On restart, pending runs for paused tasks
wait until the task is enabled again. Admitted runs wait until both a worker
and the task are available. Cron ticks rejected because the same job is already
queued or running are skipped.
Invalid concurrency settings fall back to two workers.
Agentic scheduled runs also take a permit from
OPENSRE_MAX_CONCURRENT_TURNS, so the lower of the scheduler and process-wide
limits bounds their effective concurrency; interactive traffic shares the
process-wide limit. Recovery work uses the same scheduler worker pool.
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=schedulertask. It runsopensre cron start --service, idling until tasks exist rather than exiting. - Set
OPENSRE_GATEWAY_HOST_SCHEDULER=0on the gateway tasks so they stop hosting the scheduler in-process.
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_URLabove, 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.