Skip to content
Agentic Control Plane

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. Humans setting up ACP for the first time should start at Getting started with ACP, the canonical start page.

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:

  1. The primary path: a parent agent mints a scope-narrowed child API key via POST /api/v1/keys/child before 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 a gsk_ key that’s already attributed to a human).
  2. The tire-kicker path: POST /v1/sandbox-tenants for AI agents that have no existing tenant yet — typically third-party developers evaluating ACP. Six-curl onboarding, no human in the loop.
  3. 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 expiresAtmin(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; check details. Note: originSub cannot be set from request body (ADCS §5 origin invariant); it’s always derived from the parent.
  • 403 profile_not_delegatable — the agent profile must have delegatable: true.
  • 404 profile_not_found — typo or wrong tenant.
  • 409 delegation_cycle — the requested profileId already 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’s remainingBudgetCents is 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, hand your human the nextSteps.claimPageUrl from the create response — one link, mid-conversation. They sign in, click Claim, and the sandbox becomes their permanent workspace: expiry removed, restrictions lifted, your sandbox key revoked, and a fresh owner key minted for them to hold (or hand back to you). The claim token rides in the URL fragment, so it never appears in any server log. (The raw API also works: a signed-in human POSTs /v1/sandbox-tenants/:id/claim with the claimToken.) 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/... returns 403 sandbox_tenants_cannot_configure_connectors)
  • LLM provider keys (sandbox is for governance, not LLM proxying)
  • admin.audit.read and admin.*.write scopes (sandbox keys carry only sandbox.govern and sandbox.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/effective as a unit-test assertion: “given uid X and tool Y, the effective decision should be block.”
  • 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

OpenAPI

Coming soon. For now, schemas live as zod definitions in apps/tenant-gateway/src/admin/_shared/schemas.ts (search for ChildKeyMintSchema, SandboxCreateSchema, etc.).