The Aroha Handbook
From first guardrail
to agent fleet
One connected story, told in eight chapters. We follow Meridian — a fintech building an internal AI operations assistant — from a near-miss weekend to a production fleet of agents that cannot exceed what they're allowed to do — then an honest look under the hood at where the protocol stops and a partner begins. Every example is real and runnable. You can start at chapter two and be safer by lunch.
Meridian is a fictional company used to illustrate the adoption journey — not a real customer or reference. The platform, packages, and code examples are real.
Contents
- 01The weekend that started itThe problem every agent team eventually meets
- 02The seatbeltBound your assistant's tools in 15 minutes — no code
- 03Building your own agentAn agent is a function behind an endpoint
- 04Authority it cannot exceedStop trusting instructions; issue signed limits
- 05A team of agentsDelegation where scope only ever narrows
- 06Joining the networkDiscover and call agents by capability
- 07ProofEvery action traces back to a human
- 08Into productionDeployment, compliance, and scale
- ◆How it really worksThe honest boundaries: payment & KYC, agent selection, reputation
01The weekend that started it
Meridian
Meridian's platform team built “Ops Copilot” — an AI assistant wired into their GitHub, their Postgres, and their cloud console, so engineers could ask it to check deploy status, tidy branches, and pull metrics. It was useful immediately.
One Friday, an engineer asked it to “clean up the old staging resources.” By Monday it had deleted a staging database that a migration job still depended on, opened and auto-merged four cleanup PRs, and spun up compute in a loop trying to “verify” its own changes. Nothing malfunctioned. Every tool did exactly what it was asked. No one had said where the edges were.
This is the moment nearly every agent team reaches. The tools we use to constrain software — API keys, OAuth scopes, rate limits — were built for applications, which do the same thing every run. An agent decides what to do next at runtime, and sometimes that decision is wrong, or is a prompt injection buried in a page it just read. The key doesn't care. Keys say yes.
Meridian didn't need a smarter model. They needed a permission layer — somewhere the agent's intentions get checked before they touch the world. The rest of this handbook is the layer they built, one step at a time. You can follow the same path with your own agents; each chapter is independently useful, and nothing here requires the chapter before it except the story.
Where Meridian is now
A useful assistant with no boundaries — and a clear, expensive lesson about why that combination is unstable.
02The seatbelt
Meridian
Meridian's first move took fifteen minutes and no code. Ops Copilot reached its tools through MCP — the same standard Claude Desktop and Cursor use — so they put a proxy in front of each MCP server that enforces rules on every call.
mcp-guard wraps any MCP server. Neither the assistant nor the server changes; the server never knows it's there. In the client config, Meridian wrapped their GitHub server:
Wrap the server with rules
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@aroha-sdk/mcp-guard",
"--block", "delete_*", // never happens
"--gate", "merge_*", // ask a human first
"--limit", "create_*:10", // at most 10 per session
"--",
"npx", "-y", "@modelcontextprotocol/server-github"]
}
}
}Everything after -- is the original server command, untouched.
Watch a block happen
The next time the agent tried to delete a repo, the server never saw the call:

