Introduction
agentop is a terminal UI (TUI) process inspector for developers who run Claude Code or the OpenAI Codex CLI alongside their regular shell workflow.
Think of it as top, but aware of the two coding agents: it detects their
processes, reads their on-disk state, and surfaces the signals that actually
matter — context-window utilisation, cost, pending tool calls, and status —
in a single live view.
What it shows
- A tree of every detected Claude and Codex session, plus their descendants
- Rolling CPU / memory / uptime — smoothed, time-in-state coloured
- Cumulative token counts and an estimated cost in USD for each agent session
- Current context-window utilisation, reading Claude and Codex transcripts
- Pending tool calls flagged with a
⚑badge - Project-level grouping and curated attention filters
Everything runs on-device. No network calls, no telemetry, no modifications to Claude Code or Codex settings.
Who it's for
- Developers juggling multiple AI coding sessions at once
- Anyone wanting a glanceable "is this agent still productive?" signal
- Operators running long agent workflows who need to catch stalls and cost spikes before they get expensive
What it deliberately isn't
agentop is a read-only inspector. It doesn't auto-approve tool calls, run a local LLM gate, enforce spend budgets, or install hooks into your Claude / Codex configuration. If you want those, projects like claudectl exist in that adjacent space. agentop stays simple on purpose.
Where to go next
- Installation — pick your package manager
- Quickstart — the first five minutes
- Privacy and security — what the tool reads and what it doesn't
Installation
Requires macOS or Linux. Windows is not supported.
From crates.io (recommended)
Needs Rust 1.85 or later.
cargo install agentop
cargo install builds release-optimised binaries by default.
From Homebrew
Once the tap repo is published at
leboiko/homebrew-tap, install with:
brew install leboiko/tap/agentop
From Nix
nix run github:leboiko/claude-codex-pid-inspector
Or add to a flake:
{
inputs.agentop.url = "github:leboiko/claude-codex-pid-inspector";
# ...
}
From AUR (Arch Linux)
Prebuilt binary package:
yay -S agentop-bin
Or with any AUR helper that accepts manual submissions.
Prebuilt binaries from GitHub Releases
Each tagged release ships SHA256-checksummed tarballs for:
x86_64-unknown-linux-gnuaarch64-unknown-linux-gnux86_64-unknown-linux-muslx86_64-apple-darwinaarch64-apple-darwin
See the Releases page.
Verify the download against SHA256SUMS before running.
From source
git clone https://github.com/leboiko/claude-codex-pid-inspector.git
cd claude-codex-pid-inspector
cargo install --path .
Verifying the install
agentop --version
If ~/.cargo/bin is not on your PATH, add it to your shell rc:
export PATH="$HOME/.cargo/bin:$PATH"
Quickstart
Launching
agentop
The TUI opens in an alternate screen buffer, so your terminal scrollback is
not disturbed. Quit with q or Ctrl+C.
If no Claude or Codex sessions are running, you'll see only the base process tree — which is fine; the agent view will fill in as soon as you start one.
The first five minutes
See your agent sessions
Press T to switch to the agent view. The columns change to:
PID | Name | Status | Ctx% | Cost | Tokens | Tool | Uptime
Each Claude or Codex root row now shows its model, current context-window utilisation, cumulative cost in USD, and the name of any pending tool call.
Focus on what needs attention
Press F to cycle through curated filters:
| Filter | What it shows |
|---|---|
all | every process (default) |
attention | sessions with a pending tool call or ≥ 80% context |
high-cpu | smoothed CPU ≥ 30% |
high-context | context ≥ 80% |
recent | started in the last 10 minutes |
The status bar shows which filter is active: FILTER: high-cpu (4/11).
Group by project
Press g to cluster rows by working directory. Each group gets a header
row with the slug, session count, total cost, and average context %:
▼ /Users/me/project 3 sessions · $4.21 · ctx 47% avg
Press Space on a header to collapse / expand the group.
Help is one key away
Press ? to open a modal listing every keybinding. Esc dismisses.
Drill into a session
Press Enter on a row to see the detail panel: token stats, a decay-score bar for Claude sessions, the pending tool call (if any), the last few assistant messages, and a subagent summary.
From the detail view, press Tab to jump focus to the terminal hosting that PID — supported on tmux, iTerm2, and Kitty. See the Terminal support matrix for the current state.
Inspect non-interactively
If you just want the data:
agentop --list # plain-text table
agentop --json # single-line JSON snapshot
agentop --json --pretty | jq '.processes[] | select(.kind == "claude")'
See JSON schema for the full field reference.
What's safe to try
Everything in this tool is read-only except x (kill), which prompts for
confirmation. The agent view, grouping, help overlay, and all filters are
purely visual. You cannot accidentally modify your Claude or Codex state.
Keybindings
Every binding is shown in the in-app help overlay — press ? to see the
live list.
Tree view (default)
| Key | Action |
|---|---|
q, Ctrl+C | Quit |
↑, k | Move selection up |
↓, j | Move selection down |
Enter | Open detail view for selected row |
Space | Expand / collapse selected node (or group when grouping is on) |
Tab | Cycle sort column forward |
Shift+Tab | Cycle sort column backward |
s | Toggle sort direction |
x | Kill selected process (with confirmation) |
c | Open config popup |
/ | Enter free-text filter |
F | Cycle curated focus filter |
g | Toggle project grouping |
T | Toggle agent view (Ctx% / Cost / Tokens / Tool columns) |
? | Show help overlay |
z | Clear any active filter |
Detail view
| Key | Action |
|---|---|
Esc | Return to tree view |
q, Ctrl+C | Quit |
x | Kill selected process (with confirmation) |
c | Open config popup |
Tab | Jump to the terminal hosting this PID (tmux, iTerm2, Kitty) |
? | Show help overlay |
While the help overlay is open
| Key | Action |
|---|---|
Esc | Close the overlay |
| Any other bound key | Close, then execute that key's action |
While the free-text filter bar is active
| Key | Action |
|---|---|
Esc | Clear filter and dismiss the bar |
Backspace | Delete one character |
Enter | Open detail view for highlighted row |
↑↓, j/k | Move selection (search results scroll under the bar) |
| Other printable chars | Appended to the query |
Activity badges
Every Claude and Codex root row shows a single badge character at the left of the Name cell, coloured and decorated to signal the session's live state. Row colour (Claude orange, Codex green) stays dedicated to brand identification; the badge is rendered in its own span and carries the activity signal independently.
Base shapes
| Glyph | Meaning |
|---|---|
● | Active — CPU above the idle threshold in recent samples |
○ | Idle — below the idle threshold for the sampling window |
| (none) | Unknown — not enough CPU samples collected yet |
Idle escalation
The longer a session has been continuously idle, the more urgent the badge:
| Duration in Idle | Tier | Colour | Text suffix |
|---|---|---|---|
| 0 – 60 s | Fresh | gray | (none) |
| 60 s – 5 min | Warning | yellow | (3m) |
| > 5 min | Stale | red, bold | (12m), (1h+) |
The duration suffix makes the state legible without relying on colour — important for users with deuteranopia or protanopia, where yellow / red / green collapse into a similar band.
The timer resets every time a session re-enters Idle, so an
Idle → Active → Idle transition doesn't accumulate toward the Stale
threshold. A Stale badge means a genuinely uninterrupted idle period.
Pending-tool flag
When a Claude or Codex session is awaiting user approval on a tool call, a
⚑ flag appears immediately after the activity badge, in bold yellow:
● ⚑ claude
The Tool column (in agent view) shows the tool name.
Why this exists
The real cost of AI coding sessions is not CPU — it's abandoned sessions that accumulate idle cost or block on a tool call nobody notices. The badges are tuned to pull attention when, and only when, something actionable is waiting.
Agent view
Press T to toggle. The column set changes from the process-manager
default to one optimised for Claude and Codex sessions.
Default layout
PID | Name | CPU% | Memory | Status | Command | Uptime
The neutral process-inspector view. Unchanged from traditional top-style
tools; useful for spotting runaway child processes.
Agent layout
PID | Name | Status | Ctx% | Cost | Tokens | Tool | Uptime
Codex and non-agent rows render – in the telemetry columns so the row
still reads cleanly.
Cell semantics
Status
One of: processing, needs_input, waiting_input, idle, finished,
unknown. Inferred from a priority hierarchy (CPU > pending-tool > last
stop reason > timestamp age) so a busy session never reads as Idle just
because its transcript lags.
Ctx%
Live context-window utilisation for the most recent turn.
| Value | Colour |
|---|---|
| < 50% | green |
| 50 – 79% | yellow |
| ≥ 80% | red |
Absent sessions (e.g. very fresh) render as dim –.
Claude: sum of input_tokens + cache_creation_input_tokens + cache_read_input_tokens from the most recent assistant message, divided by
the model's context window (looked up from the pricing table).
Codex: last_token_usage.input_tokens / model_context_window, both read
verbatim from the rollout file.
Cost
Session-cumulative USD estimate.
| Value | Colour |
|---|---|
| < $1 | green |
| $1 – $5 | yellow |
| > $5 | red |
Both providers compute cost locally from a model pricing table, flagged
cost_is_estimated: true in the JSON output. See the
Claude and Codex chapters
for audit dates.
Tokens
Combined {in_k}k↑ {out_k}k↓, cumulative across the session. No colour
thresholds — interpretation depends on pricing tier.
Tool
Truncated name of a pending tool call (at most 10 chars, ellipsis if longer). Bold yellow when pending; empty otherwise.
Interaction with other toggles
T is orthogonal to F (focus filters), g (project grouping), and /
(free-text filter). Any combination is valid. The default state (all
toggles off) is the classic process-inspector view.
Why a toggle, not always-on
Claude and Codex telemetry columns are only meaningful for agent rows.
Showing them as permanent columns would either waste screen width on –
cells for every shell and subprocess, or crowd out the process-manager
fundamentals. Toggling keeps both modes sharp.
Claude provider
The Claude telemetry provider reads the on-disk state a running Claude Code
session maintains and surfaces it as AgentTelemetry.
Data sources
| Path | What we read | Why |
|---|---|---|
~/.claude/sessions/{pid}.json | PID → session metadata (session ID, cwd, start time, version) | Direct PID correlation; no fuzzy matching |
~/.claude/projects/{cwd-slug}/{sessionId}.jsonl | Transcript events | Token counts, tool calls, stop reasons |
/tmp/claude-{uid}/{cwd-slug}/{sessionId}/tasks/ | Subagent task files | Count of active subagents |
{cwd-slug} is the working directory with / replaced by -.
The transcript is tailed incrementally. The provider persists a byte offset per session and reads only new bytes on each tick. File shrinkage (truncation, rotation) is detected and the offset resets to 0.
What we extract
From each assistant event's message.usage:
input_tokens,output_tokens(cumulative, summed across turns)cache_read_input_tokens,cache_write_input_tokens- The latest message's usage gives the per-turn context estimate
From each assistant event's message.stop_reason:
last_stop_reason("end_turn" / "tool_use" / ...)
From user-event tool_result blocks and assistant-event tool_use
blocks:
- Unresolved
tool_use_ids →pending_toolif the last assistant message ended intool_use
Pricing
Hardcoded in src/telemetry/claude/pricing.rs with a
CLAUDE_PRICING_AUDITED_AT constant.
| Model prefix | Input | Output | Cache write | Cache read | Window |
|---|---|---|---|---|---|
claude-opus-4-7 (1M) | $15 | $75 | $18.75 | $1.50 | 1 000 000 |
claude-opus-4-* | $15 | $75 | $18.75 | $1.50 | 200 000 |
claude-sonnet-4-* | $3 | $15 | $3.75 | $0.30 | 200 000 |
claude-haiku-4-* | $1 | $5 | $1.25 | $0.10 | 200 000 |
| fallback | Sonnet rates | — | — | — | 200 000 |
Match order is longest-prefix-first so the 1M-context Opus 4.7 variant
matches before the generic claude-opus-4-*.
Every cost surfaces with cost_is_estimated: true. We're reading from a
local table, not an authoritative bill.
Status inference
Priority hierarchy, top match wins:
- CPU > 5% →
processing pending_toolis Some →needs_input- Last assistant
stop_reason == "end_turn"and last event > 10 min ago →idle - Last assistant
stop_reason == "end_turn"and recent →waiting_input - Default →
unknown
Schema stability
Claude's transcript format is undocumented as a public API. The parser uses
#[serde(other)] catchalls and skips lines it doesn't understand rather
than failing the tick. If a future Claude release changes the schema, the
worst case is telemetry going to null until we audit and update.
The pricing table is audited before each minor release. See Stability and compatibility.
Codex provider
The Codex telemetry provider reads ~/.codex/sessions/ rollout files and
surfaces them via AgentTelemetry. The rollout format
is actually simpler than Claude's transcript: every event is a tagged
union with a known type, and the model's context-window size is written
directly into each token_count event — no per-model lookup table needed
for the context fraction.
Data sources
| Path | What we read |
|---|---|
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl | Session meta, turn context, token counts, tool calls |
/proc/{pid}/environ (Linux) | CODEX_HOME override |
ps eww -p <pid> (macOS) | Same override via appended env block |
CODEX_HOME lets users point Codex at a non-default state directory; the
provider reads it once per PID and caches the result.
PID correlation
Filenames are unreliable for correlation. Codex writes timestamps in
the host's local time, not UTC. On a UTC-3 machine a session that started
at 13:43:38Z produces a filename with 10-43-38 — naive UTC parsing
would match nothing.
The provider correlates by opening each candidate rollout file and reading
the authoritative UTC timestamp from its first session_meta event's
payload.timestamp. Matches within ±90 s of the process's start time.
What we extract
From each event_msg.payload where type == "token_count" (the latest
populated info wins):
total_token_usage.{input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens}— session cumulativelast_token_usage.input_tokens— the live context signal (cumulativetotal_tokensgrows unbounded across turns and would report >100% on any long session)model_context_window
From each response_item.payload:
function_call/custom_tool_callevents increment the unresolved call-id setfunction_call_output/custom_tool_call_outputclear it- If the last response item is an unresolved call,
pending_toolgets its name
From each turn_context.payload:
model(e.g.gpt-5.4,gpt-5-codex)
Pricing
Hardcoded in src/telemetry/codex/pricing.rs with a
CODEX_PRICING_AUDITED_AT constant. Rates are estimates aligned with
public OpenAI pricing tiers; every cost surfaces with
cost_is_estimated: true.
The table covers gpt-5*, gpt-5-codex, and o3/o4 reasoning families.
Reasoning output tokens are billed at the output rate.
Fields Codex leaves null
| Field | Reason |
|---|---|
cache_write_tokens | Codex rollout exposes cached-input but not cache-write separately |
decay_score | The decay formula relies on error-rate and file-edit signals Codex doesn't expose |
subagent_count | Codex has no subagent task files |
last_stop_reason | Codex doesn't emit a Claude-style per-turn stop reason |
Schema stability
The rollout format is internal to Codex. The parser ignores unknown event
types via a #[serde(other)] catchall. If a future Codex release changes
the schema, telemetry falls back to null until we audit. See
Stability and compatibility for the audit cadence.
JSON schema
agentop --json prints a single-line snapshot to stdout and exits.
agentop --json --pretty indents it for human reading.
The schema is versioned. schema_version is currently 1 and will not
remove or rename fields during the 1.x release line. Additive changes
(new fields) are allowed without a version bump. Breaking changes
increment the number.
The authoritative, machine-checked reference lives at
docs/output-schema.md
in the repository and is included below verbatim.
agentop JSON output schema
agentop --json emits a single-line JSON object (schema version 1).
agentop --json --pretty emits the same object with 4-space indentation.
Stability promise
Schema version 1 will never remove or rename a field. Additive changes
(new fields, new optional members in nested objects) are allowed without a
version bump and will be noted in CHANGELOG.md as a minor change.
A schema-breaking change (field removal, rename, type change) increments
schema_version to 2.
Consumers must check schema_version before parsing. A schema_version
of 1 guarantees the fields listed below are always present (never absent
due to skip_serializing_if).
Top-level fields
| Field | Type | Description |
|---|---|---|
schema_version | integer | Always 1 for this schema |
generated_at | string | RFC 3339 UTC timestamp (2026-04-21T13:45:00Z) |
agentop_version | string | SemVer string from Cargo.toml |
system | object | System-wide resource snapshot (see below) |
agent_summary | object | Aggregate counts and usage (see below) |
processes | array | Root-level agent processes (see below) |
system object
| Field | Type | Description |
|---|---|---|
cpu_usage_percent | float | Global CPU percent across all cores |
total_memory_bytes | integer | Total installed physical memory |
used_memory_bytes | integer | Currently resident memory |
total_swap_bytes | integer | Total swap space |
used_swap_bytes | integer | Currently used swap |
cpu_count | integer | Number of logical CPUs |
agent_summary object
| Field | Type | Description |
|---|---|---|
claude_count | integer | Number of Claude Code root processes |
codex_count | integer | Number of Codex CLI root processes |
total_cpu_percent | float | Total CPU % across all agent subtrees |
total_memory_bytes | integer | Total memory (bytes) across all agent subtrees |
processes array (recursive)
Each entry in processes is a root agent process. Its children field
contains the same structure recursively. Non-agent child processes appear
inside children with kind: null.
| Field | Type | Description |
|---|---|---|
pid | integer | Process ID |
parent_pid | integer or null | Parent PID; null for root entries |
kind | string or null | "claude", "codex", or null |
name | string | Short OS process name |
display_name | string | Friendly name (e.g. "claude" even when OS name is "node") |
cmd | string[] | Full argv split into tokens |
exe_path | string or null | Absolute path to executable |
cwd | string or null | Working directory |
cpu_percent | float | CPU % (3-sample rolling average) |
memory_bytes | integer | Resident memory in bytes |
status | string | OS status string ("Run", "Sleep", etc.) |
start_time_unix | integer | Unix epoch timestamp when the process started |
uptime_seconds | integer | Seconds the process has been running |
activity_state | string or null | "active", "idle", "unknown", or null (non-roots) |
activity_state_seconds | integer or null | Seconds in the current activity state; null for non-roots |
telemetry | object or null | Provider-specific session telemetry (see below); null when no provider is active |
children | array | Same shape, recursive |
activity_state values
| Value | Meaning |
|---|---|
"active" | CPU exceeded idle threshold in at least one recent sample |
"idle" | All recent samples are below the idle threshold |
"unknown" | Not enough samples collected yet |
null | This entry is a non-root child process |
telemetry object
The shape of the telemetry object is the same for all providers. Fields that
a provider does not track are null. The provider field identifies the source.
schema_version remains at 1 — this field is additive.
Claude provider
Present on root-level Claude process entries when ~/.claude/sessions/ exists
and a matching session file has been found for the PID. null otherwise.
| Field | Type | Description |
|---|---|---|
provider | string | Always "claude" |
session_id | string or null | Claude session UUID |
model | string or null | Model identifier from the most recent turn |
status | string or null | Agent status (see status values below) |
input_tokens | integer | Cumulative input tokens for this session |
output_tokens | integer | Cumulative output tokens for this session |
cache_write_tokens | integer | Cumulative cache-write tokens |
cache_read_tokens | integer | Cumulative cache-read tokens |
context_tokens | integer or null | Context token estimate from the most recent turn |
context_window | integer or null | Model context window size (from pricing table) |
context_fill_percent | float or null | context_tokens / context_window * 100 |
cost_usd | float | Accumulated session cost in USD (estimated) |
cost_is_estimated | boolean | Always true — cost is derived from a local pricing table |
pending_tool | string or null | Name of the tool currently awaiting a result, if any |
decay_score | integer or null | Context-health score 0–100 (higher = more pressure) |
subagent_count | integer | Number of subagent task files found under /tmp/claude-{uid}/... |
last_event_timestamp | string or null | RFC 3339 timestamp of the most recent assistant event |
Codex provider
Present on root-level Codex process entries when ~/.codex/sessions/ exists
(or $CODEX_HOME/sessions/) and a rollout file whose filename timestamp is
within ±90 s of the process start time is found. null otherwise.
| Field | Type | Description |
|---|---|---|
provider | string | Always "codex" |
session_id | string or null | Codex session UUID from session_meta event |
model | string or null | Model from turn_context event (prefers collaboration_mode.settings.model) |
status | string or null | Agent status (see status values below) |
input_tokens | integer or null | Cumulative input tokens (latest token_count event) |
output_tokens | integer or null | Cumulative output tokens |
cache_write_tokens | null | Not exposed in the Codex rollout format |
cache_read_tokens | integer or null | Cumulative cached input tokens |
context_tokens | integer or null | input + cached + output + reasoning from most recent token_count |
context_window | integer or null | Model context window from model_context_window field |
context_fill_percent | float or null | context_tokens / context_window * 100 |
cost_usd | float or null | Estimated cost (input + cached + output + reasoning at output rate) |
cost_is_estimated | boolean | true when model is known; false when no model seen yet |
pending_tool | string or null | Name of the most recent unresolved function_call or custom_tool_call |
decay_score | null | Not computed for Codex sessions |
subagent_count | integer | Always 0 — Codex does not use subagent task files |
last_event_timestamp | null | Not available in the Codex rollout format |
Pricing table is pinned at 2026-04-21 and covers: gpt-5-codex, gpt-5,
o4-mini, o4, o3-mini, o3, gpt-4.1, gpt-4o, gpt-4.
Model lookup uses longest-prefix matching. All costs are estimates.
status values
| Value | Meaning |
|---|---|
"processing" | CPU usage above 5% — model is actively generating |
"needs_input" | Last turn stopped with tool_use and a tool result is pending |
"waiting_input" | Last turn stopped with end_turn; a recent event was seen |
"idle" | Last turn stopped with end_turn; no recent events |
"unknown" | Not enough information to classify |
Privacy
The telemetry object intentionally omits all transcript content.
The --json output never includes message text, tool arguments, or any
other content from the conversation transcript, regardless of what the TUI
detail view displays.
This promise is enforced at the serialization boundary in src/output/json.rs:
TelemetryJson (the serialized form) is a separate type from AgentTelemetry
(the in-memory form). The conversion function build_telemetry_json() only
copies aggregated counters, never the recent_messages field.
Example (pretty-printed)
{
"schema_version": 1,
"generated_at": "2026-04-21T13:45:00Z",
"agentop_version": "0.7.1",
"system": {
"cpu_usage_percent": 42.1,
"total_memory_bytes": 17179869184,
"used_memory_bytes": 8421376,
"total_swap_bytes": 4294967296,
"used_swap_bytes": 0,
"cpu_count": 10
},
"agent_summary": {
"claude_count": 2,
"codex_count": 1,
"total_cpu_percent": 15.2,
"total_memory_bytes": 2147483648
},
"processes": [
{
"pid": 5678,
"parent_pid": null,
"kind": "codex",
"name": "codex",
"display_name": "codex",
"cmd": ["codex"],
"exe_path": "/usr/local/bin/codex",
"cwd": "/Users/me/project",
"cpu_percent": 1.1,
"memory_bytes": 524288000,
"status": "Run",
"start_time_unix": 1714000200,
"uptime_seconds": 100,
"activity_state": "idle",
"activity_state_seconds": 60,
"telemetry": {
"provider": "codex",
"session_id": "019d2d54-207e-7b80-b98e-2968e4d1204a",
"model": "gpt-5",
"status": "waiting_input",
"input_tokens": 25000,
"output_tokens": 1800,
"cache_write_tokens": null,
"cache_read_tokens": 3000,
"context_tokens": 29800,
"context_window": 258400,
"context_fill_percent": 11.5,
"cost_usd": 0.19,
"cost_is_estimated": true,
"pending_tool": null,
"decay_score": null,
"subagent_count": 0,
"last_event_timestamp": null
},
"children": []
},
{
"pid": 1234,
"parent_pid": null,
"kind": "claude",
"name": "claude",
"display_name": "claude",
"cmd": ["claude", "--resume", "abc"],
"exe_path": "/usr/local/bin/claude",
"cwd": "/Users/me/project",
"cpu_percent": 3.4,
"memory_bytes": 1073741824,
"status": "Run",
"start_time_unix": 1714000000,
"uptime_seconds": 300,
"activity_state": "active",
"activity_state_seconds": 42,
"telemetry": {
"provider": "claude",
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"model": "claude-opus-4-7",
"status": "idle",
"input_tokens": 42000,
"output_tokens": 3100,
"cache_write_tokens": 12000,
"cache_read_tokens": 28000,
"context_tokens": 82000,
"context_window": 1000000,
"context_fill_percent": 8.2,
"cost_usd": 0.87,
"cost_is_estimated": true,
"pending_tool": null,
"decay_score": 3,
"subagent_count": 2,
"last_event_timestamp": "2026-04-21T13:44:55Z"
},
"children": [
{
"pid": 1235,
"parent_pid": 1234,
"kind": null,
"name": "node",
"display_name": "node",
"cmd": ["node", "/path/to/script.js"],
"exe_path": "/usr/local/bin/node",
"cwd": "/Users/me/project",
"cpu_percent": 1.2,
"memory_bytes": 536870912,
"status": "Run",
"start_time_unix": 1714000010,
"uptime_seconds": 290,
"activity_state": null,
"activity_state_seconds": null,
"telemetry": null,
"children": []
}
]
}
]
}
List mode
agentop --list
Prints a plain-text process table to stdout and exits. Matches the TUI's default view minus the sparkline.
Example:
PID NAME CPU% MEM STATUS UPTIME
-------- --------------------------------------- -------- ---------- ---------- ----------
31133 ? ▼ claude 6.9% 840.8 MB Runnable 0d 1h 29m
15939 ├─ ▼ zsh 0.0% 3.0 MB Runnable 0m 0s
15943 ├─ head 0.0% 1.2 MB Runnable 0m 0s
15942 └─ agentop 1.4% 11.0 MB Runnable 0m 0s
...
Behaviour
- Width adapts to the TTY when stdout is a terminal
- Piped output (non-TTY) uses a 120-column default and emits no ANSI escapes
- Two sysinfo refreshes 500 ms apart are performed before printing, so CPU deltas are meaningful
- Exit status is
0on success; non-zero only on fatal error
When to use
- Grep-able snapshots of running agent sessions
- Terminal-less invocations (CI, remote
sshwith no controlling tty) - Quick pipelines:
agentop --list | awk '$3 > 10.0 {print}'
For richer data (cost, tokens, context %), use --json with
jq.
Shell completions
agentop emits completion scripts for the most common shells. The scripts go to stdout; redirect them to whatever your shell reads.
Supported shells
| Shell | Generate |
|---|---|
| bash | agentop --generate-completions bash |
| zsh | agentop --generate-completions zsh |
| fish | agentop --generate-completions fish |
| powershell | agentop --generate-completions powershell |
| elvish | agentop --generate-completions elvish |
Installing
zsh
mkdir -p ~/.zsh/completions
agentop --generate-completions zsh > ~/.zsh/completions/_agentop
Then in your ~/.zshrc, before compinit:
fpath=(~/.zsh/completions $fpath)
autoload -U compinit && compinit
bash
mkdir -p ~/.local/share/bash-completion/completions
agentop --generate-completions bash > ~/.local/share/bash-completion/completions/agentop
fish
agentop --generate-completions fish > ~/.config/fish/completions/agentop.fish
How it works
The scripts are generated by clap_complete
from the same CLI definition the binary uses. They stay in sync with the
real flags automatically.
Terminal support matrix
Pressing Tab in the detail view jumps focus to the terminal or pane
hosting the selected process's TTY. Here's the current state of
integration.
| Terminal | Detected via | Jump action | Status |
|---|---|---|---|
| tmux | $TMUX environment variable | tmux select-pane -t <tty> | ✅ supported |
| iTerm2 (macOS) | TERM_PROGRAM == "iTerm.app" | AppleScript via osascript | ✅ supported |
| Kitty | $KITTY_WINDOW_ID set | kitten @ focus-window --match tty:<tty> | ✅ supported |
| WezTerm | $WEZTERM_EXECUTABLE set | wezterm cli activate-pane --pane-id <id> | ⚠ detected; jump stubbed |
| Anything else | — | — | ❌ not supported; Tab shows a status-bar message |
Why WezTerm is stubbed
WezTerm's wezterm cli list exposes pane metadata but not a direct
TTY → pane-id mapping. A reliable lookup requires walking the process
table inside the pane, which is orthogonal work we haven't tackled yet.
Detection is in place so the feature works the moment we finish the
mapping.
Failure UX
- Unknown terminal: 3-second status-bar flash —
Cannot jump: terminal not detected (iTerm2/tmux/kitty/wezterm supported). - Self-jump (agentop is already running in the target pane): brief
flash —
This session is already in focus. No-op. - Command failed (e.g.
tmuxnot on$PATH): flash —Failed to jump: <reason>. - Success: flash —
Jumped to <terminal> <pane-id>.
Adding a new terminal
New adapters live under src/terminals/. Each implements the
TerminalAdapter trait with three methods:
name(&self)— the display name shown in status messagescurrent_target(&self)— returnsSome(TerminalTarget)if the adapter is running us nowfocus_by_tty(&self, tty: &str)— shells out to the terminal's CLI
Contributions are welcome. See Contributing.
Privacy and security
agentop is designed around a hard read-only boundary. This chapter explains exactly what the tool reads, what it writes, and what it deliberately does not do.
What we read
| Source | Used for |
|---|---|
sysinfo / ps / /proc | CPU, memory, command line, working directory, PID tree |
~/.claude/sessions/*.json | PID → Claude session correlation |
~/.claude/projects/**/*.jsonl | Claude transcripts, for tokens / cost / pending tool |
/tmp/claude-{uid}/**/tasks/ | Claude subagent count |
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl | Codex rollout events, for tokens / cost / pending tool |
/proc/{pid}/environ (Linux) / ps eww (macOS) | CODEX_HOME override per PID |
All reads are on files your user already owns and can read. No elevated privileges, no setuid bits, no platform-specific escalation.
What we write
| Path | Contents |
|---|---|
$XDG_CONFIG_HOME/agentop/config.toml | Persisted theme + graph-style preferences |
| stdout / stderr | When invoked with --list, --json, --generate-completions, or any diagnostic flag |
That's the complete list. We do not:
- Install or modify Claude Code / Codex CLI hooks, commands, skills, plugins, or configuration
- Write to
~/.claude/,~/.codex/, or any path outside~/.config/agentop/ - Spawn subprocesses except when the user explicitly triggers
x(kill), theTabterminal-jump (shells out totmux/osascript/kitten), or the one-shot macOSps ewwread for environment parsing
No network
agentop makes zero outbound network requests. It has no telemetry, no
update checker, no error reporter, and no license server. cargo-deny is
configured to deny openssl-sys, openssl, and native-tls in the
dependency graph to close the door on accidental HTTP via a transitive dep.
JSON output boundary
--json emits only aggregate counters and metadata. It never
serialises transcript content, tool arguments, tool outputs, assistant
messages, or anything else that could contain user-authored or
model-authored text.
Specifically:
- Claude
message.content[]blocks are never serialised - Codex
response_item.payloadcontent is never serialised - Tool
input/argumentsJSON is never serialised - Tool
outputis never serialised
What IS serialised: token counts, cost estimates, context fractions, pending tool names, last stop reason strings, subagent counts, session IDs, model names, working directories, and PIDs.
When to worry
If you're shipping agentop --json output across a trust boundary (into a
log aggregator, a chat channel, a ticket system), verify it against your
organisation's policy. Working-directory paths and PIDs may themselves be
sensitive in some environments. See JSON schema for the
full field list.
Disclosure
Report a suspected vulnerability via the process in SECURITY.md rather than in a public issue.
Stability and compatibility
From 1.0.0 onwards agentop follows Semantic Versioning. Post-1.0 breaking changes bump the major version.
Stable surfaces
These are covered by the version contract. A breaking change here requires a major-version bump.
CLI flags
Every flag documented in agentop --help and in the
Installation / Quickstart chapters. New
flags may be added at any time; existing flags retain their names and
semantics.
stdout / stderr conventions
agentop --jsonwrites one line of JSON to stdout when--prettyis not set (NDJSON-compatible). With--pretty, indented JSONagentop --listwrites a tabular text representation to stdoutagentop --generate-completions <shell>writes a completion script to stdout- The TUI writes only to the alternate screen; terminal scrollback is never modified
--json schema
Currently schema_version = 1. The schema file at
docs/output-schema.md
is the authoritative reference.
- Additive changes (new optional fields) do not bump
schema_version - Breaking changes (removing, renaming, or re-typing any field) bump
schema_versionto 2 (and beyond) - Every release documents its schema version in the changelog
Exit codes
0— success- non-zero — fatal error (process not launched, terminal init failed, argument parse failed)
Config file
$XDG_CONFIG_HOME/agentop/config.toml with its documented keys
(theme, graph_style). New keys may be added; existing keys and their
accepted values are stable.
Unstable surfaces
These may change without a major-version bump.
TUI keybindings
Labelled unstable. We may remap keys to fit new features. The ? help
overlay always shows the current bindings. If your muscle memory breaks
during a minor-version upgrade, the help overlay tells you what moved.
Library API (src/lib.rs)
The binary is the product. The crate's library target exists only to share
types between src/main.rs and the integration tests. Nothing under
agentop:: is part of the public API.
Telemetry internals
- The schema of
AgentTelemetryas serialised in--jsonis stable - The providers' heuristics are not: status-inference rules, context fraction formulas, idle-tier thresholds, and pricing-table entries may evolve to track Claude / Codex behavioural changes
Cognitive-decay score
Currently a context-saturation proxy. We may replace the formula with a
richer one as signals become available. The score bar will keep rendering
in the same place, but an arbitrary value of 37 today may correspond to
52 under the new formula.
Pricing audit cadence
The Claude and Codex pricing tables are pinned with *_PRICING_AUDITED_AT
constants:
CLAUDE_PRICING_AUDITED_ATinsrc/telemetry/claude/pricing.rsCODEX_PRICING_AUDITED_ATinsrc/telemetry/codex/pricing.rs
We re-audit before every minor release. If a provider's pricing changes
between audits, cost estimates drift. Every cost_usd we emit carries
cost_is_estimated: true as an explicit acknowledgement.
Platform support
- macOS (x86_64 and aarch64) — tier 1
- Linux (x86_64 and aarch64, gnu and musl) — tier 1
- Windows — not supported
The Homebrew tap, AUR package, and Nix flake all reflect this matrix.
Reporting regressions
Open an issue at https://github.com/leboiko/claude-codex-pid-inspector/issues. Include:
agentop --version- OS + kernel/darwin version
- Terminal emulator
- The offending command line or key sequence
- Whether
--jsonschema was affected (if yes, tag withschema)
Contributing
See CONTRIBUTING.md in the repository for the canonical guide — dev setup, PR checklist, project layout, and release process.
Fast path for newcomers
git clone https://github.com/leboiko/claude-codex-pid-inspector.git
cd claude-codex-pid-inspector
cargo test
cargo run
Run the same checks CI does:
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test --all-features
cargo deny check
Code of conduct
This project follows the Contributor Covenant 2.1.
Reporting bugs
Open an issue with:
- The output of
agentop --version - Your OS and terminal emulator
- Steps to reproduce
- What you expected versus what happened
For sensitive issues, follow SECURITY.md
rather than the public tracker.
Changelog
Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
Unreleased
1.0.0 - 2026-04-21
First stable release. Starting from this tag, the project follows
Semantic Versioning for its
public CLI, stdout / stderr conventions, --json schema
(currently schema_version = 1), exit codes, and config-file
layout. TUI keybindings, library internals, and the cognitive-decay
formula remain explicitly unstable — see the
Stability chapter
for the full contract.
Added
- Documentation site at https://leboiko.github.io/claude-codex-pid-inspector/
— mdBook-based reference covering installation, TUI keybindings, telemetry
providers, the
--jsonschema, terminal support matrix, privacy model, and the stability policy. Built on every push tomastervia the new.github/workflows/docs.yml. - Packaging templates for Homebrew (
packaging/homebrew/agentop.rb.tmpl) and AUR (packaging/aur/PKGBUILD-bin.tmpl), plus render scripts underscripts/that stamp them with the release version and checksums. The release workflow auto-publishes the Homebrew formula toleboiko/homebrew-tapwhen aHOMEBREW_TAP_TOKENsecret is configured; skipped gracefully otherwise. - Nix flake (
flake.nix) exposing the binary vianix runand a dev shell with Rust, cargo-deny, cargo-audit, and mdBook pre-installed. - Explicit stability policy: the new
book/src/stability.mdchapter documents which surfaces follow semver (CLI flags, stdout conventions,--jsonschema, exit codes, config file) and which are explicitly unstable (TUI keybindings, library internals, cognitive-decay formula).README.mdgains a short Stability section linking to the full chapter. - Curated focus filters (
Fkey, cycles): five lenses selectable in the tree view —all(default, no filter),attention(statusNeedsInputor context ≥ 80%),high-cpu(CPU ≥ 30%),high-context(context ≥ 80%),recent(started within the last 10 minutes). PressingFclears the free-text search; starting/input resets the lens toall. A filter pillFILTER: <label> (N/M)is now always visible in the status bar so the active lens is always apparent. - Project grouping (
gkey, toggle): re-organises the tree view so all sessions sharing the same working directory appear under a group header row (▼ project-slug N sessions · $X.XX · ctx YY% avg). Groups are sorted most-recently-active first; empty groups (all members filtered out) are hidden.SpaceorEnteron a header row collapses or expands the group. Grouping, the curated filter, and the agent view (T) are independent toggles. - Help overlay (
?key): centered modal (70% × 80% terminal) listing all keybindings in six categories — Navigation, View, Filter, Sort, Action, System.Esccloses; any other bound key closes the overlay and then executes the action. - Terminal-tab jump (
Tabin detail view): focuses the terminal pane that owns the selected process's TTY. Supported terminals: tmux (detected via$TMUX), Kitty (via$KITTY_WINDOW_ID), iTerm2 on macOS (viaTERM_PROGRAM=iTerm.app). WezTerm is detected but not yet implemented (returns a descriptive message). A 3-second status-bar flash reports success (Jumped to tmux session:1.0) or failure. TheTab jump to terminalfooter hint is shown only when a supported terminal is detected.Tabin the tree view is unchanged (cycles sort column). - Codex CLI telemetry (Phase 3): each detected Codex CLI root process is
now enriched with data read incrementally from its on-disk rollout file
(
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl). Exposed in the TUI, detail panel, and--jsonoutput:- cumulative input/output/cache tokens and an estimate of context window
utilisation derived from the
token_countevents in the rollout file - a cost-in-USD estimate from a pricing table pinned at
2026-04-21covering GPT-5, o4-mini, o4, o3-mini, o3, GPT-4.1, GPT-4o, and GPT-4 model families; all costs flagged as estimated - the name of the pending tool call when a
function_callorcustom_tool_callhas no matching output yet kind: "codex"in--jsonoutput (Claude sessions emitkind: "claude")CODEX_HOMEresolution from the live process environment (macOSps eww, Linux/proc/{pid}/environ) with fallback to~/.codex- PID-to-rollout-file correlation via filename timestamp (±90 s) — no pidfile required
- Incremental tailing: only new bytes are read each tick; file truncation (session restart) resets aggregates automatically
- Privacy: tool
argumentsand tooloutputfields are never deserialized or stored
- cumulative input/output/cache tokens and an estimate of context window
utilisation derived from the
- Claude telemetry: each detected Claude Code root process is now enriched
with data read from its on-disk state (
~/.claude/sessions/{pid}.jsonand~/.claude/projects/**/*.jsonl). Exposed in the TUI, detail panel, and--jsonoutput:- cumulative input/output/cache tokens and an estimate of the current context window utilisation
- a cost-in-USD estimate derived from a pricing table pinned at
2026-04-21(Opus 4.7, Sonnet 4.x, Haiku 4.5) — costs are flagged as estimated - the name of the pending tool call (if the session is awaiting approval),
the last model
stop_reason, and the count of active subagent tasks - an inferred
AgentStatus(processing / needs_input / waiting_input / idle) combining CPU pressure with transcript signals
- Agent view (
Tkey): swapsCPU%/Memory/CommandforCtx%/Cost/Tokens/Toolcolumns so the agent-specific telemetry is first-class in the table. Base view remains the default and is unchanged. Status bar and footer show when the agent view is active. - Pending-tool badge: a
⚑badge appears in the Name cell next to the activity badge when a Claude session is awaiting tool-call approval. - Extended detail panel for Claude sessions: token stats, a 20-cell
decay-score bar (
Decay NN), the pending tool call's name and truncated input, a scrollable list of recent assistant messages, and a subagent summary. Non-Claude rows keep their existing detail layout. telemetrysub-object added to each process entry in the--jsonschema (additive;schema_versionstays at1). Documented indocs/output-schema.mdtogether with the privacy guarantee: we serialize only aggregate counters, never transcript content.- Char-safe
truncate_charshelper inui/format.rsfor clipping user-facing text without risking a panic mid-grapheme. --listflag: prints a plain-text process table (PID, NAME, CPU%, MEM, STATUS, UPTIME) to stdout and exits. The NAME column uses box-drawing tree connectors matching the TUI view. Width adapts to the terminal when stdout is a TTY, falls back to 120 columns when piped.--jsonflag: prints a versioned JSON snapshot (schema_version=1) to stdout and exits.--prettyadds indentation. The schema includes system stats, an agent summary, and the full recursive process tree with activity state. Seedocs/output-schema.mdfor the field reference. Stability promise:schema_version=1will never remove or rename fields; additive changes are allowed without a version bump; breaking changes incrementschema_versionto 2.--generate-completions <shell>flag: prints a shell-completion script for bash, zsh, fish, powershell, or elvish to stdout and exits. Usesclap_complete. See README for per-shell install instructions.- 3-sample rolling CPU average in
ProcessScanner: raw per-tick CPU readings are averaged over a 3-sample window per PID. The smoothed value flows into sparklines and idle/active classification, reducing noise from transient spikes. - Time-in-state color escalation for idle root processes: idle badges now
change color based on how long the process has been continuously idle
(Fresh <60s, Warning 60s–5m, Stale >5m). Warning and Stale tiers append
a duration suffix to the badge (e.g.
○(3m),○(12m)) for accessibility. docs/output-schema.md: full field reference, stability promise, and an example JSON snapshot.- New dependencies:
clap4,clap_complete4,serde_json1,time0.3 (all MIT/Apache-2.0 licensed). - Internal
TelemetryProvidertrait andTelemetryPipelinerunner as the foundation for per-agent telemetry enrichment (tokens, cost, context %) in later releases. No user-visible behavior change. - Continuous integration via GitHub Actions: formatting, Clippy (
-D warnings), tests, doc build,cargo-deny, andcargo-auditacross Ubuntu and macOS, on stable and MSRV. - Release workflow that cross-builds tagged versions for
x86_64/aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl, andx86_64/aarch64-apple-darwin, with SHA-256 checksums. - Supply-chain guardrails:
deny.tomllicense/advisory/source policy, Dependabot,SECURITY.mddisclosure policy. - Contributor scaffolding:
CONTRIBUTING.md,CODE_OF_CONDUCT.md, issue templates, pull request template.
Changed
- Arg parsing migrated from hand-rolled
std::env::argstoclapderive macros.--version/-V,--help/-hbehavior is preserved;--jsonand--listare mutually exclusive via a clapArgGroup. - Pinned MSRV to Rust 1.85.
Fixed
- Transcript preview no longer panics when the 80-character truncation
boundary lands inside a multi-byte UTF-8 sequence (e.g. an em dash). A
regression test in
telemetry::claude::parserguards the fix.
0.7.1 - 2026-04-13
Fixed
- Chart background now uses the active theme color instead of the terminal's default background, so light themes render correctly.
0.7.0 - 2026-04-12
Added
- Idle / active classification for agent root processes, based on a rolling CPU-usage window.
- Search and filter via
/, with parent-chain preservation so matching children still show their parent process. - Subtree statistics aggregation and an agent summary shown in the status bar.
- Light themes with full-coverage background colors.