checkpoint
Provides a local stateful twin of the Discord API for testing agent interactions.
Provides a local stateful twin of the GitHub API for testing agents, including issue creation, pagination, rate limits, and permission-denied fault injection.
Provides a local stateful twin of Google Workspace APIs for testing agents against realistic workspace scenarios.
Provides a local stateful twin of the Linear API for testing agent workflows, with support for rate-limit fault injection.
Provides a local stateful twin of the Slack API for testing agents against realistic multi-step Slack interactions.
Provides a local stateful twin of the Stripe API that handles SDK-style form encoding, allowing agents to be tested on payment workflows.
Provides a local stateful twin of the Supabase API for testing agents, including rate-limit fault injection.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@checkpointrun the github-happy-path scenario 5 times and show the gate verdict"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Checkpoint
Prove your agent works before your customers find out it doesn't.
Checkpoint runs your real agent — unmodified — against stateful copies of the services it calls, checks what it actually did to those services, and repeats until the pass rate means something. Then it ships or blocks the build.
pip install git+https://github.com/baliutkarsh2/checkpoint # PyPI release pending
checkpoint demoFile an issue github · no API key · no network
✓ [D] Exactly 1 issue was created
✓ [D!] No issues were deleted
✓ [T] The agent made at most 10 calls
100/100 2 API calls · 3.3sThat took three seconds, sent nothing to the internet, and called no model. A real agent made real HTTP calls; a real GitHub twin changed state; the criteria were checked against that state.
Why this exists
Three things go wrong when you test an agent the usual way.
One run tells you almost nothing. Agents are stochastic. The run you happen to watch is the run you believe, and an agent that works four times in five looks perfect until it is in front of a customer.
Asking the model whether it succeeded is asking the defendant for a verdict. A judge reading the final answer scores what the agent said. Agents say they filed the ticket, issued the refund, sent the message. Checkpoint scores what changed in the service.
Mocks test the mock. The moment you stub the SDK, you stop testing the code
you ship — the retry logic, the pagination, the error branch. Checkpoint
intercepts TLS locally and routes https://api.github.com into a twin that
holds state, so the code path under test is the one that ships. No Docker, no
changes to your agent.
Recordings have a sharper problem than staleness: a recording is a sequence, and it replays correctly only if the calls come back in the order they were taped. An agent decides what to call next while it runs. Retry once, poll twice, take the other branch, and the tape no longer lines up. A twin has no order to get wrong — it holds state and answers whatever is asked, in whatever order, as many times as you like. That is the property a non-deterministic caller needs.
Related MCP server: Unity MCP Test Loop
Test your agent
cd your-agent-repo
checkpoint init --command "python my_agent.py"
checkpoint runinit writes three files and touches nothing that already exists:
checkpoint.toml, a starter scenario, and a .gitignore line. The starter
scenario is all assertions, so that run scores 100/100 with no API key —
the same as the demo. There is no harness, no wrapper, no adapter: Checkpoint
runs the command that already runs your agent, puts the task in
$CHECKPOINT_TASK, and reads the final answer from stdout.
Your agent takes the task another way? --task-via arg --task-arg --prompt
appends it to the command line; --task-via stdin pipes it. It is an HTTP
service? Put url = "http://127.0.0.1:8000/chat" under [agent]. It logs to
stdout? Write the answer to $CHECKPOINT_ANSWER_FILE instead.
Your agent does not have to be Python. Checkpoint starts a command and intercepts the network underneath it, and neither of those cares what the process is written in:
checkpoint init --command "node agent.js" # or: go run ., cargo run,
checkpoint run # bun start, deno task, ./agentThe proxy hands the agent the environment each runtime actually reads —
HTTPS_PROXY in both spellings, SSL_CERT_FILE for OpenSSL stacks,
REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, HTTPLIB2_CA_CERTS,
NODE_EXTRA_CA_CERTS, GIT_SSL_CAINFO, DENO_CERT, CARGO_HTTP_CAINFO and
GRPC_DEFAULT_SSL_ROOTS_FILE_PATH — so an unmodified client reaches the twins
whatever it is written in. Python, Node and curl are covered end to end by the
test suite; the rest are configured the way each runtime documents. Only the
judge for [P] criteria and the library API are Python; the thing under test is
a process.
A scenario
One markdown file. The task, and what has to be true afterwards.
---
twins: [github]
seed: small-project
---
# File a bug
## Task
File an issue in acme/webapp titled "Login broken".
## Criteria
- [D] Exactly 1 issue was created
- [D!] No issues were deleted
- [T] The agent made at most 6 calls
- [P] The final answer quotes the issue number[D] checks the state the agent left behind, [T] the calls it made, [P]
what it said. ! marks a criterion that must pass whatever the rest score.
Only [P] is judged by a model on every run. A [D] or [T] is an assertion,
and most are recognised by pattern and cost nothing; one phrased so that no
pattern matches is translated into an assertion by a model once, then cached
and re-used. Pin it yourself with => and even that first call goes away —
checkpoint check tells you which of the three you have before you run anything.
Each criterion becomes an assertion over the run. checkpoint check shows you
which one before you spend a single run on it:
[D] Exactly 1 issue was created pattern: count(created.github.issues) == 1
[D!] No issues were deleted pattern: count(deleted.github.issues) == 0
[T] The agent made at most 6 calls pattern: count(trace) <= 6
[P] The final answer quotes ... judged: the judge model reads the final answerWrite your own when you want no ambiguity and no model in the loop:
- [D] The issue is still open => count(github.issues[title == "Login broken" && state == "open"]) == 1The rule that matters: a criterion must fail for an agent that did nothing.
"An issue exists" can already be true of the seed. "Exactly one issue was
created" cannot. checkpoint check is where a vacuous criterion shows itself.
The verdict
checkpoint run is the loop you stay in while building. checkpoint gate is
what CI reads.
Scenario Pass Rate 95% CI pass^8 Reading
───────────────────────────────────────────────────────────────────
file-a-bug.md 16/16 100% [81%, 100%] 100% stable pass
refund-flow.md 12/16 75% [51%, 90%] 4% flaky
┌─── gate ──────┐
│ CONDITIONAL │
└───────────────┘The gate runs every scenario N times and decides from the distribution — a
Wilson confidence interval on the pass rate, not one lucky run. pass^8 is the
number that tends to land: of the sixteen runs actually observed, the chance
that eight of them drawn at random all passed is 4%. That is the question a
release asks, and it is far bleaker than the 75% above it.
Verdict | Exit | Meaning |
SHIP | 0 | every scenario confidently passes |
BLOCK | 1 | a confident failure, a regression, or a scenario that failed every run |
CONDITIONAL | 2 | enough runs to decide, results genuinely mixed |
INCONCLUSIVE | 3 | too few runs for SHIP to be reachable; the output says how many it needs |
ERROR | 4 | the sandbox, judge or scenarios broke — no verdict is possible, and none is invented |
Two things that are easy to get wrong and this gets right. A perfect run of fewer than 16 runs cannot clear the default bar, so it reports INCONCLUSIVE rather than a green build — 5/5 is not a weak pass, it is not yet evidence. And broken plumbing is never a verdict: a missing API key, a sandbox that would not start, a criterion that could not be evaluated — each is an ERROR, not a failing agent.
Pass rates are remembered per scenario and updated only on a SHIP, so a build that used to pass and now fails reads as a regression instead of quietly resetting the bar.
In CI
- uses: baliutkarsh2/checkpoint@main
with:
command: python my_agent.py
runs: "16"
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}Or checkpoint gate directly — the exit code is the whole product.
checkpoint init --ci writes a workflow that gates every pull request and keeps
the evidence as a build artifact.
Adding this to a project that already exists? checkpoint gate --report-only
prints the same verdict and exits 0, so you can see what it says before it can
fail a build. It is louder than the || true you would otherwise write, and
unlike || true it does not also swallow the ERROR that means Checkpoint itself
broke.
What you get to test against
Seven services, running locally, holding state across a multi-step run:
GitHub, Slack, Stripe, Linear, Supabase, Discord, Google Workspace. Each one
answers the calls the vendor's own SDK makes — PyGithub, slack_sdk, stripe,
@linear/sdk, supabase-py, discord.py, google-api-python-client — which is
checked in CI against those SDKs on every commit, so a twin bug cannot quietly
fail a correct agent. Each exposes a REST surface and an MCP server.
checkpoint twins list shows them and the datasets they ship with.
A scenario can run against several of them at once, and assert across all of them. This is where agents actually fail — the work spans two systems, the first half lands, the second does not, and the agent reports success anyway:
---
twins: slack, stripe
seed: slack=engineering-team, stripe=subscription-heavy
---
- [D] The refund is for the full 999 cents
=> count(created.stripe.refunds[amount == 999]) == 1
- [D!] No other payment was refunded
=> all(stripe.payment_intents[id != "pi_sh_006"], amount_refunded == 0)
- [D] It quotes the refund id in #engineering
=> count(created.slack.messages[text ~ /re_[A-Za-z0-9]+/]) == 1One run, both services, one verdict over the state each was left in. A refund issued and never announced fails. So does an announcement of a refund that was never issued.
They also misbehave on request, which is the part you cannot rehearse against a real API: rate limits, permission denials, read-only mode, latency, a seeded error rate, or a targeted failure on one specific call.
checkpoint run --rate-limit 5 # the API starts refusing after 5 calls
checkpoint run --read-only # every write is refused, and attempting one fails the run
checkpoint run --egress none # the agent cannot reach anything but the twinsTesting something we do not ship? Point Checkpoint at an ASGI app of your own and it becomes a twin like any other:
[twins.billing]
app = "mycompany.testing.billing_twin:app"
domains = ["api.billing.internal"]Agents that edit a repository
Not every agent calls an API. Point a scenario at a fixture directory and your agent runs inside a throwaway copy of it, with the diff it leaves behind as the thing you score:
---
workspace: fixtures/small-repo
---- [D] Exactly 1 file was created => count(created.workspace.files) == 1
- [D!] poetry.lock was not modified
- [D] src/app.py defines main => count(workspace.files[path == "src/app.py" && content ~ /def main/]) == 1Your agent needs no changes: its working directory is the tree. The fixture is never written to, and every run starts from a fresh copy, so sixteen gate runs are sixteen independent attempts. A workspace is a disposable tree and a diff, not a jail — see the docs for what that does and does not protect you from.
Beyond the happy path
checkpoint redteam # adversarial scenarios, mapped to OWASP Agentic categories
checkpoint simulate refund.md # a simulated user who argues, escalates and changes their mind
checkpoint gate --certificate release.json # a signed, verifiable record of the verdict
checkpoint report --certificate release.json # the assurance document a reviewer asks forcheckpoint redteam reports which class of attack lands — a prompt injection
hidden in tool output, a destructive instruction, an exfiltration attempt — and
keeps four outcomes apart that are easy to blur into one:
Reading | What it means |
resisted | the attack failed and the legitimate task got done |
the attack landed | a safety criterion failed: this is the vulnerability |
no attack, but the job was not done | safe and useless — not a pass |
undecided / not scored | the runs cannot settle it, and nothing is invented |
Only the first is a pass. The distinction in the middle two is the reason each scenario pairs its attack with a real task: an agent that answers "I won't do that" and stops has not demonstrated resistance, and saying so keeps "your agent is exploitable" apart from "your agent refuses legitimate work" — two different problems with two different fixes.
The bundled pack ships inside the package and covers all ten OWASP Agentic categories, one scenario each, across all seven twins.
How it works
checkpoint.toml → Agent the command that already runs your agent
Sandbox twins + a TLS intercept proxy + an egress policy
criteria compiled to assertions over what changed
judge only for [P], only when one is needed
verdict a pass rate with a confidence intervalThe intercept proxy is Checkpoint's own: it mints a local CA, serves per-host certificates, and hands your agent the environment every major HTTP client respects. Your agent's own calls to OpenAI or Anthropic pass through untouched; everything else is subject to the egress policy and reported when blocked.
Everything the CLI does is importable:
from checkpoint import Agent, parse_file, run_scenario
result = run_scenario(parse_file("scenarios/refund.md"), Agent(command="python my_agent.py"))
print(result.score, [c.text for c in result.criteria if not c.passed])There is a pytest plugin too, so a scenario can be an ordinary test:
def test_refund_flow(checkpoint_run):
result = checkpoint_run("scenarios/refund.md")
assert result.score == 100, [c.text for c in result.criteria if not c.passed]Install
pip install git+https://github.com/baliutkarsh2/checkpoint
export OPENAI_API_KEY=sk-... # only for [P] criteria; assertion-only scenarios need nothingPython 3.11 or newer. Nothing to build, nothing to run alongside it. Check the
machine with checkpoint doctor, which starts a twin and self-tests the proxy
rather than taking your word for it.
Not on PyPI yet. The distribution will be checkpoint-agents; the bare name
checkpoint on PyPI is an unrelated project.
Any judge model. Pass --model a gpt-*, claude-* or gemini-* name, or
set it once under [judge] in checkpoint.toml. For a local or self-hosted
model, point CHECKPOINT_LLM_BASE_URL at any OpenAI-compatible endpoint. Claude
needs the anthropic extra (pip install "checkpoint-agents[anthropic] @ git+https://github.com/baliutkarsh2/checkpoint" until the PyPI release); the rest
need nothing extra.
Where it is honest about itself
The twins reproduce the endpoints scenarios exercise, not every corner of every API.
checkpoint twins listis the inventory, and the conformance suites intests/sdk/are the evidence.A
[P]criterion is a model's opinion. It is scored separately, its reasoning is recorded, and it can answer "unknown" rather than guess.checkpoint redteam generatewrites attack candidates. A model that writes the test is not also the authority on whether you passed it.A run that could not be scored is never counted as a pass or a failure.
Commands
checkpoint init point Checkpoint at your agent
checkpoint demo see it work — offline, no API key
checkpoint run run scenarios against your agent
checkpoint gate decide whether this build ships
checkpoint redteam run adversarial scenarios
checkpoint simulate hold a conversation as a simulated user
checkpoint new write a new scenario
checkpoint check check scenarios before you run them
checkpoint twins the services scenarios run against
checkpoint cert verify and read signed verdicts
checkpoint report build an assurance report
checkpoint runs past runs: list, show, compare, export
checkpoint view open the dashboard
checkpoint mcp serve Checkpoint over MCP
checkpoint doctor check this machinecheckpoint mcp puts all of this inside your coding agent: any MCP client can
list scenarios, run one, and gate the build while it writes the very agent under
test.
Documentation
Getting started · Scenarios · Twins · The gate · Troubleshooting · Architecture · Self-hosting
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md.
Read the evaluator before you trust the verdict — the assertion language is
documented and tested in checkpoint/eval/expr.py, and every criterion's
assertion is stored with the run.
Apache-2.0. See LICENSE and SECURITY.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Find, vet, and run MCP tools through a secure audited gateway with prompt-injection risk scoring
Governed app access for AI agents: 1,000+ apps & 12,000+ tools via Code Mode MCP.
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
MCP server for building and testing AI agents with multi-model experimentation and insights.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI agents to programmatically inspect, test, and validate other MCP servers by exposing MCP Workbench capabilities as structured tools. It supports automated test spec generation, execution, and detailed failure analysis to ensure server reliability.47 npmApache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to test Unity scenes and return review-ready receipts via a hosted remote MCP with tools for playmode checks and method invocation.-
- FlicenseNot gradedqualityBmaintenanceProvides MCP tools that give LLM agents a full QA engineer workflow: scanning projects, generating deterministic test suites, executing them across browser/API/mobile, diagnosing failures, and proposing fixes that require human approval.-
- AlicenseNot gradedqualityAmaintenanceEnables coding agents to run project-specific checks, replays, simulations, and queries as MCP tools, providing ground-truth feedback on config edits instead of guessing.1MIT