Learn
Recording & Replay
Test workflows with deterministic signal replay
Recording & Replay
Record signal traces from live runs and replay them without harness calls — perfect for deterministic testing.
Why Recording?
- Deterministic Tests: Replay produces identical signal traces every time
- No API Calls: Replay mode injects signals from the recording
- CI-Friendly: Run tests without harness credentials
- Debugging: Inspect exactly what happened during a run
Record a Run
import { createWorkflow, ClaudeHarness, MemorySignalStore } from "@open-harness/core";
type State = { input: string; result: string | null };
const { agent, runReactive } = createWorkflow<State>();
const analyzer = agent({
prompt: "Analyze: {{ state.input }}",
activateOn: ["workflow:start"],
emits: ["analysis:complete"],
updates: "result",
});
// Create a signal store
const store = new MemorySignalStore();
// Record mode: signals are stored
const result = await runReactive({
agents: { analyzer },
state: { input: "Hello world", result: null },
harness: new ClaudeHarness(),
endWhen: (s) => s.result !== null,
recording: {
mode: "record",
store,
name: "analyzer-test",
tags: ["unit-test"],
},
});
console.log("Recording ID:", result.recordingId);
console.log("Signals recorded:", result.signals.length);Replay a Run
// Replay mode: signals injected from recording
const replayResult = await runReactive({
agents: { analyzer },
state: { input: "Hello world", result: null },
harness: new ClaudeHarness(), // Not called during replay
endWhen: (s) => s.result !== null,
recording: {
mode: "replay",
store,
recordingId: result.recordingId!, // From previous run
},
});
// Same result, no API calls
console.log("Replayed result:", replayResult.state.result);Signal Store Interface
interface SignalStore {
// Save a recording
saveRecording(recording: Recording): Promise<void>;
// Load a recording
loadRecording(id: string): Promise<Recording | null>;
// List recordings
listRecordings(options?: ListOptions): Promise<RecordingMetadata[]>;
}
interface Recording {
id: string;
name?: string;
tags?: string[];
signals: Signal[];
createdAt: string;
}Built-in Stores
| Store | Use Case |
|---|---|
MemorySignalStore | Testing, ephemeral sessions |
| File-based (coming soon) | Persist recordings to disk |
Testing Pattern
Use recording/replay in your tests:
import { describe, it, expect } from "vitest";
import { MemorySignalStore } from "@open-harness/core";
describe("Analyzer Agent", () => {
const store = new MemorySignalStore();
it("records a live run", async () => {
const result = await runReactive({
// ... config ...
recording: { mode: "record", store, name: "test-run" },
});
expect(result.recordingId).toBeDefined();
expect(result.signals).toContainEqual(
expect.objectContaining({ name: "workflow:start" })
);
});
it("replays deterministically", async () => {
const recordings = await store.listRecordings({ name: "test-run" });
const recordingId = recordings[0].id;
const result = await runReactive({
// ... same config ...
recording: { mode: "replay", store, recordingId },
});
// Same output every time
expect(result.state.result).toBeDefined();
});
});VCR-Style Debugging
Use the Player for step-through debugging:
import { Player } from "@open-harness/core";
const recording = await store.loadRecording(recordingId);
const player = new Player(recording);
// Step through signals
while (player.hasNext()) {
const signal = player.next();
console.log(`[${signal.timestamp}] ${signal.name}`, signal.payload);
}
// Or jump to specific signal
player.seekTo("agent:completed");Signal Trace
Every run returns the full signal trace:
result.signals
// [
// { name: "workflow:start", timestamp: "...", payload: { ... } },
// { name: "agent:activated", payload: { agent: "analyzer" } },
// { name: "harness:start", payload: { model: "claude-sonnet-4-20250514" } },
// { name: "text:delta", payload: { content: "The " } },
// { name: "text:delta", payload: { content: "analysis " } },
// { name: "harness:end", payload: { ... } },
// { name: "analysis:complete", payload: { ... } },
// { name: "workflow:end", payload: { ... } },
// ]