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() | |
|---|---|---|
| Wrapping | Monkey-patches your client in place | Returns a wrapper — you use session.client |
| Enforcement | None — capture only | Full pipeline (narrow, validate, gate) |
| Session state | None | Phases, preconditions, limits, forbidden tools |
| Return value | { client, flush } | ReplaySession with client, flush, kill, getState, getHealth, etc. |
| Cleanup | Call flush() when done | Call session.restore() (also flushes) |
| Multiple on same client | No (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", ... })client→session.clientawait 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
- Quickstart — full setup walkthrough
- Protection Levels — understand Monitor vs Protect vs Govern
- Shadow Mode — test contracts before enabling enforcement