The supervisor delegates one bounded public communication task at a time to a
separately deployed BC account. Each process still owns exactly one BcClient
and its local rate limiter. The supervisor process remains the default; set
HELPER_AGENT_ID only on a helper deployment. Helper mode starts only the
helper worker plug-in, so it does not route player commands or run the main
bot’s feature plug-ins.
| Type | Input | Completion evidence |
|---|---|---|
Chat |
Enabled public-message template and its configured location | The helper dispatches the template through CharacterCommunicationApi; the supervisor observes the same incoming message from the registered helper and accepts either its local position or fresh supervisor/peer position evidence inside that location. |
Emote |
Enabled public-emote template and its configured location | Same as Chat, with the observed message type required to be Emote. |
| Roulette stage | Internal, typed casino event for a configured wheel helper | The helper emits only a reviewed opening/spin Chat or Emote template or the supervisor’s aggregate settled result; matching room message and observed region membership complete the task. |
| ABCL interaction | Internal, typed operation for an explicitly opted-in participant and the configured ABCL region | The helper revalidates its own and the target’s observed region, runtime config revision, and consent revision before each action. Public/Hidden messages remain unconfirmed unless a matching BC signal exists; ambiguous sequences are never replayed. |
| Visual presence | Dedicated helper account and operator-managed BC avatar | The helper’s room membership and appearance are visible as a separate character. Presence is not a task completion signal, and helper tasks do not update shared room-map objects. |
Operator-assigned template task documents contain a template ID, not message
text. The internal roulette event variant is created only by the casino
composition adapter and carries bounded public Emote text; it is not exposed
through /bot helpers assign or a player command. Templates remain
operator-managed, public text only; whispers and arbitrary player-supplied text
are unsupported. A helper must be at the task’s configured map location before
dispatch. An optional startLocationKey on its helper-agent record requests a
same-room teleport after BC has joined or created the room and observed the
initial room sync. The target must be an enabled point location. Task polling
remains fenced until a matching position is observed (or already matches); a
timeout is not retried automatically. Without startLocationKey, the existing
manual-position behavior remains. The supervisor observes the server-synchronized
character position; BC does not correlate a chat packet with a position update.
The optional ABCL worker capability is separately configured by abcl.rules
in the selected runtime database. Only a helper assigned the abcl capability
can claim these tasks. The supervisor creates a task only for an opted-in
participant in the active region; the helper repeats those checks before each
action. ABCL tasks contain fixed operation enums and a target member number,
never raw mod statistics or arbitrary message text. See the
ABCL feature guide for role, configuration, and opt-out
behavior.
Casino roulette can optionally assign its public opening, spin, and settled
aggregate result to one enabled helper with the explicit roulette-wheel
capability. Configure the helper’s room key/runtime target and reviewed opening
and spinning Chat or Emote templates in
casino.rules.roulettePresentation.
Both templates must use the existing roulette region key. The helper is fenced
at dispatch unless its current identity, room, runtime, enabled state,
capability, task scope, expiry, and BC-observed position match. The mapped
location must still be an enabled game region. A fresh position observation
for the helper’s registered member from the supervisor or another helper is
also accepted as location evidence; a helper’s own uploaded position is not
counted as independent evidence.
The supervisor alone owns roulette access, rounds, wallet/economy writes, randomness, persisted outcomes, and private participant settlement. Result events are created only after settlement and contain only the existing public aggregate text; no member numbers, names, stakes, selections, balances, or private reports are delegated. Matching inbound message evidence from the registered helper, in the configured region, marks an opening/spin task observed; settled results remain Emotes. Dispatch or helper liveness is not completion evidence.
Roulette events reuse prototypeHelperTasks and its five-day terminal
retention. Stable session/stage event IDs make insertion idempotent; at most
three undispatched roulette events are queued per helper. A result expires
superseded queued opening/spin events. A claimed, running, or ambiguous task is
never canceled or replayed; uncertain work remains in the existing
reconciliation_required lifecycle. Confirmed no-dispatch conditions may use
the supervisor’s current eligible-occupant whisper fallback; ambiguous enqueue
or dispatch never falls back. Changing presentation mode to whisper is
rechecked before a message can be sent.
Location checks use the shared exact tile geometry used by the map runtime.
Legacy rectangles keep inclusive bounds; row-span regions preserve gaps, holes,
and disconnected components. Region geometry takes precedence when a location
also has x/y; those point coordinates remain explicit destinations, and a
region-only location cannot be used as a startup teleport target.
For separately deployed instances, POSITION_OBSERVATION_ENABLED=true on both
processes enables private, bidirectional observation sharing. Helpers consume
fresh supervisor observations and upload their own local observations back to
the supervisor over the same authenticated private endpoint. This remains
transient BC evidence, not a Mongo cache or a task-completion shortcut. The
session remains helper-scoped and the registry identity, room, runtime,
enabled state, and fleet-limit checks remain in force. A timeout, lost stream,
disconnect, or stop does not trigger an automatic teleport retry or unlock task
polling.
With sharing disabled (the default), each process keeps its existing local observation behavior. With sharing enabled, only fresh shared observations can satisfy an initial cached-state check; a fresh supervisor non-match takes priority over a conflicting local event. After shared evidence expires or is invalidated, only a new local BC event or a new shared BC observation may complete an in-flight teleport. Persisted/history data is never used.
The helper account itself can visually represent an NPC. Its avatar is managed in BC by the operator. A literal roulette-wheel map object is shared room state and is not changed by this feature; add or update such objects through the supervisor’s existing map administration.
Room chat and emote actions remain dispatched_unconfirmed in the helper’s
action journal. A task becomes succeeded only after the supervisor observes
the matching message and location; otherwise it can become
reconciliation_required. The worker never retries an action after an
ambiguous result.
HELPER_AGENT_ID is optional. Leave it unset for the supervisor. Each helper
deployment sets it to its stable, lowercase agentId, and uses that helper’s
dedicated BC_USERNAME and BC_PASSWORD. BC_ROOM must name the same room as
the supervisor. RUNTIME_TARGET still selects the fixed production or test
database; production helpers are supported in the existing production room.
Optional Railway sharing settings:
| Setting | Supervisor | Helper |
|---|---|---|
POSITION_OBSERVATION_ENABLED |
true to serve the private stream |
true to receive it |
POSITION_OBSERVATION_LISTEN_HOST |
Bind to 0.0.0.0 |
Unused |
POSITION_OBSERVATION_PORT |
Match the Railway private service port | Unused |
POSITION_OBSERVATION_HELPER_TOKENS |
JSON object mapping each enabled helper ID to its own random token (32–256 URL-safe characters) | Unused |
POSITION_OBSERVATION_ENDPOINT |
Unused | http://<supervisor-private-domain>:<port>/v1/map-position-observations |
POSITION_OBSERVATION_HELPER_TOKEN |
Unused | That helper’s matching token |
POSITION_OBSERVATION_HANDSHAKE_TIMEOUT_MS |
Not used | Defaults to 3,000 ms; bounded from 500–10,000 ms |
While connected, the stream sends a sequenced liveness heartbeat every second; the helper reconnects after 3.5 seconds without stream data. These control frames never create or refresh position evidence.
The standard HTTP stream relies on Railway’s encrypted private WireGuard network; do not attach a public domain or use a public endpoint. Per-helper bearer tokens authenticate receivers, and an HMAC proof authenticates the supervisor response. Store tokens only in the corresponding Railway variables, rotate them as an operator change on both services, and never log them. The feature fails closed on wrong room/runtime, invalid scope, stale/future frames, replayed sequences/epochs, malformed or oversized frames, and stream loss. The stream carries room-scoped position and redacted appearance observations, member/all invalidations, and negotiated task-available hints. It does not carry commands or task payloads.
On each authenticated connection, the supervisor also sends a snapshot of positions in its current BC room. The helper keeps those supervisor-observed positions for that stream session, updates them from later supervisor position events, and removes them on member/room invalidation or stream loss. This session-bound state is separate from the five-second event-observation cache; an unchanged position remains available while its source session is valid. Liveness heartbeats do not create or refresh position evidence. A reconnect receives a new snapshot from the supervisor’s current BC room.
After an authorized helpers assign command successfully inserts its task in
prototypeHelperTasks, the supervisor sends a best-effort task_available
frame to that helper’s authenticated connection. The frame contains only the
protocol version, type, and monotonically increasing stream sequence. Task
payloads and execution authority remain in MongoDB.
Helper transport status logs include a bounded rejection reason when the
response or frame can be classified; they never include tokens or frame
payloads.
The helper wakes the same worker used by periodic polling; there is no parallel
executor. Wake-ups during a claim or execution coalesce into one pending queue
check. Stream reconnection also wakes the worker to recover notifications missed
while disconnected. Polling continues at pollIntervalMs even when the stream
is unavailable or sharing is disabled. An unavailable or failed push never
changes a persisted task into an assignment failure or causes a second enqueue.
All wake-up paths remain fenced until startup positioning succeeds, and preserve identity, scope, fleet, lease, action-budget, and BC-observed-location checks. Disconnect and stop fence new claims and invalidate pending startup preparation; stop also removes the wake-up subscription and polling timer. Stop waits for accepted worker, observation, and recovery database work to finish before Mongo closes; a blocked claim can delay shutdown, but generation fencing prevents that claim from entering the executor afterward. Hints do not refresh position evidence, journal a BC dispatch, acknowledge a command, or mark a task successful.
Push uses the existing position-sharing configuration, without additional ports,
variables, or collections. The helper advertises
X-Helper-Task-Notifications: 1 when opening the stream; the supervisor sends
task frames only to opted-in clients. Older helpers continue receiving only
position/control frames, and newer helpers work with older supervisors through
polling. Either deployment order is supported.
See the private helper position-sharing runbook for service networking setup and shadow-mode rollout. Sharing remains disabled until both services and their token mapping are configured; there is no fallback to persisted coordinates.
The selected database contains these operator-managed documents:
{
"_id": "main",
"maxHelpers": 3,
"pollIntervalMs": 5000,
"taskLifetimeMs": 300000,
"actionBudgetPerMinute": 3
}
Collection: prototypeHelperSettings. maxHelpers defaults to 3 and can be
configured from 1 to 10. Poll interval, task lifetime, and the per-helper
one-way-action budget also have conservative defaults shown above. A task
lifetime must cover at least two polling intervals so ordinary polling delays
do not expire queued work prematurely.
The same main settings document may include a flavorSchedules array; it is
absent by default and does not change existing behavior. Configure it in Admin
Web under Helper settings and action budget. Enter an array in the
Regional flavor schedules JSON field, for example:
[
{
"agentId": "helper-abcl",
"templateId": "nursery-welcome",
"intervalMs": 300000,
"enabled": true
}
]
Keep the other existing helper settings when editing the record. Each schedule
names an enabled public Chat/Emote template whose location is an enabled region;
the helper must have the corresponding communication or emote capability.
Up to ten distinct helper/template pairs are allowed. Intervals range from
30 seconds to 24 hours. Use reviewed, non-sensitive public text; these are
region-wide messages, not participant-targeted ABCL actions.
The supervisor checks schedules at approximately 30-second intervals. It starts each schedule after its configured interval and does not replay missed intervals after a restart. At the due time, both the helper and at least one other character must have an observed position in the region. The supervisor and registered helpers, including disabled helpers, do not count as occupants; other unregistered bot accounts cannot be distinguished from players. The helper rechecks current settings, template, region, identity and occupancy before dispatch. Unknown position evidence fails closed. A currently disabled or changed schedule cannot authorize its old queued task. Busy helpers, expired work, or an exhausted action budget can prevent a message; no missed message is replayed. Queued work has a lifetime of at least two helper polling intervals, so a queued message may be delayed. Scheduled messages use the existing task queue, action budget, BC packet limiter and observation-based completion.
prototypeHelperSettings is prototype-owned; the source configuration-copy
allowlist does not copy these settings. Configure each runtime target
independently. Deploy updated helper workers before enabling a schedule, since
older workers do not recognize its task field.
{
"_id": "helper-a",
"agentId": "helper-a",
"role": "helper",
"parentAgentId": "supervisor",
"memberNumber": 5001,
"roomKey": "main",
"runtimeTarget": "production",
"startLocationKey": "roulette",
"capabilities": ["communication", "emote"],
"enabled": true,
"maxActiveTasks": 1
}
Collection: prototypeHelperAgents. The configured member number, room key,
runtime target, optional startup location, enabled state, and capability list
are checked before work is claimed and again before dispatch. The
roulette-wheel capability authorizes only the internal roulette Chat/Emote variant.
Never store BC credentials in MongoDB. The optional startup location is
operator-managed and does not permit task-supplied destinations.
The supervisor-managed ABCL feature uses an additional "abcl" capability. It
authorizes only the strict ABCL helper-task schema; it does not enable ABCL in
an ordinary helper deployment by itself. The room-scoped abcl.rules config
must also name that helper and an enabled region, and participants still need
explicit opt-in.
A dedicated wheel helper may use only the specialized capability:
{
"agentId": "roulette-wheel",
"capabilities": ["roulette-wheel"],
"enabled": true
}
This is a capability excerpt, not a complete agent document; the configured member number, parent, room key, runtime target, and task limit are still required.
{
"_id": "roulette-welcome",
"roomKey": "main",
"locationKey": "roulette",
"type": "Chat",
"text": "Welcome to the roulette table!",
"enabled": true
}
Collection: prototypeHelperTemplates. Use only reviewed, non-sensitive
public text and an enabled location from veratownLocations. Templates are not
task arguments; task records hold only the template ID.
Room administrators use /bot helpers assign <agentId> <templateId> or
!helpers assign <agentId> <templateId> to queue work. /bot helpers status
and !helpers status show recent task IDs and statuses privately. Assignment
is rejected for disabled/mismatched helpers, unsupported capabilities,
disabled templates, or helpers already holding an active task.
The helpers help entries are shown only when at least one helper is enabled
for the current room and runtime target and the fleet is within its configured
limit; otherwise assignment and status help are omitted.
Shop interactions use optional triggerKey values on the existing
prototypeHelperTemplates documents, for example shop.browse or
shop.drink_served. The supervisor seeds 24 reviewed examples with
$setOnInsert, preserving existing documents and edits. The Admin Web Helper
templates CRUD resource manages trigger, location, message type, public text,
and enabled state with expected-version checks and sanitized audit records.
Assign the helper through shop.rules.helperPresentation.agentId and disable
shop flavor with shop.rules.helperPresentation.enabled: false; enabled
templates with the matching trigger key and shop location form the available
flavor pool, from which the supervisor selects one for each interaction. The
supported triggers are:
| Trigger | When it is queued |
|---|---|
shop.browse |
After a successful shop listing |
shop.drink_quote |
After a drink quote is created |
shop.release_quote |
After a release quote is created |
shop.favor_quote |
After a favor quote is created |
shop.drink_served |
After a drink purchase is observed and settled |
shop.release_completed |
After a release is observed and settled |
shop.favor_started |
After a favor starts successfully |
shop.favor_stopped |
After an active favor is stopped |
shop.status |
After a private status response is prepared |
shop.room_rental |
Reserved for future room rentals; not currently invoked |
shop.room_code |
Reserved for future private room-code retrieval; not currently invoked |
Each template must be enabled, public, in the shop region, and supported by the
helper’s communication or emote capability. The supervisor queues only the
template ID, never a command, quote, balance, accept code, or room code. It
continues to privately whisper all command results. Drink emotes retain the
supervisor fallback only when helper unavailability is confirmed; ambiguous
enqueue outcomes are not retried as public messages.
Tasks are atomically claimed by the assigned identity with a 30-second lease
epoch. If a claim expires before the worker starts the task, an unexpired task
returns to the queue; an expired task becomes terminal. A lost lease after the
task enters running is ambiguous and becomes reconciliation_required, not an
automatic retry or unlock. Only the supervisor’s observed matching message can
resolve that uncertainty in this release; there is no automatic replay or
helper-to-helper control channel.
Terminal task metadata and action-journal records are retained for at most five days. Oldest terminal records are removed first after the 10,000-record threshold is exceeded. Nonterminal/reconciliation records are preserved rather than evicted as if they were completed. Task retention uses a TTL index; the action journal uses its existing TTL index.
ABCL task resultCode values preserve the lifecycle category and add a fixed
stage/reason suffix, such as
abcl_pre_dispatch_rejected:action_budget_reservation:action_budget_exhausted.
These bounded codes are visible in Admin Web’s Helper task activity view.
Exception messages, stacks, and packet contents are not copied into task
records.
For participant_validation, reason codes distinguish invalid/disabled
configuration, room or participant presence, helper identity/capability,
location availability, missing consent, opt-in requirements, and whether the
helper or target is outside the configured region. consent_reply tasks allow
opted-out participants; a rejection for one of those tasks therefore points to
another listed validation gate.
| Collection | Owner | Purpose and retention |
|---|---|---|
prototypeHelperAgents |
Helper Bots | Operator-managed identities, scope, optional startup point, and capabilities; no credentials or TTL. |
prototypeHelperSettings |
Helper Bots | Database-configurable fleet and task limits; one main document. |
prototypeHelperTemplates |
Helper Bots | Reviewed public Chat/Emote text and required location; no TTL. |
prototypeHelperTasks |
Helper Bots | Template delegation, bounded roulette stage queue, leases, status, and safe ABCL failure diagnostics; terminal records have a five-day TTL and oldest-terminal FIFO cap of 10,000. |
prototypeHelperActionBudgets |
Helper Bots | Atomic per-helper one-minute dispatch budget; short TTL. |
prototypeActions |
Action Coordinator | Existing BC action lifecycle records; five-day TTL and 10,000-terminal-record FIFO cap. |
All collections are created only in the fixed database selected by
RUNTIME_TARGET. Test helpers use veratown_prototype_test; production
helpers use veratown_prototype.
The authenticated admin-map dashboard’s Helper bots section manages helper identities, startup point locations, public message templates, fleet/task settings, and the per-helper action budget. It also shows recent bounded task status and active budget counters without returning task message payloads or member identifiers. Helper records are admin-only and every mutation is version-checked and audited.
The dashboard cannot create roulette task events, replay tasks, or change tasks after they leave the queued state. It may cancel only an active queued task; the cancellation is conditional so a concurrent claim wins. Railway credentials, deployment configuration, restarts, and BC operations are outside this database control plane. Persisted edits do not prove that a running helper has adopted them.
Use the dry-run-first operator script from the repository root, injecting the environment of the intended helper service so its runtime target and agent ID are checked against the request:
railway run --project <project-id> --environment production --service <helper-service-id> -- \
npm run provision:roulette-helper -- \
--agent-id helper-b \
--member-number <dedicated-member-number> \
--start-location-key casino.roulette.bot.home \
--opening-text "The roulette table is open for bets!" \
--spinning-text "The wheel is spinning!"
Review the printed plan, then repeat with --apply to insert the helper agent,
the reviewed Emote templates, and default helper settings only when absent. The
script verifies the enabled point start location and configured roulette region,
and changes only roulettePresentation in the existing room-scoped
casino.rules value through the audited admin-workspace write path. It rejects
conflicting agent/template records rather than silently replacing them.
The script does not pre-create task records or action-budget counters. Roulette
events create durable tasks as rounds progress; the configured
actionBudgetPerMinute setting applies per helper, and its transient budget
counter is created atomically when the helper first dispatches an action. The
helper’s startup teleport is attempted only after the configured BC room has
synchronized; task polling stays fenced until the destination is observed.