Skip to content

Everything it does

Andrea de Ruvo edited this page Aug 27, 2026 · 9 revisions

Everything it does

The long list. Home says what Argus is in a paragraph and the pages beside it take one part each; this is the whole catalogue, in the order it was built, kept in one place so that "does it do X" can be answered by searching a single page.

What it is for

You set an agent going in a tmux session and it runs for two hours. Then one of these happens, and none of them is a thing a terminal is good at:

  • It asks you something at twenty to eight, and you are not at the desk.
  • It finishes, and what you need next is not the terminal at all — it is the thing it wrote: the log, the table, the figure, the report.
  • You want to answer it from the train, without an SSH client, a keyboard that eats a third of the screen, and a terminal that turns to confetti when you rotate the phone.

Argus is a small web app the machine serves and a browser you already have. It attaches to the session that was already running — not a new one — and puts the filesystem in the same room as it: a path printed in the session is clickable, a file the agent writes turns up on its own, and what it wrote is readable in place.

pip install -r requirements.txt
python3 -m app.main --allow-write        # prints a URL with a token in it

Open the URL, or scan the QR code it can print, and the phone is in. That is the whole setup: no build step, no database, no agent to install anywhere, and nothing changes about how you use tmux — kill Argus and every session carries on.

Warning

Early days. It is used daily on the machine it was written for, and it changes most days: expect rough edges, expect settings to move. More importantly, Argus is remote shell access — anyone with the token can run anything you can. Read Security and Reaching it from outside before putting it anywhere but a trusted network.

Two agents working in tmux, the folder they are writing into, and the report they produced, all in one workspace

Named for the herdsman of a hundred eyes, who was set to watch and never slept.

Why this exists

An editor is built for the hours you spend writing code. More and more of that work is now done by an agent that runs for two hours in a tmux session, and what is left for you is a different job: keeping an eye on it, unblocking it when it asks something, and judging what came out — the log, the table, the figure, the document.

That job needs almost nothing an editor is good at, and one thing it is bad at: being somewhere else. The work carries on when you close the laptop, so the useful question is what you can do from the train. Today the answer is an SSH client on a phone — a keyboard that eats a third of the screen, a terminal that turns to confetti when you rotate it, no way to look at the log and the plot at once, and no answer at all to "is the disk full again?".

Argus is the other way round. The machine serves a small web app; the phone is just a browser. You reattach to the session that was already running — not a new one — read what it printed, answer it, and close the tab. The session never notices.

The point is what sits next to the terminal. An agent's output is mostly references to things: a path it wrote, a report it generated, a number in a file. So the terminal and the filesystem are the same room here:

  • a path printed in the session is clickable — it opens beside the terminal, and the file browser jumps to it and marks it
  • a file the agent writes appears on its own, without you refreshing anything
  • the thing it wrote is readable in place: markdown with its figures, PDF with a search box, Word, images, a log that starts at the end
  • a screenshot you paste lands in the folder you are looking at and hands you back its path, ready to paste into the session as the next instruction
  • a file you drag onto the session itself is uploaded and its absolute path goes to your clipboard — the shortest way to get something off your laptop and into a prompt
  • and a large piece of text you paste into a window becomes a file the same way, for when what you have is not a file yet
  • Ctrl+V a screenshot into the session itself, cursor still in the prompt: it becomes a file and its path is typed there

None of this runs the agent or knows anything about it. Argus attaches to the tmux session it already lives in, the way another terminal window would.

Questions? The FAQ answers the ones people actually ask: whether it can go on the internet (no), whether it changes tmux (no), why there is no Docker image, what happens when you log out, and what it sends anywhere.

What it is not

  • Not a tmux replacement. It attaches as an ordinary client. Kill Argus and every session carries on; the app has no state your work depends on.
  • Not multi-user. One token, one machine, one person's work. There are no accounts.
  • Not a hardened public service. It is remote shell access wearing a browser. Run it on a LAN or behind a VPN, and read the security section before anything else.

Screenshots

A workspace holding a terminal, a file browser and a log A session attached, with a key bar for the modifiers a phone lacks
Windows you arrange yourself, snapping to each other A real PTY, not a poll of capture-pane
The file tree A rendered markdown document
Files as a list or a tree, with icons and sizes on demand Markdown, PDF, Word, images, logs — rendered, not downloaded
The machine at a glance A session on a phone
CPU, memory, swap, GPUs, disks, listening ports The same session, on the device you actually have with you

