Skip to content

feat(ui): developer Home shows what is broken, where, and why - #1175

Open
santoshkumarradha wants to merge 3 commits into
mainfrom
feat/developer-home
Open

santoshkumarradha wants to merge 3 commits into
mainfrom
feat/developer-home

Conversation

@santoshkumarradha

Copy link
Copy Markdown
Member

Why. Users told us Home didn't say when an agent had gone down. Counts stopped at 10,000. Failures were listed per run, not by cause. Nothing showed what was waiting on a person.

Summary

Home now answers three questions: what is broken, where it is, and why. Every number comes from a query over tables the control plane already records. A card for a feature that isn't in use is left out, and an unknown value shows as —.

GET /api/ui/v1/dashboard/home?window=1h|6h|24h|7d        (new; 5 s response cache, 5 min baseline cache)
└─ storage.QueryDeveloperHome                            (SQLite + Postgres)
   ├─ totals, bucketed series, p50/p95        executions in the window
   ├─ baseline ("usual")                      the 7 days before the window
   ├─ problems by cause                       failing execution's own agent.reasoner + normalized message
   ├─ call edges caller → target              self-join on parent_execution_id; calls / failed / held
   ├─ endpoints                               calls, failed %, p50/p95, distinct callers, direct vs trigger
   ├─ approvals waiting                       workflow_executions approval_status = pending
   ├─ spend / webhooks / triggers / forwarder present only when rows or config exist
   └─ QueryBlastRadius(agent)                 entry points whose runs reached an agent that stopped heartbeating

Home page (pages/NewDashboardPage.tsx), top to bottom
1. Status line          N problems · X of Y agents healthy · window 1h/6h/24h/7d
2. Alert banners        fixed rules, each with its rule shown under it
3. Figures vs usual     calls/h, failed %, p95, running now, waiting on people, spend/h (only if usage rows exist)
4. Call map             plain SVG, layered by longest path; failing edges red, held edges amber
5. Problems, by cause   row → an example run
6. Endpoints            row → runs filtered to that reasoner (first 12, "Show all")
7. Waiting on people    approvals → run; agents awaiting tag approval → /access
8. In and out           triggers, failed result webhooks, forwarder drops — each only if used
9. Recent runs
Empty install → only "Connect your first agent" with the SDK snippet

Alert rules. All thresholds are fixed and printed under each banner:

  • Stopped heartbeat. A registered agent that the control plane marked offline, if it heartbeated inside the window or runs went through it. The banner names the entry points whose runs reached it.
  • High failure rate. The failure rate is ≥ 2× usual, is ≥ 5 points above usual, and covers ≥ 20 finished calls, measured against a baseline of ≥ 50 finished calls.
  • Trigger dead letters. The trigger dead-letter queue is above 0.
  • LLM circuit open. Shown only when LLM health monitoring is enabled.
  • Concurrency limiter full. Shown only when the limiter is enabled.

Live refresh. Home rides the existing execution SSE stream. Invalidation is coalesced to at most one refetch every 5 s: the first event refreshes immediately and a burst folds into one trailing refresh. Polling covers agents going silent, which emits no execution event.

Header fix. The header stopped showing "LLM Degraded" when LLM health monitoring isn't configured. The endpoint returns enabled: false, healthy: false in that case, so the header used to contradict Home.

Data fixes

Each fix has a test that fails on main:

Bug on main Cause Now Test (result on main → branch)
Summary "runs today" stops at 10,000 counted rows of a list capped at 10k COUNT over the day TestDashboardSummaryCountIsNotCapped (10000 → 50010)
Enhanced dashboard counts stop at 50,000 counted a 50k sample exact COUNTs; detail_sampled flags sampled panels TestEnhancedDashboardCountsAreNotCapped (50000 → 50010)
Agent shown offline while health=active, lifecycle=ready status came from a live probe follows recorded heartbeat + lifecycle TestDashboardAgentStatusFollowsRecordedHeartbeat (Running 0 → 1), TestRecordedAgentStatus
Incidents ignore the time range picked from the sample newest failures inside the range TestEnhancedDashboardIncidentsAreTheNewestInRange (0 → 10)

Left out, because the data isn't recorded

  • Calls refused because the target was offline. The control plane rejects them with 503 node_unavailable before an execution exists. Home can name the entry points that ran through a stopped agent, but it cannot count the refused calls.
  • Time spent self vs callee, per endpoint. It would have to be reconstructed from overlapping child intervals, which aren't stored as such.
  • "Run N× its usual size." There is no per-run step-count history to compare against.
  • "New paths today." No cheap indexed query exists; it would need a scan of all edges ever seen.
  • Version rollouts per agent. Registrations overwrite the version.
  • Cost per call. Shown only as spend/h, and only when execution_usage rows exist.

Evidence

Before: main's Home, on the same database with order-orchestrator stopped. It shows "10000 runs today" (the real count was 18,000+) and nothing about the stopped agent.

before

After: one agent stopped (dark). The mesh ran for about 45 minutes, then the order-orchestrator process was killed by PID. The banner names the five entry points whose runs reached it. The problems table also shows the child calls that timed out when it died.

after outage dark

After: one agent stopped (light).

after outage light

After: healthy. The agent was restarted. The problems listed are real failures still inside the 1 h window.

after healthy

