This guide deploys the supervisor and each helper as separate Railway services from this repository. Every service runs one bot process, one dedicated BC account, and one runtime database. Start with position sharing disabled; it is an optional feature for helper startup teleports, not a prerequisite for helper task dispatch.
RUNTIME_TARGET=production and database
veratown_prototype.RUNTIME_TARGET=test and database
veratown_prototype_test, with dedicated non-production BC accounts.agentId, memberNumber, roomKey, runtimeTarget, and
enabled value must match its Railway service and account. Do not store BC
credentials or observation tokens in MongoDB.veratown Railway project, create a supervisor service
connected to Rarsus/veratown-prototype, deploying the intended branch
(normally main).npm run build, and start command npm start. The application requires
Node.js 24; no Dockerfile is needed.Supervisor, Helper-A, and Helper-B.In the supervisor service’s Variables, set the common runtime variables:
The examples below use environment-only configuration. For database-managed
configuration, first create a supervisor profile in Admin Web for the
selected runtime target, assign its stable ID, and set only
BOT_INSTANCE_ID for profile selection. The profile supplies the role,
non-secret BC endpoint/room, and supported capabilities; keep Mongo bootstrap
settings, the dedicated BC username/password, and observation tokens in
Railway variables/secrets. The profile wizard and restart behavior are
described in the Admin Web guide.
| Variable | Production value |
|---|---|
RUNTIME_TARGET |
production |
MONGODB_DATABASE |
veratown_prototype (optional, but if set must match the runtime) |
MONGODB_URI |
The MongoDB connection string stored as a Railway secret |
BC_SERVER_URL |
The Bondage Club Socket.IO server URL |
BC_USERNAME / BC_PASSWORD |
Credentials for the dedicated supervisor account; store as secrets |
BC_ROOM |
The room the supervisor should join or create |
In environment-only mode, set RUNTIME_ROLE=supervisor and leave
HELPER_AGENT_ID unset. With a profile, the profile’s role selects the
supervisor composition and its non-secret settings override environment
values. Both modes start the room features and helper-task supervisor. Review the other optional
values in the Railway deployment guide;
leave appearance qualification disabled unless separately approved.
Deploy the service and verify successful startup, BC login, and room synchronization in Railway logs before configuring helpers.
Set the same common variable names on each helper service, but use that helper’s
dedicated BC credentials. Keep BC_ROOM and RUNTIME_TARGET consistent with
the supervisor. Set these additional values:
For profile mode, create a helper profile in the same target database with a
stable deployment ID and the helper registry identity. Set BOT_INSTANCE_ID
to that ID; the profile supplies HELPER_AGENT_ID and the non-secret
capability settings. The helper record must match the service’s runtime and
room. Do not put its BC credentials or observation bearer token in the profile.
| Variable | Example |
|---|---|
RUNTIME_ROLE |
helper |
HELPER_AGENT_ID |
helper-a — exactly the matching agentId in MongoDB |
RUNTIME_TARGET |
production only for the production fleet |
MONGODB_DATABASE |
veratown_prototype for production, or veratown_prototype_test for test |
Do not configure HELPER_AGENT_ID on the supervisor. The helper starts only its
helper-worker plug-in; identity, room, member number, runtime, and enabled-state
checks must match the helper-agent record before work can run. Deploy and verify
each helper’s BC room sync before assigning tasks.
Use the room-admin helper commands documented in the Helper Bots guide to assign work. A task uses a reviewed template ID and configured location; never put arbitrary message text or BC credentials in task documents.
Only do this after the supervisor and helper are working independently and
test-runtime qualification has passed. Sharing defaults off and is used only
when a helper has an optional startLocationKey that requires supervisor-side
position observation.
In Railway Networking for the supervisor service, enable a private
service port (for example, 8080). Do not create a public domain. In its
Variables set:
POSITION_OBSERVATION_LISTEN_HOST=0.0.0.0
POSITION_OBSERVATION_PORT=8080
POSITION_OBSERVATION_ENABLED=false
The configured port must match the service’s private networking port.
Generate a unique URL-safe token for each helper using a trusted local machine, for example:
node -e 'console.log(require("node:crypto").randomBytes(32).toString("base64url"))'
Do not paste tokens into chat, source control, MongoDB, or logs. Store each token only in Railway Variables.
On the supervisor, set POSITION_OBSERVATION_HELPER_TOKENS to a JSON object
mapping each enabled helper ID to its own token:
{ "helper-a": "<helper-a-token>", "helper-b": "<helper-b-token>" }
On each helper, set its matching POSITION_OBSERVATION_HELPER_TOKEN and set
POSITION_OBSERVATION_ENDPOINT to the supervisor’s private URL:
http://$:$/v1/map-position-observations
Replace Supervisor with the exact Railway service reference/name. If
Railway variable interpolation is unavailable, enter the resolved private
domain and port directly. The endpoint must use http, the private
railway.internal host, the configured port, and the exact path shown.
RUNTIME_TARGET=test, the test database, and dedicated test
accounts. Confirm listening on the supervisor and connected on the
helper in logs, and verify a startup teleport succeeds only after a matching
fresh BC position observation. Exercise stream loss and stop; neither may
cause an automatic movement retry or unlock task polling.POSITION_OBSERVATION_ENABLED=true on the supervisor and only the
one qualified helper. Restart/redeploy both services. Keep other helpers
disabled until separately qualified. Before production use, repeat the
qualification with the intended production account and database.Railway’s private WireGuard network encrypts service traffic; the app still requires the per-helper bearer tokens and validates the supervisor’s HMAC handshake. Heartbeats only detect stream liveness; they never count as position evidence. See the position-sharing runbook for scope, failure behavior, and rollback.
With sharing enabled on both updated services, helpers assign first persists
the task and then notifies the assigned helper over this same stream. No new
Railway variables or ports are needed. The helper still uses MongoDB atomic
claims, leases, and the normal action executor; the stream cannot submit raw
commands. Capability negotiation allows either service to be updated first;
mixed-version pairs retain polling.
Qualify in the test runtime by assigning a reviewed public template while the
helper is at its required observed location. Verify task pickup before the
configured polling interval, and verify the supervisor’s matching BC observation
is still required for succeeded. Interrupt the private stream and assign
another task: periodic polling must still pick it up without duplicate dispatch.
After stream reconnection, queued tasks also trigger an immediate queue check.
Check that a helper with an unobserved startup teleport remains fenced despite
new assignments. A log saying the wake-up is unavailable means the durable task
is still queued, not failed; it is not a reason to assign it again.
startup_failed.
Production and test database names are fixed to their runtime targets.POSITION_OBSERVATION_ENABLED,
POSITION_OBSERVATION_LISTEN_HOST, the configured private service port, and
that the service is not relying on a public domain.POSITION_OBSERVATION_ENABLED=false on both services and
redeploy/restart them. The helper returns to its existing local-observation
behavior. No public endpoint or persisted position fallback is required.