Docs › Python & Node: LangGraph, OpenAI Agents SDK and any script
Python & Node: LangGraph, OpenAI Agents SDK and any script
Two single-file clients with no dependencies. Both fail open: if RunVouch is unreachable your code runs unmonitored and prints one warning.
Python
# curl -fsSL https://runvouch.com/runvouch.py -o runvouch.py import os, runvouch runvouch.agent("nightly-etl", cadence_s=86400, cap_run_cost=2, evidence_required=True) with runvouch.vouch("nightly-etl", evidence=lambda: {"parquet": os.path.getsize("out.parquet") > 0}) as run: for f in files: run.tool("summarize", {"file": f}, cost=0.004) # identical inputs 8x → RETRY_STORM summarize(f)
An exception inside the block ends the run as fail with the error text in the alert; a clean exit ends it as ok and evaluates your evidence.
LangGraph
from langchain_core.callbacks import BaseCallbackHandler
import runvouch
class Vouch(BaseCallbackHandler):
def __init__(self, run): self.run = run
def on_tool_start(self, serialized, input_str, **kw):
self.run.tool(serialized.get("name", "tool"), input_str)
def on_llm_end(self, response, **kw):
u = (response.llm_output or {}).get("token_usage", {})
self.run.tool("llm", None, tokens=u.get("total_tokens", 0), cost=u.get("total_cost", 0))
with runvouch.vouch("research-graph", evidence=lambda: {"report": open("report.md").read() != ""}) as run:
graph.invoke(state, config={"callbacks": [Vouch(run)]})OpenAI Agents SDK
from agents import Runner
import runvouch
with runvouch.vouch("inbox-agent", evidence=lambda: {"replied": replied_count > 0}) as run:
result = Runner.run_sync(agent, "Triage today's inbox")
run.tool("openai.run", None, tokens=result.context_wrapper.usage.total_tokens)Per-tool reporting is optional; per-run cost and evidence are enough for daily caps, drift and "green ≠ done".
Node
// curl -fsSL https://runvouch.com/runvouch.js -o runvouch.js (Node 18+)
const rv = require('./runvouch');
await rv.agent('nightly-report', { cadence_s: 86400, cap_run_cost: 2, evidence_required: true });
await rv.vouch('nightly-report', async (run) => {
for (const page of pages) { await run.tool('fetch', { url: page }); await scrape(page); }
}, { evidence: async () => ({ report: fs.existsSync('out/report.html') }) });CrewAI, AutoGen, anything else
Wrap the entry point with rv run (no code changes) or use the two HTTP calls: POST /v1/runs/start and POST /v1/runs/end — see the API. Cost from OpenRouter/OpenAI/Anthropic responses goes into cost on the end call.
Need a key? Get one free · Stuck? contact