Triadr

A multi-app agent that is never left half-executed.

Triadr runs one business workflow across three external applications - auditing a pull request on GitHub, collecting team sign-off in Telegram, releasing an escrow payout through Stripe. Every side effect passes through a gate that retries, reroutes, deduplicates and, when a step is genuinely unrecoverable, rolls the whole workflow back.

Audit PR #42 in mrnetwork/triadr, get team sign-off in #eng-approvals, then release $2,500.00 USD from escrow to acct_1TriadrContractor

One sentence in. Six gate-supervised steps out.

Real decisions from a chaos run and a rollback run · hover to pause

Across 40 runs at a ~75% first-attempt failure rate:

faults absorbed0faults absorbed
steps self-healed0steps self-healed
left half-executed0left half-executed
audit chains verify0/40audit chains verify

reproduce · python3 scripts/campaign.py --runs 40

01

The problem

Linear agents fail at step 4 of 6 - and leave the first three behind

Most multi-app agents are scripts. They work in a demo and break in production, because the failure they are least prepared for is the ordinary one: a 429, a 502, a vendor quietly renaming a field.

Without a gate

Stripe rate-limits the payout. The script raises and dies. But the GitHub commit status is already green, and the Telegram approval card is still sitting in the chat asking for a decision on a payment that will never happen.

  • Steps 1-3 applied, steps 4-6 never ran
  • A stale approval request nobody will clean up
  • Retry the whole thing and you might pay twice
  • No record of what the agent actually did

With Triadr

The rate limit is classified, backed off and rerouted to a healthy gateway. If it genuinely cannot be recovered, the approval card is retracted and the commit status reset - in reverse order - before the run reports failure.

  • Every run ends fully applied or fully reverted
  • A replayed payout returns the original transfer
  • Contract drift is caught before it reaches a decision
  • The whole run is hash-chained and independently verifiable

The thesis is small and testable: a multi-step workflow should end fully applied or fully reverted, and the record of which should be independently verifiable. Everything in Triadr exists to make that true.

02

Three connected apps

14 MCP tools, each declaring how it is undone

Every tool publishes a real JSON Schema contract. Every tool that mutates remote state names the tool that reverses it - a structural test enforces this, so the saga guarantee cannot rot as tools are added.

GitHub

GitHub

App #1

Code audit

Reads the pull request, its diff and its CI check runs, then scores the change on diff surface, sensitive paths, test coverage and build state - and writes the verdict back onto the commit.

  • github.get_pull_requestread
  • github.list_changed_filesread
  • github.get_check_runsread
  • github.audit_pull_requestread
  • github.set_commit_statuswrite
  • github.clear_statusundo

Runs against the real GitHub API once GITHUB_TOKEN is set; simulated until then, and it says which.

Telegram

Telegram

App #2

Team approval

Posts an approval card carrying the audit verdict and the proposed payout with real inline Approve / Reject buttons, then long-polls until a human presses one - no public callback URL needed.

  • telegram.post_approval_cardwrite
  • telegram.await_approvalread
  • telegram.post_messagewrite
  • telegram.delete_messageundo

Runs against the real Telegram API once TELEGRAM_BOT_TOKEN is set; simulated until then, and it says which.

Stripe

Stripe

App #3

Escrow payout

Releases the contractor escrow as a Transfer under an idempotency key derived from the workload - so a retry whose response was lost in flight can never pay twice.

  • stripe.get_balanceread
  • stripe.release_escrowpayment
  • stripe.get_transferread
  • stripe.reverse_transferundo

Runs against the real Stripe API once STRIPE_SECRET_KEY is set; simulated until then, and it says which.

The workflow it composes

The plan shape is fixed in code. An LLM may extract parameters - repo, amount, chat - but can never add, remove or reorder a step. A hallucinated second release_escrow would be a financial incident, so the model is not given that authority.

  1. 1GitHubAudit the pull requestDeterministic, itemised risk scoreno side effect
  2. 2GitHubWrite the verdict to the commitNon-criticalundo · clear_status
  3. 3TelegramPost the approval cardCarries the audit + payoutundo · delete_message
  4. 4TelegramWait for Approve or RejectHuman in the loopno side effect
  5. 5StripeRelease the escrow payoutConditional on approvalundo · reverse_transfer
  6. 6TelegramPost the settlement receiptNon-criticalno side effect
