Skip to content

Latest commit

 

History

History
308 lines (253 loc) · 13.5 KB

File metadata and controls

308 lines (253 loc) · 13.5 KB

Native gateway practical guide

The native gateway is the server's structured API for custom clients, bots, agents, and external-content tools. It is not another IRC port, an IRC bouncer, or an administrator back door. It reaches the same accounts, channels, events, retention policy, installation checkpoints, and authorization used by IRC clients.

Most people should continue using an ordinary IRC client. Use the native gateway when software needs structured channel state that IRC cannot express cleanly.

Which interface should I use?

Goal Interface
Use Irssi, WeeChat, HexChat, or a mobile IRC client TLS IRC
Build a browser client that behaves like IRC IRC-over-WebSocket at /irc
Build a richer first-party channel client Native gateway on the control listener
Connect a narrowly authorized bot or model-backed agent Native gateway, usually through the reference MCP adapter
Onboard users or perform permitted HTTP administration Native control API
Offer externally hosted XDCC-like packages over HTTPS and DCC Native gateway plus the provider redemption API

The native gateway is deliberately narrower than IRC today. It supports channel join, durable resume, delivery acknowledgement, channel messages, visible automation invocation, automation-policy disclosure, optional automation command declaration, catalogue search, content requests, provider package publication, and provider-side DCC transfer control. It does not currently provide direct messages, topics, presence commands, NAMES/WHOIS, channel moderation, or account-wide human read markers. A custom client needing those features should use IRC or combine the two adapters over one local model.

Connection model

The gateway lives on the configured control listener, not the raw IRC port and not the IRC-over-WebSocket /irc endpoint:

account password or automation app key
        |
        |  POST /v1/native-sessions over HTTP Basic
        v
one-use ticket (30 seconds, process memory only)
        |
        |  WebSocket /v1/sync, subprotocol irc.native.v1
        v
native session -> channel authority -> ordinary event log -> IRC and native clients

The authentication secret obtains a short-lived ticket. The first WebSocket frame consumes that ticket and claims a nickname. A ticket can be restricted to selected operations and channels, but software that still holds the account password or app key can request another ticket; durable bot and channel grants are the actual long-term authority boundary.

Before connecting

You need:

  • an existing human account or automated-account app key;
  • private local state for a human installation token;
  • a configured control listener;
  • TLS for any non-loopback listener; and
  • Node.js 22 or newer to run the reference client.

Human clients use the bare account name and reusable account password. The first ticket response creates an opaque dr1... installation token; save it and return it in Telex-Installation on later ticket requests. It selects the delivery checkpoint but cannot authenticate. Automated adapters instead use a labeled account/key-id and ak1... app key; give each independently running deployment its own key.

For local development, the loopback control listener may use plaintext:

IRC_CONTROL_LISTEN=127.0.0.1:8098 cargo run --locked

Remote plaintext control traffic is never allowed. A deployed control origin must use the daemon's TLS identity or a reviewed layer-4 pass-through topology.

Fastest path: the reference terminal

From the repository root, start the server and then run:

cd clients
npm run native -- \
  --url http://127.0.0.1:8098 \
  --nick alice-native \
  --auth-id alice \
  --channel '#general'

The client prompts for the account password or app key without echo. For non-interactive local automation it can read IRC_CLIENT_PASSWORD once and delete it from the process environment; passwords are never accepted on the command line.

For a deployed server, replace the URL with its HTTPS control origin, for example https://irc.example.net:8098 or https://irc.example.net when the listener is safely published on the default HTTPS port.

The reference terminal understands:

Command Effect
ordinary text Send a message to the active channel
/join #channel Join, restore cached events, and resume delivery
/open #channel Select an already joined channel
/cause EVENT-ID text Send a message with an explicit causal reference
/invoke ACCOUNT question Post a visible question addressed to an admitted bot or agent
/catalog [query] Search the active channel's external package catalogue
/get PROVIDER-ID PACKAGE-ID Request a private one-use provider URL
/conversations List joined native conversations
/quit Disconnect

The terminal currently has no syntax for a shared channel key. The JavaScript library supports session.join("#room", {key: "..."}); otherwise use an IRC client for keyed rooms.

History and delivery

The reference client stores events before acknowledging them. On restart it resumes after that device's last acknowledged event. A crash can therefore produce a harmless duplicate, but should not create a silent gap.

Its state file is plaintext and is private to the local OS account. By default it is $XDG_STATE_HOME/telex/terminal-messages.json, falling back to ~/.local/state/telex/terminal-messages.json; --state PATH overrides it. A content-epoch change clears only that server/device transcript scope before resume; it cannot erase exports, other IRC client logs, model-provider records, or downloaded files.

Native acknowledgement means “this device durably stored or processed the event.” It does not mean “the human read it” and does not advance the account's IRC read marker.

Automation from a user's point of view

When a native client joins a channel, the server returns the complete durable automation roster before history replay. The reference terminal shows:

  • whether each participant is a bot or agent;
  • its purpose;
  • whether processing is local, elsewhere in the operator's infrastructure, or at a named external service;
  • its data-handling statement;
  • exactly which channel inputs and outputs it may use;
  • any retained-history window;
  • the configured invocation-context allowance;
  • whether automation-to-automation chatter is enabled; and
  • message/byte budgets, loop limits, moderator pause, and active cooldowns.

