План канально-агностической привязки сессий
Обзор
Этот документ определяет долгосрочную канально-агностическую модель привязки сессий и конкретную область для следующей итерации реализации.
Цель:
- сделать маршрутизацию сессий, привязанных к подагентам, основной возможностью
- сохранить канально-специфичное поведение в адаптерах
- избежать регрессий в нормальном поведении Discord
Почему это существует
Текущее поведение смешивает:
- политику содержимого завершения
- политику маршрутизации назначения
- специфичные детали Discord
Это вызвало граничные случаи, такие как:
- дублирование основной и потоковой доставки при одновременных запусках
- устаревшее использование токенов на переиспользованных менеджерах привязки
- отсутствие учета активности для отправок webhook
Область итерации 1
Эта итерация намеренно ограничена.
1. Добавить канально-агностические основные интерфейсы
Добавить основные типы и интерфейсы сервисов для привязок и маршрутизации.
Предлагаемые основные типы:
tsexport 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>; };
Контракт основного сервиса:
tsexport 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. Добавить один основной роутер доставки для завершений подагентов
Добавить единый путь разрешения назначения для событий завершения.
Контракт роутера:
tsexport 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 или channel send
- валидировать состояние потока (архивирован/удален)
- отобразить метаданные адаптера (идентификация webhook, ID потоков)
4. Исправить текущие известные проблемы корректности
Требуется в этой итерации:
- обновить использование токена при переиспользовании существующего менеджера привязки потока
- записать исходящую активность для отправок Discord на основе webhook
- остановить неявный откат основного канала, когда выбрано привязанное назначение потока для завершения режима сессии
5. Сохранить текущие значения безопасности runtime по умолчанию
Нет изменения поведения для пользователей с отключенным spawn, привязанным к потоку.
Значения по умолчанию остаются:
channels.discord.threadBindings.spawnSubagentSessions = false
Результат:
- обычные пользователи Discord остаются на текущем поведении
- новый основной путь влияет только на маршрутизацию завершения привязанной сессии, где включено
Не в итерации 1
Явно отложено:
- цели привязки ACP (
targetKind: "acp") - новые адаптеры каналов за пределами Discord
- глобальная замена всех путей доставки (
spawn_ack, будущийsubagent_message) - изменения уровня протокола
- переделка миграции хранилища/версионирования для всего сохранения привязки
Примечания по ACP:
- дизайн интерфейса оставляет место для ACP
- реализация ACP не начата в этой итерации
Инварианты маршрутизации
Эти инварианты обязательны для итерации 1.
- выбор назначения и генерация содержимого - это отдельные шаги
- если завершение режима сессии разрешается в активное привязанное назначение, доставка должна нацеливаться на это назначение
- нет скрытой перемаршрутизации от привязанного назначения к основному каналу
- поведение отката должно быть явным и наблюдаемым
Совместимость и развертывание
Цель совместимости:
- нет регрессии для пользователей с выключенной привязкой к потоку spawn
- нет изменений для не-Discord каналов в этой итерации
Развертывание:
- Разместить интерфейсы и роутер за текущими флагами функций.
- Маршрутизировать привязанные доставки режима завершения Discord через роутер.
- Сохранить legacy путь для не-привязанных потоков.
- Проверить целевыми тестами и canary логами runtime.
Требуемые тесты в итерации 1
Требуемое юнит и интеграционное покрытие:
- ротация токена менеджера использует последний токен после переиспользования менеджера
- отправки webhook обновляют временные метки активности канала
- две активные привязанные сессии в одном канале запросчика не дублируются в основной канал
- завершение для привязанного запуска режима сессии разрешается только в назначение потока
- отключенный флаг spawn сохраняет legacy поведение без изменений
Предлагаемые файлы реализации
Основные:
src/infra/outbound/session-binding-service.ts(новый)src/infra/outbound/bound-delivery-router.ts(новый)src/agents/subagent-announce.ts(интеграция разрешения назначения завершения)
Адаптер Discord и runtime:
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.e2e.test.ts
Критерии завершения для итерации 1
- основные интерфейсы существуют и подключены для маршрутизации завершения
- исправления корректности выше объединены с тестами
- нет дублирования доставки завершения основной и потоковой в привязанных запусках режима сессии
- нет изменения поведения для развертываний с отключенным привязанным spawn
- ACP остается явно отложенным