Open Harness
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

StoreUse Case
MemorySignalStoreTesting, 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: { ... } },
// ]

Next Steps

On this page