Pikopod/pikopod

Pikopod builds a deterministic sandbox from that spec or from their docs page, rehearses the failures their sandbox never produces, and replays the ones production still finds.

Go

39

97 commits

updated Sep 24, 2026

See the code

See what people are saying

SourceMessageScoreDate

Provider sandboxes only know how to succeed, so I built an open-source tool that makes them fail on purpose (r/golang)

I've maintained payment integrations for years. The bugs that hurt were never on the happy path. They were in retry and error handling, which sandboxes never exercise: no unexpected declines, no timeouts halfway through, no duplicate webhooks. That code first ran in production. **Pikopod** is a…

0

Sep 24, 2026

README

pikopod

Their sandbox only knows how to succeed. It has never declined a charge in a way you didn't ask for, never timed out halfway through, never delivered the same webhook twice. So the first time your retry path runs for real, it runs against real money. pikopod builds a sandbox from your provider's own spec, makes it fail on purpose, and when production fails anyway, replays that exact failure back into it so you fix it on a laptop and keep the fix as a test.

One Go binary. Runs locally. No accounts, no telemetry, no cloud.

pikopod: which failure modes does my integration have?

Install

go install github.com/pikopod/pikopod/cmd/pikopod@latest

Or with Homebrew:

brew trust pikopod/tap
brew install pikopod/tap/pikopod

Recent Homebrew requires third-party taps to be trusted explicitly; without the first line it reports Invalid formula. Or download a signed binary from Releases: macOS and Linux, amd64 and arm64, static, zero dependencies. Every release ships SHA256SUMS, cosign-signed, with SLSA provenance; the verification command is in Installation.

Try it in ten seconds

pikopod demo

Zero config. It stands up a fake provider, sends traffic, silently changes the provider's responses, and prints the alerts. Runs in about a second.

Three things, one tool

What it doesDocs
RehearseA deterministic sandbox built from the provider's spec. Eleven failure stories bind to it with nothing authored: declines, timeouts, retry storms, duplicate webhooks.Sandbox · Scenarios
ObserveA fail-open proxy in front of the real provider. It records failures from the first request and shape changes once it knows what normal is, then hands you a fingerprint.Observe
ReproduceThe fingerprint becomes a scenario that replays the production failure against the sandbox. Commit it, and the path is guarded forever.Reproduce

You cannot ask a provider's sandbox to return that exact 503, with that body, at that point in your state machine. pikopod can, because the same tool recorded it and owns the sandbox. See The loop.

Start here: catch a breaking change in CI

No proxy, no account, no config file. It reads two versions of a spec straight from git with no checkout and fails the build on a breaking change:

pikopod spec-diff git:origin/main:openapi.yaml openapi.yaml --fail-on ERR
1 change(s): 1 ERR, 0 WARN, 0 INFO

ERR  GET    /charges/{id}                            endpoint-removed
     endpoint removed from the spec  [fp_bcc85ba9a094]

breaking declared drift at/above ERR — failing the gate (exit 1)

Exit 0 clean, 1 breaking, 2 tool error. Add --format githubactions and every finding lands inline on the pull request diff. Severity comes from one fixed rule, so "breaking" means the same thing on every endpoint and every provider. See Spec diff and CI integration.

Rehearse: make the sandbox fail

curl -fsSL -o examplepay.spec.json https://raw.githubusercontent.com/pikopod/pikopod/main/docs/demo/examplepay.spec.json
pikopod init
pikopod import examplepay --spec ./examplepay.spec.json
pikopod scenario run examplepay declines retry_storm

--spec also takes your provider's spec URL, or its documentation page.

✓ declines — PASSED (4 assertion(s) passed; 0 not evaluated)
    NOT_EVALUATED  arm-decline      armed error on POST /charges
    PASSED         declined         POST /charges → 400
    NOT_EVALUATED  clear            cleared matching faults
    PASSED         recovered        POST /charges → 201
✓ retry_storm — PASSED (4 assertion(s) passed; 0 not evaluated)
    NOT_EVALUATED  arm              armed error on POST /charges
    PASSED         attempt1         POST /charges → 503
    PASSED         attempt2         POST /charges → 503
    PASSED         attempt3         POST /charges → 201

pikopod scenario list examplepay shows every story that bound and, for any that did not, which fact in the spec was missing. pikopod up serves the sandbox on :4600/examplepay so your own tests meet the same failures, and pikopod chaos arms one fault directly. See Faults.

