HANDOFF.md now covers the fourth session: the four identity changes, the measured numbers they were decided from, what was deliberately not built, and panel 7's before-table so the next run has something to compare against. AGENTS.md gains the worker-restart procedure, because tmux respawn-window -k leaves a bare shell instead of re-running the command and took both vision and identity down silently this session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.1 KiB
HANDOFF, 2026-08-12 (fourth session of the day)
Live state is in NEXT.md. This file is only what this session did. The previous handoff is in
JOURNAL.md.
Asked
"How much will has_face help with character and identity problems?", then in order: fix 2b, fix
extra-versus-cast, 2d.
Result
Identity item 2 in NEXT.md is now done as far as it can go without a run. Four changes are written,
self-checked and deployed. None has run on a GPU. No pipeline stage was executed this session.
| item | was | now |
|---|---|---|
| 2a bbox space | done last session | unchanged |
| 2b abstain and stay abstained | resolver blamed, unverified | fixed in the orchestrator, f6804e7 + ffda3df |
| 2c extra versus cast | open | two consumers gated, ca46617 + 8b27aec |
| 2d merge and split | open | examples gone; merge made non-destructive, 00096cc |
What each change was, and where the cause turned out to be
2b was never the resolver. /vision/resolve at worker_vision.py:1071 already returns
state="new" for choice: 0 and state="unresolved" for an out-of-range index. service.py read only
v.get("character_id") and ran unassign_identity on every crop of the tracklet for either one. A
deliberate NONE produced nothing, so an unnamed recurring person was unknown on every panel.
The old ponytail: comment above that block named the real blocker and was right. Minting needs an
embedding_uri the orchestrator cannot compute. siglip is resident in the identity worker and gemma in
the vision worker. Fixed by carrying the embedding, not by adding a GPU pass.
/identity/resolve writes each crop's embedding to the crop's key with a .npy suffix and returns
emb_uri. tracklets.resolve_outcome holds the three-way decision as a pure function.
decisions/identity-bbox.md#none-mints-an-anonymous-character.
2c: build_scene already dropped extras from the cast list. worker_scene.py:63 skips an unassigned
detection, so extras never reached characters or present. Their actions did. actions was built
from every detection. That list is the script prompt's content and the verifier's evidence. So a background
extra's "standing at the window" arrived as a fact with no character attached, and the verifier confirmed
it. Gated worker_scene's actions and service._beat on has_face is False. Left
service._present_characters ungated on purpose, reasoned out in
decisions/identity-bbox.md#extras-gate-consumers.
2d's worked examples no longer exist. The registry reset deleted Lim Seonho and
character_afa7623b. The registry is 8 rows, one named. Built the safety net for the coming rerun
instead, since reconcile runs inside it: merge_characters sets merged_into = keeper rather than
deleting, and stamps every repointed assignment method = merged_from:<loser_id>. Roster readers filter
merged_into IS NULL, lookup by id does not.
caveats/audit-open.md#destructive-reconcile is half-closed.
Measured, read-only, before writing anything
registry: 8 characters, 1 named -> ['Seonho']
detections: 110 assignments: 77 = 70% coverage
spread: Seonho 36, character_565c88 24, character_759e23 9, character_f7a4fd 3,
character_25f682 3, character_d72710 1, character_823aba 1
bbox space: 77/110 exceed 1000, 1 on 1000 -> PIXELS
That killed the assumed cause of 2b. Anonymous ids already recur, so the identity worker's own pending-promote path gives stable anonymous identities. Only the gemma NONE branch was discarding people.
Deliberately not built
- The unmerge path and the split. No wrong merge has been seen since the crops were fixed. Undoing one today is a hand-written SQL walk of the two records above. Revisit trigger is in the caveat.
service._present_charactersgating. An extra picked as speaker already resolves to unknown. A character drawn from behind has no face box. Gating would delete a real speaker from the only list that can attribute their line.- Vision still emits extras into the blob. Deliberate, so the audit can see what was gated.
Checks
.venv/bin/python worker_identity.py # ok
.venv/bin/python worker_scene.py # ok, 3 new cases on the actions gate
./check_stale.sh # exit 0, all 9 workers current
/usr/bin/ssh kami@192.168.1.104 "cd /home/kami/docker-apps/manga-infra/orchestrator && python3 -m pytest -q --ignore=test_api.py" # 116 passed
Deployed and verified inside the running container: resolve_outcome returns mint, _beat drops a
faceless detection, merged_into exists on the live database with 0 rows merged. Vision, identity and
scene workers restarted.
Next command
Four changes ride one GPU cycle. Coverage is 70% and is the number to beat. A gate that abstains too hard
shows up there before it shows up on panel 7. Watch the identity log line for minted N anonymous.
cd /home/kami/Programs/n8n-worker && ./check_stale.sh # must exit 0
J=778297bc-e7ce-439d-91b5-8a027060d17f; M=ef105a86-4b7e-4ac4-b45c-b7d83b8f5b5e
/usr/bin/ssh kami@192.168.1.104 "curl -s -X POST http://127.0.0.1:9090/characters/reset -H 'Content-Type: application/json' -d '{\"manga_id\":\"$M\",\"confirm\":true}'"
/usr/bin/ssh kami@192.168.1.104 "curl -s -X POST http://127.0.0.1:9090/stage/clear -H 'Content-Type: application/json' -d '{\"job_id\":\"$J\",\"stage\":\"vision\"}'"
for S in vision identity reconcile; do /usr/bin/ssh kami@192.168.1.104 "curl -s --max-time 5400 -X POST http://127.0.0.1:9090/stage/run -H 'Content-Type: application/json' -d '{\"job_id\":\"$J\",\"stage\":\"$S\"}'"; done
/usr/bin/ssh kami@192.168.1.104 "docker exec manga-orchestrator python3 /app/audit_registry.py 7c944dd4-e972-42c7-ba60-9f6939548e80 6"
Panel 7 (panel_index 6) is the worked example. Before this session's changes:
person_1 [457, 657, 642, 937] Seonho 0.00 foreground, correct
person_2 [669, 591, 763, 822] character_f7a4fd 0.00 the unnamed colleague, correct
person_3 [428, 386, 496, 526] -- none -- extra
person_4 [498, 386, 568, 533] character_d72710 0.94 extra, WRONG
person_5 [31, 554, 94, 728] Seonho 1.00 extra bound to the lead, WRONG
person_6 [34, 414, 122, 564] -- none -- extra
person_4 and person_5 are what has_face must remove without taking person_1 or person_2 with
them. Restart the identity worker after every reset: it caches the known list in-process and only
invalidates on enrollment.
Traps confirmed or found
tmux respawn-window -kdoes not re-run the window command. It leaves a bare shell. Both vision and identity sat dead for two minutes before an empty/healthcaught it. Now inAGENTS.md.docker compose up --build orchestratorrecreates the container and drops anydocker cp'd file, soaudit_registry.pyneeds re-copying after every rebuild.- Two test assertions asserted the old destructive merge (
test_db.py:235,test_merge_refs.py:37). They were rewritten, not deleted: the invariant changed on purpose. - The orchestrator image bakes its source. Editing the repo on homesrv does nothing until the rebuild.