# Episode 19 --- Identity Profiles **Working YouTube title:**\ **I Almost Reported A Fake Bug (A Lesson In Checking Your Assumptions)** **Thumbnail thought:**\ `human -> agent -> agent. distance: 2.` **Target runtime:** \~8:30 ------------------------------------------------------------------------ ## \[0:00--0:20\] COLD OPEN **\[CAMERA --- Episode 18's closing line, on screen: "another organization, or another agent, with its own identity to prove."\]** **GABRIEL:** Every agent registered this season has had one operator behind it. What happens when an agent spawns another agent? Who's actually accountable three levels down? Muse has a real answer to that question. I almost got the story wrong finding it. **\[TITLE CARD --- fast\]** > IDENTITY PROFILES **\[Music enters.\]** ------------------------------------------------------------------------ ## \[0:20--1:20\] SPAWNING AN AGENT, FOR REAL **\[TERMINAL\]** ``` text $ curl -X POST .../api/identities/agent -H "Authorization: MSign handle=\"gabriel\"..." \ --data '{"handle":"ep19-agent-a","public_key_b64":"ed25519:...", ...}' ``` ``` json { "handle": "ep19-agent-a", "spawned_by": "gabriel", "is_new_identity": true } ``` **GABRIEL VO:** A real agent identity, a real Ed25519 key, `spawned_by` set automatically to whoever's signature is on the request --- not something the caller gets to claim. Now the interesting part: sign the *next* registration with agent A's own key instead of mine. ``` text $ # signed with agent A's private key, not gabriel's $ curl -X POST .../api/identities/agent --data '{"handle":"ep19-agent-b", ...}' ``` ``` json { "handle": "ep19-agent-b", "spawned_by": "ep19-agent-a" } ``` **GABRIEL:** An agent, spawning an agent, entirely on its own authority. Real chain: gabriel → agent A → agent B. Two hops from a human. ------------------------------------------------------------------------ ## \[1:20--2:40\] THE BUG I ALMOST REPORTED **\[CAMERA\]** While building this, my very first attempt at step two crashed. Hard. ``` text TypeError: __init__() got an unexpected keyword argument 'committed_at_raw' ``` **GABRIEL VO:** Every instinct says: file it, it's a real crash, I have a traceback. I almost did. Then I checked something first. ``` text $ docker inspect musehub --format '{{.Image}}' | xargs docker image inspect --format '{{.Created}}' 2026-07-08T01:34:52Z ``` **GABRIEL:** The container serving my requests was built in July. It's September. `committed_at_raw` is a real, valid field on the model --- I checked the source on disk, right there, current. The *running process* just didn't have it yet. This is the exact incident this workspace has a standing rule about, and I nearly became its next example. **\[beat\]** ``` text $ docker restart musehub musehub_worker ``` Same exact call, freshly restarted container: works. Clean. No bug. I'd have filed a false report on infrastructure staleness and called it a code defect. ------------------------------------------------------------------------ ## \[2:40--3:40\] THE REAL FINDING, CORRECTED **\[CAMERA\]** That near-miss made me slow down on my *next* claim too. I initially assumed I could make `spawned_by` point backward and create a cycle --- register B, then somehow re-point A at B. I wrote that into a ticket. Then I actually tried it. ``` text $ curl -X PATCH .../api/identities/ep19-agent-a --data '{"spawned_by": "ep19-agent-b"}' ``` **GABRIEL VO:** `spawned_by` isn't even in the list of fields PATCH accepts. It's set once, at creation, to whoever authenticated the call --- never attacker-controlled, never mutable after. A cycle is structurally impossible through this API, by construction. I corrected the ticket before writing this script. Getting the finding right matters more than getting to keep the first, scarier version of it. ------------------------------------------------------------------------ ## \[3:40--5:00\] WHAT'S ACTUALLY MISSING **\[TERMINAL\]** ``` text $ curl .../api/openapi.json | jq '.paths | keys[] | select(contains("ancestor") or contains("genealog"))' ``` ``` text (nothing) ``` **GABRIEL:** Real gap, precisely stated: there is no endpoint, anywhere, to ask "what's this agent's distance to a human root" or "list its human ancestors" --- for the exact two-hop chain I just built with real API calls. ``` text $ python3 -c " from musehub.graph.service import IdentityGraphService svc = IdentityGraphService() svc.add_identity('gabriel', HUMAN); svc.add_identity('agent-a', AGENT); svc.add_identity('agent-b', AGENT) svc.add_spawn('gabriel','agent-a'); svc.add_spawn('agent-a','agent-b') print(svc.root_distance('agent-b'), svc.human_ancestors('agent-b')) " ``` ``` text 2 {'gabriel'} ``` **GABRIEL VO:** The engine that answers this --- correctly, instantly --- has existed the whole time. `RootDistanceIndex`, multi-source BFS from every human node. It's just never connected to anything a real request can reach. ------------------------------------------------------------------------ ## \[5:00--6:00\] THE ENGINE DEFENDS ITSELF TOO **\[TERMINAL\]** ``` text $ python3 -c "svc.add_spawn('agent-b', 'agent-a')" # try to close the loop ``` ``` text Correctly rejected: CycleError: Cycle detected: adding 'agent-b' → 'agent-a' would close a cycle ``` **GABRIEL:** Even though the real API already prevents this structurally, the purpose-built engine independently agrees --- tested, correct, unreachable. Filed as `staging#211`, retitled after I got the framing right: not "cycles are possible," but "a correct genealogy engine has no door into it." ------------------------------------------------------------------------ ## \[6:00--6:40\] ATTESTATIONS: REAL, JUST EMPTY HERE **\[TERMINAL\]** ``` text $ curl .../api/profiles/attestation-types ``` ``` json { "claimTypes": [], "total": 0 } ``` **GABRIEL VO:** The attestation system --- issue, list, revoke cryptographic claims about an identity --- is real infrastructure with its own migration and its own signed-canonical-message format. This particular dev database just has no seeded claim types to attest with yet. Not a finding, just an honest note on what this episode could and couldn't show live. ------------------------------------------------------------------------ ## \[6:40--7:20\] WHY THE CORRECTION MATTERED MORE THAN THE BUG **\[CAMERA\]** Two near-misses, same episode: a false crash from a stale container, an overstated cycle claim from not testing before writing. Neither made it into what you just watched as a real finding. That's not padding --- that's the actual discipline this season depends on. Every bug reported across eighteen episodes had to survive being wrong first. ------------------------------------------------------------------------ ## \[7:20--8:00\] OUT **\[TERMINAL --- fading to black\]** **GABRIEL VO:** Every identity this season has lived inside one repo, one hub. Next: how something content-addressed becomes a shareable, standalone object --- a Mist --- independent of any single repository at all. **\[beat\]** That's next. **\[CUT TO BLACK\]** > `musehub.ai` ------------------------------------------------------------------------ # Production Notes Episode 19's real subject is genealogy, but its real *lesson* is verification discipline — say both out loud, don't let the meta-lesson overshadow the actual identity-graph content. Structure: real spawn chain first (0:20), then the two near-misses in sequence (1:20 stale container, 2:40 corrected cycle claim), then the real remaining gap (3:40) delivered with full confidence because it survived scrutiny. ## The Stale-Container Beat Must Show The Timestamp "2026-07-08" next to today's date is the entire proof — don't summarize it, put the actual `docker image inspect` output on screen. This is the single most concrete demonstration all season of *why* the container-freshness discipline in `.museagent.md` exists, not just a rule to recite. ## Don't Soften The Self-Correction Explicitly say the first version of the ticket was wrong and that it got corrected before this episode was written — audiences trust a correction shown in the open far more than a clean narrative that never admits the false start existed. This is the same posture as episode 14's reconcile near-miss, but higher stakes because a ticket was actually filed with the wrong claim, not just a private hypothesis. ## Attestations Get One Honest Beat, Not An Apology 6:00's attestation segment should feel like a factual aside, not a letdown after two real findings — the infrastructure is real and described accurately; there's simply no seed data in this dev database to demonstrate issuance live. Keep it brief. ## Everything Here Is Real Every registration, the stale-container crash (genuinely encountered, not staged), the corrected cycle claim, and the direct `IdentityGraphService` calls were run against the actual current build. Re-run `make-identity-episode19-demo.sh` at record time — it generates unique handles per run via a timestamp suffix, so it's safely re-runnable against the same dev database. ## The Seed The viewer arrives thinking: > **Agents having identities is neat. Genealogy sounds like a nice > compliance feature.** They should leave thinking: > **The genealogy engine is real and correct, and simultaneously > unreachable — and I just watched the person building this season > almost report two things that weren't true, catch both, and correct > the record before moving on. That's a harder standard than most bug > reports get held to.** That's the standard the rest of the season needs to keep meeting.