Nexus XMPP BotHost
NexusBotHost is a headless console application that hosts a catalog of
provider-backed bots on NexusXMPP. The distinguished NexusBot instance
receives ordinary addressed MUC conversation and owns the control endpoint for
the catalog.
Bot behavior is defined by ../../../nexus-lab/examples/automation/bots/Bots.nxscript using the Bot language in
language/Bot.Language.nxscript.
Bot definitions contain Provider,
Model, Instructions, and optional plural Workspaces references. Provider names are resolved by a
case-sensitive BotHost registry; Codex and OpenAI are registered providers.
Deployment data is separate RTTI-persisted configuration. Catalog loading
rejects an unregistered provider before publishing any bot entries.
Catalog loading is atomic. Compilation, Bot-language validation, bot-name uniqueness, and static deployment bindings must all succeed before any entries are published. Invalid catalog documents are rejected as a whole with collected diagnostics; they are not retained as partially available bots. Connectivity, authentication, provider startup, and other operational failures remain runtime status rather than catalog validity.
The registered providers are Codex and OpenAI. OpenAI uses the Responses
API through Synapse and its OpenSSL 3 TLS provider, sends non-streaming
requests, and keeps only the previous response ID in memory for conversation
continuity. It sends
store: true; the response chain therefore uses OpenAI-retained response state
while BotHost writes no conversation history to disk. Streaming is not supported by this provider.
The catalog has one BotCatalog root whose Workspaces and Bots arrays hold
named inline definitions. A bot refers to a workspace through Nexus Script's
bottom-up reference resolution, for example
@NexusBots.Workspaces.Nexus. Each workspace declares a Git Source, optional
Ref, purpose, and explicit absolute Location. Before providers start,
BotHost prepares one shared checkout at <Location>/repo, records its exact
commit, and creates
<Location>/botcache/<BotName> for every assigned bot. OpenAI bots with an
assignment advertise the Responses local shell tool and receive every
repository, cache, and commit as explicit absolute paths. The deployment limits
ShellCallMaximum, ShellCommandTimeoutMS, and ShellOutputMaximumBytes
bound same-worker command execution and retained output.
File exchange
BotHost accepts self-contained XEP-0447 file shares in addressed live room messages and direct messages. XEP-0066 is emitted only as the matching fallback for an SFS share; an OOB URL by itself is never treated as an attachment. Files are downloaded in advertised source order through verified HTTPS, bounded by the deployment limits, checked against declared size and SHA-256 when present, and staged under the BotHost-owned exchange directory before provider delivery. Ordinary URLs in message prose are never downloaded.
Codex receives staged images and audio through its native local input variants.
It can inspect UTF-8 documents through the active-prompt-only
read_attachment tool, in byte ranges capped at 64 KiB, and can relay an
active attachment with send_file. Neither tool accepts a filesystem path or
an arbitrary URL. OpenAI uploads staged document/image bytes to the Files API
with purpose user_data and 24-hour expiry, then uses typed input_file or
input_image Responses content. Audio is explicitly unsupported by the pinned
OpenAI provider contract.
Outbound relay discovers an XEP-0363 service, observes its advertised maximum, requests a fresh slot, streams the PUT without redirects, and sends an ordinary SFS message with SHA-256, XEP-0428 body fallback, and matching OOB fallback.
Control plane
LIST, STATUS, INVITE, and DISMISS share one typed operation, authorization, and controller implementation. They can arrive through:
- exact addressed room commands:
list roster,status <bot>,info <bot>,invite <bot>, anddismiss <bot>; - the RTTI-modeled Codex
bot_controldynamic tool for conversational requests; - XMPP IQ requests in
urn:nexus:bot-control:1.
The existing BotHost router remains the sole owner of MUC admission. It accepts
live replies, @Nick, and Gajim's case-insensitive Nick, addressing. Human
room occupants may invite or dismiss catalog bots only in their current room,
including temporary rooms that conceal real JIDs. Allowlisted reader/operator
authorization still requires a room-disclosed or IQ-authenticated real bare
JID; nicknames and occupant JIDs are not account identities.
Bots also accept ordinary direct messages without nickname addressing and
return their answer to the same full JID. A MUC private message carries the
room occupant JID, so BotHost retains that room as the prompt context. A global
direct message carries no room provenance. Its room operations must therefore
use invite BotName room@service or dismiss BotName room@service; omitting
the room returns a bad-request result. Existing reader/operator authorization
still applies to global direct-message control.
The IQ wire operations are:
<bots xmlns='urn:nexus:bot-control:1'/>
<status xmlns='urn:nexus:bot-control:1' bot='NexusBot'/>
<invite xmlns='urn:nexus:bot-control:1' bot='NexusBot'
room='room@conference.example'/>
<dismiss xmlns='urn:nexus:bot-control:1' bot='NexusBot'
room='room@conference.example'/>
The first two use IQ get; the latter two use IQ set. The namespace is
advertised through XEP-0030. The host writes activity to standard output.
INVITE and DISMISS are idempotent. INVITE completes after the bot is joined. DISMISS leaves only the requested room; it does not stop the provider, disconnect XMPP, or disturb other room memberships.
Configuration and secrets
NexusBotHost launches from a typed RTTI-persisted JSON launch configuration:
NexusBotHost.exe --config C:\Bots\NexusBotHostLaunch.json
With no argument it uses
NexusBotHost\NexusBotHostLaunch.json under the current user's application-data
directory. A missing or invalid explicit/default configuration fails clearly.
The launch object selects the bot, controller configuration, initial room, and autostart policy:
{
"Class": "TNXBotHostLaunchConfig",
"AutoStart": true,
"BotName": "NexusBot",
"ControllerFile": "NexusBotController.json",
"RoomJID": "bots@conference.example"
}
Relative paths are resolved from the JSON file that declares them. Thus the controller path above is relative to the launch file, while catalog, CA, executable, and runtime-directory paths are relative to the controller file.
The controller configuration contains:
CatalogFileand the stableControllerFullJID;- bounded operation capacity;
- normalized reader and operator bare-JID allowlists;
- deployment bindings associating canonical catalog names with XMPP identity,
resource, nickname, endpoint/TLS data, direct
Password, and provider-specific deployment data. The Codex binding usesCodexExecutableandRuntimeDirectory; the OpenAI binding uses directOpenAIAPIKeyand an explicitOpenAICAFilepublic CA bundle.
Each deployment binding also owns ExchangeDirectory, FileMaximumBytes,
FileTransferCapacity, FileTransferTimeoutMS, StagedFileCapacity,
StagedMaximumBytes, and TrustedFileOrigins. Relative exchange paths resolve
from the controller configuration file. Trusted origins use exact canonical
https://host:port spelling and only exempt that origin from the public-address
rule; certificate and hostname verification remain mandatory. Presigned URLs,
slot headers, local staging paths, and credentials are not logged.
The configuration is the credential owner. Passwords and API keys are not copied into process environment variables, command-line arguments, logs, diagnostics, activity messages, or protocol request bodies. The configuration file must be protected with appropriate filesystem permissions.
The process remains active until it receives Ctrl+C, Ctrl+Break, or the platform's normal termination signal. Shutdown stops the selected provider and XMPP connection. Console output is suitable for direct observation or capture by a service manager such as systemd.
Use a
dedicated runtime directory outside a source repository. The host starts an
ephemeral Codex thread with sandbox = read-only, approvalPolicy = never, and
restrictive developer instructions. Existing Codex authentication is reused.
For an XMPP service with a private CA, set CAFile to its trusted issuer
certificate in PEM format. Certificate verification remains enabled.
OpenAI HTTPS always verifies the server certificate and hostname. Set
OpenAICAFile to a readable public CA bundle; it is independent of the XMPP
server's CAFile.