Skip to content

feat(dashboard): standalone "Why" causal-lineage debugging page - #71

Open
saadamjad wants to merge 13 commits into
mainfrom
feat/dashboard-why-page
Open

feat(dashboard): standalone "Why" causal-lineage debugging page#71
saadamjad wants to merge 13 commits into
mainfrom
feat/dashboard-why-page

Conversation

@saadamjad

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds a standalone, shareable "Why" debugging page at /dashboard/debugging/why — a ready-made causal-lineage query so a team using ZizkaDB to log their agents can go from "I found a bad event in the logs" to "here's its full history" without knowing the API by hand.

It wraps the existing GET /v1/events/{id}/why endpoint (already used as a tab inside the agent detail page) into its own agent-agnostic, deep-linkable screen.

Key behaviors

  • One input: an event ID. No parent_id needed — the backend's recursive WHY query walks the parent chain server-side. Optional depth field (default 10, API caps at 50).
  • Deep-linkable / shareable: ?event_id=<uuid>&depth=<n> auto-runs on load, so a trace can be pasted straight into Slack or a bug ticket.
  • CloudWatch-Insights-style visibility: every AgentEvent field is shown per node — event_id, parent_id, agent, event type, full-precision timestamp, session_id, sequence_no — plus the full data payload as expandable JSON. Nothing trimmed.
  • Root-cause-first layout: chain renders oldest→newest (API order); first node badged Origin, searched event badged at the bottom (both badges on the same node when chain_length === 1).
  • Error highlighting: any node whose event type contains "error"/"fail" gets a red Error badge — same heuristic the backend already uses for has_errors in memory.py.
  • Copy/share: per-field copy (event_id/parent_id/session_id), per-node "Copy event JSON", whole-chain JSON, and "Copy link". All clipboard calls wrapped in try/catch (non-secure contexts fail gracefully).
  • Navigation within a chain: parent_id becomes a jump-scroll link when the ancestor is in the loaded chain (plain text when depth cut it off); "Trace this instead →" re-runs WHY from any ancestor.
  • Validation: client-side UUID check before hitting the API; distinct inline errors for invalid-id vs not-found vs network failure — never a blank screen.

Files

File Change
dashboard/app/dashboard/debugging/why/page.tsx New page
dashboard/lib/api.ts getWhyChain gains optional depth param (backward compatible)
dashboard/components/DashboardShell.tsx New "Why" nav entry (desktop + mobile)
dashboard/DASHBOARD_KNOWLEDGE_BASE.md §17.3 endpoint map + new §19.3a per-screen note

No backend changes, no schema changes, no new env vars.

How to test

cd dashboard && npm run lint && npm run build

Then, against a running local stack (bash scripts/setup-local.sh), seed some causally-linked events (run an examples/ agent), grab an event_id, and visit /dashboard/debugging/why?event_id=<id>.

Edge cases covered

  • Event with no parent (chain_length === 1) — single node marked as both Origin and searched.
  • Invalid UUID — blocked client-side with an inline message.
  • Unknown/not-found event — inline "not found" error.
  • Chain with an error-typed node mid-way — red Error badge on that node specifically.
  • parent_id beyond the loaded depth — rendered as plain text, not a broken link.
  • Clipboard unavailable (non-HTTPS) — no unhandled exception.
  • Rapid re-querying — cancelled guard prevents stale overwrites.
  • Mobile viewport — nav entry + responsive layout.

Checklist

  • tsc --noEmit clean, next lint clean
  • npm run build (compiling — slow in this environment; will confirm)
  • No new allow_origins=["*"] combinations — N/A (dashboard-only)
  • Route paths unchanged; no /v1/ renames
  • DASHBOARD_KNOWLEDGE_BASE.md updated in the same PR

🤖 Generated with Claude Code

saadamjad and others added 6 commits July 16, 2026 20:14
Adds a ready-made, shareable causal-lineage tool at
/dashboard/debugging/why — the fastest path from "I found a bad event
in the logs" to "here's its full history."

- User provides only an event_id (no parent_id — the backend's recursive
  WHY query walks the parent chain server-side). Optional depth field.
- Deep-linkable: ?event_id=&depth= auto-runs on load, so a trace can be
  pasted straight into a Slack thread or bug ticket.
- CloudWatch-Insights-style: every AgentEvent field is shown per node
  (event_id, parent_id, agent, event type, full-precision timestamp,
  session_id, sequence_no) plus the full data payload as expandable JSON.
- Chain renders root-first: first node badged "Origin", searched event
  badged at the bottom (both on the same node when chain_length === 1).