03

The reliability gate

Eight checks between the agent and the outside world

Every call to every app goes through one function. It runs in this order, and the first four all happen before a single packet leaves the process.

  1. 1

    Idempotency ledger

    Keys derive from the workload, not the attempt, so a re-run of the same payout collapses onto one key. A replay returns the recorded result instead of moving money again.

  2. 2

    Input schema validation

    A compiled subset of JSON Schema - no LLM, no third-party validator on the hot path. A malformed payload is rejected before the executor runs, so a bad call has no side effect at all.

  3. 3

    Token bucket

    Client-side rate shaping sized below each vendor's documented ceiling. Cheaper to shed load yourself than to absorb a 429.

  4. 4

    Circuit breakers

    Per endpoint, CLOSED → OPEN → HALF_OPEN. An open endpoint is never selected and costs no retry budget. If the whole fleet is open, one probe is still admitted - a breaker sheds load, it does not guarantee failure.

  1. 5

    Typed fault classification

    Rate limits, timeouts, partitions and auth expiry back off exponentially with full jitter and honour Retry-After. A 403 or 404 is terminal and never retried, because retrying it is pure latency.

  2. 6

    Endpoint failover

    Attempts cycle across health-ranked MCP gateways. Scores decay multiplicatively on failure and recover additively on success, so a flapping host is deprioritised without being banned.

  3. 7

    Response contract checking

    A declared outputSchema catches drift on a tool’s very first call; a learned fingerprint catches it thereafter. A vendor renaming decision to decision_v2 is caught and rerouted, not silently fed into a payout decision.

  4. 8

    Compensation

    If a critical step is unrecoverable, completed side effects are undone in reverse order - with a larger retry budget than the forward path, because the rollback is the one thing that must not fail.

04

Four scenarios

Including the two that are supposed to fail

A reliability engine that only demonstrates its happy path has demonstrated nothing. Each scenario is one command, and the rollback path is injected at the app boundary so every layer above it behaves exactly as it would during a real outage.

Clean run

APPLIED

The happy path: six steps, three apps, one settled payout.

6/6
steps
0
faults
0
undone
python3 main.py --scenario clean

Chaos storm

APPLIED

Roughly three in four calls fail on first attempt. The gate heals them; the payout still settles exactly once.

6/6
steps
15
faults
0
undone
python3 main.py --scenario chaos

Stripe outage

ROLLED BACK

Stripe goes hard-down after the approval card is posted. Triadr retracts the card and resets the commit status - no money moved, no stale approval left in the channel.

2/5
steps
4
faults
2
undone
python3 main.py --scenario rollback

Reviewer rejects

APPLIED

The team says no. The payout is skipped by an explicit condition, not by accident.

4/6
steps
0
faults
0
undone
python3 main.py --scenario rejected
05

Evidence

Measured, not asserted

Every figure on this page is produced by a script in the repo. Re-run it and the numbers regenerate; change the engine and they change with it.

40 runs under a ~75% fault storm

Faults are injected on a seeded RNG, so the same seed replays the identical failure sequence and the identical recovery path.

Fully applied
0
Fully rolled back
0
Left half-executed
0
Duplicate payouts
0
Faults absorbed
0
Steps self-healed
0
Side effects reverted
0
Audit chains valid
0/40

The 14 rolled-back runs are the important column. At this fault rate a linear script would have left partial state in most of them - a posted approval card with no payout, or a payment with no record. Triadr left none.

python3 scripts/campaign.py --runs 40

Gate overhead

Both phases are reported, because quoting only the first would overstate the engine.

Phasep50p95p99
Input schema validationGates every side effect before it happens1.42 µs1.50 µs1.71 µs
Full pre-flightThe above plus the response drift fingerprint4.92 µs5.25 µs6.54 µs
python3 main.py --bench