The screenshots come from a demo instance with fabricated sessions, files and services.

Features

Sessions and the terminal

  • Attach to the sessions that already exist, or start new ones. Detaching leaves everything running; a dropped connection reattaches by itself.

  • A real pseudo-terminal. pty.openptylogin_ttytmux attach, so anything that works in a terminal works here: full-screen programs, colour, mouse mode.

  • Dragging a finger scrolls the session, and the text keeps up with the thumb: the drag is turned into whole lines with the remainder carried, and how far tmux moves per wheel turn is measured rather than assumed — before that, a finger travelling ten lines sent the history forty-five lines away.

  • A box to write a line in, for the phones where typing into a terminal duplicates words. Chrome on Android wraps Enter and Backspace in composition events of its own, and interrupting one makes the word arrive twice — xterm.js #3600, not something an app can fix from outside. So there is a way in that never meets it: an ordinary text box, where predictive typing behaves as it does everywhere else, and the finished line goes to the session in one go. Enter sends, Shift+Enter is a new line, placeholders are filled in.

  • A key bar for what a phone has not got — Esc, Tab, arrows, ^B (the tmux prefix), ^C, ^D, ^U (clear the line without interrupting what is running, which ^C would), and a sticky Ctrl for everything else.

  • Sizing you decide. Two clients on one session cannot both pick the size, so the size is claimed: ⤢ takes it for the screen you are looking at, and the lock beside it says "I am only watching" — the desk keeps its geometry and the phone shrinks the type to fit the whole grid.

  • Copy out of tmux. A selection made with tmux's mouse mode lands in a paste buffer on the server, where a browser cannot reach it. The copy button brings it to the clipboard of the device in your hand — even over plain HTTP, where the clipboard API does not exist.

  • Chain two sessions and type into both. A chain button on each terminal; what you type in any chained window reaches all the others, Enter and Ctrl+C included. tmux's own synchronize-panes only spans the panes of one window and changes what every attached client sees — this crosses sessions and belongs to your browser alone. Chained windows are outlined and counted in the toolbar, because a broadcast you have forgotten about is the one genuinely dangerous thing in here.

  • Hand the work to the other agent.

    A saved prompt, its placeholders already filled in, typed into the other agent's session

    Two agents on one machine share a filesystem, so what travels between them is not the work but a baton: a short sentence and a pointer. When one finishes — the bell knows — a button offers to type the sentence into the other's prompt, without an Enter. Two patterns, and they differ in behaviour rather than only in wording.

    It comes with Referee (one makes, the other reviews without editing and ends on a verdict; the return leg is a fix) and Relay (both improve the same work in turn), and you can save your own — a library that follows you between desks, kept in groups you name: Paper review, Web development, whatever you are doing, because a flat list of fifteen sentences is a list nobody reads. The sheet shows the groups first, then only the messages in the one you picked, and what will actually be typed, filled in, before anything is sent.

    Writing them has a screen of its own — Prompts and Values, two entries in the bottom bar because they are two different things — with full editing of the library: rename, duplicate, delete, and each one previewed with this desk's values as you type. The hand-over sheet is for sending; a place you pass through in a hurry is the wrong place to keep a library.

    Templates are written with placeholders. Three are filled in from the situation: {folder} — the sending session's own working directory, since a desk's folder says nothing about where tmux put the agent — plus {from} and {to}. They are defaults, not reserved words: define one in a set and yours wins, with the row saying what it is covering. The rest are yours, kept in named sets. A prompt can also name one outright — {genpat_paper.paper_text} reaches into that set whatever set the desk is on, which is what you want when one prompt is about one project and the desk is doing something else. On by default; the switch is in Settings. Default is the ground truth; any other set says only what it changes and takes the rest from it, showing what it is covering — instead of BMC Genomics — and what it is inheriting, one tap to claim. A desk picks a set, from its own tab menu, so the same set serves every desk about the same thing instead of each desk keeping a copy of your name. A placeholder with nothing to put in it is flagged and passed through as written, where you can see it.

    Or keep them open as a window. A modal you open, aim and dismiss thirty times in an afternoon is thirty times too many, so Prompts is also a window on the desk: the folders, the messages, and you drag one onto a terminal — the same gesture as a path out of the link tray. Or just tap it: the window says at the top where a tap goes, and that follows the terminal you last touched, so the usual case is one tap and no question asked. Tap a name up there to aim it somewhere else and it stays put.

    Aim it at more than one session. The names at the top are toggles, so the same instruction reaches two agents, or three, in one press — the ordinary case with two agents on one job, and something you notice you are doing by hand on the fourth round.

    Star the ones you use. A library grows into folders, and folders are where a thing is kept rather than where you reach for the four prompts you send forty times an afternoon — which are, always, in three different groups. A star on the Prompts screen puts a prompt in one line at the top of every desk's Prompts window: flat, without its group, open unless you shut it. It stays in its folder too; starring is a shortcut, not a move.

    It opens shut and says how much is in it. Groups, prompts and their placeholder lists all start collapsed, with a count beside each name, because a window three hundred pixels wide holding forty open rows is a window you scroll rather than read. What you open stays open. Values can be edited where you find them: a prompt lists what it takes and what each one is worth on this desk, and each is a box you can type into. Ordering, though, happens 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.

    Hovering a prompt shows what it will actually say, filled in, without sending it — and on the Prompts screen you can preview with any set of placeholders, not only the one the desk is on, since reading is not choosing.

    A prompt says whether it runs. "Run the tests" wants to go; "here is the file, now tell me what you think" wants a look before Enter. The switch is on the prompt itself, off by default, and a prompt that will run says so with a ↵ in the list — before you tap, not after. It is a mark and not a button: it says what pressing the row will do.

    The return goes as a separate write, half a second after the text, and that detail is the whole of whether it works. An input box does not read lines, it reads writes: one that assembles a paste treats everything arriving in a single 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. The pause is a setting for an agent that wants longer.

    The ⋯ on a row is for the times a word needs changing before it goes: it shows the text filled in, lets you edit it once and sends it. Not saved — the library is edited where the library lives, where deleting one needs no confirmation: the row becomes a red line with Undo in it for five seconds, which is a better bargain than a dialog every time. Sending one is a deliberate act and stays one; the round trip can run on its own, but only for the reviewing pattern, only once you switch it on, and only for as many rounds as you give it — see Two agents on one job.

  • Placeholders as you type. {{genpat_paper.paper_tex}} typed at a prompt in the session becomes the path, not only in a saved prompt — and it works inside an agent's own input box, which is the whole reason for having it. Typed, pasted, or half of each: one buffer holds what has gone to the session, so a placeholder does not have to arrive all one way.

    In a terminal the text is yours and {...} already belongs to the shell, so the typed form takes two braces by default, and { } or @{ } if you prefer — the choice is in Settings. With the default, {"a": 1} and mv x{,.bak} can never be touched even by accident. A saved prompt accepts any of the three, since there the whole text is a template.

  • Clickable paths. Hovering a line asks the server which of its words are real files; those get underlined, and opening one shows it in the viewer and points the file browser at it. Relative paths resolve against the pane's own working directory. On a phone, a long press does the same.

