Tutorial10 min read

Five Packages That Changed How I Build AI Agents

A working tour of the Aroha toolkit, in Python and TypeScript — from a five-line agent to multi-agent webs that can't exceed their authority. Written by the person who built it, bugs included.

The five packages: serve() the on-ramp, mcp-guard the seatbelt, credentials the authority, delegation the network, hub the directory

Every agent project I've worked on hits the same moment: the demo works. Which means the agent can now do things. Which means you suddenly, urgently care about what it's allowed to do — and you discover that “allowed” isn't a concept anywhere in your stack.

I've spent the last year building a toolkit around that moment. Everything below is MIT-licensed, works without an account, and — this was a rule I set early — has to be useful inside the first five minutes, alone, before any grand protocol vision kicks in. Here's the honest tour.

1. serve() — an agent in five lines

I resent frameworks that make me learn their cosmology before I can answer an HTTP request, so the on-ramp is just a function wrapper.

Python (pip install aroha):

from aroha import serve

@serve(name="echo")
async def handler(message, context):
    return {"echo": message}

handler.start(port=8000)

TypeScript (npm install @aroha-sdk/run — zero dependencies, deliberately):

import { serve } from "@aroha-sdk/run";

const agent = serve("echo", async ({ message }) => `Echo: ${message}`);
agent.start(8000);
The same five-line echo agent in Python and TypeScript, side by side

Both give you /v1/run with streaming, sessions, a health check, and a manifest. The TS one runs on Cloudflare Workers and Vercel Edge by exporting agent.fetch. Flip exposeMcp: true and the same agent is an MCP server Claude Desktop can call — which still feels slightly like cheating.

The point isn't the five lines. It's that this same function can later verify mandates and join delegation chains without a rewrite. The on-ramp and the protocol are the same road.

2. mcp-guard — the seatbelt

I wrote a whole post about this one, so the short version: it wraps any MCP server with block/gate/limit rules and an audit log, in one config change, no code. Everything ambiguous fails closed. I built it in a couple of days, mostly out of pieces the other packages already had — and it's quietly become the most-downloaded thing I've shipped, which tells you something about where the actual pain is.

3. Mandates — limits that math enforces

This is the heart of the toolkit, and the idea I'd defend in a bar argument: don't ask an agent to respect a budget. Hand it a signed token it cannot exceed, because everyone downstream verifies the signature, and the rules of the system say a delegated token can only ever get narrower.

import { issueIntentMandate, attenuateToPayment } from "@aroha-sdk/credentials";

// I grant my orchestrator $500
const intent = await issueIntentMandate(
  myDid, orchestratorDid,
  { spendLimitUsd: 500, allowedActions: ["book-flights"] },
  myPrivateKey, 3_600_000,
);

// It narrows to $300 for the flight agent — fine
await attenuateToPayment(intent, flightAgentDid, { spendLimitUsd: 300 }, orchKey);

// It tries $800 — throws.
// "child spendLimitUsd (800) exceeds parent (500)"

Python mirrors the same model with aroha.mandate issue_intent_mandate, attenuate_to_payment, verify_mandate.

Authority can only shrink: a human grants $500, the orchestrator narrows to $300, the flight agent spends at most $300. Widening to $800 throws; a tampered token fails signature verification.
A confession that might actually build more trust than the pitch: while writing the docs for this, I ran the example from my own README against the published package — and it was wrong. attenuateToPayment takes the whole signed envelope, not the bare mandate. The example in the docs now shows the output of actually running it, because apparently I can't be trusted to write examples from memory either. Nobody can. Run your examples.

4. Delegation — the web

The newest one, and the reason I finally believe the multi-agent story. Every orchestration framework can make agent A call agents B, C, and D. None of them answer: what stops C from doing something A never authorized?

import { serveDelegated, registryResolver } from "@aroha-sdk/delegation";

serveDelegated("orchestrator", {
  identity: { did: myDid, privateKey: myKey },
  trustAnchors: { [aliceDid]: alicePublicKeyB64 },
  resolvePublicKey: registryResolver(),
}, async (ctx) => {
  ctx.assertCapability("research");   // verified before your code even runs

  const search  = await ctx.delegate(searcherDid, ctx.message, { allowed: ["web-search"] });
  const summary = await ctx.delegate(summariserDid, search.message, { allowed: ["summarise"] });
  return summary.message;
}).start(8000);
A web of agents with receipts: mandates flow down and shrink, receipts flow back and nest, one correlationId end to end.

Each ctx.delegate() extends a signed chain where scope only shrinks and delegation depth counts down — a depth-0 mandate is a dead end, so the agents-spawning-agents runaway is structurally impossible rather than discouraged. Receipts nest all the way back to whoever started the chain, under one correlation ID. My favorite moment building it: the end-to-end demo — three real agents on localhost — caught a response-parsing bug my unit tests missed entirely. The demo now ships in the repo as the proof, warts fixed.

Two caveats, because I'd rather you hear them from me: the delegation runtime is TypeScript-first today (Python can speak the wire format — it's plain JSON — but the ergonomic runtime is on the roadmap, not in the box). And a chain of trustworthy signatures is only as interesting as the agents signing them, which brings me to —

5. The Hub — agents you can actually call

A registry you can query by capability, with live sandbox agents that need no account. This works right now, from your terminal:

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"}}}'

Full disclosure: the sandbox agents (flights, hotels, weather, search) are mock-data agents I run myself, clearly labeled as such. The registry's job right now is to make the discover-verify-call loop real end to end. Filling it with agents I didn't write is the entire game from here, and I know it.

If you only try one thing

Using an assistant with MCP tools? mcp-guard, tonight, zero code. Building an agent? serve(), then add mandate verification the day your agent grows teeth. Building a fleet? The delegation package is the quarter of engineering you were about to do yourself.

The spec is open (github.com/ArohaLabs/aroha-spec), and I read everything that lands in the repo issues. The one-line thesis, since every post needs one: agents should carry proof of what they're allowed to do — and your infrastructure should check it, because the model's good manners won't.