Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Requires macOS or Linux. Windows is not supported.

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-gnu
  • aarch64-unknown-linux-gnu
  • x86_64-unknown-linux-musl
  • x86_64-apple-darwin
  • aarch64-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:

FilterWhat it shows
allevery process (default)
attentionsessions with a pending tool call or ≥ 80% context
high-cpusmoothed CPU ≥ 30%
high-contextcontext ≥ 80%
recentstarted 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)

KeyAction
q, Ctrl+CQuit
, kMove selection up
, jMove selection down
EnterOpen detail view for selected row
SpaceExpand / collapse selected node (or group when grouping is on)
TabCycle sort column forward
Shift+TabCycle sort column backward
sToggle sort direction
xKill selected process (with confirmation)
cOpen config popup
/Enter free-text filter
FCycle curated focus filter
gToggle project grouping
TToggle agent view (Ctx% / Cost / Tokens / Tool columns)
?Show help overlay
zClear any active filter

Detail view

KeyAction
EscReturn to tree view
q, Ctrl+CQuit
xKill selected process (with confirmation)
cOpen config popup
TabJump to the terminal hosting this PID (tmux, iTerm2, Kitty)
?Show help overlay

While the help overlay is open

KeyAction
EscClose the overlay
Any other bound keyClose, then execute that key's action

While the free-text filter bar is active

KeyAction
EscClear filter and dismiss the bar
BackspaceDelete one character
EnterOpen detail view for highlighted row
↑↓, j/kMove selection (search results scroll under the bar)
Other printable charsAppended 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

GlyphMeaning
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 IdleTierColourText suffix
0 – 60 sFreshgray(none)
60 s – 5 minWarningyellow(3m)
> 5 minStalered, 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.

ValueColour
< 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.

ValueColour
< $1green
$1 – $5yellow
> $5red

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

PathWhat we readWhy
~/.claude/sessions/{pid}.jsonPID → session metadata (session ID, cwd, start time, version)Direct PID correlation; no fuzzy matching
~/.claude/projects/{cwd-slug}/{sessionId}.jsonlTranscript eventsToken counts, tool calls, stop reasons
/tmp/claude-{uid}/{cwd-slug}/{sessionId}/tasks/Subagent task filesCount 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_tool if the last assistant message ended in tool_use

Pricing

Hardcoded in src/telemetry/claude/pricing.rs with a CLAUDE_PRICING_AUDITED_AT constant.

Model prefixInputOutputCache writeCache readWindow
claude-opus-4-7 (1M)$15$75$18.75$1.501 000 000
claude-opus-4-*$15$75$18.75$1.50200 000
claude-sonnet-4-*$3$15$3.75$0.30200 000
claude-haiku-4-*$1$5$1.25$0.10200 000
fallbackSonnet rates200 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:

  1. CPU > 5% → processing
  2. pending_tool is Some → needs_input
  3. Last assistant stop_reason == "end_turn" and last event > 10 min ago → idle
  4. Last assistant stop_reason == "end_turn" and recent → waiting_input
  5. 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

PathWhat we read
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonlSession 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 cumulative
  • last_token_usage.input_tokensthe live context signal (cumulative total_tokens grows unbounded across turns and would report >100% on any long session)
  • model_context_window

From each response_item.payload:

  • function_call / custom_tool_call events increment the unresolved call-id set
  • function_call_output / custom_tool_call_output clear it
  • If the last response item is an unresolved call, pending_tool gets 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

FieldReason
cache_write_tokensCodex rollout exposes cached-input but not cache-write separately
decay_scoreThe decay formula relies on error-rate and file-edit signals Codex doesn't expose
subagent_countCodex has no subagent task files
last_stop_reasonCodex 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

FieldTypeDescription
schema_versionintegerAlways 1 for this schema
generated_atstringRFC 3339 UTC timestamp (2026-04-21T13:45:00Z)
agentop_versionstringSemVer string from Cargo.toml
systemobjectSystem-wide resource snapshot (see below)
agent_summaryobjectAggregate counts and usage (see below)
processesarrayRoot-level agent processes (see below)

system object

FieldTypeDescription
cpu_usage_percentfloatGlobal CPU percent across all cores
total_memory_bytesintegerTotal installed physical memory
used_memory_bytesintegerCurrently resident memory
total_swap_bytesintegerTotal swap space
used_swap_bytesintegerCurrently used swap
cpu_countintegerNumber of logical CPUs

agent_summary object

FieldTypeDescription
claude_countintegerNumber of Claude Code root processes
codex_countintegerNumber of Codex CLI root processes
total_cpu_percentfloatTotal CPU % across all agent subtrees
total_memory_bytesintegerTotal 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.

