Observed since 21 Oct, 08:54 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Runtime control for AI agents - replay any run, prove what happened, veto what must not happen.
Replay it. Prove it. Veto it.
Every other tool answers "what did my agent do?". Those tools already exist,
and there are a lot of them. agentveto is being built for the harder
question: what is my agent allowed to do, and can I prove it afterwards?
Today it does the first part properly. It records every step - prompts, tool calls, tokens, cost, timing, errors - into a local SQLite file and turns it into a single HTML file you can open, search, and attach to an incident report. Then it replays the whole run for free.
No account. No SaaS. No network call. Everything stays on your machine.
pip install agentveto
import agentveto
agentveto.init() # wraps any installed openai / anthropic SDK
@agentveto.trace(kind="tool")
def lookup_order(order_id: str) -> dict:
return db.orders.find(order_id)
@agentveto.trace
def handle_ticket(ticket: str):
order = lookup_order(parse_id(ticket))
return decide(order)
handle_ticket("two of three items never arrived")
agentveto.report() # writes agentveto-handle-ticket.html
Open the HTML file. Every step is on a timeline, clickable, with its input, output, token count and cost.
No API key and no LLM? Run the built-in example:
python examples/demo.py

Three calls in this run were refused before they executed. The denied rows are
red, the ones that went through a human prompt (and failed closed, since no
human was attached) carry the same veto tag. Click any row for the exact
prompt, response, model and cost.
The replay-only view is the same shape, minus the veto tags:

