MastertheMesh
agentregistry · arctl · field guide
Field guide

Pushing agents to AgentRegistry, tags, versioning, pipelines, integrity

TO
Tom O'Rourke
EMEA Field CTO · Solo.io

A short guide to publishing agents and MCP servers into AgentRegistry from a pipeline. Three things to land: the metadata.tag field and when to set it, a two-track versioning convention so promotion is a one-line operation, and the integrity pattern that keeps the path from build to runtime tamper-evident. Aimed at platform teams standing up their first AgentRegistry-backed release flow and the field SEs supporting them.

AgentRegistry arctl metadata.tag semver OIDC image digest cosign
Under review Draft. This page is undergoing technical review with the AgentRegistry product team. Treat the recommendations here as a working position, not a settled one. Field details (CLI flags, manifest shapes, the approval workflow) may change once review feedback lands.

Why bother with versions at all

An agent isn't just code. The behaviour you ship is the combination of the model name, the system prompt, the skills you bundled in, the MCP servers you wired up, and the container image those things run inside. Any one of them can change underneath you and the agent starts answering differently. Without versioning, you have no way to say "the build that was running yesterday at 3pm" or "the build the finance team certified last quarter".

Concretely, versions are what let you:

That's the why. The rest of this page is the how: the metadata.tag field, a convention for what to put in it, and the pieces around it (auth from a pipeline, approval workflow, image digest pinning, deploy-time verification) that make the whole thing tamper-evident.

What the metadata.tag field actually does

