# Channel Router & Pairing Architecture

This document describes the internal architecture of GoClaw's channel system, which enables communication across multiple platforms while maintaining a consistent experience.

---

## 1. The `Channel` Interface

Every messaging platform integrated into GoClaw must implement the `Channel` interface (defined in `internal/channel/channel.go`). This ensures the core gateway can interact with all platforms uniformly.

```go
type Channel interface {
    // Lifecycle
    Start(ctx context.Context) error
    Stop() error

    // Outbound Messaging
    SendMessage(ctx context.Context, peerID, content string) error
    SendFile(ctx context.Context, peerID, text string, files []Attachment) error
    SendEmbed(ctx context.Context, peerID string, embed *Embed) error
    SendTyping(ctx context.Context, peerID string) error
}
```

---

## 2. Inbound Data Normalization (`InboundMeta`)

When a channel receives a message, it must normalize the platform-specific payload into an `InboundMeta` struct. This metadata is used by the router to resolve the correct agent and session.

| Field | Description |
|-------|-------------|
| `ChannelName` | The platform name (e.g., `"discord"`, `"msteams"`, `"facebook"`). |
| `AccountID` | The account the message arrived on: the bot's ID on Teams, Telegram and Facebook. Discord puts the sender's user ID here. |
| `PeerID` | **Session Routing Key.** The ID of the conversation (DM or channel). Discord uses `dm:<user ID>` for a DM and `<channel ID>:<user ID>` in a guild channel, so each user has their own session there. |
| `PeerKind` | Either `"direct"` (DM) or `"group"` (guild/team/channel). |
| `AuthorID` | The platform-specific ID of the user who sent the message. |
| `AuthorName` | The display name of the author. |
| `TenantID` | The guild ID in Discord. In MS Teams, the conversation ID of a group chat or channel (with `;messageid=…` removed); empty in a 1:1 chat. Other channels leave it empty. |
| `ParentPeerID` | Used for threaded conversations: on Discord, the channel a thread belongs to. |
| `PlatformRoles` | An array of role IDs the user holds on that platform. Only Discord (the member's role IDs) and the App API set it. |

---

## 3. Agent Routing Priority

The routing engine (`internal/routing/routing.go`) maps an inbound message to an agent based on the following priority:

1.  **Exact Peer Match:** An explicit binding for a specific peer ID (see `PeerID` above).
2.  **Thread Inheritance:** Inheriting the agent bound to the parent channel of a thread.
3.  **Tenant + Platform Role:** Matching a specific tenant and a user's role (e.g., a role ID in a specific Discord guild).
4.  **Platform Role:** A role binding with no tenant, on any tenant of that channel.
5.  **Tenant:** A broad match for an entire tenant (a Discord server, or one Teams group chat or channel).
6.  **AccountID:** Used when running multiple bot identities.
7.  **Channel-wide Fallback:** The agent for that platform (e.g., all Discord messages).
8.  **Default Agent:** `agents.default`, else the agent marked `default: true`, else the first agent in `agents.list`.

See [Multi-Agent Routing](routing.md) for the binding syntax.

---

## 4. The Pairing System Architecture

Access control is enforced via a centralized pairing store. The gatekeeper checks if an incoming `AuthorID` is authorized before allowing the message to reach the agent.

### The Access Gate
Channels do not enforce pairing locally. Instead, the `InboundHandler` (provided during initialization) wraps the request in an access gate. `channels.dm_policy` decides what it does:

| `dm_policy` | Behaviour |
|-------------|-----------|
| `pairing` (default) | A sender listed in `channels.allow_from` (or `"*"` there) or approved in the pairing store gets through. Anyone else gets a challenge code to hand to an admin. |
| `allowlist` | The same check, but an unknown sender is refused ("You are not authorized to use this bot.") and gets no code. |
| `open` | Everyone gets through. |

The App API (`app_api`) skips the gate: its callers are authenticated by the API key or token before the message reaches the handler.

### Data Structures
GoClaw maintains a unified user identity across platforms.

```go
type User struct {
    ID             string              // Unified internal ID: "u_" + creation time in nanoseconds
    GoClawRole     string              // "admin" or "user"
    LinkedAccounts map[string]string   // e.g., {"discord": "1482...", "msteams": "abc..."}
    Meta           map[string]string   // Arbitrary user metadata
}
```

### Storage and TTL
Pairing state is stored in `~/.goclaw/credentials/` as JSON files:
- **`goclaw-pending.json`**: Active pairing requests with a 1-hour TTL.
- **`goclaw-users.json`**: The approved user database.

The running gateway reads these files once and keeps them in memory. A change made with `goclaw pairing` while the gateway runs is not seen until it restarts, and the gateway's next write to the file can drop it. While it runs, approve on the Web UI's Pairing tab or with `POST /api/pairing` (gateway token, body `{"action": "approve", "code": "ABCD1234", "role": "user"}`).

**Session history** is managed separately via the main **SQLite database**, ensuring persistent conversations across restarts.

---

## 5. Implementing a New Channel

To add support for a new platform, follow these steps:

1.  **Define the Channel:** Create a new package in `internal/channel/`.
2.  **Implement `Start()`:** Initialize the platform SDK and start a message listener.
3.  **Normalize Messages:** In your listener, convert inbound events into `InboundMeta` and call the provided `InboundHandler`.
4.  **Implement Outbound Methods:** Map `SendMessage`, `SendFile`, etc., to the platform's API.
5.  **Register the Channel:** In `startChannels` (`cmd/gateway/startup.go`), instantiate the channel, start it, and register its outbound sender with `agent.RegisterChannelSender`. A webhook-based channel is also mounted on the gateway's HTTP server.

### Important Principles
- **Always provide `AuthorName`:** Essential for identity recognition and LLM interaction.
- **Handle Chunking:** Platforms have different message limits. Use the built-in chunking utilities to split long responses.
- **Typing Indicators:** Use a background goroutine to refresh typing indicators during long agent processing.