Observe and reproduce

pikopod up also starts the observing agent on :4700/examplepay. Point your app's provider base URL at it, keeping your real credentials; it forwards everything untouched and watches. Incidents fire from the first request. Drift waits 50 samples and 48 hours per endpoint, because a baseline built from five responses has not seen your optional fields yet. pikopod incidents lists what failed, newest first, each with a fingerprint:

pikopod scenario reproduce fp_14835fa32dfb
reproduced fp_14835fa32dfb (examplepay answered 503 on POST /charges) as pikopod-data/scenarios/incident-14835fa32dfb.yaml
PASSED — 1 assertion(s) passed; 0 not evaluated
the failure now happens locally — fix it, then re-run: pikopod scenario run examplepay incident-14835fa32dfb

The generated pack is an ordinary scenario: commit it and it guards that path forever. pikopod replay --ci then gates every build on recorded traffic, with no network and no provider account. For a shape change rather than a failure, pikopod scenario from-drift <fp> pins the old contract instead. See Drift and Replay gate.

It cannot slow your traffic down

The agent serves first and observes afterwards: observation is asynchronous, bounded, and panic-isolated, so if pikopod breaks internally your traffic still flows. It never retries, because a retry in front of a payments API is a double-charge window. It redacts before anything touches disk: credentials become placeholders, identifiers become format-preserving tokens, and unclassifiable strings are dropped. See Data plane safety and Redaction.

Use it from a coding agent

pikopod mcp serves the same checks over the Model Context Protocol, and UNVERIFIABLE is never dressed up as CLEAN. See Coding agents.

{ "mcpServers": { "pikopod": { "command": "pikopod", "args": ["mcp"] } } }

Documentation

docs.pikopod.com: Quickstart · Configuration · Exit codes · Security · CLI reference

Contributing: CONTRIBUTING.md · DEVELOPMENT.md · SECURITY.md · CODE_OF_CONDUCT.md

License

Apache-2.0. See LICENSE.

Contributors

Tunechi28

81 commits

TayfurYldz

4 commits

team-humaki

2 commits

Pikopod/pikopod

Pikopod builds a deterministic sandbox from that spec or from their docs page, rehearses the failures their sandbox never produces, and replays the ones production still finds.

Go

39

97 commits

updated Sep 24, 2026

See the code

See what people are saying

SourceMessageScoreDate

Provider sandboxes only know how to succeed, so I built an open-source tool that makes them fail on purpose (r/golang)

I've maintained payment integrations for years. The bugs that hurt were never on the happy path. They were in retry and error handling, which sandboxes never exercise: no unexpected declines, no timeouts halfway through, no duplicate webhooks. That code first ran in production. **Pikopod** is a…

0

Sep 24, 2026

README

pikopod

Their sandbox only knows how to succeed. It has never declined a charge in a way you didn't ask for, never timed out halfway through, never delivered the same webhook twice. So the first time your retry path runs for real, it runs against real money. pikopod builds a sandbox from your provider's own spec, makes it fail on purpose, and when production fails anyway, replays that exact failure back into it so you fix it on a laptop and keep the fix as a test.

One Go binary. Runs locally. No accounts, no telemetry, no cloud.

pikopod: which failure modes does my integration have?

Install

go install github.com/pikopod/pikopod/cmd/pikopod@latest

Or with Homebrew:

brew trust pikopod/tap
brew install pikopod/tap/pikopod

Recent Homebrew requires third-party taps to be trusted explicitly; without the first line it reports Invalid formula. Or download a signed binary from Releases: macOS and Linux, amd64 and arm64, static, zero dependencies. Every release ships SHA256SUMS, cosign-signed, with SLSA provenance; the verification command is in Installation.

Try it in ten seconds

pikopod demo

Zero config. It stands up a fake provider, sends traffic, silently changes the provider's responses, and prints the alerts. Runs in about a second.

Three things, one tool

What it doesDocs
RehearseA deterministic sandbox built from the provider's spec. Eleven failure stories bind to it with nothing authored: declines, timeouts, retry storms, duplicate webhooks.Sandbox · Scenarios
ObserveA fail-open proxy in front of the real provider. It records failures from the first request and shape changes once it knows what normal is, then hands you a fingerprint.Observe
ReproduceThe fingerprint becomes a scenario that replays the production failure against the sandbox. Commit it, and the path is guarded forever.Reproduce

