Agent versioning¶
Every agent is now backed by an immutable history of AgentVersion
records. Editing an agent doesn't overwrite a row — it adds a new
version and flips a deployment pointer. This page explains the data
model, the resolution rules, and why things are laid out this way.
Identity = (owner_id, name)¶
An agent's primary key is the identity pair (owner_id, name),
not the name alone. Two different owners can have an agent called
researcher; they're distinct identities with independent version
histories.
Pre-seeded YAML specs live in the system namespace (owner_id="system").
Anything a user creates or forks lives in that user's namespace
(owner_id=principal.id).
Addressing: bare, qualified, admin-override¶
Routes accept three forms of {name}:
| Form | Example | Resolution |
|---|---|---|
| Bare | /api/v1/agents/researcher |
(principal.id, researcher) → fall back to ("system", researcher). Never falls through to another owner's shared agent. |
| Qualified | /api/v1/agents/alice:researcher |
Direct (alice, researcher) lookup. Required to read someone else's shared agent. |
Admin ?owner= |
/api/v1/agents/researcher?owner=alice |
Admin-only alternative to qualified form. |
The "no-fall-through-to-shared" rule exists so bare lookups are
deterministic — if two owners share agents named researcher with
you, which one would win? Forcing qualified addressing for shared
access keeps resolution predictable.
Version lifecycle¶
Every AgentVersion has one of five statuses:
- draft — the new-version default when the admin-approval gate is on. Inert — runners never serve drafts.
- proposed — owner has submitted the draft for admin review.
- deployed — the single active version per identity.
GET /agents/{name}always returns the spec embedded in the currently-deployed version. - archived — previously deployed, superseded by a newer deploy.
- rejected — admin declined to approve the proposal.
Run-time resolution is always the deployed version¶
The LLM loop in AgentRunner reads spec.model, spec.system_prompt,
etc. directly from the deployed AgentSpec on every request. There is
no in-memory cache of old versions. Drafts are inert until deployed.
Sub-agent delegation resolves in the parent agent's namespace¶
When Alice's researcher delegates to analyst, the sub-agent
resolves as (alice, analyst) first and falls back to
("system", analyst). It does not use the caller's namespace —
sharing an agent means sharing its whole delegation graph. This is
what makes "Bob runs Alice's shared researcher" work without Bob
having to also own every sub-agent.
Fork = clone identity with lineage¶
POST /api/v1/agents/{name}/fork copies the deployed version of
the source identity into the caller's namespace and records a
forked_from pointer on the new v1:
Fork auto-qualification of sub-refs¶
If Alice's researcher delegates to bare analyst, and Alice also
owns (alice, analyst), then Bob's fork auto-rewrites the ref to
"alice:analyst" so the fork keeps resolving to Alice's analyst.
Without this rewrite, the fork would fall back to system's analyst
(if any) or fail.
This means a fresh fork is immediately runnable against the source
owner's graph. Bob can later fork analyst separately and update the
ref to "bob:analyst" or bare analyst as he prefers.
Admin-approval gate¶
governance.require_admin_approval_for_deploy (default false) flips
the create-and-deploy flow into a PR-style workflow:
- Off:
POST /versionsauto-deploys.PUT /agents/{name}is the same thing spelled differently. - On:
POST /versionscreatesdraft; must transition throughpropose→ adminapprove→deploy.PUTreturns 409 with a pointer to/versions.
Pre-seeded YAML specs always bypass the gate on first load — bootstrap must never deadlock.
Memory + conversation history are identity-scoped¶
Memory keys become agent:{owner_id}:{name}:u:{user_id} and actor IDs
become {owner_id}:{name}:u:{user_id}. Alice's forked researcher
has fully isolated memory from Alice's original — and from Bob's fork
— even though they all share a bare name. This is critical for fork
correctness: without owner-scoping, every fork would inherit (and mutate)
upstream memory.
Configuration¶
governance:
# Gate that forces POST /versions → /propose → admin /approve → /deploy.
# Pre-seeded YAML specs always bypass.
require_admin_approval_for_deploy: false
agents:
# Past this count, the oldest non-deployed / non-draft versions per
# identity are automatically archived.
max_versions_per_identity: 50
Prometheus metrics¶
New in the governance dashboard:
gateway_agent_versions_created_total{ns_kind, auto_deployed}— new version persistence.ns_kindissystemoruser;auto_deployedis a boolean.gateway_agent_forks_total{source_ns_kind}gateway_agent_version_approvals_total{outcome}—approved,rejected, ordeployed.
Label cardinality is intentionally bounded: we never emit raw
owner_id because it would explode per-user.
See also¶
- Team versioning — identical model for teams.
- Admin-approval workflow — how to run with the gate on in production.
- Lineage visualization — the
/ui/agents/{name}/lineageDAG view.