- Error nodes flagged with a red badge using the same "error"/"fail"
  heuristic the backend already uses in memory.py's has_errors.
- Copy affordances: per-field, per-node JSON, whole-chain JSON, and a
  "Copy link" for sharing — all clipboard calls wrapped in try/catch.
- parent_id links to a jump-scroll when the ancestor is in the loaded
  chain; plain text when depth cut it off. "Trace this instead" re-runs
  WHY from any ancestor.
- Client-side UUID validation before hitting the API; distinct inline
  error states (invalid id vs not-found vs network).
- New "Why" entry in DashboardShell nav (desktop + mobile).

getWhyChain gains an optional depth param (backward compatible; the
existing agents/[id] call site is unaffected). No backend changes, no
schema changes, no new env vars. KB updated (§17.3 + new §19.3a).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`next build` (the real gate) rejects unescaped ' in JSX text via
react/no-unescaped-entities — "you're" → "you&apos;re". Verified with a
production Docker build of the dashboard against a live local stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframes the Why page around the actual debugging goal: paste an error's
event_id, get the root cause and the full story of how it happened.

- Single input only (the error event_id). Removed the confusing "depth"
  field — the client always requests max depth (50) so the full lineage
  is captured without the user thinking about it.
- Root-cause summary banner: computes the earliest error/fail event in
  the chain as the root cause and narrates the story ("began with X, ran
  N steps, failing at step K — <event>: <message>"), with at-a-glance
  stats (steps, time span, agent, session) and a "traced N parent links"
  caption that explains how the lineage walk works.
- Sequential CloudWatch-style timeline: numbered step dots joined by a
  connector rail, per-step +Δ time delta, and markers — Origin, Root
  cause, Error (cascading), You searched this. Every event field stays
  visible; data JSON expands per step (errors/searched open by default).
- No-error chains show a neutral summary instead of a forced badge.
- Keeps deep-linking, UUID validation, distinct error states, per-field
  and per-node copy, parent_id jump-links, and "trace this instead".

Verified: production build compiles clean (Docker), page serves 200, all
refined logic ships in the bundle, depth removed, and the seeded error
chain (user_message → planning → tool_call → tool_error) traces correctly
via the live API. Docs updated (KB §19.3a).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The step output was crowded — every step dumped five full-UUID rows at
full density. Reworked into progressive disclosure so the story reads
cleanly top-to-bottom and full detail is one click away.

UX:
- Collapsed step is now a clean 3-line summary: event type + role badges,
  a one-line "what happened" (error message, or a compact key/value
  preview of data), and a subtle meta line — agent · seq · timestamp · +Δ.
  All the supporting values the user asked to always see stay visible.
- The noisy bits (full event_id / parent_id / session_id, raw data JSON,
  copy + "Trace from here") move into an expandable Details panel. The
  whole header is the toggle. Error and searched steps auto-expand.
- Softer, more consistent palette and spacing; JSON is height-capped and
  scrolls instead of stretching the card.

Edge cases hardened (all verified against a running stack):
- Root cause = the EARLIEST error in the chain, so cascading downstream
  errors don't mask the true origin (verified: db_error root cause with a
  later retry_failed shown as a downstream Error).
- chain_length === 1 → the single step carries Origin + You searched this.
- No-error chain → neutral summary, no forced badge.
- Friendly, actionable error copy for not-found (404) / network / expired
  session instead of raw API text; invalid UUID blocked client-side.
- Safe date parsing (no "Invalid Date"); missing/empty data, null
  session/parent, and huge payloads all render without breaking layout.
- parent_id jump now also expands the target step; clipboard still guarded.

Verified: Docker production build compiles clean; all scenarios (main
error chain, single root, clean chain, multi-error) serve 200 and derive
the correct banner + per-step badges. Mobile-responsive (stacked meta,
1-col detail grid).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Moves "Why" out of the flat top-level nav into a dedicated Debugging
section, so debugging tools have a clear home (and room to grow — at /
search / memory-diff can slot in later).

- DashboardShell nav now supports NavLink | NavGroup; the desktop sidebar
  renders a "Debugging" section label (Bug icon) with "Why" indented under
  it. Clicking Why opens /dashboard/debugging/why exactly as before.
- The mobile bottom bar can't nest, so it flattens groups to their leaf
  links (mobileLinks) — Why still appears directly there.

Verified: production build compiles clean; the Debugging section + Why
link render in the sidebar and the Why page still loads (HTTP 200). KB
§19.3a nav note updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a second, optional Parent ID input to the Why page and a matching
guard on the backend, for callers who want an explicit "only trace if the
parent matches" assertion (useful mental model for multi-agent / fleet
setups).

