Spawn a sub-agent in CrewAI, LangGraph, AutoGen, or a Claude sub-agent
setup and check what it actually holds: your credentials. The parent's
keys, at full scope, forever. The throwaway agent you created to summarize
three PDFs can call every tool your orchestrator can, and the only
"revocation" is rotating keys everywhere at once.
We accept this because handing a child less than everything has been
genuinely hard: OAuth scopes need an authorization server someone runs;
role systems need an admin; API keys don't subdivide. So the ecosystem
quietly standardized on "copy the parent's environment" and moved on.
Here's a different shape, as an MCP server you run locally:
npx -y @grantor/mcp serve
# or wire it into Claude Code:
claude mcp add grantor-mcp -- npx -y @grantor/mcp serve
That gives any MCP-speaking framework five tools. The whole model fits in
one transcript:
grant {tools: ["search","fetch"], max_uses: 20, ttl_secs: 3600}
β {child_id: "β¦", sub: "β¦"} # a bounded child identity
check {child_id, tool: "search"}
β {allow: true, remaining_uses: 19} # gate EVERY action on this
check {child_id, tool: "write"}
β {allow: false, code: "CapabilityDenied"} # not granted β denied
delegate {parent: child_id, tools: ["search"], max_uses: 5}
β {child_id: "β¦"} # a narrower grand-child
revoke {child_id}
β revoked # authority withdrawn
The interesting properties are in what you can't do:
-
A child can never widen its slice. Asking
delegatefor a tool the parent doesn't hold is refused before anything is signed. Asking for more uses or a longer expiry silently clamps to the parent's bound. This isn't a policy file the framework consults β the delegation chain is cryptographically signed link by link, and verification re-checks the narrowing math on everycheck. -
Budgets actually run out.
max_uses: 20means the 21stcheckis denied withUsesExhausted, not logged-and-allowed. -
Revocation is real. In your own tenant,
revokebumps a revocation epoch on a public smart contract; every capability in that cohort fails its nextcheckno matter which process holds it.
And the part that makes this different from every "policy engine" you've
seen: there is no server. No authorization service, no policy backend,
no vendor API in the hot path. Authority anchors to a public registry
contract on Base; verification is a local computation plus one eth_call
that any RPC provider can serve. The broker runs on your machine, next to
the framework it guards, and holds the child keys so your agents never see
key material at all.
The zero-setup run above works because the package ships pointed at a
live shared sandbox tenant on the production registry β real chain, real
verification, publish-on-purpose demo key that controls nothing outside
the sandbox. Honest limits: it's an unaudited developer preview, the
sandbox broker self-issues its anti-replay challenge (it's holder and
verifier in one process), and use-metering is local to the broker.
Production is one contract call away (USDC on Base, no signup β your
agent can even read the machine-readable onboarding manifest and do it
itself). Docs: https://chaingrantor.com/docs/guide/mcp-broker
MCP standardized what agents can
call. A2A standardized how they talk. Nobody standardized what they're
allowed to do β that's the layer this fills.
Top comments (2)
Good shape. The two invariants Iβd test hardest are atomic consumption and conservation under delegation.
checkcannot be a read followed by a separate decrement, or concurrent calls can all observe the same remaining use. And a parent that delegates five uses should either lose/reserve those five or share one aggregate ceilingβotherwise a delegation tree mints authority. Tool names alone are also too coarse for high-impact actions: bind the capability to normalized resource scope, argument constraints, environment, expiry, and operation ID. Then test concurrent last-use races, parent revocation during execution, replay, stale children, and crashes between authorization and side effect.You were right on both, and I fixed them today.
Atomic consumption: there was indeed a window where two concurrent
last-use calls could slip through together. A use is now claimed at the
gate itself, and a denied call gives it back β I added a race test and
verified it fails against the old behavior. Your crash scenario resolves
the safe way: worst case a use is lost, never spent twice.
Conservation: Delegating used to cap each child but not draw
down the parent, so a tree could quietly multiply its budget. Delegation
now transfers uses from the parent's pool β an empty parent can't delegate,
and a test pins that a tree can never hold more than its root was given.
One honest boundary: authority handed to an external holder is bounded by
its signed caveat only; there's no local meter to conserve there.
Finer-grained constraints (arguments, operations, not just tool names):
not there yet β that's roadmap, and I'd rather say so than pretend
otherwise. Cross-broker metering stays deliberately out of scope: one
broker per state file, now stated plainly in the README.
Exactly the review I hoped these posts would attract. Thank you.