Ship each crop's embedding so a resolver NONE can mint

Two identity fixes, neither yet run on a GPU.

has_face, the extras gate: fixing the bbox coordinate space made extras worse,
because crops finally landed on their subjects and a background extra bound to
the lead at 1.00. /vision stamps has_face per character using face_detect and
_pair_faces_to_present, so containment and its margin match the speaker path.
Identity skips has_face is False before it crops or embeds. Fails open on a
missing or raising detector, and gates on `is False` so an older vision blob
behaves as before.

The NONE mint: /identity/resolve now writes each crop's embedding to the crop's
key with a .npy suffix and returns emb_uri. The orchestrator cannot compute an
embedding of its own (siglip and gemma cannot both be resident), which is why it
was clearing a deliberate NONE instead of minting an anonymous character.

Also: audit_registry.py, and the tmux respawn-window trap, which
leaves a bare shell rather than re-running the window command.

decisions/identity-bbox.md#face-gates-enrollment
decisions/identity-bbox.md#none-mints-an-anonymous-character

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 19:07:56 +04:00
parent 7f25dc43e4
commit 491dad1c67
9 changed files with 476 additions and 109 deletions
+9 -4
View File
@@ -116,10 +116,13 @@ evidence. A recurring unnamed person needs a stable anonymous identity, so narra
colleague" every time.
`match()` at `worker_identity.py:69` does abstain, returning `None` below threshold, so the 0.9 came from
cosine clearing the threshold on a wrong crop. Whether the Tier-2 gemma resolver can answer "none of
these" was not verified.
cosine clearing the threshold on a wrong crop.
Revisit trigger: immediately after the `bbox` space is settled.
**Resolved 2026-08-12, `decisions/identity-bbox.md#none-mints-an-anonymous-character`.** The gemma resolver
can answer "none of these" and always could. The orchestrator was discarding the answer. It read only
`character_id`, so a deliberate NONE and a hallucinated index both unassigned every crop of the tracklet.
A NONE now mints an anonymous character from the crop, using the embedding identity ships beside it. Not
yet proven on a GPU run.
## Vision does not separate a background extra from cast {#extras-as-cast}
@@ -142,7 +145,9 @@ The visual comparison people reach for as the fix is **already implemented**, so
`/vision/resolve` at `worker_vision.py:963` sends the query crop plus up to 3 labelled reference images
per candidate. `build_resolve_prompt` already tells the model to judge face shape first, to treat hair
and outfit as secondary, that two people sharing a hair colour are not the same, and to answer `0` for
NONE when unsure. `choice: 0` becomes a new character and an out-of-range index becomes `unresolved`. The
NONE when unsure. `choice: 0` returns `state="new"` and an out-of-range index returns `unresolved`. What
the orchestrator does with each is
`decisions/identity-bbox.md#none-mints-an-anonymous-character`. The
`ref_image_uris` column is republished as `reference_image_uris` at `worker_identity.py:152` and `:161`,
so the references reach the model.