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.

      SurfacePreview recordsReal run is checked forWhen it differs
      Tool callsName and arguments of each callA call with a new name or changed argumentshold
      FilesPaths and diff hunksWrites to a path the preview didn't touchhold
      NetworkEvery host contactedAny host not on the listblock
      SpendCost per call in USDA call more than 10% over its previewhold
      TokensModel and estimated usageUsage more than 2x the estimatelog

      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

      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.