Agents building agents — ACP
Audience: AI agents. This doc is plain markdown, dense, copy-pasteable. No images, no marketing prose. If you are a human reading this, that’s fine — but it’s optimized for an agent ingesting it as context and acting on it programmatically.
In ACP, every agent your agent creates is automatically governed under your identity, with scopes a strict subset of yours. There’s nothing to configure per-spawn. The framework does it for you, deterministically, via the delegation chain primitive.
This doc covers, in priority order:
- The primary path: a parent agent mints a scope-narrowed child API key via
POST /api/v1/keys/childbefore spawning a subagent. The chain is preserved in audit. This is what to use when your agent is running inside an existing tenant (you have agsk_key that’s already attributed to a human). - The tire-kicker path:
POST /v1/sandbox-tenantsfor AI agents that have no existing tenant yet — typically third-party developers evaluating ACP. Six-curl onboarding, no human in the loop. - The full config-as-code surface — everything the dashboard configures is also a REST endpoint.
The delegation primitive (POST /api/v1/keys/child)
The keystone. When parent agent A (already attributed to human H) creates child agent B, the parent calls one endpoint to mint B’s API key. The gateway:
- Sets
originSub = H(preserved through the whole chain — this is the audit trail back to the human) - Computes
effectiveScopes = intersect(A.scopes, profile.scopes, request.scopes)— child scopes are NEVER broader than parent - Strips
bench.impersonate(meta-scope; never propagates) - Decrements parent’s remaining budget by the child’s allocation (atomic transaction — fan-out can’t escape the parent’s budget cap)
- Sets
expiresAt≤min(parent.expiresAt, request, 24h)(child can never outlive parent) - Persists chain metadata so audit logs trace through the whole chain back to H
- Rejects cycles across the entire ancestor chain (not just the immediate parent — ADCS §8)
curl -X POST https://api.agenticcontrolplane.com/api/v1/keys/child \
-H "Authorization: Bearer $PARENT_GSK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profileId": "lead-research-bot",
"scopes": ["github.repos.read"],
"ttlSeconds": 600,
"maxBudgetCents": 50,
"reason": "summarizing inbound lead xyz"
}'
Expected 201:
{
"apiKey": "gsk_acme_<32hex>",
"keyId": "...",
"expiresAt": "2026-04-30T17:00:00.000Z",
"effectiveScopes": ["github.repos.read"],
"effectiveTools": [...],
"remainingBudgetCents": 50,
"chain": {
"originSub": "human-1",
"depth": 2,
"agentProfileId": "lead-research-bot",
"agentRunId": "...",
"parentKeyId": "..."
}
}
The apiKey is shown ONCE. The subagent uses it as its bearer token. Every governed tool call the subagent makes carries the chain forward in audit logs — no extra code needed on your side.
Errors:
400 validation_failed— body shape wrong; checkdetails. Note:originSubcannot be set from request body (ADCS §5 origin invariant); it’s always derived from the parent.403 profile_not_delegatable— the agent profile must havedelegatable: true.404 profile_not_found— typo or wrong tenant.409 delegation_cycle— the requestedprofileIdalready appears somewhere in the parent’s chain. Pick a different profile or restructure your delegation.409 delegation_depth_exceeded— the chain is already at the cap (5).409 parent_budget_insufficient— the parent’sremainingBudgetCentsis below what this child would allocate.410 parent_key_already_expired— re-mint your parent key first.429 child_mint_rate_limit— 30 mints/hour per parent key.
SDK helpers (do this, not raw curl)
In production, use the SDK wrappers — they handle context binding so every @governed tool call inside a with child_context(...) block is reported under the child token automatically:
from acp_governance import spawn_subagent, child_context
child = spawn_subagent(
profile_id="lead-research-bot",
scopes=["github.repos.read"],
ttl_seconds=600,
)
with child_context(child, agent_name="lead-research-bot"):
run_my_subagent() # every governed tool call inside is on the child key
For Claude Code: install the acp-spawn skill (in claude-code-acp-plugin). When the user (or your agent) decides to delegate work to a subagent, the skill mints the child key and runs the subagent with that token — same delegation chain, same audit, no manual curl.
Sandbox path (use only if you have no tenant yet)
The unauthenticated POST /v1/sandbox-tenants flow is for agents evaluating ACP — typically third-party developers running a benchmark or starter. After 24h the sandbox is auto-deleted unless a human claims it. Use the delegation chain primitive above for any agent-creates-agent flow within an existing tenant.
# Get an ephemeral tenant + scoped key with no signup.
curl -X POST https://api.agenticcontrolplane.com/v1/sandbox-tenants \
-H 'content-type: application/json' \
-d '{"intent":"crewai-eval"}'
Returns { tenantId, workspaceSlug, apiKey, claimToken, expiresAt }. The apiKey is a sandbox-scoped gsk_ key (no bench.impersonate, no real LLM keys, no real OAuth or custom HTTP connectors — the sandbox is a governance demo, not an LLM proxy).
To run a governed tool call:
SLUG=sb-xyz12345
KEY=gsk_sb-xyz12345_…
curl -X POST "https://api.agenticcontrolplane.com/$SLUG/govern/tool-use" \
-H "authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{
"tool_name": "Read",
"tool_input": {"file_path": "/etc/hosts"},
"session_id": "sess-1",
"agent_name": "my-eval-agent"
}'
Expected: {"decision":"allow","reason":"...","tier":"interactive"}.
To convert the sandbox to a real workspace, a human signs in to https://cloud.agenticcontrolplane.com and POSTs /v1/sandbox-tenants/:id/claim with the claimToken. The original sandbox key is revoked at claim time; the human gets a fresh owner key. From there, every agent the human’s agents create flows through POST /api/v1/keys/child (the delegation primitive above).
What’s blocked in sandbox:
- Real OAuth or custom HTTP connectors (
/admin/connectors/...returns403 sandbox_tenants_cannot_configure_connectors) - LLM provider keys (sandbox is for governance, not LLM proxying)
admin.audit.readandadmin.*.writescopes (sandbox keys carry onlysandbox.governandsandbox.read)- 24h TTL
Config-as-code surface
Once you have any gsk_ key (root or chain), every dashboard configuration is a REST endpoint. See the config-as-code reference for the full reference. Short version:
| Method | Path | Purpose |
|---|---|---|
PATCH |
/api/v1/agents/:id |
Update agent profile (field-allowlisted, model-allowlisted) |
POST |
/api/v1/keys/child |
Mint child key (delegation chain) |
GET\|POST\|PATCH\|DELETE |
/{slug}/admin/connectors[/...] |
OAuth + custom HTTP connector CRUD |
GET\|POST\|PATCH\|DELETE |
/{slug}/admin/pii-patterns[/...] |
Custom PII regex patterns (with ReDoS guard) |
GET |
/{slug}/admin/policies/effective |
Resolve effective policy for a uid + agent context |
Common patterns for agents
- Spawn-and-govern: parent calls
/api/v1/keys/child, runs the subagent with the child token, audit logs trace the full chain back to the human. Use the SDK helpers — never construct child keys manually in production code. - CI policy verification: keep agent profiles, PII patterns, and policy configs in version control. Apply via the REST endpoints. Use
GET /admin/policies/effectiveas a unit-test assertion: “given uid X and tool Y, the effective decision should beblock.” - Provision-and-evaluate: create a sandbox, run your harness against
/govern/tool-use, look at the audit logs. Useful for first-time evaluators with no existing tenant.
Source and feedback
- Source: github.com/davidcrowe/gatewaystack-connect
- Open benchmark: github.com/davidcrowe/agentgovbench — 48 governance scenarios, mapped to NIST AI RMF
- ADCS spec: github.com/agentic-control-plane/delegation-chain-spec
- SDK: github.com/davidcrowe/acp-governance-sdks — Python
acp-governancepackage withspawn_subagent+child_context - Issues: file at the gateway repo above
OpenAPI
Coming soon. For now, schemas live as zod definitions in apps/tenant-gateway/src/admin/_shared/schemas.ts (search for ChildKeyMintSchema, SandboxCreateSchema, etc.).