Human Signals

Data-driven dashboard for human-in-the-loop metrics — track interventions, escalations, sentiment, and operational patterns across your AI agents.

Why Use Human Signals?

Automated evaluation scores tell you how well your AI performs. Human Signals tells you what happens when humans step in — the overrides, escalations, sentiment shifts, and learnings that only surface through real operator interactions.

Intervention Tracking

See when and why humans override AI decisions — correction, escalation, edge case, or full manual takeover.

📊 Dynamic KPIs

Auto-generated KPI cards with sparklines. Click any KPI to expand its weekly trend chart.

🔍 Config-Driven Charts

Bar, donut, stacked bar, horizontal bar, ranked list, and single stat charts — all driven by your display config.

💬 Full Conversation View

Drill into any case to see the complete chat thread, signal values, learnings, and feature requests.

Quick Start

Get human signals data loaded in under two minutes:

1

Navigate to Human Signals

Click Human Signals in the left sidebar. If no data is loaded, you'll see the upload screen.

2

Upload CSV or Connect Database

Drag a CSV file into the upload zone, or configure config/human_signals_db.yaml for automatic database import. The expected format uses metric_name, dataset_id, signals, conversation, and timestamp columns.

3

Explore Your Dashboard

Once data loads, the dashboard populates with KPI cards, trend charts, distribution charts, and a case table. Use the time range selector and filters to slice by source, environment, or metric values.

💡 Tip
For production deployments, configure human_signals_db.yaml with auto_load: true. The sync engine will pull data from your PostgreSQL database automatically on page load, using the DuckDB pipeline for fast queries.

Page Anatomy

Here's how the Human Signals dashboard is organized, with every major section labeled:

localhost:3500/human-signals

Human Signals

Human-in-the-loop insights and operational metrics

1
Filters 2
Clear
Last 30 days
2
13.6%
AI Success Rate
68.0%
Resolution Rate
41.3%
Override Rate
18.2%
Escalation Rate
22
Extracted Slack Threads
3

Signal Trends Over Time

100% 75% 50% 25% 0% Intervention Rate Resolved Rate Escalation Rate
4

What AI Did Well

Outcome Analysis

Positive Neutral Negative Mixed
5
Cases (247 total)
Columns 7/12
Case IDSourceInterventionSentimentFailed StepTimestamp
CS-001 alpha_bot correction neutral retrieval Jan 28, 2026 👁
CS-002 alpha_bot escalation negative generation Jan 27, 2026 👁
6
The Human Signals dashboard showing the full page anatomy — header, filters + time range, KPI strip, charts, and case table.
1
Page Header — MessageSquareText icon with title "Human Signals" and subtitle describing the page purpose.
2
Filters + Time Range — Collapsible filter panel with source and metric dropdowns inline with the time range selector. Badge shows active filter count. Clear button resets all filters.
3
Dynamic KPI Strip — Grid of 2–5 cards, each with an icon, value, optional sparkline, and label. Click any card with a sparkline to expand a weekly trend chart below. Supports boolean rates, string match rates (match_value), numeric aggregations, and aggregates.
4
Signal Trend Chart — Collapsible multi-line time-series chart plotting signal rates by week. Starts collapsed.
5
Chart Sections — Config-driven chart grids (bar, donut, stacked bar, horizontal bar, text list, ranked list, single stat). All sections are collapsible and start collapsed by default.
6
Case Table — Paginated table with column picker, sort, and eye icon to open the detail modal. Colored badges are controlled by table_badge_columns in the YAML config.

KPI Strip

The KPI strip auto-generates cards from your display_config.kpi_strip configuration. Each card shows a metric icon, a large value, an optional sparkline, and a descriptive label.

KPI Strip with expanded trend
247
Total Cases
13.6%
AI Success Rate
68.0%
Resolution Rate
41.3%
Override Rate
18.2%
Escalation Rate

AI Success Rate — Weekly Trend

×
90% 50%
Clicking a KPI card with a sparkline expands a detailed weekly trend chart below the strip. Click the X to close it.

KPI card types:

  • Aggregate KPIstotal_cases (count). No sparkline.
  • Boolean rate KPIs — percentage of cases where a boolean signal is true (e.g., resolution rate, escalation rate). Includes a sparkline and is clickable to expand a trend chart.
  • Match value KPIs — percentage of cases where a string signal equals a specific value. Set match_value in the config (e.g., match_value: "success" to count cases where analysis_mode == "success").
  • Numeric aggregation KPIs — aggregated numeric signals using aggregation (mean, median, sum, min, max, p95). Supports duration and compact formats.
ℹ️ Info
KPI cards are configured in signals_metrics.yaml under the kpi_strip key. Each entry specifies label, icon, metric, signal, format (percent, number, duration, compact), and optionally match_value, aggregation, and highlight.

