/identifyHow Identify works
Identify reports what images a Kubernetes service is actually running and whether any of them have drifted from the image its controller should run. You cast it from Slack or a terminal as /identify followed by a service name; a collector inside your cluster resolves the name to its pods, walks each ReplicaSet up to the Deployment that owns it, and compares three things: the template image the Deployment declares as desired, the image on the running pod, and the container's imageID, which is the digest actually pulled. Drift is judged on the digest, not the tag, because a mutable tag can point at a new image while the tag string stays the same. It reads and nothing more: the grant is get and list on pods and pods/log, and get on Deployments and ReplicaSets, with no write verb and no Secrets.
Caster is pre-launch. This page describes how Identify is built, not a product you can install today. The transcript figures come from the demo on this site, and they are labelled where they appear.
How it works
It compares desired, running, and the digest
Identify resolves the service to its pods, the same loose match Scry uses, then works per distinct ReplicaSet. For each one it reads the ReplicaSet, then the Deployment that owns it. The Deployment's pod template holds the image it declares as desired. The running pod holds the image its own spec asked for. And the container status holds the imageID, the digest the kubelet actually pulled. Those three are what a verdict is built from.
The verdict is computed in the spell, not guessed: in sync when the digests line up, drifted when they do not, not started when a pod has no digest yet because it has not pulled, and desired unknown when the controller's intent cannot be read. A workload whose controller is not a Deployment reports its desired image from the pod spec, since there is no template to compare against. Anything Identify cannot read marks the reply degraded rather than dropping it in silence.
Drift is a digest problem, not a tag problem
A tag like v2.1, or worse latest, is a label someone can move. Two pods can both say they run v2.1 and be running different images, because the tag was repointed between their pulls. The digest is the image; the tag is a name for it. Identify reads the digest off the container status and compares that, so it catches the case a tag comparison misses: the same tag, a different image, quietly running in production.
The output is a table, one row per pod and container: what the controller wants, what the pod is running, and the verdict. When everything matches, the answer is short and boring. When it does not, the row that disagrees is the one to look at.
It reads; it does not change anything
The grant is get and list on pods and pods/log, and get on Deployments and ReplicaSets. There is no patch, no delete, and no watch, and Secrets are not in it. Identify tells you what is running and how it drifted; it does not set the image or restart the workload. Correcting drift is a deploy, which is a separate spell with a write grant and a production hard-stop of its own.
It runs in your cluster; the result comes back through the control plane
A collector runs inside your cluster and dials the control plane outbound over mTLS. It reads in place under a scoped ServiceAccount the control plane cannot widen; nothing dials in, and no kubeconfig, cloud credential, or shell leaves the cluster. The verdict table travels back through the control plane and is stored in the cast's row, isolated per tenant. What Identify returns is image names and digests, not application logs.
Setting it up
Setup happens once, run by whoever already administers the cluster. Everyone who casts a spell afterward holds none of what setup touches. What follows is the model in enough detail to check against; nothing here runs today.
Run the collector in your cluster
The collector is deployed into its own namespace with its own ServiceAccount. It needs no inbound networking, because it opens the connection to the control plane itself, outbound over mTLS. SPELLBOOK_NAMESPACES pins the namespaces it may ever touch, set here, on your side.
collector envSPELLBOOK_NAMESPACES=storefront,checkoutBind the read grant
Identify's ServiceAccount gets read on the objects it walks: pods, ReplicaSets, and Deployments. This is the ceiling the API server enforces, and there is no write verb in it, so nothing Identify finds can be changed by Identify.
identify ClusterRolerules: - apiGroups: [""] resources: [pods, pods/log] verbs: [get, list] - apiGroups: ["apps"] resources: [deployments, replicasets] verbs: [get]Grant the entitlement
In the control plane, a role carries spell.identify.cast, the string that lets a person or agent cast /identify. The match is exact unless a grant ends in .* , so it grants Identify and nothing near it.
entitlementspell.identify.castCast it
Ana signs in as herself and checks what checkout is running. She holds no kubeconfig, and she never gets one.
slack/identify checkout
This is the design, not an install guide. The collector bundle, the flags, and the exact manifests are not yet something you can apply.
What it never receives
The list a security review asks for, on the page rather than in a PDF behind a form.
- No kubeconfig and no cluster credential. The collector's ServiceAccount stays in the cluster, and you are never issued one.
- No shell. Identify's grant has no exec, attach, or port-forward, so there is no path from a cast to a prompt inside a pod.
- No write verb. The grant is get and list, so Identify can report drift and cannot correct it, restart a workload, or set an image.
- No Secrets. core/v1 Secrets are not a resource in the grant, so an image-pull secret or an env var holding a credential is out of reach.
- No registry credential. Identify reads the cluster's own view of what is running; the registry lookup is a different spell's job.
What it refuses to do
A permission that was never issued cannot be misused, by a person, by a compromised account, or by an agent.
- Set the image to fix the drift
- Identify holds no write verb. It reports drift; it does not correct it. Setting a Deployment's image is /deploy, which carries a write grant and a production hard-stop that Identify does not have.
- kubectl get deploy checkout -o yaml
- kubectl was never issued to you, so there is nothing to revoke. Identify reads the same objects and hands you the comparison, with no credential you could point at anything else.
- Trust the tag
- Identify does not. Two pods on the same tag can run different images if the tag was moved between their pulls, so the verdict is built on the container's imageID, the digest actually pulled, not the tag string.
- Read a Secret in the pod's env
- Not in the grant. Identify reads image references and digests, not Secrets, so a pull secret or an env var holding a credential is out of its reach.
- /identify a service outside your namespaces
- Out of scope, refused before a read. SPELLBOOK_NAMESPACES bounds what the collector can reach, and the RBAC rule bounds it again.
The audit record
Identify is a read, and every cast is a casts row. The durable record is that row, so there is no separate audit table. A cast refused at preflight never becomes a row: it is logged as a warning, so a queryable casts row always describes a cast that ran.
cast_id cst_2c81ff03
tenant_id tnt_northwind
principal okta|00u1a4h2 (oidc subject, not an email)
castable identify
idempotency_key slack:U03QK4T2P:1753281260
state succeeded
degraded false
started_at 2026-07-23T14:29:10Z
finished_at 2026-07-23T14:29:11Z
result { surface: slack #checkout-oncall,
target: checkout-api / storefront,
desired: checkout-api:9f3c1a2,
running: checkout-api:9f3c1a2, verdict: InSync }Where a record lands
- The casts row in the control plane's database, isolated per tenant with row-level security. This is the durable record, and it is the one a query runs against.
- The tenant's Slack audit_channel, which mirrors the result of every terminal cast, so a channel of record exists even for casts nobody ran in Slack.
- The collector's stdout as structured JSON, so whatever log pipeline you already run picks it up with no new integration.
- Your API server's own audit log records the collector's get and list calls independently, and the cast id ties the two accounts together.
Failure modes
What happens when it cannot do the thing.
- A pod has not pulled its image yet
- There is no digest to compare, so Identify marks that container not started rather than calling it drifted. A pod mid-pull is not a drift finding.
- The controller is not a Deployment
- There is no template to compare against, so Identify reports the desired image from the pod spec and says so. It does not invent a controller intent it cannot read.
- A ReplicaSet or Deployment cannot be read
- The reply is marked degraded, names what it could not read, and still returns the rows it could build.
- The name matches nothing
- It lists the services it can see in scope and casts nothing, the same private candidate reply Scry gives.
- Everything is in sync
- The table is short and every verdict says in sync.
Questions
How do I tell what image a Kubernetes service is actually running?
Cast /identify followed by the service name, from Slack or a terminal. A collector inside your cluster resolves the name to its pods, reads each pod's container status for the imageID it actually pulled, and reports it next to the image the pod's controller declares. You see the running image and its digest without holding kubectl, because the read runs in the cluster under the collector's own grant.
What is image drift, and how does Identify detect it?
Drift is the gap between the image a workload's controller says it should run and the image its pods are actually running. Identify detects it by walking each ReplicaSet up to its Deployment, taking the template image as desired and the container's imageID as running, and comparing the two. When they disagree the pod is drifted; when they match it is in sync.
Why compare the digest instead of the tag?
A tag is a movable label. Someone can repoint v2.1, or latest, at a new image, and two pods pulled at different times can run different images while both report the same tag. The digest is the content-addressed identity of the image itself, so comparing digests catches the exact case a tag comparison hides: the same tag, a different image.
What permissions does Identify hold?
get and list on pods and pods/log, and get on Deployments and ReplicaSets, in the namespaces the collector is scoped to. No write verb, no watch, and no Secrets. Kubernetes RBAC enforces that ceiling at your API server, so Identify can read what is running and cannot change it.
Can Identify fix the drift it finds?
No. Identify reports drift; it does not correct it. Setting a Deployment's image is /deploy, which carries a write grant, an immutable-tag rule, and a production hard-stop. Keeping the read and the write apart is deliberate: the spell that tells you about drift is not the one that changes what is running.
Can an AI agent use Identify?
Yes, as its own principal with its own spell.identify.cast entitlement. An agent that can identify a service's images holds no ability to change them, because the deploy entitlement is a separate string it was not given. Reading what is running never included the power to alter it.
The machine-readable version of this page is at /spells/identify.md. It is generated from the same source, so it says the same thing.
Run this in your own cluster.
Caster is not open yet. Leave an address and we will come to you first.