Queue work for the agent, typed at it or dispatched beside it - #1
Merged
Conversation
You have three things you want next and the agent is mid-task. Until now the choices were to interrupt it, hold them in your head, or write them somewhere abeam cannot see. `Alt+A` is the somewhere, and it holds work in the two shapes that word covers: an item is either *sent* — typed into the live session when it goes idle, continuing the conversation you have been having — or *dispatched* as its own `claude -p --bg` agent, with none of that context, running beside you. They are one list because they are one thought, and differ only in whether the work needs the conversation you have already had. Sending is the hard half, and all of it is about *when*. A prompt typed at the wrong moment is not cosmetic: a permission dialog answers with the first character it is given, and a half-written message gets the queued text spliced into the middle of it. Output quiescence cannot tell a finished turn from one waiting on a human, and screen scraping rots on every release of every agent. Claude answers the question itself. It keeps a JSON record per session under `~/.claude/sessions/<pid>.json` whose `status` is `idle` or `busy`, and `agentstate.rs` reads it — adding nothing to the child's argument list and asking nobody to install a hook. Reading Claude's own status logic out of the 2.1.220 binary settles the load-bearing question: a permission dialog reports `waiting`, not `idle`, and a running subagent reports `busy`. The nightmare above cannot happen through that path. Two consequences are written down where they will be found: the vocabulary is `busy`/`shell`/`idle`/`waiting` and is documented nowhere, so assume this is a version behind; and anything abeam cannot read is `Unknown`, which is treated exactly as unsafely as `busy`. The same audit found the case that does break it, and it is why the draft rule is shaped the way it is. Claude's inline autocomplete is not on the dialog stack the status is derived from, so an `Enter` that accepts a completion leaves the text in the composer with the record still reading `idle`. That cannot be fixed by classifying keystrokes, because the key that submits and the key that does not are the same key. So typing sets the draft flag and *only observing the agent go busy* clears it: a message that was really submitted makes the agent work, and nothing else the user can press does. The cost is that a draft typed and abandoned holds the queue until something is submitted, visibly. That is the safe direction to be wrong in. Four conditions therefore gate a send, and the last one is the point: it is announced in the left title for three seconds first, and any keystroke at the agent withdraws it. A thing that types on your behalf must be stoppable by the reflex of starting to type. Dispatching needs none of that, because nothing is being typed at. It passes `--permission-mode acceptEdits`, which is the one grant of authority abeam makes to a process nobody is watching — edits land without asking so the task can finish unattended, `Bash` still stops, and `--dangerously-skip-permissions` is refused by a test that asserts the whole argument vector rather than searching it. The prompt sits behind a `--` fence: it is user text, and one leading dash without the fence would let a pasted paragraph choose the process's own permissions. No `--worktree`, deliberately — creating a worktree in somebody's repository as a side effect of queueing a task is a structural change they did not ask for, and it belongs behind an explicit choice. An npm-installed Claude cannot be dispatched with a multi-line prompt at all: the command line goes through `cmd.exe`, which cannot carry a newline in any form. Since a paste becomes one item, that is the ordinary shape of a queued task, so the refusal names the live-session mode as the route that works. A native `claude.exe` has no such limit. This is `cmd.exe`'s constraint rather than abeam's, and it is stated as one. `Alt+A` was cleared the way the rest of the table was, against the same binary: `meta+a` and `alt+a` both return zero matches, where the undeclared readline bindings that caught `Alt+F` do appear as text. It is a letter and not an F-key because it joins a set — `Alt+G`, `Alt+E`, `Alt+S`, `Alt+A` are the four workspace views, and a fourth spelled `F6` would be a binding nobody groups with the other three. Arming is `a` and not `space`: `space` pages in every pane and the F1 overlay promises it by name, and a key that pages in three panes and toggles a mode in the fourth is a key nobody can learn. `serde` and `serde_json` are the only new dependencies, and they are the one place the git pane's "shell out and parse it yourself" precedent does not carry. `git status --porcelain=v2` is line-oriented and frozen; this is JSON with string escapes, three observed entry shapes, and a `peerProtocol` field that exists so Claude can change the rest. A hand-rolled scanner on the path that decides whether it is safe to type at an agent is the wrong trade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alt+Aopens a queue of work for the hosted agent. An item is either sent — typed into the live session when it goes idle, continuing the conversation you have been having — or dispatched as its ownclaude -p --bgagent with none of that context, running beside you. One list, because it is one thought; the modes differ only in whether the work needs the conversation you have already had.The hard half is when to type
A prompt typed at the wrong moment is not cosmetic. A permission dialog answers with the first character it is given; a half-written message gets the queued text spliced into the middle of it. Output quiescence cannot tell a finished turn from one waiting on a human, and screen scraping rots on every release of every agent.
Claude answers the question itself: it keeps a JSON record per session under
~/.claude/sessions/<pid>.jsonwhosestatusisidleorbusy.agentstate.rsreads it — adding nothing to the child's argument list and asking nobody to install a hook.The load-bearing assumption was verified rather than assumed. Reading Claude's own status logic out of the 2.1.220 binary establishes that a permission dialog reports
waiting, notidle, and a running subagent reportsbusy. The headline hazard cannot occur through that path. Two consequences are documented where they will be found: the vocabulary isbusy/shell/idle/waitingand is published nowhere, so assume this is a version behind; and anything unreadable isUnknown, treated exactly as unsafely asbusy.The same audit found the case that does break it. Claude's inline autocomplete is not on the dialog stack the status derives from, so an
Enterthat accepts a completion leaves text in the composer with the record still readingidle. That cannot be fixed by classifying keystrokes — the key that submits and the key that does not are the same key. So typing sets the draft flag and only observing the agent go busy clears it: a message that was really submitted makes the agent work, and nothing else the user can press does. A draft typed and abandoned holds the queue until something is submitted, visibly. That is the safe direction to be wrong in.Four conditions gate a send, and the fourth is the point: it is announced in the left title for three seconds first, and any keystroke at the agent withdraws it. A thing that types on your behalf must be stoppable by the reflex of starting to type.
Dispatching
Passes
--permission-mode acceptEdits— the one grant of authority abeam makes to a process nobody is watching. Edits land without asking so the task can finish unattended;Bashstill stops;--dangerously-skip-permissionsis refused by a test asserting the whole argument vector rather than searching it. The prompt sits behind a--fence, because it is user text and one leading dash without the fence would let a pasted paragraph choose the process's own permissions. No--worktree: creating a worktree in somebody's repository as a side effect of queueing a task is a structural change they did not ask for.An npm-installed Claude cannot be dispatched with a multi-line prompt at all — the command line goes through
cmd.exe, which cannot carry a newline in any form. Since a paste becomes one item, that is the ordinary shape of a queued task, so the refusal names the live-session mode as the route that works. A nativeclaude.exehas no such limit. Stated ascmd.exe's constraint, which is what it is.Keys
Alt+Awas cleared against the same binary as the rest of the table:meta+aandalt+aboth return zero matches, where the undeclared readline bindings that caughtAlt+Fdo appear as text. A letter and not an F-key because it joins a set —Alt+G/Alt+E/Alt+S/Alt+Aare the four workspace views. In-pane:iwrites an item,aarms,ddeletes,mswitches mode,Enterdoes the selected one now. Arming isaand notspace, becausespacepages in every pane and the F1 overlay promises it by name.Review
Three adversarial reviewers went over the first working version. What they found after it was already green and passing:
Shift+Entercleared the draft flag — it inserts a newline in Claude's composer rather than submitting, so a queued prompt would be pasted into a live draft three seconds later.app.rshad no tests; it now has eight, each verified by applying its mutation and reverting.Alt+Qsilently suppressed the countdown — the left title was anif/else ifchain, so a pending quit removed the only on-screen warning that abeam was about to type.peerProtocolbump would have handed the queue a stranger'sidlefor a whole run.Dependencies
serdeandserde_json, the one place the git pane's "shell out and parse it yourself" precedent does not carry:git status --porcelain=v2is line-oriented and frozen, whereas this is JSON with string escapes, three observed entry shapes, and apeerProtocolfield that exists so Claude can change the rest. A hand-rolled scanner on the path that decides whether it is safe to type at an agent is the wrong trade.Testing
344 unit + 3 end-to-end tests pass;
cargo clippy --workspace --all-targets -- -D warningsis clean.Not covered: no live
claude -p --bgwas ever run — that would start an unattended agent with edit permission in this repo. The command construction is tested against a realcmd.exe(quotes,&,%VAR%, flag-shaped prompts, the fence, and a shim proving the child gets EOF rather than the console), but the first genuine dispatch is untried. Also uncovered:tick_panes' queue redraw while the pane is hidden, which needs a real one-second wait to test without flakiness.