Channels

A channel is whatever lets a user send a message in and get a reply back. Sessions persist in SQLite and are keyed to their channel identity. Tasks, projects, memory, schedules, and rooms provide the intentional shared state between surfaces.

CLI, HTTP, the bundled web UI, and Discord ship with the main install. Slack ships as a first-party plugin. The HTTP API is the foundation; everything else is either a consumer of it or a channel registered with the runtime.

For workflow triggers (file-drop, email polling, RSS, calendar) see Workflows → Triggers. Triggers are event-driven workflow inputs, not user-facing channels.

CLI

Comes with @tailored-ai/cli. The tai command and tai -m "…" for one-shot. See Quick start and the CLI package page for the full surface.

HTTP API

tai (with no arguments) starts an HTTP server on port 3000. Routes cover sessions, agents, workflows, tasks, projects, memory, resources, approvals, and webhooks. SSE streams responses token-by-token.

See @tailored-ai/server for the route reference. Every other channel here is a consumer of this surface; if you're building a custom UI or integrating into your own stack, this is your interface.

By default the server binds to 127.0.0.1. For programmatic access on another interface, set server.authToken; it gates every /api/* route. For the bundled browser UI, use server.proxyAuth, which exchanges a password for the session cookie needed by SSE connections. See Self-hosting before exposing the server.

Web UI

tai (with no arguments) mounts a built React SPA at /. Chat sidebar, agent picker, session list, tasks board, memory view, workflow runner.

The bundled UI is one consumer of the HTTP API. If you'd rather build your own:

  • Build a separate Next.js / SvelteKit / Vite app against /api/sessions, /api/agents, etc.
  • Serve it separately against the HTTP API, or register a UI provider plugin and select it with server.ui.provider.

To disable the bundled UI, set server.ui.enabled: false.

Discord

yaml
channels:
  discord:
    enabled: true
    token: ${DISCORD_BOT_TOKEN}
    owner: ${DISCORD_OWNER_ID}
    respondToDMs: true
    respondToMentions: true
    allowedGuilds: []          # empty = any; otherwise list guild ids
    perChannelMapping: {}      # channel id → project id

Setup:

  1. Create a bot at discord.com/developers.
  2. Copy the token into .env as DISCORD_BOT_TOKEN=….
  3. Right-click your own user in any Discord client, "Copy User ID", put in .env as DISCORD_OWNER_ID=….
  4. Add the bot to your server with the bot and applications.commands scopes plus message-content intent.
  5. Run tai. The bot logs in and registers slash commands (/new, /agent, /project, …).

DMs and @mentions route to the agent. Per-user sessions persist, so the bot remembers context across messages from the same user.

Outbound DM tool

If you want an agent to initiate a Discord DM, enable the discord_dm tool and list it in the relevant agent's tools: array. The agent calls discord_dm({ message: "…" }) and the configured owner gets a DM.

The right channel for online-mode escalations. Preferred over gmail send for FYI-style pings, since the chat thread is more conversational than email.

Slack

Install the first-party Socket Mode channel:

bash
tai plugin install @tailored-ai/channel-slack
yaml
channels:
  slack:
    enabled: true
    token: ${SLACK_BOT_TOKEN}
    appToken: ${SLACK_APP_TOKEN}
    respondToDMs: true
    respondToMentions: true

It handles DMs and channel mentions, replies in threads, and can map Slack channels to registered projects. See the @tailored-ai/channel-slack README for app scopes and Socket Mode setup.

Other channels

For Telegram, iMessage, SMS, or another transport, three paths are available:

  1. Register a channel factory from a plugin. The CLI starts every registered channel whose config block has enabled: true. See Custom channels below.
  2. Embed the runtime in your own Node script. Construct an AgentRuntime, write a small channel class that wires your transport to runAgentLoop, start it directly. See Extending in code.
  3. Wrap the HTTP API. If your platform has a webhook or polling bridge already, point it at POST /api/sessions/:id/messages and the agent works without code changes.

The Slack plugin is the compact reference implementation. The Discord channel (packages/core/src/channels/discord.ts) shows the more complete surface, including slash commands and rooms.

Custom channels

Implement the Channel interface, then register a factory:

ts
import type { AgentRuntime, Channel, Plugin } from "@tailored-ai/core";

class ExampleChannel implements Channel {
  id = "example";
  type = "example";
  // ...connect(), disconnect(), send()
}

const plugin: Plugin = (ctx) => {
  ctx.channels.register("example", async (runtime: AgentRuntime, cfg) => {
    const channel = new ExampleChannel(runtime, cfg);
    await channel.connect();
    return { channel, disconnect: () => channel.disconnect() };
  });
};

export default plugin;

Enable it in config.yaml:

yaml
channels:
  example:
    enabled: true
    token: ${EXAMPLE_TOKEN}

The CLI starts every registered factory whose config has enabled: true. Failures are logged and skipped so one bad channel can't keep the others from starting.

Discord now uses the same channel factory and outbound registry as plugins. Cron, tasks, workflows, and notifications resolve delivery by channel id rather than depending on Discord directly.

Writing your own channel

A channel is an object that:

  1. Listens for incoming messages (HTTP, WebSocket, file change).
  2. Resolves the right session via findOrCreateSession(db, userKey, model, provider, projectId).
  3. Calls runAgentLoop(content, runtime.buildLoopOptions({ session, agentName })).
  4. Sends the response back over its transport.

See Extending in code → Adding a channel.