fix(ir): the checked turn asks nothing (ENG-745) - #317
Conversation
R5 (rodada de 02/09): quando o analista não acha nada e a evidência é
suficiente, a rota marca `checked = True` — a passagem está conferida e não
há próximo turno — mas `closing_block(None)` ainda devolvia CLOSING_PLAIN,
que manda o narrador terminar com uma pergunta. O narrador obedecia e
perguntava algo que ninguém ia responder.
closing_block(finding, *, checked=False) ganha o parâmetro mínimo que a
rota já calculava (`state.checked`), e um bloco novo, CLOSING_CHECKED, é
escolhido só quando não há achado e a passagem acabou de ser conferida:
diz que a passagem foi contada e conferida, e para — sem pergunta, sem
convite, sem perguntar como se sentem, sem despedida.
Revisão de código encontrou uma segunda contradição da mesma família: o
prompt do narrador tinha uma linha estática, incondicional, logo abaixo do
slot {{CLOSING}} — "After the team acts on this one, they will finish the
telling-back again." — em todo turno de verdict. Com CLOSING_CHECKED, essa
linha passou a aparecer bem depois de "there is no next turn on this
passage", prometendo continuação no único turno que não tem. A linha saiu
do texto estático do prompt e passou a viver dentro de cada fechamento que
de fato tem um próximo round (CLOSING_PLAIN, CLOSING_SPOKEN,
CLOSING_ON_SCREEN, CLOSING_MISSING_ON_SCREEN, CLOSING_MISSING_TO_REHEARSAL)
— e não em CLOSING_CHECKED.
Nada além disso: os outros blocos de fechamento mantêm seu texto e ordem
de decisão, e o analista não foi tocado.
🤖 Generated with [Nori](https://noriagentic.com)
Co-Authored-By: Nori <contact@tilework.tech>
Same fork as in #326: 20260901_room09 (this line's BT_CORRECTION prompt key) and 20260902_room09 (main's drop of ir_prompts, PR #320) both descend from 20260831_lang01 with nothing joining them. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
# Conflicts: # alembic/versions/20260903_join_bt_correction_and_drop_ir_prompts.py
| #: process an ending. Left there it would have sat right after "there is no next turn" and | ||
| #: said the opposite in the same breath, so it now lives inside each closing that still has | ||
| #: a next round instead, and not in the one that does not. | ||
| _NEXT_ROUND = "After the team acts on this one, they will finish the telling-back again." |
There was a problem hiding this comment.
The move itself looks right to me, but the
get_prompt_textreadsIRPrompt.promptfromir_promptsand only falls back to the.md(default_prompt) when no row exists for the key
On head get_prompt_text is just return default_prompt(key)["prompt"] (app/services/internalization_room/prompts.py) — the committed file, no row read at all — and ir_prompts was dropped in alembic/versions/20260902_room09_drop_ir_prompts.py. So if I am reading it right the .md removal propagates by itself and nobody has to touch a row by hand, and the warning as written sends someone to a table that is not there anymore. Could you verify that and, if it is the case, drop the note from the description?
Linear: ENG-745
Context
R5 finding from the 09/02 afternoon round of the open flow (no new issue). No new issue yet; cite this PR as its origin.
When the analyst finds nothing and the evidence is sufficient, the route marks
checked = True(app/api/internalization_room/back_translation.py) — the passage has been checked and there is no next turn. Butclosing_block(None)still returnedCLOSING_PLAIN: "End with exactly one answerable question or invitation." The narrator obeyed: "Como vocês se sentem agora que terminaram de ensaiar essa parte da história?" — a question nobody was going to answer.What changed
closing_block(finding, *, checked: bool = False)gains the one parameter it was missing —state.checked, which the route already computed before this call.New block,
CLOSING_CHECKED, chosen only when there is no finding and the passage was just checked:Goes to the product owner for review, recorded here in full as the plan asked.
The router now calls
room.closing_block(finding, checked=state.checked).Code-review finding: a second contradiction of the same family
Code review found a static, unconditional line in the prompt template (
prompts/backtranslation_verdict_system_prompt.md), right below the{{CLOSING}}slot, on every verdict turn:With
CLOSING_CHECKED, that line started appearing right after "there is no next turn on this passage" — the same kind of defect as the original finding, promising continuation on the one turn that has none. The line came out of the prompt's static text and now lives inside each closing that actually has a next round (CLOSING_PLAIN,CLOSING_SPOKEN,CLOSING_ON_SCREEN,CLOSING_MISSING_ON_SCREEN,CLOSING_MISSING_TO_REHEARSAL) — never inCLOSING_CHECKED. Approved by the Orchestrator to land in this same PR.ir_promptsrow does not change on its ownget_prompt_textreadsIRPrompt.promptfromir_promptsand only falls back to the.md(default_prompt) when no row exists for the key;seed_room_promptsis idempotent and does not update a row that already exists. Consequence: in any environment whereBT_VERDICT_SPEAKERwas already seeded before this PR, removing the static line from the.mddoes not propagate on its own — the old line keeps being served until someone updates that row inir_promptsby hand. TheCLOSING_CHECKEDpart of the fix itself is not affected by this:{{CLOSING}}is substituted at runtime with whateverclosing_block()computes, so that part of the fix already holds for any stored row, old or new.Scope respected
Nothing beyond this: the other closing blocks keep their text and decision order; the analyst was not touched.
Testing Plan
New file
tests/test_ir_the_checked_turn_asks_nothing.py, shaped liketests/test_internalization_room_back_translation.py— imports the ENG-676 fixtures/doubles (patch_loop,patch_speaker) from the sibling file, the waytests/test_oc_review_flags.py→tests/test_oc_recording_description_rule.pyalready does.{{ORDERED_CLOSING}}) carriesCLOSING_CHECKED, notCLOSING_PLAIN. Failed before.used_fail_safe is False. The negative mirror (a narrator that still asks) was not written: the ENG-676 doubleValidatorReadsOnlyItsOwnPromptjudges three things read from its own prompt (unsupported claim, uncited destination, unstated name), and none of them read the closing's "do not ask" instruction — so it cannot tell a workingCLOSING_CHECKEDapart from a broken one on that axis. Documented in the test's docstring rather than pretending coverage that does not exist.CLOSING_PLAIN, and it still contains the continuation line.checkedis ignored when there is a finding; it still contains the continuation line.Mutation (paired, one per finding)
return CLOSING_PLAINinclosing_block: cases 1 and 2 fail..md: case 1 fails again (the new assertion about "finish the telling-back again").Verification
ruff check .anduv run mypy app/clean. Test-quality review and code self-review done by subagents before this PR; no additional finding beyond what is already recorded above.No merge.