Files
manga-recap-pipeline/HANDOFF.md
T
kami c51871348f Rewrite the handoff for this session, document the restart trap
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>
2026-08-12 19:33:06 +04:00

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_characters gating. 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 -k does not re-run the window command. It leaves a bare shell. Both vision and identity sat dead for two minutes before an empty /health caught it. Now in AGENTS.md.
  • docker compose up --build orchestrator recreates the container and drops any docker cp'd file, so audit_registry.py needs 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.