An offline agent with retained-history access is still shown because it may receive later content. The roster is disclosure by the operator, not proof that an outside processor honors its stated retention policy.

/invoke Helper what did we decide? commits a normal visible channel message. An authenticated IRC user can create the same structured invocation with the strict leading form @Helper: what did we decide?. Only the named, currently admitted automation receives the private target marker and the zero-to-ten preceding events allowed by its channel grant. Mid-sentence mentions and malformed addresses remain ordinary chat. A leading mention also remains ordinary when the named account is human, unknown, or lacks invocation.receive; this is how an ambient participant agent can treat mentions as highlights instead of private invocation gates.

Bots and agents use the same gateway but are more restricted than humans. An automated account needs a manifest and at least one explicit channel grant before it can obtain a session. Every join, replay, live event, acknowledgement, and write rechecks that grant. See automated participants for operator provisioning and the reference MCP adapter for the easiest model/tool integration. The CommandBot example is the smallest deterministic invocation/reply scaffold. The CatalogBot example is the smallest complete leading-! consumer and publishes the declarations it implements. The command-catalogue publisher shows how a leading-! bot can advertise the commands it already handles to capable clients without changing ordinary channel-message delivery. The ScheduledPublisher example is the smallest timer-driven publish-only scaffold. The RosterBot example composes a minimal current roster page with a durable direct invocation/reply loop. The DurableWorker example persists work before acknowledgement and publishes asynchronous completion without holding the delivery cursor open. The Chatbot example is the ambient model-backed scaffold whose downstream runner discovers its current resources and tools through MCP. The ArtifactProvider example publishes a private file catalogue and exercises both one-use HTTPS retrieval and traditional DCC SEND/resume slots.

External packages

The gateway's catalogue is an XDCC-like convenience layer; the IRC daemon never hosts, fetches, or relays package bytes.

/catalog displays bounded descriptors and the provider's data-handling statement. /get requests a five-minute, one-use HTTPS capability privately. The client prints the URL but never opens or previews it. Opening it normally reveals the requester's network address to the named provider.

Provider software publishes descriptors through its granted native session. For HTTPS it redeems capability tokens through the authenticated control API. For traditional XDCC SEND, a content.transfer session receives one bounded request, opens a port inside its operator-approved range, and returns the port; Telex emits the conventional CTCP offer and the client downloads directly from the provider. XDCC HTTPS remains the explicit private-link path and is never a silent fallback from DCC. The complete trust, resume, and quota model is in external content references.

Building a custom client

The dependency-free ECMAScript library exports NativeSession and NativeDurableSync. A minimal non-durable experiment looks like this:

import { NativeSession } from "./clients/lib/index.js";

let password = process.env.IRC_CLIENT_PASSWORD;
delete process.env.IRC_CLIENT_PASSWORD;

const session = new NativeSession({
  url: "http://127.0.0.1:8098",
  nickname: "alice-tool",
  authenticationId: "alice/019-device-id",
  password,
});
password = null;

session.addEventListener("event", ({ detail }) => {
  console.log(detail.event);
});

await session.connect();
const channel = await session.join("#general");
await session.sendMessage(channel.id, "hello from the native gateway");

This example intentionally does not acknowledge delivery. Production clients should use NativeDurableSync or reproduce its ordering: persist a complete page or live event transactionally, then acknowledge its final event. Never acknowledge merely because a WebSocket frame arrived.

An automated session whose ticket allows automation.commands.set can publish one complete leading-! catalogue after joining:

const desiredCommands = [
  {
    name: "latest",
    usage: "<topic>",
    description: "Find the latest item for a topic",
  },
];

let catalogue = await botSession.setAutomationCommands(
  botChannel.id,
  desiredCommands,
  { expectedRevision: 0 },
);
if (catalogue.status === "stale") {
  catalogue = await botSession.setAutomationCommands(
    botChannel.id,
    desiredCommands,
    { expectedRevision: catalogue.result.revision },
  );
}

The replacement is metadata, not a command route. The deployment still needs live.human.read and must handle ordinary channel messages beginning with !; the same permission is required to publish the declaration. Empty desiredCommands clears the catalogue. See the publisher example for a bounded JSON-file tool and retry loop.

For a lower-level implementation, the sequence is:

  1. authenticate POST /v1/native-sessions and optionally request a restricted operation/channel scope;
  2. open /v1/sync with WebSocket subprotocol irc.native.v1;
  3. send hello with the one-use ticket and nickname within five seconds;
  4. join a channel and retain its stable conversation_id;
  5. resume, commit each returned event batch, and acknowledge only durable work;
  6. use a fresh UUIDv7 operation ID for a new message or invocation, and reuse it only for an exact retry; and
  7. reconnect and resume after lag, transport loss, or restart.

The exact frame shapes, bounds, errors, and ticket attenuation rules are in the native gateway wire contract. The native control API documents the HTTP endpoints used for administration and providers.

Security checklist

  • Use HTTPS/WSS everywhere except explicit loopback development.
  • Persist a distinct human installation token or automation app key per process.
  • Never put a password, app key, native ticket, installation token, or content capability in argv, a URL, chat history, logs, or source control.
  • Treat the local transcript as plaintext sensitive data.
  • Treat all channel text as hostile input to bots, models, and tools.
  • Remember that ticket attenuation cannot confine software that still holds the account password or app key.
  • Remember that the server operator can read retained plaintext; this is not end-to-end encryption.