Dry-run your agent before it spends or writes anything.
Janus runs the agent once against sandboxed tools and lists every call it plans to make. You approve the plan, the agent runs for real, and any call that wasn't in the plan pauses until you decide.
A sample run
Pick a task and press Run. The preview sits on the left and the receipt fills in on the right. These runs are recorded, so nothing here calls a real service.
task
Preview
Receipt
Nothing has run yet.
= same as preview~ within limits+ not in preview
What gets compared
The preview records five things. The real run is checked against each of them as it happens, not after.
| Surface | Preview records | Real run is checked for | When it differs |
|---|---|---|---|
| Tool calls | Name and arguments of each call | A call with a new name or changed arguments | hold |
| Files | Paths and diff hunks | Writes to a path the preview didn't touch | hold |
| Network | Every host contacted | Any host not on the list | block |
| Spend | Cost per call in USD | A call more than 10% over its preview | hold |
| Tokens | Model and estimated usage | Usage more than 2x the estimate | log |
Wrap the agent you already have
Janus sits between the agent and its tools. It works with MCP servers and with plain function-calling tools, and the agent code doesn't change.
import { Janus } from "runjanus";
const janus = new Janus({ onNewCall: "hold", spendTolerance: 0.1 });
const preview = await janus.preview(agent, "Refund duplicate charges on cus_R8812");
preview.calls; // 4 planned calls, $48.00
const receipt = await preview.approve().run();
receipt.held; // calls the real run added, with your decision on each
from runjanus import Janus
janus = Janus(on_new_call="hold", spend_tolerance=0.1)
preview = janus.preview(agent, "Refund duplicate charges on cus_R8812")
preview.calls # 4 planned calls, $48.00
receipt = preview.approve().run()
receipt.held # calls the real run added, with your decision on each
npx runjanus preview ./agent.ts "Refund duplicate charges on cus_R8812"
# prints the plan and writes .janus/preview.json
npx runjanus run --from .janus/preview.json
# runs for real, stops and asks on any call outside the plan
Questions
- How does the preview avoid side effects?
- Reads go to recorded responses or a read-only replica. Writes are captured with their arguments and never sent, which is how the preview knows what the real run should do.
- What happens when the real run makes a call that wasn't planned?
- The run pauses on that call. You approve it, reject it, or save a rule so the same call passes next time. Rejected calls never reach the tool.
- Where does the receipt go?
- It is written as JSON next to your run logs with a SHA-256 digest of its contents. Store the digest anywhere you like and check the file against it later.