With one agent, you watch it. With four, you poll: flip to a terminal, see
it’s still thinking, flip to the next, find the one that has been sitting on
a permission prompt for ten minutes. Most of the waiting in a multi-agent day
isn’t the agents. It’s agents waiting for you, where you didn’t look.
wrkstrms is built around one question: which agent needs you right now?
▲ wrkstrms / storefront ● 1 need you ⌃␣ a ◐ 2 working ✓ 1 done ▾ SPACES2│▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▌storefront●1│●form state ✓docs +▌● form state✓ docs│╭─● keep the checkout form's values when a…─────────────╮╭─◐ validate the postcode field as you type ─────────────╮billing◐1││││> validate the postcode field as you type │ limits││● From memory: form state lives in useCheckoutForm, │││││ and specs need bin/setup_db. ││• Explored app/checkout/, app/lib/postcode.ts │││││Edit app/lib/postcode.ts +22−3│││● I'll add a failing test first, then move the form ││Edit app/checkout/form.tsx +9│││ state out of the submit handler. ││• Adding cases for UK and US formats. │││Edit app/checkout/form.test.tsx +31││Edit app/lib/postcode.test.ts +40│││Bash pnpm test checkout ││Bash pnpm test postcode │││✗ keeps values after a failed submit ││││││││││● Fails as expected. Now the fix: ││││││││││Edit app/checkout/form.tsx +18−4││││││││││╭─Allow Bash: pnpm test checkout?││││││1 yes 2 always 3 no │││││╰─│││││││││╰─────────────────────────────────────────────────────────╯╰─────────────────────────────────────────────────────────╯│╭─ pnpm dev ──────────────────────────────────────────────╮╭─ pnpm test ─────────────────────────────────────────────╮││~/code/storefront❯ pnpm dev ││~/code/storefront❯ pnpm test --watch ││││││││ready in 412 ms ││✓ app/lib/postcode.test.ts (18 tests)│││➜ Local http://localhost:5173/││✓ app/checkout/useCheckoutForm.test.ts (9 tests)│││││✗ app/checkout/form.test.tsx (12 tests | 1 failed)│││10:41:52 page reload app/checkout/page.tsx │││││10:42:07 hmr update /app/checkout/form.tsx ││FAIL keeps values after a failed submit │││10:42:31 hmr update /app/checkout/useCheckoutForm.ts │││││││ Tests 1 failed | 38 passed│││││Waiting for file changes…│▾ AGENTS4 ═│││││●keep the checkout form…0s│││││claude·opus · Claude needs…│││││✓document the new check…0s│││││claude·sonnet · done, not …│││││◐validate the postcode fiel…│││││codex·gpt-5 · Bash pnpm te…│││││◐tighten the per-key rate l…│││││codex·gpt-5 · Bash cargo t…│╰─────────────────────────────────────────────────────────╯╰─────────────────────────────────────────────────────────╯ + new≡ menu │ NORMAL ⌃␣ ? keys·settings
Four agents in two workspaces. The top bar already has the answer: one needs you.
Four states
Every agent in wrkstrms is in one of four states, and each has a glyph and a
color that mean the same thing everywhere on screen:
● blocked, red: it needs you. A permission prompt or a question.
◐ working, yellow: it’s running.
✓ done, green: it finished a turn you haven’t looked at yet.
○ idle, gray: finished and seen, or not started.
Done matters as much as blocked. An agent that finished while you were
elsewhere is also waiting for you, just for your next prompt. Once you look
at its pane, it goes back to idle.
Where the states come from
wrkstrms doesn’t guess from the screen. Agent CLIs have hooks: small commands
they run at points in their lifecycle. ws integrate claude and ws integrate codex install a hook for each event, and each hook tells the ws
server what just happened in that pane.
session starts○ idle
you send a prompt, or it runs a tool◐ working
it asks for permission or a decision● blocked
you answer; it carries on◐ working
the turn ends✓ done
you look at its pane○ idle
One turn, as ws sees it through the agent's hooks.
A prompt or a tool call means working. A permission request means blocked.
The end of a turn means done. Claude Code also sends a notification when it
has been waiting a while for your next prompt; if the agent is already done
or idle, ws ignores that reminder instead of calling it blocked.
Agents without hooks still show up. ws checks each pane’s foreground process
every two seconds and recognizes OpenCode, aider, Gemini CLI, goose, Amp,
Cursor agent, Qwen Code, Crush and Ollama, even when they run under node, bun
or Python. It knows they’re agents, but without hooks it can’t know what
they’re doing, and it says so.
Everywhere you look
The same state shows up at every level, so you can tell from a glance where
the attention is needed.
The top bar counts: how many need you, how many are working, how many
are done. The red pill carries the key that takes you there.
● 1 need you ⌃␣ a ◐ 2 working ✓ 1 done
The sidebar lists every agent in every workspace, most urgent first,
with what it’s doing: the question it’s asking, the tool it’s using, or
“done, not seen”.
▾ SPACES2▌storefront●1▌● form state✓ docsbilling◐1 limits▾ AGENTS4 ═●keep the checkout form…0sclaude·opus · Claude needs…✓document the new check…0sclaude·sonnet · done, not …◐validate the postcode fiel…codex·gpt-5 · Bash pnpm te…◐tighten the per-key rate l…codex·gpt-5 · Bash cargo t… + new≡ menu
Tabs take the color of their most urgent agent, and pane frames take
their agent’s color, with its label on the left and what it’s doing on the
right.
╭─● keep the checkout form's values when a…─────────────╮│││● From memory: form state lives in useCheckoutForm, ││ and specs need bin/setup_db. ││││● I'll add a failing test first, then move the form ││ state out of the submit handler. ││Edit app/checkout/form.test.tsx +31││Bash pnpm test checkout ││✗ keeps values after a failed submit ││││● Fails as expected. Now the fix: ││││Edit app/checkout/form.tsx +18−4││││╭─Allow Bash: pnpm test checkout?│││1 yes 2 always 3 no ││╰─│││╰─────────────────────────────────────────────────────────╯
A blocked pane: the frame, the title and the detail all say so.
Getting there
⌃␣ a, or Alt-a without the prefix, jumps to the agent that has needed you
longest, blocked agents first. Press it again for the next one. When nothing
needs you, the top bar says so.
If an agent gets blocked while its pane isn’t on screen, wrkstrms sends a
desktop notification with the agent’s label and what it’s asking. They’re
throttled: after one goes out, the next waits at least five seconds, and
anything that piled up arrives as a single “3 agents need you”.
When a status looks wrong
⌃␣ I opens the status inspector for the focused pane: its status and since
when, what it’s asking, which agent and model and session, where its label
comes from, its process, and a timeline of the hook events that got it
there.
╭─ inspect · storefront › form state · pane 1 ─────────────────────────────────────────────────────╮│ status blocked for 0s ·→ blocked (hook Notification) at 05:17:20││ asking Claude needs your permission to use Bash││ tool Edit form.tsx││ identity claude · opus · session 218922f5-40ea · hooks reporting││ label keep the checkout form's values when a… (from its first prompt)││ process 35186 claude│├──────────────────────────────────────────────────────────────────────────────────────────────────┤│ 05:17:20 status → blocked (hook Notification)││ 05:17:20 hook Notification · Claude needs your permission to use Bash││ 05:17:20 hook PreToolUse · Edit││ 05:17:20 hook PreToolUse · Bash││ 05:17:20 hook PreToolUse · Edit││ 05:17:20 status → working (hook UserPromptSubmit)││ 05:17:20 hook UserPromptSubmit · keep the checkout form's values when a submit fails││ 05:17:19 hook SessionStart│╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
You can set a status by hand from there, until the agent’s next real event,
and copy a bug report if something’s off.
The Agents guide has the details: the exact events each CLI
reports, labels, notifications and the inspector’s keys.