Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/F1.2.mp4Shadow AI and shadow agents — the inventory is the control most orgs still lack.
Discovery, registration, ownership, risk tiering.
Discover agents from gateway and identity telemetry; build the register.
Nobody can govern what nobody has listed. The inventory is the least interesting artefact in this function and the one everything else depends on — and the hard part is not building it, it is keeping it true next quarter.
inventory() comes from registry.all_workloads(), not a survey — and the reason is exact: a survey returns the agents somebody declared, which is the complement of the set you are looking for. shadow() wraps B2.5's orphans() and answers both directions, because teams check one.Why an AI inventory built by survey returns the complement of the set you want, and how to derive one instead — plus the two directions of orphan, because teams check one.
You cannot govern, tier, test or revoke what you cannot list, and most of it is already in production.
Reconcile three sources — model registry, procurement, and egress logs to provider domains.
The denominator for every other indicator. A share whose denominator is unknown is a count wearing a percentage sign.
Measure this
| what to measure | denominator or target |
|---|---|
| the inventory | the denominator for every other indicator; a share whose denominator is unknown is a count wearing a percentage sign |
what has to be in the register
model . agent . integration . MCP server . eval corpus
|
+----v-------------------------------------------+
| owner . purpose . data classes . autonomy level|
| tools it holds . environment . last verified |
+------------------------------------------------+
building it is a project. keeping it true is the control.You cannot govern, tier, test or revoke what you cannot list. The AI inventory is therefore the first control, not a documentation exercise.
The honest finding of every first inventory is the same: most of it was already in production. Not because anyone was reckless, but because AI features arrive inside products you already bought, and agents get created programmatically by other agents.
Three sources, and the third finds what the first two miss:
Source 3 is the one that discovers the department using a frontier API on a personal card, and the SaaS product that quietly added an AI feature.
Anchor → F1.1. The inventory is the denominator. Every indicator is a share — of agents, of tool calls, of handlers — and a share whose denominator is unknown is a count wearing a percentage sign. An incomplete inventory does not make your indicators wrong; it makes them unfalsifiable.
Discovery finds that a thing exists. Governing it needs the row resolved into artefacts: which repository at which commit, which image digest rather than which tag, which IAM role and SPIFFE ID, which gateway and guardrail, and every downstream its tools call. Every other attestation skill consumes this graph, which is why it runs first. This is the file in this repository:
The skill — skills/attestation/deployment-inventory-resolver/SKILL.md
name: deployment-inventory-resolver
description: >-
Resolve a deployment_id into the artifacts that make up one agentic
deployment and build the evidence graph every other attestation skill
consumes. Use first, before any control is evaluated, or when asked which
repo, image, role, workload identity, gateway or guardrail a deployment
actually consists of.
allowed-tools: Bash, Read, Grep, GlobControls: All — this skill produces the join key
deployment_id is the primary key for every other skill in this set. Without it, an IAM finding, a SPIFFE entry and a gateway policy are three unrelated facts about three things that may or may not be the same system.
This skill turns that ID into a manifest of content-addressed artifacts, so every later finding resolves to one evaluable unit and cannot be silently confused with a neighbouring deployment.
First, before any control is evaluated. Nothing else in this chain can run without a resolved deployment_id: an IAM finding, an identity entry and a gateway policy are three unrelated facts until something establishes they describe the same system. Also run it whenever anyone asks which repo, image, role, workload identity, gateway or guardrail a deployment actually consists of — that question is usually answered from memory and usually wrongly.
sha256:…), not a tag. Confirm the digest matches the revision actually deployed, not the newest build.Input — the fixture committed at the top of scripts/deployment_inventory_resolver.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
asset kind autonomy owner found via
--------------------------------------------------------------------------------------------
fraud-scoring-model model L1 risk-eng registry
support-summariser copilot L1 support-eng registry
vendor-contract-analyser embedded-feature L1 — expense report
unknown-openai-usage-marketing copilot L1 — egress logs
pr-remediation-agent agent L2.5 — egress logs
agent-worker-7f3c agent L2.5 — egress logs
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"deployment_id": "str",
"resolved_at": "str",
"artifacts": {
"repo": {"url": "str", "commit": "str"},
"image": {"registry": "str", "digest": "str", "matches_deployed": true},
"identity": {"role_arn": "str", "spiffe_id": "str", "workload_identity_arn": "str"},
"traffic": {"gateway_arn": "str", "guardrail_id": "str"},
"downstreams": ["str"]
},
"missing": ["str"],
"orphans": ["str"],
"verdict": "PASS|PARTIAL|FAIL"
}
A manifest with entries in missing is PARTIAL at best. Every downstream skill inherits that ceiling — you cannot attest a control on an artifact you could not resolve.
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons F1.2Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick f1-2-building-the-ai-and-agent-inventory:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons F1.2 | type /f1-2-building-the-ai-and-agent-inventory |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons F1.2 | type /f1-2-building-the-ai-and-agent-inventory in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons F1.2 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons F1.2 | type $f1-2-building-the-ai-and-agent-inventory, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py F1.2Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at F1.2 --out work/cybertravels
python3 scripts/checkpoint.py --at F1.2 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
The skill loads and reports its shape. Two of its rules are what make an inventory hold: resolve digests rather than tags, because a tag is mutable and the thing you attested is not the thing running; and never record an unresolvable artefact as absent — "no gateway configured" and "could not read the gateway" are different findings with different owners.
Run the egress query for real: one week of traffic to model-provider domains, joined against your inventory. It takes an hour and it always finds something.
Tools used
Skill source: skills/attestation/deployment-inventory-resolver