Every agent at a glance.
Argus is a terminal-native orchestrator for LLM coding agents. Run a swarm of agents — Claude Code, Codex, or any LLM CLI, cloud or local — side by side, each in its own git worktree, all under a single keyboard-driven UI — and reach the same swarm from your phone, from another laptop, from another agent, or from your own notes.
Coding agents are cheap to start and expensive to babysit. Five claude tabs become five forgotten branches. A codex you fire off at lunch is a black box until you cmd-tab back. Argus replaces that pile of terminals with a persistent orchestrator that knows what every agent is doing, where its worktree lives, when it goes idle, and who needs your attention next.
- One keystroke spins up an isolated worktree, a fresh branch, and a fresh agent, all wired into a live dashboard.
- Native multi-agent coordination. A dedicated Hera tab turns one agent into a team: a coordinator delegates to workers it spawns, an idle-gated message bus passes work between them, and a live plan DAG shows the whole team's order of work at a glance — all first-class in the same UI, no separate tool.
- A persistent daemon keeps PTYs alive across TUI restarts and laptop reboots — and a separate session-supervisor keeps them alive across daemon restarts too, so you can upgrade Argus mid-flight without interrupting a single agent. Your sessions outlive your terminal.
- An idle detector quietly promotes any agent waiting for input to "in review" — so a glance at the list tells you who needs you.
- A built-in HTTP API + PWA mirrors every keystroke from your phone, so the dashboard travels with you.
- A built-in MCP server lets agents talk to Argus directly — search your notes, spawn other agents, or hand off work between models.
- Harness- and model-agnostic by design. Argus orchestrates the workflow, not a single tool. Every backend is just a templated command, so the same worktree → branch → review → notify loop is identical whether the agent underneath is Claude Code, Codex, opencode, Antigravity, Cursor, Oh My Pi, or a local model via ollama — pick the harness and model per task, keep one standardized workflow across all of them.
Argus is really a local daemon plus the clients that drive it. The daemon owns everything durable — your tasks and projects in SQLite, the agent PTYs in its session-supervisor, hera coordination in-process — and serves it all over one authenticated REST + SSE API on port 7743. Every client is a thin lens over that same API, so they stay in lockstep: start a task in the terminal, watch it go idle on your phone, review the diff on your Mac. Reach for whichever one fits where you are.
The original surface, and the one you'll live in: a full-screen tcell/tview dashboard (the two screenshots up top) with real PTY emulation, inline diffs, and a chord for every verb. Reach for it when you're already in a terminal — over SSH, inside tmux, on the box where the repos live. argus --remote gives you the identical TUI against a daemon on another machine.
Argus ships a real, installable Progressive Web App. Tap Add to Home Screen in Safari and you have a phone-shaped operations console for your agents — running locally on your machine, reachable over your Tailscale mesh, never exposed to the public internet. Reach for it from any device you didn't bring the terminal to — your phone on the couch, a borrowed laptop — and let Web Push tap you on the shoulder when an agent needs you.
- Real terminals in the browser — xterm.js fed by an SSE byte stream, with PTY auto-resize on rotation. Not a polling log viewer.
- A native compose bar that catches everything iOS sends — dictation, third-party keyboards, Wispr Flow — and forwards it cleanly into the agent's stdin. Slash-key autocomplete pulls from your
~/.claude/skills/, per-project skills, and installed plugins. - A virtual key bar with the keys iOS won't give you: Esc, Tab, Shift+Tab (cycle Claude Code modes), arrows. Tap them between dictations without losing the soft keyboard.
- Web Push notifications when an agent goes idle. Throttled, VAPID-signed, per-device subscriptions, no third-party push services.
- Share-sheet target — Argus shows up natively in the Android share sheet. iOS gets a one-paste Shortcut that does the same. Either way, sharing a URL or a chunk of text into Argus lands you on the New Task tab with the prompt pre-filled.
- GitHub-style stacked diff view — every changed file in the worktree as a collapsible panel, expand-all, wrap toggle, optimistic for thumbs.
- Per-device API tokens — your iPhone, your iPad, and your laptop each get their own labeled token. Revoke any of them from the dashboard. Master token mints; SHA-256 hashes are all that's stored.
- Offline-aware — when the daemon is unreachable (laptop closed, Tailscale off) the PWA flips to a branded offline screen and reconnects automatically.
- Pure-local — runs on
localhostand your Tailscale IP only, never0.0.0.0. Hotel/cafe LANs cannot reach the API even with the token.
A native SwiftUI app (make mac-app) that turns the same daemon into a Mac-first control room — no browser tab, no keyboard chords to memorize. A source-list sidebar of tasks grouped into per-project folders (mirroring the TUI task list, with a collapsed Archived section at the bottom), a live agent terminal powered by SwiftTerm, and detail tabs for the session, the diff, the file tree, and task info. Reach for it when you're at your Mac and want the OS working for you: native notifications when an agent needs input or goes idle, a Dock badge counting how many are waiting, and a menu-bar extra for a glance without switching windows.
- Full task lifecycle — create (a project / backend / model / prompt sheet), fork, stop, restart, resume, rename, archive, and delete, each with native confirmations.
- Live everything — the task list and every terminal update ride the daemon's SSE event stream, not polling; a connection banner appears the moment the daemon is unreachable.
- Diff & files tabs — a unified diff with per-file collapsible sections and a browsable worktree file tree, parsed client-side by ArgusKit.
- Schedules & System windows — manage scheduled tasks (⇧⌘S) and watch host load / agent-session counts, the same data the TUI and PWA show.
- Hera roster — the orchestration tree, read-only (see the parity note in the Reference).
- Keychain-backed remote settings — point it at any daemon by overriding the server URL; the token lives in the macOS Keychain, never on disk. Defaults to
http://127.0.0.1:7743with the token from~/.argus/api-token.
Requires macOS 15+ and the Swift 6.3 Command Line Tools — no Xcode. Build and run details are in the macOS app reference below.
Argus exposes itself as a Model Context Protocol server, so any agent can drive Argus the same way you do.
- Spawn other agents. An orchestrator agent can call
task_createto fan work out across worktrees, then watch progress withtask_listandtask_get. - Hand off cleanly. When a session is done, the agent calls
task_complete(status flip) ortask_archive(out of sight) using its ownpwdto identify itself — no IDs to track. - Schedule itself.
schedule_createaccepts cron,@every 30m, or a one-shotrun_once_attimestamp. An agent can plant a tomorrow-morning follow-up before signing off. - Stage clipboard text with
argus_clipboard_set— solves the iOS Safari rule thatclipboard.writeTextrequires a synchronous user gesture. The agent stages, you tap Copy (PWA) or hitctrl+y(TUI). One tap, no escape-character mangling. - Rename, fork, stop, resume — every TUI verb has an MCP equivalent.
Argus passes its MCP server to each Claude Code and Codex task it launches, including resumed sessions. The connection is scoped to Argus tasks; ordinary Claude and Codex sessions keep their own MCP configuration.
Argus indexes your Obsidian vault as a SQLite FTS5 store and serves it over MCP. Every agent it spawns sees your notes — your design docs, your meeting captures, your durable preferences — as a first-class lookup, not a copy-paste afterthought.
kb_search— ranked full-text search across the entire vault, with snippets.kb_read— full markdown by vault-relative path. Wiki-link friendly.kb_list— directory listing with prefix filtering for path-aware browsing.kb_ingest— agents write their own learnings back. Your KB grows from sessions instead of decaying between them.- Live re-indexing — files dropped into the vault are searchable in seconds.
- Schema-aware — YAML frontmatter (title + tags) drives retrieval and clustering.
Pair this with the MCP task tools and an agent can read a meeting note, decide what to build, spawn its own worker tasks, and archive itself when done — all in a single conversation.
Disabled by default — see Knowledge Base setup to enable it, point it at a vault, and verify.
- Remote TUI —
argus --remote https://your-mac.tail-xxxx.ts.net --token "$ARGUS_TOKEN"launches the full TUI against a daemon running on another machine. Same keybindings, same panels, same agent stream — over Tailscale. No local SQLite, no daemon socket; every call rides the REST API the PWA already uses. - Multi-backend — Claude Code, Codex, or any LLM CLI as a templated command. Per-backend prompt flags, plan-mode defaults, and a default model, plus a per-task model override delivered through the backend's supported launch mechanism at start.
- Account switching — keep work and personal Claude Code and Codex subscriptions side by side and pick one when you start a task. Each account has its own login and history, sessions on different accounts run in parallel, you can pin company projects to the work account, and
>moves an agent stuck on a usage limit to another account mid-conversation. See Accounts. - Worktree isolation — every task gets
~/.argus/worktrees/<project>/<task>and anargus/<task>branch, all transactionally created and cleaned up. - Session resume —
--resumeon Claude Code,codex resume <id>on Codex,--session <id>on opencode,--conversation <id>on Antigravity,--resume <id>on Cursor and Oh My Pi. Your conversation survives a daemon restart. - Consistent scrollback across viewers — switch between the TUI and the PWA at very different widths and the agent re-emits the conversation at the new size. Idle-gated so it never fires mid-tool-call; the SPA reattaches transparently.
- Agent forking — duplicate a running task with full context (source info, recent output, git diff) injected into the new worktree.
- Smart auto-naming — the task’s resolved backend quietly turns a free-form prompt into a kebab-case task name (Claude Haiku or Codex’s low-cost
gpt-6-luna, independent of the task model). Unsupported or unavailable naming backends keep the initial regex slug; Argus never falls back to another backend. - Scheduled tasks — cron, descriptors, intervals, or one-shot runs. Each fire spawns a fresh task. Manage from TUI, PWA, or MCP.
- macOS sandbox-exec — per-session SBPL profiles.
~/.gnupg,~/.aws,~/.kube,~/.config/gcloudblocked by default. - Session-supervisor — agent PTYs live in a long-lived out-of-process supervisor, not the daemon, so bouncing the daemon (for an upgrade or a config change) re-attaches to still-running agents instead of killing them. On by default; one flag rolls back to the legacy in-process runner.
- Self-update —
git pull+go install+ daemon restart from a single Settings row. Active sessions reattach across the swap. - Auto-start at login — install the daemon as a launchd LaunchAgent so your agents survive reboots without launching the TUI.
- Full PTY emulation —
charmbracelet/x/vtpainting cells directly totcell. Colors, attributes, OSC 8 hyperlinks, infinite scrollback, bracket paste.
go install github.com/argus-foundry/argus/cmd/argus@latest
argusPure Go, no CGO. SQLite via modernc.org/sqlite. Built with tcell and tview.
argus daemon install # macOS — auto-start at login via launchdTo open the PWA, enable Remote API in Settings, then point your phone at http://<your-machine>:7743/ and paste the master token from ~/.argus/api-token. Tailscale recommended.
- Go 1.26+ — to
go installthe binary above. - Git — every project Argus drives must be a git repository.
- At least one agent CLI on your
PATH. Argus shells out to whatever backend you pick; it doesn't bundle a model. The default backend is Claude Code (claude), andcodex,pi,opencode, Antigravity (agy,brew install --cask antigravity-cli), Cursor (cursor-agent,brew install --cask cursor-cli), and Oh My Pi (omp,brew install can1357/tap/omp) come pre-configured too. Install the one you use and make sure it runs from a plain shell (claude --version). - Optional:
gh(GitHub CLI) powers the open-repo / open-PR keys and the PR-status indicator — features degrade quietly if it's absent. Tailscale is recommended for reaching the PWA from your phone.
argusThe first launch creates ~/.argus/data.sql, seeds the claude / codex / pi / opencode / antigravity / cursor / omp backends, and auto-starts the background daemon. You land on an empty task list — no projects are seeded, so add one before creating a task.
- Register a project. Press
3for the Settings tab, move to the Projects section, and either:- press
ito quick-add — point it at a directory (e.g.~/src) and Argus scans for git repos; select the ones to import; or - press
nto add one manually — give it a name and the absolute path to the repo root (base branch and backend are optional; they fall back to git's default andclaude).
- press
- Create your first task. Press
1for the Tasks tab, thenn. Pick the project, type a prompt, and hitEnter. Argus cuts a fresh worktree at~/.argus/worktrees/<project>/<task>on anargus/<task>branch, starts the agent, and drops you into the agent view with its live terminal. - Drive it.
Enterreopens an agent,sadvances status,ctrl+ztoggles the git/file side panes,ctrl+qsteps back out. The full keymap is in the Keybindings reference below. - Go mobile (optional). Enable Remote API in Settings and open the PWA as described under Install.
The sections below are the dense usage docs — keybindings, REST endpoints, configuration tables. Skim if you're getting started; bookmark if you're already running.
Run node web-workspace/dev-server.mjs and open http://127.0.0.1:7750. Use --port 7751 for another frontend port and --api http://127.0.0.1:7743 for the loopback daemon origin. This optional client uses the existing daemon through a local proxy. See Theia documentation.
The tables below are the defaults. Every key here is remappable via
[keybindings.<context>] in config.toml — see [keybindings.<context>]
below. The ? overlay always shows your active bindings.
| Key | Action |
|---|---|
n |
New task (with skill autocomplete in prompt field) |
Enter |
Open agent view |
ctrl+f |
Fork task (duplicate with context) |
s / S |
Advance / revert status |
a |
Toggle archive |
P |
Toggle pin (★ section pinned to the top of the task list) |
c |
Open copy menu (copy task name or prompt to clipboard) |
r |
Rename task (display name only; branch/worktree stay locked) |
H |
Toggle visibility of Hera-managed tasks (workers + coordinators; visible inline by default, each with a hera-role indicator — press H to hide) |
F |
Keep all Tasks projects open / return to only the focused project (both Tasks layouts; remembered locally across restarts; rebind with tasklist.expand_all) |
i |
View the task's message inbox (read-only): task messages + hera messages to any role it is/was bound to, read and unread, with delivery state. r reloads, Esc/q closes; never marks anything read. Local mode only |
v |
Browse the selected task's registered artifacts (r refresh, Enter preview/open, o open externally) |
> |
Move the selected Claude task to another account and resume its conversation there (picker shows each account's 5-hour / weekly usage; auto picks the lowest). Needs no tokens on the current account; a busy agent is interrupted after a warning |
ctrl+d |
Destroy task (kill agent + remove worktree + delete branch) |
ctrl+o |
Open the project's GitHub repo in browser (via gh repo view --web) |
ctrl+r |
Prune completed tasks |
j / k |
Navigate up/down |
Cmd+← / Cmd+→ |
Enter the resident tab at its rightmost pane / enter the Projects rail (Settings → Appearance → cross-tab arrow navigation; resident only when shown) |
1 / 2 / 3 |
Switch tabs (Tasks / Projects / Settings); 0 selects the Resident tab |
ctrl+l |
Refresh screen (wipe ghost cells; works in every non-agent tab) |
ctrl+j |
Open the jump switcher (tasks + projects): a two-column popup with the Tasks list on the left (Hera-managed tasks always hidden, archived excluded) and the Projects rail on the right. ↑/↓ move, ←/→ switch column, typing filters both columns at once, ctrl+g hops to the next (?) or coordinator with an unread board, Enter goes there (a Projects row lands on that exact rail row in the Projects tab), Esc clears the filter then closes. Opens on the first (?) or unread coordinator board, else on the task you're viewing. Global — works from every tab, the agent view (zoomed or not), and every Projects pane, never reaching a PTY. Rebind with [keybindings.global] switcher |
ctrl+k |
Open the command palette — type to filter the actions applicable right here (this tab/pane's own actions + the always-available globals), ↑/↓ to select, Enter runs it immediately. Works from every tab and pane, including inside the agent view and a live Projects-tab pane |
ctrl+g |
Jump directly to the next role needing input or coordinator with an unread board (when ui.coordinator_board is on; no popup, no typing) — Board targets focus the Board on its first unread item without expanding or marking it read; press again to cycle to the next one, wrapping around. The rail's fold/selection state is snapshotted the instant you're first interrupted; once every role and enabled board is clear, pressing ctrl+g again restores that snapshot instead of just saying there's nothing to do. Works from every tab and pane |
ctrl+b |
Restore the rail to its pre-interruption fold/selection snapshot, at any time — even with roles still needing input (unlike ctrl+g above, which only restores once they're all clear). No-op if nothing is snapshotted. Works from every tab and pane |
ctrl+\ |
Toggle the resident agent tab and return to exactly where you were (tab, agent view, zoom, Projects focus, Settings pane). Global — works from every tab and pane, never reaching a PTY; ignored while a modal or text input is open. Rebind with [keybindings.global] resident. See Resident hotkey recipe |
0 |
Switch to the Resident tab ([keybindings.global] tab_resident) |
ctrl+q (Resident tab) |
Return to the previous screen, like the resident toggle |
ctrl+alt+← / ctrl+alt+→ (Resident tab) |
Move between the team rail and the resident / worker panes (the tab opens on the resident conversation) |
j / k / Enter / Tab (Resident rail) |
Navigate the standing team; Enter revives a dead or suspended worker (the resident row only navigates) |
i (Resident rail) |
Read-only inbox of the selected role; the rail shows an unread ✉N per worker and the resident pane title shows the resident's own count |
r / a / s / S / ctrl+d (Resident rail) |
Rename / hide / role status ± / nuke — worker rows only, inert on the resident row; the resident is told about a nuke, hide or rename. Spawn, new coordinator, move, join, detach, pin, kanban and bounce keys are inert and absent from ? and ctrl+k on this tab |
q |
Quit |
A terminal UI cannot see Cmd, and macOS reserves Cmd+` for cycling windows. So the default toggle is the control byte 0x1c (ctrl+\), which reaches Argus in any terminal. To get a Cmd+` feel, map it to that byte:
- iTerm2: Settings → Profiles → Keys → Key Bindings →
+, press Cmd+`, action Send Hex Code, value0x1c. - Ghostty: add
keybind = super+grave_accent=text:\x1cto your config. - macOS: turn off System Settings → Keyboard → Keyboard Shortcuts → Keyboard → Move focus to next window (or rebind it), otherwise the system consumes Cmd+` before the terminal sees it.
Rebind the action itself with [keybindings.global] resident = "<keyspec>"; any modified spec works (ctrl+<letter>, ctrl+\, shift+up, ...). An unmodified key such as ~, backspace, or delete is rejected with a config warning and the default stays, because it would steal ordinary typing.
Settings → Appearance → Persistent task rail ([ui] tasks_rail = true, default off) keeps a 34-column project task list visible beside the agent. Browse existing sessions without starting them; Enter enters the selected task. Tab enters the selected terminal, terminal Tab/Shift+Tab stay agent input, Ctrl+Q returns focus to the rail, and Cmd+Left/Right traverse the rail and visible content panes. Navigation skips headers and expands the destination project; entering Archive opens it. Headers have no manual fold action. F keeps all projects open until pressed again. Agent zoom keeps the rail visible.
| Key | Action |
|---|---|
ctrl+q |
Back, 3-level (diff → files panel → task list) |
Esc |
Refocus terminal from diff/files; on the terminal, forwarded to the agent (does NOT exit the agent view) |
Cmd+← / Cmd+→ |
Switch panels (no-op when zoomed — side panels are hidden). With cross-tab arrow navigation on, Cmd+→ from the rightmost visible pane (files unzoomed, the terminal when zoomed) hops to the Projects rail; Cmd+← from the rail returns to this same agent view (same zoom, rightmost pane) |
Cmd+↑ / Cmd+↓ |
Navigate between tasks |
ctrl+k |
Open the command palette (see the Task List table above — same global action) |
ctrl+g |
Jump directly to the next role needing input or coordinator with an unread board — no popup, no typing (see the Task List table above — same global action) |
ctrl+b |
Restore the rail's pre-interruption fold/selection snapshot at any time (see the Task List table above — same global action) |
ctrl+z |
Toggle the git + file side panes (default layout set by Settings → Appearance → "Default agent view") |
ctrl+l |
Open link picker (fuzzy search all session URLs) |
ctrl+t |
Browse the current task's registered artifacts (intercepted before PTY input) |
ctrl+r |
Switch Claude session (searchable picker of this task's conversations; resumes the chosen one). Claude backends only |
ctrl+p |
Open PR for the worktree branch in browser (via gh pr view --web) |
ctrl+n |
Re-render at this width: kill + --resume the agent so its history reprints at the current pane width. A width change alone only resizes the PTY; when history is owed a re-render the pane's top border shows width changed · ctrl+n re-render (clicking it does the same). Busy / prompt-blocked / scrolled-up / unsent-draft agents defer; no drift is a no-op. Also in the Projects view when a terminal pane is focused, and in the command palette |
ctrl+y |
Copy agent-staged text; flashes "Nothing to copy" if no payload is pending (always intercepted — never sent to the PTY) |
Shift+↑ / Shift+↓ |
Scroll terminal (with acceleration) |
The Projects tab (2) has three regions: a left rail, a middle coordinator pane, and a right details region. The rail lists active orchestrators with their coordinator/worker roles, plus Pinned, Freelance, and a collapsed Archive section. A live worker or freelance role's row also carries a trailing context-pressure indicator – nothing under 40% of worker_context_window (default 1000000 – a worker's real context window, deliberately separate from the coordinator's much smaller recycle-nudge budget below), a • warming from pale yellow to hot orange through 40–90%, then a red ! past 90% (see Context-budget Stop hook); coordinators show a plain live-role count in that spot instead, since they already have the Stop hook's own budget/recycle guard. Every TOP-LEVEL coordinator also carries an independent kanban status (active / backlog / blocked / done, default active) — the active list is grouped by it, in that order, each group with its own labeled divider (an empty group renders nothing). Only the group holding the current rail selection is expanded; the other three collapse to their header line, and stepping past a group's edge transparently expands the next one. This is fully independent of pin/archive and of the s/S role-status keys below. Keys act on the rail selection:
| Key | Action |
|---|---|
j / k |
Move the rail cursor down / up |
Space |
Collapse / expand an orchestrator, or the Freelance / Archive section |
/ |
Filter the rail by name (substring, ancestry-preserving); ↑/↓ navigate the filtered rows while typing, Enter accepts, Esc clears |
Tab |
Enter a pane from the rail. Once a terminal pane is focused, Tab / Shift+Tab pass through to the agent's PTY so its autocomplete works (e.g. /plugi+Tab → /plugin) — they no longer cycle focus |
ctrl+alt+← / ctrl+alt+→ |
Move focus between panes once you're in one (the focus ladder; Tab is reserved for the agent there). ctrl+q steps back to the rail |
ctrl+z |
Fullscreen the focused content pane (rail stays; the other pane hides). Also traps ^Z so it can never suspend the pane's agent |
ctrl+j |
Open the jump switcher — works from the rail AND from a focused pane (never reaches the PTY). See the Task List table above |
ctrl+k |
Open the command palette — works from the rail AND from a focused pane (never reaches the PTY; this replaces the old raw pass-through of ctrl+k into a pane). From a pane its rows also include that pane's own fullscreen/copy actions |
ctrl+g |
Jump directly to the next role needing input or coordinator with an unread board, cycling on repeat (wraps around) — works from the rail AND from a focused pane (never reaches the PTY). See the Agent View table above |
ctrl+b |
Restore the rail's pre-interruption fold/selection snapshot at any time — works from the rail AND from a focused pane (never reaches the PTY). See the Agent View table above |
ctrl+y |
Copy the agent-staged clipboard payload for the focused pane's task (coordinator or worker) — the Projects view shows several tasks at once, so the copy is scoped to whichever pane has focus. Always steals the key (the pane's title shows (ctrl+y copy) when a payload is staged); flashes "Nothing to copy" otherwise — never falls through to the PTY |
Enter |
Enter the selected role's pane, reviving its session first — a dead session is restarted, and a suspended/stuck worker is resumed in place via --session-id |
w |
Spawn a worker under the selected coordinator (opens the full new-task modal: project / branch / backend / model / prompt, project defaulted to the coordinator's) |
n |
Create a new top-level coordinator (same new-task modal); bootstraps a fresh orchestrator + coord role bound to a new task. Works on an empty rail |
r |
Rename the selected role / orchestrator |
a |
Hide the selected worker / sub-coordinator into its parent coordinator's nested archive (Tier 1): reversible toggle, keeps the session + worktree alive, no confirm. A top-level coordinator has no parent archive, so a is a feedback no-op there |
P |
Pin / unpin the selected role / orchestrator |
s / S |
Advance / revert the selected Hera role status (idle → working → blocked → done) |
m / M |
Advance / revert the selected top-level coordinator's kanban status (active → backlog → blocked → done, wrapping); a no-op on a role, a nested/bridged sub-coordinator, or an empty selection |
J |
Adopt a freelancer into, or re-parent a coordinator under, a chosen orchestrator (type-to-filter picker) |
B |
On a coordinator: Force-recycle (confirm): kill its session and restart it immediately on the same task/worktree/branch, no idle wait — the human-forced counterpart to the coordinator's own self-service recycle (see Context-budget Stop hook). On a worker or freelance role: Bounce (confirm): send its live session an instruction to call hera_status(handoff_note=..., request_recycle=true) itself — the same self-service recycle then restarts it in place once it goes idle and makes that call. No kill/restart happens directly from the key; no fallback if the role never responds. No-op on an empty selection |
c |
Clear the selected coordinator's archive (confirm): NUKE every Tier-1 hidden agent under it — reclaim their worktrees + branches, archive their tasks, remove them from the rail (rows retained for DB recovery). Scoped to the selected coordinator, never global |
C |
Cleanup: open the merge-safety review popup over the full stuck-task backlog across every project (Tier A + Tier B classification, daemon-side) and immediately clean the chosen scope (row/worktree/branch). Selection-independent — works regardless of the rail cursor |
i |
View the selected role's task message inbox (read-only; same viewer as the Task List i). On an orchestrator header it opens the coordinator's inbox |
> |
Move the selected role's task to another account (same picker as the Task List >). Same task, worktree, branch and hera role; only the agent process restarts. Local mode only |
ctrl+d |
Nuke the selected role; on a coordinator / orchestrator header (or a nested sub-coordinator row), cascade the whole subtree — every nested sub-coordinator + their agents (Tier 2). Nuke removes the rows from the rail entirely (a nuked_at mark — no DB deletes; role / orchestrator / inbox / task rows all retained and recoverable via the DB) and reclaims the worktree + branch + session. On a single (sole-bound) role this opens the merge-safety review popup (Tier A, local-only) instead of a plain confirm — NOT-SAFE/SAFE sections, Clean safe/Clean all/Cancel, never a hard block; a cascade keeps its existing count-bearing confirm, augmented with a confirmed-merged count. A task bound live in another orchestrator is preserved. (vs a, which hides but keeps the worktree/session) |
← |
Move to parent coordinator (rail focused only — passes through to the PTY when a pane is focused) |
Cmd+← |
Return to Tasks from the focused rail (cross-tab arrow navigation) |
Cmd+→ |
From the rightmost present pane (agent, else coordinator), continue to the Settings tab's left pane (cross-tab arrow navigation; not while a pane is fullscreen) |
Cmd+↑ / Cmd+↓ |
Move the rail cursor up / down without changing the focused pane (the mod-7 escape sequence is consumed — the pane's PTY never sees it) |
ctrl+q |
Return focus to the rail |
When a worker is selected the details region shows its live agent terminal. A coordinator shows its read-only Agents roster above the plan DAG or its durable human board. Selecting a coordinator with unread board items lands on the board; refreshes preserve your view. p toggles plan/board from the rail or details pane. An unread board changes that coordinator's marker to a mailbox. The resident tab has the same board controls, with replies pasted into the resident conversation; new unread reminders also open its board during refresh, and unread board items light its header marker and rail box until read or dismissed. Set [ui] coordinator_board = false to disable automatic board landing and mailbox markers.
| Key (coordinator board) | Action |
|---|---|
p |
Toggle plan / board (rail or details focused) |
Cmd+→ |
Follow the focus ladder into the right details / board pane |
↑ / ↓ / j / k |
Select previous / next item |
Enter |
Expand / collapse; expanding marks the item read |
PgUp / PgDn |
Scroll expanded text |
r |
Focus coordinator and paste Re: <subject> (#<id>) plus two newlines; never submits |
x |
Dismiss item as human; decisions also leave a durable coordinator inbox notice |
The plan DAG shows the coordinator-authored worker dependency graph; a coordinator without an authored plan shows the empty-plan hint.
| Key (plan DAG) | Action |
|---|---|
↑ / ↓ / j / k |
Move between plan stages (collapses any fanned-out group on the way) — always the plan graph's, never the Agents roster above it |
PgUp / PgDn |
Scroll the Agents roster when it has more agents than fit — its own dedicated keys, so they never compete with the plan graph's stage nav |
← / → / h / l |
Move between slots; inside a fanned-out group, walk its members |
Space |
Fan out / collapse a parallel group — a pure toggle that never opens a node (on a lone leaf it is a no-op; opening is Enter's job) |
Enter |
Fan out a collapsed group; on a fanned-out group member, a sub-coordinator node, or a plain leaf, open that node: drill into a sub-coordinator's child orchestrator's plan, else jump to that node's role within the Projects view (selects it in the rail + focuses its agent pane — no tab switch), reviving a dead/suspended session just like the rail's Enter. On a member it does not collapse the group — that's Space / Esc |
Esc |
Back out one level: collapse a fanned group, else drill out to the parent plan (consumed at the root — leave the pane via Ctrl+Q / Tab) |
End-of-life has two resting states, and no DB row is ever hard-deleted — "done with" always means gone from the rail + worktree gone from disk, with the role / orchestrator / inbox / task all retained and recoverable: Hide (a, Tier 1) nests a worker / sub-coordinator in its parent coordinator's archive and keeps its session + worktree alive (reversible — un-hide restores it exactly); Nuke (ctrl+d, Tier 2; or C for a coordinator's whole archive) removes the row from the rail entirely, reclaims its worktree + branch, and stops its session, leaving only the DB rows behind. Every nuke is confirm-gated and honors multi-binding isolation (a task bound live under another orchestrator is never touched).
| Key | Action |
|---|---|
Enter |
Open diff |
s |
Toggle split/unified diff |
f |
Reveal in Finder |
o |
Open file (default app) |
e |
Open in editor ($EDITOR) |
t |
Open terminal in worktree |
| Key | Action |
|---|---|
Esc / ctrl+q |
Close / cancel |
Esc (Project first) |
Close autocomplete first, then return to project selection with prompt/name preserved; Esc again cancels. ctrl+q cancels outright. |
Enter |
Confirm / submit |
Tab / Shift+Tab |
Navigate fields |
Appearance → Project first (ui.project_first, default false) asks for a project before new tasks and coordinators when multiple projects exist. The empty filter starts with the current project highlighted, followed by recent projects, then unused projects alphabetically. Enter continues to the prompt. Worker spawn keeps its existing flow. This preference affects the local TUI only; remote TUI, web, and macOS support are named follow-ups.
The Bridge section provides Enabled, Message delivery, Hera band, Message toasts and Keep place on re-render and Auto re-render on resize toggles in local and remote TUI mode. Keep place defers the width re-render kick while a pane is scrolled up or the composer holds unsent text, and (Claude) restores the composer draft after a kick; the setting is ui.rerender_keep_place ([ui] in config.toml, also in GET/PUT /api/settings as ui.rerender_keep_place). ui.auto_rerender_on_resize (default off, [ui] in config.toml, also in GET/PUT /api/settings) restores the old automatic kill+resume on every pane width change; by default a width change only resizes the PTY (the agent repaints itself) and the ctrl+n hotkey re-renders history on demand. It governs the TUI only — the web and macOS clients keep their daemon-side kick for now. Message delivery applies immediately to new deliveries and budget nudges; the band and toasts apply in running sessions within about 15 seconds (or at the next turn); the master switch also controls loading the mod into new sessions and gates the other three. Config-file values override Settings. Web and macOS Bridge settings controls are a named follow-up.
| Key | Action |
|---|---|
j / k |
Navigate rows |
n |
New project / backend / schedule / backend tier / account |
e |
Edit project / backend / schedule / backend tier threshold / account field |
d |
Delete project / set default backend / delete schedule / remove backend tier / delete account (press twice) or reset the default / a project's account |
a |
Edit project's AppleEvents allowlist (on a project row) |
m |
Edit backend's default model (on a backend row) |
p |
Cycle probe kind (on a Backend Tiers row) |
A |
Cycle account (on a Backend Tiers row) |
K / J |
Move backend tier up / down (on a Backend Tiers row) |
t |
Toggle schedule enabled (on the Scheduled Tasks section) |
r |
Run schedule now (on the Scheduled Tasks section) |
i |
Quick add projects |
Enter / ◀ / ▶ |
Toggle / cycle settings |
Cmd+← / Cmd+→ |
Move between the left (categories) and right (detail) panes; Cmd+← from the left pane returns to the Projects tab's rightmost pane (cross-tab arrow navigation) |
argus --remote https://mbp-2026.tail1efd7.ts.net --token "$ARGUS_TOKEN"Launches the TUI pointed at a remote argus daemon instead of the local one. No local SQLite is opened, no daemon socket is contacted — every persistence call goes through the REST API the daemon already serves on port 7743 (the same surface the PWA uses). --token falls back to ARGUS_TOKEN.
A few local-only operations gracefully degrade in remote mode: spawning a fresh task via the new-task form, forking, schedule fires, and prune-completed all require local worktree access. The status bar surfaces the equivalent REST endpoint when these are attempted remotely. Everything else — task list, attach, input, resize, archive/rename/status flips, settings — works identically against the remote.
A native SwiftUI client (Argus) built on ArgusKit, a typed Swift SDK over the daemon's REST + SSE API. It drives the same daemon as the TUI and the PWA — there is no separate backend.
Requirements: macOS 15+ and the Swift 6.3 Command Line Tools (xcode-select --install). No Xcode or xcodebuild needed — the app is a SwiftPM package (macos/Package.swift) built entirely from the CLT toolchain. SwiftTerm (the live-terminal widget) is the only third-party dependency; ArgusKit itself is pure Foundation.
| Target | Command | What it does |
|---|---|---|
make mac-build |
swift build --disable-sandbox |
Compile ArgusKit + Argus. |
make mac-test |
swift run --disable-sandbox ArgusKitTests |
Run the ArgusKit test suite. This is the test entry point, not swift test (see below). |
make mac-run |
swift run --disable-sandbox Argus |
Build and launch the app from source. |
make mac-app |
./scripts/mac-app.sh |
Assemble the double-clickable macos/dist/Argus.app (release build + Info.plist + codesign) and print the open command. |
--disable-sandbox is required because macOS forbids nested sandbox-exec profiles: when you build from inside an argus agent sandbox (how this repo is dogfooded), SwiftPM's own manifest sandbox fails to apply. The app's own manifest needs no protection from us, so disabling it makes the targets work everywhere.
Why not swift test: on a CLT-only install (no Xcode) swift test builds the .xctest bundle but cannot execute it — it silently runs zero tests and exits 0, so even failing tests "pass". The suite is therefore an executable swift-testing target (ArgusKitTests) run via make mac-test, which executes it for real and propagates a correct exit code.
Launch env hooks (automation / deep-linking; each read once at startup):
| Variable | Value | Effect |
|---|---|---|
ARGUS_MAC_SELECT_TASK |
task id or exact name | Selects that task at launch, overriding the default auto-select. |
ARGUS_MAC_INITIAL_TAB |
terminal | diff | files | info |
Sets the initial detail tab regardless of which task is selected. Unrecognized values ignored. |
# run the bundle binary directly — `open` does not pass environment variables through
ARGUS_MAC_SELECT_TASK=my-task ARGUS_MAC_INITIAL_TAB=diff \
macos/dist/Argus.app/Contents/MacOS/ArgusSettings: by default the app connects to http://127.0.0.1:7743 using the master token from ~/.argus/api-token. To drive a remote daemon (e.g. over Tailscale), set a Server URL override (stored in UserDefaults) and a token override in Preferences — the token is written to the macOS Keychain, never to disk. Preferences also carries the needs-input / idle notification toggles and the menu-bar-extra toggle.
Feature surface:
| Area | What's there |
|---|---|
| Task rail | Sidebar of tasks grouped into per-project folders (TUI-parity ordering; Archived collapsed at the bottom), with status icons, needs-input markers, and a live connection indicator. |
| Detail tabs | Terminal (live SwiftTerm session), Diff (unified, per-file collapsible), Files (worktree tree), Info. |
| Lifecycle | New-task sheet (project / backend / model / prompt), fork, stop, restart, resume, rename, archive, delete — with confirmations. |
| Live updates | Task list + terminals driven by the SSE event stream; a 30 s safety-net poll; offline/connection banner. |
| Notifications | Native UNUserNotificationCenter alerts on needs-input / idle (when not frontmost), a Dock badge counting tasks awaiting you, and an optional menu-bar extra. |
| Windows | Main window, Schedules (⇧⌘S), System (host metrics + session counts), and standard Settings. |
| Hera | Orchestration roster (GET /api/hera) plus nuke / hide / pin / rename / status / kanban actions. |
Parity note: the Hera view is read-only in both the macOS app and the web app — they render the roster and plan but expose no coordinator mutations (spawn / block / status). Driving a hera team stays TUI-only until the hera mutation endpoints are exposed over REST. Everything else — task lifecycle, terminal I/O, diffs, schedules, settings — is at full parity across all three clients.
Keyboard shortcuts:
| Shortcut | Action |
|---|---|
| ⌘N | New task |
| ⌘R | Rename selected task |
| ⇧⌘S | Schedules window |
| ⌘Q | Quit |
| ⌘1 / ⌘2 / ⌘3 / ⌘4 | Switch detail tab: Terminal / Diff / Files / Info |
| ⇧⌘/ | Shortcuts help sheet |
| ⌘⌫ | Destroy selected task (with confirmation) |
| ⇧⌘B | Fork selected task |
| ⇧⌘E | Open selected task's worktree in Finder |
| ⇧⌘U | Open selected task's PR in the browser — works from the app's global scope and while the Terminal tab has focus |
| ⇧⌘J | Jump to the next task whose session needs input |
| ⇧⌘A | Archive/unarchive selected task |
| ⇧⌘P | Pin/unpin selected task |
| ⌘F | Focus the sidebar's filter field |
| ⌘↑ / ⌘↓ | Previous/next task, while the Terminal tab has focus |
| ⌘← / ⌘→ | Cycle the detail tab, while the Terminal tab has focus |
| ⇧↑ / ⇧↓ / ⇧PageUp / ⇧PageDown / ⇧End | Scroll the terminal's scrollback, while the Terminal tab has focus |
| ⇧⌘C | Copy the terminal's visible output |
The Cmd-modified chords listed as "while the Terminal tab has focus" are intercepted by a local key monitor before they reach the live terminal — they never leak into the agent's input stream. Every other keystroke, including all Ctrl chords, reaches the agent unchanged. Right-clicking a task row also surfaces status-advance/status-revert; the toolbar's "…" overflow menu has "Prune Stale Worktrees"; the sidebar's filter bar has a persistent hera-managed-tasks visibility toggle; and the Terminal tab's toolbar has a button that opens the Claude session switcher (see the REST endpoints above).
From the Settings tab (Status section, when the daemon is connected) the Source path row holds the path to your local Argus checkout, and the Update Argus row runs git pull --ff-only followed by go install ./... and then restarts the daemon so the new binary takes over. Active sessions reattach across the restart. The same controls are exposed in the web UI under Settings → Argus update (master token only).
Hera is Argus's native layer for running a team of agents. It introduces roles — a coordinator plus the workers and freelancers it spawns — bound to argus tasks and addressed by name. A coordinator delegates work to workers it spawns (hera_spawn_worker / the rail's w key), they trade messages over the same idle-gated bus that powers inter-task messaging, and the team's work renders as a plan DAG (the planned + live worker roles laid out by their hera_blocks dependency order, with sub-coordinator drill-in) folded into the Projects tab's details pane. The whole surface is the second tab (2) — see the Projects Tab keybindings above. The coordination layer runs in-process in the daemon; the view renders directly in the TUI. Agents drive it over MCP (the hera_* tools).
Native Hera and the external Hera plugin are mutually exclusive, selected by hera.enabled (default ON):
hera.enabled = true(default) — native Hera is active. It stores its state in the same~/.argus/data.sql(thehera_*tables), exposes thehera_*MCP tools in-process, and owns the second tab. The legacy Hera plugin's tools are suppressed so they never double-register.hera.enabled = false— the nativehera_*MCP tools are not served, and you can instead run the external Hera plugin over the plugin substrate. The plugin keeps its own~/.herastate and plugin view, entirely unaffected by Argus. The TUI's second tab is always the native Hera view regardless of this flag.
The two run independently and share no state. Switching to native Hera performs no migration of any prior ~/.hera data – native Hera starts fresh. Set the flag in config.toml ([hera] enabled = …) or the DB.
A long-lived coordinator accumulates context for the life of its orchestration in a way a disposable worker never does. argus coord-hook is a CLI subcommand meant to run as a Claude Code Stop hook: on every turn of any hera-bound session (coordinator, worker, or freelance) it self-discovers the daemon's REST port + API token, tails the session transcript for the latest main-chain assistant message's input_tokens + cache_creation_input_tokens + cache_read_input_tokens (summed, not cache_read_input_tokens alone – a prompt-cache miss collapses that field toward zero even though real context is unchanged or larger), and stamps the total into task_meta (hera, context_size) – this same stamp feeds the Projects tab rail's worker/freelance context-pressure indicator (a •/! mark on the row, see Projects Tab). Claude Code documents the transcript file as written asynchronously and warns it may lag the turn that just finished at the moment the hook fires – no hook event exposes token usage directly, so the hook re-scans across a short bounded retry window and keeps the largest value seen, but only when a single scan doesn't already match or exceed the task's previously-stamped size (the common case skips the retry entirely, so this isn't a fixed tax on every turn). Only for a coordinator role does the hook go further: once that value reaches the project's coordinator_context_budget ([hera] coordinator_context_budget, default 300000) it blocks the Stop event with a "reach a safe seam and recycle" nudge. The nudge is throttled: it recurs only after context_size grows by another coordinator_nudge_increment (default 50000) past the size at which it last fired, or immediately on a fresh over-budget episode following a drop back under budget (typically via a recycle). It self-gates hard on ARGUS_TASK_ID plus a resolved hera role of any kind, so it is a silent no-op only for a task with no hera binding at all (or any other Claude Code session on the machine).
The resident is an additional eligible subject without a Hera binding. Its hook stamps context and a sanitized reply preview on every Stop, uses [resident] context_budget with the same growth increment, and delivers its nudge through the reliable notification bus while allowing Stop. It never forces a recycle: the agent writes open loops to its configured memory folder and calls task_recycle at a safe seam. Daemon.ResidentState exposes unread, waiting, and the reply preview together.
Because every Argus-spawned agent inherits the daemon's real HOME regardless of which project it's working in, the hook is registered once, globally – Argus cannot write to a user's global settings file on their behalf, so this is a one-time manual step. Add to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "argus coord-hook" } ] }
]
}
}argus doctor checks whether this hook is registered and warns if it's missing – see Diagnosing binary skew below.
Diligence profiles route model choice per archetype and vary process rigor per project: spend premium models up the tree (plan / orchestrate / review / synthesize), where leverage is high and output is hard to verify; save cheaper models down the tree (CI loops, verification, docs), where work is high-volume and verifiable. A profile is a named, on-disk TOML preset; a project points at one by name. At spawn, Argus resolves the bound profile, feeds the per-archetype model into the existing model-resolution chain, and exports the resolution to the agent's environment so the in-repo hera/DAG skill is profile-aware.
A task's archetype names what kind of job it is. It is an optional task property set at the spawn layer – the new-task form, the new-worker prompt, the hera_spawn_worker archetype param, and plan-DAG nodes – on every spawn path except a new hera coordinator (which is always orchestrator). The archetype is the key matched against a profile's [archetype.<name>] tables; the selector defaults to (none), meaning no profile is consulted. The thirteen canonical archetypes (and the model the seed default profile assigns each):
| Archetype | What it is | default model |
|---|---|---|
brainstorm |
Exploratory design / ideation | opus |
orchestrator |
Coordinator that delegates and routes work (default for hera coordinators) | sonnet |
big_build |
Large, multi-file implementation | sonnet |
code_slice |
A scoped implementation slice (default for hera workers) | sonnet |
bug_fix |
A targeted defect fix | sonnet |
review |
A code / plan review pass | opus |
security_review |
A security-focused review | opus |
synthesis |
Consolidating multiple inputs into one result | sonnet |
spec_audit |
Auditing spec coverage / conformance | sonnet |
ci_loop |
A mechanical CI-green loop | haiku |
verify |
Verification / acceptance checking | haiku |
recovery |
Recovering a stuck or failed task | sonnet |
docs |
Documentation writing | haiku |
A profile's model field uses each backend's built-in model list. Claude's are stable CLI aliases that always map to the current model, so a profile does not churn per model release: opus, sonnet, haiku, fable. Codex has no such alias layer — its model IDs are versioned and OpenAI renames/retires them per release, so this list drifts and needs refreshing from time to time (current as of this writing: gpt-6.1-sol, gpt-6-sol, gpt-6-astra, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5). Validation accepts any model in the union of these built-in aliases and every configured backend's models list – so adding a foreign reviewer model under [backends.<name>] models makes it a valid profile model with no Argus code change. A profile model is applied only when it is valid for the worker's resolved backend; otherwise resolution falls through (e.g. opus named for a codex worker falls open to that backend's default).
A profile is one TOML file, discovered from two locations:
~/.argus/profiles/<name>.toml– the per-user library (fallback).<repo>/.argus/profiles/<name>.toml– an in-repo copy (checked in or gitignored, operator's choice). In-repo takes precedence over the library for the same name, so a repo can pin its own profile.
The DB stores only the project→profile name – never a profile body, and there is no profiles table.
extends– a profile may setextends = "<parent>"; the child's declared fields overlay onto the fully-resolved parent, recursively and per-field (a field the child omits is inherited). Anextendscycle is a validation error.validateCLI –argus validate <name>loads, resolves, and validates a profile, reporting every conformance error (unknown archetype, out-of-enumeffort/window, unknown model,extendscycle) or confirming it is valid and naming the source (in-repo vs library) it resolved from. Exit0= valid,1= not found / invalid. It is operator tooling only – not wired into the Go build, CI, or any Make gate.- Seed profiles – three documented examples ship embedded in the binary (
internal/profiles/seeds/), installable via Settings → Hera → "Install Default Profiles" orargus profiles install-defaults; either writes any seed name not already present into~/.argus/profiles/and never overwrites an existing (possibly customized) file. Installation is always an explicit action – nothing auto-installs on daemon startup. You can also copy a seed by hand into~/.argus/profiles/or a repo's.argus/profiles/and adapt it.default– balanced allocation across all 13 archetypes (the table above).lean–extends = "default", minimal process (single review pass, no gating) for daily-driven personal tooling.customer_grade–extends = "default", turned-up rigor (two review passes, gating, a security spot-check) plus a reviewer[panel], for customer-facing code with no dogfooding loop. Also re-escalatesorchestrator/big_build/spec_auditback toopusand swapsbrainstorm/reviewtofablefor this tier specifically.
- Project binding – Settings → project view shows a validated select-list of on-disk profiles; only profiles that pass validation are selectable, and the chosen name persists as the project's default binding. The new-agent modal also lets you pick a profile for that spawn. An unbound project resolves the
defaultprofile. effort/window– each[archetype.<name>]entry may also carryeffort(∈low/medium/high) andwindow(∈200k/1m). These are validated and surfaced in the plan/DAG view, but currently onlymodelis wired into hera spawn resolution — and, for Claude's native sub-agent dispatch,effortis threadable only through aWorkflowscript'sagent()(opts.effort), since the built-inAgent/Tasktool has no effort parameter as of this writing (see theargus-resolve-modelskill).- Reviewer
[panel]– a profile may carry an opaque[panel]block, composed and consumed by the cross-vendor review work (hera-spawn-review); validation enforces its grammar when the reviewer-panel validator is wired in, falling back to structural well-formedness otherwise. - Native sub-agent dispatch – hera worker spawn resolves an archetype's model automatically at spawn time; Claude's native sub-agent dispatch (the
Agent/Tasktool, or aWorkflowscript) has no such automatic path and must resolve it explicitly viamcp__argus__profile_resolve— see theargus-resolve-modelskill for the documented convention (resolve once per pipeline, gate the resolved model against the four in-session values, thread effort only where the mechanism accepts it). - Env vars – when a bound profile actively contributes a backend-valid model, the spawn exports
ARGUS_PROFILE,ARGUS_ARCHETYPE, andARGUS_MODELto the agent (mirroringARGUS_TASK_ID); when no profile resolves, none of the three are exported. - Plan/DAG view – each node shows its archetype and the applied model/effort, and a node/project is flagged with a warning when its bound profile is missing or invalid.
- Fail-open – a missing or invalid bound profile never hard-fails a spawn: Argus logs it and passes no model override (no
--modelflag, and no inlinemodelkey for OpenCode), so the agent uses its own CLI default. Validation is the loud surface (the CLI, the Settings select-list, the DAG warning); resolution itself fails open.
Resolution reads ~/.argus/profiles/ and the worktree's .argus/profiles/, so it runs daemon-side at spawn – outside the sandbox, where global ~/.argus reads would EPERM. The agent itself only ever reads the exported env vars (or its own in-repo .argus/profiles/).
Argus splits agent supervision across two background processes:
- The daemon (
argus daemon) owns coordination — hera, the REST API, MCP, the scheduler, the DB, and the TUI's Unix socket. It is bounce-able: restarting it (for an upgrade, a config change, or to iterate on coordination) is cheap. - The session-supervisor (
argus session-supervisor) owns the agent PTYs themselves — theexec.Cmd, the master fd, the read/wait loops, the ring buffers, and the real exit codes. It is long-lived and rarely restarted. The daemon connects to it over~/.argus/supervisor.sockand proxies every session through it.
Because the supervisor — not the daemon — is the agent's parent process, bouncing the daemon no longer interrupts agents. A daemon restart re-attaches to the still-running sessions (the in-flight turn continues); only restarting the supervisor interrupts agents (they get SIGHUP when their PTY master closes), which is why the supervisor's interface is kept strict and it almost never needs to restart. Self-update therefore restarts the daemon, not the supervisor — your agents keep running across the swap.
Cycling the daemon (the safe, common case — agents survive):
argus daemon restart # stop + wait for socket cleanup + start a fresh daemon
argus daemon stop # graceful shutdown
argus daemon start # start (auto-starts a supervisor too if none is answering)A daemon restart is the path you want for an upgrade or config change: the supervisor keeps the PTYs alive and the new daemon re-attaches to the still-running sessions. You can also trigger it from Settings → System → Restart Daemon (Enter).
Cycling the supervisor (rare — this interrupts agents): the daemon auto-starts a supervisor on its own startup if none is answering on the socket (Setsid-detached, so it outlives daemon bounces), so you rarely drive it by hand. When you must — e.g. to load a new supervisor binary — the subcommands exist:
argus session-supervisor start # start the supervisor (auto-started by the daemon if absent)
argus session-supervisor stop # stop it — INTERRUPTS all agents (they re-resume on next start)
argus session-supervisor status # show supervisor pid/socket/protocol stateOr use Settings → System → Restart Session Supervisor (Enter), which is gated behind a confirmation prompt because it SIGHUPs every running agent. Under the hood it stops the supervisor and then restarts the daemon, since the daemon holds the supervisor connection and has no mid-life reconnect — bouncing the daemon is how it picks up the freshly-started supervisor. Active tasks are interrupted and flip to In Review. The row only appears when supervisor mode is on.
Supervisor mode is ON by default (supervisor.enabled, see the config table below). To roll back to the legacy in-process path — where the daemon owns the PTYs itself, exactly as before the supervisor existed — set supervisor.enabled = false (config.toml or the DB) and restart the daemon. The in-process path is retained as a supported fallback for one release.
A Claude Code mod shipped inside Argus (internal/bridgemod/mod/argus-bridge) that reports a session's state to the daemon as structured events instead of Argus inferring it from the terminal screen. Argus embeds it, materializes it content-addressed under ~/.argus/cache/bridge-mod/ (the only part of ~/.argus a sandboxed session may write), and adds --plugin-dir to every Claude-family launch (other backends keep screen-reading). The mod is inert unless ARGUS_TASK_ID is set, fails open (a dead daemon never affects the session), and finds the daemon through ~/.argus/api-token + ~/.argus/api-port (the daemon rewrites the port file each time the API binds).
It reports session.start (session id, model, cwd — re-sent after /clear or a model change), turn.start / turn.complete (busy/idle), needs_input / cleared (AskUserQuestion/ExitPlanMode are exact; permission asks still pending after a short grace are pending-or-running; subagent dialogs included), session.measure (context, cost, plan rate limits) and a 15s heartbeat.
Phase 2 uses the bridge as truth: a report is live for 45s only when its launch generation matches the currently running Claude process. Daemon, TUI, web and macOS retain its busy state throughout permission intervals; only uncertain permission needs-input requires scraper corroboration. Exact dialogs use bridge state directly. Stale/missing bridges, other backends and the in-process TUI keep scraping. Source transitions log [bridge] source only when liveness changes; the scraper comparison remains at GET /api/bridge/shadow. Measurements supply context and cumulative cost, and fresh account-matched weekly rate limits precede the default-account hidden probe. Named accounts without a fresh bridge reading stay unknown. Disable with [bridge] enabled = false.
go install can update one argus binary while the others keep running — the TUI on a new build, the daemon and/or supervisor on the old bytes — which silently breaks the keys that need the TUI↔daemon round-trip (Enter to attach, Ctrl+Q to detach) while local keys keep working.
argus doctor # read-only: enumerate every argus binary + running process, print a verdictdoctor resolves the argus on your PATH, the ~/.argus/argusd symlink target, the go install target, and the identity each live process (daemon, supervisor, this binary) is running, then prints a table and one of three verdicts with the exact fix:
- HEALTHY — every resolvable actor agrees (exit 0).
- RESTART NEEDED — same file, older bytes in a running process (a rebuild landed); the fix is
argus daemon restart. - PATH DIVERGENCE — the daemon symlink target and your
PATHargusresolve to different files (the real footgun — a plain restart just relaunches the divergent binary and loops); the fix re-points/reinstalls so both point at one build.
The supervisor is judged on its executed surface, not its binary hash. Restarting the supervisor SIGHUPs every running agent, and roughly 9 in 10 builds change nothing it actually executes while still changing the whole-binary hash — so a hash-based verdict pointed at a fleet-killing remedy on a signal that was right about one time in ten. Instead, each build declares a two-part surface version naming the observable behavior of supervisor-resident code: a spawn component (BuildCmd, sandbox profile, skills/routing injection, secrets, cache dirs — read only when a session starts) and a stream component (PTY read loop, ring buffer, session log, R/S handlers, exit-info caching — serving live sessions). A supervisor whose surface matches is HEALTHY even when its binary hash differs; a mismatch is reported with what it costs — spawn-only says already-running agents are unaffected and only new sessions use the old spawn config, stream says live sessions are affected and a restart is warranted. Both hashes and both surface versions stay in the printed table. A supervisor too old to report a surface version falls back to the hash, exactly as before. The constants are hand-bumped like ProtocolVersion, and a test fails CI if a declared supervisor-resident source file changes without the author explicitly deciding whether the behavior did.
The TUI re-evaluates skew about once a minute while running, and on reconnecting to the daemon — not only at launch. A skew found after startup surfaces as a transient status-bar notice, never a modal; at launch, the blocking prompt is reserved for a stale daemon or a stream-surface mismatch, since a spawn-only mismatch cannot affect anything already running.
It is strictly read-only (never touches a symlink, binary, PATH, or process) and best-effort — an unresolvable row degrades to "unknown" rather than aborting. Exits non-zero on any non-healthy verdict.
doctor also independently reports whether the context-budget Stop hook is registered — REGISTERED, NOT REGISTERED (prints the exact snippet to add), or UNKNOWN (~/.claude/settings.json missing/unreadable, reported distinctly rather than assumed absent). This check is purely advisory and never affects the exit code above, which stays governed solely by the binary-coherence verdict.
doctor also independently reports whether the per-user diligence-profile library (~/.argus/profiles/) has anything in it — FOUND (at least one profile file validates), NONE FOUND (prints the argus profiles install-defaults remediation), or UNKNOWN (the directory couldn't be listed for a reason other than not existing). This checks the library's existence only, not whether any given project is bound to a profile — an unbound project is expected and unwarned. Like the Stop-hook check, it is purely advisory and never affects the exit code.
doctor also independently reports the [secrets.op] bootstrap-credential status — RESOLVED (bootstrap_source is configured and resolves), NOT RESOLVED (configured but the resolve failed — e.g. 1Password signed out or a renamed Keychain item), or NOT CONFIGURED ([secrets]/[secrets.op] absent, a no-op). The same tri-state is mirrored live in Settings → System. Like the checks above, it's purely advisory and never affects the exit code; the resolved credential value itself is never printed or logged, only the tri-state.
doctor also lists dev stacks (see [devstack] below) still running in the worktree of a finished task — FOUND, NONE FOUND, or UNKNOWN (process scan or task lookup unavailable). Advisory only: it never affects the exit code and never stops anything.
Launch the TUI with ARGUS_TYPING_LOG=1 argus to write keystroke timing and slow tick lines to ~/.argus/ux.log. Input content and length are never logged. Normal launches leave this diagnostic off.
Run go run ./cmd/argus-latency report --last 10m for per-pane percentiles, including a separate quiet population (quiet_ms >= 50, no overlapping key). Use --since and --until with RFC3339 timestamps, or --log for a saved capture. go run ./cmd/argus-latency stalls --duration 30s samples the TUI's pprof listener on port 6061; goroutine dumps add overhead, so compare captures under similar load.
Toggle from Settings → Status → Auto-start at login (Enter), or use the CLI:
argus daemon install # write ~/Library/LaunchAgents/com.drn.argus.daemon.plist and bootstrap into launchd
argus daemon uninstall # bootout and remove the plist
argus daemon status # show plist path + installed/loaded stateThe plist is configured with RunAtLoad and KeepAlive { SuccessfulExit = false }, which means launchd starts the daemon at login and restarts it if it crashes (non-zero exit) — but a clean argus daemon stop is honored and won't trigger a respawn. Stdout/stderr are written to ~/.argus/launchd.log. The plist points at ~/.argus/argusd, a symlink to the resolved argus binary; reinstalling rewrites the symlink so launchd picks up the new binary on next start. macOS only — Linux/Windows show no toggle.
Accounts let one Argus run tasks under different Claude Code and Codex subscriptions, such as a work plan and a personal plan. You pick the account when you start a task, and sessions on different accounts run side by side.
Each account is a separate config directory per tool. A Claude task runs with CLAUDE_CONFIG_DIR set to the account's dir, which gives it its own login (macOS Keychain entry), transcripts, and .claude.json. This is the multi-account method Claude Code's docs recommend. A Codex task runs with the account's CODEX_HOME, because that is where Codex keeps its login (auth.json) and all its state. Argus never reads, copies, logs, or writes credentials.
Easiest: open Settings → Accounts in the TUI. Press n to add an account (it starts with ~/.claude-<name>), then e on its rows to set the Claude config dir, Codex home, or label. ▶/Enter on the Default account row, or on a project row, cycles which account is the default. Deleting an account (press d twice) also resets any default or project selection that pointed at it, so tasks never silently fall through to another login. Settings also refuses two accounts that share a directory, since that would share a login. Edits take effect for the next task, with no restart. Anything defined in config.toml (below) overrides the same name from Settings and shows as (config.toml), read-only. The web and macOS apps pick an account when you start a task but can't edit accounts yet; --remote mode shows them read-only.
Or edit the file directly:
# ~/.argus/config.toml
default_account = "personal" # optional global default; top-level keys go before any [table]
[accounts.work]
label = "Work"
claude_config_dir = "~/.claude-work"
codex_home = "~/.codex-work"
[accounts.personal]
label = "Personal"
claude_config_dir = "~/.claude-personal" # Claude only: Codex tasks can't pick it
[project_accounts] # optional per-project defaults
acme-api = "work"
acme-web = "work"An account needs at least one of claude_config_dir / codex_home. Paths must be absolute or start with ~. The name default is reserved: it means each tool's usual dir (~/.claude, ~/.codex). With nothing configured, every task runs on default, exactly as before, and the form shows no Account field. Accounts are editable in Settings or config.toml. All keys are listed under Config file → Accounts.
Pin company projects to the work account. List every work repo in [project_accounts], so a task in that project uses the work account unless you pick another one. That keeps company code from running under a personal subscription by accident, and personal projects from being billed to work.
First login. A new account dir starts signed out. For Claude Code, start a task on the account and run /login inside the session; the login is saved in that account's dir (and its own macOS Keychain entry), and later tasks reuse it. For Codex, sign in once from a terminal with CODEX_HOME=<codex_home> codex login (a login done inside an Argus Codex session lands in Argus's overlay home rather than the account's codex_home). argus doctor shows each Claude account's login and whether the coord-hook Stop hook is registered there.
- Order: a named account you pick in the form, else the project's
project_accountsentry, else the account with the most headroom among the task's backend's tiers, else the best tier overall on any backend (only when the backend wasn't set explicitly), elsedefault_account, elsedefault. Pickingdefaultin the forms means "route by usage" (the entry is labelled auto-routed); only a named account pins. Bursts spread: each spawn counts as 5 extra usage points on its backend and account until a usage reading for that account taken at least 5 minutes after the spawn arrives (30 minutes at most; per daemon process, not persisted). Via REST/MCP,account: "default"still pins. - Backend support: a Claude backend needs an account with
claude_config_dir, and a Codex backend needs one withcodex_home. pi, opencode, antigravity, cursor, omp, and custom commands run only ondefault. The forms list only the accounts that can run the selected backend. If you explicitly pick an account that can't run the backend, task creation fails before anything is created. If the account came from a project or global default, the task falls back todefaultand the switch is logged toux.logunder[account]. - Fixed unless you move it: the account is saved on the task when it is created. Resume and restart use the saved account even if you later change the defaults. Only an explicit move changes it; a fork keeps a pinned source account and routes again when the source was auto-routed.
- Removed account: if a task's account is removed from config, starting or resuming the task fails with an error naming the account. It never silently falls back to
default. - Inheritance: Hera workers, sub-coordinators, freelancers and MCP
task_createchildren inherit a pinned coordinator or caller account (a named account, or an explicitdefaultset through the API). A blank account permits routing, and so does a coordinator whose account was itself picked by tier routing: its workers are routed again instead of piling onto that account after it fills up. If an inherited account can't run a worker's backend (for example tier routing picks Codex but the account has nocodex_home), the spawn fails rather than running on a different login; add the missing dir or pin the backend. MCPtask_createinherits a pinned caller's account throughcaller_idorcwd(an unpinned or routed caller's children are routed too) and accepts an explicitaccount.
When an agent is stuck on its account's usage limit, press > on it (Task List or Projects rail) and pick an account, or auto for the other account with the lowest current usage. The picker lists every account (the current one marked; picking it restarts the task in place), shows each one's 5-hour and weekly usage, and warns on any at or above 80% without blocking it. Argus copies the conversation into that account, switches the task's account, and restarts the same task in place with the conversation resumed: same worktree, branch, and hera role, so its coordinator's messages still reach it. Nothing runs on the exhausted account, so it works with no usage left. A busy agent is interrupted after a warning, and its last in-flight turn may be lost.
Out of Claude budget: restart on Codex with a handoff. When every Claude account is spent, the picker also lists rows like Codex · default — restart with handoff (conversation not carried over), with Codex usage when a reading exists. This is a restart in place, not a resume: the transcripts differ, so the conversation does not come along. The task keeps its worktree, branch and hera role; the new session opens with a handoff that says it was moved and has no prior context, then gives the worktree, branch, git status, the last exchange with you (when the Claude transcript is readable), and the original prompt. The same rows move a Codex task back to Claude. What you lose on Codex: the argus bridge mod and context indicator, accurate needs-input detection (it falls back to screen scraping), and hera_new_orchestrator (rejected for non-Claude callers). Web and macOS have no move UI; the REST and MCP routes below work from anywhere.
A coordinator can do the same for its workers with the MCP tool task_move_account, and any client can call POST /api/tasks/{id}/account. A task cannot move itself. Moving clears the routed mark, so the task's later workers inherit the account you picked.
| What | Per account |
|---|---|
CLAUDE.md, skills/, commands/, agents/ |
Shared. Symlinked from ~/.claude the first time the account is used (change the list with inherit). |
settings.json |
Copied once from ~/.claude/settings.json, then separate. apiKeyHelper, forceLoginMethod, forceLoginOrgUUID, awsAuthRefresh, awsCredentialExport, otelHeadersHelper and the whole env block are left out of the copy: they are tied to the default login or run credential-fetching commands (and would override or break the account's own login), and env can hold other secrets that must not be duplicated. An existing file is never overwritten, so later changes to ~/.claude/settings.json don't reach the account. |
Login, .claude.json, projects/ (transcripts), plugins/ |
Separate. Never linked or copied. |
| Codex home | Separate. Created empty (mode 0700); nothing is copied from ~/.codex. Argus's builtin skills reach it through a per-account overlay. |
| Argus MCP server | Same for every account. It is passed as a launch flag; nothing is written into an account's config. |
- An inherited
CLAUDE_CONFIG_DIRis always removed from the spawned agent's environment. On an explicit account, inheritedANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN, andCLAUDE_CODE_OAUTH_TOKEN(Claude) orOPENAI_API_KEYandCODEX_API_KEY(Codex) are removed too, because both tools rank them above the stored login. A backend's ownenv_varsmapping is applied afterwards and still wins. For example, the seededcodexmappingOPENAI_API_KEY -> HERA_OPENAIsupplies a key wheneverHERA_OPENAIresolves. - Sandboxed tasks get write access to their own account dir only (when it is outside
~/.claude/~/.codex), never to another account's.
Tier routing and account moves can run a project's Claude task on an account other than the one its claude.ai artifacts belong to, and Claude Code's built-in artifact tool always publishes to the session's own login. [project_artifact_accounts] names each project's artifact home account (a project with no entry uses its [project_accounts] entry; with neither, nothing changes — default_account is never used):
[project_artifact_accounts]
history = "personal"At every spawn, resume, restart and account move, Argus exports ARGUS_ARTIFACT_ACCOUNT=<home account> to the agent when the project has a home account and either the task runs on a different account or its backend has no built-in artifact tool (every backend except Claude). Otherwise the variable is absent (never empty), and an inherited value is removed. Argus's builtin routing guidance tells an agent that sees it to publish with the artifact_publish MCP tool (account = $ARGUS_ARTIFACT_ACCOUNT, plus update = the link for an existing artifact) and never to fall back to the built-in tool on its own. The home account must be default or an account with a claude_config_dir. Cursor and custom backends receive the variable but not the routing guidance (they have no routing delivery), so their agents won't know to use artifact_publish unless a project skill tells them. The table is config.toml-only for now.
Publishing from the daemon. Argus publishes the page itself, from the daemon, with a per-account claude setup-token token; the token never reaches an agent. Setup, once per account:
-
Sign in to Claude Code as that account and run
claude setup-token(the token is valid for a year). -
Store it in a secret store, e.g. 1Password, and point
[artifact_tokens]at it (a literal token is rejected):[artifact_tokens] personal = "op://claude/shell-env/CLAUDE_SETUP_TOKEN_PERSONAL" default = "keychain://argus-artifacts/default"
-
That's it:
[artifact_tokens]is read live. A daemon restart is needed only when you rotate a secret the daemon has already resolved (resolved secrets are cached for its lifetime).
If the home account can't publish right now (its usage is exhausted, so claude.ai switches its artifact tools off), artifact_publish saves the page as a task artifact, probes the other [artifact_tokens] accounts, and returns status: "unavailable" with the saved copy's artifact_path and the accounts that can publish. The agent then asks you whether to publish to one of those, open the page in the browser, reveal it in Finder, or park it; publish it later with argus artifact publish <file> --account <name> [--title <t>] [--update <link>] (exit 0 prints the link, 3 = account unavailable and lists the accounts that can publish, anything else non-zero). A rejected token (HTTP 401) means it expired or was revoked: re-run claude setup-token, update the secret, and restart the daemon so the cached old value is dropped. Only single self-contained HTML pages (≤16 MiB) are supported. Publish buttons in the TUI, web and macOS apps are a named follow-up; POST /api/artifacts/publish is the parity surface.
- TUI, web, and macOS new-task forms have an Account selector, preselected from the project or global default and labelled with the Claude sign-in (email / org) when known. It is hidden when only
defaultapplies. Skill autocomplete reads skills from the selected account. - REST:
POST /api/taskstakesaccount, and task JSON includesaccountandaccount_routed.GET /api/accountslists accounts with each one's currentusage;POST /api/tasks/{id}/accountmoves a task. See Projects & backends. - Limits: Claude tier accounts have their own bridge/headless readings; named Codex usage remains unknown rather than borrowing the default account’s rollout. Moving accounts is TUI, REST and MCP only (the web and macOS apps have no move button yet) and only for Claude tasks. Schedules use the project or global default. The TUI Hera spawn form ignores its Account field (nonblank coordinator accounts are inherited; blank accounts permit routing).
hera_new_orchestratorcoordinators don't inherit the caller's account. A supervisor older than protocol v8 can't carry the account, so Argus refuses to start or restart a non-default-account task on it until the supervisor is restarted (argus doctorflags the skew).
Argus can run agent processes inside macOS sandbox-exec for filesystem and credential isolation. Each agent session gets an SBPL profile that restricts reads and writes.
Global sandbox settings are managed in the Settings tab (3 key):
| Setting | Description |
|---|---|
| Enabled | Master toggle — applies to all projects by default |
| Deny Read | Extra paths to block reads from (comma-separated) |
| Extra Write | Extra paths to allow writes to — one row per path; n add, e edit, d delete |
Per-project overrides are set in the project form (e on a project in Settings) — Inherit, Enabled, or Disabled. Per-project deny-read and extra-write paths are appended to the global lists.
A per-task override is set on the new-task prompt (TUI Sandbox selector, web Sandbox select) — Inherit, Enabled, or Disabled — and wins over both the project and global setting for that one task; left on Inherit, the task follows the existing project/global resolution exactly as before. The override is resolved once at creation time, like the task's Sandboxed state itself.
Always denied read: ~/.gnupg, ~/.aws, ~/.kube, ~/.config/gcloud
Always allowed write: the task's worktree directory, /tmp, /var/folders, ~/.claude.json, ~/.claude/, ~/.codex/, the task's own account dir when outside ~/.claude / ~/.codex, ~/Library/Application Support/Google/Chrome (Chrome's crashpad writes there regardless of --user-data-dir), the main repo's .git dir.
Always allowed (IOKit): user-client opens (iokit-open / iokit-open-user-client) — required for headful Chrome (Playwright/Puppeteer), which calls IOServiceOpen on IOPMrootDomain at startup and SIGSEGVs on the denied open otherwise. The crashpad write rule above is necessary but not sufficient on its own.
ARGUS_SANDBOXED=1 identifies an agent launch actually wrapped in macOS sandbox-exec. Argus reserves this variable: inherited values and backend/cache mappings cannot override it, and unwrapped launches leave it absent. This applies to every backend, including resume and recycle. Use this signal for permission hooks that require OS sandboxing; ARGUS_TASK_ID and the worktree path only identify an Argus session.
Every agent argus spawns — sandboxed or not — gets ARGUS_TASK_ID set to its task ID, and always runs inside a git worktree at the deterministic ~/.argus/worktrees/<project>/<task> path. Together these identify an Argus-managed session, which skills should treat as unattended by default. Argus's own bundled skills already self-gate on exactly this (ARGUS_TASK_ID/$PWD residency); any third-party project skill can check the same signal (ARGUS_TASK_ID != "", or the worktree path prefix as a fallback) and fail fast with a clear message instead of hanging on a step that has no one present to answer.
Argus does not — and structurally cannot — reliably distinguish "a human happens to be watching this pane right now" from "this is running fully unattended." A step that needs a synchronous human response (typing a token at an interactive git credential prompt, tapping "Trust This Computer?" on a phone, accepting a USB-debugging dialog) should treat every Argus sandbox as unattended by default and surface that limitation rather than being worked around — a fresh git worktree also never inherits gitignored machine state from the project's main checkout (.env files, installed dependencies, platform SDKs), so a build step that assumes it's already provisioned needs its own guard too. See context/knowledge/gotchas/worktree.md for the fuller investigation this contract came out of.
argus renders via tcell v2.13+ which automatically wraps every frame in DECSET 2026 (Synchronized Output / BSU+ESU) when the terminal claims to be XTermLike — tmux's terminfo does. This means the inner application emits an atomic-frame sequence that, when honored, eliminates rendering tearing during fast updates (typing, PTY streaming, cursor nav).
tmux 3.2+ does not honor those inner sequences by default — you have to opt in. Without the opt-in, you'll see occasional visual artifacts (stale cells, partial frames) during rapid screen updates. Add this to ~/.tmux.conf:
set -g default-terminal "tmux-256color"
set -as terminal-features ',xterm*:sync'Reload tmux config (Prefix + : then source-file ~/.tmux.conf, or restart tmux entirely) and the artifacts disappear. This is the same fix used by Claude Code, neovim, kakoune, and other modern tcell/ncurses apps that emit synchronized output.
If you still see visible flashing after this config, that's a different issue — file a bug. argus calls screen.Sync() (the only thing that emits CSI 2J / clear-screen) in just two places: when you press Ctrl+L (manual refresh, one flash expected) and on tmux pane focus regain (recovers from any drift while you were on another window). Every other UI update flows through tcell's diff and should be invisible inside a properly-configured tmux.
Cycle through styles in the Settings tab using Enter or ◀/▶ on the Spinner row:
| Style | Frames | Speed |
|---|---|---|
| Progress (default) | Nerd Font progress icons | 150ms |
| Dots | Braille dots ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ |
100ms |
| Braille | Braille pattern ⣷⣯⣟⡿⢿⣻⣽⣾ |
100ms |
| Classic | ASCII |/-\\ |
150ms |
Argus runs an MCP server on port 7742 by default (or the next available port) and passes its live URL to Claude Code and Codex tasks at launch and resume.
Knowledge Base:
| Tool | Description |
|---|---|
kb_search |
Full-text search with ranked results and snippets |
kb_read |
Read full document content by vault-relative path |
kb_list |
List documents with optional path prefix filtering |
kb_ingest |
Add or update a document in the knowledge base |
kb_delete |
Remove a document by vault-relative path |
Task Management (lets agents orchestrate other agents):
| Tool | Description |
|---|---|
task_create |
Create a task with worktree and start an agent. Params: name, prompt, project, model (optional --model override), base_branch (stacked-PR start point), upsert, account (explicit account, "default" included), sandbox_override ("enabled" | "disabled"; disabled is rejected when the caller is sandboxed or unidentified), caller_id / cwd (identify the calling task so the new task inherits its account when pinned; unpinned callers' children route by usage). The session starts immediately. |
task_move_account |
Move a Claude task to another account and resume it there (same task/worktree/branch/hera role). Params: task_id, account (name, "default", or "auto" for the lowest current usage), force (interrupt a busy agent), caller_id / cwd (a task cannot move itself). For a coordinator rescuing workers stuck on a usage cap; check usage first with argus_api_get on /api/accounts. Pass backend (a configured backend of the other CLI) to restart on Codex, or back on Claude, with a handoff instead of a resume (account defaults to default); the result adds from_backend, to_backend, handoff, conversation_carried (always false). Codex loses the bridge mod and context indicator, accurate needs-input detection, and hera_new_orchestrator. |
task_list |
List tasks, filtered by status and/or project. |
task_get |
Get task details by id |
task_stop |
Stop a running agent (moves task to "in review") |
task_archive |
Archive or unarchive a task. Pass cwd (from the agent's pwd) to resolve by worktree, or id. Omit archived to toggle. |
task_rename |
Rename a task. Updates only the display name (branch and worktree paths stay locked to the original slug). Pass cwd or id plus name. |
task_complete |
Mark a task as complete (sets status, stamps EndedAt). Pass cwd or id. Does NOT stop a running agent — call task_stop first if needed. |
task_set_result |
Persist an opaque JSON result blob the orchestrator can read (PR URL, milestone, failure reason). Pass cwd or id plus result. Up to 64 KiB. |
task_recycle |
Reset the task's context window while continuing the same work: kills the current session and starts a fresh, empty-context one on the identical task/worktree/branch, seeded with handoff_note (required, ≤16 KiB) plus the original prompt as background. Deferred until the calling session goes idle. Pass cwd or id. Only the resident's own session may recycle the resident; other callers targeting it are refused. |
The twelve bundled skills (argus-archive, argus-complete, argus-handoff, argus-recycle, argus-resolve-model, argus-schedule, hera, hera-plan, hera-remind-me, hera-review, hera-review-test-adversary, hera-spawn-review) are auto-available in every spawned session — see Agent-facing skills. They let an agent finalize, schedule, coordinate, review, dispatch, hand off, and reset its own work via cwd resolution. Completing and archiving are independent axes.
Inter-Task Messaging (peer-to-peer between live or paused tasks):
| Tool | Description |
|---|---|
task_message_send |
Send a peer-to-peer message. Params: to, body, optional kind (note / question / answer), optional in_reply_to. Caller resolved via cwd or id. Body ≤ 64 KiB. Recipient inbox capped at 500 unread; sender rate-limited to 50/min. |
task_inbox |
Read messages addressed to the caller, oldest-first. Filters: unread_only (default true), sender, since (RFC3339), limit (default 50, max 500). Does NOT auto-mark read. |
task_message_ack |
Mark messages read. Pass message_ids (up to 500). IDs not addressed to the caller are silently ignored. |
task_ask |
Convenience: send a question and optionally block until a reply lands. Params: to, body, optional timeout_seconds (default 0 = return immediately; max 120). When blocking, polls the answer at 500 ms cadence; callers wanting longer waits poll. |
If the recipient has a live agent session the daemon also writes a single notification line into their PTY (best-effort). Same surface available over REST: GET /api/tasks/{id}/inbox, POST /api/tasks/{id}/inbox/ack, POST /api/tasks/{id}/messages.
Hera (native multi-agent coordination — served only when hera.enabled; see the Hera reference):
| Tool | Description |
|---|---|
hera_new_orchestrator |
Bootstrap an orchestrator and claim its coordinator role. The name resident is reserved; the resident uses its standing team and resident workers must request graduation with hera_send. Non-Claude callers use a kind=subcoord plan node. |
hera_join |
Claim the calling task's existing role + unread count, or (with role_name + kind) attach a new worker/freelance role under an orchestrator. Attach mode rejects (directing to hera_move) when the caller already holds a live binding under a different orchestrator. |
hera_move |
Relocate the caller's live binding to a different orchestrator: ends the current binding (end_reason: "moved") and creates a new worker/freelance role+binding under the target, in one transaction. Use instead of hera_join when already bound elsewhere. |
hera_rebind |
Repair a binding stuck claim-says-none / attach-says-exists (a reused worktree path left the live binding pointing at a stale argus task): reconciles the binding to the caller's real live task without tearing down the session — the role, its prompt, messages, and status all survive. Refuses when genuinely ambiguous. |
hera_spawn_worker |
Spawn a born-bound worker task + session under the caller's orchestrator (caller must hold a live coordinator binding). Resident calls require an explicit registered project, obey max_workers, and route only among enabled eligible accounts; optional account pins one. Resident workers require the Argus sandbox. Omit backend so [backend_routing] picks one by usage headroom; the result reports the backend and whether it was routed or explicit. Optional model overrides the worker's model (backend-scoped; prefer archetype). Optional archetype (diligence profile) rides onto the task; defaults to code_slice when omitted. |
hera_delete_worker |
Resident-only, connection-bound tool, hidden from other callers. role names a live worker of the standing team. Default deletion requires merge-safe or empty work; force=true returns the pre-delete branch, commit subjects/count, dirty count and cached PR state, with explicit unknown fields. Every force delete must be reported to Aaron in the resident conversation. |
hera_graduate_worker |
Resident-only, ask-tier role tool. Graduates a live Claude worker to a top-level coordinator without changing its task, conversation, worktree, branch or account provenance. Refuses ineligible backends, busy sessions and missing transcripts. Restart waits for idle without forcing, survives a daemon bounce and shows “graduation pending restart” on both Hera rails. The former worker is unreachable by resident delete. |
hera_send |
Send a role-addressed message. status is required for worker/freelance senders (idle/working/blocked/done/failed) and is applied synchronously before send. Workers/freelancers default to the coordinator when to is omitted; coordinators must name a recipient. |
hera_inbox |
Fetch unread role messages immediately, or block up to 120s with timeout_seconds; returned messages are marked read and pane deliveries cancelled. Managed Claude/Codex/OpenCode MCP clients use a 150s deadline so long waits can finish. |
hera_mark_read |
Mark a specific list of message IDs read and cancel their pending deliveries. |
hera_board_post |
Coordinator-only: post a human decision or fyi with one-line subject and body; returns stable numeric id. |
hera_board_update |
Replace own open item's kind, subject, body by id; material changes mark unread. |
hera_board_resolve |
Resolve own item by id, with optional note; records agent as resolver. |
hera_board_list |
List own open items, or history with include_resolved=true. Join/status/recycle also surface open ids and subjects. |
hera_status |
Set the caller role's status (idle/working/blocked/done/failed), mirrored to task_meta; done rolls the worker's task to in-review + ready_to_close; failed rolls to in-review without ready_to_close. Optional handoff_note (string) and request_recycle (bool) are accepted from any hera-bound role kind (coordinator, worker, or freelance): handoff_note is stamped to task_meta for the next recycle's seed prompt, request_recycle=true flags a pending self-service recycle that the daemon acts on once the session goes idle — for a coordinator this is driven by the coord-hook budget nudge, for a worker/freelance role only by a human-initiated rail B bounce. |
hera_revive |
Coordinator-only PULL-revive of one role the caller coordinates, by role_name. A dead session (no live process) restarts in place; a live session that is idle and not blocked on a prompt is kicked (stopped and resumed in place, recovering fresh with its original prompt when its transcript is missing). A kick restarts the agent and kills any background jobs it started in-session, so it is a last resort for a truly wedged session, not a progress check. Anything else (busy, blocked on a question, a live coordinator, a kick already in flight) is left untouched and reported as such. hera_send's auto-revive only ever restarts a dead recipient; it never kicks a live one. |
hera_tree_updates |
Scan the caller's orchestrator subtree for messages since a per-role cursor; returns TLDR subject lines only and auto-advances the cursor. |
hera_get_messages |
Fetch full message bodies by ID (after hera_tree_updates), scoped to the caller's orchestrator subtree. |
hera_plan_node |
Author a single planned node under the caller's orchestrator (coordinator-only). Params: name, kind (worker|subcoord, default worker), prompt (worker nodes) or goal (subcoord nodes — required; the goal handed to the spawned coordinator), optional archetype (diligence profile) persisted on the node and copied onto the task it materializes. A subcoord node materializes as a distinct coordinator agent with its own task, worktree, and child orchestrator. |
hera_block |
Add a blocking edge: blocked waits until blocker reaches role-status done. Coordinator-only; both roles must be in the same orchestrator. No cycles. |
hera_plan |
Author an entire plan graph in one call: a nodes array (each with name, kind, prompt/goal, optional project, optional archetype) and an edges array (blocked→blocker pairs). Coordinator-only; atomically creates all nodes then all edges. Supports mixed kind values in the same graph. |
hera_plan_node_update |
Edit a planned node's prompt and/or project before it materializes. Rejected after materialization. |
hera_unblock |
Remove a blocking edge between two roles. Idempotent. Re-pointing an edge is hera_unblock + hera_block. |
hera_plan_node_cancel |
Cancel a planned node: stamps cancelled_at, excludes it from materialization, unblocks dependents. Kept visible in the plan DAG as grey ✕. |
Diligence Profiles:
| Tool | Description |
|---|---|
profile_resolve |
Resolve the diligence profile in effect for the caller and return its full body (per-archetype model/effort/window, [rigor], [panel]) as structured JSON. Works from any cwd/task, not just hera-bound ones. Optional explicit profile name bypasses cwd resolution. Fails open: {"resolved": false, "errors": [...]} rather than a hard error. The argus-resolve-model skill is the documented convention for using this to pick a model for Claude's native sub-agent dispatch (the Agent/Task tool), as opposed to hera worker spawn, which resolves archetypes automatically at spawn time. |
Daemon State (read-only):
| Tool | Description |
|---|---|
argus_api_get |
GET a daemon REST path in-process (e.g. /api/tasks, /api/tasks/{id}/bridge, /api/hera) and return the body — no curl, no token, no permission prompt. GET-only: non-GET routes, SSE streams, ../scheme/host paths, and master-token-only routes are refused; the API token never appears in output. Present only when both the MCP server and the REST API are enabled. |
Schedule Management:
| Tool | Description |
|---|---|
schedule_list |
List all schedules with name, project, cron expression, enabled state, next/last fire timestamps |
schedule_create |
Create. Params: name, project, prompt, plus exactly one of schedule (cron or @every <duration>) or run_once_at (RFC3339 UTC); optional backend, model (per-schedule --model override), enabled. Explicit models are validated against the resolved backend before saving. |
schedule_update |
Partial update. Pass id plus any fields to change. Toggling enabled, rotating prompts, setting/clearing the model override, or converting between cron and one-shot (set the new field; the other clears automatically). The effective model is validated before saving. |
schedule_delete |
Remove a schedule by id. Tasks already created by previous fires are unaffected. |
schedule_run_now |
Fire a schedule immediately, out of cycle. Bookkeeping is updated so the next regular tick will not double-fire. One-shot rows auto-disable. Does NOT send a push notification — only cron-tick fires do. |
To-Do List (served only while a backend is configured — see Settings → To-Do List; today's only backend is Things 3, macOS-only; requires kb.enabled like every other MCP tool family below, since that flag gates the MCP server's existence, not just the KB tools specifically):
| Tool | Description |
|---|---|
todo_create |
Create an item. Params: title (required), notes. Returns the created item's id, needed by every other todo_* call. |
todo_list |
List open (not completed/canceled) items. Always queries the backend live — Argus never caches or mirrors item content. |
todo_update |
Update title and/or notes on an item. Params: id (required), title, notes — only fields you pass are changed. |
todo_complete |
Mark an item resolved/completed. Params: id. |
todo_delete |
Delete an item. Params: id. On Things 3 this moves the item to Trash — the same as deleting it in the app. |
Configuring a backend in Settings makes these tools appear on the very next tools/list call — no daemon restart. Clearing the backend selection makes them disappear the same way.
Agent-Staged Clipboard:
| Tool | Description |
|---|---|
argus_clipboard_set |
Stage text for the user to copy with one tap (PWA Copy button) or one keypress (TUI ctrl+y). Params: text (required), id or cwd. Last-write-wins, 5-min TTL, 1 MiB max. |
sandbox_propose_write_paths |
Ask the human to grant the task's project extra sandbox write paths. Params: paths (array of {path, reason}, ≤20, required), id or cwd. Records a PENDING proposal only — nothing applies until a human approves in the TUI (Settings > Sandbox), web Settings, or macOS Settings; applies to new sessions. No MCP tool can approve. |
Artifacts:
| Tool | Description |
|---|---|
artifact_register |
Register a file the agent produced (HTML report, PDF, markdown, image, or text) so it renders in Argus Web. Params: path (required — a file, or a directory to register a whole folder shown as a thumbnail gallery in Argus Web; ≤500 files / 2 GiB), title, type, id or cwd. Self-contained files render best; 25 MiB max per file. The path must be inside the calling task's worktree, temp dirs or extra_write paths (no symlinks or hard links). |
artifact_publish |
Publish a single self-contained HTML page (.html/.htm, ≤16 MiB) to claude.ai as a configured account, from the daemon with that account's [artifact_tokens] token. Params: path, account (required), title, update (an existing artifact's link, to publish a new version), id or cwd (required: the calling task). path must be a regular, single-linked file (not a symlink) inside the calling task's worktree, temp dirs or extra_write paths, and outside its sandbox deny-read paths. Returns JSON: status: "published" with url, or status: "unavailable" with the saved task artifact and the available_accounts that can publish. |
artifact_open |
Open a registered artifact of the calling task on the Mac (open), or with reveal: true select it in Finder (open -R). Params: filename (required), reveal, id or cwd. Opening is limited to HTML, PDF, image, Markdown, text, audio and video files with a matching extension; anything else can only be revealed, and directories/bundles are refused. macOS only. |
A Claude session inside an argus worktree sees the mcp__argus__* tool names but not when to reach for them or how they compose. Argus ships that orientation automatically — no install step, nothing to symlink or append:
- Skill bodies —
argus-archive,argus-complete,argus-handoff,argus-recycle,argus-resolve-model,argus-schedule,hera,hera-plan,hera-remind-me,hera-review,hera-review-test-adversary, andhera-spawn-revieware embedded from their sole in-repo sources atinternal/skills/builtin/<name>/SKILL.mdviago:embed. They are materialized into~/.argus/skills/.claude/skills/<name>and reach every spawned Claude backend session via an appended--add-dirflag — a documented exception where Claude Code loads.claude/skills/from an--add-dirroot instead of just granting file access. - Routing content (
internal/routing/builtin/*.md, embedded the same way) — orientation text that points the agent at the skills above, plus theargus-artifactspublish rule (see Artifact home account) — is materialized and injected into every spawned Claude backend session via an appended--append-system-prompt-fileflag.
internal/skills/builtin/ is the sole in-repo source for Argus builtins; none is mirrored under .agents/skills/, which remains available for project-only skills such as update-agent-models. hera-spawn-review resolves configured review and lens instructions by exact ID from the child session's native skill catalog before spawning any finder; a project-scoped same-ID skill remains an intentional override when the backend's catalog selects it.
Both are unconditional across every session kind (coordinator, worker, freelance, plain solo task) and self-gating at read time — each section checks ARGUS_TASK_ID/$PWD sandbox residency (the artifacts section checks ARGUS_ARTIFACT_ACCOUNT), so injecting them into a non-argus spawn is inert. Materialization failure is logged and the launch continues without them rather than blocking. See internal/skills/builtin.go and internal/routing/routing.go.
Codex and opencode get Argus's skills through mechanisms native to each CLI, not the flags above (neither has a --add-dir/--append-system-prompt-file equivalent). Argus-launched Codex sessions use a child-only CODEX_HOME at ~/.local/share/argus/codex-home (or a custom-<hash> subdirectory for a custom CODEX_HOME), where Argus's skill bodies live alongside links to the user's existing Codex config, auth, system skills, and other installed skills; ordinary Codex sessions do not see the Argus skills. Files created only in the overlay remain there. If a later launch finds the same name in the normal Codex home, it preserves the overlay copy under .argus-preserved-* and links the normal entry; newly installed user skills also take precedence over colliding Argus skills. Pi gets the same Argus skill directory through a per-launch --skill flag. OpenCode gets it through child-only OPENCODE_CONFIG_CONTENT, preserving any existing inline config and skill paths; Argus removes its old global skill-path entry on daemon startup. Routing orientation is delivered natively too, never prepended to the prompt: Codex gets a generated AGENTS.md in that overlay (your own global AGENTS.md/AGENTS.override.md content first, never linked or modified), Pi a per-launch --append-system-prompt, and OpenCode an instructions entry in the same inline config. Antigravity (agy) has no per-process hook at all — it reads only ~/.gemini/config — so the daemon registers Argus's MCP server in ~/.gemini/config/mcp_config.json and Argus's skill directory in ~/.gemini/config/skills.json at startup (idempotent, other entries preserved), and Argus writes an always-on rule file, ~/.gemini/config/rules/argus-routing.md (self-gating, so inert outside Argus sessions), at daemon startup when agy is installed and again before each launch. Cursor (cursor-agent) currently gets no routing orientation: it has no per-process or file-based global instruction hook. Oh My Pi (omp) is fully per-launch: Argus's MCP server arrives as a generated Claude-style plugin under ~/.argus/cache/omp-mcp/ passed with --plugin-dir, the skill directory through a --config overlay (skills.customDirectories) under ~/.argus/cache/omp-config/, and routing through --append-system-prompt; omp's own ~/.omp config is never written. See internal/agent/backends.go (the per-backend family registry), internal/agent/nonclaude_context.go, internal/skills.EnsureCodexSkills, internal/inject/opencode.InjectGlobal, and internal/inject/antigravity.InjectGlobal.
All endpoints require auth — Authorization: Bearer <token> header or ?token=<token> query param (the latter is required for EventSource/SSE because browsers cannot set headers on it). The token can be the master token from ~/.argus/api-token or any non-revoked device token.
Every authenticated token has the same permissions except a small master-only denylist: backends CRUD (command templates can run arbitrary code), self-update (/api/source-path, /api/update), daemon restart (/api/daemon/restart), host reveal (/api/tasks/{id}/reveal), and token list/mint/revoke (/api/tokens). Those endpoints return 403 for device tokens; everything else — tasks, projects, schedules, settings, messages, push — accepts any token. Sandbox proposal approve/reject are also master-only. One extra carve-out lives inside PUT /api/settings: the sandbox section is master-only (it governs the host sandbox-exec boundary), while KB/API/UX-defaults are open.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/status |
Running/idle session counts, task counts by status |
GET |
/api/runtime |
Daemon boot id, daemon/installed/supervisor binary hashes, current/stale/unknown state, supervisor compatibility, restart_supported |
POST |
/api/daemon/restart |
Master-only. {"expected_boot_id"} → 202; restarts only the daemon (no build, supervisor untouched); 409 stale boot/pending, 503 unsupported |
GET |
/api/system-metrics |
Host-load snapshot: CPU %, 1/5/15-min load avg, memory + swap, disk usage of the ~/.argus filesystem, Argus process RSS, host uptime, and live agent-session counts. Sampled on a background ticker; each metric carries an availability flag. Also carries host_sleep (status of not_applicable/safe/held/at_risk/unknown, plus message and fix). Polled by the Settings tab. |
GET |
/api/tasks |
List tasks. Filters: ?status=, ?project=, ?archived=1 (or =all). Each task always includes the stored boolean pinned (including false) and carries live (a real input-addressable session exists, any workflow status, false during a pending restart), idle: true (live session waiting for input; an absent session is not idle), failure (null or {"reason","source":"task_result","recorded_at"} from an explicit {"failed":true} Result), and pr_lifecycle (open/merged/closed/unknown from the cached GitHub result; pr_state still collapses merged and closed). |
POST |
/api/tasks |
Create and start a task. JSON {"name", "prompt", "project", "backend?", "model?", "sandbox_override?", "account?"}, OR multipart/form-data with name/prompt/project/backend/model/sandbox_override/account plus files parts (uploaded into <worktree>/.context/, paths appended to the prompt). Per-file 100MB / total 250MB / 20 files cap. sandbox_override is "" (inherit the project/global setting, default) | "enabled" | "disabled" — a per-task override of sandboxing, validated before any worktree is created. account names an account ("default" included; empty = project/global default); an unknown account, or one that can't run an explicitly requested backend, is 400. A non-empty model is validated against the resolved backend (see model_policy on GET /api/backends) before anything is created; a rejected model is 422 {"error","code":"model_not_allowed","backend","requested_model","allowed_models"}. |
GET |
/api/tasks/{id} |
Get single task detail (includes pinned as an always-present boolean, archived, worktree_path, prompt, live, idle, failure, pr_lifecycle) |
GET PUT DELETE |
/api/tasks/{id}/pr |
Manual PR link. GET returns {"source":"manual|automatic","url","state","lifecycle","checked_at"} from stored state. PUT {"url":"https://github.com/o/r/pull/7"} links a PR (github.com pull URLs only, else 400) and returns state/lifecycle unknown until verified; DELETE restores branch discovery; an absent link leaves automatic cache unchanged and emits nothing. Manual polling skips archived/missing tasks, follows dormancy cadence, and trusts cache only for the linked URL. Branch polling never replaces a manual link or sends merge notices for a refused result. |
POST |
/api/tasks/{id}/stop |
Stop a running agent (moves to in_review) |
POST |
/api/tasks/{id}/resume |
Resume a stopped agent; recover fresh with the original prompt if its Claude transcript is missing. Returns {status,pid,session_id} (plus healed:true on reattach). Optional body {"force_revive":bool}: a closed-out Hera worker/freelance task returns 409 hera_closeout (can_force_revive:true) unless forced; busy is task_running, mid-restart is agent_restarting for every task status, before marker changes. A definite launch failure returns 500 start_failed and restores unchanged close-out state; an ambiguous launch returns 503 start_ambiguous with markers cleared. Check task runtime before retrying. Bodies must be JSON objects of at most 1024 bytes with no trailing data (400 invalid_body); an empty body means false. |
POST |
/api/tasks/{id}/account |
Move a Claude or Codex task to another account: `{"account":" |
POST |
/api/tasks/{id}/restart |
Re-spawn a finished session in the same worktree; resume a saved conversation or recover fresh if missing. Returns {status,pid,session_id} (plus healed:true on reattach). Same force_revive / hera_closeout contract as resume. |
DELETE |
/api/tasks/{id} |
Delete a task |
POST |
/api/tasks/bulk-delete |
Tracked bulk delete: {"task_ids":[...≤100 unique],"idempotency_key","preconditions":{"allow_dirty","require_pr_closed"}} → 202 {"job_id","status"}. Same key+body returns the same job; changed body is 409 idempotency_conflict; 400 for empty/oversized/duplicate ids. Skips coordinators (hera_cascade_required), pending tasks with a busy creation fence (task_creation_in_progress), dirty worktrees unless allow_dirty, and open PRs only when require_pr_closed is true. |
GET |
/api/task-delete-jobs/{job_id} |
Job status (queued/running/complete), completed, total and per-task results (state, record_deleted, worktree/local_branch/remote_branch outcome, error); 404 unknown job. Jobs are persisted and resume after a daemon restart. |
POST |
/api/tasks/{id}/archive |
Archive (hidden from default list) |
POST |
/api/tasks/{id}/unarchive |
Restore from archive |
POST |
/api/tasks/{id}/rename |
{"name":"..."} |
POST |
/api/tasks/{id}/pin |
{"pinned":true|false} → {"task_id","pinned","archived"}. Targeted write of the pin/archive flags only (pinning restores an archived task); 400 on bad/unknown/oversized body, 404 unknown task. Emits task.updated. |
GET |
/api/tasks/{id}/runtime |
→ {"task_id","runtime":{backend,model,sandbox_override,prompt},"live","busy","available_apply","revision"} (also ETag). available_apply is ["next_resume","restart"] for a stopped task, ["restart"] live, [] while a restart is pending. Resident row returns 404. |
PATCH |
/api/tasks/{id}/runtime |
{"model"?,"backend"?,"sandbox_override"?,"prompt"?,"apply":"next_resume"|"restart","force"?,"force_revive"?} + required If-Match: <revision> → {"task_id","runtime","apply","changed_fields","revision","session_id","live","started"}. restart is a controlled stop + resume (never a hot switch). 400 bad body/field/apply, 403 sandbox_override without master, 404, 409 for a resident row or (session_live, task_busy without force, agent_restarting, conversation_incompatible, resume_unavailable, hera_closeout), 412 stale revision, 428 missing If-Match. Persisted-but-restart-failed: 500 {"error":"restart failed","code":"restart_failed"|"restart_unknown","settings_applied","started":false,"live","rolled_back","revision"}. Emits task.updated and task.runtime_changed. |
POST |
/api/tasks/{id}/fork |
Clone to a new task. Body: {"name?", "prompt?", "project?"} |
POST |
/api/tasks/{id}/status |
Set status. Body: {"status":"in_review"|"complete"|"pending"|"in_progress"} |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/tasks/{id}/output |
Recent output (text). Optional ?bytes=, ?clean=1 |
GET |
/api/tasks/{id}/links |
Extract http/https URLs from terminal output. Returns {"links":[{"label","url"}]}. Powers the PWA's "Open link" overflow item. |
POST |
/api/tasks/{id}/bridge/events |
Ingest argus-bridge events with type, seq, boot, ts, session_id and generation. Returns {accepted,received}; duplicates, retired boots and superseded launches are dropped. Live measurements update context and cumulative cost. Heartbeats include busy/dialog snapshots for daemon recovery. |
GET |
/api/tasks/{id}/bridge |
The task's latest bridge-reported state (busy, needs-input, session id, model, context/cost/rate limits, live, suggestion); 404 until the mod has reported. suggestion is null or {text,session_id,generation,recorded_at}, the engine's suggested next prompt (≤4 KiB plain text, never written to the session); null when none, unsupported, cleared by a new turn/session change, or the bridge is not live. Appearing/clearing emits task.suggestion_changed ({session_id,available,recorded_at}, no text). |
GET |
/api/tasks/{id}/context |
Backend-neutral context usage {task_id,session_id,provider,live,measured_at,source,context:{used_tokens,window_tokens,percent,categories}}; Claude fills it from the bridge (categories: messages, memory, tools, agents, skills, system, compaction; unmeasured = null, measured zero = 0). context is null when nothing was measured or the measurement belongs to a replaced launch; live tracks current bridge authority, not measurement age. Fresh heartbeats keep unchanged context live; clients can show age from measured_at. A session, process or account launch replacement invalidates the old context; an inactive bridge returns recorded data with live=false. Codex fills it from its own rollout (provider openai, source codex_rollout, latest-call context, categories all null, window/percent null when unrecorded). Live measurements emit task.context_changed ({session_id,measured_at}, no counts). |
GET |
/api/tasks/{id}/usage |
Session and per-turn token counts {task_id,session_id,provider,measured_at,source,totals:{input_tokens,cached_input_tokens,output_tokens},turns,next_cursor}; input_tokens includes cached, turns newest first, ?session_id= (404 if not recorded for the task), ?limit= (default 50, max 200), ?before= cursor (400 if invalid). Defaults to the current session, with null totals until measured. Codex verifies rollout identity and launch ownership (openai, codex_rollout); other backends return totals=null, turns=[]; unreported counters are null, measured zero is 0. Changed GET or watcher observations emit task.usage_changed ({session_id,turn_id,measured_at}, no counts). |
GET |
/api/tasks/{id}/bridge/ui |
Read-only effective UI settings, live bridge/context, Hera bindings/status/budget (coordinators add workers working/blocked roll-up plus separate board_unread/board_subject) and unread Hera/task summaries. Human dismissal mail is labelled Aaron (board). Does not acknowledge items/mail or claim deliveries. |
POST |
/api/tasks/{id}/bridge/draft |
Hand a freshly booted mod the composer draft its predecessor reported ({generation,boot}); returns {draft:null} or {draft:{text,cursor}}, once, only to a different boot, within two minutes, and while ui.rerender_keep_place is on. |
POST |
/api/tasks/{id}/bridge/deliveries |
Pull the active delivery using {generation,boot}; returns {delivery:null} or a fenced token, text and delivery ID. |
POST |
/api/tasks/{id}/bridge/deliveries/authorize |
Authorize one idle submission using {generation,boot,token}; returns {authorized}. Cancelled, stale or superseded claims are rejected. |
POST |
/api/tasks/{id}/bridge/deliveries/ack |
Acknowledge successful prompt submission with the claim identity/token; returns {acked} without marking durable inbox mail read. |
POST |
/api/tasks/{id}/bridge/budget |
Queue a coordinator's throttled nudge with {size,text} when bridge delivery is live; returns {enabled}. The Stop hook owns budget/increment/pending gates and retains legacy blocking when unavailable. |
GET |
/api/bridge/states |
Task-id-keyed live bridge snapshot, matched to running Claude launch generations. Used by the remote TUI; an empty object means scraper fallback. Local TUI uses the equivalent Daemon.BridgeStates RPC. |
GET |
/api/bridge/shadow |
Shadow-mode counters comparing screen-reading verdicts with the bridge, per signal (idle, needs_input, and needs_input_permission kept apart because a permission ask can span the tool's execution): agree, disagree, settling, no_bridge, plus the settle window and open disagreements. |
POST |
/api/tasks/{id}/input |
Send raw bytes to PTY stdin Archived task: 409 task_archived; unknown task: 404 task_not_found; no session: 404 no_active_session. |
POST |
/api/tasks/{id}/upload |
Upload files mid-session. multipart/form-data with files parts; saved to <worktree>/.context/<name> (auto-suffixed on collision) and returns {paths:[]}. Same 100MB/250MB/20-file caps as create. |
GET |
/api/tasks/{id}/stream |
SSE stream of live output (base64-encoded chunks) |
GET |
/api/tasks/{id}/size |
Current PTY dimensions: {cols, rows} |
POST |
/api/tasks/{id}/resize |
Resize PTY: {"cols":N,"rows":M}. Optional scrolled_up / composer_dirty report client-side keep-place state. Returns {cols,rows,rerendered,deferred}; deferred ("scrolled"/"draft") means the kick was held and the client re-POSTs the same size. A ≥15-column drift can queue kill+resume; a Claude session without its recorded transcript defers and remains eligible for retry. The SPA reattaches automatically. |
GET |
/api/tasks/{id}/claude-sessions |
List a Claude-backed task's available Claude Code sessions ({"sessions":[{"id","title","branch","pr_ref","mod_time","size_bytes"}],"current_session_id"}, newest first). 400 for a Codex/Pi/Opencode/Antigravity/Cursor/Omp-backed task. REST mirror of the TUI's ctrl+r session switcher. Optional ?check_session_id=<id> returns {available} using exact transcript lookup without parsing metadata. |
POST |
/api/tasks/{id}/claude-session |
Switch a Claude-backed task to a different Claude session: {"session_id":"..."}. {"status":"unchanged"} if it's already the current session, otherwise stops/restarts (or starts fresh) resuming it and returns {"status":"switched","pid":N}. |
POST |
/api/sessions/stop-all |
Stop every running session |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/tasks/{id}/git/status |
git status output + branch diff for the task's worktree, plus comparison, per-file files (path, status, additions/deletions null unless kind is text, kind, optional original_path), git_revision (opaque), stale, and watch_degraded (polling fallback); 503 git_status_unavailable on a cold-read timeout |
POST |
/api/tasks/{id}/git/discard |
Restore one file to HEAD: {path, scope:"local", delete_untracked}; 400 invalid_path, 409 untracked_requires_delete/rename_requires_explicit_paths/conflict_state/no_changes/task_archived; publishes worktree.changed |
GET |
/api/tasks/{id}/git/diff?path=<file> |
Unified file diff and comparison. Untracked capture is capped at 16 MiB. |
GET |
/api/tasks/{id}/files?dir=<rel> |
Worktree file listing |
POST |
/api/tasks/{id}/reveal |
Master-only. Reveal the worktree ({}) or one contained file ({"path":"rel"}) in Finder on the daemon host; 400 invalid_path, 404, 501 unsupported_host, 500 reveal_failed |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/tasks/{id}/artifacts |
List artifacts the agent registered via artifact_register (name, title, type, size) |
GET |
/api/tasks/{id}/artifacts/{filename...} |
Serve one artifact's raw bytes (filename may be a nested folder/x.png; ?thumb=1 returns a ≤320px JPEG thumbnail for images/video, 404 if none). Scoped to the registered manifest set (no path traversal); HTML served in a sandbox. |
POST |
/api/artifacts/publish |
Publish an HTML page to claude.ai as a configured account (what argus artifact publish calls). Multipart form: account (required), title, update (existing artifact link), file (the page, ≤16 MiB). 200 with {"status":"published","url":…} or {"status":"unavailable","available_accounts":[…]}; 400 invalid page / no [artifact_tokens] entry; 502 endpoint failure (incl. an expired token). |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/maintenance/prune-completed |
Delete all completed tasks — removes worktrees/branches and sweeps orphans (mirrors TUI ctrl+r) |
POST |
/api/maintenance/cleanup-candidates/compute |
Start (or no-op onto) a background merge-safety classification pass over the stuck-task backlog |
GET |
/api/maintenance/cleanup-candidates |
List the cached classification (safe/not-safe + reason) for every stuck-task candidate, plus whether a pass is in flight |
POST |
/api/maintenance/cleanup-candidates/clean |
Master-only. Immediately delete the given scope ("safe" or "all") of the last-computed candidate snapshot — removes worktrees/branches, same guards as prune-completed |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/projects |
List project names |
GET |
/api/projects/full |
List with path, branch, backend, sandbox, profile ("" = default) |
POST |
/api/projects |
Create. Body: {"name", "path", "branch?", "backend?", "sandbox?", "profile?"} (unknown profile 400, config.toml-defined project 409) where sandbox is {"enabled": true|false|null, "deny_read":[], "extra_write":[]} (null = inherit global) |
PUT |
/api/projects/{name} |
Update. Omitted profile preserves the stored one, "" clears it; unknown project 404. Emits config.changed {"resource":"projects","name"} on a real change |
DELETE |
/api/projects/{name} |
Delete |
GET |
/api/backends |
List with command + prompt_flag + model + models + model_policy (configured|curated|freeform), plus family metadata: kind, label, skill_prefix, session_switcher, account_move |
GET |
/api/accounts |
List accounts: bare array of {name, label, claude_config_dir, codex_home, supports: {claude, codex}, is_default, source: {label, claude_config_dir, codex_home} (each database, config_fileorbuiltin), editable, logged_in, email?, org?, plan?, codex_logged_in?, usage?} (default first, then configured accounts by name). Optional ?project= makes is_default mark that project's default. logged_in/email/org/plan are the Claude sign-in; codex_logged_in is omitted when unknown. usage is {five_hour_pct?, five_hour_resets_at?, weekly_pct?, weekly_resets_at?} from the account's live sessions or background probe, omitted when no window has a current reading. Best-effort (3s); credentials are never returned. |
GET |
/api/usage |
Read cache-only provider usage for Settings: Claude's per-account five-hour/weekly windows and Codex's default-account weekly window. Codex reset/probe timestamps may be absent during a valid depleted hold. Never starts a provider probe or sign-in lookup. |
POST |
/api/accounts |
Create a database-owned account. Body: {"name", "label?", "claude_config_dir?", "codex_home?"} (at least one directory, absolute or ~-prefixed, none shared with another account). 201 with the effective account; 400 invalid, 409 duplicate. Registers directory references only: never creates a login or touches files. Master token required. |
PATCH |
/api/accounts/{name} |
Update only the supplied label / claude_config_dir / codex_home (empty string clears one). 200 with the effective account; 404 missing, 409 if config.toml defines it (read-only), 400 for default. Master token required. |
DELETE |
/api/accounts/{name} |
Delete a database-owned account; 204. 409 with {"error", "references": {tasks?, default?, projects?, artifact_projects?, resident?, tiers?}} while any task (including resident and archived tasks), default, project mapping, artifact home, configured resident account or routing tier still names it. Never removes the credential directories. Master token required. |
GET |
/api/account-defaults |
{"global": D, "projects": {name: D}} where D is {explicit, effective, source, editable}: explicit is the stored selection or null, effective the resolved default chain before backend/tier routing, source is database, config_file, inherited (project, from the global/default chain) or builtin. |
PUT |
/api/account-defaults |
Body {"account": "work"} or {"account": null} to clear. Returns the global D. 400 for a missing property or unknown account, 409 if default_account is in config.toml. Existing tasks never move. |
PUT |
/api/projects/{name}/account |
Same body for one project's default. Returns that project's D. 404 unknown project, 409 if config.toml defines the mapping. |
POST |
/api/backends |
Create. Body includes optional model (default --model). Master token required (command templates can run arbitrary code). |
PUT |
/api/backends/{name} |
Update. Master token required. |
DELETE |
/api/backends/{name} |
Delete. Master token required. |
GET |
/api/skills |
Skill autocomplete. Filter: ?project=, ?filter= (case-insensitive substring), ?account= / ?task= (read skills from that account's / task's Claude config dir; unknown → 400) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/hera |
Read-only orchestration roster. Returns {orchestrators:[{id,name,pinned,archived,kanban_status,subtree_cost_usd,bridge_parent_orch_id,bridge_parent_role_id,subtree_needs_input,roles:[…]}], freelance:[…]}. Each role carries kind (coordinator/worker/freelance), status, bound task_id/task_name/task_status, live, ready_to_close, needs_input, per-rate-class token totals (tokens_input/tokens_cache_write_1h/tokens_cache_write_5m/tokens_cache_read/tokens_output), and cost_usd — all omitted (not 0) when never measured. Each role also carries archetype, model_resolution (requested_model, effective_model — null when the CLI picks its default —, effort, source, basis current_config/planned_intent, profile, warning; resolved from current config, not observed launch telemetry) and context (null when unmeasured, else tokens, denominator_tokens, denominator_kind, percent, observed_at always null today). Claude roles accrue via argus coord-hook and the bridge; Codex roles accrue from a daemon sweeper that prices each call in the worker's Codex rollout files (sub-agent threads included) at the rates.toml entry for the model Codex recorded. subtree_cost_usd sums the orchestrator's own roles only, not a recursive walk into nested sub-coordinators (see internal/pricing). bridge_parent_orch_id/bridge_parent_role_id are null for a top-level orchestrator, else the parent orchestrator/role it nests beneath via a worker→coordinator bridge; subtree_needs_input is true when any role in the orchestrator's subtree (including nested sub-orchestrators) needs input. Feeds the webapp's Hera tab. |
GET |
/api/hera/roles/{id}/board |
Coordinator role's open board {items, unread_count}; ?include_resolved=true includes history. Roster roles also expose board_unread. Reads do not acknowledge items. |
PUT |
/api/hera/roles/{role_id}/board/{item_id}/read-state |
{read, expected_updated_at} → {item, unread_count}. Revision-checked (409 revision_conflict leaves a changed item unread; 409 item_resolved); never rewrites updated_at. Errors are {error, code}. |
POST |
/api/hera/roles/{role_id}/board/{item_id}/dismiss |
{expected_updated_at, note?} → {item, unread_count}. Human dismissal reusing the single durable coordinator-notice transaction; identical repeat is a no-op, changed revision is 409. |
GET |
/api/tasks/{id}/hera-messages?limit=&before_id= |
Read-only Hera mail history for every role the task is or was bound to: {messages, next_before_id} (limit 1–500, default 100; newest page by id, returned oldest first). Never marks read or touches delivery. |
POST |
/api/hera/{orchestrators|roles}/{id}/{archive|unarchive|pin|unpin|rename} |
Hera rail mutations (any authenticated token). rename takes {name}; 409 on a name conflict. Archiving a worker also stops its live session. |
POST |
/api/hera/roles/{id}/status, /api/hera/orchestrators/{id}/kanban |
Set role status (idle|working|blocked|done) or top-level orchestrator kanban (active|backlog|blocked|done) via {status}. |
GET/POST |
/api/hera/orchestrators/{id}/nuke-preview, /api/hera/{orchestrators|roles}/{id}/nuke |
Master token. Preview counts, then cascade-nuke (stop sessions, end bindings, stamp rows nuked, archive sole-bound tasks, preserve tasks bound elsewhere; worktree reclaim via the reclaim sweep). Never hard-deletes. |
PUT |
/api/tasks/{id}/hera/tokens |
Hook-facing, not user-facing: argus coord-hook stamps a live hera binding's freshly-scanned raw token totals here every Stop event. The daemon-side handler diffs against the binding's previously-persisted totals, prices only that delta against the current rate table, and adds the result to a persisted running cost_usd_accrued — a rate-table correction never retroactively changes an already-accrued figure. Body: {"tokens_input","tokens_cache_write_1h","tokens_cache_write_5m","tokens_cache_read","tokens_output"}. |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/push/vapid-public-key |
VAPID public key (urlsafe base64) for pushManager.subscribe() |
POST |
/api/push/subscribe |
Register a subscription. Body: {"label","endpoint","keys":{"p256dh","auth"}} |
GET |
/api/push/subscriptions |
List with masked endpoints |
DELETE |
/api/push/subscribe/{id} |
Unsubscribe |
POST |
/api/push/test |
Fan out a test notification to every device |
The daemon polls running sessions every 5s; when a session transitions to idle, every subscription receives a notification (throttled to 1 per task per 5 min). Subscriptions returning 410 Gone are auto-pruned.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/tokens |
List tokens with last-4 + label |
POST |
/api/tokens |
Mint a new device token. Master token required. Body: {"label":"My iPhone"} → {"id","label","token"} (plaintext shown once) |
DELETE |
/api/tokens/{id} |
Revoke. Master token required. |
Tokens are stored as SHA-256 hashes; plaintext is never persisted on the server.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/schedules |
List schedules with next_run_at, last_run_at, last_task_id, last_error. |
POST |
/api/schedules |
Create. Body: {"name","project","prompt","schedule","backend?","model?","enabled"}. model is an optional per-schedule --model override (empty = backend default). Invalid explicit models return the task creation 422 body before saving. Returns the created row. |
PUT |
/api/schedules/{id} |
Partial update. Every field is optional. The effective model is validated before saving, with the same 422 response as creation. |
DELETE |
/api/schedules/{id} |
Remove. Tasks already created by the schedule are not affected. |
POST |
/api/schedules/{id}/run |
Fire the schedule now, regardless of cron timing. Returns {"task_id"}. |
Schedule expressions accept the standard 5-field cron syntax (e.g. 0 9 * * 1-5), descriptors (@hourly, @daily, @weekly, @monthly, @yearly), and intervals (@every 30m).
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/settings |
Returns sandbox / KB / API / Bridge / defaults / display ({"spinner_style":"progress"}) config plus sandbox.available (whether sandbox-exec is on this host). |
PUT |
/api/settings |
Partial update — every section is optional. Body: {"sandbox":{...}, "kb":{...}, "api":{...}, "bridge":{"enabled":true,"delivery":true,"band":true,"toasts":true}, "defaults":{...}, "display":{"spinner_style":"progress|dots|braille|classic"}} (unknown style 400 {"error","code":"invalid_spinner_style"}). The sandbox section is master token required; other sections accept any token. |
GET |
/api/sandbox/proposals |
Agent-submitted sandbox write-path proposals (?status=pending|approved|rejected). Any token (readable by agents via argus_api_get). |
GET |
/api/sandbox/proposals/{id} |
One proposal. |
POST |
/api/sandbox/proposals/{id}/approve |
Master token required. Body {"scope":"project"|"global","paths":[...optional edit]}; appends to extra_write; responds takes_effect:"new_sessions". 409 if already decided. |
POST |
/api/sandbox/proposals/{id}/reject |
Master token required. No config change. |
GET |
/api/logs/{ux|daemon}?bytes=N |
Tail the last N bytes of the log (default 64K, max 1M). Missing files return 200 with empty body. |
Argus can host external programs as plugins — a separate process (on 127.0.0.1) that registers MCP tools, Settings forms, and full-screen views, and consumes a live event stream. This is the substrate for out-of-tree orchestrators and tools driven entirely over these endpoints. Hera now also ships natively in-tree (default — see the Hera section); the plugin substrate remains the path for the external Hera plugin when hera.enabled = false, and for any other third-party orchestrator.
Plugins authenticate with a scope token (X-Argus-Auth: scope:<name>), a third tier alongside master and device tokens. Scope tokens are minted programmatically by the daemon (not over HTTP). A scope token may only register/unregister tools, sections, and views under its own scope — cross-scope access returns 403. Revoking a scope token cascades: every tool, section, and view registered under that scope is dropped. Scope tokens are still blocked from the master-only denylist (token mint/revoke, backends CRUD, self-update, sandbox settings).
MCP tool registration — a plugin exposes tools through Argus's MCP server (port 7742); on invoke, the daemon POSTs {tool, input, context} to the tool's callback_url.
| Method | Endpoint | Token | Description |
|---|---|---|---|
POST |
/api/mcp/tools |
scope:<n> |
Register a tool. Body: {name, description, input_schema, callback_url, auth_header?}. name must start with <scope>_. 100/scope, idle-swept after 10 min. |
DELETE |
/api/mcp/tools/{name} |
master / owner | Unregister a tool |
Plugin views — a plugin hosts a full-screen TUI pane, dialed over WebSocket at callback_url. While active, Argus surrenders all keystrokes to the plugin (binary frames); the plugin sends control frames back (hotkeys, help, release) and receives resize/focus/blur. A double-ctrl+q within 400 ms is the reserved failsafe to force back to Argus.
| Method | Endpoint | Token | Description |
|---|---|---|---|
POST |
/api/plugins/views |
master / scope:<n> |
Register a view. Body: {title, hotkey, callback_url} (ws:// URL). Opened by its hotkey in the TUI. |
GET |
/api/plugins/views |
master / scope:<n> |
List views (scope sees only its own) |
DELETE |
/api/plugins/views/{id} |
master / owner | Delete a view |
Plugin settings sections — a plugin registers a form (fields: bool / int / string / enum) or a live stream section that appears in Argus Settings. On save, the daemon POSTs the {key: value} map to the section's callback_url.
| Method | Endpoint | Token | Description |
|---|---|---|---|
POST |
/api/plugins/settings/sections |
scope:<n> |
Register. Body: {title, type:"form"|"stream", callback_url, auth_header?, fields?} |
GET |
/api/plugins/settings/sections |
any | List all registered sections |
POST |
/api/plugins/settings/sections/{scope}/{title}/submit |
any | Proxy a user's saved values to the plugin's callback |
DELETE |
/api/plugins/settings/sections/{scope}/{title} |
master / owner | Unregister a section |
Event stream — clients (PWA, plugins) subscribe to a live SSE feed of daemon events.
| Method | Endpoint | Token | Description |
|---|---|---|---|
GET |
/api/events/stream?since=<n> |
any | SSE feed of task.*, session.*, message.*, link.*, hera.board_changed, worktree.changed events (`{revision?,reason:filesystem |
task.updated — emitted after a task mutation commits (REST, MCP, TUI and daemon paths alike) as {"id":123,"type":"task.updated","at":"...","task_id":"t","payload":{"changed_fields":["archived","pinned"]}}. changed_fields lists only fields that actually changed, from name, status, archived, pinned, runtime (backend/model/sandbox override/session id), account, result, pr, in that order. It carries no prompt or result content: re-read the task over REST. Existing task.renamed / task.status_changed / task.archived events are unchanged and still fire, so a mutation may produce a specific event plus one task.updated. No-op writes and failed writes emit nothing; hard delete keeps task.deleted. Emission is best-effort after commit, so keep a slow reconciliation fetch.
task.runtime_changed — emitted when PATCH /api/tasks/{id}/runtime settles: {"apply":"next_resume"|"restart","changed_fields":["model","backend","sandbox_override","prompt"],"started":bool}. started:false after a persisted-but-failed restart lets callers reconcile. Never carries prompt text. Not emitted for rejections. Kick errors, failed outcome probes and pending restarts return 500 restart_unknown, retaining the stored settings; confirmed launch failures return restart_failed.
session.runtime_changed — payload {"live":true,"idle":false,"needs_input":false}, published by the daemon's watcher tick whenever any of a task's REST runtime flags changes (including a workflow-status transition that suppresses needs_input). task.failure_changed — payload {"failure":null} or the full {"reason","source","recorded_at"} object, published after a Result write starts, changes or clears an explicit failure. task.pr_changed — payload {"url","state","lifecycle","source"} (lifecycle is open|merged|closed|unknown), published by the PR poller after a changed cache commit and when a manual PR link is set or cleared (source is manual|automatic); consumers ignore unknown keys. Terminal caches without a verified merged or closed lifecycle get one immediate attempt per daemon process, then dormancy retries if unsuccessful, without repeating either merge notice. A stale terminal/open pair projects unknown until refreshed.
task_delete_job.updated — emitted as a bulk-delete job progresses, payload {"job_id","status","completed","total"} (no task_id). GET /api/task-delete-jobs/{job_id} is authoritative.
task.account_changed — emitted once POST /api/tasks/{id}/account has persisted a new account: {"from","to","resumed","started"}. started:false after a persisted-but-failed restart lets callers reconcile. Not emitted for rejections or a rolled-back restart.
The daemon runs as a normal process on the host machine. When the host sleeps, HTTP responses stall, SSE streams disconnect, and push notifications stop firing. PTY sessions pause where they were and resume when the host wakes.
For a clamshell-mode laptop driving an external display:
- Use
caffeinate -is(no-d) or KeepingYouAwake with Allow display sleep enabled — keeps system + idle awake while letting the display sleep. - For a permanent setup on AC power:
sudo pmset -c sleep 0 disablesleep 1 displaysleep 1. - With the remote API enabled,
argus doctor("Host sleep"), TUI Settings > Remote API, the web Settings tab, and the macOS System tab warn when the Mac can idle-sleep. Nothing is shown if the remote API is off. - Sleeping the external display via
pmset displaysleepnow(or a hot corner) is fine; physically disconnecting it will sleep the Mac because the lid is closed.
For secure remote access without exposing ports to the internet:
- Install Tailscale on your machine and phone
- Enable the API in Argus Settings
- Access the dashboard at
http://<tailscale-ip>:7743/from your phone
When the PWA cannot reach the API — daemon stopped, host asleep, or Tailscale off — it flips to an offline screen with the Argus banner and a Tailscale reminder, then auto-reconnects once the daemon is reachable again.
All state (tasks, projects, backends, UI settings, KB index) is persisted in SQLite at ~/.argus/data.sql. Keybindings are the exception — they live in the built-in defaults plus config.toml overrides only (no DB rows).
An optional ~/.argus/config.toml overrides any setting, layered on top of the built-in defaults and the SQLite-backed settings (precedence: defaults < settings menu < config.toml). It's the alacritty-style power-user layer — customize beyond what the settings menu exposes, and keep your config in version control. The file is optional; a missing file changes nothing. Edits are picked up live on the next read.
Behavior notes:
- The file wins over the settings menu — any key present here masks changes you make in Settings. That's intentional.
- Unknown or misspelled keys are silently ignored (the file stays forward-compatible), so check the spelling against the tables below if an override seems to do nothing.
- A malformed file is ignored — logged to
~/.argus/ux.log(or~/.argus/daemon.logwhen the daemon reads it) — and Argus falls back to the defaults + settings-menu values until the file parses again. - Maps merge by key.
[backends.<name>]/[projects.<name>]add a new key or replace an existing one wholesale — a partial entry zeroes the fields you omit (it can blank a project'spath), so define those entries in full.
Every option below is overridable. A
| Key | Type | Default | Description |
|---|---|---|---|
backend |
string | "claude" |
Backend used for a new task when none is chosen in the New Task form. Must name a key under [backends]. |
share_project |
string | "" |
Project preselected in the New Task form when the PWA share target (iOS/Android share sheet) lands a payload. Empty falls back to the currently expanded project folder. |
permission_mode |
string | "bypass-active" |
Permission flags injected into Claude-style backends at launch. One of default, acceptEdits, plan, bypass-allow, bypass-active. |
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
After a supervisor restart kills agent sessions, the daemon resumes the interrupted ones: coordinators first, then agents with no coordinator above them that were mid-turn or parked on a question. Each gets an [argus] note saying what happened and what it was doing; a coordinator also gets the list of its killed workers and is told to hera_revive them. Idle plain tasks and residents stay dormant. false restores the old behavior (tasks flip to in_review, nothing resumes). Read live. |
max_concurrent |
int | 3 |
Resumed sessions that may be starting (not yet idle) at once. |
stagger_seconds |
int | 4 |
Minimum gap between two resume starts. |
expiry_hours |
int | 6 |
Entries older than this are never resumed (for example after a long shutdown). |
| Key | Type | Default | Description |
|---|---|---|---|
stop_finished |
bool | true |
The daemon stops the devbox dev stack (mysqld, redis-server, postgres, caddy, process-compose) left running in the Argus worktree of a complete or archived task with no live session, about a minute after it finishes. A task you deliberately resume is left alone, and a stack you start by hand in a finished task's worktree is stopped too. Set false to never touch them. Read live; no restart. |
| Key | Type | Default | Description |
|---|---|---|---|
agent_commit_signing |
string | "inherit" |
inherit preserves Git signing settings; opt-in unsigned sets commit.gpgsign=false only in agent process trees, so ordinary commits do not wait for a signing prompt. Restart or recycle sessions to pick up changes. Explicit git commit -S or git -c options still win. |
To opt in, add [git] with agent_commit_signing = "unsigned" to ~/.argus/config.toml. Unsigned feature commits have no individual signature; a GitHub squash-merge signature attests the merge. Confirm repository rules permit unsigned feature commits. The setting follows the session across branch switches and leaves global/shared Git configuration unchanged. An upgraded supervisor is required; restart a stale supervisor before restarting/recycling sessions. This is config.toml-only for TUI, web, and macOS launches; Settings controls are the named follow-up agent-git-policy-settings-controls.
Command templates, keyed by name. Seeded with claude, codex, pi, opencode, cursor (cursor-agent --approve-mcps; Argus adds --trust and maps defaults.permission_mode to --force / --plan; MCP is registered in ~/.cursor/mcp.json), antigravity (agy --prompt-interactive; Argus maps defaults.permission_mode to --mode accept-edits / --mode plan / --dangerously-skip-permissions), and omp (omp; Argus maps defaults.permission_mode to --approval-mode write / always-ask / yolo and delivers MCP through a generated --plugin-dir plugin). The OpenCode default uses its full interactive UI with --prompt; Argus adds --auto at launch so permission requests that would ask are approved.
Argus adds --auto when launching OpenCode, including resumed sessions and stored custom OpenCode commands. Explicit OpenCode permission denials still apply.
| Key | Type | Default | Description |
|---|---|---|---|
command |
string | — | Executable plus base flags for the agent CLI (e.g. claude, codex --dangerously-bypass-approvals-and-sandbox). Permission flags come from defaults.permission_mode and are not baked in here. |
prompt_flag |
string | "" |
Flag used to pass the initial prompt to the backend (empty = positional/piped). |
model |
string | "" |
Default model for this backend. Claude, Codex, Pi, Antigravity, and Cursor receive it as --model <value>; OpenCode receives it through the child-only OPENCODE_CONFIG_CONTENT model key because its v2 full TUI rejects the top-level flag. Empty = the CLI's own default. A per-task model overrides it. |
models |
array | [] |
Option list for the new-task model selector for this backend. Empty = built-in list (claude → opus/sonnet/haiku/fable, codex → its current model lineup — churns per Codex release, see agent.KnownModels, others including opencode, antigravity, and cursor → none, so custom… only). A custom… entry always lets you type a model not in the list. |
| Key | Type | Default | Description |
|---|---|---|---|
trust_worktrees |
bool | true |
Trust Argus worktrees in Claude and Codex. Claude seeds the task account's trust before first launch under its config lock (500ms acquisition limit); Codex uses a process-scoped inline-table override on fresh/resumed launches without persisting trust to account config. Failures log and launch continues. config.toml only; applies across TUI, web and macOS. |
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Load the argus-bridge mod into Claude sessions, use current live reports for session state and measurements, and retain scraper comparisons. false restores scraper authority and default-account probing. |
delivery |
bool | true |
Deliver Hera/task notices and throttled coordinator budget nudges as idle plugin-framed turns without editing drafts or blocking Stop. Requires enabled; false restores PTY delivery and the Stop-hook nudge. Read live, with config.toml taking precedence over DB. |
band |
bool | true |
Show unread count and newest sender, coordinator budget %, role status, a coordinator's working/blocked worker roll-up and a bridge-offline flag above the Claude prompt. Hidden when unbound; composes with other mods. Changes apply to running sessions on refresh (at most 15s). Requires enabled. |
toasts |
bool | true |
Toast newly observed Hera/task messages once. Existing unread mail establishes a quiet baseline. Changes apply on refresh in running sessions; requires enabled. |
All four switches are in Settings → Bridge in local and remote TUI mode; config.toml overrides DB values. Web/macOS settings controls are the named follow-up bridge-settings-web-macos-parity. In Claude sessions, /hera-inbox lists unread sender, summary and age without marking mail read; /hera-status shows role, binding, budget and bridge liveness. Both answer locally without a model turn.
Backend/account tiers drive all task creation, including Hera workers. ordered picks the first available tier; headroom prefers the largest known threshold_pct − usage, then unknown tiers if no known tier is available. An explicit or project backend restricts routing to that backend while still allowing account selection. Account precedence is explicit (including nonblank inherited pins) → project_accounts → tier account → default_account → default. A blank parent account permits routing. Coordinators consider only Claude-capable tiers and retain the Claude fallback when none is available. Settings → Backend Tiers supports n add, d remove, K/J reorder, p probe, A account, and e threshold; config.toml-owned tiers remain read-only.
Choose the reserved backend dynamic as the global default, a project backend, or a task backend to route across all configured tiers. The task stores the concrete backend selected at creation and keeps using it on later launches. For example, use Claude below 80% weekly usage, then Codex below 80%, then Pi:
[defaults]
backend = "dynamic"
[backend_routing]
strategy = "ordered"
[[backend_routing.tier]]
backend = "claude"
probe = "claude_usage"
threshold_pct = 80
[[backend_routing.tier]]
backend = "codex"
probe = "codex_usage"
threshold_pct = 80
[[backend_routing.tier]]
backend = "pi"
probe = "none"| Key | Type | Default | Description |
|---|---|---|---|
strategy |
string | "ordered" |
ordered picks the first available tier (above). headroom picks the tier with the most room under its threshold (threshold_pct − usage, ties → list order), preferring any tier with a known reading over tiers with no reading (unknown/stale probes and none tiers, taken in list order). An unrecognized value is logged and treated as ordered. config.toml-only, read even when the tiers come from Settings. |
codex_pty_fallback_enabled |
bool | false |
Allow the costed headless codex /status probe when no Codex rollout file has a usable reading. |
Usage probes run at daemon startup and every 5 minutes, and again right before each task spawn (debounced to one round per 30 seconds, 5-second cap) so a burst never routes on a stale snapshot. Claude probes the default account and each distinct configured Claude tier account using its own CLAUDE_CONFIG_DIR, skipping accounts with fresh live bridge measurements. Routing and the TUI cla readout use the fuller valid five-hour/session or seven-day/week window; each expires independently at reset or after one hour without measurement. Failed probes fail open. Codex readings use the weekly (10080-minute) window and stay valid until its reset. Web/macOS tier controls and usage readouts are the named follow-up web-macos-routing-settings-parity.
[[backend_routing.tier]] (repeatable, in resolution order):
| Key | Type | Default | Description |
|---|---|---|---|
backend |
string | — | Must name a key under [backends]; a tier naming an unknown backend is skipped, never a load error. |
account |
string | empty | Optional named identity (e.g. personal); omitted uses existing account defaults. Invalid or backend-incompatible accounts are skipped with warnings. |
probe |
string | — | One of claude_usage, codex_usage, or none (uncapped — e.g. a local pi/ollama backend). An unrecognized value is skipped, not an error. |
threshold_pct |
int | 0 |
Usage percentage at/above which this tier is treated as capped-out and resolution falls through to the next tier. Ignored for none. |
[[backend_routing.tier]]
backend = "claude"
probe = "claude_usage"
threshold_pct = 90
[[backend_routing.tier]]
backend = "claude"
account = "personal"
probe = "claude_usage"
threshold_pct = 90
[[backend_routing.tier]]
backend = "codex"
probe = "codex_usage"
threshold_pct = 90
[[backend_routing.tier]]
backend = "pi"
probe = "none"Named work / personal accounts for Claude Code and Codex. How resolution, first login, and inheritance work: Accounts. Editable in Settings → Accounts (everything except inherit); config.toml entries override Settings per name.
| Key | Type | Default | Description |
|---|---|---|---|
[accounts.<name>] claude_config_dir |
string | — | Absolute or ~-prefixed path of the account's Claude config dir (CLAUDE_CONFIG_DIR, created 0700). Required to run Claude backends. At least one of claude_config_dir / codex_home is required. default is reserved. |
[accounts.<name>] codex_home |
string | — | Absolute or ~-prefixed path of the account's Codex home (CODEX_HOME, created 0700). Required to run Codex backends. |
[accounts.<name>] label |
string | <name> |
Display label in the account selector. |
[accounts.<name>] inherit |
array | ["CLAUDE.md","skills","commands","agents"] |
Entries symlinked from ~/.claude into the Claude config dir. settings.json is copied once instead (add it here to symlink it). .credentials*, .claude.json, projects, plugins are never linked. An empty list links nothing. |
default_account |
string | "" |
Account used when the task, project, and routed tier name none. Empty = default. |
project_accounts |
table | {} |
Top-level map of project name to default account, e.g. [project_accounts] acme-api = "work". Recommended for every work repo. |
project_artifact_accounts |
table | {} |
Top-level map of project name to its artifact home account, e.g. [project_artifact_accounts] history = "personal". Must be default or an account with claude_config_dir. Falls back to project_accounts; never to default_account. config.toml only. |
artifact_tokens |
table | {} |
Top-level map of account name to the secret source of its claude setup-token token (op://, keychain:// or env://; a literal token is rejected), e.g. [artifact_tokens] personal = "op://claude/shell-env/CLAUDE_SETUP_TOKEN_PERSONAL". Read live and resolved inside the daemon when publishing artifacts; only rotating a secret the daemon has already resolved needs a daemon restart. config.toml only. |
Registered repos, keyed by name. The DB projects table is the primary source; entries here override it.
| Key | Type | Default | Description |
|---|---|---|---|
path |
string | — | Absolute path to the git repository. |
branch |
string | — | Base branch new worktrees fork from. |
backend |
string | — | Per-project backend override; falls back to defaults.backend. |
profile |
string | "" |
The diligence profile bound to this project, by name. Only the name is stored; the body lives on disk. Empty resolves the default profile. |
[projects.<name>.sandbox] — per-project sandbox overrides. These untagged fields match by lowercased Go name, not snake_case (denyread, not deny_read):
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | inherit | Override the global sandbox on/off for this project (omit to inherit sandbox.enabled). |
denyread |
[]string | [] |
Extra paths appended to the global deny-read list for this project. |
extrawrite |
[]string | [] |
Extra writable paths appended to the global list. |
allowappleevents |
[]string | [] |
Extra AppleEvent destination bundle IDs allowed for this project. |
[projects.<name>.cache_dirs] — per-project overrides/additions to [cache_dirs] below. A key here wins over the same key at the global level; any other key is merged in alongside it.
| Key | Type | Default | Description |
|---|---|---|---|
coordinator_board |
bool | true |
Automatically land on unread human board at coordinator selection and show a mailbox marker. OFF keeps plan landing and the coordinator marker. Explicit p toggle remains available. |
spinner_style |
string | "progress" |
Spinner animation: progress, dots, braille, or classic. |
default_agent_zoom |
bool | true |
Resting agent-view layout: true opens single-pane/zoomed (side panels collapsed); false opens the 1:3:1 three-pane layout. Ctrl+Z toggles at runtime. |
tasks_rail |
bool | false |
Keep a 34-column Tasks rail beside the selected agent; Appearance → Persistent task rail applies live. |
cross_tab_arrows |
bool | false |
Opt-in. One continuous Cmd+Left/Cmd+Right chain: Resident rail ⇄ resident conversation ⇄ worker pane (when shown) ⇄ Tasks list / agent view (unzoomed or zoomed) ⇄ Projects rail ⇄ coordinator pane ⇄ agent pane ⇄ Settings left pane ⇄ Settings right pane. Entering from the left lands on the leftmost pane, from the right on the rightmost. Never fires while a modal, filter / inline edit has focus, or while a Projects or resident pane is fullscreen. |
theme |
string | "default" |
default exists today and nothing reads this yet — reserved for a future theming layer. |
show_elapsed |
bool | true |
|
show_icons |
bool | true |
|
cleanup_worktrees |
bool | true |
Remap argus's own keys, alacritty-style. Bindings are context-scoped: each
[keybindings.<context>] table maps an action id to a keyspec, layered on top of
the built-in defaults (only the entries you set change). Edits are picked up live
— no restart. Contexts: global, tasklist, agent, filepanel, diff,
settings, hera_rail, hera_board.
[keybindings.tasklist]
new = "N" # new task
[keybindings.global]
fork = "ctrl+g" # fork task (the ctrl-shortcuts live in `global`)
[keybindings.agent]
session = "ctrl+t" # switch Claude session
[keybindings.hera_rail]
spawn_worker = "W"Keyspec grammar: a single printable rune (n, ?, /, J), a named key
(enter, esc, tab, space, up/down/left/right, pgup/pgdn,
home/end, backspace, delete), ctrl+<letter>, cmd/opt/alt+arrow,
or shift+(arrow/pgup/pgdn/home/end). The action ids are the ones shown
in the ? help overlay (which always reflects your active bindings).
Limits (rejected overrides log a warning and keep the default): structural
keys (enter/esc/tab, the ctrl+c/ctrl+q failsafe, plain arrows) are not
rebindable; agent bindings must carry a modifier (so plain typing still reaches
the agent); two actions can't share a key within one context; and plugin-view
keys stay fully reserved.
macOS sandbox-exec (SBPL) controls for agent processes.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Wrap agent processes in a per-session SBPL profile. |
deny_read |
[]string | [] |
Paths denied read access, on top of the always-denied ~/.gnupg, ~/.aws, ~/.kube, ~/.config/gcloud. |
extra_write |
[]string | [] |
Additional writable paths. |
allow_apple_events |
[]string | [] |
CFBundleIdentifiers allowed as AppleEvent destinations (e.g. com.apple.iChat) — required to script Messages/Finder from a sandboxed agent. |
Shared build/tool cache directories exported to every spawned agent — the opt-in, project-configurable generalization of the GOCACHE/PLAYWRIGHT_BROWSERS_PATH redirect argus always forces (see Sandbox note above, and agent.BuildCmd). Each key is a TARGET environment-variable name; each value is a subdirectory created under ~/.argus/cache/ and shared across every worktree of every task — instead of a multi-GB toolchain (an Android SDK install, a CocoaPods Specs repo clone, a Yarn/npm cache, ...) getting re-provisioned from scratch in every disposable worktree. Holds directory paths only — never a secret; see [[backends]].env_vars / [secrets] for credential injection instead.
[cache_dirs]
ANDROID_SDK_ROOT = "android-sdk"
GRADLE_USER_HOME = "gradle"[projects.<name>.cache_dirs] merges on top per-project — a shared key here wins, and any new key is added:
[projects.myapp.cache_dirs]
ANDROID_SDK_ROOT = "myapp-android-sdk" # overrides the shared key above
COCOAPODS_REPOS_DIR = "myapp-pods" # project-onlyAn entry whose target is empty or contains =, or whose subdirectory is absolute or escapes the cache root via .., is skipped (logged, not fatal) rather than exported.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Run the knowledge-base MCP server. |
http_port |
int | 7742 |
KB server port. |
metis_vault_path |
string | iCloud Metis vault | Obsidian vault indexed for the KB. |
Full enable/verify walkthrough: docs/knowledge-base.md.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Run the HTTP REST API + PWA for remote control. |
http_port |
int | 7743 |
API port (binds 127.0.0.1 + the Tailscale IP only — never 0.0.0.0). |
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable native Hera — serves the hera_* MCP tools in-process, backed by the hera_* tables in data.sql. Set false to serve the external Hera plugin's tools instead (its ~/.hera state is independent; no migration is performed). The TUI's second tab is always the native Hera view regardless of this flag. |
coordinator_context_budget |
int | 300000 |
Context-size threshold (see Context-budget Stop hook) past which argus coord-hook blocks a coordinator's Stop event with a reach-a-seam recycle nudge. |
coordinator_nudge_increment |
int | 50000 |
Context-size growth past the last-fired size (see Context-budget Stop hook) before argus coord-hook's over-budget nudge is allowed to re-fire. |
worker_context_window |
int | 1000000 |
Reference context window a worker/freelance role's context_size is divided by for the Projects Tab rail's context-pressure indicator percentage. Deliberately separate from coordinator_context_budget, which is a coordinator recycle-nudge policy threshold, not a context window size. |
The always-on resident agent: one persistent Claude session in its own tab, toggled with ctrl+\. Off by default; choosing a name is the act of opting in. Needs api.enabled (its reply, budget and attention signals run through the REST API), kb.enabled and an existing kb.metis_vault_path (its memory lives in the vault); Settings shows whichever is missing. Claude backend only.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Run the resident. Refused (and nothing persisted from Settings) until name is valid and the KB prerequisite holds. Read live by the daemon's ensure-running loop; a manually stopped resident is restarted while this is true. |
name |
string | "" |
Display name, 1–12 printable characters. Shown on the tab and rail box. No default. |
worker_accounts |
table | {} (all eligible enabled) |
Map account names to booleans; absent accounts are enabled. Local Settings → Resident lists eligible Claude accounts only. Per-key config.toml overrides are read-only. Disabled accounts affect new workers only; running workers retain their account. Web/macOS controls: resident-worker-account-controls-parity. |
max_workers |
int | 8 |
Maximum live worker bindings in the standing resident team; ended/deleted/graduated workers free a slot. Below 1 uses 8. Settings edits are local; config.toml overrides show a read-only marker. |
context_budget |
int | coordinator budget | context_size at which the resident is nudged to recycle itself. The Stop hook never forces a recycle. |
memory_dir |
string | "resident" |
Vault-relative working-memory folder. Independent of name, so renaming never moves memory. |
model |
string | "" |
Optional model override; empty uses the resolved default. |
account |
string | "" |
Optional account override; empty uses the default account. |
allow_tools |
list | ordinary work, reads and drafts | Shipped built-ins (Bash, file edits, web, skills, agents), Argus tools subject to the structural Hera tiers and task_message_send confirmation, and reviewed calendar/mail/Slack/Notion/Linear/Granola/Keystone/Figma/Sketch/Claude Docs/artifact reads and drafts. |
ask_tools |
list | outward actions and graduation | hera_graduate_worker is the only ask-tier Hera tool. Other tools that require confirmation: calendar create/update/delete/respond, Gmail send/reply/forward, Superhuman send, Slack canvas/list creation. |
deny_tools |
list | Slack sends and team structure changes | Hera bootstrap/join/move/rebind, plan tools and block are denied. Other tools refused outright: slack_send_message, slack_schedule_message. Deny always wins. |
The resident always runs in --permission-mode default (any bypass flag is stripped from its backend command and bypass mode is disabled in its inline settings; the backend command may otherwise carry only --model, --effort, --fallback-model and --verbose, and any other flag, such as --allowedTools, --plugin-dir or --settings, makes the resident refuse to start with an error naming it; the executable may start with ~/ and values may be simple single-quoted, e.g. --model 'opus[1m]', but double quotes, $VARS, shell operators and unquoted [/] globs are refused), with the three tool lists delivered as inline --settings rules, so any tool in none of them prompts. The resident ignores your user-level Claude settings (it launches with --setting-sources project): only its own allow list runs unprompted, so a wildcard allow rule in your ~/.claude/settings.json cannot widen it. Your user deny and ask rules are added to its lists (never your allow rules), and your settings model and effortLevel are carried over when [resident] model and the backend set none. Your settings sandbox block is carried over by allowlist (only enabled, failIfUnavailable, network.deniedDomains, filesystem.denyRead and filesystem.denyWrite; everything else is dropped), with Bash auto-approve and unsandboxed-command escapes forced off; enabled is not carried when the resident itself runs under Argus's sandbox. User hooks, plugins and enabledPlugins do not apply to it (the argus coord-hook Stop hook is supplied inline instead; skipped user hooks are logged), but your account's CLAUDE.md and user skills/ are still loaded for it (@path imports inside that CLAUDE.md are not expanded, since it is appended as plain text). Your settings env block (telemetry, experimental flags and similar) does not apply to the resident. Claude.ai-synced skills and plugin skills are not. Every write/send/delete/modify tool of the outward connectors (Notion, Gmail, Superhuman, Slack, Linear, Google Calendar, Granola, Keystone, Figma, Sketch, Claude Docs, artifacts) defaults to the ask list, except draft creation/update; Slack send and schedule are denied. The policy uses concrete connector grants: unknown tools (including new reads) prompt until reviewed; no global MCP allow wildcard exists. Deny beats ask, and ask beats allow. Built-in Bash remains sandbox-bound but connector rules cannot inspect its outward intent. After installing this policy (spawn surface 46), restart the supervisor and daemon, then restart the resident so its inline settings are rebuilt; a daemon-only bounce preserves the old session. The Settings screen edits enabled, name, context_budget, max_workers, memory_dir, model, account and eligible worker-account toggles; the three tool lists are config.toml only. A [resident] value in config.toml overrides the same value edited in Settings, with no warning (standard precedence: defaults < Settings < config.toml).
Resident workers use bypass-active mode and disallow AskUserQuestion, EnterPlanMode, and ExitPlanMode only while wrapped in the Argus sandbox. Launch, resume, kick and recycle refuse when confinement is unavailable. Questions go through hera_send(status=blocked); the resident receives one prompt-backstop message per blocked episode. Codex 0.167.0 failed the unattended verification spike and is ineligible. Restart the supervisor after installing this behavior (spawn surface 43, stream surface 17).
Disabling the resident leaves its workers running. Settings → Resident shows how many worker sessions remain; recycle, crash recovery and daemon bounce preserve the standing team and unread Hera messages.
Selects the single active to-do-list backend (todo_* MCP tools). Also configurable live from Settings → To-Do List — no restart needed either way. Requires [kb] enabled = true — the MCP server itself only starts when KB is enabled (see [kb] above), so a todo.backend selection with KB off persists but produces no visible todo_* tools until KB is turned on too.
| Key | Type | Default | Description |
|---|---|---|---|
backend |
string | "" |
Active backend name. Empty disables the todo_* tools entirely. Only one backend is ever active — setting this replaces, never adds. Today's only registered backend: things3. |
Configures the things3 backend (macOS-only — drives the Things 3 app via AppleScript). No-op when todo.backend isn't "things3".
| Key | Type | Default | Description |
|---|---|---|---|
project |
string | "" |
Things 3 project new items are filed under, and todo_list reads from. Empty uses the Inbox. |
tag |
string | "" |
Tag applied to every item Argus creates. Empty applies no tag. |
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Drive agent PTYs through the out-of-process session-supervisor so the daemon can bounce without interrupting agents. Set false to roll back to the legacy in-process runner (daemon owns the PTYs); retained one release as a supported fallback. config.toml wins over the DB. |
| Key | Type | Default | Description |
|---|---|---|---|
source_path |
string | "" |
Local clone of the Argus repo used by the self-update (go install) flow. |
[defaults]
backend = "claude"
permission_mode = "bypass-active"
[ui]
spinner_style = "braille"
default_agent_zoom = true
tasks_rail = false
cross_tab_arrows = false
# Maps merge by key; an existing key is replaced wholesale, so list every
# field you want to keep.
[backends.claude]
command = "claude"
prompt_flag = ""
[projects.argus]
path = "/Users/me/code/argus"
branch = "master"
[projects.argus.sandbox]
enabled = true
denyread = ["~/.ssh"] # note: lowercased, not deny_read
[sandbox]
enabled = false
deny_read = ["~/.gnupg", "~/.aws"]
[api]
enabled = true
http_port = 7743





