trackers

$npx mdskill add BuilderIO/agent-native/trackers

Detect recurring concepts across calls to surface key moments.

  • Enables agents to identify and highlight specific concepts in transcripts.
  • Supports keyword regex patterns and agent-classified smart matching.
  • Stores match data with speaker labels and precise timestamp segments.
  • Generates UI chips for transcripts and filters for the library view.
SKILL.md
.github/skills/trackersView on GitHub ↗
---
name: trackers
description: >-
  Per-workspace tracker definitions and per-call hits. Two kinds — keyword
  (regex word-boundary, synchronous) and smart (agent-classified). Use when
  adding a new tracker kind, tuning the keyword regex, changing the smart
  tracker delegation, or touching the default tracker seed.
---

# Trackers

Trackers are the "recurring moment detector" — per-workspace definitions that look for a concept across every call and mark the moments it shows up. Each hit carries a quote, a speaker, and a timestamp so the UI can render it as a chip on the transcript line and as a filter in the library.

## When to use

Read this skill before:

- Adding a new tracker kind
- Changing how keyword matches are detected (regex, word boundaries, casing)
- Touching the smart-tracker delegation payload
- Modifying the default trackers seeded on first run
- Debugging "why didn't the tracker fire on this quote?"

## Data model

- **`tracker_definitions`** — per-workspace rows. Fields:
  - `kind: "keyword" | "smart"` — the matcher.
  - `keywords_json` — JSON array of phrases (for keyword kind).
  - `classifier_prompt` — free-text criterion (for smart kind).
  - `enabled` — disabled trackers are skipped by `run-trackers`.
  - `is_default` — seeded defaults carry this so we can refresh them.
- **`tracker_hits`** — per-call match rows:
  - `speaker_label` — may be null if the hit spans multiple speakers.
  - `segment_start_ms` / `segment_end_ms` — the segment the hit lives in.
  - `quote` — verbatim substring from the transcript.
  - `confidence` — 0–100. Keyword hits are 100; smart hits carry the agent's confidence.

## Two kinds

### Keyword trackers

Implementation: `server/lib/trackers/keyword-tracker.ts`. Synchronous; no LLM.

- Each phrase becomes a case-insensitive regex with **word boundaries** (so `"refund"` matches `Refund?` but not `refundable`).
- Multi-word phrases (`"next steps"`) are matched as an ordered regex `\bnext\s+steps\b` with flexible whitespace.
- Hits are recorded per segment — one segment can have multiple hits across phrases, but duplicates on the same segment+tracker are deduped.
- Confidence is always 100 (exact match).

When a user tweaks keywords via `update-tracker`, existing hits are **not** retroactively refreshed — the user must run `run-trackers --callId=<id>` on affected calls (or the library can lazy-rerun on the next view).

### Smart trackers

Implementation: `server/lib/trackers/smart-tracker.ts`. Agent-delegated.

- The tracker carries a `classifier_prompt` — a single-sentence criterion, e.g. `"Is the prospect raising a pricing objection?"`.
- `run-trackers --kind=smart` iterates enabled smart trackers and, for each, writes a delegation payload to `application_state.ai-delegation-<callId>-<uuid>`:
  ```json
  {
    "kind": "smart-tracker",
    "callId": "...",
    "trackerId": "...",
    "trackerName": "Objections",
    "trackerDescription": "...",
    "classifierPrompt": "Is the prospect raising a pricing objection?",
    "segmentsJson": "[...]",
    "message": "Run smart tracker \"Objections\" against call .... For each paragraph that matches, call run-smart-tracker-hit --callId=... --trackerId=... --speakerLabel=... --segmentStartMs=... --segmentEndMs=... --quote=\"<verbatim>\" --confidence=<0-100>. Do not invent quotes — quotes must be verbatim sub-strings of the transcript."
  }
  ```
