veratown-prototype

Helper Bots

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.

First-release task catalog

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.

Roulette stage tasks

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.

Runtime configuration

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.

Push-assisted durable task polling

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.

Optional regional flavor

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.

Operator commands and lifecycle

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.

Shopkeeper presentation

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.

Collections

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.

Admin-map management

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.

Provisioning a roulette helper

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.