План канально-агностической привязки сессий

Обзор

Этот документ определяет долгосрочную канально-агностическую модель привязки сессий и конкретную область для следующей итерации реализации.

Цель:

  • сделать маршрутизацию сессий, привязанных к подагентам, основной возможностью
  • сохранить канально-специфичное поведение в адаптерах
  • избежать регрессий в нормальном поведении Discord

Почему это существует

Текущее поведение смешивает:

  • политику содержимого завершения
  • политику маршрутизации назначения
  • специфичные детали Discord

Это вызвало граничные случаи, такие как:

  • дублирование основной и потоковой доставки при одновременных запусках
  • устаревшее использование токенов на переиспользованных менеджерах привязки
  • отсутствие учета активности для отправок webhook

Область итерации 1

Эта итерация намеренно ограничена.

1. Добавить канально-агностические основные интерфейсы

Добавить основные типы и интерфейсы сервисов для привязок и маршрутизации.

Предлагаемые основные типы:

ts
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>;
};

Контракт основного сервиса:

ts
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. Добавить один основной роутер доставки для завершений подагентов

Добавить единый путь разрешения назначения для событий завершения.

Контракт роутера:

ts
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 или 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 каналов в этой итерации

Развертывание:

  1. Разместить интерфейсы и роутер за текущими флагами функций.
  2. Маршрутизировать привязанные доставки режима завершения Discord через роутер.
  3. Сохранить legacy путь для не-привязанных потоков.
  4. Проверить целевыми тестами и 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.ts
  • src/discord/monitor/reply-delivery.ts
  • src/discord/send.outbound.ts

Тесты:

  • src/discord/monitor/provider*.test.ts
  • src/discord/monitor/reply-delivery.test.ts
  • src/agents/subagent-announce.format.e2e.test.ts

Критерии завершения для итерации 1

  • основные интерфейсы существуют и подключены для маршрутизации завершения
  • исправления корректности выше объединены с тестами
  • нет дублирования доставки завершения основной и потоковой в привязанных запусках режима сессии
  • нет изменения поведения для развертываний с отключенным привязанным spawn
  • ACP остается явно отложенным