Храните состояние вне разговоров
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.
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 }));
}
}События при этом типизированы, что и делает восстановление и аудит надёжными.
}
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 как единственный источник бизнес-состояния.
Состояние вынесено наружу. Перед параллельным запуском осталось развести владение артефактами, иначе ветви подерутся за общий объект.