Starting an agent - andreaderuvo/argus GitHub Wiki

Starting an agent

Argus used to attach to work you had already begun from a shell, which made a desk a window onto a job rather than the place a job starts. On a phone that gap was the whole story: you could watch an agent and answer it, but you could not begin one, because beginning one meant a shell and a phone has not got one.

The same one button as before โ€” New session on the desk toolbar, the line at the top of the Sessions sheet, and the first row of the Sessions screen โ€” opens Start something here (button: Start), which asks three more things:

what to start a shell, or one of the agents this machine has
the first instruction typed into it once it has finished starting. From the prompt library, filled in for this desk, or written there and then
a worktree when the folder is a git repository: a second checkout on its own branch, so two agents are not editing one tree

Nothing new to find and no new icon: it is the box that used to ask for a name.

From the Sessions screen it opens on a plain shell, already chosen, in your home folder, and Start lands you in the terminal it made. That row is there however many sessions exist โ€” including none, which is exactly the state right after a tmux server has died, when a shell is the one thing you came for. (It used to be only a + in the header, and only once a session existed.) The agents are one tap away in the same box.

What it can start, and why that is a list

A launcher is a name and a shell line, and it lives in your configuration:

launchers:
  - name: Claude Code
    command: claude
  - name: Codex
    command: codex
  - name: Reviewer
    command: conda activate review && claude --model opus
  - name: A shell
    command: ""

With nothing configured you get Claude Code, Codex, Gemini and a plain shell, which is a starting point and not a claim about your machine. Your list replaces that one entirely.

Two things follow from a launcher being a shell line rather than a program:

  • It runs through your login shell, so whatever your shell knows, it knows. "It is on my PATH" usually means "my profile puts it there", and conda activate x && claude is a perfectly good entry.
  • The API can only start a launcher by name from this list. That is what keeps the endpoint from being "run anything" โ€” and worth saying plainly, because the terminal next to it has always been exactly that. This is one of the few places in Argus where a feature narrows what a token can do.

Each row says whether the command can actually be found, asked the way it will be used โ€” through the login shell, not by walking this process's PATH. That distinction is not pedantry: a server started by systemd has a minimal PATH, and Argus greyed out claude on the machine it was written on while a login shell found it instantly.

The choices are there the moment the box opens. Asking a login shell costs the better part of a second (0.9s measured, nearly all of it the shell starting: claude --version itself takes 10ms), and each version is another run. So the answers are kept, and once a minute has passed the box is shown the last answer at once while the question is asked again behind it โ€” one refresh however many boxes open meanwhile. The server asks everything once as it starts, so the first box after a restart is not the slow one either: 2ms for the list, 1ms for the versions, measured three seconds after start. The one cost: a tool installed a moment ago shows up on the second opening rather than the first. Picking a launcher keeps every row's version where it was.

Its options, in words

Nobody remembers --dangerously-skip-permissions. Pick Claude Code, Codex or Gemini and the box offers what matters about starting it, each with a sentence:

Claude Code Codex Gemini
What it may do without asking ask ยท edit files freely ยท plan only ยท decide for itself ยท everything (--dangerously-skip-permissions) ask ยท write in this folder ยท approvals reviewed automatically ยท work without asking, inside the sandbox (--full-auto) ยท everything (--dangerously-bypass-approvals-and-sandbox) ask ยท everything (--yolo)
Model its default, Opus, Sonnet, Haiku, Fable a name you type a name you type
How hard it thinks low โ€ฆ max (--effort)
Also continue the last conversation in this folder search the web

Under them is the command line they make, and the "everything" choices say in plain words what they mean before you press Start. What you chose is remembered per launcher, on the server like your other preferences, so the next agent starts the same way.

