c51871348f
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>
125 lines
7.1 KiB
Markdown
125 lines
7.1 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
.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`.
|
|
|
|
```bash
|
|
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.
|