fix(transcript-fixer): adjudicate with available evidence before escalation (#513)

This commit is contained in:
daymade
2026-09-09 18:27:57 +08:00
committed by GitHub
parent a2bcaafad9
commit fdcd7d43e7
7 changed files with 79 additions and 12 deletions
+1 -1
View File
@@ -305,7 +305,7 @@
"description": "Audio processing suite covering the full speech pipeline: ASR transcription (Qwen3, StepFun) with batch-mode guards against music-only repetition-loop hallucinations, speaker diarization and CAM++ voiceprint identification for multi-speaker recordings, transcript error correction, structured meeting minutes generation, and TTS voice synthesis (StepFun). Install once for the complete audio workflow.",
"source": "./daymade-audio",
"strict": false,
"version": "1.35.1",
"version": "1.36.0",
"category": "suite",
"keywords": [
"suite",
+1
View File
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **peer-message** v1.0.0 (marketplace v3.6.0): restore the previously uncommitted Claude UDS messenger from its source session and extend it into a local Claude Code ↔ Codex coordination layer. The bundled stdlib CLI discovers `claude:` and `codex:` targets across isolated standard Claude profiles, preserves the original authenticated UDS fallback, routes Codex through the first-party `codex queue --thread` command, supports explicitly counted cross-provider broadcasts, adds source/reply envelopes with explicit provenance strength, and verifies delivery from Claude transcripts or Codex queue/thread history without writing either product's SQLite stores. Claude uses a host-recognized peer wrapper; Codex provenance remains advisory text enforced by receiver-side governing instructions. The Skill-local official-feature reference owns the corrected availability and inbound-policy boundaries. The registered test suite, live Codex queue acceptance, and subsequent thread-history consumption were independently verified.
### Changed
- **transcript-fixer** (`daymade-audio` v1.36.0): continue correction with available evidence when audio export is unavailable, apply user-confirmed terms immediately, and distinguish unresolved queue items from questions that require the user. Preserve identity, numeric/polarity, and draft-quality boundaries.
- **frontend-visual-qa** v1.13.0: audit information necessity before fitting text into the layout. Add a first-viewport DOM text inventory with repeated encodings and label echoes to support the full-surface audit; keep raw labels out of JSON reports and necessity explicitly unverified. Preserve task-dependent essential context and reject completion claims that merely downgrade an unmet user requirement. Validate capture and coverage boundaries with renderer-backed cases.
- **transcript-fixer** (`daymade-audio` v1.35.1): align entry and batch instructions with the Native packet workflow, correct queue context examples and finalization order, remove copied defaults and derived prose counts, and add the owning workflow index to `CLAUDE.md`.
- **transcript-fixer** (`daymade-audio` v1.35.0): add frozen Native review packets with explicit file scope, bounded segments, result validation, and missing-segment recovery. Keep coverage separate from correction quality and publication. Fix same-line repeated-token selection so unique context selects the intended occurrence and ambiguous verdicts preserve the transcript; strengthen same-file identifier spelling sweeps.
+2 -1
View File
@@ -119,7 +119,8 @@ only collects `unittest.TestCase` subclasses, not bare pytest-style functions.
### Transcript Correction
Use [transcript-fixer](daymade-audio/transcript-fixer/SKILL.md) for transcript
correction. Its Native checklist owns the correction and finalization order;
correction. Its Native workflow owns evidence selection, selective human escalation,
and correction/finalization order;
[Native review packets](daymade-audio/transcript-fixer/references/native_review_packets.md)
owns split, batch, and resumed review instructions. Keep CLI parameters and
validation behavior in
+5 -4
View File
@@ -21,8 +21,9 @@ Use a two-phase loop:
- Never rewrite speech for fluency. A correction must explain a plausible ASR error and preserve who said what.
- Never infer or reassign speaker identities. Preserve speaker-label lines; human-confirmed labels and user verdicts are authoritative.
- Before correcting any person name, directly read both the configured global people roster and the owning project's explicit identity roster or alias ledger. Stage 1 auto-loads only global `ASR 变体` entries; it does not load project rosters or expose suppressed, disabled, and unlisted entries. If an expected source is missing or the sources conflict, leave the name unchanged and enqueue or ask once. Never use occurrence frequency as identity evidence. Read [references/dictionary_identity_and_context.md](references/dictionary_identity_and_context.md) before settling the name.
- Leave unresolved text unchanged and enqueue it. A visible garble is safer than a fluent wrong guess.
- Treat an unfamiliar token as unknown, not as an error. Exhaust the local evidence ladder first. For a load-bearing token that remains unresolved, use the clip-level cross-recognizer rung only when source audio and a permitted second engine are already available; otherwise enqueue or ask. Agreement from a genuinely different recognizer family strongly corroborates the sound, but never chooses between homophonic spellings or overrides the person-name gate. Read native workflow step 4, rung 7 before using it.
- Resolve doubts from available evidence before escalating. Audio download is one evidence channel, not a prerequisite for Native correction. When it is unavailable, follow [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation); do not require the user to change download permissions or treat every pending row as a question only they can answer.
- Leave genuinely unresolved text unchanged and enqueue it. A visible garble is safer than a fluent wrong guess; a pending row records uncertainty, not an automatic human handoff.
- Treat an unfamiliar token as unknown, not as an error. Exhaust the local evidence ladder first. For a load-bearing token that remains unresolved, use the clip-level cross-recognizer rung only when source audio and a permitted second engine are already available; otherwise continue with the available evidence under the escalation policy above. Agreement from a genuinely different recognizer family strongly corroborates the sound, but never chooses between homophonic spellings or overrides the person-name gate. Read native workflow step 4, rung 7 before using it.
- Treat a single-line `asr_note` value as correction provenance: it intentionally cites old forms and is excluded from matching. Multi-line YAML ledger values are not masked; keywords, titles, other ASR-derived metadata, and body text remain in correction scope.
- Read [references/native_ai_full_workflow.md](references/native_ai_full_workflow.md) in full before performing a native pass. Read the task-specific references named below before their corresponding action.
@@ -114,7 +115,7 @@ Use vocabulary and stakes as the primary tier signals; use length only as a tieb
- Full tier: use fresh-context review. For a single unsplit review, assign one corrected file and require a compact residual table or explicit `no new residuals`; an empty/truncated response is a failed review.
- For a split, multi-file, or resumed Full review, follow [native_review_packets.md](references/native_review_packets.md) for packet assignment, JSON results, validation, and recovery.
- High-stakes multi-recording: a sampled clip settles only that anchored item. If the user asked for a higher-quality or complete transcript and the baseline audio is available, load **`/daymade-audio:asr-transcribe-to-text`** and run its full-file transcription path across the complete clearest/canonical recording before claiming whole-transcript coverage; otherwise report `sampled cross-check only — incomplete`. Prefer a recognizer different from the producer of the canonical body. If only the same recognizer is available, the run proves complete-source coverage but is not independent cross-recognizer corroboration; state that boundary.
9. **Enqueue every unresolved item and open only this file.** Follow `Review queue safety` below and [review_queue_dashboard.md](references/review_queue_dashboard.md). Detection and enqueueing are not correction: for a higher-quality/final claim, every queue row anchored to this exact file must leave `pending`. Start the dashboard with `uv run scripts/review-dashboard/server.py --file "<absolute-canonical-file>"`; add `--item <id>` to land on one fork. If a human is unavailable, keep the artifact explicitly labeled `draft / unresolved — incomplete` and enumerate the rows; do not ship the raw suspect text under a completed quality claim.
9. **Enqueue every unresolved item; escalate selectively.** First apply [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation), then follow `Review queue safety` below and [review_queue_dashboard.md](references/review_queue_dashboard.md). Open only this file when human review is needed. Detection and enqueueing are not correction: for a higher-quality/final claim, every queue row anchored to this exact file must leave `pending`. For human review, start the dashboard with `uv run scripts/review-dashboard/server.py --file "<absolute-canonical-file>"`; add `--item <id>` to land on one fork. If a human is unavailable, keep the artifact explicitly labeled `draft / unresolved — incomplete` and enumerate the rows; do not ship the raw suspect text under a completed quality claim.
10. **Read back the human state, then finalize.** When the human says they marked the dashboard, do not rerun ASR or ask the same questions again. First run `uv run scripts/fix_transcription.py --list-review --review-file "<absolute-canonical-file>" --review-status all --json`, apply any resulting file state, and require `stats.pending_total == 0` for that exact path; zero pending rows is required before the high-quality/final claim. Then diff the file actually edited, run numeric consistency when numbers matter, rerun plain Stage 1, re-grep known corrections, and confirm every change traces to a triage decision. Global queue counts cannot close or reopen this file's quality claim. Last, run `--close-sidecars --input "<absolute-canonical-file>"`: it re-reads every `*_changes.md`/`*_needs_review.md` entry against the file and the queue, refuses while an entry still reads as the original without a verdict or any row is pending, and removes the sidecars only when everything is closed (see `Finalization`).
11. **Compound the learning in the same turn.** Route each stable pattern to its correct home; do not leave confirmed fixes only in chat. Native-pass edits never reach Stage 1's correction history, so harvest them mechanically right after the final diff:
@@ -260,7 +261,7 @@ Numeric-slot scan:
uv run scripts/scan_numeric_consistency.py transcript.md --domain myproject
~~~
Its output is candidates, never automatic edits. For a single load-bearing number, wire the original audio and decide by ear through the review dashboard.
Its output is candidates, never automatic edits. For a load-bearing number, follow [evidence selection and escalation](references/native_ai_full_workflow.md#evidence-selection-and-escalation). When original audio is accessible, use the review dashboard to decide by ear. Otherwise retain competing readings unless other evidence settles them, and continue the remaining items; do not require export permission.
For delegated batches, every agent owns one file, cannot cross-file replace, and returns a residual list. Afterward compare `git diff --name-only` with the explicit file list and inspect every unexpected file under the repository's worktree-safety rules.
@@ -70,11 +70,14 @@ case of ROVER (Recognizer Output Voting Error Reduction, NIST 1997) — worth
knowing by name, because the published work explains why voting across systems
beats improving any one of them. Do not discard a "redundant" second recording
of a meeting you already have; it is a reference transcript for exactly the
values that matter most. If only one recording exists and a number is
load-bearing, settle it by ear through the path this skill already has: wire the
values that matter most. If only one recording exists, a number remains
load-bearing and ambiguous, and the original audio is accessible, use the
transcript's `audio:` frontmatter (see [review_queue_dashboard.md](review_queue_dashboard.md)), enqueue the number as a review item, and press `Q` in the review
dashboard — it plays exactly the anchored utterance, so you hear the digits
spoken instead of re-reading them. For names and terms rather than numbers, a
dashboard — it plays the anchored utterance. If audio is inaccessible, return to
[evidence selection and escalation](native_ai_full_workflow.md#evidence-selection-and-escalation):
use available evidence, preserve unresolved readings, and continue other items.
Do not require export permission or change the value merely to fit domain knowledge.
For names and terms rather than numbers, a
photographed in-room artifact can stand in as the second system — see "In-room
artifacts are another independent engine" below.
@@ -164,6 +167,11 @@ family and authorization are already known. Check those prerequisites before
starting; do not install a model, download weights, or send audio externally just
because this section names the capability.
This is an optional evidence route. If the source cannot be downloaded or the
recognizer is unavailable, return to [evidence selection and escalation](native_ai_full_workflow.md#evidence-selection-and-escalation).
Do not turn this route's prerequisites into prerequisites for the entire correction
task, or require someone to enable exports before other items can be adjudicated.
- **Get the source audio** from whichever channel owns the recording — the
meeting platform's API, the local file the transcript came from, the
recorder's export. `fetch_minute_audio.py` implements one such platform; the
@@ -32,6 +32,57 @@ A recording can be long but still fast-tier (two known speakers, plain language)
**One metadata field is the exception by design: `asr_note`.** When you record a run's correction history in the transcript's frontmatter, use the key `asr_note` — Stage 1 masks its value before matching, so the verbatim old forms your ledger cites (`修正含:<old-form>→<canonical>`) never re-fire as phantom matches, and no phantom review items get enqueued from it. The field stays a full-fidelity ledger; the masking keeps line numbers exact and splices the ledger back untouched. Before this existed, one such ledger line produced 18 phantom Stage 1 matches and 9 phantom queue enqueues on every re-run — a per-file tax that recurred forever. **Two constraints, both load-bearing: the value must sit on the same line as the key (single-line flow style — a multi-line `asr_note: |` / folded value is out of the masker's reach and stays unprotected), and other frontmatter keys are processed normally — keep correction ledgers under `asr_note` and nowhere else.**
### Evidence selection and escalation
Use this branch when a term remains uncertain, a review queue is being resumed,
or a requested media channel is unavailable. Choose evidence that can distinguish
the competing readings; do not make completing a fixed retrieval sequence the goal.
1. **Consume existing decisions first.** A user's explicit correction settles that
occurrence immediately. Resolve its queue row and verify the file edit; if the
anchor moved, use `--reanchor-review` and retry the same verdict. Do not ask for
audio, repeat the question, or rerun ASR to validate what the user just confirmed.
Reusability is a separate decision under the dictionary/identity matrix.
2. **Use the raw conversation as evidence.** Read the complete turn, its responses,
and relevant later mentions. A plausible ASR sound change plus an explicit
same-referent definition, correction, spelling, or unambiguous raw occurrence
can settle ordinary wording or a technical term without audio. Record that
basis as text-supported, not audio-verified. Mere fluency, occurrence counts,
another model's confidence, or a proposed rewrite making more sense are not
independent evidence. Never use an earlier AI edit as its own corroboration.
3. **Check another available source when it would change the verdict.** Reuse an
existing parallel transcript, the meeting's written material, a relevant roster,
or an exact project/tool identifier. A written source can settle spelling or
referent; it does not prove how a word was pronounced. Preserve the person-name
and speaker-identity gates. Do not turn a speaker's factual error or self-correction
into correct technical prose, or change a number, unit, or negation just because
the alternative matches domain knowledge.
4. **Use audio when accessible and useful.** Download through an authorized channel
when available. If export is denied, stop that export attempt and use other
evidence; do not make granting download permission the user's next required
task. Already-authorized playback can help only if the runtime can actually hear
or transcribe the relevant utterance. Seeing a player, captions, or a screenshot
is not hearing audio. Do not bypass export controls, guess another account, or
claim an unavailable listening capability. The clip/recognizer contract in
`advanced_correction_evidence.md` still applies when running that route.
5. **Adjudicate the item, then continue the others.** Apply supported corrections
through their queue rows. Use `kept_original` only when evidence supports the
original reading. Reject a speculative correction without manufacturing proof
that the original was spoken; if its underlying ambiguity remains, keep it
pending with the best candidate and the missing discriminator. A low-impact
unresolved fragment may remain in the draft without interrupting the user.
Ask the user only for a discriminator they possess: an unresolved identity, private
reference, or material wording/number/polarity whose competing readings still matter
after the available checks. Show the original span, candidate, evidence, and exact
missing fact. Do not send the queue wholesale as a checklist of mandatory approvals,
or ask again for a verdict already recorded. State what is resolved and what remains
unresolved; neither permission failure nor queue cleanup establishes transcript quality.
`skipped` is not evidence of a correct reading and must not manufacture a final/clean
claim while substantive uncertainty remains.
### Correction sequence
1. Run Stage 1 (dictionary) on all files (parallel if multiple)
2. Verify Stage 1 — diff against the original. If the dictionary introduced false positives, work from the **original** file instead and apply your edits there. **A false positive here is debt you owe the dictionary**: the same bad rule fires on every future transcript until retired, so the moment you spot one — a rule that turned correct speech wrong, especially "real-word → real-word" rules (both sides are valid-word-shaped, so the non-word guard doesn't catch them; and under `--apply-domain` every matching rule applies regardless of its risk class) — e.g. a `买买→卖卖` rule rewrote a correct "买买工作流" into "卖卖工作流" — disable it in the same session with `--report-false-positive <from_text> <to_text> -d <domain>` — pass the rule's stored from→to pair exactly as Stage 1's `*_changes.md` shows it (the From/To columns) or as it sits in the dictionary, NOT "wrong-word → right-word" semantics. The direction is counter-intuitive for a false positive: the `买买→卖卖` rule stored `from=买买, to=卖卖` (it rewrote a correct 买买 into a wrong 卖卖), so you pass `"买买" "卖卖"` — the rule's stored from→to pair, which is what the tool keys on. One call disables the rule and lowers its confidence (the tool prints "The rule has been disabled"); it will not fire on the next transcript. If the word is genuinely *ambiguous* (correct in some contexts, wrong only here) rather than plain wrong, don't disable the rule — record the disambiguating cue in the domain context file instead. Fixing this transcript while leaving the trap armed guarantees the next one trips it too.
**And when the input already passed through an automated corrector** (a sync pipeline's pre-classify stage, a previous Stage 3 API run), your input is NOT raw ASR — upstream corrections are baked in with no evidence trail. Before triaging, diff against the raw source (the caller's raw transcript — sync engines typically keep one alongside the corrected copy, e.g. `transcript_raw.txt` — or re-pull from the source API). Two things fall out of that diff, in opposite directions: **(a)** every upstream entity swap is itself a suspect in step 4's triage *until its provenance is checked* (below), because an upstream AI "correction" can be a fluent wrong guess — real case: raw ASR 「新的车辆」 was "smoothed" by a pipeline AI into 「新出来的反馈」 (grammatical, plausible, wrong: the speaker said a near-homophone name), and only the raw diff caught it; **(b)** what upstream already fixed correctly is settled — check the diff *before* proposing a fix that's already applied, or you redo work and risk "fixing" a correct form back to a wrong one
@@ -114,7 +165,7 @@ A recording can be long but still fast-tier (two known speakers, plain language)
6. **WebSearch** — only for genuinely public entities (a public-company ticker, a known researcher, a drug name). Skip for anything project-internal.
7. **The audio itself — one clip through a second recognizer.** Every rung above searches *records*; this one returns to the signal. Use it only for a **load-bearing** token that remains unresolved, when source audio is available and the current authorization already permits a second local or external recognizer. Otherwise enqueue or ask once. Cut tight and medium clips, verify the returned text contains the token's neighbouring words, and use an engine from a genuinely **different family** than the transcript producer. Cross-family agreement is strong counter-evidence against an unsupported sound-distant rewrite, not proof: it corroborates the *sound* and never chooses between homophonic spellings or overrides the person-name gate. For an alphabetic token, search exact local authorities first; use WebSearch only after evidence makes the entity public or the user authorizes sending the token externally. Method, timestamp trap, two-re-cut ceiling, result table, and cost boundary: [advanced_correction_evidence.md](advanced_correction_evidence.md) § *One recording, two engines*.
Only after all of these strike out do you ask the user — and by then you've shown the entity isn't already recorded on this machine, which makes the ask legitimate. A confirmed result becomes a Confident fix; if the search *can't* confirm it, it drops to Uncertain. **Batch these**: collect the unique unknowns and run the ladder once per unique entity, not once per occurrence. **When asking for a canonical person name, always preserve an escape hatch outside your shortlist** — include an `Other / none of these` path (or verify the UI supplies one) that accepts the user's exact free-text spelling. A single local occurrence can justify putting a candidate on the list, but not making the list exhaustive: the real canonical may be an English name while every candidate you found is a Chinese transliteration.
Stop the search when available evidence settles the occurrence. If it does not, apply [evidence selection and escalation](#evidence-selection-and-escalation): an unavailable rung does not make the user the automatic next tool, and a question is justified by the missing discriminator and its consequence, not by having exhausted a checklist. A confirmed result becomes a Confident fix; if the search *can't* confirm it, it drops to Uncertain. **Batch these**: collect the unique unknowns and run the ladder once per unique entity, not once per occurrence. **When asking for a canonical person name, always preserve an escape hatch outside your shortlist** — include an `Other / none of these` path (or verify the UI supplies one) that accepts the user's exact free-text spelling. A single local occurrence can justify putting a candidate on the list, but not making the list exhaustive: the real canonical may be an English name while every candidate you found is a Chinese transliteration.
**And when the user answers, their verdict is ✅ authoritative — the strongest source in this whole loop — and it compounds three ways in the same session.** A user who says "X is actually Y (my colleague on team Z)" has handed you a source stronger than any local document. Cash it in immediately: ① apply the fix; ② persist the variant where it compounds — an important recurring person goes to the **people roster** (per [dictionary_identity_and_context.md](dictionary_identity_and_context.md)), a project term or one-off name goes to `--add ... --domain <project>` (the same ASR will mishear the same name again next week); ③ record it in the ledger / roster / domain context with the user's words, the date, and a ✅ "user-confirmed" marker — no later session should re-ask. Two refinements learned the hard way:
- **Collision-check the FROM side before dict-adding.** If the garbled string is itself a real person's name elsewhere in your world (another project's roster holds a *different* real `李明`), a `李明``黎明` dictionary rule will corrupt that person's future transcripts. That fix belongs in the domain context file as a trap with its disambiguating cue ("in editing-team context, `李明` = `黎明`"), never in the dictionary.
@@ -153,7 +204,7 @@ A recording can be long but still fast-tier (two known speakers, plain language)
- **Prefer the smallest edit that explains the error — rank candidates by phonetic distance before you judge any of them.** The rule above bounds how *much* one candidate may change; this one decides *which* candidate wins when several would read fine. ASR errors are small perturbations — the engine maps a heard sound to the nearest word it knows — so among candidates that all make sense, the one changing the fewest phonemes is almost always what was said. Useful fingerprint in Mandarin: **a reduplicated or multi-syllable tail surviving intact while only the leading syllable differs** points at an initial-consonant confusion (retroflex/alveolar `sh`/`s`, `zh`/`z`, `ch`/`c`, and the `n`/`l`, `f`/`h` pairs), so search same-final/different-initial candidates *before* concluding the whole word was misheard.
**Where this fails is not while you generate candidates — it's while you audit text that is already there** (an upstream correction, or a fix you accepted on the first pass). Reviewing existing text puts you in verify-mode: you ask "is this reasonable?", it is, and you move on — never noticing you were handed one candidate rather than a ranked set. A candidate that rewrites three syllables can be perfectly idiomatic *and* be a rewrite; the only thing that separates it from the one-phoneme candidate is that you generated both and compared. So when auditing any already-applied correction, force the question: **is there a smaller edit that also explains this?** If you cannot answer it, you have validated rather than verified. **One priority outranks phonetic minimality: the in-document self-proof — the intended word occurs correctly elsewhere in the passage, the passage is *about* that referent, and the proof occurrences check out against the raw text** (occurrences written by the same upstream pass prove nothing — judge and judged sharing a source is circular). If both candidates occur correctly, the proof is non-discriminating; fall back to phonetic minimality or the queue. And when the fix under audit is rule-backed, a raw-verified self-proof against it *is* the affirmative evidence step 2's revert bar asks for — revert, then retire or scope the rule there. (Real case 2026-08: an already-applied fix read 「完全」 where the raw was 「原全」 — nearest sound, perfectly fluent — but the surrounding exchange said 「全职」 three times in the raw and was *about* 全职; the phonetically smaller candidate was the wrong one, and only the self-proof check caught it.)
A second-pass subagent that returns 8 sharp rows beats one that returns 8000 tokens of narration every time. Task works when you're in the main context; if it truly isn't available — e.g. these instructions are themselves running inside a subagent, which can't spawn another — do one more thorough line-by-line re-read yourself from the corrected artifact. Do not substitute a known-pattern grep and call it a re-read. Never skip the second pass over a missing tool.
7. **Emit a needs-checking list AND enqueue it**the chat summary alone evaporates when the session ends, so every *Uncertain* item gets dual-written: (a) in your chat summary to the human — line number, the original text you left in place, what you suspect, why you couldn't confirm it; (b) into the persistent review queue via `--enqueue-review items.json` (see [review_queue_dashboard.md](review_queue_dashboard.md); item field/alias schema: [script_parameters.md](script_parameters.md) §Review Queue Item Schema — unknown keys are silently dropped, so write `line`, not `line_hint`) with the same fields plus a proposed action pack, so the human can one-keystroke-resolve it later in the dashboard — or a later agent session can close it with new evidence (`--resolve-review ID --decision … --note "<evidence>"`). Entity/name questions get `kind: entity` because a wrong identity has higher business impact and therefore leads the queue; the human verdict still fixes only this occurrence unless the separate reuse matrix admits a dictionary/roster/context update. Pure phrasing doubts get `kind: wording`. If nothing is uncertain, say so. A minimal `items.json` for `--enqueue-review` (one object per uncertain item; `suggested` may be empty when you have no candidate — the dashboard lets a human fill it later):
7. **Record unresolved items AND enqueue them**apply [evidence selection and escalation](#evidence-selection-and-escalation) before treating any item as a human question. The chat summary alone evaporates when the session ends, so every *Uncertain* item gets dual-written: (a) in your chat summary to the human — line number, the original text you left in place, what you suspect, why you couldn't confirm it; (b) into the persistent review queue via `--enqueue-review items.json` (see [review_queue_dashboard.md](review_queue_dashboard.md); item field/alias schema: [script_parameters.md](script_parameters.md) §Review Queue Item Schema — unknown keys are silently dropped, so write `line`, not `line_hint`) with the same fields plus a proposed action pack, so the human can one-keystroke-resolve it later in the dashboard — or a later agent session can close it with new evidence (`--resolve-review ID --decision … --note "<evidence>"`). Entity/name questions get `kind: entity` because a wrong identity has higher business impact and therefore leads the queue; the human verdict still fixes only this occurrence unless the separate reuse matrix admits a dictionary/roster/context update. Pure phrasing doubts get `kind: wording`. If nothing is uncertain, say so. A minimal `items.json` for `--enqueue-review` (one object per uncertain item; `suggested` may be empty when you have no candidate — the dashboard lets a human fill it later):
```json
[
@@ -21,6 +21,11 @@ queue gives all three one persistent home in `corrections.db` (`review_items`),
and the dashboard makes deciding them nearly free — that friction is what stood
between "AI suspects an error" and "the dictionary learns the answer."
A pending row records an unresolved occurrence; it does not assign the work to
the user. Before asking for a verdict, follow [evidence selection and escalation](native_ai_full_workflow.md#evidence-selection-and-escalation).
User-confirmed corrections are applied immediately; media-access failures do not
reopen them or stop independent items.
**Queue CLI** (all support `--json`):
```bash