Skip to main content

Migrating from observe()

If you're already using observe() to capture tool calls, switching to replay() takes about 5 minutes. This guide shows the exact code changes.


Key differences

observe()replay()
WrappingMonkey-patches your client in placeReturns a wrapper — you use session.client
EnforcementNone — capture onlyFull pipeline (narrow, validate, gate)
Session stateNonePhases, preconditions, limits, forbidden tools
Return value{ client, flush }ReplaySession with client, flush, kill, getState, getHealth, etc.
CleanupCall flush() when doneCall session.restore() (also flushes)
Multiple on same clientNo (throws)No (throws)

Step 1: Replace observe() with replay() in log-only mode

This is a zero-risk change. log-only mode behaves identically to observe() — captures everything, enforces nothing.

// Before
import { observe } from "@vesanor/replay";

const openai = new OpenAI();
const { client, flush } = observe(openai, {
apiKey: process.env.VESANOR_API_KEY,
});

const response = await client.chat.completions.create({
model: "gpt-4o-mini",
messages,
tools,
});

await flush();
// After
import { replay } from "@vesanor/replay";

const openai = new OpenAI();
const session = replay(openai, {
contractsDir: "./contracts",
agent: "my-agent",
mode: "log-only",
apiKey: process.env.VESANOR_API_KEY,
});

const response = await session.client.chat.completions.create({
model: "gpt-4o-mini",
messages,
tools,
});

session.restore(); // flushes + releases the wrapper

What changed:

  • observe(openai, ...)replay(openai, { mode: "log-only", ... })
  • clientsession.client
  • await flush()session.restore()

Step 2: Add contracts, switch to shadow mode

Write contracts for your tools (or use vesanor observe to auto-generate them). Then switch to shadow mode to see what enforcement would do:

const session = replay(openai, {
contractsDir: "./contracts",
agent: "my-agent",
mode: "shadow", // Compute enforcement, don't apply
apiKey: process.env.VESANOR_API_KEY,
});

Check the dashboard for shadow_delta data — it shows which tool calls would have been blocked or narrowed.


Step 3: Enable enforcement

When shadow shows zero false positives, switch to enforce mode:

const session = replay(openai, {
contractsDir: "./contracts",
agent: "my-agent",
mode: "enforce",
gate: "reject_all",
});

Drop the apiKey initially if you just want local enforcement (Protect level). Add it back when you want server-backed durable state (Govern level).


Common gotchas

"Can't use original client"

With observe(), the original client was monkey-patched — you could use it directly. With replay(), you must use session.client. Using the original client triggers bypass detection.

// Wrong — triggers bypass detection
const response = await openai.chat.completions.create({ ... });

// Right
const response = await session.client.chat.completions.create({ ... });

"observe() already active"

You can't have both observe() and replay() on the same client. Remove observe() first.

// This throws ReplayConfigError
const { client } = observe(openai, { ... });
const session = replay(openai, { ... }); // Error!

"Missing contractsDir"

replay() needs contracts even in log-only mode (it validates the contract directory exists). Create an empty contracts/ directory with just a session.yaml:

# contracts/session.yaml
schema_version: "1.0"
agent: my-agent

"flush() doesn't exist on session"

replay() returns session.flush() (not a destructured flush). But in most cases, session.restore() is better — it flushes and releases the wrapper in one call.


Next steps