0014 — The Oracle explains a low-graded Clara turn and holds the fix for a human

Not the "oracle guard." propflowai ADR-0086 uses oracle for the human-approval bit on a generated eval scenario (provenance.approved). This ADR is about the Oracle, Gera's name for the Agent Smith workflow that explains a low-graded Clara turn. The two share a word and nothing else.

The gap

Since 2026-09-07 every Clara turn is graded by a computed number (propflowai turn-grade.ts: fixed deductions over deterministic findings, never a judge's opinion) and rendered as one line per turn in the property's ops thread. A turn below 7 shows its reason. Nobody explains it. The reason line says what fired — "said she would check pricing and availability, then only transferred the call" — and stops. Which layer failed (the prompt, a tool that is not bound, a result she ignored, a policy gate), and what would keep it from happening again, was a human reading three repos.

Gera asked for the thing that does that reading: one more Smith specialty workflow in charge of exactly one job — turning a low-graded turn into a grounded explanation and, when the cause is a prompt or tool gap, one held PR.

Decision

  1. One door. The grading seam (postScorecardIfNew, the one function every detector already reports through) gains an injected onLowTurn hook. It fires inside the per-turn posting loop, after the turn line's Slack ts comes back, when the computed grade is below a named constant ORACLE_THRESHOLD = 8, and only for turns that started within 72 hours (a backfill of days-old rows must not become a storm). The hook's default implementation is a fail-soft Temporal start on the tools-prod namespace — OracleTurnWorkflow, id oracle-<conversationId>-<turnKey>, conflict policy FAIL, 2-second bound, outcome logged (started | already_started | refused:<reason>). It never throws into the seam. There is no polling, no second detector, no cron.
  2. One evidence source. The Oracle may quote only what scripts/oracle-evidence.ts (propflowai) prints. That script rebuilds the turn with the same pure modules production graded it with, reads the persisted grade rows, resolves who spoke (the call's transfer_to_agent results carry the agent ids), reads the tool catalog and the speaker's prompt file at the pinned worktree's HEAD, and prints a byte-deterministic bundle with an explicit quotes[] list. Smith shells it from a worktree pinned to origin/main. No Python port of the turn rebuild exists or will.
  3. The bucket is a table, not an opinion. A decision table over the bundle picks one of prompt gap / missing tool / tool result ignored / policy / grader gap / could not determine, names the fix layer (prompt, tool binding, tool handler, personalization injection, detector, none), and says whether a PR opens (prompt gap, missing tool and grader gap only). Grader gap is the fifth bucket, added by the Architect on 2026-09-08: the check fired on a shape the grader does not follow — today, a sibling hand-off whose promised subject the receiving agent delivered on a later turn of the same call — and the held PR proposes the detector change, not a prompt change. "Could not determine" is a real outcome, posted with the evidence that would settle it. The bucket is decided once, in that TypeScript script; Smith only renders it (a second decider in Python would be a drift the live instrument fails). No LLM writes the finding: it is a template over the bundle, validated before posting — every quoted span a substring of the bundle, no resident name/phone/email/address, no digit run that could be a phone number.
  4. The fix is proposed, never merged. A PR-bearing bucket takes a fleet-scoped work claim keyed on a cause signature (check key | what followed | speaker | fix layer | fix target), resumes an open PR when one exists, and otherwise hands Smith's brain — in #agent-smith, never in the property's thread, so the thread keeps exactly one Oracle reply — a prompt that names the bucket, the target file and the evidence and forbids re-diagnosis. The PR's link reaches the finding by an in-place edit after a workflow-owned wait, never a second reply. The PR carries hold-for-review and auto-merge disabled, both read back; its body names the fleet blast radius (Camellia is the shared registry — a prompt merge deploys everywhere). Two low turns with the same cause yield one PR; the second finding links it.
  5. A nightly line, from rows. The morning queue gains one step, oracle-nightly, that computes per live property the average turn grade, the count below 8 and the delta against the trailing seven days from the persisted grade rows (one bounded GSI3 window, grouped per property) — never from Slack — and counts the turns it could not measure. Full mechanics: 0014-panel-synthesis.md beside this file.

How it relates to the ladder

Consequences

Alternatives considered