Files

  • Browse, search, rename, move, copy, delete, upload (drag and drop), make folders. All of it off by default: allow_write turns it on.
  • Paste a screenshot straight into a folder. Ctrl+V in a listing writes the clipboard image where you are looking, named screenshot-1.png, screenshot-2.png — the number is the server's, so nothing is ever overwritten and two devices cannot collide.
  • Drop a file on a terminal and it lands in drop_dir with its absolute path on your clipboard. A terminal is not a folder, so the server picks the one place drops go and makes it the first time it is needed.
  • Text into a file. A window you paste a wall of text into, and get a path to it — in the drop folder from the desk's row, or in the folder you are looking at from the browser's. It stays open beside the session the text is for, and an unsaved draft survives a reload.
  • Video and audio play in the window, streamed with range requests so the scrubber works from the first second, and they keep your place across a reload.
  • Type a path where the path is shown. Click the address and write in it, ~ included, with folders offered as you type. A path that is not there says so in the box; the listing under it stays put.
  • List or tree, hidden files on or off, favourites — kept on the server, so both devices see the same ones.
  • Two panes side by side; either one can be a window in a workspace. Drag a row onto a folder or onto the other pane to move it there — the folder under the pointer wins over the pane behind it, in either pane, including the one you are dragging from.
  • The three you do all day are on the row: copy the path, rename, delete. Everything else is behind the ⋮, which is right for moving, copying and downloading — you do those thinking about it. Copying says that it copied, with a tick for a moment, because a button that copies and then looks identical is a button you press again to be sure. Pointer only: on a phone, three targets in a row is a lottery and the ⋮ is one honest target with all of it behind it.
  • What a folder weighs, on request: a button per folder, because finding out means walking it. It stops at 400k entries or twenty seconds and says "at least" rather than presenting a partial sum as the total.
  • Text files are editable, with a save that refuses if the file changed underneath — the normal case when a job is writing to it.
  • A link is a way to upload: give an address and the machine fetches it into the folder you are looking at, headers and all — a pasted curl command is read for its address, cookies and credentials — so a file never has to come down to a phone to go back up again.