Filters & Time Range

The filter bar sits at the top of the dashboard with the time range selector inline on the right. Click the bar to expand the filter panel, which shows a responsive grid of dropdown selectors.

Two filter categories:

  • Source filters — filter by source_component and environment (single-select). Only appear if your data contains those fields.
  • Metric filters — filter by signal values like override type, sentiment, escalation type (multi-select with checkboxes). Only appear for signals with 8 or fewer unique values.

The active filter count badge shows how many filters are currently applied. The Clear button resets all source and metric filters. The time range selector offers presets (7d, 30d, 90d, 6mo, 1yr) and a custom date range picker.

📝 Note
Filters apply globally — KPI cards, charts, trend lines, and the case table all update together when you change any filter or time range.

Chart Section

The chart section contains two areas: the Signal Trends time-series chart and the config-driven chart sections with various visualization types. All sections are collapsible and start collapsed by default to keep the page compact.

A collapsible multi-line chart plotting signal rates as weekly percentages. Each line represents a KPI that has format: percent in the display config. The chart automatically extracts trend signals from your KPI configuration. Click the section header to expand.

Chart Types

Below the trend chart, chart sections render in configurable grid layouts (full, grid_2, or grid_3). Each section has a collapsible title with a chevron toggle. The available chart types are:

Chart Types

Intervention Type (Bar)

correction escalation edge_case manual

Sentiment (Donut)

247 total

Failed Step (Horizontal Bar)

retrieval 38% generation 25% reasoning 18% none 15%

Override Breakdown (Stacked Bar)

40% 25% 20% 15% full_override partial_edit no_change other

Top Learning Categories (Ranked List)

1. Process Improvement 34
2. Knowledge Gap 28
3. Edge Case Handling 19
4. Prompt Tuning 12

Actionable Feedback (Single Stat)

58.3%
144 of 247 cases
Six chart types: bar, donut, horizontal bar, stacked bar, ranked list, and single stat. All driven by the display_config.chart_sections configuration.
Chart TypeConfig ValueBest For
BarbarComparing category counts (intervention types, failed steps)
DonutdonutProportional breakdowns (sentiment distribution)
Horizontal Barhorizontal_barRanked categories with long labels
Stacked Barstacked_barPart-of-whole comparisons
Ranked Listranked_listTop-N frequency lists (learning categories, feature requests)
Single Statsingle_statBoolean rate as a single large number (actionable feedback %)

Case Table

The full paginated case table appears below the charts. Each row represents one aggregated case (one dataset_id) with all its flattened signal values.

Case Table
Cases (247 total)
Columns 7/12
Case ID Source ↕ Intervention ↕ Sentiment Failed Step Messages Timestamp ↕
CS-001-abc alpha_bot correction positive retrieval 12 Jan 28, 2026 👁
CS-002-def alpha_bot escalation negative generation 8 Jan 27, 2026 👁
CS-003-ghi beta_bot edge_case neutral 5 Jan 26, 2026 👁
CS-004-jkl alpha_bot manual positive reasoning 15 Jan 25, 2026 👁
1–10 of 247
1 2 3 25
Case table with column picker, sortable headers, color-coded signal badges, pagination, and page size selector.

Table features:

  • Column Picker — click the "Columns" button to show/hide columns. Defaults to 7 of the most important columns.
  • Sortable columns — click any column header with ↕ to sort ascending, descending, or reset. Uses lexicographic comparison with numeric awareness.
  • Color-coded badges — signal values render as colored badges using color_maps from your display config. Control which columns show badges with the table_badge_columns list in signals_metrics.yaml. Omit the list to badge all mapped columns.
  • Pagination — configurable page size (10, 25, 50, 100). Page pills with a 5-page sliding window around the current page.
  • Eye icon — click to open the Case Detail Modal.

Case Detail Modal

Click the eye icon on any case row to open a full detail view. The modal shows every signal value, metadata, the complete conversation thread, learnings, and feature requests.

Case Detail Modal
CS-001-abc alpha_bot
×
correction positive resolved medium
🕒 Jan 28, 2026 14:23 💬 12 messages 🏷 Acme Corp 🏷 alpha_bot
Intervention Type
Intervention Type
correction
Has Intervention
Yes

Suggested Action

Update retrieval pipeline to include product catalog entries for edge-case SKUs.

Learnings 3
Full Conversation 12
Close
Case detail modal showing case ID, source badge, status badges, metadata, metric signals in grouped sections, suggested action, and expandable learnings and conversation.

