fix: her evidence comes from the trace, not from a form field

Case 58325 died submitting findings: the submit_decision call arrived with
no `data` at all. The final model reply was exactly 4,096 output tokens —
the service's cap — so the JSON was cut off mid-call. Asking Ava to paste
16 full SQL statements into sql_log, on top of a summary, root cause, five
recommendations and a scanned-summary, is what pushed one tool call past
the limit and lost the whole investigation.

The platform already records every statement the agentic tool sends,
verbatim, with Databricks' own SUCCEEDED/FAILED and the row count. So
SqlEvidence now renders from the trace and treats sql_log as a one-line
index per step. Her charter gained hard per-field character limits to
match (sm2/custom-apps/denials-investigator/05_ai_employee.sql).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-16 13:56:52 +05:30
parent e294df6eba
commit 92ea27d27e
2 changed files with 51 additions and 19 deletions

View File

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { AlertTriangle, CheckCircle2, ChevronDown, FileSearch } from 'lucide-react'; import { AlertTriangle, CheckCircle2, ChevronDown, FileSearch } from 'lucide-react';
import { Badge, Card, Facts } from './core'; import { Badge, Card, Facts } from './core';
import type { Row } from '../api/types'; import type { AvaTraceStep, Row } from '../api/types';
import { import {
firstDeadline, firstDeadline,
headline, headline,
@ -148,11 +148,22 @@ export function FullStory({ row }: { row: Row }) {
); );
} }
/** Every SQL statement she ran. For the sceptic in the room. Collapsed. */ /**
export function SqlEvidence({ row }: { row: Row }) { * Every SQL statement she ran. For the sceptic in the room. Collapsed.
*
* SOURCE OF TRUTH IS THE TRACE, not her sql_log field. The platform records
* every statement the agentic tool sent, verbatim, with Databricks' own
* SUCCEEDED/FAILED and the row count — so that is what is shown. Her sql_log
* is now a one-line-per-step index ("3c: department cut -> Cardiology +13
* pts") and is used only as the note beside each statement. Asking her to
* paste 16 full queries into a form field was what blew the tool call past
* the output cap and lost a whole investigation.
*/
export function SqlEvidence({ row, steps }: { row: Row; steps: AvaTraceStep[] }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const steps = sqlSteps(row.sql_log); const queries = steps.filter((s) => s.type === 'api' && s.detail);
if (steps.length === 0) return null; const notes = sqlSteps(row.sql_log);
if (queries.length === 0 && notes.length === 0) return null;
return ( return (
<Disclosure <Disclosure
@ -160,19 +171,40 @@ export function SqlEvidence({ row }: { row: Row }) {
onToggle={() => setOpen(!open)} onToggle={() => setOpen(!open)}
icon={<CheckCircle2 className="h-4 w-4 text-good" />} icon={<CheckCircle2 className="h-4 w-4 text-good" />}
title="Her evidence" title="Her evidence"
hint={`${steps.length} database queries, exactly as she ran them — anyone can re-run these`} hint={
queries.length
? `${queries.length} database queries, exactly as she ran them — anyone can re-run these`
: `${notes.length} steps, in her own words`
}
> >
<ol className="divide-y divide-line"> {queries.length > 0 ? (
{steps.map((s, i) => ( <ol className="divide-y divide-line">
<li key={i} className="px-5 py-3"> {queries.map((q, i) => (
{s.label && ( <li key={`${q.seq}-${i}`} className="px-5 py-3">
<p className="text-[11px] font-semibold tracking-wide text-faint uppercase">{s.label}</p> <p className="flex items-center gap-2 text-[11px] font-semibold tracking-wide text-faint uppercase">
)} Query {i + 1}
<pre className="sql mt-1 text-muted">{s.sql}</pre> {q.query_state === 'FAILED' ? (
{s.note && <p className="mt-1.5 text-xs text-ink">{s.note}</p>} <Badge tone="amber">error — corrected next</Badge>
</li> ) : q.rows_returned != null ? (
))} <span className="tnum font-normal normal-case">{q.rows_returned} row{q.rows_returned === 1 ? '' : 's'}</span>
</ol> ) : null}
</p>
<pre className="sql mt-1 text-muted">{q.detail}</pre>
{notes[i]?.note && <p className="mt-1.5 text-xs text-ink">{notes[i].note}</p>}
</li>
))}
</ol>
) : (
<ol className="divide-y divide-line">
{notes.map((s, i) => (
<li key={i} className="px-5 py-3">
{s.label && <p className="text-[11px] font-semibold tracking-wide text-faint uppercase">{s.label}</p>}
<p className="mt-1 text-sm text-ink">{s.sql}</p>
{s.note && <p className="mt-1 text-xs text-muted">{s.note}</p>}
</li>
))}
</ol>
)}
</Disclosure> </Disclosure>
); );
} }

View File

@ -167,7 +167,7 @@ export function CaseScreen() {
{hasFindings && ( {hasFindings && (
<> <>
<FullStory row={row} /> <FullStory row={row} />
<SqlEvidence row={row} /> <SqlEvidence row={row} steps={trace.data ?? []} />
{!aiOwned && ( {!aiOwned && (
<Disclosure <Disclosure
open={showWork} open={showWork}
@ -185,7 +185,7 @@ export function CaseScreen() {
)} )}
{quiet && ( {quiet && (
<> <>
<SqlEvidence row={row} /> <SqlEvidence row={row} steps={trace.data ?? []} />
<Disclosure <Disclosure
open={showWork} open={showWork}
onToggle={() => setShowWork(!showWork)} onToggle={() => setShowWork(!showWork)}