Starting work

  • Start a shell or an agent from the app, in a folder, with its first instruction already typed in — from the prompt library, filled in for this desk. The same button that used to ask only for a name. On a phone this is the difference between watching an agent and beginning one, since beginning one used to need a shell.
  • What it may start is a list in your configuration: a name and a shell line each, run through your login shell so conda activate x && claude works, and checked for existence the same way — a which on a systemd-started server misses everything your profile puts on the PATH. The API can start a launcher by name from that list and nothing else, which is one of the few places here where a feature narrows what a token can do.
  • It waits for the thing to stop drawing before typing, because an agent is not ready when its process starts — it has a banner to print and often a question to ask. Stillness rather than a marker per agent, which would rot at their next release; and the return is not pressed unless you asked, nor ever into something still moving. The answer says which of those happened.
  • A git worktree per job, when the folder is a repository: a second checkout on its own branch, beside the first, so two agents are not editing one tree. New branch or existing one, inside the configured roots, and removal refused while there is uncommitted work in it.

The list, and the workspace

  • A short list of things to do, on the machine rather than in the browser: a date, a line of text, one of three states, and a filter. The state is a button that cycles and the words are a button that edits; the cap drops the oldest finished one and never an open one.
  • The workspace lives on the machine too. The desks, where every window sits, the prompt library, the placeholder sets, the shortcuts — sixty keys that used to be in one browser's storage. A desk made at the desk is on the phone, and a script or an agent can read the library it sends from. The browser pushes only the keys it changed, so two devices editing different things both keep their change.

