Repository navigation
Let the agent make its case over Radar evidence - #1715
Conversation
PR Summary by QodoFrame Radar evidence with agent roles and ruled-out hypotheses
AI Description
Diagram
High-Level Assessment
Files changed (16)
|
Code Review by Qodo
1. Evidence notes can miss their cards
|
Add evidence and ruled_out to the agent's final JSON: up to eight (ref, role, claim, subject) items and five dropped hypotheses. The server validates each ref against the turn ledger like root_cause_evidence_refs, drops a failing item on its own as unlinked, and binds the case for every assessment including healthy and inconclusive. root_cause_evidence_refs is unchanged. Findings places a claim on a card only when (ref, subject) names exactly one observation of that source; a superseded observation gets the claim on its revision row; anything else renders beside the source in Assessment details. Roles only order the main list (cause, symptom, unlabelled, context, demoted) and add an Agent note; they cannot hide, collapse, or recolor a card. A Ruled out block lists each dropped hypothesis with a link to the placed observation. Uncited cards and hosted runs render exactly as before.
The main-list ordering seeded the role rank with the unlabelled rank, so context and demoted groups never moved below Radar's order. Start from the first item's rank instead and pin the ordering with a test that places a demoted Key card last.
Match a stated resource identity on every discriminator both sides supply: a core resource has a known empty group and a cluster-scoped one a known empty namespace, so a Knative Service subject no longer lands on the core Service card. Bind a claim to the card head only for the group's latest observation; a superseded read keeps its own revision row even when it displays the same, and bound rows survive display folding. Promote a broader group for a subject-less item only when the cited call was scoped to one resource. Open the collapsed collections before revealing a ruled-out target, dedupe ruled-out entries, list an earlier assessment's placed claims read-only with their observation, and keep the legacy source-row markup untouched when no case is present.
An agent stating group "" or namespace "" means core group or cluster scope; omitempty dropped that on the wire and the frontend read it as a wildcard, so the subject's group and namespace are pointers. Parse each ruled_out entry on its own so one malformed entry cannot discard its siblings. Treat a metrics subject's group as known, open history for a bound revision row even when it displays like the card, and hide a ruled-out entry whose card is withheld.
A diagnose bundle captures up to three vitals charts for one resource, so a subject naming the resource with observation "metrics" matches all of them and falls to the source row. Let observation carry a category qualifier (metrics:cpu, metrics:memory, metrics:restarts) that selects the chart whose identity ends in it, tell the agent about it in the prompt, and pin the promotion of a diagnose-origin chart into main with a test.
A follow-up answer that cites evidence never reached Findings: the pane only resolved the current assessment's case, so an answer to "chart this and cite it" left its chart in the collapsed workload collection. The newest completed non-apply, non-explanation turn with a bound case or linked root-cause refs now drives selection and chips; the assessment's own items and earlier answers list read-only, and an answer shows its sources under the reply. Placement also accepts the patterns a live agent used: a log stream named by the diagnosed workload plus container and stream, container or stream on a non-log observation when the evidence kind is stated, a category qualifier ignored on an agent-run query chart, and an observation with no resource of its own named by its evidence kind.
The healthy-conflict banner points at supporting-tier warning and error cards, but only Key-tier cards carried a coloured rule, so the evidence the banner named looked neutral. Supporting cards now get a 2px semantic warning or error rule; the predicate is unchanged, and a test checks that every card it fires on is visibly marked.
The sources block under Full analysis is provenance: one row per cited tool result with its scope and a View source action. Role chips no longer sit on source rows, notes already shown on evidence cards are counted rather than repeated, and the notes that have no card are nested under their source with the observation they refer to. When the agent placed a less-relevant or rules-out note on every card that would trigger the healthy-conflict banner, the banner now says the evidence was explained and names the cards instead of reading as a contradiction. Any unexplained conflict keeps the amber warning.
The agent marks identifiers with backticks in its one-sentence notes and the note showed the backticks literally.
âĶcase A follow-up that cites one chart became the pane's case and every note the initial assessment had placed on other cards disappeared with it, so the healthy-conflict banner accused a logs card the agent had already marked less relevant. Card- and revision-placed notes from earlier turns now carry over unless the live turn addressed the same card; ordering, the ruled-out list and source-placed notes still belong to one turn.
d21f278 to
fa59e11
Compare
A healthy verdict that contradicts captured adverse evidence raises Radar's own warning. The agent could switch that warning off: labelling each conflicting card `demoted` or `rules_out` turned it into a blue "no active problem" notice. The label needed no explanation text, could be pinned to a superseded read of the card, and could come from a later answer or an assessment that had already been replaced. An explanation now reframes the warning instead of replacing it. It stays amber, still says the evidence is there, and points at the notes; it never claims a severity tier Radar did not check. To reframe it at all, a note must belong to the assessment on screen, carry text, sit on the card's current state, and not be contradicted by a `cause` or `symptom` note from the same assessment on the same card. None of these judge whether the agent is right, which is exactly why the warning stays a warning. Three neighbouring defects in the same lifecycle: - A follow-up answer replaced the displayed assessment's evidence resolution, so the assessment lost its "does not cite specific Radar evidence" qualification and evidence it had cited could fall back into the withheld count. A later turn's citations now widen the selection and nothing else; provenance stays with whoever authored it. - Carrying a superseded assessment's notes took coverage of a group per item, so only the first note on a group survived. The newest assessment that spoke about a group now carries everything it said about it. - A claim on a card the pane withheld was counted as "on an evidence card" and suppressed from the source row, so it rendered in neither place. The receipt now follows what actually rendered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
âĶ a merge Two defects in the previous commit's own lifecycle handling. The conflict banner asked whether the assessment's qualifying note survived into the case the pane renders, and asked by object identity. The pane feeds the merge freshly resolved copies of every turn, so the assessment's items arrive equal but not identical: any unrelated follow-up made the banner conclude its own note had vanished and drop back to the unexplained warning. Survival is now decided by value, and the merge reuses the assessment's own resolution rather than resolving it a second time. Reducing a later turn's citations to group ids also lost the source that cited them. Selecting a broader group is not enough to show it â the broader gate needs to find who cited it, and with no source it withheld a card the follow-up had just cited. The citation now travels with its source, while provenance markers stay with the assessment that authored them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
6eeef59 to
8fa77cd
Compare
Omitting a subject asks Radar to place the note wherever the cited call produced. Supplying one that does not validate asked for something specific, and normalising it to "omitted" put the claim on whichever observation happened to be the only one that call made. Such an item now stays beside its source. docs/mcp.md said the server emits `evidence` and `ruledOut` on every assessment. Binding runs on every assessment; the fields are `omitempty`, so a diagnosis with no bindable case carries neither. Consumers are told to treat both as optional. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
The live answer's source receipt was built from the merged case, so it listed notes the merge had carried from earlier assessments as though that answer had cited them â the same notes already shown on their cards and again under the assessment that wrote them. It now reads the live turn's own case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
The role travelled with the card title, in the same slot as Radar's own kind and status badges, while only the sentence beneath it was marked as the agent's. A reader could take "Secret dev/cloud-provisioner · Cause" for Radar having established causality. Sparkle, role and sentence now share one row, so the label is as plainly the agent's reading as the sentence is. A role the agent gave without a sentence still renders that row rather than vanishing. A rules-out card names what it excludes. "Rules out" alone made the reader find the hypothesis in a separate block and match it back by hand; the chip now reads "Rules out: The Atlas user was deleted", and only for the role that actually excludes something. The ruled-out block moves under the Assessment. It answers why this conclusion over the alternatives, so it belongs with the conclusion rather than after the evidence list among the withheld counts, where it read as bookkeeping. A card the agent calls the cause takes a 3px accent rule â but only where Radar left that slot empty. A cause is often a Secret or a ConfigMap that Radar has no reason to colour, so it sorted first while every card echoing the failure below it carried red. Severity still outranks the agent's framing: where Radar coloured the card, Radar's colour stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
Five roles had no way to say that adverse-looking evidence does not indicate an active problem. `rules_out` excludes some other hypothesis and `demoted` says a card is peripheral â both true of a problem that is still happening â so an agent calling a workload healthy over Radar's contradicting evidence reached for whichever role was nearest. On a live run it labelled a memory chart `symptom` while its own sentence used that chart to rule memory out. `benign` says exactly that and nothing else, and it is now the only role that lets the healthy-conflict banner reframe. The other guardrails are unchanged: the note must belong to the assessment on screen, carry text, sit on the card's current state, and not be contradicted by a cause or symptom note on the same card. A rules-out card names the hypothesis it excludes as text beside the chip rather than inside it â a hypothesis long enough to be useful makes a badge that swallows the row. It renders only for `rules_out`, where the chip frames it as rejected: a hypothesis is something the agent DISPROVED, so on a card labelled anything else it reads as a statement of fact. The card the agent calls the cause lifts its surface instead of taking a coloured rule. The left rule is Radar's severity and the ring is the source you are viewing; both were already spoken for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
â Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4fedf9e. Configure here.
`benign` was added to the prompt and to the label table but never to the parser's accepted set, so every benign item was discarded as unlinked. The chip would never have appeared and the healthy-conflict banner could never have reframed â silently, with nothing anywhere saying why. A test now walks every role the prompt offers and fails if the parser rejects one. The card's note block was still gated on an item having a claim, so a role the agent gave without a sentence rendered nothing at all: the note itself learned to handle that case when the chip moved off the title, but the block around it did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
Two things stopped a healthy verdict from ever being reframed, and both were invisible. The prompt named the role but never asked the agent to cite the evidence it applies to. On a live run the agent annotated the results that looked fine and never cited the one Radar was warning about, so the warning stood over an assessment that had, in prose, addressed it. It is now told that Radar checks its verdict against what was captured, which results count as active-problem evidence, and that leaving one uncited keeps the warning â which is the right outcome when it has not explained them. `observe` then keeps one log stream read through two different calls in separate groups, so a same-named pod from another workload cannot inherit the target's relevance by colliding on a display identity. That partition is worth keeping, but it was also deciding whether the agent had addressed the stream: the agent explained one card and the conflict was recorded on its twin. The conflict predicate now treats groups of one kind sharing an identity as the same observation for that question, and nothing else changes. Verified end to end against a live EKS workload: the agent cites the restart and the previous-instance logs, marks both benign with its reasons, and the banner reframes to "Agent reports no active problem; adverse evidence remains" â still amber, still naming the evidence, with the coverage limitation intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
A sparkle, an accent-toned chip and the words "Agent's note:" all said the same thing on every annotated card, and the reader paid for it in row width on the densest surface in the product. The sparkle now sits inside the chip, so a single badge is the agent's mark and the sentence follows it directly. The words carried attribution where colour and iconography do not reach, so they stay as screen-reader text â "Agent:" rather than "Agent's note:", because a role can arrive with no sentence and nothing may announce a note that is not there. The role vocabulary does some of this work by itself: Radar writes CrashLoopBackOff and 0/1 replicas ready, never Symptom or Rules out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
Centring the badge box dropped the label about a pixel below the text beside it, because padding and border make the box taller than the line; aligning to the badge's own baseline overshot by the same amount, since that baseline comes from the sparkle rather than from the label. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW
Two doc comments still described the earlier rule, where a "less relevant" or "rules out" note could qualify the healthy-verdict banner. Only benign can. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125QDX24TNuod7FKLwTDhvW

