Order bbox corners, and find the ground truth is for another manga
`_bbox_to_pixels` sorts each coordinate pair after clamping. Run B returned `p007 person_1` as `[226, 417, 130, 551]`, x1 > x2, which cropped to nothing and lost that detection silently. The third GPU cycle came back with 0 degenerate boxes over 119 detections. The plan's first item cannot run as written. All 145 rows of `identity_labels` belong to chapter 8ca8249b, a different manga. Chapter 7c944dd4 has none, so `/review/identity` reports `accuracy: null`. Scoring 8ca8249b gives 7/138 on an identity run that predates every fix. The cycle also settled the coverage question. All 68 assignments landed on face-bearing detections and none on a gated one, so recall among face-bearing detections is 96%, up from 82%. Coverage is the `has_face` gate and nothing else. What is now open is precision: the lead holds 36 of 68 assignments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+55
-127
@@ -1,160 +1,88 @@
|
||||
# HANDOFF, 2026-08-12 (fifth session)
|
||||
# HANDOFF, 2026-08-12 (sixth session)
|
||||
|
||||
Live state is in `NEXT.md`. This file is only what this session did. The previous handoff is in
|
||||
`JOURNAL.md`.
|
||||
|
||||
## Asked
|
||||
|
||||
"go ahead" on the GPU cycle the fourth session left staged. Then "what do we do now". Then go ahead on
|
||||
the fixes its evidence asked for, and run it again.
|
||||
The two files, then "go" on the third GPU cycle.
|
||||
|
||||
## Result
|
||||
|
||||
Two full GPU cycles ran. All four of the fourth session's identity changes are now proven on real panels.
|
||||
Four more fixes were written on top, all orchestrator-side, all deployed. Coverage fell twice and that is
|
||||
the open question.
|
||||
One worker fix, one measurement that killed the plan's first item, one GPU cycle. The cycle proved the new
|
||||
fix and the fifth session's merge fix. Coverage rose for the first time, 50% to 57%. It is now explained
|
||||
entirely by the `has_face` gate.
|
||||
|
||||
| cycle | detections | assignments | coverage | registry | named |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| baseline (13:11, pre-change) | 110 | 77 | 70% | 8 | 1 |
|
||||
| run A (16:39-16:45) | 110 | 67 | 61% | 16 | 2 |
|
||||
| run B (17:38-17:44) | 117 | 59 | 50% | 20 | 2 |
|
||||
| metric | 17:38 run | 18:07 run |
|
||||
| --- | --- | --- |
|
||||
| detections | 117 | 119 |
|
||||
| assignments | 59 | 68 |
|
||||
| coverage | 50% | 57% |
|
||||
| degenerate boxes | 1 | 0 |
|
||||
| assigned among face-bearing | 59/72 = 82% | 68/71 = 96% |
|
||||
| assigned among gated | -- | 0 |
|
||||
| chains deeper than one hop | 1 | 0 |
|
||||
| `merged_from` stamps | 9 | 22 |
|
||||
|
||||
Vision is non-deterministic, so detection counts move between runs. Each cycle is ~6 minutes:
|
||||
vision ~3m50s, identity ~1m25s, reconcile ~50s.
|
||||
## The corner-ordering fix
|
||||
|
||||
## First, a correction the session started with
|
||||
`_bbox_to_pixels` sorts each coordinate pair after clamping. Three lines
|
||||
(`decisions/identity-bbox.md#bbox-corners-ordered`). The self-check feeds it run B's real
|
||||
`[226, 417, 130, 551]` and asserts `[117, 688, 203, 909]`. Unsorted the call returns `[203, 688, 117, 909]`,
|
||||
so the assert fails without the fix.
|
||||
|
||||
The vision/identity/reconcile timestamps in `/job/status` are UTC. The git log is local, UTC+4. The run
|
||||
that looked like a completed rerun was the pre-change baseline: it finished 13:17 local, and
|
||||
`_mark_has_face` was not committed until 19:07. The fourth session's handoff was right that nothing had
|
||||
run.
|
||||
The run returned 0 degenerate boxes over 119 detections. The caveat is deleted and the decision is indexed.
|
||||
|
||||
## Run A: the four staged changes, verified
|
||||
## The measurement that killed item 1
|
||||
|
||||
| change | verdict |
|
||||
| --- | --- |
|
||||
| `has_face` stamp | on all 110 detections, 67 true / 43 gated (39%) |
|
||||
| extras gate | panel 7's two wrong bindings gone |
|
||||
| NONE mints | 16 characters where 8 existed |
|
||||
| non-destructive merge | 7 rows carry `merged_into`, 9 assignments stamped `merged_from:` |
|
||||
The plan said to score precision with `eval_identity.py` against the 145 rows of `identity_labels`. Every
|
||||
one of those rows belongs to chapter `8ca8249b`, a different manga with cast "Rico" and "Ikekin", spread
|
||||
over 81 panels. Chapter `7c944dd4` has zero labels, so
|
||||
`/review/identity?job_id=778297bc...` returns `labeled: 0, correct: 0, accuracy: null`.
|
||||
|
||||
Panel 7, the worked example:
|
||||
Scoring `8ca8249b` anyway gives 7/138, with 113 rows labelled as a real person and left unassigned. That
|
||||
chapter's identity run is stale: 44 assignments over 246 panels, predating every fix. The number measures
|
||||
old code on the wrong chapter.
|
||||
|
||||
```
|
||||
person_1 Seonho 1.00 -> -- none -- has_face=False
|
||||
person_2 character_f7a4fd 0.00 -> character_519d2b 0.00 has_face=True
|
||||
person_4 character_d72710 0.94 -> -- none -- WRONG binding removed
|
||||
person_5 Seonho 1.00 -> -- none -- WRONG binding removed
|
||||
```
|
||||
So the eval path is proven end to end and the ground truth is absent. A precision number needs a hand pass
|
||||
over `7c944dd4` through `POST /review/identity/label`, keyed by bbox rather than `local_id`.
|
||||
|
||||
`person_1` going unassigned is correct, and this was measured, not assumed. Ran `face_detect.detect_faces`
|
||||
on `p006.png` directly: one face on the whole panel, conf 0.599, inside `person_2`. Swept the threshold to
|
||||
0.04 and nothing else appears above 0.056. The crop shows the lead drawn from behind at his desk, back of
|
||||
the head and headphones. Detector right, gate right, lead unidentifiable in that panel.
|
||||
## What the cycle settled
|
||||
|
||||
`person_1`'s box now frames the lead. The old "empty window mullion" note was written against pre-fix
|
||||
boxes and is retired.
|
||||
**Coverage is the gate and nothing else.** All 68 assignments landed on face-bearing detections and none on
|
||||
a gated one. Recall among face-bearing detections is 96%, up from 82%. The resolver is not losing cast, so
|
||||
the suspicion in `caveats/audit-open.md#coverage-trend` is closed.
|
||||
|
||||
## Run A's new defect: a roster hint named the wrong man
|
||||
**The merge-chain fix holds under load.** 9 merges this run against 7 last run, and 22 `merged_from`
|
||||
stamps. No chain is deeper than one hop.
|
||||
|
||||
Verified against the art, three real people:
|
||||
## What the cycle opened
|
||||
|
||||
- `character_92129ac7` "Lim Seonho", 22 assignments. The p010 caption reads `LIM SEONHO (29)`, yellow
|
||||
plaid shirt, headphones, matching the roster's description. Correct.
|
||||
- `character_556aef60`, 25 assignments, unnamed. The woman with short black hair and pearls, the roster's
|
||||
second character, whom the roster itself calls "Unknown". Correct.
|
||||
- `character_dbadfff7` "Seonho", 15 assignments. A different man, glasses, dark clothing, wearing the
|
||||
lead's roster name.
|
||||
**The lead may be absorbing.** 36 of 68 assignments, 53%, against 25 of 59 before. `audit_registry.py`
|
||||
flags it. A protagonist in half the panels looks identical to an over-merge without labels.
|
||||
|
||||
p020 assigned `Seonho` to `person_1` and `Lim Seonho` to `person_3`, two people in one panel.
|
||||
|
||||
## What was changed, and where the cause was
|
||||
|
||||
**Roster hints no longer seed detection.** Deleted the two lines at `service.py:882` that appended
|
||||
`_roster_char_hints` to `known_characters`. `build_detect_prompt` drops any hint without a name. Passing a
|
||||
nameless hint would have contributed nothing, so removing the seeding was the only real option.
|
||||
Names now reach detection from registry rows only, which carry embeddings and were named from an in-panel
|
||||
caption or address. The roster still feeds `roster_cast` in `run_stage_dialogue`, where names match
|
||||
against speech rather than faces.
|
||||
|
||||
**`merge_characters` keeps `merged_into` one hop deep.** Two halves, one per direction, and the second was
|
||||
missed on the first attempt:
|
||||
|
||||
- resolve the keeper to its chain root before merging (a bounded 64-step walk, the cap only so a cycle
|
||||
cannot hang reconcile)
|
||||
- repoint the loser's own dependents to the keeper when the loser is retired
|
||||
|
||||
The keeper walk alone does not work. Run B still produced `477c1894 -> a92d9df4 -> 4fb94c15`. At merge
|
||||
time that pair was fine. The chain formed later, when a row that was already somebody's keeper was itself
|
||||
retired.
|
||||
|
||||
**`reset_registry` deletes `name_claims`** for the manga's panels and reports the count. Confirmed live:
|
||||
run B's reset reported `name_claims: 5`, the orphans that had pointed at `character_afa7623b` since two
|
||||
resets earlier.
|
||||
|
||||
**`audit_registry.py` is in the repo.** It had only ever been `docker cp`'d, so every rebuild dropped it.
|
||||
`Dockerfile` has `COPY . .`, so it is baked now and that trap is gone.
|
||||
|
||||
## Run B: what the fixes did
|
||||
|
||||
The registry split is fixed. "Lim Seonho" came back as ONE row holding 25 assignments. That answers the
|
||||
open risk from before the run: dropping the roster hint did not split the lead across the panels before
|
||||
his caption. "Seonho" fell from 15 assignments to 1.
|
||||
|
||||
Two things run B surfaced:
|
||||
|
||||
- **A second naming mechanism, untouched by the fix.** Even with no roster hint, p011 and p026 emit
|
||||
`name: "Seonho"` on `person_2` from in-panel text. Detection reads a name off the panel and attaches it
|
||||
to the wrong body. That is what keeps the glasses man named, now at 1 assignment instead of 15.
|
||||
- **One degenerate bbox.** `p007 person_1` is `[226, 417, 130, 551]`, x1 > x2. One in 117. It crops to
|
||||
nothing, so that detection can never enroll or match. `_bbox_to_pixels` clamps each coordinate but never
|
||||
orders the corners.
|
||||
**Panel 7 got worse.** 5 of its 6 detections carry `has_face = False`, and `person_6`, the one that does,
|
||||
went unassigned. The previous run bound its colleague. That is the cost of requiring a face, `NEXT.md`
|
||||
item 4.
|
||||
|
||||
## Not done
|
||||
|
||||
- **The degenerate-box guard.** It is a worker change (`worker_vision.py:_bbox_to_pixels`), so it needs a
|
||||
vision worker restart and a third GPU cycle to prove. Not started, nothing half-edited.
|
||||
- **The coverage trend.** 70% -> 61% -> 50%. Part is the `has_face` gate, which is stable at ~39% of
|
||||
detections across both runs. Framed against face-bearing detections only, run B assigned 59 of 72, or
|
||||
82%. Nothing establishes whether the rest is correct abstention or lost cast. Settle that number before
|
||||
trusting the registry.
|
||||
- **Nothing downstream re-ran.** The job is parked at `dialogue waiting` with every stage below it
|
||||
cleared. No clip or chapter has been rebuilt against the new cast.
|
||||
- Nothing downstream re-ran. The job is still parked at `dialogue waiting` with every stage below cleared.
|
||||
- Nothing is committed. All edits are in the working tree.
|
||||
|
||||
## Checks
|
||||
|
||||
```bash
|
||||
/usr/bin/ssh kami@192.168.1.104 "cd /home/kami/docker-apps/manga-infra/orchestrator && python3 -m pytest -q --ignore=test_api.py" # 118 passed, was 116
|
||||
./check_stale.sh # exit 0, all 9 workers current
|
||||
.venv/bin/python worker_vision.py # self-check ok, including the swapped-corner assert
|
||||
./check_stale.sh # exit 0 before the run and after it
|
||||
```
|
||||
|
||||
Both new merge tests were confirmed to fail with their fix disabled, then the fix was restored and the
|
||||
suite re-run. The deployed container was verified by parsing its source, not grepping it. The first
|
||||
attempt gave a false negative, matching `_roster_char_hints` inside the comment that explains its removal.
|
||||
## Traps confirmed
|
||||
|
||||
## Next command
|
||||
|
||||
The third cycle, after adding the corner-ordering guard to `_bbox_to_pixels` and restarting the vision
|
||||
worker. Watch coverage against 50% and the degenerate box count against 1.
|
||||
|
||||
```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\"}'"
|
||||
# restart the identity worker here, see the trap below
|
||||
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"
|
||||
```
|
||||
|
||||
## Traps confirmed or found
|
||||
|
||||
- **`kill $(pgrep -f "worker_identity:app")` kills the shell running it.** The pattern matches the calling
|
||||
command line. It killed this session's own script mid-way, so the `send-keys` relaunch never fired and
|
||||
the identity worker sat dead. Restart it with the two `tmux send-keys` lines only, then poll `/health`.
|
||||
- **Verify deployed code by parsing it, not by grepping it.** A comment explaining a removal contains the
|
||||
name of the thing removed.
|
||||
- **A test that cannot fail proves nothing.** Both merge tests were run with their fix disabled first.
|
||||
- Panel-7 line numbers move between runs, because vision is non-deterministic and `local_id` is assigned
|
||||
top-to-bottom per run. In run B the lead is `person_5`, not `person_1`. Compare by bbox, not by id.
|
||||
- Restart a worker with `tmux send-keys -t manga-workers:<window> C-c`, then re-send the launch line from
|
||||
`start_workers.sh` with the `MIOPEN_ENV` prefix. `pgrep`-based kills match the calling shell.
|
||||
- `POST /characters/reset` returns `restart_identity_worker: true`. Honour it, the worker caches the
|
||||
registry.
|
||||
- `identity_assignments` has no `method` column. The `merged_from:` stamps live in
|
||||
`identity_assignment_sources`.
|
||||
- `chapters` has no `title` column.
|
||||
|
||||
Reference in New Issue
Block a user