Reference/Reward API + MCP

Reward API + MCP

Developer reference for the randomized-canary reward loop — verdict contract, REST endpoints, MCP tools, cadence, and abstention semantics.

Developer reference for the randomized-canary reward loop. Auth by project key on every request.


Verdict contract

Every response from the Reward API — REST and MCP — uses the same shape:

Jsonc
{
  "experiment_id": "3fa85f64-…",
  "lift": 0.043,          // treatment − control conversion rate; null while abstaining for power
  "confidence": 0.97,     // 1 − running-min p-value (always-valid; null while abstaining)
  "ci": [0.011, 0.074],  // always-valid confidence interval; null while abstaining
  "verdict": "keep",      // keep | rollback | iterate | abstain
  "abstain_reason": null, // "power" | "non_identifiable" | "interacting" | null
  "value_at_risk": -3100, // ±$/day when value_per_conversion is configured; else null
  "cadence": "day",       // "day" | "fast" | null (engine-selected)
  "power_state": {
    "clock": "open",      // "open" = still running; "decided" = verdict is final
    "decided": false
  },
  "guardrail_breaches": [] // names of guardrail metrics the treatment significantly harmed
}

Verdict semantics

VerdictMeaning
keepmSPRT fired positive; treatment is significantly better. Ship it.
rollbackmSPRT fired negative; treatment is significantly worse. Revert.
iterateTarget lifted but a guardrail metric regressed. Revise the change.
abstainNot enough signal yet — or the regime is non-identifiable. See abstain_reason.

abstain is first-class, not a failure. power means the mSPRT needs more traffic; non_identifiable means the design is aliased (VIF > 10); interacting means the observational regime is non-causal (Boundary B4 — a confident guess here would be a lie).

Cadence

The engine selects cadence from traffic and effect size:

  • day — closes for ~20 % lift at 24 k sessions/day.
  • fast — closes for ≥ 50 % lift at higher traffic.

Both cadences run concurrently when supported. Thin-traffic routes report abstain with abstain_reason: "power" until the required sessions accumulate.


REST API

Base URL: https://ingest.trueclara.com (or your TRUECLARA_API_URL).
Auth header: x-trueclara-project-key: <your-project-key>.

POST /v1/reward/register

Register a code change as a canary experiment.

Request

JSON
{
  "deploy_id": "dep_abc123",
  "treatment_commit": "a1b2c3d",
  "target_metric": "checkout_conversion"
}

All fields are optional — pass what your CI step has. target_metric can be a string (metric name) or an object { name, value_per_conversion } to enable ±$/day pricing.

Response — 201

JSON
{ "experiment_id": "3fa85f64-…" }

GET /v1/reward/{experiment_id}

Poll the current verdict contract for an experiment.

Response — 200 — the verdict contract above.

Poll until power_state.decided === true (or verdict !== "abstain" with abstain_reason !== "power"). The mSPRT is always-valid: peeking does not inflate type-I error (measured 0.017 vs 0.45 naive under continuous peeking).


MCP server

The MCP server wraps the REST API as tools, for use with Claude Desktop, custom agents, or any MCP-compatible host.

Install

Terminal
npx @trueclara/mcp

Required environment variables:

TRUECLARA_API_URL=https://ingest.trueclara.com
TRUECLARA_PROJECT_KEY=tc_sk_…

Connect (Claude Desktop claude_desktop_config.json)

JSON
{
  "mcpServers": {
    "trueclara": {
      "command": "npx",
      "args": ["@trueclara/mcp"],
      "env": {
        "TRUECLARA_API_URL": "https://ingest.trueclara.com",
        "TRUECLARA_PROJECT_KEY": "tc_sk_…"
      }
    }
  }
}

Transport: stdio. The server name is trueclara-reward.

Tools

register_experiment

Register a change and get an experiment_id.

Jsonc
// Input
{
  "deploy_id": "dep_abc123",          // optional
  "treatment_commit": "a1b2c3d",      // optional
  "target_metric": "checkout_conversion" // optional
}

// Output
{ "experiment_id": "3fa85f64-…" }

get_verdict

Poll the current verdict contract.

Jsonc
// Input
{ "experiment_id": "3fa85f64-…" }

// Output — verdict contract (see above)

subscribe

Single poll of the verdict contract — equivalent to get_verdict in this version. To implement poll-until-resolved, call subscribe repeatedly until power_state.decided === true or verdict !== "abstain" with abstain_reason !== "power".

Jsonc
// Input
{ "experiment_id": "3fa85f64-…" }

// Output — verdict contract

Typical agent loop

Python
# 1. Register after CI deploy
result = mcp.call("register_experiment", {
    "treatment_commit": sha,
    "target_metric": "checkout_conversion",
})
experiment_id = result["experiment_id"]

# 2. Poll until decided
while True:
    contract = mcp.call("get_verdict", {"experiment_id": experiment_id})
    if contract["power_state"]["decided"]:
        break
    time.sleep(3600)  # day-cadence: check hourly

# 3. Act on verdict
match contract["verdict"]:
    case "keep":    ship()
    case "rollback": revert()
    case "iterate":  file_issue(contract["guardrail_breaches"])
    case "abstain":  log(f"abstain: {contract['abstain_reason']}")

Notes for integrators

  • abstain on every poll is expected while the mSPRT is accumulating traffic. Do not treat it as an error.
  • guardrail_breaches is non-empty when the treatment helped the target metric but harmed a guardrail. The verdict is downgraded from keep to iterate automatically — do not ship a keep verdict that carries non-empty guardrail_breaches (the API enforces this, but the field is surfaced for transparency).
  • value_at_risk is null unless value_per_conversion is set on the target metric (in project settings → Value Routes). When set, it estimates ±$/day based on the control arm's baseline conversion rate and the measured lift.
  • Project key scope: the project key (TRUECLARA_PROJECT_KEY) scopes all experiments to one project. Use a separate key per project.