Regenerate these from the bundled demos with:
python scripts/make_assets.py
| Zero setup | init() and you are done. No project, no token, no dashboard to sign into. |
| Auto-instrumentation | Installed openai or anthropic SDKs are wrapped automatically - tokens and cost captured without touching your call sites. |
| Explicit spans | @trace or with start_span(...) for your own functions and tool calls. Nesting is automatic. |
| Cost attribution | Per-step and per-run, using a pricing table you can override. Unknown models are reported as unknown rather than guessed. |
| Record and replay | First run records LLM responses. init(replay=True) replays them, so a 40-step run is reproducible for free. |
| Policy gates | @guard evaluates a call before it runs and can deny it or route it to a human. Blocked calls are on the record like everything else. |
| Tamper-evident evidence | prove sign chains every run's digest and optionally signs it with Ed25519. verify detects a single changed byte and names the run it is in. |
| Self-contained reports | One HTML file, no CDN, no build step. Renders offline, six months from now, on a plane. |
| Nothing leaves your machine | Traces are written to ./agentveto.db. There is no server component. |
Replay tells you what happened. @guard decides, before a dangerous call goes
out, whether it is allowed to happen at all:
from agentveto import guard, VetoError
POLICY = {
"default": "allow",
"rules": [
{
"name": "no-customer-email",
"action": "send_email",
"effect": "deny",
"reason": "Outbound customer email requires a human in the loop.",
},
{
"name": "refund-needs-approval",
"action": "issue_refund",
"effect": "ask",
"when": {"amount_usd": {"gt": 250}},
"reason": "Refunds over $250 need a human approver.",
},
],
}
@guard(POLICY, action="send_email")
def send_email(to, subject, body): ...
@guard(POLICY, action="issue_refund")
def issue_refund(order_id, amount_usd): ...
issue_refund("A-1", 214.50) # under the line: runs
issue_refund("A-1", 412.50) # raises VetoError - no money moves
VetoError; the wrapped function never executes.when clauses
with eq/ne/gt/gte/lt/lte/in/exists on dot paths into the call payload.veto.python examples/veto_demo.py # 3 blocked calls, no API key needed
agentveto.init() # run once: records every LLM response
agentveto.init(replay=True) # run again: serves recorded responses
The LLM calls come from disk; everything else - routing, tool calls, branching, your business logic - executes for real. That is what makes an old run reproducible instead of merely inspectable. It also makes iteration on step 37 free, because you stop paying for steps 1-36.
Streaming responses are traced but not replayable yet.
A trace in a SQLite file is only as trustworthy as whoever controls the file.
prove turns a run into evidence: a canonical digest chained across runs,
optionally signed with Ed25519, exportable as one portable file that anyone can
verify offline - without your database and without your secret key.
agentveto prove keygen -o ./keys # agentveto.key + agentveto.pub
agentveto prove sign --db agentveto.db --key ./keys/agentveto.key
agentveto prove export --db agentveto.db --run <run-id> -o incident.evd
agentveto verify incident.evd # the auditor's path: offline, no db needed
agentveto verify agentveto.db # or: is my own store still untouched?
OK ok
[ok] target_run_digest data matches attested digest
[ok] chain ok
[ok] head_anchor ok
[ok] signature valid
Two layers, on purpose:
pip install 'agentveto[sign]'): an Ed25519 key
signs the chain head, so a third party can verify the whole history without
holding your key.Signing is a deliberate act, not ambient tracing - you sign when you want to lock in the record. That keeps recording fast and dependency-free.
Honest limits: this proves the integrity of what was recorded, not that the recording is true. Anyone who edits the database before you sign edits the evidence too, so sign at the moment that matters. The chain is linear rather than a Merkle tree: the right size for one process's history.
Recording is a crowded space. Here is the honest map:
| agentveto | agentblackbox | agentledger (TS) | LangSmith | LangFuse | |
|---|---|---|---|---|---|
| Python-native | yes | yes | no | yes | yes |
| Works fully offline | yes | yes | yes | no | no |
| Account required to view a trace | no | no | no | yes | no |
| Single-file report you can email | yes | no | no | no | no |
| Deterministic replay of LLM calls | yes | yes | no | no | no |
| Runtime policy enforcement | yes (V1 preview) | no | no | no | no |
| Tamper-evident evidence chain | yes (v2 preview) | no | yes | no | no |
| Signed, portable evidence file | yes (.evd) | no | no | no | no |
Nobody in the Python ecosystem does enforcement yet. Recording tells you what went wrong; a veto point stops it from going wrong. The preview of that is in this repo now.
The other honest part: today this does less than the hosted platforms. No prompt management, no dataset curation, no hosted dashboards. It does one thing
agentveto demo # example run, no API key needed
agentveto demo --veto # policy-gate demo: calls blocked before they run
agentveto list # recorded runs
agentveto report --run <id> # write a report for a specific run
agentveto serve # local viewer (pip install 'agentveto[serve]')
# policy tooling
agentveto policy show policy.json
agentveto policy validate policy.json --strict
agentveto policy explain policy.json --action send_email --payload '{"to":"x@y.com"}'
# evidence (pip install 'agentveto[sign]' to enable Ed25519 signing)
agentveto prove keygen -o ./keys
agentveto prove sign --db agentveto.db --key ./keys/agentveto.key
agentveto prove export --db agentveto.db --run <run-id> -o incident.evd
agentveto verify incident.evd
agentveto verify agentveto.db
pip install 'agentveto[mcp]'
Then add to your editor's MCP config:
{
"mcpServers": {
"agentveto": {
"command": "agentveto-mcp",
"args": ["--policy", "/abs/path/to/policy.json"]
}
}
}
The LLM in your editor can now ask evaluate_policy("send_email", {...})
and rewrite a call before it gets vetoed at runtime. Full docs:
docs/mcp.md.
agentveto.init(
db="./traces.db", # or set AGENTVETO_DB
replay=False, # serve recorded responses instead of calling the API
record=True, # store responses for later replay
auto_patch=True, # wrap installed LLM SDKs
)
agentveto.set_pricing({"my-internal-model": (1.0, 4.0)}) # USD per 1M tokens
Early. The data model and the report are stable enough to rely on; the API may still change before 1.0. Issues and PRs welcome.
Apache 2.0
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 9 Sept 2026 | 9 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 08:54 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5bc417737412719475f3138b3caae7b48630e9f21035557f69f132168c297a02