Skip to main content
Glama
README.md

# Triadr - Self-Healing Multi-App Agent & Reliability Engine

<img width="2988" height="1716" alt="image" src="https://github.com/user-attachments/assets/dfe3da29-83ed-4c0d-90de-d7710ce3aeb1" />


> Built for the **Multi-App AI Agent Hackathon** (`multiappagenthackathon.com`)
> **Connected apps:** GitHub (code audit) · Telegram (team approval) · Stripe (escrow payout)
> **Stack:** Model Context Protocol · Python 3.11+ · FastAPI · Next.js 14 · Tailwind CSS
> **License:** Apache 2.0

Triadr takes one sentence - *"Audit PR #42, get team sign-off on Telegram, then release
$2,500 from escrow to the contractor"* - and carries it across three external apps as a
six-step workflow. Every side effect passes through a reliability gate that validates,
retries, reroutes, deduplicates and, when a step is genuinely unrecoverable, rolls the
whole workflow back. Every decision lands on a SHA-256 hash chain you can verify yourself.

**The guarantee:** a run ends fully applied or fully reverted. Never half-executed.

## Demo video

**[Watch the 2-minute demo](https://youtu.be/It-J686I8NI)** - the whole workflow running against
the real GitHub, Telegram and Stripe APIs, including a live approval pressed on a phone, a real
payout, a fault storm the gate heals, and a rollback that undoes itself.

[![Watch the 2-minute Triadr demo](public/brand/demo-thumb.png)](https://youtu.be/It-J686I8NI)

## Live demo

| | |
|---|---|
| **Live app** | **https://usetriadr.vercel.app** |
| **Run console** | https://usetriadr.vercel.app/dashboard |
| **Demo video** | https://youtu.be/It-J686I8NI (2 min) |
| *Direct to the control plane* | http://38.49.213.208:8791 · [API health](http://38.49.213.208:8790/api/health) |

The front end is on Vercel and proxies to a FastAPI control plane on a VPS; the second row
is that control plane served directly, as a fallback if the proxy is unavailable.

The public deployment is deliberately **credential-free**: all three apps run in
SIMULATED mode and the UI says so on every card. That is not a limitation of the
integration - it is the only mode that works for a shared URL, because a live run posts
an approval card to one specific person's Telegram and waits for *them* to press a
button. The live runs against the real GitHub, Telegram and Stripe APIs are shown in the
demo video and reproducible with your own credentials in five minutes - see
[§5](#5-try-it-live---real-github-telegram-and-stripe).

## Team

| Member | Email |
|---|---|
| MrNetwork (Ifeanyichukwu Onwo) | mrnetwork0001@gmail.com |

Solo entry. Repository: https://github.com/mrnetwork0001/Triadr

---

## Submission checklist

Everything the hackathon asks for, and where it is in this repository.

| Required | Where |
|---|---|
| **Project overview** - what it is and the problem it solves | Top of this file, and [§1 What you will see](#1-what-you-will-see) |
| **External apps used** (at least three) | **GitHub** (code audit) · **Telegram** (team approval) · **Stripe** (escrow payout) - [§1](#1-what-you-will-see), tool-by-tool in [§11](#11-triadr-as-an-mcp-server) |
| **Setup instructions** | [§3 Try it in five minutes](#3-try-it-in-five-minutes---no-accounts-needed) (no accounts needed) and [§5 Try it live](#5-try-it-live---real-github-telegram-and-stripe) (real credentials) |
| **How reliability was tested** | [§10 Evidence](#10-evidence) - 176 tests, a 40-run fault campaign, measured latency, a verifiable audit chain. Full write-up in [docs/RELIABILITY_BRIEF.md](docs/RELIABILITY_BRIEF.md) |
| **Two-minute demo video** | [youtu.be/It-J686I8NI](https://youtu.be/It-J686I8NI) - 1:57 |
| **Judges can access the repo and demo** | This repository is public; the video is on YouTube; the app is live at [usetriadr.vercel.app](https://usetriadr.vercel.app) |
| **Team** | [§Team](#team) - one person, email included |

---

## Contents

0. [Submission checklist](#submission-checklist) - every requirement and where it lives
0. [Demo video](#demo-video) - 2 minutes, start here
1. [What you will see](#1-what-you-will-see)
2. [Prerequisites](#2-prerequisites)
3. [Try it in five minutes - no accounts needed](#3-try-it-in-five-minutes---no-accounts-needed)
4. [Guided walkthrough of the dashboard](#4-guided-walkthrough-of-the-dashboard)
5. [Try it live - real GitHub, Telegram and Stripe](#5-try-it-live---real-github-telegram-and-stripe)
6. [Command reference](#6-command-reference)
7. [Configuration reference](#7-configuration-reference)
8. [Troubleshooting](#8-troubleshooting)
9. [How it works](#9-how-it-works)
10. [Evidence](#10-evidence)
11. [Triadr as an MCP server](#11-triadr-as-an-mcp-server)
12. [Safety](#12-safety)
13. [Repository layout](#13-repository-layout)

---

## 1. What you will see

The fastest way to see it is the [2-minute demo video](https://youtu.be/It-J686I8NI).
Below is the same workflow in writing, and what can go wrong at each step:

| # | App | Step | If it fails |
|---|---|---|---|
| 1 | GitHub | Audit the pull request - diff surface, sensitive paths, tests, CI - into a 0-100 risk score | retried, rerouted; unrecoverable → run stops cleanly |
| 2 | GitHub | Write the verdict onto the commit as a status check | non-critical: the run continues |
| 3 | Telegram | Post an approval card with real Approve / Reject buttons | unrecoverable → run stops cleanly |
| 4 | Telegram | Wait for a human to press a button | timeout → payout skipped, nothing moved |
| 5 | Stripe | Release the escrow payout as a transfer, under an idempotency key | unrecoverable → **the card is retracted and the status reset** |
| 6 | Telegram | Post the settlement receipt as a reply to the card | non-critical: the run continues |

Four scenarios exercise the happy path and every failure path - see [§6](#6-command-reference).

Under a fault storm that fails ~75% of calls on first attempt, across 40 runs:

| | |
|---|---|
| Faults absorbed | **356** |
| Steps self-healed | **130** |
| Runs ending fully applied | **26** |
| Runs ending fully rolled back | **14** |
| Runs left half-executed | **0** |
| Duplicate payouts | **0** |
| Audit chains that verify | **40 / 40** |

Reproduce it yourself: `python3 scripts/campaign.py --runs 40`

---

## 2. Prerequisites

| Requirement | Notes |
|---|---|
| **Python 3.11+** | The engine (`risk_gate.py`, `agents/`, `mcp_servers/`) is stdlib-only. `python3 main.py` needs nothing installed. |
| **Node.js 18+** and npm | Only for the web app. |
| macOS or Linux | Windows works under WSL. |
| *Optional:* GitHub, Telegram and Stripe accounts | Only for [§5](#5-try-it-live---real-github-telegram-and-stripe). Everything else runs on deterministic simulators. |

---

## 3. Try it in five minutes - no accounts needed

Every app falls back to a simulator when its credential is absent, and says so in every
result, so the whole demo runs offline.

### 3.1 Clone

```bash
git clone https://github.com/mrnetwork0001/Triadr.git
cd Triadr
```

### 3.2 Run the agent in your terminal (30 seconds)

```bash
python3 main.py --scenario all
```

You will see four runs and a comparison table:

```
  scenario   outcome      steps   faults  healed  undone  chain       ms
  clean      APPLIED      6/6     0       0       0       valid      602
  chaos      APPLIED      6/6     15      6       0       valid     1384
  rollback   ROLLED BACK  2/5     4       0       2       valid      612
  rejected   APPLIED      4/6     0       0       0       valid      460
```

How to read it: `chaos` absorbed 15 injected faults and still settled the payout once;
`rollback` lost Stripe after the approval card was posted, so it *undid* the two side
effects it had already made (`undone 2`) and moved no money; `rejected` skipped the payout
by an explicit condition. Every run's audit chain verifies.

Each run also writes `.triadr/<run_id>.jsonl` and an attestation. Verify one:

```bash
python3 main.py --verify .triadr/<run_id>.jsonl
```

### 3.3 Run the web app

Two processes: the FastAPI control plane and the Next.js front end.

```bash
# one-time setup
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
npm install

# terminal 1 - control plane (API + live event stream)
.venv/bin/uvicorn server:app --port 8000

# terminal 2 - front end
npm run dev            # http://localhost:3000
```

If port 8000 or 3000 is taken on your machine, pick others and tell the front end
where the API is:

```bash
.venv/bin/uvicorn server:app --port 8770
TRIADR_API_URL=http://127.0.0.1:8770 npx next dev --port 8771
```

| Route | What it is |
|---|---|
| `/` | Landing page: the problem, the three apps, the eight gate stages, scenarios, evidence, audit log, how to run it. Works with the API down; adds live app status and a freshly measured benchmark when it is up. |
| `/dashboard` | The run console. Pick a scenario, run the agent, watch every gate decision stream in, inspect the sealed audit chain. |

---

## 4. Guided walkthrough of the dashboard

Open `http://localhost:3000/dashboard`. Left to right, top to bottom:

1. **Left rail** - the five sections of the console, the three connected apps with a
   green dot for *live* or grey for *simulated*, and whether the control plane is online.
2. **Run console** - the instruction (pre-filled), a scenario selector, and **Run agent**.
   Start with **Clean run**.
3. Press **Run agent**. Within a second:
   - **KPI strip** fills with measured numbers: reliability score, faults absorbed, steps
     self-healed, and the gate's own overhead in microseconds.
   - **Execution tree** shows the six steps turning from *queued* to *running* to
     *applied*. Click any step to open its **gate attempts**: each physical attempt,
     which endpoint it hit, the fault it got, and the backoff before the next try.
   - **Gate event stream** scrolls every decision as it happens: `gate.fault`,
     `gate.backoff`, `gate.ok`, `saga.compensated` and so on.
   - **Cryptographic audit log** seals when the run ends: entry count, Merkle root, and
     a *Chain verified* badge that was recomputed from the entry bodies, not stamped.
4. Now run **Chaos storm**. Watch steps go amber (*self-healed*) and expand one - you
   will see `TIMEOUT` on the primary gateway, `RATE_LIMITED` on the replica, then success.
   The payout still settles exactly once.
5. Run **Stripe outage**. Stripe is forced down *after* the approval card is posted.
   Watch the run stop, then the **rollback**: the Telegram card is retracted and the
   commit status reset, in reverse order. Result banner: *rolled back cleanly, 2/2 side
   effects reversed, no partial state remains.*
6. Run **Reviewer rejects**. The payout step is *skipped* by condition, and the receipt with it.

Everything you just watched came from the simulators. To make it real, keep going.

---

## 5. Try it live - real GitHub, Telegram and Stripe

Each app switches itself to **LIVE** the moment its credential is present. You can bring
them up one at a time; the readiness check works per app. Full detail, including the
minimum scopes, is in [docs/LIVE_SETUP.md](docs/LIVE_SETUP.md).

### 5.1 Credentials (about 15 minutes total)

```bash
cp .env.example .env     # then fill in the values below
```

| App | What you need | Where |
|---|---|---|
| **GitHub** | A fine-grained personal access token scoped to **one repo you own**, with *Pull requests: read* and *Commit statuses: read & write* (*Checks: read* is optional). Plus an **open pull request** in that repo. | github.com → Settings → Developer settings → Fine-grained tokens |
| **Telegram** | A bot token from **@BotFather** (`/newbot`). Then open your bot and send it any message - bots cannot start a conversation. | Telegram app |
| **Stripe** | A **test-mode** secret key (`sk_test_…`), and **Connect enabled** once (Dashboard → Connect → Get started → *You collect payments and pay recipients*). | dashboard.stripe.com |

```dotenv
GITHUB_TOKEN=github_pat_…      TRIADR_REPO=owner/repo       TRIADR_PR=1
TELEGRAM_BOT_TOKEN=123456:AA…  TRIADR_TELEGRAM_CHAT_ID=      # left blank - discovered below
STRIPE_SECRET_KEY=sk_test_…    TRIADR_CONTRACTOR_ACCOUNT=    # left blank - created below
TRIADR_APPROVAL_TIMEOUT=120
```

### 5.2 Prove each app, in stages

```bash
python3 scripts/live_check.py
```

Read-only. Prints `PASS` / `FAIL` / `SKIP` per tool with the exact fix for each failure.
On the first run it also **discovers your Telegram chat id** and prints the line to add
to `.env`.

```bash
python3 scripts/live_check.py --stripe-setup
```

Funds the test balance (a $100 test-mode charge) and creates a **test payee** through
Stripe's Accounts v2 API, printing an `acct_…` id to add to `.env` **and an onboarding
link**. Open the link and complete the form with test data - use the *Test (Non-OAuth)*
bank and Stripe's test values (`000 000 0000`, DOB `01/01/1901`, SSN `0000`). Until this
is done the payee's transfers capability is `restricted` and every transfer is refused.

```bash
python3 scripts/live_check.py --write --payout
```

A real commit status on your PR, a real Telegram message that deletes itself, and a real
**$1.00 transfer, replayed under the same idempotency key (same transfer id comes back -
no double payment), then reversed.**

```bash
TRIADR_APPROVAL_TIMEOUT=300 python3 scripts/live_check.py --run clean
```

The whole saga, live. A card lands in your Telegram - **press ✓ Approve** - and the
$25 transfer settles, with the receipt posted under the card. Expected output:

```
  succeeded    audit          github.audit_pull_request     2641ms
  succeeded    status         github.set_commit_status      1763ms
  succeeded    approval_card  telegram.post_approval_card    933ms
  succeeded    approval       telegram.await_approval      48921ms   ← the human
  succeeded    payout         stripe.release_escrow         1356ms
  succeeded    receipt        telegram.post_message        10717ms

  6/6 steps applied across 3 apps. 0 fault(s) absorbed, 0 step(s) self-healed,
  0 steps left half-executed. Payout tr_… settled for USD 25.00.
  chain valid: True
```

`--run rollback` does the same with Stripe forced down: the card you just saw is
retracted and the commit status reset, for real.

### 5.3 The live demo from the dashboard

Restart the control plane after editing `.env` (it reads the file at startup), reload
`/dashboard`, and the app dots turn green. The run box now names your real repo, chat and
payee. Press **Run agent**, then **press Approve on your phone when the card arrives** -
the command bar tells you when the agent is waiting on you. Everything on screen is a
real API call; injected faults (Chaos storm) are labelled `[injected]` in the event stream.

Each live clean run moves $25 of test money to the test payee. `--stripe-setup` tops the
balance up again whenever it drops below $50.

---

## 6. Command reference

```bash
# The agent
python3 main.py                        # clean run
python3 main.py --scenario chaos       # clean | chaos | rollback | rejected | all
python3 main.py --instruction "Audit PR #7 in acme/api, sign-off on Telegram, pay acct_1X $50 USD"
python3 main.py --json                 # full result as JSON
python3 main.py --bench                # measured gate overhead, both phases
python3 main.py --verify <log.jsonl>   # re-verify a written audit chain

# Evidence
python3 scripts/campaign.py --runs 40  # regenerates every figure in this README
python3 -m pytest tests/ -q            # 176 tests

# Live readiness
python3 scripts/live_check.py                 # read-only checks per app
python3 scripts/live_check.py --write         # + a write and its undo per app
python3 scripts/live_check.py --approve       # + a real approval card; press a button
python3 scripts/live_check.py --stripe-setup  # fund test balance, create/inspect the payee
python3 scripts/live_check.py --payout        # + $1 transfer, replay, reversal
python3 scripts/live_check.py --run clean     # full saga, live
python3 scripts/live_check.py --run rollback  # full saga with Stripe forced down

# Web app
.venv/bin/uvicorn server:app --port 8000      # control plane (npm run api)
npm run dev                                   # front end (npm run build / npm start for production)
npm run typecheck
```

---

## 7. Configuration reference

All variables live in `.env` (see `.env.example`), loaded by every entry point. Existing
environment variables always win over the file.

| Variable | Purpose | Default |
|---|---|---|
| `GITHUB_TOKEN` | Fine-grained PAT; presence switches GitHub to LIVE | - |
| `TRIADR_REPO` / `TRIADR_PR` | Repository (`owner/name`) and open PR number to audit | `mrnetwork/triadr` / `42` |
| `TELEGRAM_BOT_TOKEN` | BotFather token; presence switches Telegram to LIVE | - |
| `TRIADR_TELEGRAM_CHAT_ID` | User or group id (negative for groups) or `@public_channel` | `@triadr_approvals` |
| `TRIADR_APPROVAL_TIMEOUT` | Seconds a human has to press Approve / Reject | `30` |
| `STRIPE_SECRET_KEY` | Test-mode key; presence switches Stripe to LIVE | - |
| `TRIADR_CONTRACTOR_ACCOUNT` | Connected account the escrow is released to | placeholder |
| `TRIADR_PAYOUT_AMOUNT` / `TRIADR_CURRENCY` | Payout amount (major units) and ISO currency | `2500.00` / `usd` |
| `TRIADR_ALLOW_LIVE_MONEY` | Must be `1` for an `sk_live_` key to move money. Leave unset. | - |
| `TRIADR_APPROVAL_THRESHOLD` | Risk score at or above which a human must approve | `30` |
| `TRIADR_LOG_KEY` | HMAC key that signs each run's attestation | unsigned |
| `TRIADR_LOG_DIR` | Where audit logs are written | `.triadr` |
| `TRIADR_<APP>_ENDPOINTS` | Comma-separated real gateways to fail over across in LIVE mode | one real API |
| `TRIADR_CHAOS` | `1` starts the MCP stdio server with chaos armed | - |
| `TRIADR_LLM_PLANNER` + `ANTHROPIC_API_KEY` | Let Claude extract instruction parameters (never invent steps) | off |
| `TRIADR_API_URL` | Where the front end proxies `/api/*` | `http://127.0.0.1:8000` |

---

## 8. Troubleshooting

| Symptom | Cause and fix |
|---|---|
| `address already in use` starting uvicorn or Next | Another process owns the port. Use other ports: `uvicorn server:app --port 8770` and `TRIADR_API_URL=http://127.0.0.1:8770 npx next dev --port 8771`. |
| Dashboard says *api offline* | The control plane is not running, or the front end proxies to the wrong port. Start uvicorn; check `TRIADR_API_URL`. |
| Apps still show *simulated* after adding credentials | The control plane reads `.env` at startup - restart uvicorn. The CLI and scripts read it on every run. |
| `python3 main.py --scenario clean` rolls back with Stripe LIVE | Your real key is in `.env`, so Stripe is live and the placeholder payee does not exist. Run `--stripe-setup` and complete onboarding, or remove the key to go back to the simulator. |
| Telegram: `no chat has messaged the bot yet` | Open your bot in Telegram, press **Start**, send any message, re-run `live_check.py`. |
| Telegram: `409 Conflict` | A webhook is registered for the bot. Triadr clears it automatically and continues. |
| Stripe: *Stripe no longer recommends Accounts v1* | Your platform requires Accounts v2 - `--stripe-setup` already uses it; update Triadr if you see this elsewhere. |
| Stripe: `insufficient_capabilities_for_transfer` | The payee has not finished onboarding. Re-run `--stripe-setup` to re-open the link, complete it with test data, retry `--payout`. |
| Stripe: *You can only create new accounts if you've signed up for Connect* | Dashboard → Connect → Get started, then re-run `--stripe-setup`. |
| GitHub: 404 on the repository | Fine-grained tokens are per-repo: the token must be granted access to `TRIADR_REPO`. |
| GitHub: no *Checks* permission offered | Optional. `get_check_runs` falls back to the commit-status API, which *Commit statuses* already covers. |
| The approval card arrived but the run timed out | Press the button on the **newest** card; older cards are disarmed and answer with an "expired" toast. Raise `TRIADR_APPROVAL_TIMEOUT`. |
| `npm run build` broke the running dev server | Both use `.next/`. Stop the dev server before building, then restart it. |

---

## 9. How it works

```
        "Audit PR #42, get sign-off on Telegram, then pay $2,500 from escrow"
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │  Planner              │  deterministic plan shape,
                          │  agents/planner.py    │  parameters bound from text
                          └───────────┬───────────┘
                                      ▼
                          ┌───────────────────────┐
                          │  Saga orchestrator    │  6 steps, typed dependencies,
                          │  agents/orchestrator  │  every mutation has a rollback
                          └───────────┬───────────┘
                                      ▼
        ╔═════════════════════════════════════════════════════════════╗
        ║   RELIABILITY GATE - risk_gate.py                           ║
        ║   idempotency · schema · drift · rate shaping · breakers    ║
        ║   backoff+jitter · endpoint failover · compensation         ║
        ╚══════════┬═══════════════════┬══════════════════┬═══════════╝
                   ▼                   ▼                  ▼
            ┌────────────┐      ┌────────────┐    ┌────────────┐
            │  GitHub    │      │  Telegram  │    │  Stripe    │   MCP servers,
            │  6 tools   │      │  4 tools   │    │  4 tools   │   14 tools total
            └────────────┘      └────────────┘    └────────────┘
                   │                   │                  │
                   └───────────────────┼──────────────────┘
                                       ▼
                          ┌───────────────────────┐
                          │  SHA-256 hash chain   │  tamper-evident evaluation log
                          │  reliability_logger   │  + merkle root + HMAC signature
                          └───────────────────────┘
```

### The gate, in execution order

Every call to any of the three apps goes through `ReliabilityGate.guard()`:

1. **Idempotency ledger** - a replayed key returns the recorded result. A retried payout whose response was lost in flight cannot pay twice.
2. **Input schema validation** - zero-LLM, ~1.4 µs. A malformed payload is blocked *before* the executor runs, so a bad call has no side effect at all.
3. **Token bucket** - client-side rate shaping sized below each vendor's documented ceiling, so Triadr sheds load before the vendor does.
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 rather than failing a workflow a single call would have saved.
5. **Execution with typed faults** - every failure is classified (`RATE_LIMITED`, `TIMEOUT`, `AUTH_EXPIRED`, `PERMISSION_DENIED`, …). Retryable faults back off exponentially with full jitter and honour `Retry-After`; terminal faults like a 403 are never retried.
6. **Endpoint failover** - attempts cycle across health-ranked gateways, not the same dead host.
7. **Response contract checking** - a declared `outputSchema` catches drift on the very first call; a learned per-tool fingerprint catches it thereafter. A vendor renaming `decision` to `decision_v2` is caught and rerouted instead of silently corrupting the payout decision.
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.

### The audit log

```
digest(n) = SHA256( digest(n-1) ‖ canonical_json(entry(n)) )
```

Editing, reordering or deleting any entry breaks verification at exactly the corrupted
index. The attestation carries a Merkle root over all entries and, with `TRIADR_LOG_KEY`
set, an HMAC signature. `run.end` is recorded before the seal, so the commitment covers
the complete log.

---

## 10. Evidence

*This is the section that answers "how did you test that it works".*

Three independent kinds of evidence, all reproducible from this repository:

1. **A test suite** - 176 tests, run with `python3 -m pytest tests/ -q`.
2. **A fault campaign** - 40 full workflow runs under a storm that fails ~75% of calls on
   first attempt, run with `python3 scripts/campaign.py --runs 40`. Result: 356 faults
   absorbed, **0 runs left half-executed, 0 duplicate payouts, 40/40 audit chains verify**.
3. **A verifiable audit chain** - every run writes a hash-chained log you can recompute with
   `python3 main.py --verify .triadr/<run_id>.jsonl`, so the numbers above are checkable
   rather than asserted.

Plus a live readiness check against the real APIs (`python3 scripts/live_check.py`), which is
how the video's live run was proven end to end.

Measured on a laptop (`python3 main.py --bench`), never hardcoded:

| Phase | p50 | p99 |
|---|---|---|
| Input schema validation | **1.42 µs** | 1.71 µs |
| Full pre-flight (+ drift fingerprint) | **4.92 µs** | 6.54 µs |

The landing page re-measures this on every load when the control plane is up, and the
dashboard shows the percentiles from your own run.

```bash
python3 -m pytest tests/ -q     # 176 tests
```

Covering schema validation edge cases, breaker transitions, backoff bounds, endpoint
routing, idempotency, drift detection, chaos determinism, the MCP JSON-RPC surface, the
LIVE request wiring for all 14 tools (method, URL, encoding, auth and idempotency
headers, verified against a recorder), hash-chain tamper evidence, and an 8-seed property
test asserting the core invariant: **every run ends fully applied or fully reverted.**
The suite is hermetic: a developer's `.env` can never turn a unit test live.

The full write-up for judges is [docs/RELIABILITY_BRIEF.md](docs/RELIABILITY_BRIEF.md).

---

## 11. Triadr as an MCP server

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

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

| App | Tools |
|---|---|
| **GitHub** | `get_pull_request` · `list_changed_files` · `get_check_runs` · `audit_pull_request` · `set_commit_status` · `clear_status` |
| **Telegram** | `post_approval_card` · `await_approval` · `post_message` · `delete_message` |
| **Stripe** | `get_balance` · `release_escrow` · `get_transfer` · `reverse_transfer` |

Every tool that mutates remote state declares the tool that undoes it - enforced by a test.

---

## 12. Safety

- Stripe refuses to move money on an `sk_live_` key unless `TRIADR_ALLOW_LIVE_MONEY=1` is explicitly set.
- The planner's *shape* is fixed in code. An LLM may only extract parameters, never invent a step - a hallucinated extra `release_escrow` would be a financial incident.
- Condition evaluation is a deliberately tiny parser, not `eval`.
- Every payout carries an idempotency key derived from the workload, not the attempt.
- Every app reports `LIVE` or `SIMULATED` in results, dashboard and audit log; injected faults are labelled `[injected]`.
- Secrets never reach a log: the Telegram bot token is redacted from every fault, and `.env` / `.triadr/` are git-ignored.

---

## 13. Repository layout

| Path | |
|---|---|
| `risk_gate.py` | The reliability gate. Stdlib only, no LLM on the hot path. |
| `agents/planner.py` | Instruction → typed plan with a fixed shape. |
| `agents/orchestrator.py` | Saga engine with reverse-order compensation. |
| `agents/reliability_logger.py` | Hash-chained evaluation log, Merkle root, signature. |
| `mcp_servers/` | GitHub, Telegram and Stripe servers, the registry, and the MCP stdio server. |
| `env.py` / `brand.py` | Stdlib `.env` loader; the text glyph used on Telegram and the CLI. |
| `main.py` | CLI: scenarios, benchmark, verifier. |
| `server.py` | FastAPI control plane with SSE streaming. |
| `scripts/campaign.py` | Reproduces every figure in this README. |
| `scripts/live_check.py` | Proves each app against its real API; Stripe test setup. |
| `tests/` | 176 tests, hermetic. |
| `app/page.tsx` · `components/landing/` · `lib/landing-content.ts` | Landing page, its sections, motion kit and single-sourced figures. |
| `app/dashboard/` · `components/dashboard/` · `components/` | Run console: sidebar, top bar, command bar, KPI strip and the live panels. |
| `public/brand/` · `public/logos/` · `app/icon.png` | Header lockup, vendor logos, favicon. |
| `docs/RELIABILITY_BRIEF.md` | System & reliability brief for judges. |
| `docs/LIVE_SETUP.md` | Credentials, minimum scopes, Stripe Connect setup, what is real vs injected. |
| `docs/BRAND_BRIEF.md` | Positioning, voice and vocabulary. |

---

## License

Apache 2.0