Observed since 20 Oct, 17:19 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
One window for an AI coding session on Windows or Linux: your agent hosted in a pty pane, with git, a file viewer and a shell beside it. Built on Claude Code; OpenAI Codex CLI has had a signed-out smoke test, and GitHub Copilot CLI and the hailer data agent are supported but not yet exercised.
One window for an AI coding session.
Your agent runs in the left column — hosted in a pty, parsed and drawn by abeam, not passed through to your terminal — and you can start more than one there. The right pane shows the state of the git worktree, the document the agent just wrote, a shell to run things in, work lined up for the agent, a pad to write your own notes in, or, where that provider is supported, a second copy you can ask about the file in front of you. A file watcher drives the first two, so neither has to be asked.
The agent area holds more than one agent when you want it to: start another
in a second worktree and they use two columns on wide terminals, stacking on
smaller ones. Panes without enough height keep a title row. See More than one
agent below — and read docs/status.md first, because
nobody has yet driven that part by hand.
It replaces a three-window setup: the agent in one terminal, git in another, and
an editor open purely to read the markdown it produced. The difference from
assembling that yourself out of a multiplexer and a git TUI is that the right
pane knows what the agent just did — one watcher on the repository root turns
the markdown it writes into the document on screen and refreshes git within a
debounce interval of any file it touches. Nothing in the right pane writes into
your repository except the one file you choose — the one the files view is
showing when you press e, or a new one named with a in its file list — and
that only when you press Ctrl+S; the pad and the recovery copies of unsaved
text live in your profile directory. The panes never take focus from the agent,
and they never switch themselves.
Text comes back the other way by dragging over it. Run something in the shell
pane, highlight what it printed, and it is on your clipboard when you let go —
or press Enter and the rows land in the agent's composer, unsent, ready for
you to say what you want done about them. That round trip is
a section of its own below.
┌ claude ──────────────────────────────┐┌ git · main ↑2 · 12 changed ──────┐
│ ││ Staged (1) +40 -6 │
│ > implement the parser ││ M crates/abeam/src/app.rs +40-6│
│ ││ Changed (1) +7 -0 │
│ ● Writing crates/abeam/src/parse.rs ││ M docs/design.md +7-0 │
│ ││ Untracked (1) │
│ ││ ? notes/ │
│ ││ Recent │
│ ││ a1b2c3d 2m parser skeleton │
└──────────────────────────────────────┘└──────────────────────────────────┘
Before you rely on it, read what is done and what is not — or the short version under Status at the foot of this page. abeam is used daily on Windows with Claude Code; several other combinations ship without anyone having watched them work, and that document says which.
uvx abeam # run it without installing anything
uv tool install abeam # or keep it on PATH
There is no Python in abeam. PyPI is the delivery van: the wheel's whole payload
is a compiled binary, so uvx fetches a few hundred kilobytes and runs it — no
Rust toolchain, no build step, nothing to compile. Asking for hailer as well
changes what is fetched, by about 480 MB of Python, none of it in abeam's
wheel, which is the same binary either way.
You also need:
git on PATH, for the git pane. Its absence is reported in the pane
rather than being fatal.npm i -g @anthropic-ai/claude-code.winget install GitHub.Copilot on Windows, npm i -g @github/copilot on Linux, or gh copilot once on either to fetch it.
The npm package wants Node 22 or newer, which is why gh copilot is worth
knowing: it is the route that works where the other two do not.npm i -g @openai/codex, then run codex once and
sign in with ChatGPT or an API key. See OpenAI's CLI
setup and authentication
guide.uv tool install hailer, or beside abeam with uv tool install abeam --with-executables-from hailer (an abeam you already installed is fine),
or for one run uvx --with hailer abeam +hailer notebook. A route that
reports hailer's Python requirement unsatisfiable wants --python 3.12.
Then store an API key once with hailer login openai, which puts it in
the OS credential store, or set OPENAI_API_KEY — it speaks to
OpenAI-compatible providers only — and run hailer init in each project,
in the directory you start abeam in. That writes hailer.toml,
.config/hailer/, notebooks/ and data/ there, and until it has,
hailer notebook lists what is missing and exits. On the one-run route
those two are uvx hailer login openai and uvx hailer init. hailer also
keeps its own state in a .hailer/ directory in the project, which the
git pane will show as untracked until you ignore it. The abeam[hailer]
extra is for an environment already on Python 3.12 or newer: on an older
one it installs the newest abeam and no hailer, without a word, and
+hailer then says what to run. pyproject.toml argues
the routes, and status says what has been run —
none of them as written.Start it in the directory you want to work in:
abeam
Everything on the command line belongs to the agent, except a single leading
token beginning +, which is abeam's. That is the whole rule, and what it
buys is that abeam <anything> is the session claude <anything> would have
started, with two panes around it.
abeam # the default agent, in the current directory
abeam --resume # ...which is `claude --resume`
abeam -p "fix the tests" # ...and `claude -p "fix the tests"`
abeam agent # ...and `claude agent`, subcommands included
abeam +copilot --resume # GitHub Copilot CLI, with its own --resume
abeam +codex [args] # OpenAI Codex CLI; every argument is forwarded
abeam +hailer notebook # hailer, with marimo started for it (see below)
abeam +bash # anything else on PATH
abeam +help # abeam's own help; `--help` is the agent's
abeam -- +1 more thing # `--` stops abeam reading, and goes to the agent too
A + token is read only in first position, and there is at most one — so a
prompt may begin with a +, and abeam config set +x is a real command line.
Behind the sigil you can write an agent abeam knows (claude, copilot,
codex, hailer), a preset from your config file, or any program on PATH.
Case does not matter. Two names are reserved: +help and +version.
abeam claude, abeam copilot, abeam codex and abeam hailer — without the
sigil — are refused with a message naming both readings, rather than being
reinterpreted. Use the corresponding +name form to select an agent, or put a
prompt that happens to start with one of those words behind --:
abeam -- hailer ….
Codex support is deliberately narrow: abeam +codex [args] hosts the ordinary
interactive Codex TUI in the left pty. Ask is unavailable. Claude's readiness
record cannot establish whether Codex is idle, so queue send items are
blocked both automatically and when you press Enter; type the item in the
left pane instead. Background dispatch is Claude-only and is unavailable too.
hailer support is Codex's, then narrowed twice more by hailer itself. Ask and
background dispatch are unavailable, as they are for Codex, and queue send
items are blocked for Codex's reason — nothing tells abeam when hailer is idle
— so type the item in the left pane. The first further narrowing is marimo.
hailer works in a marimo notebook open in a browser tab, and in 0.2.5 plain
hailer fails unless a marimo server is already running; hailer notebook
starts one or reuses one, opens the tab, runs the chat, and stops the marimo
it started when the chat ends. So abeam +hailer notebook is the line to
type — after hailer init, above — and a preset (below) can type it for
you. The second is the hand-off out of the right pane. A drag there still
copies, but Enter will not hand the rows to hailer's prompt — the border
says the agent is not taking pastes instead. That hand-off is a bracketed
paste, hailer 0.2.5 reads a plain line and never asks for the mode, and
without it a newline in the rows would submit them. The check is abeam's
general rule and not a hailer exception, so a hailer release that turns
bracketed paste on gets Enter with no change to abeam.
What happens to marimo when hailer's pane closes is less settled than it
looks. A marimo that hailer notebook reused was never hailer's child, and
hailer leaves it running on purpose. One it started is its child, and on
Linux it stays in hailer's process group — which, inside abeam, is the one
the terminal hangs up the moment hailer exits, even at an ordinary quit. So
on Linux --keep-marimo is not expected to keep anything, pane closed or
not. On Windows it is unverified whether abeam's kill reaches marimo at all:
hailer.exe is uv's launcher and starts Python at once, and abeam adopts a
child into its job only after the child is running. Nobody has watched
either; status has the detail.
ABEAM_AGENT names what to host when no + token did, and a + overrides it
for one run. It holds a name, not a command line, so ABEAM_AGENT=copilot
rather than ABEAM_AGENT=+copilot. Be aware that it applies to every command
line that does not lead with a +, so one exported in a dotfile years ago will
quietly redirect abeam -p "commit my changes" as well as bare abeam. The
left border always says which agent is taking your typing.
The directory you start in is the first agent's working directory and the root that the git pane, the watcher and the shell use. The right pane can later be pointed at another worktree of the same repository; an agent pane cannot, ever, because a running process cannot be moved — starting a second agent somewhere else is what you do instead.
The left column is your agent — or your agents, if you have started more than one; see More than one agent. The right pane is one of seven views, and switching between them or scrolling them costs you nothing — you only need to move focus to pick something out of a list, to type, or to copy with the keyboard rather than the mouse.
git (F1, G) — read-only. Branch, ahead/behind, staged / unstaged /
untracked files with per-file line counts, and recent commits. Every git call
is a read: it stages nothing and commits nothing. It refreshes when the watcher
sees a write, and on a two-second timer for changes the watcher cannot see, such
as a commit made in another terminal. Enter opens the selected file in the
reader. a starts another agent in this checkout. w lists the repository's
other worktrees, which is how the right pane is pointed at one.
files (F1, E) — markdown and source, to read and, when you press e, to
edit. Markdown is rendered rather
than shown as source: headings, lists, tables, quotes, GFM alerts, footnotes,
YAML front matter as a header rather than a slab of source,
syntax-highlighted code fences, and graph/flowchart and sequenceDiagram
mermaid blocks drawn in box-drawing characters. Nothing of the markup is left on
the page — a heading carries its level in a rule under it or a pip before it,
not in the # it was typed with.
Docstrings and doc comments are rendered where they stand. A .rs or a
.py is still highlighted code with a line-number gutter, but its ///, //!
and """ blocks are markdown by the time you read them, laid out in place
between the code they describe. The gutter stays honest about it: a rendered
block carries the first and last file lines its words come from, with a ┊ on
the rows between, because N lines of docstring do not become N rows of prose and
a number per row would be inventing one.
t swaps any of that for the source it came from, and back — that is what it
means on a documented .rs now as much as on a .md. o opens the outline:
the document's headings, or a source file's definitions, indented by level;
Enter jumps there and Esc leaves you where you were. The title carries a
breadcrumb of the section you have scrolled into.
On startup it opens the newest markdown under the
root; after that it follows what the agent writes. A document arriving while you
are looking at something else waits, and the border says ◆ F1, E rather than
switching under you.
F1, B opens the file browser, a gitignore-aware list starting where the
open file lives, and focuses it. Enter descends or opens, Backspace climbs.
Inside a repository it shows dot-named files too — .claude, .github,
.gitignore — because that is where a good deal of the work lives, and
gitignore is there to keep the rest out. Started somewhere that is not a
repository, gitignore is inert, so they stay hidden as before: nothing else
would keep .ssh off the list.
Three keys search, and they ask three different questions:
| Key | Where | Question |
|---|---|---|
/ | file list | which file is called this |
/ | document | where is it on this page (n / N walk the matches) |
f | either | which files say this — reads every file under the root |
f reads every file under the root except directories that are worktrees of
another repository, which the file list will still walk you into. Standing in
one, the file list's border says unindexed, because a search that answered
0 matches there would be answering about a corpus rather than about the
tree — use w to move the window to that worktree instead.
Only f touches the disk, and it is the only one whose box waits for Enter
rather than narrowing as you type. Enter on a result opens that file with the
document search already looking for the same phrase.
e edits the file on screen, and it is the one way anything in the right
pane writes into your repository. The page becomes its source with a caret in
it and a line-number gutter beside it, at the line you were looking at: the top
line shown if you were reading the source, the same fraction of the way down if
you were reading the rendering, whose rows belong to no one line. From there it
types as the pad does — every letter is a letter, q and j included; the
arrows, Home, End and a click move the caret; PgUp/PgDn page — and
Tab types two spaces, or a tab if the file already indents a line with one.
Ctrl+Z and Ctrl+Y undo and redo a word, a run of deletes or a paste at a
time, and Ctrl+Shift+Z redoes too where the terminal reports the Shift.
Ctrl+S saves and leaves you typing. Nothing else saves — not a pause, not
F4, not quitting. Esc goes back to reading and keeps what you typed: the
page shows your text, rendered or as source as t says, the title reads
● README.md, and e puts you back at the caret — or at the place you have
scrolled to since. Ctrl+S saves from there too. x twice throws the text
away and shows the file as it is on disk; the second x counts only once the
question is on screen, and any other key keeps the text. F4 hands your keys
to the agent and leaves the editor exactly as it was, which is why the border
shows ctrl+s only while the pane has your keys: with the agent focused,
Ctrl+S is the agent's, and in Claude it stashes your prompt.
A save gives back the file it was given. The line endings, a byte order
mark, whether the last line ends in a newline, and the tabs all come back as
they were, so the diff is the lines you changed. A file abeam could not give
back byte for byte stays read-only, with the reason above the page: text that
is not UTF-8, line endings of two kinds, a binary, anything over 512 KiB, a path
that leads outside the workspace once its links are followed. Over 64 KiB the
editor draws plain text rather than colour, and says so. The save writes a
temporary file beside the target and then puts it in the target's place — on
Windows with ReplaceFileW, which keeps the file's permissions and attributes,
waiting a moment if Defender or the indexer is holding it; on Linux keeping its
permission bits. A read-only file is refused rather than replaced, and a link is
saved through to the file it names when that file is inside the workspace.
It will not write over the agent. Immediately before replacing anything
abeam reads the file again and compares it, byte for byte, with what you started
from. If the agent has changed it, nothing is written, the border says
◆ changed on disk, and a second Ctrl+S writes over the version you were just
told about — that one and no newer one, so a change the agent makes in between
is refused in turn. The watcher usually gets there first and puts the ◆ up the
moment the agent touches the file you are typing into; then the first Ctrl+S
is the one refused. A file you have opened and not changed simply follows the
agent: it is read again, with a fresh undo history, so that no Ctrl+Z can take
the agent's change back out without anyone noticing.
While there is unsaved text nothing replaces the page under you. Enter in the
git view, F1, B, Tab, r and a switch to another worktree are each refused
with a sentence on the border, and a document the agent writes meanwhile waits
behind ◆. F1, Q asks twice and names the file — and says whether a recovery
copy of the text was written, or whether quitting will lose it. If the agent
exits, abeam holds the door as it does for a live shell — the left title reads
unsaved README.md · F1, Q to quit — and saving the text does not let go of it:
the door stays held until you press F1, Q, so the keys you type after a save
never land in the shell abeam was started from.
Unsaved text survives a crash, and is never kept in the repository. Two
seconds after you stop typing, a recovery copy goes into your profile directory
beside the pad's — drafts/, next to scratch/ — and not into the work, where
it would be an untracked file in git status. Opening that file again says a
copy is waiting; e puts it back as unsaved text, one Ctrl+Z from what is on
disk, and starts in the ◆ state if the file has changed since the copy was
typed. A copy is deleted by a save or by x x, and by e when it turns out
to hold exactly what is on disk; nothing else deletes one, so quitting keeps it.
a in the file list makes a new file. Name it — a path like notes/plan.md is
fine, and the box reads new file: notes/plan.md · in docs/ · creates notes/,
what you typed first — and Enter opens an empty editor. Nothing exists on
disk until the first Ctrl+S, which makes the directories too, and Esc on a
new file you never typed into leaves nothing behind. A name that is already
there, or differs from one only in case, opens that file for editing instead.
The name is relative to the directory the list is showing, and abeam refuses
..; anything that resolves into a .git directory, however it is spelled; a
link, or a path through one, that leads outside the workspace; a part longer
than 255 UTF-16 units; and names Windows cannot hold — <>:"|?*, CON, NUL,
COM1 and the rest of the reserved names with or without an extension, a
trailing dot or space — on Linux as well, because the next checkout of the
repository may be on Windows. A new file takes the line endings of the files
beside it, and never replaces one that appeared while you were typing it.
Nobody has typed into a file this way by hand yet. It is built and tested; status says what that does and does not cover.
shell (F1, S) — real shells in the directory abeam was pointed at, next to
the session that is about to be told what they printed. pwsh on Windows,
falling back to powershell then cmd; $SHELL then bash then sh on Linux;
or whatever ABEAM_SHELL names. F1, S shows and focuses the active shell,
creating one if there are none; F1, C always starts and selects a fresh shell.
The border identifies the active shell and, when there is more than one, shows
its position in the workspace. Use F1, Left and F1, Right to select the
previous or next shell, wrapping at the ends. F1, X closes the active shell
and its process tree after you enter the full F1, X sequence a second time to
confirm. Closing the last shell returns focus to the agent and leaves the view
empty until F1, S or F1, C starts another.
This is the one view that keeps Esc and q — they belong to the shell — so
F4 is the reliable way out. abeam will not quit out from under a running
command: the agent exiting holds the door and the left title says
shell open · F1, Q to quit.
Telling the agent what it printed is a drag and a keystroke. Highlight the
output with the mouse — that copies it — and press Enter while the highlight
is up to put those rows in the agent's composer without sending them. Long lines
come back as the lines they were written as, not as the rows the pane was too
narrow to fit them in. See Copying out of the right
pane.
queue (F1, W) — work lined up for the agent, for the gap between having a
thought and being able to act on it. Items go one of two ways: send, typed
into one agent's session the moment that agent goes idle, continuing its
conversation; or dispatch, started as its own background agent with none of
that context, running beside you. i writes an item, m switches it between
the two, a arms unattended sending, Enter does the selected one now, d
deletes one and r clears the rows it has finished with. Those last two ask
twice — press again, and any other key, paste or click is the answer no —
because a view key leaves your keys in this pane and a command typed at it by
mistake is made of letters. Enter is not guarded: it is the pane's ordinary
verb and it acts only on the row you chose, but it does end every mistyped
command there is. A send waits for the agent's own record to say it is idle and for
nothing to be sitting unsubmitted in its composer, and announces itself in the
left title first — typing at the agent during that pause defers it.
pad (F1, P) — a scratch pad, one per workspace, and the only thing on screen
that nobody but you wrote. It is where the thought goes when the agent is
mid-task and interrupting it would cost you the turn. F1, P opens it and gives
it your keys, because a pad you have to press a second key to type into is a
picture of a pad. F4 hands them back to the agent. It holds markdown and opens on the
source with a caret in it, so every plain key is a letter rather than a command —
the arrows, Home and End move the caret, and Alt+T shows you the rendering
instead, which is read-only and where a bare t brings the source back. What
you type is saved a couple of seconds after you stop, in your own profile
directory rather than in the repository, so it is still there next week and it
never appears in git status. Ctrl+Z and Ctrl+Y undo and redo in the
source, and Ctrl+S saves at once rather than two seconds later, from either
form. Alt+T here is the pad's own key rather than a
global, and it works from either Alt key like every other one. One thing to
know about it: it needs the pad
to have your keys. After F4, the pad is still on
screen but no longer listening, so Alt+T goes to the agent instead and the
page does not turn over. F1, P again, or a click, and it does. F7, a drag and
Enter work here as they do in every other
right-hand view, which is how a line of it reaches the agent.
ask (? from the document or the git view, F1, A from anywhere) — for Claude
and Copilot, a second copy of your agent in the right pane which may read and
may not write. For
the question that is about what is on screen — what does this call do, where is
this written, is this the only caller — without spending the conversation on the
left. ? attaches the file you were looking at and shows you that it has; F1, A
attaches nothing, which is the question you have while typing at the agent, and
is also the only way to take an attachment back off.
What travels is a path, never a payload: the child stands in the same
directory and goes and reads what it needs, so nothing of your repository is
shipped off the machine beyond the file name shown above the composer. Answers
stream, and the row along the bottom counts the seconds so a long turn cannot be
mistaken for a dead pane. Ctrl+L ends the conversation and the child with it,
which is worth doing when you move on — every turn is re-sent as context with
the next question.
Enter never runs anything. Tab picks a single-line command out of an answer
and Enter on an empty composer types it at the shell without submitting it.
A block of more than one line is never offered, and neither is one carrying a
control character.
Under Claude the child gets --tools "Read,Grep,Glob" — an allowlist, so no
other tool exists for that session — and the pane draws the list the child
reports back, so what is on screen is what it actually got. Under Copilot the
guarantee is weaker and made the other way round, with --deny-tool; that half
has never been run by anyone, the pane says so on its opening screen, and
status has the detail. Under Codex and under hailer, Ask is
unavailable rather than starting a different provider or guessing at a safe
non-interactive mode.
pty diagnostics (F1, D) — what the emulation layer is doing: alt-screen,
application cursor, bracketed paste, mouse mode, byte counts, sizes, and the
frame clock. DSR answered is the one that matters on Windows — a red zero
means the session is hung rather than slow. docs/conpty-findings.md explains
each field.
F1 is the application-command hub. Press and release F1, then press the
displayed mnemonic: F1, S means the sequence, not a simultaneous chord. This
is the only global command language. The former Alt bindings and direct
F2/F3/F6/F8/F9 routes were removed; they are not aliases.
An unrecognised or modified continuation stays in the hub and is never typed
into the focused child.
| Key or sequence | Action | Focus result |
|---|---|---|
F1 | Open the command hub; F1, ? opens the full reference | Unchanged until a command is chosen |
F1, G / F1, E | Show git / files-reader | Keep current focus |
F1, B | Open the file browser | Focus right |
F1, S | Show the active shell, creating one if none exists | Focus right |
F1, C | Start and select a fresh shell | Focus right |
F1, Left / F1, Right | Select the previous / next shell | Focus right |
F1, X | Close the active shell; enter F1, X again to confirm | Keep current focus if another shell remains; otherwise focus left |
F1, W / F1, P / F1, A | Queue / scratch pad / ask without an attachment | Queue keeps focus; pad and ask focus right |
F1, D / F1, T | Diagnostics / reader theme | Keep current focus |
F1, Z | Hide or show the right pane | Keep focus on the meaningful pane |
F1, L | Cycle agent layout: Auto / One column / Two columns | Keep current focus |
F1, J / F1, K | Scroll the right pane one line | Keep current focus |
F1, PageUp / F1, PageDown | Page the right pane | Keep current focus |
F1, N | Focus the next agent | Focus left |
F1, O | Open another agent: the git view's chooser, cursor on the row that re-runs this session's command line | Focus right |
F1, Q | Quit; enter F1, Q again to confirm while a child is live | As confirmed |
F1, Esc | Dismiss the hub | Unchanged |
F4 / F5 | Focus the current agent / show and focus the right pane | Left / right respectively |
F7 | Start or end keyboard selection in the right pane | Focus right |
Ctrl+\ or F12 | Send the next key to the agent verbatim | Agent receives that key |
F4 always returns to the current agent; it no longer cycles agents. Use
F1, N when choosing the next agent is the intention. The command hub makes
the primary shell route independent of the physical Alt key and Windows AltGr
reporting. Alt combinations are forwarded to the focused child unless a
right-pane mode owns one as a local text-editing command. On layouts where a
backslash needs AltGr, F12 remains the portable literal-next route.
View commands un-zoom and drop any active highlight. Git, files, queue, and
diagnostics are glances and retain the existing focus. Shell, pad, and ask
accept text, so their commands focus the right pane. To leave any right-pane
view reliably, press F4.
While the right pane holds your keys its border leads with the way out —
esc→agent, or f4→agent at a live shell, or esc→done in a file you are
editing — ahead of the pane's own keys and then its title, so neither a long
branch name nor a long instruction can clip it off the end: esc→done · ctrl+s save · ● README.md · editing. Nothing else on screen says it: four of the seven
views draw no cursor, so a focused one leaves the window with no cursor anywhere
at all. The pad draws one while you are editing and none in the rendering, and
the files view one while you type into a file and none while you read it, for
the same reason: there is nothing there to put it in front of.
Once the right pane has focus, plain keys work — deliberately the same vocabulary as Claude's own transcript view:
j / k, arrows a line g / G, Home / End the ends
space / b a page Tab / Shift+Tab the selection
Ctrl+D / Ctrl+U a half page Enter open · queue: do it now
r refresh t the rendering / what was typed Esc or q back to the agent
o outline of this document, when it has one
e edit the file on screen a in the file list, a new file
Not all of those mean the same thing everywhere. r refreshes in the files and
git views and clears the finished rows in the queue; a names a new file in the
file list, starts another agent in the git view and arms sending in the queue;
and e and o belong to the files view alone.
Three views do not answer to that paragraph, and a fourth stops answering to
it while you type. The shell takes every plain
key, because a pane you type into cannot also read what you typed. The ask
is the same: its composer is live the whole time the pane is, so j, k, g,
G, space, b, r and q are letters there, and what scrolls is the
arrows, PgUp/PgDn, Home/End and Ctrl+D/Ctrl+U. So is the pad while you
are editing, which is why t there is Alt+T — the letter is a letter. The
pad's rendering is read-only, so the vocabulary comes back to it, t included.
And so is the files view once e has put a caret in it: every letter is the
file's, Ctrl+D/Ctrl+U do nothing, and Esc goes back to reading rather than
to the agent — with your text kept — at which point the vocabulary is back.
That is what the F1, ? reference's mode rows are for: a pane whose keys mean
something else, with nothing on screen saying so, reads as a broken pane.
A Ctrl chord pressed while the right pane has your keys is that pane's, and it
never reaches the agent: the pad's and the editor's Ctrl+Z, Ctrl+Y and
Ctrl+S, the ask's Ctrl+L, Ctrl+D/Ctrl+U for the half page. With the
agent focused every one of them is the agent's, as it always was.
Ctrl+\ exists so abeam can never permanently shadow a binding of the agent you
are typing at. If a future release of an agent binds a key needed outside the
hub, Ctrl+\ then that key still reaches it.
abeam turns mouse capture on, which takes your terminal's own drag-select away — and a linear drag across a split window would hand you both panes and the border between them anyway. So copying is abeam's job.
Drag over what you want and let go. That copies it. No key, no mode: on a
command line, highlighting something is what wanting to take it looks like, and
the border says copied 3 rows · ⏎ agent so you know it went. Press Enter
while the highlight is still up and the rows go straight into the agent's
composer, unsent — which is the round trip the feature exists for: run
something in the shell view, drag over what it printed, hand it over, and add a
sentence of your own before you press Enter yourself.
Ctrl+C copies too, whenever a highlight is up. It is the only Ctrl+letter
abeam ever takes away from a child, and only in that state — with nothing
selected it is the child's, as always. If you meant to interrupt something,
Esc first.
F7 is the same thing without a mouse: it puts a caret on the right pane, the
scroll keys move it, v anchors a selection, and y or Ctrl+C copies. That is
also the way in when the right pane is running something that wants the mouse
itself. While a selection is up nothing reaches the pane behind it, including a
live shell.
Two things worth knowing. The selection is whole rows as they are on screen,
not a range in the content: scroll under it and the highlight stays where it is,
naming whatever is there now — so what is highlighted is always exactly what
will be copied. And the clipboard is reached with OSC 52, which your terminal has
to honour: Windows Terminal, VS Code, iTerm2, kitty, WezTerm and Alacritty do, a
legacy Windows console does not, and tmux wants set -g set-clipboard on.
Enter needs none of that — it never goes near a clipboard.
Claude Code makes git worktrees inside the directory abeam is watching — usually
<root>/.claude/worktrees/<name> — and runs agents in them, so one watched root
can hold several working trees. abeam routes every watched file to the workspace
that owns it, which is the innermost one containing it, exactly as git status
does. A neighbouring agent's writes do not refresh your git pane or pull its
scratch markdown into your reader.
w in the git view lists every worktree git knows about, with a count of the
agents of yours working in each and ▸ where the right pane is standing.
Enter moves the right pane there and puts the status list back. The right
pane's border names its workspace whenever it is not the session's own root.
Design notes has the argument for the routing rule, including why the obvious version of it does not work.
a in the git view starts another agent here, in the checkout you are
looking at. a on a row of that worktree list starts one there. Both are
the whole gesture: no path to type, no confirmation, and the row's own count
goes up on the frame you pressed it.
A is the same key with a question in front of it, and the question is only
ever which one. It opens a list over whichever of the two you pressed it in.
The first row is the command line this session was started with, everything
you typed included, and the cursor starts there; below it are the names +
already takes, which is claude, copilot, codex, hailer and any [preset.*]
blocks of your own, in that order, with the one this session was started as
marked session. So A Enter is an exact copy of the session: uvx abeam agents --cwd "." reads claude agents --cwd . and starts that, abeam +fleet --foo reads fleet --foo and runs what the session ran, claude agent --foo,
and plain abeam reads claude. The session's own row of the table is j
down to the row marked session, and A j j j Enter is a Codex pane
beside your Claude one. The plain copy — the session's program without
anything you typed — is a. An argument with a space in it is shown in
quotes, and a long first row wraps rather than being cut off at the edge, so a
--resume <id> at the end of it is on screen. F1, O opens the same list from
anywhere: it shows the git view, gives it your keys, and asks about the
checkout that view is showing.
That first row runs what it shows, verbatim — -p, --resume, a prompt and
all. Nothing is stripped for what it says, because a row that filtered the
line would run something other than what it shows. What keeps it safe is that
it must have been on screen whole: Enter on it does nothing until abeam has
drawn the whole line, and a line too long for the pane is drawn as far as it
fits, marked too long · widen to run instead of as launched, and does not
run until the pane is big enough to show all of it. The rule is that anything
which re-runs your typed arguments shows them to you first. a shows nothing,
so it runs none of them: it starts the session's program with a preset's own
words and nothing you typed, as it always has.
A program named outright has a first row like every other session. abeam +pwsh -NoLogo has no row in the table, so nothing is marked session, but the
first row reads pwsh -NoLogo and A Enter starts another. a starts
another plain pwsh, as it does everywhere else.
A preset says what it hosts — fleet → claude — because that is what decides
whether the queue can type at the pane. The border names the checkout the pane
will start in, and Esc puts back the list you came from. Choosing something
that is not on the machine opens no pane and writes the sentence abeam +codex
would have given at startup onto the left border, naming what was looked for
and how to install it.
The first of those two is the one most sessions want, because Claude Code makes
its own worktrees: open a second agent where you already are, tell it to branch
off, and it runs git worktree add and moves into the result. abeam does not
make worktrees for you and will not — but it follows an agent that makes one, so
the list's count and the row x acts on move to the worktree it is actually
working in, and so does the pane's own border. The border names that worktree
only when it is not the checkout abeam was started in, which is the same rule
the right pane's label follows and for the same reason: 72 columns is not enough
to spend three of them on the answer that is true by default. So two agents in
one checkout read claude · 1/2 and claude · 2/2, and one that has branched
off reads claude · 2/2 · branch-name.
What does not follow is the record abeam reads to tell whether that agent is
busy: it goes on being matched against the directory the pane started in, which
is what stops one pane being told another session's state. Following an agent
also waits on git worktree list, which runs every ten seconds — so expect the
border and the count to catch up a few seconds after the agent moves, not at
once.
And a pane that is not Claude does not follow its agent at all. Following is
built on the agent writing its own working directory into a session record abeam
can read and check the identity of, and Claude is the only one that writes such
a record — abeam knows of none for Codex, Copilot or hailer, and it will not
guess one out of git worktree list, which would name directories nothing is
standing in.
So a Codex pane that makes itself a worktree and moves in keeps the border and
the occupancy count of the checkout it was started in, for as long as it runs.
The row is still there and x x on it still ends the pane. The same
difference is why the queue can never type at such a pane: its item says
cannot receive on its own row rather than waiting for a state that will not
arrive.
F4 gives your keys to the left column. F1, N moves to the next agent. The
border of the pane that has them is highlighted and reads
claude · 2/3, which is the only thing on screen that says which session your
next sentence is going to.
The default Auto layout uses two agent columns when each can have at least 80 characters inside its border. Agents fill left to right, then downward, in the order you started them. With three agents, the second uses the full height of its column. Smaller terminals use one column.
The right pane reserves room for 120 characters of code, plus line numbers,
a scrollbar and borders (130 terminal columns altogether). It shrinks only on
small terminals where that width and a readable agent cannot fit together.
The full reading width fits from 212 terminal columns; two agent columns fit
from 294. F1, Z hides the right pane and lowers the two-column threshold to
164. These are terminal character columns, not monitor pixels.
F1, L cycles Auto, One column, and Two columns. The hub shows the
preference and the actual column count. Two columns falls back to one when
there is insufficient width; Auto currently uses the same width rule. This
choice lasts for the current session; set agent-layout in your configuration
to choose how new sessions open.
Within each column, a pane that will not fit whole shrinks to its title row, keeping its busy / idle / waiting-on-you word visible where height permits. The focused agent gets priority in its column. A whole pane wants twelve rows and a border, so two agents in the same column want 28 rows. The right pane does not follow the agent cursor: moving between agents keeps your place in the file you were reading.
The queue (F1, W) aims each item at the agent that had your keys when you
wrote it, and it stays aimed there — moving the cursor afterwards does not move
the prompt. The row says which pane it is for once there is more than one, the
three-second countdown appears on that pane's border, and if you close the pane
before the item goes, the item disarms and says so rather than being sent
somewhere else. There is no way to re-aim an item: use F1, N to select the
pane you meant, then write it there.
Closing. x twice at an agent whose child has exited closes that pane; x
twice on its row in the worktree list ends it even if it is still running, and
the pane's own border asks first, in those words — the second press is refused
if those words were not actually on screen, so a fast double tap becomes the
question rather than the answer. Two agents in one checkout are one row, and
abeam refuses to guess between them: it says so and points at F1, N, then
F5 and x x. Prompts queued for a pane you close
are not sent anywhere else — each says which pane it was for, and the border
counts them. The agent abeam started with
is the session and never closes — its exit is what abeam exits with, so leaving
it is F1, Q. While any agent or shell is still live, F1, Q asks twice and
the title says which of them is holding the door.
Read docs/status.md before relying on any of this. It works in the tests
and nobody has yet watched it work.
There is one file, it is optional, and most machines will not have one.
Windows %APPDATA%\abeam\abeam.toml
Linux $XDG_CONFIG_HOME/abeam/abeam.toml, or ~/.config/abeam/abeam.toml
[defaults]
view = "git" # git | files | shell | queue | pad | ask
focus = "left" # left | right — which pane has the keyboard
zoom = false
theme = "dark" # light | dark — the reader's page
agent-layout = "auto" # auto | one-column | two-columns
[preset.fleet]
host = "claude" # an agent abeam knows, or any program on PATH
args = ["agent"]
view = "queue"
theme = "dark"
[preset.openai]
host = "codex"
view = "files"
[preset.data]
host = "hailer"
args = ["notebook"]
[defaults] is how every session on the machine opens. A preset is a name
behind the sigil that behaves exactly like a built-in agent: abeam +fleet --resume starts claude agent --resume with the queue showing, and +help
lists fleet, openai and data beside claude, copilot, codex and
hailer. abeam +openai [args] resolves the preset to the built-in Codex host
and forwards the typed arguments after any preset args. A preset's own args
go in front of what you typed, because a subcommand is the first word of its
line — which is what makes abeam +data the hailer notebook that hailer
0.2.5 needs, without typing the subcommand each time.
Migration: codex is now a built-in, so an existing [preset.codex] is
reserved and the configuration is refused. Rename that preset (for example to
[preset.openai]) and invoke the new name behind +.
And again for hailer, which joined the built-ins the same way, so a
[preset.hailer] — the obvious thing to have written to reach it before — is
now refused too, and the refusal says which fix it needs. One that sets
nothing but host = "hailer" only repeats the built-in, and can be deleted.
Any other wants a new name, such as [preset.my-hailer], with its host line
left as it is — where that line is host = "hailer", it now names the
built-in.
Three things are refused rather than quietly worked around: a preset whose
host names another preset (there is no chaining, so there is no cycle to
check), a preset taking a name abeam already answers, and a key abeam does not
recognise — a [presets.fleet] with the plural spelling is a line you wrote and
expected to work, so it is an error rather than a shrug.
It is read from your profile and never from the repository, which is a
security decision rather than a filing one. The repository on screen is the one
directory in this program somebody else gets to write to, and abeam goes to some
length to make sure a claude.exe committed to it can never be what starts. A
repo-local config would undo that in six lines of TOML.
Working, and used. Not finished. The full account is in docs/status.md; these are the things most worth knowing before you install it.
main and on every pull request, and the six manual
pass criteria in
docs/conpty-findings.md have been confirmed on Windows only.hailer at all.Ctrl+S, will not
write over a change the agent has made since you opened the file unless you
press Ctrl+S a second time, and keeps unsaved text in your profile rather
than beside the file — all of it tested, none of it used. The Linux half of
saving is tested by CI's Linux job and has never run on a developer's machine.F1, Q. There is
no cancel key and no timeout.cargo build --workspace --all-targets
cargo test --workspace
cargo clippy --workspace --all-targets
--all-targets, or the examples bit-rot. cargo run -p abeam and cargo run -p abeam -- +copilot do what the installed binary does. Building needs Rust 1.95+,
the MSVC toolchain on Windows and a C toolchain on Linux for the linker; neither
build looks for a system library.
To release: bump version in [workspace.package] in Cargo.toml — it lives
there and nowhere else — commit it, then tag and push.
git tag v0.8.1 && git push origin v0.8.1
release.yml refuses to run if the tag disagrees with Cargo.toml, builds a
win_amd64 and an x86-64 manylinux wheel, and publishes both to PyPI through
trusted publishing. workflow_dispatch builds the wheels without publishing, so
the build can be exercised without spending a version number.
crates/abeam-pty/ the pty host layer: sessions, input, DSR
crates/abeam-pty/src/tree/ killing a child's children, per platform
crates/abeam/ the binary: shell, layout, focus, panes
crates/abeam/src/agent.rs the agents abeam knows, and how one is chosen
crates/abeam/src/config.rs the one file abeam reads
crates/abeam/src/launch/ where a program may be found, and what may
then be started
crates/abeam/src/workspace.rs which workspace owns a watched path
crates/abeam/src/select.rs the rows a drag chose, and the keys that
choose them without one
crates/abeam/src/ask/ the second agent, and a wire format abeam
does not own
crates/abeam/src/editor/ text with a caret in it, and undo: what the
pad and the files view both type into
crates/abeam/src/disk/ reading a file to edit it and writing it
back — the only code that writes into a
repository — and the recovery copies, which
never go there
crates/abeam/src/panes/ one file per view
crates/abeam/src/panes/pad/ the scratch pad, and the file in your
profile it saves itself to
crates/abeam/tests/end_to_end.rs abeam itself, hosted in a pty and typed at
Most of the reasoning lives in the module documentation of the file that owns each decision, rather than in this README.
| Document | |
|---|---|
| docs/status.md | what is done, what is not, and what nobody has watched work |
| docs/keymap.md | the keyboard audit against Claude, Copilot, Codex and hailer, including custom-map gaps |
| docs/design.md | worktree routing, the draw loop, the layout |
| docs/conpty-findings.md | read before touching the pty layer. Five constraints that look like things to tidy up and are not |
MIT. See LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.15.0 | 8 Oct 2026 | 1 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 17:19 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:077606d03e784cadc1ad1d147358378e7cccad1faf777538c57c05691530c1fb