0 tests

Schema edge cases (including True not satisfying integer), breaker state transitions, backoff bounds, endpoint deprioritisation, idempotency, drift detection, chaos determinism, the MCP JSON-RPC surface, LIVE request wiring for all fourteen tools, hash-chain tamper evidence - and an eight-seed property test asserting the core invariant.

python3 -m pytest tests/ -q
06

Cryptographic audit log

Reliability claims you can recompute yourself

Every gate decision, injected fault, recovery and compensation becomes one entry in an append-only SHA-256 hash chain.

digest(n) = SHA256( digest(n−1) ‖ canonical_json(entry(n)) )
  • Tamper-evident by construction

    Editing, reordering or deleting any entry breaks verification at exactly the corrupted index - demonstrated by test, not merely claimed.

  • Deterministic across machines

    Canonical serialisation fixes key order and separators, so the same entry hashes identically anywhere.

  • Merkle root, optionally signed

    The attestation commits to every entry, and with TRIADR_LOG_KEY set it carries an HMAC-SHA256 signature. run.end is recorded before the seal, so the commitment covers the complete log.

Verify any past run

python3 main.py --verify .triadr/<run_id>.jsonl
entries 44
chain VALID
reason chain intact
merkle root 3c49e353f8…addf507d2b

This is what separates a reliability number from a marketing number. The log is written to disk on every run, and the verifier is a standalone command that takes nothing on trust from the process that produced it.

07

Safety

The properties that matter when an agent can move money

PropertyMechanism
No accidental live paymentsStripe refuses an sk_live_ key unless TRIADR_ALLOW_LIVE_MONEY=1 is set explicitly
No agent-invented paymentsThe plan shape is fixed in code; an LLM may only extract parameters, never add a step
No double paymentWorkload-derived idempotency keys plus a replay ledger
No code execution from instructionsCondition evaluation is a small hand-written parser, never eval
No silent contract driftDeclared output schemas plus a learned per-tool response fingerprint
No unverifiable claimsHash-chained log with a standalone verification command
Demo never mistaken for productionEvery app reports LIVE or SIMULATED in results, dashboard and audit log

Honest limitations

Judges will find these anyway, so they are stated up front rather than buried.

  • The campaign figures were produced against the simulators. The bindings implement real GitHub, Telegram and Stripe request and response shapes and LIVE mode is wired end to end, but injected faults come from a seeded distribution, not production traffic.
  • A Telegram bot cannot open a conversation: the reviewer (or group) must message the bot once before it can post there. The live check discovers that chat id for you.
  • The idempotency ledger and breaker state are in-process. A multi-replica deployment would move them to Redis or equivalent; the interfaces are narrow enough to swap.
  • The risk score is a heuristic on purpose - explainable and fast rather than clever, with every contributing factor itemised so a human can disagree with it.
08

Run it

No credentials required

Every app falls back to a deterministic simulator and says so, so the full demo runs offline. Adding a token switches that app to LIVE with no code change.

The agent, end to end
# stdlib only - no pip install neededpython3 main.py --scenario all # the test suite and the reliability campaignpython3 -m pytest tests/ -qpython3 scripts/campaign.py --runs 40
The live visualiser
python3 -m venv .venv.venv/bin/pip install -r requirements.txt # terminal 1 - control plane.venv/bin/uvicorn server:app --port 8000 # terminal 2 - dashboardnpm install && npm run dev

Use Triadr as an MCP server

All 14 tools are exposed over the standard MCP stdio transport, dependency-free. Every call an MCP host makes is gate-supervised, so the host inherits retry, rerouting, idempotency and rollback for free.

claude mcp add triadr -- python3 /path/to/Triadr/mcp_servers/stdio_server.py

Or just watch it run

The dashboard streams every gate decision live - retries, reroutes, circuit breakers, rollbacks and the hash chain sealing itself as the run completes.

Launch app

Watch it heal a workflow in real time

Pick a scenario, run the agent, and follow every retry, reroute and rollback as it streams - then verify the hash chain yourself.