- The agent reads the segments, finds matching paragraphs, and emits one `run-smart-tracker-hit` per match.
- **Quotes must be verbatim** — `run-smart-tracker-hit` rejects hits whose `quote` is not a substring of the transcript's `fullText`. This is the anti-hallucination guard.
- Confidence is the agent's own estimate (0–100).

## The `run-trackers` flow

```bash
pnpm action run-trackers --callId=<id> [--kind=keyword|smart|all]
```

1. Loads all enabled tracker definitions for the call's workspace.
2. For each tracker matching the requested kind:
   - **Keyword:** delete existing hits on `(callId, trackerId)`, run the regex over segments, insert new hits.
   - **Smart:** write the delegation payload to app-state. Agent picks it up and calls `run-smart-tracker-hit` per match.
3. Bumps `refresh-signal`.

`request-transcript` calls `run-trackers --kind=keyword` at the end of the pipeline so keyword hits appear immediately when the call flips to `analyzing`. Smart hits trickle in as the agent works through the queue.

## Default trackers

On first workspace create, `server/lib/trackers/seed-defaults.ts` inserts these with `isDefault=true`:

| Name           | Kind    | Keywords / Prompt                                                              |
| -------------- | ------- | ------------------------------------------------------------------------------ |
| Pricing        | keyword | `price`, `pricing`, `cost`, `quote`, `dollars`, `budget`                        |
| Competitors    | smart   | "Is the speaker mentioning a competing vendor or alternative solution?"        |
| Objections     | smart   | "Is the prospect raising an objection (price, authority, timing, need, trust)?" |
| Next Steps     | smart   | "Is the speaker proposing or agreeing to a concrete next step or follow-up?"    |
| Budget         | smart   | "Is the conversation touching on budget, ROI, or procurement process?"         |
| Timing         | smart   | "Is the speaker referencing a deadline, go-live date, or urgency?"              |
| Filler words   | keyword | `um`, `uh`, `like`, `you know`, `basically`, `actually`                         |

Users can edit, disable, or delete these via the Trackers page.

## `run-smart-tracker-hit` guards

The agent-only action in `actions/run-smart-tracker-hit.ts` validates:

- `assertAccess("call", callId, "editor")`.
- `tracker_definitions.kind == "smart"` (can't pipe keyword hits through this endpoint).
- `confidence` in `[0, 100]`, `segmentStartMs < segmentEndMs`, both within the call duration.
- `quote` is a verbatim substring of `call_transcripts.full_text` (case-insensitive substring match is allowed; exact case preferred).

Reject with a descriptive error — the agent will retry with a corrected quote.

## UI

Hits render in two places:

- **Transcript line chips** — a colored dot + tracker name at the start of the segment that has hits.
- **POI tab → Trackers** — grouped list of hits with click-to-jump to the quote.

Library filter: `list-calls --trackerId=<id>` filters to calls with at least one hit for that tracker. Pair with `search-calls` for full-text across transcript.

## Rules

- **Keyword regex is always case-insensitive + word-bounded.** Don't ship a tracker that matches mid-word (`"ref"` matching "reference") — the false positives will drown the signal.
- **Smart trackers must carry a `classifier_prompt`.** The shape validator rejects smart trackers without one.
- **Hits must cite verbatim quotes.** Never generate a synthetic quote — the UI renders the quote verbatim, and any drift will look like a hallucination to the user.
- **`run-trackers` is idempotent per `(callId, trackerId)`** — keyword runs DELETE then INSERT; smart runs queue a fresh delegation each time.
- **Always bump `refresh-signal` after writing hits.** The library chip counts and the transcript marks both depend on it.

## Related skills

- `transcription` — `run-trackers --kind=keyword` runs synchronously at the end of the transcription pipeline.
- `delegate-to-agent` — smart trackers are the canonical delegation example.
- `call-search` — tracker filters combine with full-text search in the library.
- `storing-data` — why `keywords_json` and `tracker_hits` are separate tables.
More from BuilderIO/agent-native