FieldTypeDescription
pidintegerProcess ID
parent_pidinteger or nullParent PID; null for root entries
kindstring or null"claude", "codex", or null
namestringShort OS process name
display_namestringFriendly name (e.g. "claude" even when OS name is "node")
cmdstring[]Full argv split into tokens
exe_pathstring or nullAbsolute path to executable
cwdstring or nullWorking directory
cpu_percentfloatCPU % (3-sample rolling average)
memory_bytesintegerResident memory in bytes
statusstringOS status string ("Run", "Sleep", etc.)
start_time_unixintegerUnix epoch timestamp when the process started
uptime_secondsintegerSeconds the process has been running
activity_statestring or null"active", "idle", "unknown", or null (non-roots)
activity_state_secondsinteger or nullSeconds in the current activity state; null for non-roots
telemetryobject or nullProvider-specific session telemetry (see below); null when no provider is active
childrenarraySame shape, recursive

activity_state values

ValueMeaning
"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
nullThis 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.

FieldTypeDescription
providerstringAlways "claude"
session_idstring or nullClaude session UUID
modelstring or nullModel identifier from the most recent turn
statusstring or nullAgent status (see status values below)
input_tokensintegerCumulative input tokens for this session
output_tokensintegerCumulative output tokens for this session
cache_write_tokensintegerCumulative cache-write tokens
cache_read_tokensintegerCumulative cache-read tokens
context_tokensinteger or nullContext token estimate from the most recent turn
context_windowinteger or nullModel context window size (from pricing table)
context_fill_percentfloat or nullcontext_tokens / context_window * 100
cost_usdfloatAccumulated session cost in USD (estimated)
cost_is_estimatedbooleanAlways true — cost is derived from a local pricing table
pending_toolstring or nullName of the tool currently awaiting a result, if any
decay_scoreinteger or nullContext-health score 0–100 (higher = more pressure)
subagent_countintegerNumber of subagent task files found under /tmp/claude-{uid}/...
last_event_timestampstring or nullRFC 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.

FieldTypeDescription
providerstringAlways "codex"
session_idstring or nullCodex session UUID from session_meta event
modelstring or nullModel from turn_context event (prefers collaboration_mode.settings.model)
statusstring or nullAgent status (see status values below)
input_tokensinteger or nullCumulative input tokens (latest token_count event)
output_tokensinteger or nullCumulative output tokens
cache_write_tokensnullNot exposed in the Codex rollout format
cache_read_tokensinteger or nullCumulative cached input tokens
context_tokensinteger or nullinput + cached + output + reasoning from most recent token_count
context_windowinteger or nullModel context window from model_context_window field
context_fill_percentfloat or nullcontext_tokens / context_window * 100
cost_usdfloat or nullEstimated cost (input + cached + output + reasoning at output rate)
cost_is_estimatedbooleantrue when model is known; false when no model seen yet
pending_toolstring or nullName of the most recent unresolved function_call or custom_tool_call
decay_scorenullNot computed for Codex sessions
subagent_countintegerAlways 0 — Codex does not use subagent task files
last_event_timestampnullNot 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

ValueMeaning
"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 0 on success; non-zero only on fatal error

