Глава 20

Состояние нельзя прятать в разговоре

Инструменты дают действия, но действия бессмысленны без состояния. История сообщений удобна для языка, но плохо годится как единственная база состояния: заказ, actor, выбранный язык, approval и текущий этап должны жить в типизированном state, а не растворяться в тексте диалога.

TypeScript
type ConversationState = {
  sessionId: string;
  actorId: string;
  tenantId: string;
  locale: string;
  verifiedEntities: {
    orderId?: string;
    callbackPhone?: string;
  };
  workflow: {
    intent?: string;
    stage: "discover" | "explain" | "preview" | "awaiting_approval" | "done";
    pendingActionId?: string;
  };
  memoryRefs: string[];
};

Разные данные живут разное время, и хранить их нужно по-разному.

  • Текущая реплика и промежуточный tool result - на один turn, в invocation context.
  • Выбранный заказ и pending action - на сессию, в session state.
  • Предпочтительный язык - между сессиями и с согласием, в user profile или memory.
  • Политика возврата - это версионируемое знание, ему место в knowledge base, а не в памяти пользователя.
  • Approval - с коротким TTL и одним применением, в server-side token store.

Модели тоже нужно явно сказать, как обращаться со state.

- Не считай entity подтверждённым, пока оно не появилось в verifiedEntities.
- Не извлекай permissions из текста диалога.
- При смене намерения сбрось только workflow-specific поля, сохрани actor и locale.
- Перед завершением перечисли state changes как proposed delta.
- Backend применяет delta после schema и policy validation.

И осторожно со сводками.

Summary может потерять запрет. Compaction и handoff обязаны сохранять policy, approvals и неразрешённые конфликты в структурированном виде. Короткая литературная сводка - ненадёжный continuation state.

Ссылки