Activity Detection
Kangentic monitors each agent’s activity in real-time, so you always know what your agents are doing without opening their terminals. All fourteen supported agents (Claude Code, Codex CLI, Gemini CLI, Cursor CLI, GitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Aider, Qwen Code, Kimi Code, Factory Droid, Warp/Oz CLI, and Ollama) feed into a single activity state machine that drives the card indicators, but each uses a different underlying strategy because each CLI exposes activity differently.
Agent States
Section titled “Agent States”The Kanban board shows each agent’s current state with visual indicators. These update in real-time, giving you an at-a-glance view of your entire agent fleet.
| State | Description |
|---|---|
| Thinking (pulsing animation) | Agent is processing, running tools, or generating a response |
| Idle (static icon) | Agent has stopped, hit a permission prompt, or is asking a question |
| Queued (label) | Waiting for a concurrent session slot to open |
Event Types
Section titled “Event Types”Kangentic tracks these events from each agent session. They feed into both the card indicators and the Activity Tab in the bottom panel.
User-Facing Events
Section titled “User-Facing Events”| Event | Key | Effect on Indicator | Description |
|---|---|---|---|
| Tool start | tool_start | Thinking | Agent began using a tool (Read, Edit, Bash, etc.) |
| Tool end | tool_end | (no change) | Tool execution completed |
| Prompt | prompt | Thinking | User submitted a message or the agent resumed after approval |
| Idle | idle | Idle | Agent stopped, hit a permission wall, or asked a question |
| Interrupted | interrupted | Idle | User interrupted the agent (Escape or interrupt signal) |
Internal Events
Section titled “Internal Events”These events are logged but don’t directly change the card indicator - they support subagent tracking, context management, and system-level operations. Not every agent emits every event (see per-agent strategies below).
| Event | Key | Effect on Indicator | Emitted by | Description |
|---|---|---|---|---|
| Session start | session_start | (no change) | All | Session was created or resumed |
| Session end | session_end | (no change) | All | Session exited |
| Notification | notification | (no change) | Claude, Gemini, Grok, Qwen, Kimi | Informational message from the agent (e.g., “context getting full”). Grok also reports a denied tool call here |
| Turn failed | turn_failed | Idle | Claude, Grok | The turn ended on a service or API error (rate limit, overload, server error) instead of a normal stop, sourced from the CLI’s StopFailure hook. Treated like an interrupt: resets in-flight counters and commits idle immediately, with the error type carried in the event detail so the log reads “service error” |
| Turn retrying | turn_retrying | Thinking (conditional) | Claude, Grok | A transient, auto-retried API error (overloaded, server error, rate limit) that the CLI is retrying rather than a final abort. Unlike turn_failed, it keeps a genuinely live turn in the thinking state through the retry backoff instead of showing a false idle, deferring the idle decision to the watchdog timers. If the turn had already wound down or ended, it is treated exactly like turn_failed |
| Idle hint | idle_hint | Idle (conditional) | Claude | Agent signaled out-of-band that it is waiting for input (e.g. a Claude “waiting for your input” notice). Settles the session to idle only when nothing else is keeping it thinking (no pending tools, subagents, background shells, or permission prompt); otherwise log-only. |
| Compact | compact | Thinking | Claude, Gemini, Copilot, Grok, Qwen, Kimi | Context compaction occurred |
| Subagent start | subagent_start | Thinking | Claude, Grok, Kimi | A subagent was spawned (increases depth counter) |
| Subagent stop | subagent_stop | (no change) | Claude, Grok, Kimi | A subagent finished (decreases depth counter) |
| Teammate idle | teammate_idle | (no change) | Claude | Another agent in the same project went idle |
| Task completed | task_completed | (no change) | Claude | A task was marked complete |
| Config change | config_change | (no change) | Claude | A configuration setting was modified |
| Worktree create | worktree_create | Thinking | Claude | A git worktree was created |
| Worktree remove | worktree_remove | (no change) | Claude | A git worktree was removed |
| Background shell start | background_shell_start | Thinking | Claude | Agent launched a backgrounded Bash tool. Kangentic tracks the detached child so the session isn’t falsely flagged idle while it’s still running. |
| Background shell end | background_shell_end | (no change) | Claude | A tracked background shell exited. Releases a deferred idle when the last background child finishes. |
| Model start | model_start | (no change) | Gemini, Qwen | Fired before each LLM API call within an agent turn - per-call visibility in the Activity Tab. |
| Model end | model_end | (no change) | Gemini, Qwen | Fired after each LLM API call returns. Log-only counterpart to model start. |
| Tool selection start | tool_selection_start | (no change) | Gemini, Qwen | Fired when the agent is choosing which tool to invoke next. |
Per-Agent Detection Strategy
Section titled “Per-Agent Detection Strategy”Kangentic uses a different detection strategy for each agent because each CLI exposes activity differently. All fourteen strategies feed into the same unified state machine, so card indicators behave identically regardless of which agent is running.
| Agent | Strategy |
|---|---|
| Claude Code | Hooks only |
| Codex CLI | PTY silence timer |
| Gemini CLI | Hooks primary, PTY fallback |
| Cursor CLI | PTY silence timer + stream-JSON init event for session ID |
| GitHub Copilot CLI | Hooks primary, PTY fallback |
| Grok Build | Hooks (Claude-compatible set) primary, PTY fallback |
| Antigravity CLI | Hooks primary, PTY fallback + idle-footer regex |
| OpenCode | Plugin events primary (tool.execute.before/after, session.idle), PTY fallback |
| Aider | PTY prompt regex |
| Qwen Code | Hooks (BeforeModel / AfterModel / BeforeToolSelection) primary, PTY fallback |
| Kimi Code | Wire-protocol JSONL primary, PTY fallback |
| Factory Droid | PTY silence timer + cursor-hide first-output marker |
| Warp (Oz CLI) | PTY silence timer |
| Ollama | PTY silence timer (single-turn ollama run) |
Claude Code, hooks only
Section titled “Claude Code, hooks only”Claude Code’s native hooks system is the sole source of truth. Kangentic injects an event-bridge.js shim into the session that fires on Claude Code hook events and writes them to a structured log file that Kangentic watches incrementally.
| Claude Code hook | Effect |
|---|---|
| PreToolUse | Agent is about to use a tool > thinking state |
| PostToolUse | Tool finished (no state change) |
| PostToolUseFailure | A tool call failed > no state change (the agent may retry) |
| UserPromptSubmit | User sent a message > thinking state |
| Stop | Agent stopped naturally > idle state |
| StopFailure | Turn ended on a service or API error > idle state (logged as turn_failed) |
| PermissionRequest | Agent needs approval > idle state |
| SubagentStart / SubagentStop | Track subagent nesting depth |
| Notification / PreCompact | Logged as internal events |
| SessionStart / SessionEnd | Logged as internal events |
| TeammateIdle / TaskCompleted | Logged as internal events |
| ConfigChange | Logged as an internal event |
| WorktreeCreate / WorktreeRemove | Worktree lifecycle; create counts as thinking |
Hook injection is automatic - no manual setup. All Kangentic artifacts stay in the .kangentic/ directory; nothing is written to your .claude/settings.local.json. Claude is the only agent Kangentic parses a status.json file for (context window percentage, token counts, cost, rate limits).
Codex CLI, PTY silence timer
Section titled “Codex CLI, PTY silence timer”Codex’s Rust CLI (0.118+) does not read .codex/hooks.json at runtime, so Kangentic falls back to a pure PTY strategy: after each burst of terminal output, a 3-second silence timer marks the session idle. Content deduplication filters repeated TUI frames automatically so resize redraws don’t look like activity.
Codex’s › prompt character stays visible even during tool execution, so a prompt regex can’t be used - the silence timer is the only reliable signal. Kangentic no longer writes a Codex hook file at all: on spawn it only strips any Kangentic-owned entries left in a legacy .codex/hooks.json by an older install, preserving hooks you added yourself and deleting the file when nothing but Kangentic’s entries remained.
Gemini CLI, hooks primary with PTY fallback
Section titled “Gemini CLI, hooks primary with PTY fallback”Gemini supports hook injection via .gemini/settings.json (reference-counted so concurrent sessions don’t clobber each other). Hooks fire on BeforeAgent, AfterAgent, BeforeModel, AfterModel, BeforeToolSelection, BeforeTool, AfterTool, SessionStart, SessionEnd, Notification, and PreCompress events.
If hooks don’t fire (e.g., early in startup or during a hang), a PTY-based fallback kicks in: Kangentic watches for Gemini’s closed-box TUI border (╰────╯) or the “I’m ready.” message to detect idle, backed by the same 3-second silence timer as Codex. Once hooks start firing, the PTY fallback defers to them.
Cursor CLI, PTY silence timer
Section titled “Cursor CLI, PTY silence timer”Cursor’s terminal agent (cursor-agent) has no hooks and no status file. Activity detection is a PTY silence timer, same 3-second threshold as Codex and Warp. Kangentic spawns Cursor in non-interactive mode (cursor-agent -p ... --output-format stream-json) by default so the first line of NDJSON output is an init event containing the model and session ID. That event lets ContextBar resolve the model pill immediately and lets --resume=<id> work reliably across restarts. There is no subagent tracking and no compaction event.
GitHub Copilot CLI, hooks primary with PTY fallback
Section titled “GitHub Copilot CLI, hooks primary with PTY fallback”Copilot (v1.0+) supports inline hooks written to a per-session config.json in the copilot-config/ directory beside the Kangentic events file. Kangentic wires preToolUse, postToolUse, agentStop, and preCompact hooks; these are the primary activity signal. A PTY silence timer backs them up because Copilot’s statusLine is best-effort (empirically it doesn’t always fire in ConPTY). Stream-output parsing picks up the model label either from the NDJSON stream when invoked with --output-format json or from the interactive TUI’s status bar.
Grok Build, hooks primary with PTY fallback
Section titled “Grok Build, hooks primary with PTY fallback”Grok Build deliberately clones Claude Code’s hook surface, so Kangentic gets the full Claude-compatible hook set - PreToolUse, PostToolUse, UserPromptSubmit, Stop, SubagentStart/SubagentStop, and friends - written to a Kangentic-owned .grok/hooks/kangentic.json in the working directory. Hooks are the primary signal, but grok gates project hooks on its own folder trust, so a PTY silence timer carries activity until trust lands. There is no idle prompt regex: grok’s ❯ prompt stays visible during active work, the same always-visible-prompt trap as Codex’s ›. Grok has no statusline either; context percentage, token counts, and cost come from tailing the session’s updates.jsonl log.
Antigravity CLI, hooks primary with PTY fallback
Section titled “Antigravity CLI, hooks primary with PTY fallback”Antigravity (agy) has a lifecycle hook system; Kangentic writes one named hook into .agents/hooks.json in the working directory, wiring PreInvocation (prompt), PostToolUse (tool end), and Stop (idle, with agy’s fullyIdle flag). Hooks don’t fire in print mode, so a PTY fallback covers those spawns: the standard silence timer plus an idle regex on agy’s footer - ? for shortcuts shows at rest, while a running turn shows esc to cancel instead. Conversation IDs arrive in every hook payload, which is also how session capture works.
Like Droid, Antigravity streams no live telemetry to Kangentic, so its context bar shows the same Telemetry: TUI only affordance rather than spinning forever. The agy footer carries the active model and effort, and per-thought token counts appear inline in its own output. Its brain-directory transcript is still parsed for the Transcript tab.
OpenCode, plugin events with PTY fallback
Section titled “OpenCode, plugin events with PTY fallback”OpenCode loads plugins from a project-shared .opencode/plugins/ directory at TUI startup. Kangentic copies an activity-stream plugin into that directory at spawn (refcounted across concurrent same-project sessions) so its tool.execute.before, tool.execute.after, event (for session.created / session.idle / session.error) callbacks fire and stream activity back to Kangentic. The plugin captures the native session ID (ses_<id>) from event.properties.info.id. A PTY silence timer covers the gap between plugin idle events.
Aider, prompt regex
Section titled “Aider, prompt regex”Aider doesn’t support hook injection, so detection is pure PTY. Kangentic strips ANSI escapes from the output stream and matches a prompt regex at the end of the buffer:
/(?:^|\n)\s*(?:architect|ask|help|context)?>\s*$/Any terminal output flips the session to thinking; a prompt match flips it back to idle. There is no subagent tracking, no status.json, and no compaction event. This is consistent with Aider’s edit-first, fire-and-forget design.
Qwen Code, hooks primary with PTY fallback
Section titled “Qwen Code, hooks primary with PTY fallback”Qwen Code is a soft fork of Gemini CLI, so it inherits Gemini’s hook system unchanged in .qwen/settings.json - the same hook events Kangentic registers for Gemini, including the BeforeModel, AfterModel, and BeforeToolSelection hooks that distinguish the model-call boundary from tool selection. Hook files are reference-counted across concurrent sessions in the same project. The PTY fallback is the same closed-box-TUI border + 3-second silence timer as Gemini.
Kimi Code, wire-protocol JSONL with PTY fallback
Section titled “Kimi Code, wire-protocol JSONL with PTY fallback”Kimi emits a structured wire-protocol JSONL stream to ~/.kimi/sessions/<work_dir_hash>/<id>/wire.jsonl on every spawn. Kangentic watches the file incrementally for tool events, model start/end, plan markdown content, hook requests, and subagent lifecycle markers. The same stream feeds the ContextBar’s context-window percentage, token counts, and cost. PTY silence timer backs it up if the wire file is slow to flush.
Factory Droid, PTY silence + cursor-hide first-output
Section titled “Factory Droid, PTY silence + cursor-hide first-output”Droid 0.109 ignores hooks injected through --settings, so Kangentic uses pure PTY detection: a 3-second silence timer for idle plus the \x1b[?25l cursor-hide marker for first usable output. Session UUIDs are captured from ~/.factory/sessions/<cwd-slug>/<id>.jsonl rather than stdout.
Droid does not stream live telemetry to Kangentic - /cost and /context are post-hoc TUI commands and the OTEL endpoint is out-of-band. The context bar shows a “Telemetry: TUI only” affordance instead of spinning forever; run those commands inside the Droid TUI to see model, token, and cost details.
Warp (Oz CLI), PTY silence timer
Section titled “Warp (Oz CLI), PTY silence timer”Warp’s oz CLI is the simplest adapter: no hooks, no status file, no structured event output, no trust mechanism, and no caller-specified session IDs. Activity detection is a PTY silence timer only. Unlike Codex and Cursor, there is no prompt regex, so the silence timer is the sole idle signal.
Ollama, PTY silence + REPL prompt
Section titled “Ollama, PTY silence + REPL prompt”Ollama has no hooks and no native session IDs, so detection is PTY-only. A one-shot ollama run streams its answer and exits, which means the silence timer normally carries the idle transition on its own. A prompt regex sits alongside it for the other case: spawning with no prompt drops you into Ollama’s interactive REPL, and matching its >>> prompt marks idle the moment the model stops rather than waiting the timer out.
Thinking vs. Idle Indicators
Section titled “Thinking vs. Idle Indicators”The card indicators are designed to avoid false signals:
- Tool end doesn’t set idle. Between consecutive tool calls there’s a brief gap - showing idle here would cause the spinner to flicker. For agents that emit explicit events (Claude, Gemini), only
StoporPermissionRequestsets idle. - Tool failure doesn’t set idle. The agent continues processing after a failure and may retry.
- Notifications don’t change state. Informational notifications (like “context getting full”) fire unpredictably and shouldn’t flip the indicator.
- Frame redraws don’t wake a session. For PTY-based agents, content-deduplication filters resize redraws so they don’t look like real activity.
Subagent Awareness
Section titled “Subagent Awareness”Claude Code’s Agent tool lets a main agent spawn subagents to delegate work. Kangentic tracks the subagent depth from Claude’s SubagentStart / SubagentStop hook events so the card indicator stays accurate during nested operations:
- If the main agent is idle but a subagent is still running tools, the card correctly stays idle - subagent tool events don’t override the main agent’s state
- When all subagents finish and the main agent resumes, the card correctly shows thinking
- Main-agent
Stopis deferred until any running subagent finishes, so a pending user decision isn’t hidden - User prompts and interrupts always take effect immediately regardless of subagent state
Grok Build fires the same Claude-compatible SubagentStart / SubagentStop hooks, with one simplification: grok’s Stop fires only for the main agent, so the depth gating above isn’t needed. The other agents either don’t expose subagents (Codex, Gemini, Cursor, Copilot, Antigravity, OpenCode, Aider, Qwen Code, Droid, Warp, Ollama) or surface them through their own JSONL stream (Kimi Code, via SubagentEvent envelopes in the wire protocol).
Background Shell Tracking (Claude Code only)
Section titled “Background Shell Tracking (Claude Code only)”When Claude Code launches a backgrounded Bash tool (e.g., npm run dev &), the agent’s main loop returns to idle while the child process keeps running. Without tracking, Kangentic would flag the session idle the moment Bash returned even though work is still happening. The bg-shell watcher tracks each backgrounded child PID until it exits, deferring the idle transition until the last tracked child finishes. Two new event types - background_shell_start and background_shell_end - record this in the Activity Log.
Retiring an Agent That Exited Under Its Shell
Section titled “Retiring an Agent That Exited Under Its Shell”Added in v0.36.0, and not an activity signal: it changes a session’s status, not its state.
Kangentic spawns a shell and writes the agent CLI command into it, so the agent is a child of the terminal’s root process rather than the root itself. When the agent exits on its own - you type /exit, the CLI crashes, a launch fails - the shell survives, the terminal never reports an exit, and nothing marked the session finished. The record stayed Running indefinitely: the status bar counted a phantom agent, the card read active, and the bottom panel kept a tab whose agent was gone.
Kangentic now sweeps for that case and retires the session cleanly, exactly as an ordinary exit would. The sweep piggybacks on the watcher that already holds each session’s root process and takes one process snapshot per cycle, so it adds no new timer or polling loop, and it runs at most once a minute while a task agent is running.
Marking a live agent dead is far worse than the phantom, so the sweep only acts when the root process is alive, the process snapshot is healthy, the agent is missing on two consecutive checks, and the session is past a 30-second spawn grace. Command Terminal sessions are excluded outright: they are bare shells by design, and would otherwise be retired the moment you opened one.
Native Session History
Section titled “Native Session History”For Claude Code, Codex, Gemini, Grok Build, Antigravity, OpenCode, Qwen Code, Kimi Code, and Factory Droid, Kangentic reads each agent’s own native session history file (in addition to the hook/PTY activity stream) to produce higher-fidelity telemetry: token-accurate context percentages and tool call transcripts. Kangentic watches these files incrementally so the Activity Tab and context bar stay in sync without polling the CLI.
The same files are what handoff context points at when a task moves between agents: Kangentic hands the next agent the file’s path rather than a manufactured summary, and the agent reads it directly.
| Agent | Native history path |
|---|---|
| Claude Code | ~/.claude/projects/<sanitized-cwd>/<session-id>.jsonl |
| Codex CLI | ~/.codex/sessions/.../rollout-*.jsonl |
| Gemini CLI | ~/.gemini/tmp/<cwd>/chats/session-*.jsonl (current builds; .json on older ones, and both generations are matched) |
| Grok Build | ~/.grok/sessions/<encoded-cwd>/<session-id>/updates.jsonl |
| Antigravity CLI | ~/.gemini/antigravity-cli/brain/<conversation-id>/.system_generated/logs/transcript.jsonl (parsed for the Transcript tab; no live telemetry) |
| OpenCode | ~/.local/share/opencode/storage/messages/<session-id>/... |
| Aider | <cwd>/.aider.chat.history.md (parsed for session cost only, but readable in full by a receiving agent) |
| Qwen Code | ~/.qwen/projects/<sanitized-cwd>/chats/<session-id>.jsonl (Qwen forked Gemini but moved chat persistence, so it does not use Gemini’s layout) |
| Kimi Code | ~/.kimi/sessions/<work_dir_hash>/<id>/wire.jsonl |
| Factory Droid | ~/.factory/sessions/<cwd-slug>/<session-id>.jsonl (parsed for the Transcript tab; no live telemetry) |
Copilot, Warp, and Ollama expose no file at all. Cursor does write one, at ~/.cursor/projects/<cwd-slug>/agent-transcripts/<session-id>/<session-id>.jsonl, but Kangentic deliberately leaves it unread: its records carry no timestamp, and the stored text is wrapped in <user_query> markup rather than kept verbatim, so neither a staleness bound nor an exact match against what was submitted is possible. For all four, the context bar falls back to PTY-derived heuristics and a task moving out of one of them carries no handoff context.
Authentication Status
Section titled “Authentication Status”For agents that ship their own OAuth/credential flow, Kangentic probes the auth state at detection time - a credentials directory, or a lightweight CLI probe - and surfaces an amber warning when a fresh spawn would die at the auth wall. This shows up next to the agent’s entry in the Welcome screen and in the column / settings agent picker.
| Agent | Probe | Resolve by running |
|---|---|---|
| Grok Build | grok models (its output reports “not authenticated”) | grok login |
| OpenCode | ~/.local/share/opencode/auth.json | opencode auth login |
| Kimi Code | ~/.kimi/credentials/ | kimi login |
Board Overview
Section titled “Board Overview”The combination of activity detection and the Kanban layout gives you a dashboard view of all agent work:
- See which agents are actively working vs. waiting for input
- Identify stuck or idle agents that may need intervention
- Monitor overall project progress at a glance
- Know when agents finish without watching their terminals
When an agent goes idle, Kangentic can also notify you via desktop notifications and idle badges. See Notifications for details. The Agent Monitor extends this dashboard view to every project on the machine at once.
Next steps
Section titled “Next steps”Next: Notifications - desktop alerts and idle badges driven by activity state.
See also:
- Session Persistence - how activity state survives restarts
- Settings - configure notification triggers
Kangentic is free and open source. A star helps other people find it.
Star on GitHub