Honest scope, reflected in the UI copy: tenant isolation is already the
real boundary (why is WHERE event_id=$1 AND tenant_id=$2, and event_id is
a globally-unique UUID), so this guard is an integrity assertion, not an
access control. It's enforced server-side so it isn't bypassable.

- Backend (core/api/events.py::why): optional `parent_id` query param.
  When present it's UUID-validated and checked against the searched
  event's real parent_event_id; mismatch → 400. Omitted → unchanged
  behavior (backward compatible; SDK + agents/[id] call site unaffected).
- lib/api.ts: getWhyChain gains an optional parentId → ?parent_id=.
- Page: second "Parent ID — optional integrity check" input; deep-linkable
  (?event_id=&parent_id=); client-side UUID check; friendly inline messages
  for mismatch / invalid-uuid; "Trace from here" now carries the target
  node's own parent_id so re-traces keep the guard (origins re-trace blank).
  Left optional on purpose so root/origin events (no parent) stay traceable.

Verified live against the locally-built API: baseline 200, correct parent
200, wrong parent 400, invalid uuid 400, root+parent 400, root alone 200;
dashboard production build compiles clean and all input combos load. Docs
updated (KB §17.3 + §19.3a).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@saadamjad
saadamjad requested a review from Zizka-ai July 17, 2026 08:31
saadamjad and others added 7 commits July 17, 2026 13:36
main's "centralized exceptions" refactor (#28) dropped the direct
HTTPException import from core/api/events.py in favor of
services.exceptions helpers. This branch predated it, so after merging
main the new parent_id guard still called HTTPException → ruff F821 in CI
(which lints the PR merged into main).

Convert the two parent_id 400s to bad_request() and import it alongside
not_found(). No behavior change (bad_request → HTTPException 400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI/UX pass on the Why page based on review feedback:

- Inputs no longer presume an error. "Error event ID" → "Event ID";
  empty state and header copy are neutral ("trace any event's causal
  history") — root-cause language now appears only when an error is
  actually detected (the summary badge/banner remains data-driven).
- Proper field labels ("Event ID", "Parent ID · optional") above each
  input instead of cramming everything into placeholders that vanish on
  type; shorter, clearer placeholders.
- Consistent input styling — both fields now share the same background,
  border, and green focus ring (previously mismatched #111/#0d0d0d and
  different focus colors), for a cleaner, calmer form.
- Primary input (Event ID) is paired with the Trace action; the optional
  Parent ID sits below with a one-line helper, replacing the long
  multi-sentence paragraph.
- Button label simplified "Trace root cause" → "Trace".

Verified: production build compiles clean; page loads (200) and the
parent_id guard still enforces (wrong parent → 400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Label-only rename of the debugging tool — clearer than the abstract
"Why" for a nav item. The route stays /dashboard/debugging/why so
already-shared deep links and bookmarks keep working, and the SDK method
(db.why()), API path (/v1/events/{id}/why), and getWhyChain client keep
the "why" identifier.

- DashboardShell nav label + page <h1> → "Causal Trace"
- KB §19.3a heading updated with a note that the route is unchanged

Verified: production build compiles clean; sidebar + header show "Causal
Trace"; deep link /debugging/why?event_id=…&parent_id=… still loads (200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three verified UX-clarity fixes from the debugging-system review (each
confirmed against the live API):

- Search: the page swallowed the API's "embeddings not configured" 400
  and showed "No results" — misleading. Now surfaces a distinct setup
  state linking to Settings → Embeddings, plus separate network/other
  error states (was: silent empty).
- Time Travel: for agents that don't emit STATE_SET/STATE_DELETE events,
  the API returns state = {_last_event: …}. The UI now detects this and
  explains it ("no key/value state to rebuild — showing the most recent
  event instead") rather than dumping a lone _last_event that looks
  broken; also handles the empty-state case.
- Behavior/baseline: the warming-up and no-baseline states now explain
  what drift tracking does and when it activates, instead of only echoing
  the raw "need N sessions" message.

No API/route/schema changes. Dashboard production build compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full structured report (Phases 1–5, 15 deliverables): per-feature review
(purpose, user journey, backend flow, inputs, outputs), architecture
analysis, UX review, prioritized gap analysis, new-feature specs
(Debugging Hub, Impact Trace, Error Explorer, Ask ZizkaDB), API/DB/
security recommendations, comprehensive testing plan, risks, and a
High/Medium/Low roadmap. Grounded in live verification against the
running stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant