DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path scoping-notes/loomworks-b39-agent-identities-scoping-note-v0_1.md

Loomworks — B-39 scoping note: ephemeral agent identities — v0.1

Version. 0.1 Date. 2026-08-07 Author. Claude Code (Wave 0 session). Operator: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. Scope. Wave 0 investigation 0.3 of standing-notes/loomworks-development-schedule-v0_1. Read-only. No code changed. Read against. Engine loomworks-engine at afdeff5; stele, stele-agentic-id, grantha read read-only. Status. Decision note. This is the one item the schedule does not pre-decide (loomworks-development-schedule-v0_1:38). CR-2026-189 waits on one Operator word on §5.


The problem, stated from the code

When no agent is named, the engine mints a UUID on the spot and stamps it into the permanent record as the actor that did the work. Verified sites:

| Site | What it does | |---|---| | api/deps.py:331 | SeedInductionAgent(llm, agent_id=uuid.uuid4())a new agent identity on every HTTP request | | api/routers/considerations.py:202 | id=body.agent_actor_id or uuid.uuid4() — client-suppliable, falsy-coalesced to an invention | | engagement/render.py:227–229 | agent_actor_id=uuid.uuid4() and instruction_set_id=uuid.uuid4() — an instruction-set id pointing at no instruction set | | orchestration/routers/converse.py (3 sites) | same pattern, hand-rolled |

It is declared, not accidental. api/schemas.py:4256 documents it to API consumers: "when omitted a fresh agent identity is allocated for the run." That is exactly why B-39 was struck from B-25 and opened as its own item — B-25's test is untrue record writes, and a documented allocation is not untrue. It is, in the build list's own words, "a record naming an agent that never existed."

Two structural facts make it stick. First, memory_events.actor_id is a bare UUID with no foreign key — verified: zero ForeignKey declarations on any actor_id across every migration. Nothing has ever been able to check it. Second, the engine's own law says there is exactly one sanctioned way to write an actor it cannot name (memory/base.py:164: "Anywhere else, an unnameable actor is a defect") — and these sites are not it.

And it is permanent. Memory is append-only; every invented id already written stays written. Whatever is decided applies going forward only.


What constrains any answer

  1. Provenance.wasAttributedTo is required (base.py:194). You cannot fix this by writing no actor.
  2. Human commit authority. NON_HUMAN_ACTOR_KINDS is rejected at commit and discard (assertions.py:284, :515, :601). Changing an actor's kind changes who may commit — so no option may quietly re-kind an agent.
  3. GRANTHA owns authorization and refuses identity, bilaterally ratified. B-39 sits upstream of GRANTHA. No option here may decide a permission.
  4. GRANTHA is not wired into the engine at all — verified: six matches across src/, all of them comments. It is a named future occupant of two function bodies, and the one that takes an actor (authority/authorizer.py:119) has never been passed an agent. So no option can lean on GRANTHA existing.
  5. Stele has no non-human principalPrincipalRow has eight columns and no nature discriminator, deliberately (stele-agent-identity-scoping-v0_3:32: "a nature column fights the design").
  6. There is no single agent-actor constructor. stele/actor.py:12 states it: agent-actor construction "has no single constructor." That absence is the mechanical reason there are ~20 independent mint sites.

Three options

Option A — Name what acts. Stop inventing; use stable declared identities.

Replace per-request uuid4() with stable, deterministic identities for the small set of things that actually act, extending a pattern already in the tree: RENDER_AUTO_TRIGGER_SYSTEM_ACTOR, RECONCILIATION_SPECIALIST_ACTOR, _COMPANION_ACTOR_ID and three others are already fixed sentinel UUIDs.

Option B — Resolve against the registry. Make agent actors refer to rows that exist.

Agent actors resolve to real contributors rows (kind='agent') rather than being minted, with validation at the write boundary.

Option C — Adopt Stele Agentic ID's derived-agent model.

stele-agentic-id already specifies precisely this problem's subject — spec V12: "Derived Agent: ephemeral principal — signed credential, no stored row, own UUID, TTL-bounded." Local signature mint (no issuer round trip), bounded-chain-walk verification, clamp/kill revocation, acts_for as the human link.


The unresolved fork underneath all three

stele/scoping/stele-agent-identity-scoping-v0_3.md §9 posed Path A (agent nature inside Stele) / Path B (separate substrate) / Path C (federation seam), leaned Path C, and instructed: "Do not advance the agent design or this note further until §9 is decided."

A search of the record found no document resolving §9. stele-agentic-id is Path B built out, and the July investigation treats the Stele/Agentic-ID split as settled at the product level — but that is not the same as §9 being answered, and the instruction may still bind. This ambiguity is itself worth an Operator word, because Option C is a §9 commitment in all but name, while Options A and B are deliberately fork-neutral.


Lean

Option A now; Option B as its named follow-on; Option C as the stated destination, not this CR.

The reasoning, plainly: B-39's actual complaint is that the record names agents that never existed. Option A removes exactly that, costs little, touches no substrate, commits to no fork, and can be verified by reading. It is the honest floor.

Option B is the right second step and should be named as such rather than folded in — it is where an FK and a single constructor belong, and it is a bigger change than it looks because of the mixed-actor table.

Option C is where this genuinely ends up, and the lean is explicitly not to reach for it now: adopting a not-yet-production, wire-frozen substrate across a seam that does not exist, on top of an unresolved substrate fork, to solve "stop minting throwaway UUIDs," is a large commitment bought for a small defect. It should be chosen when agent identity needs to be verifiable by someone outside the engine — which is a product decision, not a cleanup one.

What would change the lean. If the Operator intends Loomworks to be agentic-id's customer zero on a near horizon, then A is throwaway work and C should be scoped directly instead — at which point the §9 question must be settled first.


The one thing to decide

Which option, and — if A or B — whether the §9 fork stays parked. Everything else in Wave 3's CR-2026-189 follows mechanically from that answer.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-39 scoping note — v0.1 — 2026-08-07