Opening the Platform: How We Built Granular Third-Party Agent Onboarding for a Large-Scale Agent OS
AitherOS runs scores of services, 29 internal agents, and exposes 300+ tools through the Model Context Protocol. Until now, all of that was for our agents. The question we kept getting: can my agent use your tools?
The honest answer was: sort of, but we'd have to hand you an admin key and hope for the best. The MCP Gateway had tier-based filtering (free/pro/enterprise), but it was coarse — module-level blocking, no per-agent identity, no quota tracking, and the tier was hardcoded to "free" inside MCP session handlers because the ASGI scope wasn't propagated into the MCP SDK.
This post covers how we built a complete third-party agent platform in one pass: registration, verification, tiered roles, cryptographically-signed capability tokens, per-agent tool manifests, per-agent rate limiting, daily quota enforcement, and a self-service developer portal — all wired through the existing security stack without breaking anything.
The Design Constraint: Four Security Layers, One Request Path
AitherOS already had strong security primitives, but they were designed for internal agents:
- Role-Based Access Control — Users, roles, groups, hierarchical permissions. Had a public user role but nothing in between.
- Capability Engine — Cryptographically signed tokens per agent. Default-deny. But only profiled 16 internal agents.
- MCP Gateway — Tier-based tool allow lists. But tier was hardcoded; no per-agent scoping.
- Caller Context — Boolean permission flags for agentic capabilities, forge access, and more. PUBLIC callers got all-false.
The challenge: thread a third-party agent's identity through all four layers on every request, from the HTTP boundary down to individual tool execution.
The Architecture
Third-Party Agent
│ Bearer token
▼
┌──── Auth Middleware ─────────────┐
│ Prefix routing: │
│ Billing keys → Billing │
│ External keys → Agent Registry│
│ Other → Identity │
│ Sets: agent context │
└──────────────┬───────────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
┌───────┐ ┌──────┐ ┌─────────────┐
│ RBAC │ │Rate │ │ Capability │
│ │ │Limit │ │ Validation │
│role │ │per │ │ tool-level │
│per │ │agent │ │ checks │
│tier │ │tier │ │ + max_tier │
└───┬───┘ └──┬──┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌──── Tool Manifest (4 layers) ────┐
│ L0: Internal module blacklist │
│ L1: external_safe metadata │
│ L2: Custom deny/allow per agent │
│ L3: Capability engine check │
│ L4: Tier-based fallback │
└──────────────┬───────────────────┘
│
▼
┌──── Usage Tracking ─────────────┐
│ Daily counters per agent │
│ Quota enforcement │
│ Historical flush to disk │
└──────────────────────────────────┘
Phase 1: Tiered RBAC Roles
The first thing a third-party agent needs is an identity. We added three graduated roles to the RBAC system:
Explorer (Free tier) — 6 permissions covering basic LLM access, search, code graph reads, and memory reads. Enough to query the system but not modify anything.
Builder (Pro tier) — 13 permissions. Everything in Explorer, plus memory writes, reasoning model access, and orchestrator reads. Enough to build useful integrations.
Enterprise tier — 17 permissions. Full external access to orchestration, canvas, and code generation capabilities. But hard-denied from secrets, security services, identity management, and system internals.
The key design decision: no role inherits from internal roles. External agent roles are completely standalone permission sets. An enterprise-tier external agent can never accidentally inherit admin privileges through a group chain. The security boundary is structural, not policy-based.
Phase 2: Cryptographic Capability Tokens with MCP Tool Granularity
RBAC controls service-level access. But we need tool-level control — which of the 300+ MCP tools can this specific agent call?
We extended the capability system with an external agents section that uses a naming convention mapping each tool to an explicit grant or denial:
Explorer gets basic tool access — code search, memory queries, read-only file and git operations — capped at 100 LLM calls per day on the fastest model tier. Shell and code evaluation are explicitly denied.
Builder adds agent delegation, subagent dispatch (50/day), swarm coding (10/day), web search, and file writes. Shell access remains denied.
Enterprise gets wildcard access to all external-safe tools, using the reasoning model tier. Hard-denied from secrets management, identity management, and shell access — permanently, regardless of other grants.
The denial prefix system is new — it creates an explicit security boundary that the capability engine respects even if a wildcard grant would otherwise match. Enterprise agents get access to all tools, but shell access is cryptographically denied.
Constraint annotations like maximum tier and daily limits are carried on the cryptographically-signed token and enforced at check time. We didn't need to add any new enforcement code.
Phase 3: Agent Registration
Registration creates a chain of linked resources:
- Generate a unique agent ID
- Generate an API key (shown once at registration)
- Create an RBAC user with the appropriate external role for the tier
- Create a cryptographically-signed capability token from the tier profile
- If a URL is provided, set status to pending verification
- Persist to the agent registry
The registry tracks everything: owner tenant, tier, status, trust score, custom tool allow/deny lists, quota overrides, and verification state.
Verification: Proving You Own Your Agent
If an agent has a URL (A2A endpoint), we don't just trust it. Two verification paths:
Token-based: We generate a verification_token at registration. The developer submits it back to prove they initiated the request.
URL-based: We fetch /.well-known/aither-verify.json from the agent's registered URL and check that the token matches. This proves URL ownership — the developer must be able to deploy code to that endpoint.
The verification uses constant-time string comparison to prevent timing attacks. The token expires after 24 hours.
Phase 4: Per-Agent Tool Manifests
The hardest engineering problem: how does the MCP Gateway know which tools to show each agent?
Previously, the tier was hardcoded to "free" and all agents saw the same tools. The new approach threads agent identity through request context — when the auth middleware resolves an external agent key, it sets the agent's identity and tier into the request context. The MCP handler reads this context and filters tools accordingly.
The tool manifest system applies a 4-layer filter on every tool:
| Layer | Check | Effect |
|---|---|---|
| 0 | Internal module blocklist | Blocks entire module categories (training, infrastructure, chaos) |
| 1 | External safety metadata | Blocks tools not explicitly marked safe for external use |
| 2 | Agent custom deny/allowlist | Per-agent overrides from admin |
| 3 | Capability token check | Cryptographically-signed capability token validation |
| 4 | Tier-based defaults | Fallback to the existing free/pro/enterprise allow lists |
If an agent has a custom allowlist, only those tools are visible — period. If they have a denylist, those tools are blocked regardless of tier. The capability engine is the cryptographic layer — even if all other layers pass, an unsigned or revoked token means denial.
We also annotated 50+ MCP tools with metadata — each tagged with a pillar (context, orchestration, creation, etc.), a category, and an explicit external_safe flag.
This powers the tool manifest endpoint — agents can query which tools they have access to, organized by pillar. A Builder-tier agent might see 28 tools across context, orchestration, and creation pillars.
Phase 5: Per-Agent Rate Limiting and Quotas
The existing rate limiter tracked per-service and per-endpoint buckets. External agents need per-agent buckets, driven by tier:
| Resource | Explorer | Builder | Enterprise |
|---|---|---|---|
| Requests/min | 30 | 120 | 600 |
| Burst size | 10 | 30 | 100 |
| LLM requests/min | 5 | 20 | 100 |
| Forge requests/min | 0 (blocked) | 5 | 30 |
On top of rate limiting, daily quota enforcement tracks cumulative usage:
| Resource | Explorer | Builder | Enterprise |
|---|---|---|---|
| LLM calls/day | 100 | 500 | 5,000 |
| MCP tool calls/day | 500 | 5,000 | 50,000 |
| Forge calls/day | 0 | 50 | 500 |
| Tokens/day | 10K | 100K | 1M |
Quotas are checked before every tool call. When exhausted, the agent gets a clear message:
{"text": "Quota exceeded: LLM call quota exhausted (100/day). Resets at UTC midnight."}
Usage data flushes to disk every 60 seconds per agent, so historical usage is queryable through the API.
Phase 6: The Developer Portal
The full self-service API provides 20 endpoints covering the complete agent lifecycle:
Registration — provide name, description, tier, optional A2A URL, and optional tool allowlist. Returns an agent ID and API key (shown once).
Verification — submit a verification token, or trigger URL-based verification that checks /.well-known/aither-verify.json at the agent's registered endpoint.
Introspection — query the full tool manifest organized by pillar, or retrieve the agent's capability profile.
Usage — view today's counters and quota status, or pull historical daily breakdowns.
Lifecycle — admin operations for upgrade, suspend, reactivate, and deactivate. Suspension immediately revokes the capability token — no more tool calls until reactivation.
Admin tooling — customize per-agent access at the individual tool level with allowlist and denylist overrides.
The Security Boundary: What External Agents Can Never Do
Regardless of tier, external agents are cryptographically denied:
- Shell access — no arbitrary command execution or code evaluation
- Secret access — no access to API keys, database passwords, or encryption keys
- Security services — no access to threat detection, intrusion monitoring, or chaos testing
- Identity management — no ability to create, modify, or delete users or roles
- Training infrastructure — no access to model training or learning systems
- Internal automation — no access to deployment, automation, or RBAC management tools
These denials exist at multiple layers: RBAC denials, capability engine denial prefixes, internal module blocklists, and external safety metadata flags. An attacker would need to defeat all four independently.
Testing: 25 Tests, All Passing
The test suite covers every layer:
- Registry: Registration, URL verification, wrong-token rejection, API key lookup, suspend/reactivate, tier upgrade, persistence across restarts, per-tenant listing, custom tool lists
- Usage: Recording, quota enforcement
- Tool Manifest: Metadata existence, pillar/category listing, denylist blocking, allowlist restriction
- RBAC: Role existence, permission scoping (explorer limited, enterprise broad), group existence
- Capabilities: Profile validation (explorer has basic tools, enterprise has wildcard, all deny shell)
- Rate Limits: Config validation (per-tier limits exist, explorer gets 0 forge)
What's Next
This is the foundation. The next steps:
- AitherVeil marketplace page — discover, compare, and onboard third-party agents from the web dashboard
- Trust scoring — reputation based on usage patterns, error rates, and violation history; agents that misbehave get auto-suspended
- OAuth registration — GitHub/Google sign-in for the developer portal instead of requiring an existing Identity token
- Capability escalation workflow — agents request higher-tier access, admin approves through AitherVeil
- A2A federation scoping — tenant-scoped agent discovery so agents only see peers in their tenant
The goal: make AitherOS the platform where any agent can plug in, get exactly the capabilities it needs, and have its access controlled down to the individual tool level — without trusting it more than necessary.
The entire third-party agent platform — registry, RBAC roles, capability profiles, tool manifests, usage tracking, rate limiting, developer portal API, and 25 tests — was built and shipped in a single session. Every component integrates with the existing security stack (role-based access control, capability engine, caller context, MCP Gateway auth) rather than building parallel systems. That's the advantage of having security primitives already in place — you extend them, you don't replace them.