Skip to content

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>, and dismiss <bot>;
  • the RTTI-modeled Codex bot_control dynamic 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:

  • CatalogFile and the stable ControllerFullJID;
  • 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 uses CodexExecutable and RuntimeDirectory; the OpenAI binding uses direct OpenAIAPIKey and an explicit OpenAICAFile public 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.