Skip to main content
A router is one Slack app that sits in front of many agents and sandboxes. Instead of installing a new Slack app for every agent (often an IT approval each time), a company installs the router once and adds routes: “via this router, this person lands on that agent”. A person who DMs or mentions the router is handed to their routed agent, the target’s own access rules still decide whether they may talk to it, and the router’s App Home in Slack shows and controls the sandboxes that person can reach. Routers live in the client app under Agents -> Routers (/routers); each router opens at /routers/:agentId with the tabs Overview (overview), Routing (routing), Access (access), Slack (slack) and Versions (versions). The same routes are also visible from the target side on an agent’s or sandbox’s Routing tab (/agents/:agentId/routing, /sandboxes/:agentId/routing).

What it does

A router is an agent. createRouterAgent creates an Agent with kind = router (AgentKind: standard, router) in status draft, with a version 1 so the Versions tab has a row. A router has no prompt, model, tools, knowledge, memory or sandbox: updateAgent on a router accepts name, description, avatar and status only and rejects anything else with “Routers have no prompt, model, tools or sandbox”. The router’s sidebar therefore has no Chat, Instructions, Tools, MCP or Skills sections. Routers count against the company’s max_agents limit like any agent, and the slug is derived from the name and must be unique in the company. Agent lifecycle, versions and the Access table are described in Agents. Routes. A route (RouterRoute) says: via router R, principal P lands on target agent A. The principal is a RouterPrincipalKind: SLACK_USER (a Slack user id starting with U or W) or OLYROS_USER (an active member of the company). One principal has at most one route per router (adding a second fails with <person> is already routed to <agent> via this router). A target can be a regular agent or a sandbox; it cannot be the router itself or another router (no chains). Router and target must belong to the same company. An agent can sit behind several routers and still keep its own Slack app. Default target. setRouterDefaultTarget names the agent that people without a route land on; passing no target clears it. With no default, an unrouted person is told “No agent is configured for you via this assistant. Please contact a workspace admin.” How a message to the router is dispatched. When someone DMs the router or mentions it in a channel, the server runs these steps (resolve_slack_target):
  1. Router gate. The router’s own agent.chat grants (its Access table) are checked. A person the router denies gets the access-denied message.
  2. Thread binding. If this Slack thread is already bound to a target, it goes there. A bound target that is no longer active falls through to the next steps.
  3. Route. A route for the Slack user wins; otherwise a route for the Olyros user that Slack user is linked to (the oldest such route).
  4. Default target, if set. Only active, non-router agents of the router’s company qualify at steps 2 to 4; otherwise the person gets the “no agent is configured” message.
  5. Target gate. The target’s own agent.chat grants are checked on every message, bound or not, so removing someone from a target’s Access table takes effect immediately. A denied person gets “You don’t have access to this agent. Please contact a workspace admin to be added.”
  6. Bind. On first contact the thread is bound to the target, with reason mapped (a route) or default (RouterBindingReason).
Both refusal messages are posted ephemerally, visible only to the sender. A route routes but never admits: the access check on the target is the authority. How grants are matched, including Slack groups and linked Olyros users, is described on Agents. Thread bindings. A binding (RouterBinding) pins one Slack thread to one target so a conversation stays with its agent even if routes change later. One row is written per thread (surface is slack, the only RouterSurface), keyed by workspace, channel and thread, recording who started it (bound_by) and why (reason). Removing a route does not move threads already bound to that target. In a DM, each top-level message starts its own thread and so its own binding. routerBindings(routerAgentId, limit) lists a router’s bindings newest first, with the target’s name. How replies travel back. The target agent answers, but the reply leaves through the router’s Slack bot, in the same thread, with the first chunk opened by the answering agent’s name in bold. Rows the turn creates (questions to the user, approval cards, MCP connect cards, outbound messages, sessions) record the router as the path the turn arrived on, so follow-up posts and button clicks go through the same router. When a later post starts from an agent id alone, the server picks the Slack connection in this order: the router the conversation came through; the agent’s own app; the only connected router the agent is reachable through. If several connected routers qualify and none is recorded, nothing is posted. App Home of a router. A router’s Slack app always has an App Home tab, regardless of the platform toggle for regular agents. It is rendered per viewer:
  • The targets that viewer is routed to (their Slack-user route, their linked Olyros user’s route), sorted by name, then the default target. Each is shown only if it is active and the viewer passes its agent.chat check. A viewer the router itself denies sees no targets.
  • A sandbox target shows its status, any failure detail and the same buttons as the sandbox’s own App Home: Start or Stop by state, Rebuild, Open in Olyros, and Stop run while a turn is running. A regular agent shows its name, description, provider and model.
  • At most 8 targets are shown. Beyond that, a line links to the router’s Routing page in Olyros. With no targets the tab says “No agents are available to you via this assistant. Ask a workspace admin to add a route.”