Modal sections (top to bottom):

  1. Header — Case ID with source badge and close button
  2. Slack Link — purple button linking to the original Slack thread (if message_url is present in additional_input)
  3. Status Badges — color-coded badges for intervention type, sentiment, resolution status, priority, etc. Colors come from color_maps
  4. Metadata Row — timestamp, message count, business name, agent name
  5. Metric Sections — grouped by metric name, with simple signals in a 2-column grid and complex/structured signals rendered full-width with nested key-value displays
  6. Suggested Action — highlighted panel when actionable feedback is detected
  7. Learnings — expandable section with numbered learning items and category tags
  8. Feature Requests — expandable section with numbered feature request items
  9. Full Conversation — expandable chat view with assistant (left) and user (right) message bubbles, Slack markdown parsing

Data Format

Human Signals uses a long format where each row is one metric observation for one case. Cases are grouped by dataset_id and signals are flattened as {metric_name}__{signal_key}.

Required Columns

dataset_id,metric_name,signals,source_name,timestamp

Full CSV Schema

dataset_id,metric_name,metric_score,metric_category,signals,conversation,conversation_stats,additional_input,source_name,source_component,environment,timestamp
ColumnRequiredDescription
dataset_idYesUnique case identifier. All metric rows with the same ID are grouped into one case.
metric_nameYesName of the metric (e.g., intervention_type, sentiment_category)
signalsYesJSON dict of signal key-value pairs (e.g., {"intervention_type": "correction", "has_intervention": true})
source_nameYesSource system name (e.g., alpha_bot). Used for filtering and display.
timestampNoISO datetime. Used for time-range filtering and trend charts.
conversationNoJSON with {"messages": [{"role": "...", "content": "..."}]}
conversation_statsNoJSON with turn counts (e.g., {"turn_count": 12})
additional_inputNoJSON with extra metadata (e.g., {"message_url": "...", "sender": "..."})
source_componentNoComponent within source (used for filters)
environmentNoDeployment environment (used for filters)

How Flattening Works

The backend groups rows by dataset_id, then for each metric row flattens all signal keys as {metric_name}__{signal_key}. For example:

# Input row:
metric_name: "intervention_type"
signals: {"intervention_type": "correction", "has_intervention": true}

# Becomes flattened case fields:
intervention_type__intervention_type: "correction"
intervention_type__has_intervention: true
⚠️ Warning
The signals column must contain valid JSON (or Python dict syntax). The backend tries JSON parsing first, then falls back to ast.literal_eval. Malformed signals will be silently skipped.

Configuration

The display layout is auto-generated from the metric schema but can be customized via config/signals_metrics.yaml. The backend generates a display_config that controls KPIs, charts, filters, table columns, and color maps.

Display Config Structure

{
  "kpi_strip": [
    { "label": "Total Cases", "aggregate": "total_cases", "icon": "zap", "highlight": true },
    { "label": "Intervention Rate", "metric": "intervention_type", "signal": "has_intervention",
      "format": "percent", "icon": "alert-triangle" }
  ],
  "chart_sections": [
    {
      "title": "Outcome Distribution",
      "layout": "grid_2",
      "charts": [
        { "metric": "intervention_type", "signal": "intervention_type",
          "type": "donut", "title": "Intervention Type" }
      ]
    }
  ],
  "filters": [
    { "type": "source", "field": "source_component", "label": "Component" },
    { "type": "metric", "metric": "sentiment_category", "signal": "sentiment",
      "label": "Sentiment", "options": ["positive", "neutral", "negative"] }
  ],
  "table_columns": [
    { "key": "Case_ID", "label": "Case ID", "sortable": true },
    { "key": "intervention_type__intervention_type", "label": "Intervention", "sortable": true }
  ],
  "color_maps": {
    "intervention_type__intervention_type": {
      "correction": "#4f46e5", "escalation": "#ca8a04", "edge_case": "#16a34a"
    }
  }
}

Database Configuration

To auto-import from a PostgreSQL database, create config/human_signals_db.yaml from the example template:

human_signals_db:
  enabled: true
  auto_load: true
  url: "postgresql://user:pass@host:5432/db"
  dataset_query: |
    SELECT dataset_id, conversation, additional_input, timestamp
    FROM hitl_cases WHERE created_at > NOW() - INTERVAL '30 days'
  results_query: |
    SELECT dataset_id, metric_name, signals, source_name, timestamp
    FROM hitl_results WHERE timestamp > NOW() - INTERVAL '30 days'
  query_timeout: 60
  row_limit: 10000
  visible_metrics: []  # Empty = show all
💡 Tip
Use visible_metrics to limit which metrics appear on the dashboard. This is useful when your database contains many metric types but you only want to surface a curated subset.

Next Steps

AXIS Documentation · Built with MkDocs Material