A TypeScript library that turns AI coding agent transcripts into normalized, real-time event streams.
agentprobe-demo-github.mp4
Unlike traditional observability tools (AgentOps, LangSmith) that require active instrumentation of your LLM calls, AgentProbe uses Passive Observability. It parses the artifacts (transcripts, logs) that tools like Cursor and Claude Code already leave behind, giving you real-time visibility into proprietary agents you don't own.
It is designed in layers:
core: generic runtime + lifecycle diffing (tool-agnostic)providers/cursor: Cursor transcript discovery + parsingproviders/claude-code: Claude Code session discovery + parsingproviders/codex: Codex JSONL session discovery + parsingproviders/opencode: OpenCode SQLite database adapter
The core observer API is provider-injected and tool-agnostic. All four providers are enabled by default.
npm install @agentprobe/coreimport { createObserver } from "@agentprobe/core";
const observer = createObserver({
workspacePaths: ["/Users/me/my-project"],
});
observer.subscribe((event) => {
console.log(event.change.kind, event.agent.id, event.agent.status);
});
await observer.start();
// stop() clears all subscriptions and resets state
await observer.stop();createObserver enables all four built-in providers (Cursor, Claude Code, Codex, OpenCode) by default. You can pass a custom providers array, debounceMs, or checkIdleDelayMs as needed.
The watch runtime (used by createObserver) is built around a state machine and an internal typed event bus that processes events sequentially.
stateDiagram-v2
[*] --> stopped
stopped --> starting : start()
starting --> started : connect + subscribe
starting --> stopped : abort / error
started --> stopping : stop()
stopping --> stopped : disconnect
state started {
[*] --> waiting
waiting --> processing : file‑changed / check‑idle / refresh‑requested
processing --> waiting : snapshot + lifecycle emitted
processing --> waiting : error emitted
state waiting {
[*] --> watchingFS
watchingFS --> idleTimer : check‑idle scheduled
idleTimer --> watchingFS : timer fires
}
}
Inside the started state, all work flows through a sequential event bus with three event types:
file-changed: dispatched after debounced fs.watch events; reads the snapshot and schedules a check-idle timercheck-idle: fires on a timer (default 2s); re-reads the snapshot to catch time-based status transitions (e.g.running→idle→completed) and self-reschedules while agents existrefresh-requested: dispatched byrefreshNow(); reads the snapshot and resolves waiting callers
Events are processed one at a time (no overlapping reads). Subscribers only receive events when agent statuses actually change (joined, statusChanged, left). Heartbeat-only cycles are silent, and the internal polling is invisible to consumers.
Agent statuses depend on time elapsed since last activity. Without periodic re-evaluation, time-based transitions are missed when transcript files stop changing. The check-idle mechanism solves this:
- After every
file-changedorrefresh-requested, a check-idle timer is scheduled - When it fires, the runtime re-reads the snapshot with the current timestamp
- If agents still exist, the timer self-reschedules
- Configurable via
checkIdleDelayMs(default2000, set tofalseto disable)
- Internal states:
stopped → starting → started → stopping start()connects to the source, installs optional watch subscriptions, emitsstarted, and dispatches an initialfile-changedeventstop()clears timers/subscriptions/bus queue, rejects in-flight waiters, disconnects, and emitsstopped
- A monotonic lifecycle token guards all event dispatch
- Every start/stop cycle advances the token
- Events dispatched with a stale token are silently dropped
- The event bus processes handlers sequentially with no overlapping async work
- Snapshot/read failures emit
errorand reject cycle waiters - Calling
refreshNow()while not running rejects withNOT_RUNNING - Stopping during an active refresh rejects pending waiters with
STOPPED_BEFORE_REFRESH_COMPLETED
When a provider exposes subscribeToChanges, runtime subscriptions:
- resolve configured/default watch paths
- normalize paths (trim + drop empty + dedupe)
- debounce bursty events before dispatching
file-changedto the bus - resubscribe with exponential backoff on subscription failures
@agentprobe/core: full package with all providers,createObserverenables all by default@agentprobe/core/core: core runtime, lifecycle, model, and provider types only@agentprobe/core/providers/cursor: Cursor transcript provider@agentprobe/core/providers/claude-code: Claude Code session provider@agentprobe/core/providers/codex: Codex session provider@agentprobe/core/providers/opencode: OpenCode database provider
npm install
npm run check
npm run buildnpm run format- format code with Biomenpm run lint- lint code with Biomenpm run typecheck- run TypeScript checkingnpm run test- run Vitest suitenpm run build- produce dist bundles with tsupnpm run check- biome check + typecheck + test
See the examples/ directory for 9 self-contained demos, from a minimal observer to a full terminal dashboard and floating macOS overlay.
Read the full deep-dive on how AgentProbe reconstructs agent lifecycles from Cursor and Claude Code transcripts without instrumentation:
AgentProbe: Real-Time Observability for AI Agents
MIT. See LICENSE.