---
title: Outbound Session Mirroring Refactor
description: Track outbound session mirroring refactor notes, decisions, tests, and open items.
summary: "Refactor notes for mirroring outbound sends into target channel sessions"
read_when:
  - Working on outbound transcript/session mirroring behavior
  - Debugging sessionKey derivation for send/message tool paths
---

# Outbound Session Mirroring Refactor

This is an engineering note for channel session routing. User-facing session
docs live in [Agents, Sessions, And Tasks](/concepts/agents-sessions-tasks),
[Sessions](/concepts/session), and channel pages.

## Status

- Implemented for core send/message-tool paths and bundled plugin send paths.
- Core + plugin channel routing has been updated for outbound mirroring.
- Gateway send now derives target session when sessionKey is omitted.
- Follow-ups remain for unusual external plugin session formats and voice-call mapping.

## Context

Outbound sends were mirrored into the _current_ agent session, using the tool
session key, rather than the target channel session.

Inbound routing uses channel/peer session keys. That meant outbound responses
could land in the wrong session, and first-contact targets often had no session
entry yet.

## Goals

- Mirror outbound messages into the target channel session key.
- Create session entries on outbound when missing.
- Keep thread/topic scoping aligned with inbound session keys.
- Cover core channels plus bundled extensions.

## Implementation Summary

- New outbound session routing helper:
  - `src/infra/outbound/outbound-session.ts`
  - `resolveOutboundSessionRoute` builds target sessionKey using `buildAgentSessionKey` (dmScope + identityLinks).
  - `ensureOutboundSessionEntry` writes minimal `MsgContext` via `recordSessionMetaFromInbound`.
- `runMessageAction` (send) derives target sessionKey and passes it to `executeSendAction` for mirroring.
- `message-tool` no longer mirrors directly; it only resolves agentId from the current session key.
- Plugin send path mirrors via `appendAssistantMessageToSessionTranscript` using the derived sessionKey.
- Gateway send derives a target session key when none is provided (default agent), and ensures a session entry.

## Thread/Topic Handling

- Slack: replyTo/threadId -> `resolveThreadSessionKeys` (suffix).
- Discord: threadId/replyTo -> `resolveThreadSessionKeys` with
  `useSuffix=false`. This matches inbound because the thread channel id already
  scopes the session.
- Telegram: topic IDs map to `chatId:topic:<id>` via `buildTelegramGroupPeerId`.

## Extensions Covered

- Matrix, MS Teams, Mattermost, BlueBubbles, Nextcloud Talk, Zalo, Zalo Personal, Nostr, Tlon.
- Notes:
  - Mattermost targets now strip `@` for DM session key routing.
  - Zalo Personal uses DM peer kind for 1:1 targets (group only when `group:` is present).
  - BlueBubbles group targets strip `chat_*` prefixes to match inbound session keys.
  - Slack auto-thread mirroring matches channel ids case-insensitively.
  - Gateway send lowercases provided session keys before mirroring.

## Decisions

- **Gateway send session derivation**: if `sessionKey` is provided, use it. If it
  is omitted, derive a sessionKey from target + default agent and mirror there.
- **Session entry creation**: always use `recordSessionMetaFromInbound` with
  `Provider/From/To/ChatType/AccountId/Originating*` aligned to inbound formats.
- **Target normalization**: outbound routing uses resolved targets (post `resolveChannelTarget`) when available.
- **Session key casing**: canonicalize session keys to lowercase on write and during migrations.

## Tests Added/Updated

- `src/infra/outbound/outbound.test.ts`
  - Slack thread session key.
  - Telegram topic session key.
  - dmScope identityLinks with Discord.
- `src/agents/tools/message-tool.test.ts`
  - Derives agentId from session key (no sessionKey passed through).
- `src/gateway/server-methods/send.test.ts`
  - Derives session key when omitted and creates session entry.

## Open Items / Follow-ups

- Voice-call plugin uses custom `voice:<phone>` session keys. Outbound mapping is
  not standardized here. If message-tool should support voice-call sends, add
  explicit mapping.
- Confirm if any external plugin uses non-standard `From/To` formats beyond the bundled set.

## Files Touched

- `src/infra/outbound/outbound-session.ts`
- `src/infra/outbound/outbound-send-service.ts`
- `src/infra/outbound/message-action-runner.ts`
- `src/agents/tools/message-tool.ts`
- `src/gateway/server-methods/send.ts`
- Tests in:
  - `src/infra/outbound/outbound.test.ts`
  - `src/agents/tools/message-tool.test.ts`
  - `src/gateway/server-methods/send.test.ts`
