Session 綁定頻道無關計畫
概覽
本文件定義長期的頻道無關 session 綁定模型,以及下一次實作迭代的具體範圍。
目標:
- 將 subagent 綁定 session 路由設為核心能力
- 將頻道專屬行為保留在轉接器中
- 避免一般 Discord 行為的回歸
為何需要
目前的行為混合了:
- 完成內容策略
- 目的地路由策略
- Discord 專屬細節
這導致了邊緣案例如:
- 並行執行下的主頻道與執行緒重複投遞
- 重用綁定管理器時的過時 token 使用
- webhook 傳送缺少活動記帳
第一次迭代範圍
本次迭代刻意限縮。
1. 新增頻道無關的核心介面
新增核心型別和綁定與路由的服務介面。
提議的核心型別:
export type BindingTargetKind = "subagent" | "session";
export type BindingStatus = "active" | "ending" | "ended";
export type ConversationRef = {
channel: string;
accountId: string;
conversationId: string;
parentConversationId?: string;
};
export type SessionBindingRecord = {
bindingId: string;
targetSessionKey: string;
targetKind: BindingTargetKind;
conversation: ConversationRef;
status: BindingStatus;
boundAt: number;
expiresAt?: number;
metadata?: Record<string, unknown>;
};
核心服務合約:
export interface SessionBindingService {
bind(input: {
targetSessionKey: string;
targetKind: BindingTargetKind;
conversation: ConversationRef;
metadata?: Record<string, unknown>;
ttlMs?: number;
}): Promise<SessionBindingRecord>;
listBySession(targetSessionKey: string): SessionBindingRecord[];
resolveByConversation(ref: ConversationRef): SessionBindingRecord | null;
touch(bindingId: string, at?: number): void;
unbind(input: {
bindingId?: string;
targetSessionKey?: string;
reason: string;
}): Promise<SessionBindingRecord[]>;
}
2. 新增 subagent 完成的核心投遞路由器
新增完成事件的單一目的地解析路徑。
路由器合約:
export interface BoundDeliveryRouter {
resolveDestination(input: {
eventKind: "task_completion";
targetSessionKey: string;
requester?: ConversationRef;
failClosed: boolean;
}): {
binding: SessionBindingRecord | null;
mode: "bound" | "fallback";
reason: string;
};
}
本次迭代:
- 僅
task_completion透過此新路徑路由 - 其他事件類型的既有路徑保持不變
3. 保留 Discord 作為轉接器
Discord 仍為第一個轉接器實作。
轉接器職責:
- 建立/重用執行緒對話
- 透過 webhook 或頻道傳送綁定訊息
- 驗證執行緒狀態(已歸檔/已刪除)
- 對應轉接器中繼資料(webhook 身分、執行緒 id)
4. 修復目前已知的正確性問題
本次迭代必須修復:
- 重用既有執行緒綁定管理器時重新整理 token 使用
- 為基於 webhook 的 Discord 傳送記錄出站活動
- 停止在已為 session 模式完成選擇綁定執行緒目的地時隱式回退至主頻道
5. 保留現行的執行時安全預設值
停用執行緒綁定 spawn 的使用者不會有行為改變。
預設值保持:
channels.discord.threadBindings.spawnSubagentSessions = false
結果:
- 一般 Discord 使用者維持現行行為
- 新的核心路徑僅影響啟用時的綁定 session 完成路由
不在第一次迭代中
明確延後:
- ACP 綁定目標(
targetKind: "acp") - Discord 以外的新頻道轉接器
- 全面取代所有投遞路徑(
spawn_ack、未來的subagent_message) - 協定層級變更
- 所有綁定持久化的儲存遷移/版本重新設計
ACP 備註:
- 介面設計保留 ACP 的空間
- 本次迭代未開始 ACP 實作
路由不變量
這些不變量在第一次迭代中為必要。
- 目的地選擇和內容生成為分開的步驟
- 若 session 模式完成解析為活躍的綁定目的地,投遞必須以該目的地為目標
- 不從綁定目的地隱式重路由至主頻道
- 回退行為必須明確且可觀察
相容性與上線
相容性目標:
- 關閉執行緒綁定 spawn 的使用者不會有回歸
- 本次迭代不改變非 Discord 頻道
上線:
- 在現行功能開關後面落地介面和路由器。
- 透過路由器路由 Discord 完成模式的綁定投遞。
- 保留非綁定流程的舊有路徑。
- 以目標測試和金絲雀執行時日誌驗證。
第一次迭代所需的測試
必要的單元和整合覆蓋:
- manager token 輪換在 manager 重用後使用最新 token
- webhook 傳送更新頻道活動時間戳
- 同一請求者頻道中的兩個活躍綁定 session 不重複投遞至主頻道
- 綁定 session 模式執行的完成僅解析至執行緒目的地
- 停用的 spawn 旗標保持舊有行為不變
提議的實作檔案
核心:
src/infra/outbound/session-binding-service.ts(新增)src/infra/outbound/bound-delivery-router.ts(新增)src/agents/subagent-announce.ts(完成目的地解析整合)
Discord 轉接器和執行時:
src/discord/monitor/thread-bindings.manager.tssrc/discord/monitor/reply-delivery.tssrc/discord/send.outbound.ts
測試:
src/discord/monitor/provider*.test.tssrc/discord/monitor/reply-delivery.test.tssrc/agents/subagent-announce.format.test.ts
第一次迭代的完成標準
- 核心介面已存在並連接完成路由
- 上述正確性修復已合併且有測試
- session 模式綁定執行中無主頻道和執行緒的重複完成投遞
- 停用綁定 spawn 的部署無行為改變
- ACP 明確延後