Глава 15

Храните состояние вне разговоров

Shared transcript удобен для прототипа, но плохо подходит как database. Production-state требует схемы, versioning, событий и воспроизводимого восстановления. Система должна отличать durable state от контекста модели: durable state содержит work items, statuses, artifact references, budgets, approvals и audit events, а контекст - временная проекция нужной части этого состояния для конкретного вызова.

Три представления решают разные задачи. Transcript удобен модели, но быстро растёт и плохо обновляется атомарно. Snapshot удобен чтению, но без истории трудно объяснить переходы. Event log удобен аудиту: append-only факты позволяют восстановить state и увидеть причину решения. В companion-проекте основа - минимальный append-only log.

TypeScript
import type { EventRecord, RunEvent } from "./types.js";

export class EventLog {
  readonly #records: EventRecord[] = [];
  readonly #clock: () => number;

  public constructor(clock: () => number = Date.now) {
    this.#clock = clock;
  }

  public append(event: RunEvent): EventRecord {
    const record: EventRecord = {
      sequence: this.#records.length + 1,
      timestamp: this.#clock(),
      event
    };
    this.#records.push(record);
    return record;
  }

  public records(): readonly EventRecord[] {
    return this.#records.map((record) => ({ ...record }));
  }
}

События при этом типизированы, что и делает восстановление и аудит надёжными.

TypeScript
}

export type RunEvent =
  | { readonly type: "run.started"; readonly runId: string }
  | { readonly type: "task.started"; readonly taskId: string; readonly agentId: AgentId; readonly attempt: number }
  | { readonly type: "artifact.written"; readonly taskId: string; readonly key: string; readonly version: number }
  | { readonly type: "task.completed"; readonly taskId: string; readonly agentId: AgentId }
  | { readonly type: "task.failed"; readonly taskId: string; readonly agentId: AgentId; readonly reason: string }
  | { readonly type: "run.completed"; readonly runId: string }
  | { readonly type: "run.failed"; readonly runId: string; readonly reason: string };

export interface EventRecord {
  readonly sequence: number;
  readonly timestamp: number;
  readonly event: RunEvent;
}

export interface RunReport {
  readonly runId: string;
  readonly status: "completed" | "failed";
  readonly taskStatus: Readonly<Record<string, TaskStatus>>;
  readonly artifacts: readonly Artifact[];
  readonly evidence: readonly Evidence[];
  readonly budget: BudgetSnapshot;
  readonly trace: readonly TraceSpan[];
  readonly events: readonly EventRecord[];

Blackboard pattern может быть полезен, если workers публикуют независимые findings в общий store, а supervisor читает их по schema. Но общий writeable текстовый документ быстро создаёт lost updates, скрытые зависимости и борьбу за последнюю редакцию.

State projection. Стройте контекст worker из durable state по allowlist полей. После ответа валидируйте output и запишите event. Не сохраняйте произвольный reasoning как единственный источник бизнес-состояния.

Состояние вынесено наружу. Перед параллельным запуском осталось развести владение артефактами, иначе ветви подерутся за общий объект.

Ссылки