Only what the installed version takes. Flags come and go between versions (this machine's Codex has no --full-auto any more), so each option is checked against the agent's own --help, asked once in the background and remembered until its version changes. An option the help does not mention is not offered, rather than offered and refused.

No command line travels. The page sends the options by name; the server builds the flags from its own table and refuses anything off it, so the rule this page started with still holds: only a launcher named in the config is ever run.

An agent cannot pick "everything". When an agent starts another agent with its own key, the choices that ask nobody (--dangerously-skip-permissions and its kind) are refused with a 403. That choice is yours, from the New session box.

The first instruction, and the one hard problem

An agent is not ready when its process starts. It prints a banner, works out what is in the folder, and very often asks something first โ€” do you trust the files here? Text delivered into the middle of that lands in the wrong place: at best ignored, at worst answering a question you never read.

There are three ways to know when it has finished starting, and only one of them survives their next release:

  • A marker per agent โ€” "Claude is ready when line X appears" โ€” is precise, and rots. This app has been burnt by exactly that kind of table before, in the agent detection.
  • A fixed sleep is a guess: too short on a cold cache, too long always.
  • Stillness is neither clever nor precise, and it is true of every program there is: when a thing has finished starting, it stops writing.

So Argus watches the pane's cursor and history length โ€” not its text; reading a pane's text has taken a whole tmux server down on one machine this was tested on โ€” and calls it ready when nothing has moved for a second and a half. A first attempt used 0.8s and ended the wait during a launcher's thinking pause, which is precisely the moment this exists to avoid.

And then it still does not press Enter unless you asked. press Enter for me is off, and when the wait runs out with the screen still moving the prompt is left typed in and unsent whatever the switch says. The reply tells you which happened, in those words, because "typed in, not sent" is the case you need to know about โ€” the alternative is waiting on an agent that was never going to answer.

The request is held while it waits, twelve seconds by default. A script that does not care can ask for another wait with wait_seconds, from 1 to 60 seconds.

Limits

  • Twelve starts a minute. More than that answers 429 and starts nothing. A machine fills up quietly. If you meant a fan-out, raise launches_a_minute in the config. New worktrees have their own count with the same limit, raised by the same key.
  • A name already taken answers 409. A session is never started on top of another. A name with : or . is refused (400) โ€” tmux uses both as separators โ€” and so is a launcher not in the list. A tmux that fails to start it answers 502.
  • The folder must be inside the configured roots, like everything else.

Into a desk by name

A launch can say which desk it is for: "desk": "pippo" (or "desk": true, the desk on screen). That desk is found by name (any case), or made if it is not there yet, and every open page is told to put the session in it. This is how an agent does "open a desk called pippo with a Codex and a Claude in it", through the MCP tools open_desk and start_agent (see An agent driving Argus).

POST /api/desks with {name, folder?, show?, session?} finds or makes a desk without starting anything โ€” session puts a window on an existing session in it. folder is where its browsers and new sessions start, and show (on by default) switches the open pages to it. {rename: "Desk 11", name: "Pippo"} renames one. It only adds and renames: it never removes or rearranges a desk you have.

A worktree, when the folder is a repository

Two agents in one checkout tread on each other. Git's own answer is a second working directory on its own branch, sharing the object store โ€” no copy of the repository, no pair of clones to keep in step. Tick in a new git worktree, give it a branch, and Argus makes it and starts the agent in it.

  • The branch may be new or existing: -b when it does not exist, a plain checkout when it does, because "it already exists" is not a reason to refuse what you asked for.
  • It goes beside the repository โ€” project-feature-x next to project โ€” rather than inside it. A checkout nested in its own parent gets walked by that parent's tooling and counted twice by everything that looks for files.
  • It has to land inside the configured roots, or Argus would make a checkout it cannot then browse, edit, or open a session in.
  • Making and removing one need --allow-write, and on removal git refuses while there is uncommitted work in it. That refusal is the only thing standing between an afternoon and a button, so it is not overridden quietly: forcing is a separate, deliberate answer (DELETE /api/git/worktree?path=โ€ฆ&force=true). The repository itself is never removed (400).

Argus is not becoming a git client. It lists worktrees, makes one and removes one โ€” the three things needed to start work where it cannot collide. Committing, merging and resolving are what the agent in the terminal is for.

The routes

GET /api/launchers the list, with available per row โ€” null when the command is a shell line and the answer is not knowable. ?versions=1 adds each one's version, which agent it is, and the options it takes
POST /api/tmux/launch {launcher, name, path, options, prompt, run, wait, wait_seconds, desk} โ†’ {name, path, launcher, command, seeded, ready, sent}. options are the box's choices by name; command is the line that was actually run
POST /api/desks find or make a desk by name, rename one, or put a session on it
GET /api/git/worktrees, POST/DELETE /api/git/worktree list for a path, add on a branch, remove one. The last two want --allow-write

See API reference for every route.