Live trajectory verdict for AI coding agent sessions. Local-only. No LLM.
AgentPulse watches your Claude Code, Cursor, and Codex transcript files and classifies what each agent is doing right now — converging, exploring, stuck, done, drifting, or idle — in a two-pane terminal dashboard. Deterministic templating over local signal. No outbound network calls, no language model, no cloud.
npx @conalh/agentpulse@latest liveThat's the headline command. Drop it in a terminal window next to your editor and you get an always-on read on every session in ~/.claude/projects/, ~/.cursor/projects/, and ~/.codex/sessions/.
Sample narrative on a converging session:
Your agent has been working on the login bug for 18 minutes. It focused
on `src/auth/`, made 3 changes to `session.ts`, and ran the tests after
each change. Tests went from failing to passing. Looks like it solved it.
Verdict: ● converging (confidence 0.85)
Part of the agent-gov suite.
Several tools watch agent sessions. AgentPulse's wedge is the specific combination none of them cover:
| Local-only | No LLM | Trajectory verdict | Per-session live dashboard | PR gate | |
|---|---|---|---|---|---|
| LangSmith / Langfuse / AgentOps | ❌ cloud | ❌ LLM-judge | ❌ traces only | ⚠ | ⚠ |
| Claude Code Session Memory | ✅ | ❌ LLM | ⚠ structured | ❌ | ❌ |
| agenttrace | ✅ | ✅ | ❌ metrics only | ⚠ TUI | ✅ |
| AgentPulse | ✅ | ✅ | ✅ | ✅ | ✅ |
The wedge is the combination. AgentPulse pairs naturally with agenttrace (which covers cost/health metrics) and with the rest of the agent-gov suite (which covers PR-time gates).
agentpulse live [options]Options:
| Flag | Default | Effect |
|---|---|---|
--window <duration> |
20m |
Recap window per session (5m, 1h, etc.) |
--refresh <duration> |
30s |
Background refresh cadence. Watcher fires sub-second on file changes regardless. |
--roots <p1,p2,...> |
platform defaults | Override discovery roots (comma-separated) |
--stale <duration> |
1h |
Skip sessions older than this |
--hide-idle |
off | Hide sessions with no activity in the window (default: visible, grey-dimmed) |
--max-sessions <N> |
10 |
Cap the displayed list |
--show-subagents |
off | Include agent-<hex> SDK-spawned subagent transcripts |
--no-detectors |
off | Skip the drifting bucket entirely |
--once |
off | Headless snapshot mode. Runs once, prints, exits. No TUI. (v0.4.0+) |
--format <fmt> |
text |
With --once: text (human) or json (structured snapshot). (v0.4.0+) |
--strict |
off | With --once: exit 1 if any session is drifting or stuck. (v0.4.0+) |
--notify <mode> |
none |
Local notification on transition INTO drifting/stuck. Modes: none, bell, os, both. (v0.4.2+) |
Keyboard:
| Key | Action |
|---|---|
| ↑ ↓ / k j / w s | Move selection |
| r | Force refresh on selected session |
| a | Whitelist current session's drift findings — first press shows preview, second press within 3s commits (v0.4.1+, two-stage in v0.4.8+) |
| n | Name / rename the selected session (alias) (v0.4.7+) |
| ? | Toggle help overlay |
| q / Ctrl-C | Quit |
Verdict pills:
| Pill | Meaning |
|---|---|
| ● green converging | Actively editing with focus, often with verification |
| ◐ gray exploring | Reading around, no edits yet |
| ▲ yellow stuck | Edits + tests failing + user pushing back |
| ■ blue done | Completion verb + idle gap |
| ⚠ red drifting | Privileged-path access, network exec, or write outside repo |
| ○ gray idle | Window had activity earlier OR is silent; agent is parked |
When multiple agents work on the same project — say two Claude Code windows + a Cursor + a Codex — the dashboard rows look identical until you name them. Press n on a selected row to drop into rename mode, type whatever helps you tell them apart (CC1, CC2, frontend, backend), and press Enter. The alias appears in front of the project name (CC1 · AgentPulse (claude-code)) and replaces the auto-generated hex disambiguator.
Aliases live in two optional JSON files (cwd wins over home):
<session.cwd>/.agentpulse-aliases.json— per-project, commit alongside.agentpulse-exceptions.jsonif you want team-shared conventions.~/.agentpulse/aliases.json— your personal default, persists across machines if you sync your home dir.
n-then-Enter writes to the home file by default; promoting an alias to the shared per-project file is a manual copy/paste — intentional, so a shared file never gets written without you knowing.
Empty + Enter clears the alias.
{
"version": 1,
"aliases": {
"c3d4566ef4c5": "CC1",
"7a8b91234567": "CG1"
}
}Press a on a drifting session to surface a whitelist preview in the footer — kind summary, count, 3-second confirmation window. Press a again within the window to commit; any other key cancels. AgentPulse appends the previewed fingerprints to <session.cwd>/.agentpulse-exceptions.json, refreshes the session, and the verdict re-classifies away from drifting instantly.
The two-stage confirm (v0.4.8+) protects against a stray keypress permanently whitelisting findings the user couldn't see clearly. The captured drift set is the one written — a refresh during the 3-second window can't slip new findings into the commit silently.
The exception file is a simple JSON list keyed by stable Finding.fingerprint:
{
"version": 1,
"exceptions": [
{
"kind": "agent_pulse.live_drift_shell_exfil",
"fingerprint": "a1b2c3...",
"approvedAt": "2026-05-23T22:00:00.000Z",
"note": "approved by user via TUI"
}
]
}Commit it to your repo. CI gating (agentpulse live --once --strict or the GitHub Action) honors the same baseline — the trajectory layer reads it the same way the TUI does.
--notify <mode> fires a local notification when any session transitions INTO drifting or stuck. Useful when you're not actively staring at the TUI.
| Mode | Effect |
|---|---|
none (default) |
Silent |
bell |
Writes \x07 to stderr (terminal beep) |
os |
Native notification — osascript on macOS, notify-send on Linux, BurntToast/NotifyIcon on Windows |
both |
Bell + OS |
Trigger policy: fires only on safe-to-alert transitions. converging → drifting fires; drifting → drifting doesn't (no double-alert); drifting → idle doesn't (clearing is silent).
Best-effort: a missing notify-send on Linux, or a stripped-down Windows env, is a silent no-op rather than a crash.
Two ways to gate a pipeline on agent state:
- uses: Conalh/AgentPulse@v0.6.1
with:
transcript-dirs: agentpulse-transcripts
strict: 'true'
comment-on-pr: 'true'The action runs agentpulse live --once against the provided transcript directory, emits a markdown summary to the GitHub step summary, optionally posts a sticky PR comment that updates in place across pushes, and (with strict: true) fails the workflow when any session is in drifting or stuck. Full input/output reference in action.yml; a complete PR-check workflow at examples/agentpulse-pr-check.yml.
If you're not on GitHub Actions:
npx @conalh/agentpulse@latest live --once --strict --roots <transcript-dir>Exits 1 if any session is drifting/stuck. Add --format json to pipe a structured snapshot into downstream tools.
Commit .agentpulse-exceptions.json to your repo — both the Action and the raw CLI read it from the session's cwd. A finding whitelisted via the TUI's a key locally stops gating CI too.
For piping a single transcript into scripts or dashboards (vs. the multi-session discovery the live subcommand does):
agentpulse recap --transcript-dir ~/.claude/projects/<your-project>/ --format jsonSame pipeline, narrower input. Use --watch for a polling re-emit loop.
- No LLM, anywhere. Not for summarization, not for classification. The whole point is determinism.
- No outbound network calls. Reads your local transcript files, writes to your terminal (and optionally the local OS notifier). Nothing leaves the machine.
- No web UI / hosted dashboard. TUI for the live view, GitHub Action for PRs, JSON output for everything else. Nothing hosted.
- No multi-session memory. Each invocation reads the window and exits.
- No "AI to review AI" loop. Detectors are deterministic. The trajectory classifier is a rule tree, not a model.
Deterministic pipeline. Each layer is pure where it can be; all layers share the src/types.ts contract.
| Layer | File | Input → Output |
|---|---|---|
| 1. Parser | agent-gov-core/parsers/ (v1.1.0+) |
Claude Code / Cursor / Codex JSONL → TranscriptEvent[] |
| 2. Enrichment | src/enrich.ts |
events → keywords, cwd-relative path clusters, action classes |
| 2.5. Sequences | src/sequences.ts |
events → ordered-pattern signal (tdd_loop / stuck_loop / refuse_to_verify / exploratory_edit) |
| 3. Outcome | src/trajectory.ts |
events → verification trend, user tone, completion verbs, idle gap |
| 4. Trajectory | src/trajectory.ts |
enrichment + outcome + sequence + exceptions → six-bucket verdict |
| 5. Narrative | src/narrative.ts |
verdict → plain-English recap |
Live infrastructure on top:
src/sessions/— auto-discovery + filesystem watchersrc/orchestrator.ts— multi-session pulse runner with concurrent-refresh coalescingsrc/exceptions.ts—.agentpulse-exceptions.jsonreader/writer (v0.4.1+)src/notifications.ts— bell + cross-platform OS notifications on state transitions (v0.4.2+)src/once.ts— headless--oncerunner for CI (v0.4.0+)src/tui/— Ink TUI components
- Local by default. Zero network calls in any code path.
- Deterministic. Same transcript window in, same verdict out. No model drift, no API outages, no rate limits.
- MIT. No telemetry. No commercial offering.
- Substrate-built. Wires
agent-gov-coreprimitives where the contract overlaps. The Layer 1 transcript parser lives inagent-gov-core@1.1.0+(promoted out of AgentPulse v0.5.0 — see CHANGELOG); the Finding schema and detector library follow the same pattern.
If you're running on Windows, prefer Windows Terminal over the legacy cmd.exe. cmd.exe has known ANSI limitations that this project routes around (the alt-screen-buffer fix in v0.2.8 was specifically for cmd.exe). It works on cmd.exe, but Windows Terminal renders the dashboard more cleanly.
- agent-gov-core — the substrate (Finding schema, fingerprint, MCP canonical normalization)
- SessionTrail — PR-time runtime-behavior review. AgentPulse is the live-view sibling.
- GovVerdict — cross-tool meta-reviewer. AgentPulse verdicts can roll up.
MIT.