awareness
kolu knows what your agents are doing.
kolu watches the foreground process of every terminal. When that process is a coding agent it recognizes — Claude Code, Codex, Grok Build, or OpenCode — it reads that agent’s own on-disk session to derive whether it’s thinking, running a tool, waiting on you, or done. That state paints the tile chrome, the dock row, and the minimap, so a glance across a workspace of twenty terminals tells you who needs you.
There is no agent registry to configure and no per-agent setup. Run a supported agent in any terminal and detection lights up on its own.
One state vocabulary
Every agent folds onto the same small set of states, drawn with the same glyphs everywhere kolu shows agent status. The shape carries the meaning, so the distinction survives a peripheral glance or reduced color sensitivity.
The awaiting-you hue sits deliberately outside the warm/pending family, so an agent that needs you never reads as a build still churning. When an agent finishes in the background and you haven’t looked yet, its row also gains a small amber unread badge in the corner until you focus it.
How detection works
The pipeline is the same for every agent; only the last step — which on-disk store holds the session — differs.
kolu asks each terminal for its foreground process id (tcgetpgrp, POSIX — Linux
and macOS alike), matches it against the known agents, and reads that agent’s
session from disk — tailing a JSONL transcript, or querying a SQLite database and
watching its write-ahead log. No /proc scraping and no argv inspection. Adding a
new agent is one small adapter and one line of registration; nothing server-wide
changes.
Claude Code
kolu finds a Claude session by checking whether ~/.claude/sessions/<foreground-pid>.json
exists, then tails its JSONL transcript to derive state. The tile also shows the
session’s title and a running token count.
| State | Indicator | Meaning |
|---|---|---|
| Thinking | pulsing accent dot | an API call is in flight — Claude is generating |
| Tool use | pulsing amber dot | Claude is executing tools |
| Running in background | spinning teal ring | Claude ended its turn while a dynamic workflow it launched is still running — busy-waiting, not blocked on you |
| Awaiting input | pulsing alert | blocked on you — an AskUserQuestion prompt or a tool-permission gate |
| Waiting | dim dot | finished responding (or interrupted with Esc) and idle |
The awaiting-input state is the subtle one: while a question or permission prompt is pending, the transcript doesn’t reveal it, so kolu reads the rendered screen for the framework’s prompt chrome and buckets it as awaiting you.
What it can’t detect:
- Plan approval (
ExitPlanMode) — its on-screen shape has no arrow-nav footer to anchor on yet; a deliberate follow-up. - Streaming progress — only final state transitions, not intermediate tokens.
- Wrapped invocations — launched via a wrapper (e.g.
script -q out.log claude), the foreground pid is the wrapper, so the session lookup misses. - Sub-agents — nested spawns aren’t tracked as separate sessions. (When the parent waits on a launched dynamic workflow, kolu does surface that workflow’s name and live sub-agent count.)
Codex
When the foreground process is codex, kolu queries Codex’s threads SQLite
database (the highest-numbered ~/.codex/state_<N>.sqlite) for the most recently
updated thread whose working directory matches the terminal, and tails that
thread’s rollout JSONL for state and context-token count. Live updates ride a
watch on the SQLite write-ahead log.
| State | Indicator | Meaning |
|---|---|---|
| Thinking | pulsing accent dot | latest lifecycle event is task_started, no open tool call |
| Tool use | spinning amber | a function_call is open that isn’t an awaiting-user tool |
| Awaiting input | pulsing alert | every open call is an awaiting-user tool — request_user_input, request_permissions, or request_plugin_install |
| Waiting | dim dot | latest lifecycle event is task_complete |
The context count is Codex’s own last_token_usage.input_tokens — the number its
/status command shows — not a sum that would double-count cache re-reads.
What it can’t detect (yet):
- Task-progress checklist — Codex has no user-visible todo list to read.
- Column-schema changes — if an upstream release renames a depended-on column,
detection returns no match; set
KOLU_CODEX_DBto pin a known-good database while a fix ships. - Same-directory disambiguation — with multiple Codex threads in one directory, kolu picks the most recently updated.
Grok
When the foreground process is grok, kolu prefers the entry in
~/.grok/active_sessions.json whose pid matches the terminal; otherwise it takes
the most recently updated session under ~/.grok/sessions/. State comes from
tailing that session’s events.jsonl — phase_changed for thinking / tool use /
awaiting you, turn_ended for waiting. An open ask_user_question promotes to
awaiting you even mid-tool, so a multiple-choice prompt never reads as “running
tools”.
Resume uses grok -c (most recent in the directory) or grok --resume <uuid>
(an exact conversation). Context tokens come from signals.json
(contextTokensUsed); task progress stays null until Grok’s plan checklist
is a stable first-class count. Transcript export reads chat_history.jsonl
(unwraps Grok’s <user_query> harness so the HTML shows the human prompt).
OpenCode
When the foreground process is opencode, kolu queries OpenCode’s SQLite database
directly at ~/.local/share/opencode/opencode.db for the most recently updated
session whose directory matches the terminal. The database’s write-ahead log
drives live updates.
| State | Indicator | Meaning |
|---|---|---|
| Thinking | pulsing accent dot | latest assistant message has no time.completed |
| Tool use | spinning amber | a running part whose tool is neither question nor plan_exit |
| Awaiting input | pulsing alert | every running part is question or plan_exit — blocked on a reply |
| Waiting | dim dot | latest assistant message is completed with finish: "stop" |
Todo progress comes free from a count over OpenCode’s todo table, and the tile
shows the running token count OpenCode pre-sums itself.
What it can’t detect (yet):
- Same-directory disambiguation — most recently updated session wins.
- Non-default database location — set
KOLU_OPENCODE_DBto override the path.
Adding an agent
Because detection is agent-agnostic, a new CLI doesn’t wait on kolu. Once you’ve run a known agent in any terminal it also surfaces in the command palette — as a launch option in the worktree-naming flow and in the Recent agents group. Teaching kolu to read a brand-new agent’s state is a single adapter that reads its session store and emits the same five states; everything downstream — the dock, the tile border, the minimap, the fleet view — lights up unchanged.