· Security
I Built the Wrong AI Agent Passport System. 3 Things Chimoney Fixed.
One error message at 11:23 PM showed me that independent agent passports do not scale. The template and instance pattern from Chimoney's codebase fixed it.
At 11:23 PM, my coding agent returned an error I didn't expect while building AI agent passport management for AppRoom:
{
"error": "Either org_as_owner or pending_owner is required. Set org_as_owner: true to make org the owner, or provide pending_owner for user claim flow."
}
I had been building AppRoom's passport management system from scratch. My assumption: each AI agent gets its own passport, created fresh, storing its own KYC level. Simple. Independent. Wrong.
That error sent me to Chimoney's codebase. What I found there changed how I think about agent identity entirely.
The error that exposed my flawed mental model
The immediate error was straightforward to fix:
// Wrong: missing required field
async createPassport(passportData) {
return this.aportClient.createPassport(passportData)
}
// Right: org_as_owner tells APort who owns this passport
async createPassport(passportData) {
return this.aportClient.createPassport({
...passportData,
org_as_owner: true // AppRoom org is the issuer and owner
})
}
But that fix raised a harder question: if AppRoom issues passports on behalf of users, how should the relationship between a user's identity and their AI agents' identities actually be structured?
My original design:
- User creates account, create passport A
- User creates app, create passport B (independent, same KYC level copy-pasted)
- User gets flagged for fraud, suspend passport A, and also B, and also C, D, E manually
This is what my coding agent diagnosed as "manual suspension hell."
The architecture at a glance
Here is the relationship between the two tiers:
AppRoom Org (Issuer)
|
├── User A's Builder Passport (template) ← one per user
│ assurance: L4KYC
│ status: active
│ |
│ ├── App #1 Passport (instance) ← inherits L4KYC
│ ├── App #2 Passport (instance) ← inherits L4KYC
│ └── App #3 Passport (instance) ← inherits L4KYC
│
└── User B's Builder Passport (template)
assurance: L0
status: SUSPENDED ← revoke one, all apps are revoked
|
├── App #4 Passport (instance) ← suspended
└── App #5 Passport (instance) ← suspended
Suspend User B's template: both apps are revoked in one operation, no iteration required.
Why this architecture solves everything my original design didn't
Here is how the two approaches compare across the operations that matter in production:
| Operation | Independent Passports | Instance Pattern |
|---|---|---|
| Suspend fraudulent user | Suspend every passport individually | Suspend template once |
| User completes KYC upgrade | Update every passport individually | Update template once |
| User creates 10th app | Create 10th independent passport | Create 10th instance |
| Audit trail per agent | Separate record per agent | Instance linked to template audit chain |
| Assurance level drift risk | High (manual sync required) | None (automatic inheritance) |
Fraud and suspension
Old way: User commits fraud. Find every passport they own. Suspend each one manually. Hope you found them all.
Instance pattern: Suspend the user's template passport. Every agent passport that is an instance of that template is automatically revoked. One operation. Complete coverage.
This matters at scale. An active user on AppRoom might have 10, 20, 50 agents. You cannot reliably manage fraud remediation by iterating over individual passports.
KYC upgrades
Old way: User completes verification and upgrades from L0 to L4KYC. You have to find every passport associated with that user and update each one's assurance level. Miss one and that agent is operating with stale trust data.
Instance pattern: Update the user's template passport with the new assurance level. All instances inherit it automatically. One write operation. Zero drift.
This is how financial infrastructure works at Chimoney scale. You update the source of truth; dependent records reflect it.
Scalability
Old way: 5 agents per user = 5 passports to manage. 50 agents = 50 passports. API calls multiply linearly. Database records multiply. Error surface grows.
Instance pattern: 1 template + N instances. N can be unlimited. The template is the control plane. Instances are cheap to create because they inherit their properties instead of duplicating them.
What this is not
This pattern is not appropriate for every use case. If you need agents to have genuinely independent identity roots (separate orgs, separate trust chains, separate audit logs), then instances may be the wrong abstraction. Independent passports give you full isolation at the cost of the management burden described above.
For a marketplace model, where the platform is the org-level issuer and users are the identity-holders, the template/instance pattern matches how real-world credential hierarchies work. An employee's access card inherits from the company's security policy. A contractor's badge inherits from the client's clearance level. Same principle.
What changed in our codebase
If you're new to AI agent authorization and want the broader context, I wrote about pre-action authorization in a post called "I Logged 4,519 AI Agent Tool Calls." This one is about the identity layer underneath those controls.
Three files changed:
functions/lib/services/aport.ts: addedorg_as_owner: truetocreatePassport(), addedcreateInstance(templateId, data)methodfunctions/lib/services/agents.ts: changedcreateAgentPassport()to callcreateInstance()instead ofcreatePassport()collections/agents/index.js: addedbuilderPassportIdto the agent schema as a reference to the user's template
One error message. Three file changes. A fundamentally better architecture.