Enforced Agent Instance: What it is and How to Create One
Definition
An Enforced Agent Instance is a tenant- and platform-scoped passport derived from a Template Passport. It is the ID you enforce at runtime to gate risky actions (refunds, data export, PR merge), with tenant-specific limits, regions, assurance, webhooks, and suspend.
Why enforce an Instance (not a Template)?
- Per-tenant control: set different limits/regions for each customer/workspace/store.
- Precise suspend: suspend one tenant’s bot without impacting others.
- Clear audit: who did what, under which limits, in which tenant.
- Scalable ops: webhooks per tenant, blast radius isolation, simpler incident response.
Prerequisites
- A Template Passport exists (builder created it;
kind:"template"). - You know the tenant context:
platform_id(e.g.,"gorgias","zendesk","shopify","github")controller_id/controller_type(tenant org/user in APort)tenant_ref(platform-side tenant identifier, e.g.,store_987)
API: Create an Instance (install flow)
POST /api/passports/:template_id/instances
Content-Type: application/json
css
Copy code
Body`json
{
"platform_id": "gorgias",
"controller_id": "ap_org_1234567890abcdef",
"controller_type": "org",
"tenant_ref": "store_987",
"overrides": {
"limits": {
"refund_amount_max_per_tx": 50,
"refund_amount_daily_cap": 200
},
"regions": ["US","CA"],
"status": "active",
"contact": "[email protected]",
"links": {
"homepage": "https://acme.example.com/bots/happyrefunds"
}
}
}
201 Response
json
Copy code
{
"agent_id": "ap_22222222222222222222222222222222",
"kind": "instance",
"parent_agent_id": "ap_11111111111111111111111111111111",
"platform_id": "gorgias",
"controller_id": "ap_org_1234567890abcdef",
"controller_type": "org",
"tenant_ref": "store_987",
"name": "HappyRefunds Bot",
"role": "CX refunds assistant",
"capabilities": ["finance.payment.refund"],
"limits": { "refund_amount_max_per_tx": 50, "refund_amount_daily_cap": 200 },
"regions": ["US","CA"],
"status": "active",
"assurance_level": "L2",
"attestations": [],
"created_at": "2025-09-19T12:34:56Z",
"updated_at": "2025-09-19T12:34:56Z",
"webhook": null
}
Notes
Instances inherit core identity (name/role/capabilities) from the template at creation; overrides apply only to operational fields.
On template revocation, platform should expect auto-suspend cascade to instances.
Enforcing at runtime
Option A - Call /verify directly (platform middleware)
pgsql
Copy code
POST /api/verify/policy/finance.payment.refund.v1
Content-Type: application/json
Body
json
Copy code
{
"agent_id": "ap_22222222222222222222222222222222",
"context": {
"amount_usd": 37.50,
"sku": "SKU-123",
"region": "US",
"order_age_days": 12
}
}
Allow example
json
Copy code
{
"allow": true,
"pack_id": "finance.payment.refund.v1",
"passport": {
"agent_id": "ap_22222222222222222222222222222222",
"status": "active",
"assurance_level": "L2"
}
}
Deny example
json
Copy code
{
"allow": false,
"pack_id": "finance.payment.refund.v1",
"violations": ["limit.refund_amount_max_per_tx"],
"reason": "Requested $120 exceeds $50 per-transaction cap"
}
Option B - GitHub Action (PR/merge packs)
Use the APort Action (or a curl step) as a required check to block merges that violate repo.pr.create.v1 or repo.merge.v1.
Webhooks (recommended)
Register at org level (all instances) or per instance:
status.changed → { agent_id, old_status, new_status, ts }
passport.updated → { agent_id, changed_fields[], ts }
assurance.updated → { agent_id, new_level, attestations[], ts }
Signature: X-Aport-Signature: sha256=HEX(hmac_secret, body)
Use webhooks to invalidate caches, disable UI buttons, or fire incident playbooks.
Suspend & lifecycle
Suspend this tenant only
swift
Copy code
POST /api/passports/ap_22222222222222222222222222222222/status
{ "status": "suspended", "reason": "chargeback spike" }
Global revoke (template)
If the template is set to "revoked", all child instances auto-suspend with reason "parent_revoked".
Assurance upgrades
Encourage tenants to reach L2 (GitHub org) or L3 (domain) for higher limits.
Minimal UI flow (platform-side install)
Select agent (Template card) → click Install.
Install wizard:
Set platform_id, tenant_ref, controller_*.
Choose pack + limits/regions (pre-filled from template).
Create Instance (API call).
Copy Instance ID and place into:
Your server middleware (refund/export routes), or
A GitHub secret used by the APort Action.
(Optional) Add webhook target for instant updates.
Diagram (Mermaid)
mermaid
Copy code
sequenceDiagram
participant Builder as Builder (Template)
participant Platform as Platform (Install Wizard)
participant APort as APort API
participant Tenant as Tenant App/API
Builder->>APort: Create Template Passport (ap_11111111111111111111111111111111)
Platform->>APort: POST /passports/ap_11111111111111111111111111111111/instances { controller, limits, ... }
APort-->>Platform: 201 Instance Passport (ap_22222222222222222222222222222222)
Tenant->>APort: POST /verify/policy/finance.payment.refund.v1 {agent_id: ap_22222222222222222222222222222222, context}
APort-->>Tenant: {allow/deny, reason, passport summary}
Platform-->>Tenant: Proceed/Block refund
Platform->>APort: (optional) Register webhook target
APort-->>Platform: status.changed / assurance.updated events
Operational guidance
Cache /verify responses at the edge for up to 60s; rely on webhooks to react faster on suspend.
Keep instance records small (copy only display identity + operational policy).
Use prefix indexes for listing instances by template or tenant:
idx:parent:
idx:tenant:
Test checklist
Create Instance and verify through the low-latency hosted path without adding billing or entitlement lookups to the verifier.
Deny reasons are actionable (limit.refund_amount_max_per_tx, assurance.min_not_met, etc.).
Suspend flips enforcement within your cache TTL (≤ 60s); webhook received.
Template revocation cascades suspend to all Instances with correct reason.
makefile
Copy code