# Agent Detection > How Prowl knows there's an agent in a pane and whether it's Working, Blocked, > Idle, or Done — and which agents it recognizes. **Keywords:** agent detection, claude, codex, gemini, cursor, working, blocked, idle, done, status, process probe, screen heuristics, indicator, spinner **Related:** [active-agents](active-agents.md) · [notifications](notifications.md) · [terminal](terminal.md) ## What it is Prowl continuously inspects each terminal pane to decide whether a coding agent is running and what state it's in. That signal drives the [Active Agents panel](active-agents.md), the per-tab activity indicator, [Canvas](canvas.md) cards lighting up, and [notifications](notifications.md). ### Devin CLI Devin 3000.11.3 uses a `devin` terminal process and a `devin acp` child. Prowl selects the ACP child for native session ownership and keeps the original terminal launcher identity. An open `devin/cli/session_locks/.lock` descriptor identifies the session exactly; leftover lock files and unrelated transcripts are not scanned. The live composer footer identifies **Working** while thinking, running tools, or streaming text. Directory trust, permission choices, and selection menus are **Blocked**. An empty composer after completion or cancellation is **Idle**. Retained dialog text and old working footers above a new composer are ignored. Devin currently uses screen state and cooperative delivery, without a managed `hook_devin` channel or a native state provider. Diagnostic logs and the session store do not expose a complete live state contract; the native `Stop` hook fires before other hooks can prevent stopping. A workflow finishes only after its explicit `prowl workflow deliver` receipt. `agents read` has no semantic Devin result reader; use `prowl read` for terminal output and workflow delivery for complete artifacts. ### Antigravity CLI Antigravity (`agy`, verified 1.3.2) keeps an attached conversation's `~/.gemini/antigravity-cli/presence/.lock` descriptor open for the life of the session, so that open file — not a lock file left behind after exit — identifies the session exactly. Its `conversations/.db` and `brain//.system_generated/logs/transcript.jsonl` share the same id. The detector reads the full active screen and anchors on the live composer: a terminal-wide `─` border, a column-0 `>` prompt row (wrapped input continues on indented rows), and a terminal-wide `─` bottom border. The built-in status row renders directly below that box — `esc to cancel` or `esc to interrupt` is **Working** while a turn runs, `? for shortcuts` is **Idle** — padded away from the right-aligned model label (`Gemini 3.8 Flash · high`, `Claude Sonnet 4.6 (Thinking)`, or nothing until the label resolves). The last composer on screen is the live one, and the row under its bottom border is the only state evidence: a `stack_with_default` status script renders verbatim below that row, so nothing below it counts, however it is shaped. A typed draft drops the signature from the status row (only the model label remains), and the slash-command autocomplete popup that opens under the composer when `/` is typed rewrites it to `esc to cancel` whatever the turn state; both are **unknown**, so the state from before the user started typing is retained. A screen without a composer, a composer caught without its status row, or a viewer such as `/help` or `/model` is **unknown**, never Idle. Workspace-trust, tool-permission, and ask-user dialogs are **Blocked**: they replace the composer with option rows (a column-0 `> ` marks the selection, within eight rows above the hint) and a `↑/↓ Navigate` hint row, and a permission dialog keeps `esc to cancel`, so the dialog read runs before the status row and nothing below the hint can veto it. The autocomplete popup shares the hint shape but renders below a live composer box, which a dialog never does, so a hint under a composer is the popup. Agent responses render indented, so column-0 dialog chrome is the live dialog or the user's own echoed text; a verbatim quote at column 0 reads Blocked until it scrolls off the screen, a delay rather than a dispatch into a modal prompt. Answered dialogs leave only the chosen option echoed at column 0 (`> Red`), never the hint. A bare hint row with no selection — cropped chrome or residue — denies the composer evidence below it and reads **unknown**. Option shape alone is not dialog evidence: a slash command echoes as a column-0 `> ` row with no rule above it and an indented result beneath, and an echoed prompt whose rule scrolled off the top of the screen looks the same, so a dialog whose hint copy the detector does not recognize falls through to the composer read and fails toward unknown, never Idle. Terminal width is the longest `─`-only column-0 row on screen. The echoed prompt's rule is narrower, agent responses render indented, and the dialog rule is not followed by a prompt row, so none of them can pose as the composer. The accepted residual is a user's own status script that draws a terminal-wide `─`/`>`/`─` box followed by a padded status signature under an idle or working composer; that output is the user's configuration, and the plain spoofs reviewed so far (`? for shortcuts custom help`, narrower boxes, dividers) fail toward unknown, while a live dialog stays Blocked whatever is stacked beneath it. A pane that never shows a recognized screen — a headless `agy --print` run, a viewer overlay such as `/diff`, or a future layout change — reports unknown the whole time. Its process is still classified, but an unknown pane emits no roster entry: it is absent from Active Agents and `prowl agents`, `agents wait --until idle|blocked` times out on it, and `agents wait --until exit` treats it as gone. The state machine retains the last recognized state, so a working turn that only briefly drops its footer stays Working. Antigravity currently uses screen state and cooperative delivery, without a managed hook channel or transcript reader. ## Agents it recognizes Claude (Claude Code), Codex, Gemini, Cursor, Cline, OpenCode, GitHub Copilot, Kimi, Droid, Amp, Pi (`pi`), Oh My Pi (`omp`, `oh-my-pi`), Qoder CLI (`qodercli`), Qwen Code (`qwen`), Grok Build (`grok`), Devin (`devin`), and Antigravity (`agy`). Detection covers common wrappers (node, python, bun, bash, etc.) so agents launched indirectly are still found. Pi and Oh My Pi are independent detected agents. Pi recognizes its own minimal working/idle cues, including its built-in braille-prefixed `Working...` loader and a running `pi-subagents` background card after the parent turn settles; Oh My Pi owns its richer spinner and interactive Ask-prompt heuristics, plus its own session layout and icon. Grok Build also ships an `agent` symlink; Prowl only treats that name as Grok when the path points at a `~/.grok/` install (so Cursor's own `agent` entrypoint stays Cursor). ## How detection works 1. **Process probe.** Prowl reads the pane's foreground process group and matches process names / argv against known agent executables, scoring argv[0] highest, then process name, then command-line tokens. 2. **Screen heuristics.** Claude detection consumes the full active screen (bounded by the terminal height); Pi starts from the last ~32 non-blank lines so an expanded background-agent widget stays intact, and every other agent starts from the last ~24 as a guard against transcript history. The classifier then selects agent-specific live UI regions rather than treating every transcript line as current state. Structured confirmation/permission chrome is **Blocked**; status rows and spinners are **Working**. Claude working rows come from a live status block walked bottom-up from its prompt box by row shape — the spinner or `●` status row, `⎿` attachments such as todo lists and tips, queued `❯` messages, and right-aligned chrome — stopping at the first transcript-shaped row, so a long todo list cannot push the live row out of view and a status row quoted inside a `⏺` block cannot read as live. Confirmation text is consulted only around a current numbered selection row such as `❯ 1. Yes`; a bare input prompt cuts off the preceding transcript. Claude's internal scroll view keeps the composer visible while hiding live status. When `Jump to bottom (click) ↓` or a counted `new message(s) (click) ↓` control appears in the last non-blank row above that composer, Prowl treats the screen as a viewer and retains the last known state. The control can overlay text in the middle of a row. Return to the bottom to refresh screen-based status; completion while browsing history is not visible to this detector. Codex uses exact bottom-of-screen `•`/`◦ Working (... esc to interrupt)` and `•`/`◦ Waiting for background terminal (... esc to interrupt)` footer fallbacks. The footer can also sit up to three text rows above the composer, so `└` detail rows such as a background command or a `Tip:` between them do not hide it. Braille-only starfield rows around the composer do not count toward that footer window; animation alone does not indicate **Working**. An empty Codex composer hint and status line remain **Idle** evidence with Astra's starfield background, so a workflow can send its first task before any turn has completed. The shortcut hint row that Codex 0.158 shows below the status line (`← for agents · ? for shortcuts`) does not change this. Draft text and attachments do not qualify as an empty composer. Its confirmation detector requires a numbered selected row such as `› 1. Yes` paired with a live bottom footer or an explicit Yes/No choice structure. It also recognizes the directory-trust (including Codex 0.158's `Trust this folder?` prompt), hook-review, and initial sign-in menus as **Blocked** from their complete selected-choice and footer structures. Ordinary prompt text and completed responses are not confirmation boundaries. Amp (verified 0.0.1791547250) reads only its bottom composer box. Any status in the box's bottom border — `Connecting`, `Sending`, `Waiting`, `Thinking`, `Streaming` (with or without a token counter), `Running Tools`, and whatever else the thread client reports behind its `∼`/`≈`/`≋` spinner — is **Working**; a bare border is **Idle**. `Disconnected` and `Amp Is Redeploying` keep the previous state. A dialog box directly above the composer (`Approval Required` with `‣`-marked options, `Tell Amp what to do differently`) is **Blocked** even though the border still says `Running Tools`, and so is a `‣` option row inside the composer (`Out of Credits`). Quoted status or approval text elsewhere on screen is ignored, and a screen without a complete composer keeps the previous state. Older Amp builds cannot start threads any more, so their `esc to cancel` and approval-text cues are no longer recognized. Pi also treats its bottom `── Working ──` footer and the adjacent `async subagent … · background` header with a matching braille job row as **Working**. The compact `subagents (N/M running)`, progressive `Async agents · N agent(s) running`, and multi-job `Async agents · background` layouts carry the same signal; completed, paused, and failed cards use static glyphs and remain idle. Oh My Pi's loader row is **Working** evidence when it leads with the theme's Esc glyph (`󱊷`, `⎋`, or `esc`) followed by any label: the label is the model's own intent (`Working…`, `Running requested command`, `等待命令完成`, …) and is not enumerated. The row counts in the bottom five non-blank rows and in the five rows directly above the live `box` composer, so a long queued draft does not hide it. While a turn runs, Oh My Pi's status line swaps its brand glyph for a braille spinner and turn timer (`⠋ 9s`); the `box` composer embeds that status in its `╭──` header, which is **Working** evidence when the header belongs to the live composer — the last `╭` row followed only by `│` input rows and a final `╰` row, which covers one-line, multiline, and IME-safe layouts. An older spinner frame above transcript text or above a newer idle composer is ignored. The `band` (fresh-install default), `claude`, and the other composer shapes start a status row with that spinner, which the leading-spinner rule reads; an idle composer shows the static brand glyph instead. Other agent families keep their own patterns (including Oh My Pi's `Working… ⟦esc⟧` loader and the Esc-prefixed loader row above, braille frames, symbol cycles, Cursor's hexagons, Kimi's moon phases, etc.). Copilot recognizes the bottom `Working … esc interrupt` footer across its `∙ ∘ ○ ◎ ◉` animation frames, including an optional streaming-size field such as `· 101 B` or `· 1.2 KB`. Its live boxed numbered choices with `enter to select · esc to cancel` are **Blocked**, including folder trust; that picker takes precedence over the older `esc to cancel` working cue. Claude's live status row (`●