After: fresh install.

after empty

"usual —" in the screenshots is honest: the mesh was younger than the window, so there is no baseline yet.

Tests

  • go test -tags sqlite_fts5 -count=1 passes on:
    • ./internal/storage/
    • ./internal/handlers/...
    • ./internal/server/...
    • ./internal/services/...
    • ./pkg/types/
  • The storage tests for Home also pass on Postgres (pgvector/pgvector:pg17, POSTGRES_TEST_URL), as /sqlite and /postgres subtests:
    • TestQueryDeveloperHome
    • TestQueryDeveloperHomeEmptyInstall
    • TestCountsAndRecentFailuresFollowTheRange
  • go build ./... and go vet on the touched packages are clean.
  • Frontend checks:
    • npx vitest run: all files pass. New: homeModel.test.ts (16), NewDashboardPage.test.tsx (10), the coalescing test in useSSEHooks.test.tsx, and the not-configured LLM test in HealthStrip.test.tsx.
    • npx tsc --noEmit -p tsconfig.app.json: clean.
    • eslint: clean on touched files.
    • npm run build: passes.

Timing at 250,000 executions (seeded locally, measured against a running server):

Endpoint main SQLite main Postgres branch SQLite branch Postgres
/dashboard/summary 147 ms (capped, wrong) 92 ms (capped, wrong) 45 ms (exact) 30–37 ms (exact)
/dashboard/enhanced 260–337 ms (capped) 277–452 ms (capped) 386–484 ms (exact) 362–521 ms (exact)
/dashboard/home 1h (cold / refresh) — — 511 / 262 ms 306 / 187 ms
/dashboard/home 24h (cold / refresh) — — 642 / 437 ms 443 / 335 ms
/dashboard/home 7d — — ~1.5 s ~1.2 s

/dashboard/enhanced is somewhat slower because it now issues exact COUNTs beside the sample. On the live mesh (~19k calls/hour), /dashboard/home?window=1h answers in about 60 ms.

Merge Danger

Door:

  • The /dashboard route (NewDashboardPage) is rewritten.
  • GET /api/ui/v1/dashboard/home is new.
  • /dashboard/summary and /dashboard/enhanced change how they count, and how they derive agent status and incidents.
  • Five read-only methods are added to StorageProvider.

Blast Radius: Moderate.

  • Anything that implements StorageProvider outside this repo must add the five methods. The in-tree mocks are updated.
  • /dashboard/summary and /dashboard/enhanced now return larger counts than before. Those are the true counts.
  • Agent status in those responses follows the recorded heartbeat rather than a live probe.
  • The old Home components under components/dashboard/ stay, because their tests still import them.
  • No migrations. No writes. No config changes.

santoshkumarradha and others added 2 commits November 9, 2026 14:28
The dashboard summary counted the rows of a list capped at 10,000 and the
enhanced dashboard sampled 50,000, so both stopped counting there. Counts now
come from COUNT queries over the requested range, and the enhanced view says
when its detail panels were drawn from a sample.

Agent status came from a live probe that reported "offline" for agents whose
recorded health was active and lifecycle ready. It now follows the recorded
heartbeat and lifecycle.

Incidents were picked from the sampled rows regardless of the time range; they
are now the newest failures inside the range.

GET /api/ui/v1/dashboard/home?window=1h|6h|24h|7d returns, from indexed
queries over existing tables: totals and a bucketed series, a seven-day
baseline, failures grouped by the failing endpoint and a normalized message,
caller-to-target call edges with failed and held counts, per-endpoint calls,
failure rate, p50/p95, callers and direct versus trigger entry, pending
approvals, and the entry points that ran through any agent whose heartbeats
stopped. Spend, webhooks, triggers and the event forwarder appear only when
they are in use. Tested on SQLite and Postgres.

Assisted-by: CodeAF
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
Home now reads /dashboard/home and shows, top to bottom: a status line, alert
banners from fixed rules (an agent that stopped heartbeating with the entry
points that ran through it, a failure rate well above usual, trigger
dead letters, an open LLM circuit or a full concurrency limiter when those are
configured), figures against their usual level, a call map with failing edges
in red and held edges in amber, problems grouped by cause, an endpoints table,
what is waiting on people, triggers/webhooks/forwarder when in use, and recent
runs. An install with no agents shows only a "Connect your first agent" panel.

Execution events refresh Home at most once every five seconds. The header no
longer reports LLM health as degraded when LLM health monitoring is not
configured.

Assisted-by: CodeAF
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
@santoshkumarradha
santoshkumarradha requested review from a team and AbirAbbas as code owners November 9, 2026 19:30
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 88.10% 87.40% ↑ +0.70 pp 🟡
sdk-go 93.30% 92.00% ↑ +1.30 pp 🟢
sdk-python 94.77% 93.73% ↑ +1.04 pp 🟢
sdk-typescript 92.44% 90.42% ↑ +2.02 pp 🟢
web-ui 85.23% 84.79% ↑ +0.44 pp 🟡
aggregate 86.29% 85.75% ↑ +0.54 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 1006 84.00% ✅
sdk-go 0 — ➖ no changes
sdk-python 0 — ➖ no changes
sdk-typescript 0 — ➖ no changes
web-ui 385 97.00% ✅

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

This branch has not been deployed

No deployments
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