Documents

  • Code is coloured — 27 languages, chosen from the file's name, with the highlighter vendored and loaded the first time you open one rather than on every visit. Above 260KB it stays plain, because highlighting a file that size takes longer than reading it.

  • Which viewer an extension gets is a setting: markdown rendered, code coloured, the rest plain is only the default, and a .log that is really JSON can say so.

  • Markdown rendered, with a source toggle, and the figures beside it on disk shown where they belong — ![](results/plot.png) resolves against the document's own folder.

  • ```mermaid fences are drawn, in this app's palette rather than mermaid's own themes, and they follow a theme switch. Loaded only when a document actually has one: it is the largest thing vendored here and most documents are not diagrams. One that will not parse keeps its source, with a line saying what the parser objected to.

  • PDFs are drawn by Argus, with pdf.js, and open where you left them — the page, the scroll position to the pixel, the zoom you chose, across reloads and across days. The browser's own viewer cannot do this and never could: measured, it says nothing at all while you scroll, so there is nothing to record. It also drops view=FitH in silence, which is why page width never survived a reload. Now it is computed here instead of asked for. The text stays selectable, and only the pages on screen are drawn.

  • A search box for a PDF that Argus answers itself: the browser's Ctrl+F searches the page around a document, and a phone has no Ctrl+F at all, so the text is extracted here and each hit scrolls to its page.

  • Word/ODT/RTF rendered through pandoc when the machine has it, with figures inlined — and the plain-text extraction as a fallback when it does not.

  • Logs: a file too big to send whole arrives as its tail, which is the part anyone wants, and says so.

  • Every previewed document is sandboxed into an opaque origin, so a stray HTML report cannot read the access token.

Workspaces and windows

  • A new desk asks what goes in it: the session list opens straight away and takes as many as you tick, rather than one at a time with the sheet closing after each. The same sheet adds sessions to a desk you already have.
  • Desks (tabs) you can name, colour, reorder by dragging, and pin. Each holds its own set of windows, has its own address (#/wall?ws=3, copyable from the tab menu) and its own starting folder, and survives a reload. Every tab carries a for its menu — holding it and right-clicking still work, but neither is a gesture anybody finds.
  • A desk's folder can be written with a placeholder{folder}, {paper} — filled from the set that desk is on, so a desk pointed at a project does not repeat what its set already says. The sheet shows what it resolves to as you type, and a placeholder with nothing to fill it sends browsers home rather than to a folder with a brace in its name.
  • A folder each desk starts in. A desk is usually about something, so its browsers should land there rather than in the same home directory as everything else. Pick one of the shortcuts or type the path: it completes folder names as you go, Tab finishes the word the way a shell does, and a folder that is not there is refused rather than quietly stored.
  • Windows for terminals, file browsers, documents and proxied web pages. Move or duplicate them between desks.
  • Magnetic layout: windows snap to each other and to the wall's edges, with a preview of where the drop will land — including into the gap between two windows, which it fills exactly.
  • Shared edges behave as splitters: widen one column and the next gives up precisely what the first one took — and a column gives way as a column, every row of it, not only the one whose edge you happen to be touching.
  • Tile as a grid, columns or rows when you want to start over. The desk toolbar is ruled into two: flat on the left, what is on the desk; after a divider, gathered into controls, how it is arranged.
  • Every desk tab carries how many windows it holds, including desks you have not opened this visit.
  • Keep the arrangement you made. Grid, Columns and Rows are arrangements the machine picks and every one of them throws yours away. Custom is the fourth, and it behaves like the other three: press it and you are in it, lit while you are. It restores the desk rather than only the geometry, so a window closed since comes back and a window opened since goes away, with six seconds to undo it. Saving is a smaller button held inside the same control — saving is not a fourth way to arrange a desk, it is something you do to this one — and it goes out when what is on screen is already what is saved. A dot on the corner says there is something saved here at all, which is the question you ask from across the room.
  • A narrow window folds its own title bar: below about 340px the buttons move into a ⋯, which reads them off the bar itself, so nothing runs past the edge on a phone. It is asked of the window, not the screen — a 300px window on a wide monitor has the same problem, and a media query cannot tell the two apart.
  • The window list says where each session actually is — its tmux working directory, which is otherwise written down nowhere and is what a hand-over sentence points at.
  • Drag a line out of the tray onto a window: onto a terminal it is typed into that session — with shell quoting, and without an Enter, because what to do with it is the point of handing it over; onto a file browser that folder is shown. Holding the line starts the drag on a phone too, so the list still scrolls. There is a copy button on each line for the times you want it on the clipboard instead.
  • Numbers where they are useful: the Links button carries how many are waiting and moves as things are printed whether or not the tray is open; the List button carries how many windows the desk holds; the Sessions tab carries how many tmux sessions exist, and turns amber while one of them is waiting for you. All of them are about the desk you are on, so switching tabs shows that desk's own numbers.
  • A filter in the tray, for when it holds thirty things and you want the one with report in it. It says how many of how many, and clearing it brings the rest back.
  • The tray can empty itself: 1, 3, 5, 10, 30 minutes or never, per desk. It drops what is older than the span rather than wiping the lot on a timer, so a link that just arrived is never snatched away.
  • A link tray per desk. What an agent produces is mostly references — where it wrote the report, what port it is serving on, which file failed — and by the time you have read the sentence it is four screens up. The tray catches every absolute path and URL that goes past in the desk's terminals and keeps them in a list you click. A path only earns a line if it is really there, so what collects is a short list of things that open, not everything that looked like a path. Empty it whenever it stops being useful.

Two agents on one job

The baton above hands work from one session to the next. This is the other shape: two agents working towards one goal over time, with roles — a button in the desk toolbar, because doing it by hand is four things in the right order and getting the last one wrong is what ruins the afternoon.

There is no protocol here and there is deliberately no attempt at one. Two agents on one machine share a filesystem, and what you can arbitrate with is a file both of them can read. So the button writes two files in the desk's folder — PLAN.argus.md, what they are trying to do, and BRIDGE.argus.md, what they say to each other — and sends each of them a prompt that points at both. Nothing is installed in the agent, and nothing is asked of it that it cannot already do: read a file, append to a file, wait a minute. Which is why it works the same with Claude Code, Codex, Gemini or a script that reads its own instructions.

Two patterns, and they differ in behaviour rather than wording.

Together, without stepping on each other Both work towards one goal. The plan lists every file with one owner, and neither may touch the other's. Need something that is not yours? Write it under ## Blocked and stop — that turns a collision into a line in a file instead of a lost afternoon. One prompt goes to both, through the chain, which is then unhooked.
One builds, the other reviews One writes and never marks its own work correct. The other reads the diff and never edits the work. They pass it back and forth through the bridge — the two cannot see each other's terminals, so a review printed into a pane is a review nobody reads.

The desk says a pair is on it. A note in the toolbar reads the plan file rather than a flag somebody set — a flag says what was started, and what you want to know is what is happening. It shows the pattern, who is who, and how long since either of them wrote to the plan; after twenty minutes of silence it turns amber, because at that point both terminals still look busy and the pair has stopped. Clicking it opens the plan.

Letting them run, if you ask for it. The switch when you start a pair adds the polling loop to both prompts and gives them two limits: how many passes they may take, and how long they have. Both go into the bridge, so they hold whether or not a browser is watching — and two agents who cannot agree will not start agreeing at three in the morning, which is what the deadline is for. It is off unless you switch it on when you start the pair, it is per desk, and it counts down — the note shows the rounds it has left rather than "on", and clicking it stops the loop there and then. Two agents bouncing a change between them for six hours unattended is not a feature.

Three things it does on purpose, each of which cost a round to learn:

  • A verdict in the first twenty seconds after a hand-back is ignored. The prompt quotes both verdict lines and a terminal echoes what is typed into it, so without that pause the loop reads its own instructions and answers them.
  • Attaching replays the scrollback, and the first sweep of a session hands nothing on. Otherwise reloading the page spends a round on a verdict from ten minutes ago.
  • Two verdicts word for word identical, one after the other, count as one. Argus attaches a real tmux client, which is a full-screen program, so a row number means a position on the screen rather than a place in the history — the sentence is the only thing that does not move. The cost is a missed round, and for something that spends money while nobody is watching, missing one is the right way to be wrong.

The bridge. One file, append-only, that both of them read on a timer — and both patterns use it, the reviewing pair and the two peers:

@TURN who=WORKER at=2026-08-18T09:14:02Z status=DONE
Added the cache and a test for the empty case. 48 tests pass.
@END at=2026-08-18T09:15:30Z

@TURN who=REVIEWER at=2026-08-18T09:21:40Z status=REDO round=2
src/pipeline.py:88 — the cache key ignores the scheme version, so a stale entry
survives an update. The test passes because it never updates the scheme.
@END at=2026-08-18T09:26:02Z

Timestamp, actor, status, text — all four inside the opening marker, named rather than positional, so a field left out is visible instead of shifting the others along. Extra ones are allowed and ignored (round=2, tests=16/0). The last turn says whose move it is, so no state lives anywhere else. DONE hands over, REDO hands back, OK ends it for both, ASK is a question the other one answers before doing anything else, BLOCKED stops and asks for a person, and ARGUS: STOP is the board saying the rounds or the minutes you allowed are used up. A turn counts as finished only when its @END is there: otherwise the other one is still typing, and acting on half a review is worse than waiting a minute.

Both may write at once and nothing takes a lock: appending is safe, because the kernel will not let two appends land on top of each other. Writing a turn in pieces is not, so the rule — and the command the file gives you — is one append per turn.

The rules are written into the top of the file itself, so an agent that reads it cold needs nothing else. Five placeholders carry the rest into the prompts: {plan}, {bridge}, {every} how often they read it, {limit} how long the whole run may take, and {tries} how many reads before giving up on something that is not coming.

Nothing in this needs a browser. Close the tab and they carry on; what Argus does while it is open is read the same file — the pair note shows the last status, it rings on OK and on BLOCKED, and it writes ARGUS: STOP when the rounds or the deadline run out.

The idea is not new and the neighbours are worth knowing: claude-codex-handoff does it in JSONL with two directional streams, llm-handoff with markdown state files, and the field set is FIPA-ACL's and A2A's. This is the small readable end of that family: one file, both directions, markdown, so you can read it on a phone.

The prompts are ordinary templates. One per pattern, plus a nudge for when one of them stops reading the file. They arrive in your library in two groups — Two agents · together and Two agents · one reviews — so you can open them, read exactly what your agents are being told, and change it. One rule if you rewrite them: never start a line with a runnable command, because Argus types a prompt into a terminal and a shell will run it. The set it ships with cannot be deleted; editing one clears its stock mark, which is also how an unedited copy gets brought up to date when the wording improves without ever overwriting words you wrote.

The machine

  • CPU, load, memory, swap, GPUs (temperature and memory), every disk, and uptime — each with a plain reading of whether it is fine, and a note on why swap under pressure is the number that matters.
  • Listening ports, with what is holding them. A service bound to 127.0.0.1 is unreachable from a phone by design; Argus will stand in front of it (--allow-proxy, then open that port by hand) and serve it under /proxy/<port>/.
  • Reach a port nobody found, by typing its number: a service that has not started yet, or one the scan did not see. It appears in the list either way, so you can close it again.
  • Rescue a login that went to the wrong machine. A tool running on the server starts a browser login whose callback is http://localhost:1455/…. You log in on your own laptop, where localhost is your laptop, and the callback lands on nothing. Paste that dead URL into the same box and Argus forwards it — path and query intact — to the port it was always meant for.
  • Argus's own credentials stop at the proxy: neither the token in the query nor the Authorization header is passed to the service behind it.
  • Why not just forward the port, like VS Code? Because VS Code has a piece running on your laptop that can open a socket there; a web page cannot, and no browser will ever let one. If you want localhost:1455 to work literally, the tool VS Code uses under the hood is already on your machine: ssh -L 1455:127.0.0.1:1455 you@server. The proxy is the answer when all you have is a browser — a phone, a borrowed laptop — and the price is that the address changes.

Being told when it is done

  • It has finished and it is waiting for you are different events, and a notification that cannot tell them apart is noise by the end of the day. So nothing is guessed from the output: an agent hook posts to /api/bell and says which of the two it is. Claude Code's Stop and Notification hooks and codex's notify both do this in one line of configuration. Settings has a button that does the wiring for youLet your agents ring. It writes the little script and adds the hooks to each agent's own configuration file, additively: an event you have already claimed is reported and left exactly as it was, a copy of each file as it was before Argus first touched it is kept beside it, and the same button takes it all back out. Agents read their configuration at startup, so it counts from the next one you open. tools/argus-bell in this repository is that one line: it reads the token from the config so no copy of it ends up in a hook, works out the tmux session by itself, and unwraps the JSON codex hands its notify program.
  • For everything that is not an agent, the escape sequence every modern terminal implements: OSC 9. One printf at the end of a build, no configuration. Note that tmux swallows it unless it is wrapped in tmux's passthrough with set -g allow-passthrough on — measured, and the wiki has the shell function that gets it right in both cases.
  • The window that rang is outlined, green for finished and amber for waiting; the tab of the desk holding it is marked; a message takes you there; two short tones you can turn off. Looking at the window is what stops it.
  • A bell per session. Every terminal window carries one: lit means that session rings, struck through means it keeps quiet. Ringing for everything is the default, because a bell you have to switch on for each session is a bell that is silent the day you needed it — and silencing is a property of the session, so it holds wherever that session is shown and across reloads.
  • It works in another tab, over plain http. Bells arrive on an open stream rather than by polling, because a background tab has its timers throttled to about once a minute — which is exactly the case that matters. The tab title changes to ● session, a coloured dot is burnt onto the favicon (the part that survives a crowded tab strip, where the title is not shown at all), and the sound plays. None of the three needs a permission or a certificate.
  • A notification from the browser itself does need a secure context, and Settings says so plainly instead of failing quietly. Three ways round it, in order of effort: declare the origin trusted in your own browser (chrome://flags/#unsafely-treat-insecure-origin-as-secure takes a full origin with its port; Firefox's dom.securecontext.allowlist takes bare hostnames and needs a second preference to stop it breaking images; Safari has no equivalent at all); put a real certificate in front (tailscale serve, or mkcert offline); or point the same hook at ntfy or Gotify as well — Argus does not try to be a push service. The wiki has the steps for each.

Everything else

  • Settings is nine groups, and you can get to one of them. The headings are chips at the top, built from the finished page rather than a list kept beside it, so a group added next month appears there without anybody remembering; and a box hides every row that does not match what you type, along with any heading left holding nothing.
  • Ready-made looks for tmux, from a button on the terminal itself — Argus, Paper, Amber, Slate, or Plain to undo them. One session or all of them: style options are session options, so a look can dress the window you are looking at and leave everybody else's alone, without writing anything to disk. Choosing "every session" writes it into the config instead, where it outlives a restart. Either way they set colours only — status line, borders, messages — never keys or behaviour, from a fixed list of options checked on the server, and the config route goes through the same throwaway-server check as anything else. A look cannot take a session down. The browser's own terminal is dressed to match, per session when that is what you chose.
  • Editing the tmux config and handing it to every session at once. Sourcing a config runs it, so the file is tried on a throwaway tmux server first and only applied if it survives — a bad line ends the server it is sourced into, and that server holds all your work.
  • Four languages (en, it, fr, es), and adding a fifth is one JSON file — no build, no extraction step, nothing to register.
  • A phone-shaped interface: bottom navigation, thumb-sized targets, drag-to-scroll in the terminal, a full-screen button (F11 is not on a phone), light and dark themes. Windows resize with a finger — the handles straddle the frame and are 34px at the corners on a touch screen, rather than the 8px strip outside it that a mouse is happy with and a thumb cannot find.
  • Keyboard shortcuts for the places you go and the windows you open, with ? for the list — and a key icon in the header, on screens wide enough to have a keyboard. They fire only when you are not typing: a terminal, or any box you are writing in, keeps the keyboard to itself, because stealing one key from tmux would be worse than having no shortcuts at all. Two modifiers, and the modifier says where you are going: Ctrl+Alt reaches the screens down the side, Ctrl+Shift works on the desk you are on, and Ctrl+Shift+1…9 (or Alt+1…9) goes to a desk by its number. Bare letters were not enough — plenty of the app is neither a terminal nor a text box — and a single Ctrl is worse, because a browser owns nearly every Ctrl+letter at a level a page cannot argue with. Changing one is click the row, then hold the combination: it records the physical key you pressed, so there is nothing to spell and Ctrl+Shift+E does not come back as ctrl+E. The list is grouped the way the scheme is, with a box to search it.
  • A QR code to pair a phone, and an installable PWA over HTTPS.
  • The GitHub mark in the header opens the repository, this wiki and the landing page, so the documentation is one tap from wherever you are rather than something you have to go and look for.
  • No build step. The frontend is plain ES modules; xterm.js, marked and the QR library are vendored. pip install -r requirements.txt and run it.

Knowing there is a newer one

Once a day Argus asks github.com whether a later tag exists, and says so once if there is. It sends nothing — not an identifier, not your configuration, not even which version is running — and it never updates anything: this is somebody's shell, and a program that can rewrite itself on a schedule can be made to rewrite itself into something else.

check_releases: false     # and it never asks

With no route out to the network it stays quiet rather than complaining.

Watching several machines

A machine can hand out a token that opens exactly one door, GET /api/overview: hostname, uptime, load, memory, the disk in the most trouble, and which tmux sessions are ringing. No shell, no files, no writes.

watchers:
  - name: panoptes
    token: <at least 16 characters, and not your real one>

That is what a board across several machines should hold: losing it loses a list of session names, not every box on it.

The board itself is Panoptes (in one page) — one page showing every machine, which sessions are on it and which one is waiting for you, with a click through to that machine's own Argus. It is a separate project on purpose: Argus never depends on it, and if it goes away every Argus carries on alone.

A machine the board cannot reach can announce itself instead, which is the case for two boxes on one wire where only one direction is open:

report_to:
  url: http://board.internal:8070
  token: <the board's registration token>
  name: gpu2                        # what you want it called on the board
  reach: http://gpu2.internal:8090  # where a browser should go

It sends what /api/overview returns and nothing else — no file, no path, no token, no command — and it is off unless you configure it.

Letting a board start and stop things

Off unless you write it down, and no command ever arrives in a request. This machine publishes a list of names; a board may ask for one of them and nothing else:

runnable:
  - name: nightly
    run: python3 nightly.py
    cwd: /srv/work

watchers:
  - name: panoptes
    token: <at least 16 characters>
    may_run: true              # otherwise the token stays read-only
    may_stop_argus: false      # the one thing a board cannot undo — its own permission

may_stop_argus stands on its own: a machine with nothing worth publishing as runnable can still be one you want to be able to switch off.

start creates that session, stop kills it by exact name, and only a name on the list can be killed — a board cannot touch the work you did not list. Asking twice is not an error.

may_stop_argus allows one more thing: stopping this server. Every tmux session carries on untouched, because Argus is a client and not their parent — but nothing on the board can start it again, and the reply says so. That takes a shell here.

For a machine a board cannot reach, the same two actions arrive in the reply to its own announcement, which is the only channel there is:

obey_board: true               # off by default: announcing is not agreeing to take orders
board_may_stop_argus: false

Either one is enough to make obey_board mean something — a box you want to be able to switch off from the board is not thereby a box with work to publish.

Even then the answer is bounded by runnable: a reply can name one of those and nothing else. There is no path by which a command reaches this machine.

Clone this wiki locally