Skip to content

Multi-Agent Routing

Triggerfish supports routing different channels, accounts, or contacts to separate isolated agents, each with its own workspace, sessions, personality, and classification ceiling.

Why Multiple Agents?

A single agent with a single personality is not always enough. You may want:

  • A personal assistant on WhatsApp that handles calendar, reminders, and family messages.
  • A work assistant on Slack that manages Jira tickets, GitHub PRs, and code reviews.
  • A support agent on Discord that answers community questions with a different tone and limited access.

Multi-agent routing lets you run all of these simultaneously from a single Triggerfish installation.

How It Works

  Inbound Messages                      Agent Workspaces

  WhatsApp         ----------------->   Agent: "Personal"
  (personal #)                          Skills: calendar, email
                                        Classification: PERSONAL

  Slack            ----------------->   Agent: "Work"
  (work workspace)                      Skills: jira, github
                                        Classification: CONFIDENTIAL

  Telegram         ----------------->   Agent: "Side Project"
  (@projectbot)                         Skills: coding, deploy
                                        Classification: INTERNAL

The AgentRouter examines each inbound message and maps it to an agent based on configurable routing rules. If no rule matches, messages go to a default agent.

Routing Rules

Messages can be routed by:

CriteriaDescriptionExample
ChannelRoute by messaging platformAll Slack messages go to "Work"
AccountRoute by specific account within a channelWork email vs personal email
ContactRoute by sender/peer identityMessages from your manager go to "Work"
DefaultFallback when no rule matchesEverything else goes to "Personal"

Configuration

Define agents and routing in triggerfish.yaml:

yaml
agents:
  list:
    - id: personal
      name: "Personal Assistant"
      channels: [whatsapp-personal, telegram-dm]
      tools:
        profile: "full"
      model: claude-opus-4-5
      classification_ceiling: PERSONAL

    - id: work
      name: "Work Assistant"
      channels: [slack-work, email-work]
      tools:
        profile: "coding"
        allow: [browser, github]
      model: claude-sonnet-4-5
      classification_ceiling: CONFIDENTIAL

    - id: support
      name: "Customer Support"
      channels: [discord-server]
      tools:
        profile: "messaging"
      model: claude-haiku-4-5
      classification_ceiling: PUBLIC

Each agent specifies:

  • id -- Unique identifier for routing.
  • name -- Human-readable name.
  • channels -- Which channel instances this agent handles.
  • tools -- Tool profile and explicit allow/deny lists.
  • model -- Which LLM model to use (can differ per agent).
  • classification_ceiling -- Maximum classification level this agent can reach.

Agent Identity

Each agent has its own SPINE.md defining its personality, mission, and boundaries. SPINE.md files live in the agent's workspace directory:

~/.triggerfish/
  workspace/
    personal/
      SPINE.md          # Personal assistant personality
    work/
      SPINE.md          # Work assistant personality
    support/
      SPINE.md          # Support bot personality

Isolation

Multi-agent routing enforces strict isolation between agents:

AspectIsolation
SessionsEach agent has independent session space. Sessions are never shared.
TaintTaint is tracked per-agent, not across agents. Work taint does not affect personal sessions.
SkillsSkills are loaded per-workspace. A work skill is not available to the personal agent.
SecretsCredentials are isolated per-agent. The support agent cannot access work API keys.
WorkspacesEach agent has its own filesystem workspace for code execution.

WARNING

Inter-agent communication is possible through sessions_send but is gated by the policy layer. One agent cannot silently access another agent's data or sessions without explicit policy rules allowing it.

Default Agent

When no routing rule matches an inbound message, it goes to the default agent. You can set this in configuration:

yaml
agents:
  default: personal

If no default is configured, the first agent in the list is used as the default.

Released under the MIT License.