-
Notifications
You must be signed in to change notification settings - Fork 0
Sessions and the terminal
Argus attaches to the tmux sessions that already exist — it does not wrap them, and it starts nothing you did not ask for. Closing the tab detaches; the session carries on. A dropped connection reattaches by itself, which is what a phone changing network does several times a day.
Under it is a real pseudo-terminal (pty.openpty → login_tty → tmux attach), not a
poll of capture-pane, so full-screen programs, colour and mouse mode all work.
-
Every session says how long it has been up rather than the day it started. "Aug 03" answers a question nobody asks; what you want to know is whether something has been going for ten minutes or for two months. The exact moment is still there, on hover.
-
Every session says how much RAM it is using, and not just the shell sitting in the pane — a shell alone never grows past a few megabytes, whatever is actually eating memory is something that shell went on to start. So the figure is the pane's own process plus every descendant it has spawned, summed across every pane the session holds, which is what makes a runaway job show up on the session that started it rather than as an anonymous number on the System screen. It costs one
list-panesand onepsfor the whole server, not per session, and it is only read when this screen opens.
A phone has no Ctrl, no Esc and no arrows. The bar under the terminal has them, plus:
-
^B— the tmux prefix as one key. Useful on a desktop too: Firefox keeps Ctrl+B for its bookmarks, so the keystroke never reaches the page. -
Ctrl— sticky: tap it, then the next character becomes a control code. - ⧉ copy, ⤢ fit, 🔒 hold, A− A+, and a keyboard toggle.
The bar scrolls sideways; the order is the priority order.
tmux draws a window at one size and gives it to whichever client acted last. With a phone and a desk on the same session, someone always loses. So the size is claimed:
- ⤢ takes the size for the screen you are looking at, now.
- 🔒 says "I am only watching": this client stops resizing the session, and shrinks its own type to fit the whole grid instead. Tap again to take the size back.
Argus also claims the size when a tab comes back to the front, which is usually what you want and never what the other device wants — hence the lock.
A selection made with tmux's mouse mode lands in a tmux paste buffer on the server; the browser cannot see it, which is why Ctrl+C never worked. The ⧉ button takes whatever is selected in the browser, or the tmux buffer otherwise, and puts it on the clipboard of the device in your hand — including over plain HTTP, where the clipboard API does not exist.
Setting set -g set-clipboard on in your tmux config makes tmux send the selection as an
OSC 52 escape, which Argus also honours.
Drag a file from your desktop onto a terminal — in a window or filling the screen — and it
is uploaded and its path is typed into that session. Not sent: it sits at the prompt, or
in the agent's input box, waiting for you to say what to do with it. read in front of it,
Enter, and the agent has the file.
The path was always going to be typed there — that is what the drop was for — and handing it over through the clipboard left you to do the last step by hand, which on a phone is two taps and a long press. It goes to the clipboard as well, quietly, for when the file is meant for something other than that session.
How you know it worked: the frame the drag drew comes back for a moment, solid and green, and the path appears where your cursor is. A file whose name has a space in it arrives quoted, so the shell still sees one argument, and each drop ends with a space so a second one does not begin where the first ended.
This is the same move as pasting a screenshot into a listing, from the other end. The
alternative was scp from your laptop and then working out where it went, or opening a
file browser next to the session purely as a place to drop things through.
A terminal is not a folder, so there is no here to drop into — the file goes to one
folder the server keeps for exactly this, drop_dir in the config. By default that is
argus-drops inside the first root, made the first time you drop something and never
before. The name it arrived with is kept; the second report.pdf becomes report-2.pdf
rather than being refused, because the second version of a report is the normal case and
you were not looking at that folder anyway.
One tap to it from anywhere. A folder icon in the header — hidden on a server with no
drop_dir — opens a window onto that folder on the desk you are on, whichever screen you
were looking at: a document, Settings, another desk entirely. The desk's own Browser
button opens that desk's folder, which is a different thing; a screenshot pasted from
Settings has nothing to do with any one desk, and until this existed, reaching it meant
remembering the path and typing it in.
Nothing in there is deleted unless you ask. drop_keep_days — in the config, as
--drop-keep-days N, or as Keep dropped files for in Settings → Files — sweeps it when the
server starts and once a day after that: files older than N days, by when they were last
written, and nothing else. Folders in there are left alone, and so are symlinks: a tidy-up
that recursed would be a recursive delete, and following a link would reach outside the
folder entirely. Every file it removes is named in the log, because this deletes without
asking. 0, the default, keeps everything for ever — a path handed to an agent yesterday
has to still resolve today, and choosing which of your files have expired is not something to
do quietly.
Setting it from Settings sweeps immediately and says how many went: a number whose effect you only see tomorrow is one you cannot tell you set correctly. It writes the one line into the config file and leaves the rest of it, comments included, exactly as you wrote it.
Several files at once are typed as a list, ready for a command that takes several.
None of this depends on the clipboard, which matters: over plain HTTP the clipboard API does not exist at all, and this is the one path into a session that never needed it.
It needs --allow-write; a read-only Argus does not light a session up as somewhere a file
can land. And nothing dropped anywhere on the page can navigate the tab away from the app
any more, which used to be one slip of the hand while dragging towards a folder.
Copy a screenshot and paste it with the cursor still in the prompt. It becomes a file in
the drop folder — screenshot-1.png, numbered by the server, because the clipboard calls
every one of them image.png — and its path is typed into the session, exactly as a dropped
file's is.
A terminal owns its paste, so the rule is narrow: Argus takes only a clipboard carrying an image file. Text of every kind still goes to tmux untouched, including the HTML flavour that rides along with a copied picture. That is what lets both live on one keystroke.
Ctrl+V is a paste here, not a ^V sent onward. xterm hands that key to the far end as a
control code, which through a browser can only disappoint: an agent that receives it goes
looking through the server's clipboard, and your image is on the machine with the keyboard.
Measured — with the key pressed for real, no paste event reached the page at all. So the
terminal gives the key up and the browser does what it does everywhere else.
A literal ^V — readline's quoted-insert, vim's visual block — is still there: tap Ctrl
on the key bar, then V.
Pasting where no folder is open used to say "open a folder first". Now it goes to the drop folder and hands back the path. A file listing in front of you still wins: you were looking at that folder, so the image goes there.
Text in the desk's row opens a window: paste, and it is saved as a file whose path comes back on your clipboard. Same errand as the drop, for the case where what you have is not a file yet — eight thousand characters of log, a schema, somebody's email.
A window and not a box that takes over the screen, because of what it is for: the text is going to somebody in the terminal beside it, and you want to see both. It sits on the desk like everything else, is moved and resized like everything else, and stays open while you paste the next thing.
The terminal is the wrong shape for that text. Pasted into a prompt it is a wall an agent
reads badly and a pane redraws slowly; pasted into cat > file it is at the mercy of
bracketed paste and of whatever the text does to the shell. A path is one line, and the
agent opens the file itself.
It lands where drops land and is called note.txt unless you say otherwise — the name is a
box, and the window's title follows it, so schema.json or mail.eml if the extension
matters to whatever reads it next. A name already taken becomes note-2.txt. Ctrl+Enter
saves; Enter cannot, because the whole point is text with a great many newlines in it.
Where it went stays on the window, with its own copy button: a clipboard holds one thing and the next thing you copy is the next thing you copy, while the path on the window is still readable in an hour, which is when you want it again.
An unsaved draft survives a reload — kept in the tab, never sent anywhere and never written into your preferences, which are one document every device fetches and no place for a scratch pad's contents.
The same button sits in the file browser's toolbar beside ↑ and 🔗, and there it writes into the folder you are looking at: when a folder is in front of you, that is where you mean it to go. The drop folder is for when there is no folder in front of you.
Hovering a line asks the server which of its words are real files. Those get underlined;
clicking one opens it in the viewer and points the file browser at it. Relative paths
resolve against the pane's own working directory, so src/main.rs means what the person
reading it meant. On a phone, a long press does the same.
URLs are links too. A loopback URL — http://localhost:5002, which agents print
constantly — is the interesting case: on the phone reading it, localhost is the phone.
Argus is already on the right machine, so it opens that port and serves it through
/proxy/5002/ instead (needs --allow-proxy).
Ctrl (⌘ on a Mac) + click skips all of that. A plain click is a helpful guess — open
the file in the viewer, open the port through the proxy — and a modifier held down is
asking to be spared the guess: a file opens as the real bytes on disk in a new tab, no
viewer chrome around it and no max_preview_bytes either — that cap is itself part of the
guess a plain click makes, and does not apply to a request that already said it is not a
preview — and a URL opens exactly as printed, un-proxied. The one browser convention
everybody already has for "this exact thing, in a new tab, don't interpret it," applied
here too. The desk's Links tray, which is a running record of the same paths and URLs,
answers to the same click.
Scroll back and a round ↓ appears over the pane; it returns you to where the work is
happening. It asks tmux to leave copy-mode, so it appears whenever tmux is holding the
history — including when you entered it with ^B [ — and not when it cannot help.
It cannot help when a program has taken the mouse: an agent's own transcript view scrolls
itself, tmux never enters copy-mode, and only that program's own key brings you back. Use
^B [ to scroll tmux's history instead, and the button works again.
The same names work at a shell prompt, and in an agent's own input box. Type
echo {{genpat_paper.paper_tex}}
and as the closing braces land it becomes the path. What you typed has already gone to tmux by then, so the expansion is done the way a person would: backspaces to erase it, then the value.
Two braces, not one. In a saved prompt the whole text is a template, so {paper} is
unambiguous; in a terminal it is not — {...} already belongs to the shell, to JSON, and
to most languages. The doubled form cannot collide with any of them, so {"a": 1} and
mv x{,.bak} are never touched. Settings offers {{ }}, { } and @{ }; the first is
the default and a saved prompt accepts all three.
Typed, pasted, or half of each. One buffer holds what has gone to the session, so you
can paste {{pap, type er}}, and it is still a placeholder — including through the
bracketed-paste markers a terminal wraps a paste in.
It works inside a full-screen program as well — an agent's own input box is one, and is the whole reason for having this. All it relies on is that a backspace is a correction, which is true of any text field. Verified by typing a placeholder into vim in insert mode and reading back what the saved file contained.
The one place it could surprise is a program where { is a command rather than a
character — vim's normal mode, say — and there you would not be typing {name} in the
first place.
Settings has a switch, Placeholders as you type, on by default.
Ctrl+C in an agent's input box interrupts the work, which is not what you want when you
only meant to throw away what you had half-typed. Ctrl+U empties the line and leaves
the job alone — it is on the key bar as ^U, since a phone has no Ctrl to reach it with.
Typing into a terminal on Chrome for Android can make a word arrive twice. It is not something Argus is doing: a terminal takes input through a hidden textarea, and Chrome — with GBoard especially — wraps Enter and Backspace in composition events of its own, which when interrupted deliver the word again. It is xterm.js #3600, open since 2022.
Argus drops the clean cases: a word repeated within a moment of the keyboard committing it, and a whole word repeated within 120ms of itself. Single characters are never dropped — two of the same letter are two keystrokes, and losing one would be worse than the bug.
For the rest there is a box to write in, from the pencil in the key bar. It is an ordinary text field, so predictive typing behaves the way it does everywhere else on the phone, and the finished line is handed to the session in one go — the bug is in the path between the keyboard and the terminal, and this does not take that path. Enter sends, Shift+Enter starts a new line, ↵ puts the text in without running it, and placeholders are filled in on the way.
It is also simply better for writing a paragraph to an agent, which is most of what a phone gets used for here.
A phone has no wheel and tmux with mouse on scrolls only when it gets one, so a drag is
turned into wheel turns. Two things make it follow the thumb rather than lurch:
- the drag is converted to whole lines with the remainder carried over, instead of raw pixels whose fractions were thrown away
- how far tmux moves per wheel turn is measured, not assumed. It is not one line — here it was five, so a finger that had travelled ten lines sent the history forty-five lines away. The first drag that starts inside the history measures it; after that a drag of ten lines moves ten lines.
It will never feel like scrolling a page: every turn is a round trip to tmux, which repaints the pane. What it can do is move with your finger and ask for as few turns as possible, and it now does both.
The chain button in a terminal's title bar puts that session into the desk's chain. Everything typed into any chained window reaches all the others: characters, Enter, Ctrl+C. There is no pairing and no direction — a window is in the chain or it is not, which keeps the answer to "who is hearing this" down to something you can see.
tmux has synchronize-panes, but it spans only the panes of one window and it changes
what every client attached to that session sees. This crosses sessions, and it belongs to
your browser rather than to the server.
Because this is the one control in Argus that can send a command line to three machines at once, it says so loudly: every chained window is outlined in amber, the button is lit, and the toolbar carries a count that unhooks all of them in one click. The chain is kept with the desk, so it survives a reload — which is the point when you are running the same thing on three machines, and the reason the outline is not subtle.
Two agents running on the same machine do not need to talk to each other: they share a filesystem. So what travels between them is not the work — it is a baton: a short sentence and a pointer to where the work is. Passing prose from one to the other loses context and turns the first one's output into the second one's instructions, which is a bad shape for both reliability and safety.
The button is in the title bar of every terminal, and it lights up when that session's bell says it has finished. It opens the sentence for you to read before it goes, and puts it into the other agent's prompt without an Enter.
The i beside a terminal's name opens a strip of five facts — open by default, and one press
closes it on every terminal at once:
[model Opus 5] [tmux ~/argus] [codex ~/phd_thesis] [desk ~/test_argus] [values Trading]
| model | what the agent says it is running. Nothing outside it can know: the process is called claude whatever is behind it, and a model named in a config is the one it started with rather than the one /model chose since |
| tmux | where tmux sees the session, which for an agent is where it was launched. Shown only when it disagrees with the next one — when they agree, one chip carries both |
| the agent's name | where the agent itself says it is working. claude: and codex: rather than "process", because on a desk holding both that is the distinction that matters |
| desk | the folder this desk opens in — what a browser lands on and what {folder} fills in |
| values | the set of placeholders this desk fills from |
The folders are places you can go: press one and a file browser opens there; press values
and the placeholder set opens. Only the agent's folder is ever coloured — green when it
agrees with the desk, amber when it does not — because that is the one a prompt full of
{folder} will act on.
Which agent is in a pane is worked out from the terminal itself rather than from
pane_current_command, which reports the pane's first process: a Codex started through its
npm shim is node, with the real binary two processes down. The command name is matched, or
the program a wrapper was pointed at — and only by basename, so a folder called
codex-andrea in a vim command line is not an agent.
The folder on a terminal's title bar is the pane's process directory — what tmux reports
as #{pane_current_path}, which is /proc/<pid>/cwd of whatever is running in that pane.
For a shell that is exactly right: cd really does chdir(), and the mark follows within
ten seconds.
For an agent it is right and useless. Claude Code and Codex run your commands in children
and keep their own idea of where they are; the agent's own process never calls chdir(), so
it stays where it was started whatever it is working on. Measured: a claude started in
/tmp/agente-qui reports that path from both pane_current_path and /proc/<pid>/cwd, and
nothing at the tmux level can see otherwise, because nothing has changed.
So whatever does know can write it down, and Argus will believe it:
tmux set -p @argus_cwd /the/folder # -p: this pane onlyClaude Code can do that on every turn, from its own status line hook, which is handed the
live directory and inherits $TMUX_PANE. In ~/.claude/settings.json:
{ "statusLine": { "type": "command", "command": "~/.claude/where.sh" } }#!/bin/bash
# Tell tmux where this agent is actually working, and print a status line while we are here.
read -r payload
dir=$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin)["workspace"]["current_dir"])')
[ -n "$TMUX_PANE" ] && tmux set -p -t "$TMUX_PANE" @argus_cwd "$dir"
printf '%s' "${dir/#$HOME/~}"With nothing set, the pane's own directory is used, which is the honest answer rather than a guessed one.
{folder} in the sentence is the sending session's own working directory, the one
tmux gave it — not the folder the desk was given. Those are different things and it is
worth knowing which is which:
| what it decides | |
|---|---|
| the desk's folder | where a file browser in that desk lands |
| the session's directory | where the agent actually is, and what {folder} becomes |
Argus asks tmux for it when the sheet opens and shows you the answer, falling back to the desk's folder if the session cannot say. Nothing here changes where an agent is running: that was decided when the session was made.
A modal you open, aim and dismiss thirty times in an afternoon is thirty times too many, so there is no button on the terminals: Prompts is a window on the desk, from the button in the desk's toolbar. It holds the folders and their messages, and you drag one onto a terminal — the same gesture as a path out of the link tray.
Or just tap it. Along the top the window says where a tap goes:
TO [ claude ] chatgpt
and that follows the terminal you last touched — clicking into a session, or sending a message to one, aims it there. So the ordinary case is one tap and nothing to answer, and when you do want somewhere else you tap the name and it stays. Being asked "which session?" every single time is the click worth removing; guessing silently is worse than asking, which is why it is shown rather than assumed.
Deleting one takes no confirmation: the row is replaced, where it stood, by a red line with Undo in it, and after five seconds it is done. A dialog for something that small charges you a click every time to save you from a mistake you can already take back.
Hovering a prompt shows what it will actually say, filled in, without sending anything — the placeholders resolved, the folder of the session it would come from, the names of both ends. A finger never opens it, since tapping sends the prompt; on a touch screen the ⋯ shows the same thing.
Each prompt says whether it runs. Open it and the switch is beside its folder: waits for Enter or sends it. Some prompts are errands — run the tests, make the plan — and want to go; others hand over something to look at, and pressing Enter for you would be presumptuous. It is off by default, and a prompt that will run carries a ↵ in the list, so you know before you tap rather than after.
The ⋯ on a row is for the times a word wants changing before it goes: it shows the text filled in, lets you edit it once and sends it. That edit is not kept — the library is edited where the library lives, which the pencil in the title bar goes to, and anything changed there shows up in the window straight away.
Dragged from the window there is no sender to speak of, so {from} and {folder} are
taken from the other terminal in the desk — the one whose work is being handed over.
The wording is a prompt, and prompts are a library that follows you between desks. Prompts, in the bottom bar, is where they are written: rename, duplicate, delete — deleting takes no confirmation, the row becomes a red line with Undo in it for five seconds — and each one shown filled in as you type. The window on the desk only picks one and sends it: a place you pass through in a hurry is the wrong place to keep a library.
It comes with three:
| Referee | review this, do not edit it, end on a verdict |
| Referee back | here is the review — fix what it got right, and say what you disagree with |
| Relay | improve what is weakest and pass it back; if nothing was worth changing, say so |
Write your own wording, press Save as…, and it joins them. The ones it came with stay; yours have an × to forget them.
They live in groups you name — Paper review, Web development, Migration — because a flat list of fifteen sentences is a list nobody reads.
The screen has two sections — Messages and Placeholders — one at a time, and every message is shut until you open it: a line with its name and its opening words, which is enough to find the one you meant.
A group is a thing in its own right, so New group makes an empty folder and it stays. Each folder carries Add here (which asks for the name before the message exists), Rename and Delete — deleting a folder with messages in it moves them to General rather than taking them along. The box under an open message says which folder it is in; type another name and it moves there.
The screen is for writing prompts; the window is for using them, and everything about it is shaped by that.
Everything starts shut. Groups, prompts, placeholder lists — a window three hundred pixels wide holding forty open rows is a window you scroll rather than read. What you open stays open while the window is there.
Counts sit beside the names, so a shut group still says how much is in it, and a prompt says how many placeholders it will fill before you open it.
A ↵ beside a prompt means it sends itself — it is a mark, not a button. It says what will happen when you press the row; it is not a second thing to press.
Send to more than one session at once. The chips at the top say where a prompt is going, and you tap them on and off: two agents, three, or one. Sending the same instruction to a pair is the ordinary case with two agents on one job, and doing it twice by hand was the sort of thing you notice you are doing on the fourth round.
Placeholder values can be edited in place. Open a prompt and it lists what it takes and what each one is worth on this desk right now; a value is a box, and typing in it writes the set. A prompt that needs a name you have not filled in is a prompt you want to fix where you noticed it, not two screens away.
What you leave open stays open. Which groups and which prompts are unfolded is remembered across a reload — it is a place in a library rather than a mood, and shutting everything makes the third prompt in the fourth group a hunt again.
Ordering is not here. Groups and prompts are dragged into order on the Prompts screen and nowhere else: this window is where you send from, in a hurry, and a list whose rows can be picked up is a list where a press that travels three pixels moves something instead of sending it.
Which one comes up when you open the sheet is the one you last sent from that session. That is how a referee's outward and return sentences sort themselves out without anybody declaring a direction, and it keeps working with three sessions and five templates.
Three are filled in from the situation itself, so you never have to write them down:
{folder} |
the sending session's own working directory — see above |
{from} |
the session handing over |
{to} |
the session receiving it |
They are defaults, not reserved words: define folder in a set and yours wins. That is
the right answer when every prompt in a project should point at one directory rather than
wherever the agent happens to be sitting. The row says what it is covering — instead of
the folder of the session it comes from — because {folder} quietly meaning something
else would be a nasty surprise three prompts later.
The rest are yours, on that same screen, two boxes to a row — a name and what it stands for — kept in named sets.
Default is the ground truth. Everything you always want available:
author |
de Ruvo |
journal |
BMC Genomics |
Any other set says only what it changes. A set called pluto holding just
journal = Bioinformatics still has your name in it, taken from Default. The row says
what it is covering — instead of BMC Genomics — and what it is inheriting is listed
underneath, one tap to give this set its own copy.
Or name the set in the prompt. {genpat_paper.paper_text} takes paper_text from
the set called genpat_paper, whatever set the desk happens to be on — for the prompt
that is always about one project while the desk is doing something else. The named set
still falls back to Default for what it does not define, exactly as it would if the
desk were using it. It is on by default, and there is a switch in Settings
(Placeholders from another set) for anyone who would rather a prompt only ever read its
own desk.
A desk picks a set. From the desk's own ⋮ menu — that is a property of the desk, not of the library — and each set says which desks are using it.
On the Prompts screen the previews fill from the set the desk you came from is on, and a preview with picker lets you read them through any other set without changing what the desk uses: reading is not choosing. When the two differ it says so, rather than leaving you to wonder why a value came out that way. That is the point of naming them: one set serves every desk about the same thing, instead of each desk keeping its own copy of your name.
An empty row waits at the bottom of each: adding one is typing, not finding a button.
That is what makes a template reusable: the thing that changes between desks is named
once rather than typed into the sentence every time. One Referee paper wording then
serves every manuscript you have.
A placeholder with nothing to put in it is not quietly emptied — it is flagged under
the box and goes across written as {like_this}, where you or the agent will notice it.
And the sheet shows what will actually be typed, filled in, before anything is sent.
A prompt goes into the other agent's box and, unless it is marked with ↵, stops there: what to do with it is the point of handing it over.
For the ones marked to send themselves, the return is a separate write half a second later, and that detail is the whole of why it works. An input box does not read lines, it reads writes: a box that assembles a paste treats everything arriving in one read as part of the paste, so a return sent along with the text lands in the box and the prompt sits there, complete and unsent. Claude Code submits it anyway; Codex does not — which is what "on one it starts by itself and on the other I have to press Enter" was. The pause is in Settings if an agent of yours wants longer.
The round trip can run on its own, but only in one place and only if you ask for it —
the reviewing pattern on Two agents on one job, where a VERDICT: REDO can
go back to the builder by itself, switched off by default and counted down so it ends.
That page is also where the plan file, the two patterns and the pair note live.
Three things worth knowing before you use any of it in anger:
- The referee that matters is not the other model. An LLM judging an LLM is a weak judge. Ground the round in something that cannot be talked round — your test suite, the type checker, a benchmark — and let that decide whether it goes another round.
- Use two different models. Two instances of the same one share their blind spots and tend to approve each other.
- Commit before each round. Two agents in a loop with write access can undo an afternoon quickly, and a checkpoint is what makes "undo" mean something.