Keep State Outside Conversations
A shared transcript is convenient for a prototype but poor as a database. Production state requires a schema, versioning, events, and reproducible recovery. The system must distinguish durable state from the model's context: durable state holds work items, statuses, artifact references, budgets, approvals, and audit events, while the context is a temporary projection of the needed part of that state for a specific call.
Three representations solve different tasks. A transcript is convenient for the model but grows fast and updates atomically poorly. A snapshot is convenient to read but, without history, transitions are hard to explain. An event log is convenient for audit: append-only facts let you rebuild the state and see the reason for a decision. In the companion project the basis is a minimal 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 }));
}
}The events are typed, which is what makes recovery and audit reliable.
}
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[];A blackboard pattern can be useful if workers publish independent findings to a shared store and the supervisor reads them by schema. But a shared writeable text document quickly creates lost updates, hidden dependencies, and a fight over the last edit.
State projection. Build a worker's context from durable state by an allowlist of fields. After the answer, validate the output and record an event. Don't keep arbitrary reasoning as the only source of business state.
State is moved outside. Before running in parallel, all that remains is to separate artifact ownership, or the branches will fight over a shared object.