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]
$ curl -X POST .../api/identities/agent -H "Authorization: MSign handle=\"gabriel\"..." \
--data '{"handle":"ep19-agent-a","public_key_b64":"ed25519:...", ...}'
{ "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.
$ # signed with agent A's private key, not gabriel's
$ curl -X POST .../api/identities/agent --data '{"handle":"ep19-agent-b", ...}'
{ "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.
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.
$ 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]
$ 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.
$ 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]
$ curl .../api/openapi.json | jq '.paths | keys[] | select(contains("ancestor") or contains("genealog"))'
(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.
$ 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'))
"
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]
$ python3 -c "svc.add_spawn('agent-b', 'agent-a')" # try to close the loop
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]
$ curl .../api/profiles/attestation-types
{ "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.