You cannot ask a provider's sandbox to return that exact 503, with that body, at that point in your state machine. pikopod can, because the same tool recorded it and owns the sandbox. See The loop.

Start here: catch a breaking change in CI

No proxy, no account, no config file. It reads two versions of a spec straight from git with no checkout and fails the build on a breaking change:

pikopod spec-diff git:origin/main:openapi.yaml openapi.yaml --fail-on ERR
1 change(s): 1 ERR, 0 WARN, 0 INFO

ERR  GET    /charges/{id}                            endpoint-removed
     endpoint removed from the spec  [fp_bcc85ba9a094]

breaking declared drift at/above ERR — failing the gate (exit 1)

Exit 0 clean, 1 breaking, 2 tool error. Add --format githubactions and every finding lands inline on the pull request diff. Severity comes from one fixed rule, so "breaking" means the same thing on every endpoint and every provider. See Spec diff and CI integration.

Rehearse: make the sandbox fail

curl -fsSL -o examplepay.spec.json https://raw.githubusercontent.com/pikopod/pikopod/main/docs/demo/examplepay.spec.json
pikopod init
pikopod import examplepay --spec ./examplepay.spec.json
pikopod scenario run examplepay declines retry_storm

--spec also takes your provider's spec URL, or its documentation page.

✓ declines — PASSED (4 assertion(s) passed; 0 not evaluated)
    NOT_EVALUATED  arm-decline      armed error on POST /charges
    PASSED         declined         POST /charges → 400
    NOT_EVALUATED  clear            cleared matching faults
    PASSED         recovered        POST /charges → 201
✓ retry_storm — PASSED (4 assertion(s) passed; 0 not evaluated)
    NOT_EVALUATED  arm              armed error on POST /charges
    PASSED         attempt1         POST /charges → 503
    PASSED         attempt2         POST /charges → 503
    PASSED         attempt3         POST /charges → 201

pikopod scenario list examplepay shows every story that bound and, for any that did not, which fact in the spec was missing. pikopod up serves the sandbox on :4600/examplepay so your own tests meet the same failures, and pikopod chaos arms one fault directly. See Faults.

Observe and reproduce

pikopod up also starts the observing agent on :4700/examplepay. Point your app's provider base URL at it, keeping your real credentials; it forwards everything untouched and watches. Incidents fire from the first request. Drift waits 50 samples and 48 hours per endpoint, because a baseline built from five responses has not seen your optional fields yet. pikopod incidents lists what failed, newest first, each with a fingerprint:

pikopod scenario reproduce fp_14835fa32dfb
reproduced fp_14835fa32dfb (examplepay answered 503 on POST /charges) as pikopod-data/scenarios/incident-14835fa32dfb.yaml
PASSED — 1 assertion(s) passed; 0 not evaluated
the failure now happens locally — fix it, then re-run: pikopod scenario run examplepay incident-14835fa32dfb

The generated pack is an ordinary scenario: commit it and it guards that path forever. pikopod replay --ci then gates every build on recorded traffic, with no network and no provider account. For a shape change rather than a failure, pikopod scenario from-drift <fp> pins the old contract instead. See Drift and Replay gate.

It cannot slow your traffic down

The agent serves first and observes afterwards: observation is asynchronous, bounded, and panic-isolated, so if pikopod breaks internally your traffic still flows. It never retries, because a retry in front of a payments API is a double-charge window. It redacts before anything touches disk: credentials become placeholders, identifiers become format-preserving tokens, and unclassifiable strings are dropped. See Data plane safety and Redaction.

Use it from a coding agent

pikopod mcp serves the same checks over the Model Context Protocol, and UNVERIFIABLE is never dressed up as CLEAN. See Coding agents.

{ "mcpServers": { "pikopod": { "command": "pikopod", "args": ["mcp"] } } }

Documentation

docs.pikopod.com: Quickstart · Configuration · Exit codes · Security · CLI reference

Contributing: CONTRIBUTING.md · DEVELOPMENT.md · SECURITY.md · CODE_OF_CONDUCT.md

License

Apache-2.0. See LICENSE.

Contributors

Tunechi28

81 commits

TayfurYldz

4 commits

team-humaki

2 commits

Languages

Go

99.3%