When to use

  • Grep-able snapshots of running agent sessions
  • Terminal-less invocations (CI, remote ssh with 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

ShellGenerate
bashagentop --generate-completions bash
zshagentop --generate-completions zsh
fishagentop --generate-completions fish
powershellagentop --generate-completions powershell
elvishagentop --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.

TerminalDetected viaJump actionStatus
tmux$TMUX environment variabletmux select-pane -t <tty>✅ supported
iTerm2 (macOS)TERM_PROGRAM == "iTerm.app"AppleScript via osascript✅ supported
Kitty$KITTY_WINDOW_ID setkitten @ focus-window --match tty:<tty>✅ supported
WezTerm$WEZTERM_EXECUTABLE setwezterm 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. tmux not 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 messages
  • current_target(&self) — returns Some(TerminalTarget) if the adapter is running us now
  • focus_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

SourceUsed for
sysinfo / ps / /procCPU, memory, command line, working directory, PID tree
~/.claude/sessions/*.jsonPID → Claude session correlation
~/.claude/projects/**/*.jsonlClaude transcripts, for tokens / cost / pending tool
/tmp/claude-{uid}/**/tasks/Claude subagent count
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonlCodex 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

PathContents
$XDG_CONFIG_HOME/agentop/config.tomlPersisted theme + graph-style preferences
stdout / stderrWhen 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), the Tab terminal-jump (shells out to tmux / osascript / kitten), or the one-shot macOS ps eww read 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.payload content is never serialised
  • Tool input / arguments JSON is never serialised
  • Tool output is 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 --json writes one line of JSON to stdout when --pretty is not set (NDJSON-compatible). With --pretty, indented JSON
  • agentop --list writes a tabular text representation to stdout
  • agentop --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_version to 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 AgentTelemetry as serialised in --json is 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_AT in src/telemetry/claude/pricing.rs
  • CODEX_PRICING_AUDITED_AT in src/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 --json schema was affected (if yes, tag with schema)

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 --json schema, terminal support matrix, privacy model, and the stability policy. Built on every push to master via 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 under scripts/ that stamp them with the release version and checksums. The release workflow auto-publishes the Homebrew formula to leboiko/homebrew-tap when a HOMEBREW_TAP_TOKEN secret is configured; skipped gracefully otherwise.
  • Nix flake (flake.nix) exposing the binary via nix run and a dev shell with Rust, cargo-deny, cargo-audit, and mdBook pre-installed.
  • Explicit stability policy: the new book/src/stability.md chapter documents which surfaces follow semver (CLI flags, stdout conventions, --json schema, exit codes, config file) and which are explicitly unstable (TUI keybindings, library internals, cognitive-decay formula). README.md gains a short Stability section linking to the full chapter.
  • Curated focus filters (F key, cycles): five lenses selectable in the tree view — all (default, no filter), attention (status NeedsInput or context ≥ 80%), high-cpu (CPU ≥ 30%), high-context (context ≥ 80%), recent (started within the last 10 minutes). Pressing F clears the free-text search; starting / input resets the lens to all. A filter pill FILTER: <label> (N/M) is now always visible in the status bar so the active lens is always apparent.
  • Project grouping (g key, 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. Space or Enter on 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. Esc closes; any other bound key closes the overlay and then executes the action.
  • Terminal-tab jump (Tab in 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 (via TERM_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. The Tab jump to terminal footer hint is shown only when a supported terminal is detected. Tab in 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 --json output:
    • cumulative input/output/cache tokens and an estimate of context window utilisation derived from the token_count events in the rollout file
    • a cost-in-USD estimate from a pricing table pinned at 2026-04-21 covering 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_call or custom_tool_call has no matching output yet
    • kind: "codex" in --json output (Claude sessions emit kind: "claude")
    • CODEX_HOME resolution from the live process environment (macOS ps 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 arguments and tool output fields are never deserialized or stored
  • Claude telemetry: each detected Claude Code root process is now enriched with data read from its on-disk state (~/.claude/sessions/{pid}.json and ~/.claude/projects/**/*.jsonl). Exposed in the TUI, detail panel, and --json output:
    • 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 (T key): swaps CPU%/Memory/Command for Ctx%/Cost/Tokens/Tool columns 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.
  • telemetry sub-object added to each process entry in the --json schema (additive; schema_version stays at 1). Documented in docs/output-schema.md together with the privacy guarantee: we serialize only aggregate counters, never transcript content.
  • Char-safe truncate_chars helper in ui/format.rs for clipping user-facing text without risking a panic mid-grapheme.
  • --list flag: 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.
  • --json flag: prints a versioned JSON snapshot (schema_version=1) to stdout and exits. --pretty adds indentation. The schema includes system stats, an agent summary, and the full recursive process tree with activity state. See docs/output-schema.md for the field reference. Stability promise: schema_version=1 will never remove or rename fields; additive changes are allowed without a version bump; breaking changes increment schema_version to 2.
  • --generate-completions <shell> flag: prints a shell-completion script for bash, zsh, fish, powershell, or elvish to stdout and exits. Uses clap_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: clap 4, clap_complete 4, serde_json 1, time 0.3 (all MIT/Apache-2.0 licensed).
  • Internal TelemetryProvider trait and TelemetryPipeline runner 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, and cargo-audit across 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, and x86_64/aarch64-apple-darwin, with SHA-256 checksums.
  • Supply-chain guardrails: deny.toml license/advisory/source policy, Dependabot, SECURITY.md disclosure policy.
  • Contributor scaffolding: CONTRIBUTING.md, CODE_OF_CONDUCT.md, issue templates, pull request template.

Changed

  • Arg parsing migrated from hand-rolled std::env::args to clap derive macros. --version / -V, --help / -h behavior is preserved; --json and --list are mutually exclusive via a clap ArgGroup.
  • 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::parser guards 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.