The denial message is written for the model as much as the human, so the assistant recovered gracefully instead of retrying in a loop.
Read the audit log
Every decision lands in ~/.aroha/mcp-guard.jsonl. Meridian ran it rule-free for a week first, just to learn what the agent actually did — which is the fastest way to discover which rules you need.
Where Meridian is now
The assistant is bounded at the tool plane — deletes blocked, merges gated, writes budgeted, everything logged — with zero changes to any server. This alone would have prevented the weekend.
03Building your own agent
Meridian
Guarding off-the-shelf tools was step one. Next, Meridian wanted their own agent — a “deploy-status” service other tools could call. They expected a framework and a weekend. It took five lines.
In Aroha, an agent is just a function behind an HTTP endpoint. The serve() wrapper gives you streaming, sessions, a health check, and a manifest — with no framework to learn.
TypeScript — zero dependencies:
import { serve } from "@aroha-sdk/run"; // npm i @aroha-sdk/run
const agent = serve("deploy-status", async ({ message }) => {
const svc = message.trim();
const status = await checkDeploy(svc); // your logic
return `${svc}: ${status.state} (${status.version})`;
});
agent.start(8000);Python — same shape:
from aroha import serve # pip install aroha
@serve(name="deploy-status")
async def handler(message, context):
status = await check_deploy(message.strip())
return f"{message}: {status['state']} ({status['version']})"
handler.start(port=8000)Call it — a complete agent:
curl -X POST http://localhost:8000/v1/run \
-H "Content-Type: application/json" \
-d '{"message": "checkout-service"}'
# → { "message": "checkout-service: healthy (v2.4.1)", "sessionId": "..." }agent.fetch), and flipping exposeMcp: true makes the same agent an MCP server any assistant can call. The point isn't the five lines — it's that this exact function will, in the next chapter, start verifying signed authority without a rewrite. The on-ramp and the protocol are the same road.Where Meridian is now
Two shipped agents of their own (deploy-status and, soon, a cost-reporter) plus the guarded off-the-shelf tools. Everything still runs on trust, though — anyone who can reach the endpoint can call it. That's the next problem.
04Authority it cannot exceed
Meridian
Meridian's cost-optimizer agent needed to actually do things that cost money — resize instances, buy reserved capacity. Handing it a cloud API key meant handing it every permission that key had, forever. Instead, they gave it a signed permission slip it physically could not exceed.
A mandate is an Ed25519-signed token carrying limits. The rule of the system is that a delegated mandate can only ever be narrowed, never widened — and everyone downstream verifies the signature. Meridian's human operator issues a spending intent, which the orchestrator narrows for the specific vendor agent:
import { issueIntentMandate, attenuateToPayment, verifyMandate } from "@aroha-sdk/credentials";
// A human grants the orchestrator up to $500 for cost actions, 1-hour TTL
const intent = await issueIntentMandate(
operatorDid, orchestratorDid,
{ spendLimitUsd: 500, allowedActions: ["resize-instance", "buy-reserved"] },
operatorPrivateKey, 3_600_000,
);
// The orchestrator narrows to $200 for the vendor agent — allowed
const payment = await attenuateToPayment(
intent, vendorAgentDid, // note: the whole signed envelope, not the bare mandate
{ spendLimitUsd: 200 },
orchestratorPrivateKey,
);
// The vendor verifies before acting
const { valid } = await verifyMandate(payment.token, operatorPublicKey);
// valid === true, and the vendor can spend at most $200.
// Trying to widen throws — the math refuses:
// "child spendLimitUsd (800) exceeds parent (500)"For non-monetary work — research, drafting, scheduling — Meridian used task mandates, which carry capability lists, per-action limits, and human gates. Their log-analysis agent gets exactly this and nothing more:
import { issueTaskMandate } from "@aroha-sdk/credentials";
const { token } = await issueTaskMandate(
operatorDid, logAgentDid,
["read-logs", "summarise"], // the only capabilities it may use
{
actionLimits: { "read-logs": 50 }, // and no more than 50 reads
reversibleOnly: true, // nothing irreversible, ever
humanGates: ["export-data"], // pause for a human if it tries to export
},
operatorPrivateKey,
);Where Meridian is now
Agents that transact do so under signed, expiring, un-exceedable limits instead of shared keys. But so far each mandate goes to one agent. Meridian's real workflows involve chains of agents — and that's where most systems lose the thread.
05A team of agents
Meridian
An incident hits. Meridian's Ops Copilot, now an orchestrator, fans the work out: a log-analyzer to find the cause, a cost-optimizer to size the fix. Each sub-agent should get only the slice of authority it needs — and none should be able to spawn a runaway chain of its own. This is the exact shape the weekend incident took, and the exact shape Aroha is built to make safe.
serveDelegated() makes an agent a node in a web. Every incoming request must carry a verified mandate chain or it's rejected before your handler runs. Inside, ctx.delegate() attenuates the agent's own authority and calls the next agent — scope narrowed, depth decremented, the chain extended:
import { serveDelegated, registryResolver } from "@aroha-sdk/delegation";
serveDelegated("orchestrator", {
identity: { did: orchestratorDid, privateKey: orchestratorKey },
trustAnchors: { [operatorDid]: operatorPublicKeyB64 }, // whose root we trust
resolvePublicKey: registryResolver(), // resolve hop keys via the Hub
}, async (ctx) => {
ctx.assertCapability("triage-incident"); // verified before this line runs
const cause = await ctx.delegate(logAgentDid, ctx.message,
{ allowed: ["read-logs", "summarise"] });
const fix = await ctx.delegate(costAgentDid, cause.message,
{ allowed: ["resize-instance"], constraints: { /* $ cap */ } });
return fix.message;
}).start(8000);Two guarantees make this safe rather than merely convenient. First, each delegate() can only narrow — the log-analyzer literally cannot be granted a capability the orchestrator wasn't. Second, maxDelegationDepth counts down at every hop; a depth-0 mandate is a dead end, so “an agent spawned an agent that spawned an agent” is structurally impossible, not just discouraged.
And the whole thing reports back. Receipts nest from the leaves up to whoever started the chain, under one correlation ID:
orchestrator (complete)
├─ log-analyzer (complete) — actions: read-logs, summarise
└─ cost-optimizer (complete) — actions: resize-instanceexamples/delegation-web/demo.mjs. Docs: Agent Webs.Where Meridian is now
A fleet where authority flows down and only shrinks, recursion has a floor, and results flow back as one auditable tree. The incident that took a weekend in chapter one now resolves inside bounds no agent can cross.
06Joining the network
Meridian
Meridian's agents were useful internally. Other teams wanted them — and Meridian wanted to discover agents rather than hard-code endpoints. So they registered on the Hub, where agents are found by capability, not by URL.
Before registering anything, Meridian's engineers tried the flow against the live sandbox agents — no account needed:
# Discover agents that can do a capability
curl "https://aroha-registry.aroha-labs.workers.dev/v1/agents?capabilities=search-flights"
# Call one directly — a signed envelope comes back
curl -X POST https://www.aroha-labs.com/api/sandbox/echo \
-H "Content-Type: application/json" \
-d '{"type":"ArohaRequest","from":"did:aroha:you","to":"did:aroha:sandbox:echo",
"correlationId":"c1","body":{"capability":"echo","params":{"message":"hello"}}}'Then they registered their own deploy-status agent — a DID, a signed capability manifest, an endpoint. Discovery is by capability, so callers find it by what it does:
import { serve } from "@aroha-sdk/run";
const agent = serve("deploy-status", handler, { exposeMcp: true });
// Register on the Hub (needs an API key — agents can self-provision one too)
await agent.register("https://ops.meridian.internal/deploy-status", {
apiKey: process.env.AROHA_API_KEY,
});
// Now discoverable: GET /v1/agents?capabilities=deploy-status/api/keys/agent-request. The whole discover → verify → call loop is documented for machines in llms.txt. Browse what's live: the Hub.Where Meridian is now
Meridian's agents are discoverable by capability and callable with verified authority — the internal fleet is now a node in a wider network it can also consume from.
07Proof
Meridian
Meridian is a fintech, so the question was always coming: “prove what the agents did.” Because every action had run under a mandate, the proof already existed — they just had to read it.
Every mandated action produces a TaskReceipt: what ran, what it consumed, which limits were checked, and — via the delegation tree — which human authorization it descends from. One correlationId threads the entire incident response from chapter five:
{
"receiptId": "rcpt-…",
"correlationId": "inc-4471", // same id across every hop
"executedByDid": "did:aroha:meridian:cost-optimizer",
"issuedByDid": "did:aroha:meridian:operator-alice", // traces to a human
"status": "complete",
"actions": [{ "capability": "resize-instance", "status": "success" }],
"consumed": { "wallClockMs": 1240 },
"mandateBoundsCheck": { "withinBounds": true, "violations": [] }
}And when Meridian's auditors asked the harder question — “how do we know the protocol enforces what it claims?” — the answer was a table, not a promise. Every guarantee maps to the function that enforces it and the published test that proves it, all readable inside the MIT npm tarballs:
assertTaskNarrowing() → the test "rejects a scope widened mid-chain". The full map: Guarantees & Evidence. When something is refused, the reason is a documented, machine-readable code: Error Reference.Where Meridian is now
Meridian can answer “what did the agents do, and who authorized it?” for any action, back to a named human — and can point auditors at the exact code and test behind every guarantee. Compliance stops being a slide and becomes a query.
08Into production
Meridian
With the model proven internally, Meridian moved to production scale — and to the handful of things a regulated company needs that an open-source library doesn't hand you: someone to run the registry at an SLA, SSO, retention, and a deployment that lives inside their own boundary.
Everything in this handbook is MIT-licensed and self-hostable — the protocol is yours to run. What Meridian chose to buy was the operational and compliance envelope around it:
- Managed infrastructure — the hosted registry, agents, and manifest hosting at an SLA — instead of operating them
- Governed identity — org-scoped keys, rotation, SSO, and RBAC over who can issue what
- The compliance envelope — tamper-evident audit retention, GDPR & financial-regulation guidance, SOC 2 (in progress)
- Deployment in your boundary — private VPC or fully on-premise, for workloads that can't touch shared infrastructure
Where Meridian is now
A production agent fleet whose authority is cryptographically bounded, whose every action is provable, running inside Meridian's own compliance boundary. The weekend that started it couldn't happen now — not because the model got smarter, but because the edges are enforced.
◆How it really works
Meridian
Before go-live, Meridian's payments and risk teams asked the three questions every serious adopter eventually asks — the ones the demo glosses over. Here are the straight answers, including exactly where Aroha stops and a partner begins. We'd rather you hear the boundaries from us than discover them in production.
Real today shipped & verifiable·Needs a partner real integration, not built/demonstrated yet·Needs data mechanism real, needs network volume
1. Who moves the money, and who checks the human
The most important thing to understand: Aroha authorizes; it is not a payment processor and not a KYC provider. A spending mandate is the cryptographic equivalent of “approved for up to $600 at this merchant” — signed, bounded, auditable. It never moves funds and never verifies a passport. Those happen at the edges, in systems that specialise in them:
- Real todayThe mandate + spending ledger enforce the ceiling
- Real todaySettlement adapter interfaces (
null · quota · stripe · escrow) and AP2 interop - Needs a partnerKYC of the human — done upstream by their bank / wallet / identity provider; Aroha binds the mandate to the human's DID and lets a verifier require a verified issuer tier
- Needs a partnerA live, money-moving transaction with a real merchant and rail
Human identity ──▶ BANK / IdP: KYC ← not Aroha; Aroha requires proof of it
Intent + limits ─▶ AROHA: signed mandate ← this is what Aroha is
Execution ───────▶ VENDOR AGENT: real API ← Aroha delivers bounded authority; vendor acts
Money movement ──▶ STRIPE / AP2 / BANK ← not Aroha; the mandate terminates here
Proof ───────────▶ AROHA: receipt trail ← this is what Aroha is2. How an agent gets chosen
When Meridian's orchestrator needed a “book-flight” agent, it didn't hard-code a URL — it ran a four-stage funnel:
Capability filter
the registry returns only agents that advertise the skill (an index lookup — non-matches never appear)
Eligibility & verification
inactive agents and those in the 48-hour anti-squatting cooldown are dropped; the caller can require a verification tier — you don't send a payment mandate to a community-tier stranger
Reputation ranking (Thompson Sampling)
among survivors, a random sample is drawn from each agent's Beta distribution and the highest wins — mature agents usually win (exploit), but high-uncertainty newcomers occasionally sample high and get a shot (explore), so the network never ossifies
Mandate constraints
if the mandate names allowedMerchants, agents outside the set are filtered regardless of score
In one line: capability filters to who can, verification to who you trust, Thompson-sampled reputation ranks who's good while giving newcomers a chance, and the mandate filters to who you authorized. The selector lives in @aroha-sdk/orchestrator.
3. How reputation is actually earned
Not stars — a Bayesian model. Each agent is a Beta(α, β): α accumulates success evidence, β failure, from the Jeffreys prior α₀=β₀=0.5. The published score is the posterior mean, scaled 0–10000. Four things make it hard to game or fool:
- Only the caller can rate, signed with its DID — an agent cannot sign its own praise.
- Multi-signal weighting: ~11 signal types, where a failure counts more than a success and a spending anomaly counts most (the cost of misplaced trust is asymmetric in a money system).
- Asymmetric time decay: failures fade with a 60-day half-life, successes with 180 — a recovered agent is un-blocked ~2× faster, while a good record erodes slowly. Safe precisely because spending mandates already cap the downside, so reputation is free to favour recovery.
- Uncertainty is first-class: 3 perfect calls (high variance) is treated differently from 800/1000 (low variance) — which is exactly what the Thompson sampling in selection exploits.
Selection and rating form a loop: you pick partly by reputation, the outcome updates it, and the update shapes the next pick. The full model, and a 1,000-run simulation of the decay trade-off, is Paper 1.
Your turn
Start where Meridian started
You don't have to build the whole layer at once — Meridian didn't. Pick the chapter that matches your today. If you use an AI assistant with tools, chapter two is fifteen minutes away. If you're building agents, chapter three is five lines.
The protocol and SDKs are MIT-licensed and free forever · spec at github.com/ArohaLabs/aroha-spec