Supersedes #1691, whose content merged into the
feat/findings-next-layerintegration branch and so never reachedmain.Summary
The investigation agent can now make its case on top of Radar's evidence instead of beside it. Its final JSON carries up to eight
(ref, role, claim, subject)items and up to five ruled-out hypotheses; Findings renders a role chip and a one-sentence agent note on the card each item names, orders the main list by role, and lists ruled-out hypotheses under the assessment they argue for.The boundary is the point. The agent can order and frame. It can never author a card, hide one, collapse one, or change its tone, and it can never turn Radar's own contradiction warning into an all-clear. Every card still comes from a tool result Radar recorded.
What changed
Go (
internal/ai)evidenceandruled_outalongside the unchangedroot_cause_evidence_refs, with instructions on the six roles (cause,symptom,context,benign,demoted,rules_out), one-sentence claims, and subjects for results that fan out into several observations.parse.goreads both arrays with their caps (8 items, 5 hypotheses, 200-char claims). A malformed item stays at its index as invalid rather than being dropped, soevidence_indexkeeps pointing where the agent meant.runs.goshares one turn-ledger match table between the legacy root-cause binder and the new case binder. A failing item becomesstatus: "unlinked"on its own, without taking the others down; binding runs for every assessment, healthy and inconclusive included. Subject and claim are copied verbatim as untrusted text.A word for "this is not a live problem"
benignis the only role that says an adverse-looking result does not indicate an active problem. "Excludes a hypothesis" (rules_out) and "is peripheral here" (demoted) are different statements, both of which stay true of a problem that is still burning, so neither reconciles a healthy verdict with evidence contradicting it.Because the distinction only pays off if the agent knows to reach for it, the prompt states the consequence: when it sets
healthy=true, Radar compares the verdict against what it captured and warns the operator about any active-problem evidence left unaddressed, so it must cite each such result and mark itbenignwith the reason â a failing probe, a restart or crash, an unready workload, an error or panic in a log stream, a Warning event, a failed DNS or network check. Citing only the results that look fine leaves the warning standing, which is the correct outcome when the adverse ones have not been explained.Placing a claim (
investigationCase.ts)Each item resolves to exactly one observation by
(ref, subject). Every discriminator both sides state must match â a core resource has a known empty group, a cluster-scoped one a known empty namespace, and producer-unstated fields are wildcards. Exactly one match pins the claim: on the card head for the group's latest observation, on its own revision row for a superseded read. Anything else renders beside the source in Assessment details rather than guessing.Omitting a subject and supplying a broken one are different requests. Omission asks Radar to place the note wherever the cited call produced; a subject that does not validate asked for something specific that does not resolve, so the item stays at its source instead of landing on whichever observation happens to be the only one.
The pane (
InvestigationEvidencePane.tsx)causecard sits on the elevated surface with a shadow, so the card the agent argues from carries visual weight without borrowing a severity colour it was never assigned.AssessmentSourcesrows show roles and any source-placed claim; earlier assessments list their placed claims read-only with the observation each was bound to. A claim whose card the pane withheld is shown here rather than counted as visible on a card, so it is never absent from both.Radar's own warning stays Radar's
A healthy verdict that contradicts captured adverse evidence raises Radar's warning, computed from the evidence alone. An agent explanation reframes that warning; it never replaces it with reassurance. The banner stays amber, still states that Radar captured evidence of an active problem, points at the notes, and says the evidence is unchanged in the list below. It never names a severity tier, because none was checked.
To reframe it at all, a note must clear five deterministic conditions. None judges whether the agent is right â which is exactly why the warning remains a warning:
benign, the one role that asserts the evidence is not a live problem;causeorsymptomâ self-contradiction is not an explanation.Every conflicting card must qualify, and the qualifying note must still be one the pane renders.
observe()deliberately partitions a log stream read through two different calls into separate groups, so a same-named pod from another workload cannot inherit the target's relevance; that partition must not also decide whether the agent addressed the stream, so groups sharing a kind and identity count as the same observation for this question.Whose claim is whose, across turns
Presentation
Everything the agent contributed to a card lives in one attributed row beneath Radar's fact: a single badge carrying a sparkle and the role, then the sentence, sharing the sentence's baseline. The role travelled with the card title once, where it sat in the same slot as Radar's own badges and could be read as Radar having established it. A
rules_outnote carries the hypothesis it excludes as text beside the chip â "Rules out" alone forces the reader to find the hypothesis further down and match it back â and only for that role, since a rejected hypothesis stated as bare fact on any other card reads as a claim the agent believes.Badgegains a muted-accentagenttone (documented in DESIGN.md) â never a severity hue, so agent framing cannot read as a Radar finding.docs/mcp.mddescribes the case fields, notes thatevidence/ruledOutareomitemptyand must be treated as optional, and records that a hosted backend sending noevidencerenders exactly as before.Testing
make tscâ ·go test ./...both modules â ·web1078 tests across 88 files â ·packages/k8s-ui3603 tests across 178 files â ·git diff --checkclean.investigationCase.test.tsxandinternal/ai/case_test.go, plus additions toinvestigationState.test.ts,parts.test.tsxandInvestigationEvidencePane.test.tsx. They cover role parsing and caps, malformed items holding their index, per-item unlinking, a malformed subject staying at its source, subject discrimination on group and namespace, placement on head vs revision row vs assessment details, role ordering, every condition on the conflict banner, carrying all of a winning assessment's notes, a follow-up widening selection without removing any, and a withheld claim falling back to its source row.benign: the banner stays amber and still points at the evidence, which remains in the list unchanged.Scope and tradeoffs
observationaccepts a category qualifier (metrics:cpu,metrics:memory,metrics:restarts) to name one. The baremetricsstill works when the bundle captured a single chart. The prompt tells the agent so.Open product questions
Unresolved taste calls, not defects:
rules_outis a role name in the wire contract and "Rules out" on screen. It reads as jargon next to "Not a problem" and "Less relevant"; a better word has not surfaced.demotedrole renders as "Less relevant" â keep that label, or name the role?Note
Medium Risk
Changes diagnosis parsing, server-side evidence binding, and complex multi-turn Findings logic where agent framing must not be mistaken for Radar authority; extensive tests but high interaction surface for investigations.
Overview
Investigation diagnoses can now include an agent case: up to eight
evidenceitems (ref, role, one-sentence claim, optional subject) and up to fiveruled_outhypotheses tied to evidence indices. The Go layer parses and caps this JSON, then binds each ref through the same turn-ledger rules asroot_cause_evidence_refs; failed items becomeunlinkedwithout dropping siblings. The prompt and explanation flow are extended so roles/claims are read-only in follow-up explanations.Findings resolves each item to a single observation when possible and renders agent role chips and claim notes on cards (or on revision rows / Assessment details when placement is ambiguous). Main evidence ordering follows roles; a Ruled out block links to the contradicting observation. Selection still cannot hide or recolor Radar cards; a
benignnote on every conflicting card can reframe (not remove) the healthy-vs-adverse warning. Follow-up turns widen cited evidence; earlier assessmentsâ card notes merge unless a later turn addressed the same group.Adds
Badgetoneagent, API/types,investigationCaseresolver, and docs inDESIGN.md/docs/mcp.md. Hosted backends with noevidencebehave as before.Reviewed by Cursor Bugbot for commit d8531a6. Bugbot is set up for automated code reviews on this repo. Configure here.