Quick orientation first. You publish things into AgentRegistry by writing YAML manifests and applying them with the arctl CLI (or posting them to the registry's REST API). Each manifest declares an artifact (an Agent, an MCPServer, a Skill, or a Prompt) in a Kubernetes-style envelope: an apiVersion, a kind, a metadata block that names the artifact, and a spec block that describes it.

apiVersion: ar.dev/v1alpha1
kind: Agent
metadata:
  name: summarizer        # what to call this artifact
  namespace: marketing    # which logical group it lives in
  tag: 1.4.0              # which version of it this manifest is
spec:
  description: Summarises long-form text
  # ... source image, model, MCP servers, etc.

metadata.tag is the field on that envelope that identifies which version of the artifact this manifest represents. The registry uses the triple (namespace, name, tag) as the unique key for every artifact it stores. Two manifests with the same name and namespace but different tags coexist as separate snapshots. Two manifests with the same name, namespace, and tag overwrite each other.

The tag field is optional. Omit it and the server stores the artifact under the literal tag latest. That default works fine while you're iterating on your laptop. The minute another manifest, deployment, or teammate needs to depend on a specific build, you want an explicit tag with a discipline behind it.

AgentRegistry treats taggable artifact kinds (Agent, MCPServer, Skill, Prompt) as immutable per (namespace, name, tag) tuple. Once you publish summarizer:1.4.0, those bytes are the canonical 1.4.0. Publish again at the same tag and you replace the snapshot, which is what makes pointer tags like stable useful. Mutable control-plane objects (Providers, Deployments) do not take tags. They're managed by public name and namespace.

The tag string itself is freeform. AgentRegistry doesn't enforce semver, calver, or any other shape. That's a feature, not a bug, but it means the discipline is on the team to pick a convention up front and stick with it.

Heads up on the rename. Earlier declarative inputs called this field metadata.version. It's been renamed to metadata.tag. Both refer to the same thing, and the server still maps the absence of the field to latest.

When to set a tag

If nothing in the registry references your artifact by name, leave the field out and keep iterating against latest. As soon as another manifest, agent, or deployment pins to you, publish an explicit tag and let them reference that. The cost of running on latest the whole way through development is zero. The cost of having two teammates each thinking latest means a different build is real.

The CLI surface is symmetric: every get and delete takes a tag selector.

arctl get agent summarizer               # latest
arctl get agent summarizer --tag stable  # explicit tag
arctl get agent summarizer --all-tags    # all tags for that name

arctl delete agent summarizer                # latest only
arctl delete agent summarizer --tag stable   # one tag only
arctl delete agent summarizer --all-tags     # every tag

What to put in the tag (versioning convention)

Because the tag string is freeform, the decision that matters is the convention you pick. The pattern I'd recommend by default is a two-track scheme: an immutable snapshot tag that the build pipeline writes once per build, and a floating pointer tag that consumers reference and that promotion republishes.

Immutable snapshot tag

What the build pipeline writes. One tag per build, never reused.

  • Semver (1.2.3, 1.2.3-rc.1) when the artifact has a release cadence and external consumers
  • Git SHA (a1b2c3d) when you want maximum determinism and don't need human ordering
  • Build number (build-417) when the pipeline is the source of truth and SHA is too opaque

Floating pointer tag

What consumers reference. Republished on every promotion to point at a new snapshot.

  • stable, prod, canary, dev
  • Consumers pin to the pointer in their own manifests
  • Promotion is a single arctl apply against the new snapshot's bytes
  • Rollback is the same operation against the previous snapshot

Concretely, the build pipeline publishes summarizer:1.4.0, then a separate promotion step republishes summarizer:stable pointing at the same image bytes. A consumer that referenced summarizer:stable picks up the new build at the next reconcile. If 1.4.0 misbehaves, you re-promote 1.3.9 under stable and the consumer rolls back without redeploying anything else.

Two rules I'd hold to:

Authenticating from a pipeline

Enterprise AgentRegistry authenticates clients via OIDC. From a CI runner the shape is: obtain a JWT from your identity provider (workload identity, client-credentials grant, the OIDC token a CI system issues to its own runners, and so on), then exchange it at the registry for a bearer token via arctl user login. arctl apply uses that bearer for every subsequent call.

# In the pipeline runner
arctl user login \
  --oidc-issuer-url https://idp.example.com/realms/platform \
  --oidc-client-id  ci-publisher

arctl apply -f agent.yaml

The principle here is short-lived credentials. The pipeline never holds a long-lived password or static API key. It holds an identity (workload identity in the runtime, a client registration in the IdP, or a CI-native OIDC token) and exchanges that for a registry bearer that expires.

RBAC and the approval workflow

RBAC is driven by JWT claims. The role-mapper expression looks at a configurable claim path (group memberships, realm roles, whatever your IdP populates) and maps each caller to a role in AgentRegistry. From there, scoped permissions decide what the caller can read, publish, or promote.

On top of that, AgentRegistry can stage non-admin tagged artifacts for approval before they land in production storage. Combined with the two-track tag convention above, this gives you a clean separation:

Pipeline action Tag Workflow
Build publishes a new snapshot 1.4.0 (semver) or a1b2c3d (SHA) Lands directly. Snapshot is immutable.
Pre-prod promotion dev or canary Lands directly under the same service account.
Production promotion stable or prod Staged for human approval before it replaces the live pointer.

The pipeline service account never needs admin. It publishes snapshots freely, moves dev and canary on its own, and asks a human to move stable.

Pinning the image by digest

The Agent's spec.source.image field takes an OCI reference. Use the digest form rather than a floating tag, so every arctl pull or downstream deploy resolves to the same bytes regardless of what's happened in the OCI registry since.

apiVersion: ar.dev/v1alpha1
kind: Agent
metadata:
  name: summarizer
  tag: 1.4.0
spec:
  description: Summarises long-form text
  source:
    # Pin by digest, not a floating tag, so the manifest references
    # the exact image bytes the build produced.
    image: ghcr.io/example/summarizer@sha256:9f1a3c0e5b6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f

Tagging the OCI image with 1.4.0 is still useful for human readability and for tooling that wants to track which semver shipped, but the value the AgentRegistry manifest references should be the digest. A digest is the only form that can't be silently replaced.

Verifying at deploy time

Cosign-sign the image in the build pipeline, ideally keyless via the runner's OIDC identity, and the signature lands in the OCI registry alongside the image. Verification of that signature happens downstream when the image is about to run. In Kubernetes that's a policy controller (Sigstore's policy-controller, Kyverno with its verifyImages rule, OPA Gatekeeper with a custom constraint), pointed at the identity that signed and the expected issuer.

The AgentRegistry manifest's job in this picture is small but important: hand the verifier a stable digest. The digest is what the cosign signature is computed over, so a digest-pinned manifest and a policy controller that verifies that exact digest give you an end-to-end chain from build runner to running pod that can be audited and replayed.

Cosign docs cover the actual signing command shape: docs.sigstore.dev/cosign/signing/overview.

Best-practices checklist

Pushing to AgentRegistry, the short version