Govern a tool in one wrapper

Start by gating a tool you already have, then move the actions that matter inside the execution boundary. Both modes are one import.

Install

npm install @trustrail/sdk-js
# Python: pip install trustrail

A new workspace’s Overview walks you through the two things an agent needs and then prints this block with every value filled in — no placeholders to go and look up. Registering an agent returns its tr_agent_… credential; adding a connection gives you the last two lines.

TRUSTRAIL_URL=http://127.0.0.1:3001
TRUSTRAIL_GATEWAY_URL=http://127.0.0.1:3003
TRUSTRAIL_AGENT_CREDENTIAL=tr_agent_...
TRUSTRAIL_ENVIRONMENT_ID=...
TRUSTRAIL_CONNECTION_ID=...
TRUSTRAIL_PROVIDER=smtp-test

Those six names are why setup is one line: agentFromEnvironment() in TypeScript and agent_from_environment() in Python read exactly this set.

Every action names a connection, so one must exist before anything can be evaluated. For Decision mode a decision-only connection is a single click: it holds no provider credential and can never execute, which is exactly the guarantee guard() already makes. Actions that really move money need a reviewed provider integration instead — that is Enforce mode, where the gateway holds the credential.

Decision mode — gate the tool you have

guard() wraps an existing implementation. It keeps the original signature, so adopting it is a one-line change at the definition site, and the wrapped function runs only on an allowing decision.

import { agentFromEnvironment } from "@trustrail/sdk-js";

const agent = agentFromEnvironment();

const sendEmail = agent.guard(
  {
    actionType: "communications.email.send",
    resource: { type: "mailbox", id: "support@example.com" },
    purpose: (args) => `Email ${args.to.join(", ")}: ${args.subject}`,
  },
  myExistingSender,
);

A denial throws TrustRailDeniedError, and a wait that runs out throws TrustRailApprovalRequiredError. A spent monthly action quota throws TrustRailQuotaExceededError (TrustRailQuotaExceeded in Python) — distinct from a denial because policy never saw the action, and distinct from a transport failure because retrying cannot help; the fix is an owner upgrading the plan. All three carry text that is safe to hand to a model verbatim, because it tells the model the action did not happen and that finding another route is not the fix.

Decision mode gates your code; it does not hold your credentials. A compromised or buggy agent process could call the provider directly. Do not describe it as enforcement.

Enforce mode — the agent never holds the credential

act() drops the local implementation. TrustRail evaluates the action, waits for a human if policy requires one, mints a one-use Ed25519 authorization, and spends it at the governed gateway, so the provider call happens inside the boundary with credentials the agent process never sees.

const result = await agent.act({
  purpose: "Reorder the stock the planner flagged",
  actionType: "payments.purchase.create",
  resource: { type: "payment", id: "order-4821" },
  target: { type: "merchant", id: "supplier-acme" },   // who gets paid
  parameters: { lineItems: [{ sku: "WIDGET-1", quantity: 12 }] },
  financial: { amountMinor: "4999", currency: "USD" },      // how much
  waitForApprovalSeconds: 120,
});

// result.status: "EXECUTED" | "DENIED" | "AWAITING_APPROVAL"

The counterparty and the amount are target and financial, not parameters. Policy and risk read who is being paid and how much from those fields directly, rather than parsing an action-specific payload — so a spend rule works the same across every action type that moves money.

act() never throws on a policy outcome. A denial is a returned result, because refusing an action is a normal answer — and code that treats a refusal as a crash tends to work around it.

Which actions can execute

Enforce mode needs a reviewed provider adapter, so the action registry states plainly which actions have one. A build-time check fails the release if the registry and the adapter table ever disagree, in either direction.

Action typeStatusHow to govern it
payments.purchase.createEXECUTABLEact() — via payments-sandbox or stripe
communications.email.sendEXECUTABLEact() — via smtp-test
repository.mergeDECISION_ONLYguard() — no git adapter yet

Submitting an unregistered action type, or a type paired with a provider the registry does not list for it, is rejected at evaluation. The catalogue is the contract.

Framework adapters

The adapters match each framework's shape structurally and import nothing, so there are no peer dependencies and nothing to keep in step across major releases. Their real job is failure shape: most frameworks treat a thrown error as a run failure instead of feeding it back to the model, so these convert a governance outcome into a tool result.

SDKs

Run the platform locally

Prerequisites: Docker and Node.js 22.23.1 with Corepack enabled.

git clone <your-trustrail-checkout>
cd trustrail
./scripts/bootstrap.sh

Bootstrap installs the pinned workspace, starts digest-pinned infrastructure, applies forward-only migrations, regenerates the API contract, runs the full quality gate, and starts all four processes: the web dashboard on :3000, the control API on :3001, the worker on :3002, and the execution gateway on :3003.

API contract

The OpenAPI 3.1 document under packages/contracts is the source of truth, covering identity, agents, actions, approvals, policies, integrations, executions, delegation, MCP, the agent network, federation, and operations. Generated SDK code is never hand-edited, and the contract check rejects any mutating operation that lacks an idempotency key.

Plans & billing

Plans meter fleet size and action volume, never safety features. Every workspace starts on the Free tier (3 governed agents); an organization owner upgrades from the console's Billing view, which opens a hosted Stripe Checkout page — TrustRail itself never sees card details. Payment failures keep paid entitlements while Stripe retries; entitlements drop to the free tier only when collection stops.

Self-hosting? Set STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, and STRIPE_BILLING_WEBHOOK_SECRET on the API service, and point a Stripe webhook endpoint subscribed to customer.subscription.* events at POST /api/v1/billing/stripe/webhook. Developer mode enforces test-mode keys, so no real card is ever charged from a development deployment.

Verifying the platform

One command runs every layer of verification:

pnpm verify
# quality gate -> migrations -> four clean-database rehearsals
# -> four-process smoke test -> artifact digest verification

The rehearsals create throwaway databases, replay the full migration history, and exercise raced approvals, kill switches mid-flight, webhook quarantine, delegation escalation attempts, and cross-tenant probes — proving the invariants, not asserting them.