Button clicks on a router’s home are checked twice: the clicker must pass the router’s own Access table, and the target must be reachable through this router (a route, the default target or a thread binding) in the same company. The target’s own access check then runs as for a click on its own App Home. When a sandbox changes state, the App Home of every router that reaches it is refreshed for viewers seen in the last 5 minutes. Sandbox lifecycle is described in Sandboxes. Channel list and installation. A router’s channel list is enforced on the router’s own Slack connection: when the list is non-empty, a mention in a channel not on it gets an ephemeral “I’m not enabled in this channel.” and is not routed. DMs are never limited by the list. Trigger mode accepts only mentions_only; there is no “all messages” mode, so a router acts on DMs and @mentions only. Installing the router’s Slack app uses a one-time, signed install link: a link that is stale, reused or started by someone who is no longer a member is refused, and the fix is to start the install again from the Slack tab. Details of the Slack connection are in Slack.

Who can use it

Talking to a router in Slack needs the router’s agent.chat grant and the target’s agent.chat grant, decided per object by each agent’s Access table: an empty table is open to everyone, any grant closes it. Roles do not bypass these checks. There is no per-feature plan gating; the only plan limit that applies is the max_agents structural limit at creation. The roles are listed on Company settings.

How to configure it

  1. Create the router. Agents -> Routers (/routers) -> New router. Name is required (blank names are rejected), description is optional. createRouterAgent(name, description, avatarUrl). The app opens /routers/:agentId/overview.
  2. Connect it to Slack. Slack tab (/routers/:agentId/slack): create and install the Slack app as for any agent (Slack). The router’s app gets an App Home tab automatically. Optionally restrict the channel list here.
  3. Set the default target (optional). Routing tab (/routers/:agentId/routing) -> Default target select -> pick an agent or No default. setRouterDefaultTarget(routerAgentId, targetAgentId).
  4. Add routes. Routing tab -> Add route: choose Olyros user (a member of this company) or Slack users (picked from the Slack workspace), and a target agent. addRouterRoute(routerAgentId, targetAgentId, principalKind, principalRef). Rejected when the target is the router itself or another router, the Slack id is not a U…/W… id, the Olyros user is not an active member, or the person already has a route on this router. The same form exists on the target’s own Routing tab, where it picks the router instead of the target.
  5. Check the warnings. A route row shows a warning, Not on <agent>'s allowlist; they will be denied, when the target’s Access table would refuse that person (onTargetAllowlist false). Fix it on the target’s Access tab. The warning is advisory: it does not evaluate Slack groups or linked accounts, and the dispatch-time check is authoritative.
  6. Restrict who may use the router (optional). Access tab (/routers/:agentId/access): add Olyros users, Slack users or Slack groups. setAgentAccess. Leaving it empty keeps the router open; the targets still gate.
  7. Publish. Header Publish (publishAgentVersion(targetEnv: production)) makes the router active; Move to draft returns it to draft. Both active and draft routers dispatch Slack messages; a paused or archived router ignores them.
  8. Remove a route. Routing tab -> remove on the row. removeRouterRoute(routeId). Threads already bound to that target stay bound.

Limits and defaults

What it emits

  • Route rows with creator and creation time, listed per router and per target by routerRoutes.
  • Thread binding rows, one per routed Slack thread, with target, starter and reason (mapped or default), listed by routerBindings.
  • The path a turn took. Interactions, sessions, approval requests, outbound Slack messages, pending messages and App Home lifecycle actions carry the router as via_agent_id when the turn or click came through a router. The rows themselves belong to the target agent, so its executions, costs and audit history show routed traffic as the target’s.
  • Server log lines for each routed message with its outcome (routed, router_denied, no_route, target_denied); there is no routing log in the product UI.

Known gaps

  • No page shows thread bindings: routerBindings exists in the API but no client-app screen calls it.
  • No per-route or per-target routing log or counters in the UI; outcomes go to server logs only.
  • Routing is by person only. Routing by channel, user group, keyword or message content is not built.
  • Replies post under the router’s name with a text prefix; posting under the target’s own name and avatar is planned. The app requests the scope for it already.
  • A person cannot pick or switch the agent inside a thread; the binding is fixed at first contact.
  • Bindings never expire, and every top-level DM message adds one; retention is undecided.
  • Slack is the only surface. Microsoft Teams and web chat through a router are planned.
  • The workflow Slack message node’s agent picker (slackCapableAgents) still lists routers that have a Slack connection.
  • Other agent mutations (tools, knowledge, MCP, skills, duplicate, conversion to sandbox) do not reject a router id at the API; only the pickers hide routers.
  • Background start, stop and rebuild outcome rows do not record the router; click-time audit rows do.
  • Edit, publish and archive of a router have no role gate beyond company membership, unlike routes and access, which need editor or admin.
  • When an agent is reachable through several connected routers and a post has no recorded router, nothing is posted (logged as ambiguous).

Customer-safe summary

A router lets your company install one Slack app and put many agents and personal sandboxes behind it, so adding an agent no longer needs a new Slack app. An editor or admin adds routes that send each person, by Slack account or Olyros account, to their agent, and can set a default agent for everyone else. Each agent’s own access rules still decide who may talk to it, and the router can have its own access list as an extra gate. Replies come back in the same thread under the router, labelled with the name of the agent that answered, and a conversation stays with its agent once it starts. The router’s App Home in Slack shows each person the agents and sandboxes they can reach, with start, stop and rebuild controls for sandboxes.