@saadnvd1/agent-os 0.3.539 → 0.3.542
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1994
- package/components/SessionList/SessionList.types.ts +1 -0
- package/components/SessionList/SessionShelves.tsx +8 -1
- package/components/SessionList/SidebarBody.tsx +1 -0
- package/components/SessionList/index.tsx +2 -1
- package/lib/db/queries.ts +1 -1
- package/lib/orchestrator/events.ts +1 -0
- package/lib/recipes/settle.ts +1 -1
- package/lib/security/auth.ts +1 -1
- package/lib/sidebar/machines.ts +7 -1
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/shelves.ts +38 -4
- package/lib/status/collect.ts +5 -1
- package/package.json +1 -1
- package/scripts/docs-links.test.ts +160 -0
package/README.md
CHANGED
|
@@ -12,132 +12,30 @@ and check on all of it from your phone.
|
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
### Each kind of work on the right model
|
|
37
|
-
|
|
38
|
-
Planning, gate reviews and the orchestrator run on Opus, implementation and
|
|
39
|
-
scope checks on Sonnet, titles and summaries on Haiku, each editable in
|
|
40
|
-
Settings → Models and per workspace. A task plans on one model and
|
|
41
|
-
implements on another in the same conversation, can be resumed on any
|
|
42
|
-
model, and can run on another provider (DeepSeek, GLM, any
|
|
43
|
-
Anthropic-compatible endpoint) whose key stays a reference.
|
|
44
|
-
**Usage → Compare** shows Claude-only and routed tasks side by side.
|
|
45
|
-
[More](#model-routing)
|
|
46
|
-
|
|
47
|
-

|
|
48
|
-
|
|
49
|
-

|
|
50
|
-
|
|
51
|
-
### Nothing starts until you send
|
|
52
|
-
|
|
53
|
-
⌥N opens a draft in the project you're in. Project, machine, agent, worktree
|
|
54
|
-
and base branch are chips on it, model and access sit under the composer, and
|
|
55
|
-
**Open a PR when done** makes it a task. Unsent drafts wait in the sidebar.
|
|
56
|
-
[More](#new-sessions)
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
### Other machines in the same list
|
|
61
|
-
|
|
62
|
-
Link another machine that runs AgentOS and its sessions show up next to
|
|
63
|
-
yours, with the same rows, menu and address. Their chats and terminals are
|
|
64
|
-
relayed through this server. Pick the machine on a draft to start a session
|
|
65
|
-
or task there, even for a project that only lives here (it finds or clones
|
|
66
|
-
the repository), and move a running terminal task between machines.
|
|
67
|
-
[More](#machines)
|
|
68
|
-
|
|
69
|
-

|
|
70
|
-
|
|
71
|
-
### Built for your phone
|
|
72
|
-
|
|
73
|
-
The whole app works on a phone, not a cut-down view. Every session has its
|
|
74
|
-
own link (`/?session=<id>`) you can bookmark or share between devices, and
|
|
75
|
-
pairing a new device is a QR code. Tailscale gives you an encrypted address
|
|
76
|
-
that works from anywhere. [More](#mobile-access)
|
|
77
|
-
|
|
78
|
-

|
|
79
|
-
|
|
80
|
-
### Agents that talk to each other
|
|
81
|
-
|
|
82
|
-
Sessions find and message each other with `aos peers`, `aos send` and
|
|
83
|
-
`aos inbox`, start tasks and sessions, and schedule check-ins. You see every
|
|
84
|
-
conversation in **Messages** and can join in. [More](#agent-network)
|
|
85
|
-
|
|
86
|
-

|
|
87
|
-
|
|
88
|
-
### And the rest
|
|
89
|
-
|
|
90
|
-
- **Chat or terminal, same conversation.** Streaming replies, steps and
|
|
91
|
-
diffs, plan mode, a queue you can steer, slash commands and skills,
|
|
92
|
-
`@file`, undo, and inline charts and pages. ⌘F (Ctrl+F) finds text
|
|
93
|
-
anywhere in the conversation, tool output and older pages included, and
|
|
94
|
-
opens the step it's in. One click hands the conversation to the agent's
|
|
95
|
-
own terminal. [More](#chat)
|
|
96
|
-
- **Project config.** `agentos.json` (or an existing `.dispatch.json`) gives
|
|
97
|
-
each session its own ports, environment, copied files, cloned
|
|
98
|
-
dependencies, a private Postgres copy, and a "Running this project"
|
|
99
|
-
section in the agent's brief. [More](#project-config-agentosjson)
|
|
100
|
-
- **Schedules.** Start tasks, chats or orchestrator prompts on a timer, or
|
|
101
|
-
check in on a session, with run history. [More](#schedules)
|
|
102
|
-
- **Recipes.** Save a job that comes up again (a build, an audit, a report);
|
|
103
|
-
each run is a fresh task or session that is told what the last run
|
|
104
|
-
reported. [More](#recipes)
|
|
105
|
-
- **Plans in pieces.** A plan splits into several PRs only for a real reason,
|
|
106
|
-
checked by AgentOS; later pieces start by themselves. [More](#plans-in-pieces)
|
|
107
|
-
- **Stacks.** Run a board's cards as stacked PRs that merge bottom-up.
|
|
108
|
-
[More](#stacks)
|
|
109
|
-
- **Machine load.** A gauge in the sidebar, each session's share of CPU and
|
|
110
|
-
memory, and a note to agents before they start a full test suite or build
|
|
111
|
-
on a busy machine. [More](#machine-load)
|
|
112
|
-
- **Status from the screen and the agent.** Every session moves between
|
|
113
|
-
Needs you, Working and Done as it changes, including programs that report
|
|
114
|
-
their own state (OSC 7501). [More](#the-sidebar)
|
|
115
|
-
- **Also:** a ⌘K palette for every session and action, code search, a file
|
|
116
|
-
picker with uploads from a phone, a git panel (diffs, commits, PRs), dev
|
|
117
|
-
servers (Node and Docker), up to four panes side by side, dictation in the
|
|
118
|
-
terminal toolbar, and tmux sessions you started yourself listed under their
|
|
119
|
-
project.
|
|
120
|
-
|
|
121
|
-

|
|
122
|
-
|
|
123
|
-

|
|
124
|
-
|
|
125
|
-
**Guides:** step-by-step how-tos are in [`docs/guides/`](docs/guides/), starting with [installing AgentOS](docs/guides/install.md) and [using it from your phone](docs/guides/phone.md).
|
|
15
|
+
- **An orchestrator for each workspace.** A chat that starts tasks, answers blocked agents, and merges pull requests only through gates. Anything that is yours to decide waits as an ask. [More](docs/reference/orchestrator.md)
|
|
16
|
+
- **Tasks that end in a pull request.** Each gets its own worktree, branch and ports, runs the project's code review, and waits for your sign-off. [More](docs/guides/tasks.md)
|
|
17
|
+
- **Each kind of work on the right model.** Planning, review, implementation and titles each run on their own model, set in Settings → Models. [More](docs/reference/model-routing.md)
|
|
18
|
+
- **Nothing starts until you send.** ⌥N opens a draft with project, machine, agent and branch as chips. [More](docs/guides/sessions.md)
|
|
19
|
+
- **Other machines in the same list.** Link another machine that runs AgentOS and start or move work there. [More](docs/reference/machines.md)
|
|
20
|
+
- **Built for your phone.** The whole app works on a phone; pair it with a QR code. [More](docs/guides/phone.md)
|
|
21
|
+
- **Agents that talk to each other.** Sessions message each other with `aos`, and you see every conversation in Messages. [More](docs/guides/agent-network.md)
|
|
22
|
+
- **Chat or terminal, same conversation.** Streaming replies, steps and diffs, plan mode, slash commands, `@file`, undo. [More](docs/guides/chat.md)
|
|
23
|
+
- **Project config.** `agentos.json` gives each session its own ports, environment, files and a private Postgres copy. [More](docs/reference/project-config.md)
|
|
24
|
+
- **Schedules and recipes.** Start work on a timer, or save a job that comes up again. [More](docs/guides/schedules.md)
|
|
25
|
+
- **Plans in pieces and stacks.** A plan splits into several PRs only for a real reason; a board can run as stacked PRs. [More](docs/guides/tasks.md#plans-in-pieces)
|
|
26
|
+
- **Machine load and status.** A load gauge, each session's share of CPU and memory, and Needs you, Working, In review and Done as it changes. [More](docs/guides/sessions.md)
|
|
27
|
+
- **Workspaces.** Group projects, finish work with Done, and get a log of every merged task. [More](docs/reference/workspaces.md)
|
|
28
|
+
- **LumifyHub (optional).** Boards, cards and docs for a workspace, if you want them. [More](docs/lumifyhub.md)
|
|
29
|
+
- **Also:** a ⌘K palette, code search, a file picker, a git panel, dev servers, up to four panes, and dictation.
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
- [Guides](docs/guides/README.md): [install](docs/guides/install.md), [phone](docs/guides/phone.md), [sessions](docs/guides/sessions.md), [chat](docs/guides/chat.md), [tasks](docs/guides/tasks.md), [schedules](docs/guides/schedules.md) and the [agent network](docs/guides/agent-network.md).
|
|
34
|
+
- [Reference](docs/reference/README.md): how each part works in detail, from the orchestrator's gates to the development workflow.
|
|
126
35
|
|
|
127
36
|
## Install
|
|
128
37
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
You need Node.js 20+, tmux, and at least one agent CLI:
|
|
132
|
-
[Claude Code](https://github.com/anthropics/claude-code),
|
|
133
|
-
[Codex](https://github.com/openai/codex),
|
|
134
|
-
[OpenCode](https://github.com/anomalyco/opencode),
|
|
135
|
-
Pi,
|
|
136
|
-
[Kilo Code CLI](https://kilo.ai/docs/cli),
|
|
137
|
-
[Gemini CLI](https://github.com/google-gemini/gemini-cli),
|
|
138
|
-
[Aider](https://aider.chat/), [Cursor CLI](https://cursor.com/cli), Amp or
|
|
139
|
-
[GitHub Copilot CLI](https://github.com/github/copilot-cli) or
|
|
140
|
-
[Kiro CLI](https://kiro.dev/docs/cli/).
|
|
38
|
+
You need Node.js 20+, tmux, and at least one agent CLI (the installer offers to set up what's missing).
|
|
141
39
|
|
|
142
40
|
```bash
|
|
143
41
|
npm install -g @saadnvd1/agent-os
|
|
@@ -152,1890 +50,23 @@ curl -fsSL https://raw.githubusercontent.com/saadnvd1/agent-os/main/scripts/inst
|
|
|
152
50
|
agent-os start
|
|
153
51
|
```
|
|
154
52
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
git clone https://github.com/saadnvd1/agent-os
|
|
159
|
-
cd agent-os
|
|
160
|
-
npm install
|
|
161
|
-
npm run dev # http://localhost:3011
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Then open AgentOS, add a project from ⌘K, and press ⌥N. To use it from your
|
|
165
|
-
phone, see [Mobile access](#mobile-access).
|
|
166
|
-
|
|
167
|
-
### Windows (WSL2)
|
|
168
|
-
|
|
169
|
-
AgentOS runs on Windows inside WSL 2, as a Linux install. WSL 1 isn't
|
|
170
|
-
supported (`wsl --set-version <distro> 2`). In an Ubuntu or Debian distro,
|
|
171
|
-
run the install script above: it installs Node.js, tmux, ripgrep, lsof, a
|
|
172
|
-
compiler for the native modules, and `wslu`, then prints the WSL next steps.
|
|
173
|
-
The installer and the server both detect WSL the same way (the kernel
|
|
174
|
-
release names Microsoft, or `WSL_DISTRO_NAME` is set).
|
|
175
|
-
|
|
176
|
-
- **Open it from Windows** at `http://localhost:3011`. WSL 2 forwards
|
|
177
|
-
localhost to the distro by default, and the server's loopback address is
|
|
178
|
-
what's forwarded. Use `localhost`, not `127.0.0.1`: passkeys only work on
|
|
179
|
-
localhost or https. With `networkingMode=mirrored` in `.wslconfig`
|
|
180
|
-
(Windows 11), Windows and the distro share localhost directly. From a
|
|
181
|
-
phone or another machine, install Tailscale inside the distro and use
|
|
182
|
-
[Mobile access](#mobile-access) as on Linux.
|
|
183
|
-
- **Keep projects in the Linux filesystem** (`~/code/...`), not on the
|
|
184
|
-
Windows drive (`/mnt/c/...`). Git, npm and file watching are many times
|
|
185
|
-
slower there, and changes made from Windows aren't seen by file watchers
|
|
186
|
-
(inotify doesn't cross the boundary), so the "/" menu only picks up new
|
|
187
|
-
skills after its cache expires. The folder picker warns on `/mnt/<drive>`,
|
|
188
|
-
and the server log lists projects that live there.
|
|
189
|
-
- **Links** open in the Windows browser through `wslview` (from `wslu`), or
|
|
190
|
-
`explorer.exe` when it isn't installed. The server never touches the
|
|
191
|
-
clipboard; copy buttons use the browser's.
|
|
192
|
-
- **Approvals with a passkey** use Windows Hello in the Windows browser,
|
|
193
|
-
registered for `localhost` (a passkey made on the tailnet https address is
|
|
194
|
-
a separate one).
|
|
195
|
-
- **Start at login**: `agent-os enable` writes a systemd user service, which
|
|
196
|
-
needs systemd in the distro. Add this to `/etc/wsl.conf`, then run
|
|
197
|
-
`wsl --shutdown` from Windows and reopen the terminal:
|
|
198
|
-
|
|
199
|
-
```ini
|
|
200
|
-
[boot]
|
|
201
|
-
systemd=true
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
WSL still stops an idle distro, service and all, a few seconds after its
|
|
205
|
-
last terminal closes. To keep AgentOS up without a terminal open, add this
|
|
206
|
-
to `%UserProfile%\.wslconfig` (Windows 11) and start the distro at Windows
|
|
207
|
-
login, for example with a Task Scheduler task running
|
|
208
|
-
`wsl.exe -d <distro> -- sleep infinity`:
|
|
209
|
-
|
|
210
|
-
```ini
|
|
211
|
-
[general]
|
|
212
|
-
instanceIdleTimeout=-1
|
|
213
|
-
[wsl2]
|
|
214
|
-
vmIdleTimeout=-1
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
What's been verified: the WSL detection and the installer's WSL paths have
|
|
218
|
-
unit tests that run on any OS, and the manual [WSL workflow](.github/workflows/wsl.yml)
|
|
219
|
-
ran on a GitHub-hosted Windows runner (Windows Server 2025, Ubuntu 24.04
|
|
220
|
-
under WSL 2): the installer detected WSL 2 and installed what was missing
|
|
221
|
-
through apt, tmux, lsof and node-pty worked, the server started and logged
|
|
222
|
-
its WSL note, and Windows reached it at both `localhost:3011` and
|
|
223
|
-
`127.0.0.1:3011`. Untested: Windows Hello approvals, links opening through
|
|
224
|
-
`wslview`/`explorer.exe`, mirrored networking, `agent-os enable` under
|
|
225
|
-
systemd, the idle-timeout settings above, and Debian or other distros.
|
|
226
|
-
|
|
227
|
-
### First run
|
|
228
|
-
|
|
229
|
-
On a fresh install (no sessions, no projects of your own) the web app opens a
|
|
230
|
-
short setup at `/?welcome`: it shows each agent CLI as ready, needing a
|
|
231
|
-
sign-in, or missing, installs or signs one in from an embedded terminal, adds
|
|
232
|
-
a first project (a folder, a clone or an empty one), then opens a session in
|
|
233
|
-
it. A devices step offers to pair a phone, link another machine and set up
|
|
234
|
-
notifications. Every step after the first can be skipped. It is remembered on the server,
|
|
235
|
-
so a phone you pair later doesn't see it, and an install that already has work
|
|
236
|
-
never does. Reopen it from Settings → Setup (**Run setup again**) or at
|
|
237
|
-
`/welcome`; `AGENTOS_SKIP_WELCOME=1` turns it off. Settings → Setup & health
|
|
238
|
-
also lists what this machine needs (Node, git, tmux, ripgrep, lsof, an agent
|
|
239
|
-
CLI) with the fix for anything missing (`/api/doctor`).
|
|
240
|
-
|
|
241
|
-
### Supported agents
|
|
242
|
-
|
|
243
|
-
| Agent | Chat | Resume | Fork | Auto-approve |
|
|
244
|
-
| -------------- | ---- | ------ | ---- | -------------------------------------------- |
|
|
245
|
-
| Claude Code | ✅ | ✅ | ✅ | `--dangerously-skip-permissions` |
|
|
246
|
-
| Codex | ✅ | ✅ | ✅ | `--dangerously-bypass-approvals-and-sandbox` |
|
|
247
|
-
| OpenCode | ✅ | ✅ | ✅ | `--auto` |
|
|
248
|
-
| Pi | ✅ | ✅ | ✅ | Never asks |
|
|
249
|
-
| Kilo Code | ❌ | ✅ | ✅ | Config file |
|
|
250
|
-
| Gemini CLI | ❌ | ❌ | ❌ | `--yolo` |
|
|
251
|
-
| Aider | ❌ | ❌ | ❌ | `--yes` |
|
|
252
|
-
| Cursor CLI | ❌ | ✅ | ❌ | `--force` |
|
|
253
|
-
| Amp | ❌ | ✅ | ❌ | `--dangerously-allow-all` |
|
|
254
|
-
| Oh My Pi | ❌ | ❌ | ❌ | N/A |
|
|
255
|
-
| GitHub Copilot | ✅ | ✅ | ❌ | `--allow-all` |
|
|
256
|
-
| Kiro CLI | ❌ | ❌ | ❌ | `--trust-all-tools` |
|
|
257
|
-
| Amazon Q (old) | ❌ | ❌ | ❌ | `--trust-all-tools` |
|
|
258
|
-
|
|
259
|
-
Agents without chat run in the terminal. The new-session picker says when an
|
|
260
|
-
agent isn't installed or needs a sign-in first, and what to run.
|
|
261
|
-
|
|
262
|
-
Kiro CLI (and the older Amazon Q CLI, `q`, shown only where it is installed)
|
|
263
|
-
signs in on its own: a session first checks `whoami`, and if you're signed out
|
|
264
|
-
runs `login --use-device-flow`. AgentOS reads the link and one-time code off
|
|
265
|
-
the screen and shows them in a card above the terminal, with a push when it's
|
|
266
|
-
waiting, so you can finish on your phone. No credential passes through
|
|
267
|
-
AgentOS. Identity Center sign-in (including Microsoft or Okta federation) works
|
|
268
|
-
this way; a login through an external identity provider doesn't support the
|
|
269
|
-
device flow, so sign in once on the machine itself, or set `KIRO_API_KEY`.
|
|
270
|
-
|
|
271
|
-
### CLI
|
|
272
|
-
|
|
273
|
-
```bash
|
|
274
|
-
agent-os run # start and open the browser
|
|
275
|
-
agent-os start # start in the background
|
|
276
|
-
agent-os stop # stop the server
|
|
277
|
-
agent-os restart # stop, then start
|
|
278
|
-
agent-os status # show its addresses
|
|
279
|
-
agent-os pair # add a phone, tablet or laptop (prints a QR code)
|
|
280
|
-
agent-os logs # tail the logs
|
|
281
|
-
agent-os update # update to the latest version
|
|
282
|
-
agent-os enable # start at login (disable turns it off)
|
|
283
|
-
agent-os passkeys reset # when every passkey is lost (see Orchestrator)
|
|
284
|
-
agent-os uninstall
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
# Reference
|
|
288
|
-
|
|
289
|
-
Everything below is how each part works, in detail.
|
|
290
|
-
|
|
291
|
-
## Chat
|
|
292
|
-
|
|
293
|
-
Sessions open as a chat by default: streaming replies, tool calls folded into
|
|
294
|
-
expandable steps, inline diffs for edits, a plan checklist, highlighted code
|
|
295
|
-
and mermaid diagrams, subagent cards, image attachments (pick, paste or drag
|
|
296
|
-
them in) and a Stop button, all readable on a phone. Chat drives the same
|
|
297
|
-
agent as the terminal, with your own logins: Claude Code through the Agent
|
|
298
|
-
SDK, Codex through `codex app-server`, OpenCode through its own `opencode
|
|
299
|
-
serve` (loopback only, behind a per-conversation password) and Pi through its
|
|
300
|
-
RPC mode. Pi never asks before a tool, so AgentOS loads a small Pi extension
|
|
301
|
-
that asks the chat as the access setting says. GitHub Copilot runs through
|
|
302
|
-
`copilot --acp` (the Agent Client Protocol): its permission prompts become
|
|
303
|
-
approval cards under the same access setting, and a conversation resumes with
|
|
304
|
-
`session/load` after a restart. It signs in with `copilot login` or a
|
|
305
|
-
`COPILOT_GITHUB_TOKEN`, `GH_TOKEN` or `GITHUB_TOKEN`; it has no plan mode,
|
|
306
|
-
undo or in-chat model list yet, so it runs on its own default model unless one
|
|
307
|
-
is named. The **Chat /
|
|
308
|
-
Terminal** switch in the tab bar hands the same conversation between the two:
|
|
309
|
-
the terminal resumes it (`claude --resume`, `codex resume`, `opencode
|
|
310
|
-
--session`, `pi --session`, `copilot --resume`), and switching back closes the
|
|
311
|
-
terminal so only one side drives it. Each chat conversation runs in its own
|
|
312
|
-
worker process (in tmux, like terminal sessions), so restarting or updating
|
|
313
|
-
AgentOS never cuts off a turn: the server reconnects to running workers when
|
|
314
|
-
it starts. A worker from an older build finishes its turn, and any
|
|
315
|
-
background work (subagents, shells), then hands over at the next turn
|
|
316
|
-
boundary: messages that arrive meanwhile wait in the queue, and a current
|
|
317
|
-
worker resumes the same conversation with this build's tools and sends them.
|
|
318
|
-
An orchestrator fed events mid-turn picks up new tools this way with nobody
|
|
319
|
-
restarting it. A turn whose worker did go away mid-step is resumed
|
|
320
|
-
once, with a note telling the agent to check what it was doing, and a task's
|
|
321
|
-
orchestrator hears about it. History is kept in AgentOS.
|
|
322
|
-
|
|
323
|
-
The composer formats markdown as you type: `code`, **bold**, _italic_, lists,
|
|
324
|
-
`- [ ]` task lists, and ` ```ts ` fenced blocks with syntax highlighting.
|
|
325
|
-
What it sends is the markdown you typed, character for character. Code pasted
|
|
326
|
-
from an editor (or anything multi-line that reads like code) lands in a code
|
|
327
|
-
block, prose stays prose, and a paste of 32 KB or more becomes an attachment
|
|
328
|
-
sent as a fenced block. Cmd/Ctrl+Shift+V pastes as plain text, and the **T**
|
|
329
|
-
button (Cmd/Ctrl+/) turns live formatting off for this browser. Enter sends on
|
|
330
|
-
a keyboard and is a newline on a phone; Cmd/Ctrl+Enter always sends.
|
|
331
|
-
Shift+Enter is always a newline, and three of them at the end of a code block
|
|
332
|
-
leave it.
|
|
333
|
-
|
|
334
|
-
Type `/` in the composer for every slash command and skill the agent knows,
|
|
335
|
-
yours included, filtered as you type. A skill or command you add or edit
|
|
336
|
-
(in `~/.claude/skills`, `~/.claude/commands` or the project's own) shows up
|
|
337
|
-
on its own; the menu's refresh button reloads the list now. Commands such as `/compact`, `/usage`
|
|
338
|
-
and `/context` run in chat and show their output inline; skills the agent
|
|
339
|
-
invokes on its own appear as a chip. `/model` (or the picker under the
|
|
340
|
-
composer) switches the model mid-conversation. `/mcp` lists the session's MCP
|
|
341
|
-
servers with their status and tools, without spending a turn. Commands that
|
|
342
|
-
only make sense in a terminal, such as `/vim`, are left out, and say so if
|
|
343
|
-
typed anyway.
|
|
344
|
-
|
|
345
|
-
Type `@` for files and folders, matched the way Claude Code matches them
|
|
346
|
-
(ripgrep over the folder until the agent is running), and pick one to insert
|
|
347
|
-
`@path`. After each turn but the first, the agent's guess at your next message
|
|
348
|
-
shows in the empty composer: **Tab** or **→** takes it, typing or **Esc** sets
|
|
349
|
-
it aside; on a phone it's a chip to tap. **↑** and **↓** in an empty composer
|
|
350
|
-
walk back through what you've sent. Select text in a reply and **Quote** puts
|
|
351
|
-
it in the composer as a blockquote.
|
|
352
|
-
|
|
353
|
-
A message sent while the agent is working waits in a queue above the
|
|
354
|
-
composer, kept on the server so a reload doesn't lose it, and goes when the
|
|
355
|
-
turn ends. Queued messages can be edited, moved or removed, and **Send now**
|
|
356
|
-
stops the turn (as Esc does) and sends that one with the stop, so it's the
|
|
357
|
-
very next thing the agent reads, ahead of a background task's notice it had
|
|
358
|
-
waiting. A message typed right after Esc goes the same way.
|
|
359
|
-
|
|
360
|
-
**Plan mode** (the **Plan** toggle under the composer, or Shift+Tab) has the
|
|
361
|
-
agent read and plan without changing anything; it's remembered per session.
|
|
362
|
-
When the agent has a plan it shows up as a card: **Carry it out** leaves plan
|
|
363
|
-
mode and starts it, **Keep planning** goes back to the composer.
|
|
364
|
-
|
|
365
|
-
The ring in the tab bar (in the composer on a phone) is how full the
|
|
366
|
-
conversation's context is, measured against the room before auto-compact:
|
|
367
|
-
amber past 60%, red past 85%. Tap it for the breakdown and **Compact now**.
|
|
368
|
-
|
|
369
|
-
Two thresholds (Settings → Workspaces → Compact; 0 turns one off). Mid-turn,
|
|
370
|
-
the agent compacts itself at 300k by default: AgentOS launches Claude Code
|
|
371
|
-
with `CLAUDE_CODE_AUTO_COMPACT_WINDOW` set to that size (its floor is 100k),
|
|
372
|
-
high enough that a long job isn't cut in half. Between
|
|
373
|
-
turns AgentOS catches what it didn't: a chat over its 150k threshold is compacted
|
|
374
|
-
before the next message, and so is one idle past its prompt cache (5 minutes
|
|
375
|
-
or an hour, read from the agent's own usage), whose next message would
|
|
376
|
-
re-read the whole conversation at the cache-write price. A chip above the
|
|
377
|
-
composer says "Idle 47m · 412k context · Compact first?": Send compacts and
|
|
378
|
-
then sends, **Send as-is** skips it for that one message. Messages nobody is
|
|
379
|
-
typing (the orchestrator's `send`, AgentOS events and wakes, `aos send`,
|
|
380
|
-
schedules) compact first on their own, only between turns, and the chat
|
|
381
|
-
shows one quiet line with the context before and after. An orchestrator
|
|
382
|
-
isn't compacted (a summary would lose the fence around what it read from
|
|
383
|
-
tasks and CI): it starts a fresh conversation from its brief and the current
|
|
384
|
-
state at its fresh-start threshold, or earlier once idle past its cache.
|
|
385
|
-
While a chat compacts (any trigger, Claude, Pi or Codex) it is shown as
|
|
386
|
-
compacting, not just working: a pulsing blue dot and a "Compacting" chip in
|
|
387
|
-
the sidebar, and "Compacting context… 182k tokens" above the composer. The
|
|
388
|
-
status API (`/api/sessions/status`) keeps `status: "running"` and adds
|
|
389
|
-
`compacting: { since, before? }`; the orchestrator's `sessions` tool says
|
|
390
|
-
"compacting context". It clears when the compaction ends, errors, is
|
|
391
|
-
stopped, or the worker goes away.
|
|
392
|
-
What survives a compaction: the summary is asked to keep the task, scope
|
|
393
|
-
changes, every edited file by path, what's done, in progress and next, and
|
|
394
|
-
the decisions. Right after it, AgentOS hands back a note built from its own
|
|
395
|
-
records (a task's brief and scope changes, the files it edited, its todos,
|
|
396
|
-
the latest message and `git status`), which doesn't depend on what the
|
|
397
|
-
summary kept, and tells the agent not to re-read or rewrite whole files.
|
|
398
|
-
Terminal agents don't get the note yet.
|
|
399
|
-
On a desktop browser the screen button attaches a screenshot of a screen or
|
|
400
|
-
window you pick; on a phone, attach a screenshot as a photo.
|
|
401
|
-
|
|
402
|
-
**Usage** (the sidebar's ⋯ menu, or the palette) shows the account's 5-hour
|
|
403
|
-
and weekly windows, and what chats cost today, over 7 or 30 days, by day,
|
|
404
|
-
workspace and session. Costs are the agent's own estimates, recorded as each
|
|
405
|
-
turn ends. **Gate reviews** below it shows what the orchestrator's
|
|
406
|
-
independent reviewer and scope check cost, by PR: they run as `claude -p`
|
|
407
|
-
and leave no transcript, so each run's tokens, model and duration are read
|
|
408
|
-
from its JSON result into `check_runs`, and the review's verdict says them
|
|
409
|
-
too. Every compaction is kept in `chat_compactions` (who ran it, whether it
|
|
410
|
-
fired `mid_turn` or `between_turns`, and the context before and after), so cost per task can be compared by date.
|
|
411
|
-
Rows from before the column existed, and any written by a chat worker still
|
|
412
|
-
running older code, have a NULL `phase`: read those as unknown
|
|
413
|
-
(`COALESCE(phase, 'unknown')`), not as either kind.
|
|
414
|
-
|
|
415
|
-
**Esc** stops a running turn, as it does in Claude Code's terminal, unless a
|
|
416
|
-
menu, dialog or the command list is open (those close first). On a phone the
|
|
417
|
-
Stop button does the same. A stopped turn keeps what it wrote.
|
|
418
|
-
|
|
419
|
-
Stop ends the turn even while the model is still thinking, before any tool
|
|
420
|
-
call: the turn is interrupted, and if it hasn't ended within a few seconds the
|
|
421
|
-
agent process is ended and restarted for the next message (not if the chat is
|
|
422
|
-
waiting on an approval or question, or if a new message has started a new
|
|
423
|
-
turn meanwhile). The conversation and
|
|
424
|
-
worktree are kept, and whatever is sent next (yours, another agent's, the
|
|
425
|
-
orchestrator's) runs as the next turn, never queued behind the stopped one.
|
|
426
|
-
The UI, the orchestrator's `stop` tool and `aos stop` all take this one path
|
|
427
|
-
and report "stopped" or a real error.
|
|
428
|
-
|
|
429
|
-
A turn that has produced nothing (no token, tool call or result) for 8 minutes
|
|
430
|
-
shows as **Stalled** in the session list, and its workspace's orchestrator gets
|
|
431
|
-
one event naming the session, how long it has been silent and its last action.
|
|
432
|
-
It fires once per stall and starts over when the session does something. A
|
|
433
|
-
foreground tool call gets four times as long, and a session waiting on a
|
|
434
|
-
background command it started isn't flagged. Set the time (2 to 120 minutes, 0 for off)
|
|
435
|
-
in Settings → Workspaces → Stall alerts.
|
|
436
|
-
|
|
437
|
-
The access picker under the composer sets what the agent may do on its own:
|
|
438
|
-
**Ask first** (an approval card in the chat for anything not already allowed),
|
|
439
|
-
**Accept edits** (edits files, asks before other commands) or **Full access**
|
|
440
|
-
(the default, like running with permissions skipped). Questions the agent asks
|
|
441
|
-
you come up as cards to answer. **Undo** on a message puts back the files the
|
|
442
|
-
agent edited since, rewinds the conversation to before it, and returns the
|
|
443
|
-
message to the composer to edit and resend. Changes made by shell commands
|
|
444
|
-
aren't tracked, so they stay.
|
|
445
|
-
|
|
446
|
-
While a turn runs, a line above the composer says what the agent is doing
|
|
447
|
-
and for how long (with Stop), and every step shows its time. Work the agent
|
|
448
|
-
starts in the background (shells, subagents, monitors) sits behind a
|
|
449
|
-
"running in background" chip: each task with its elapsed time, its live
|
|
450
|
-
output, and its own Stop. The session list says the same thing, so a long
|
|
451
|
-
wait never looks like nothing is happening.
|
|
452
|
-
|
|
453
|
-
**Visuals.** Every chat agent (not an orchestrator, which reads other
|
|
454
|
-
sessions' text and gets no browser) has two tools: `html_preview` renders a page in
|
|
455
|
-
headless Chrome and hands back a screenshot plus the console, so it can check
|
|
456
|
-
its own work, and `html_render` shows the finished page (a chart, a table, a
|
|
457
|
-
mockup) inline as its own card, with full screen and "open in new tab". Pages
|
|
458
|
-
are kept under `~/.agent-os/artifacts/<session>/` with a row in SQLite, so
|
|
459
|
-
they survive reloads and restarts (`GET /api/sessions/<id>/artifacts` lists
|
|
460
|
-
them). They're served behind the same device gate with a CSP sandbox and no
|
|
461
|
-
same-origin access, so a page can't read AgentOS cookies or call its API; the
|
|
462
|
-
preview browser gets an empty profile and a proxy that only reaches public
|
|
463
|
-
addresses. An image or SVG file the agent writes shows inline when its reply
|
|
464
|
-
links to it by absolute path. The tools read files and reach the web, so
|
|
465
|
-
under **Ask first** they ask like any other tool. Previews need Chrome, Chromium, Brave or Edge
|
|
466
|
-
installed (or `AGENTOS_CHROME` pointing at one).
|
|
467
|
-
|
|
468
|
-
Chat runs on this machine, and on linked machines through their own AgentOS
|
|
469
|
-
(see [Machines](#machines)); sessions on machines reached over ssh only use
|
|
470
|
-
the terminal.
|
|
471
|
-
Drivers for other agent CLIs plug into `lib/chat/drivers`; undo, `@file`
|
|
472
|
-
suggestions and visuals are Claude-only for now, and plan mode is Claude and
|
|
473
|
-
OpenCode.
|
|
474
|
-
|
|
475
|
-
## Tasks
|
|
476
|
-
|
|
477
|
-
Hand off work and keep going. A task is a draft with **Open a PR when
|
|
478
|
-
done** turned on (**New task** in ⌘K, the sidebar's New menu or the Tasks
|
|
479
|
-
panel opens one). AgentOS creates a git worktree and branch, starts Claude there in tmux
|
|
480
|
-
with a brief to finish by pushing and opening a pull request, and tracks it:
|
|
481
|
-
Working, Needs input, Blocked, Ready for review, Checks failing or Agent
|
|
482
|
-
exited. **Sign off & merge** merges the PR (refused while CI is failing
|
|
483
|
-
or pending), then removes the session, worktree and branches. **Drop** closes
|
|
484
|
-
the PR and removes everything. Agents never merge their own work.
|
|
485
|
-
|
|
486
|
-
**Merging settings.** How a PR merges (sign-off, Done, Land and the
|
|
487
|
-
orchestrator's external PRs alike) and what's cleaned up after it are set in
|
|
488
|
-
**Settings → Merging** (or ⌘K → Merge settings) for every project,
|
|
489
|
-
and overridden per project in its **Project Settings → Merging** or in its
|
|
490
|
-
`agentos.json`:
|
|
491
|
-
|
|
492
|
-
```json
|
|
493
|
-
{
|
|
494
|
-
"merge": {
|
|
495
|
-
"method": "rebase",
|
|
496
|
-
"delete_remote_branch": true,
|
|
497
|
-
"delete_worktree": false
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
`method` is `squash` (the default), `merge` (a merge commit) or `rebase`.
|
|
503
|
-
GitHub enforces the repository's allowed methods: if it refuses the one set,
|
|
504
|
-
the merge fails with a message naming the method and the repository setting
|
|
505
|
-
to turn on (Settings → General → Pull Requests), and AgentOS never falls back
|
|
506
|
-
to another method. The project's settings show a method its repository has
|
|
507
|
-
turned off. `delete_remote_branch` and `delete_worktree` (both on by default)
|
|
508
|
-
delete the branch on origin and the local worktree with its branch after a
|
|
509
|
-
merge. With either on, `main`, `master` and `HEAD` are never deleted, nor a
|
|
510
|
-
branch a stacked task still targets, nor a worktree holding uncommitted work
|
|
511
|
-
or commits the merge didn't include. The most specific setting wins: the
|
|
512
|
-
project's, then `agentos.json`, then the global one.
|
|
513
|
-
|
|
514
|
-
**Queued tasks.** A workspace can limit how many tasks run at once (none by
|
|
515
|
-
default; set it in **Settings → Workspaces**, or `PATCH /api/workspaces/<id>` with `{"maxRunningTasks": 3}`, or
|
|
516
|
-
`null` for no limit). A task started over the limit is queued, with no
|
|
517
|
-
worktree or agent yet, and starts by itself in line order when one finishes
|
|
518
|
-
(merged, dropped or done). `aos task --after <task|any>` (and the
|
|
519
|
-
orchestrator's `start_task` with `after`) holds a task until a named task
|
|
520
|
-
finishes, or until any task still working now does (a running task whose PR
|
|
521
|
-
was closed or merged doesn't count; with none, it starts at once). The
|
|
522
|
-
orchestrator's `drop` removes a queued task by its id. Pause stops automatic starts,
|
|
523
|
-
and the orchestrator's brakes still apply when its queued tasks start. A task runs on Claude Code unless
|
|
524
|
-
`POST /api/tasks` names another `agent` (`"copilot"`): that one runs as a chat on
|
|
525
|
-
this machine, with no plan-first step or role models, and is never picked for
|
|
526
|
-
you. The
|
|
527
|
-
line is kept in the database across restarts (`GET /api/tasks/queue`; start
|
|
528
|
-
now, move up or down, or remove with `/api/tasks/queue/<id>`). The sidebar
|
|
529
|
-
shows them under **Queued** with their place in line or what they wait on,
|
|
530
|
-
and the same actions in each row's menu; a row becomes the task's own row
|
|
531
|
-
when it starts. Why it works
|
|
532
|
-
this way: [docs/decisions/2026-10-10-queued-tasks.md](docs/decisions/2026-10-10-queued-tasks.md).
|
|
533
|
-
|
|
534
|
-
A paired device's token can run the building loop through read-only routes:
|
|
535
|
-
`GET /api/start-options?projectId=` (agents and models, the models a task can
|
|
536
|
-
run on, machines, branches, whether a task plans first, in one request),
|
|
537
|
-
`GET /api/tasks/<id>/diff` (the task's whole change, base to head: a file list,
|
|
538
|
-
then `?file=<path>` for one file's patch; capped, with `truncated` when a cap
|
|
539
|
-
is hit; read from the worktree, else the PR), and `GET /api/tasks/<id>/checks`
|
|
540
|
-
(the PR head's checks, the tail of a failing job's log, and the review gate's
|
|
541
|
-
verdict). A task's `plan` in `GET /api/tasks` carries `status` (`writing`,
|
|
542
|
-
`ready` or `approved`), a title and capped text. Their shapes are in
|
|
543
|
-
`lib/tasks/loop-types.ts`, which has no server imports so a client can share it.
|
|
544
|
-
|
|
545
|
-
## Model routing
|
|
546
|
-
|
|
547
|
-
Each kind of work AgentOS launches an agent for runs on its own model, set in
|
|
548
|
-
**Settings → Models** (⌘K → Model settings). A model is named
|
|
549
|
-
`provider/model`:
|
|
550
|
-
|
|
551
|
-
| Role | Default |
|
|
552
|
-
| --------------------------------------------------- | ------------------------------------- |
|
|
553
|
-
| Task planning (a task's plan step) | `anthropic/claude-opus-5-5` |
|
|
554
|
-
| Gate review | `anthropic/claude-opus-5-5` |
|
|
555
|
-
| Task implementation | `anthropic/claude-sonnet-5-5` |
|
|
556
|
-
| Orchestrator | `anthropic/claude-opus-5-5` |
|
|
557
|
-
| Scope check | `anthropic/claude-sonnet-5-5` |
|
|
558
|
-
| Interactive sessions | `anthropic/claude-opus-5-5` |
|
|
559
|
-
| Chat & summary helpers (titles, summaries, PR text) | `anthropic/claude-haiku-4-5-20251001` |
|
|
560
|
-
|
|
561
|
-
The most specific setting wins: a task's own model, then its workspace's
|
|
562
|
-
(**Settings → Workspaces → Models**), then Settings, then the default.
|
|
563
|
-
`AGENTOS_REVIEW_MODEL` (gate review and scope check) and
|
|
564
|
-
`AGENTOS_TITLE_MODEL` (helpers) in the server's environment still replace a
|
|
565
|
-
role's default where Settings names none. A
|
|
566
|
-
Claude session you start without a model runs on the interactive role's. One
|
|
567
|
-
an orchestrator starts (its `start_session`, or `aos spawn` run from an
|
|
568
|
-
orchestrator) is recorded as started by it (`started_by`) and runs on the
|
|
569
|
-
implementation role instead, so delegated work doesn't use Opus by default;
|
|
570
|
-
`start_session` takes a `model` (`"provider/model"`, checked like a task's:
|
|
571
|
-
gate roles can't be set, other providers must be enabled). One you pick a
|
|
572
|
-
model for keeps it.
|
|
573
|
-
|
|
574
|
-
**Plan, then implement.** A chat task whose planning and implementation
|
|
575
|
-
models differ starts with a plan step: its first turn runs on the planning
|
|
576
|
-
model and writes the plan to `.agentos/plan.md` in its worktree (ignored by
|
|
577
|
-
git), changing nothing else. When that turn ends, the same conversation
|
|
578
|
-
switches to the implementation model (a live model switch, or a resume with
|
|
579
|
-
`--model` when the provider changes) and is told to follow the plan. Turn it
|
|
580
|
-
off in Settings, or per task with `plan: false`. Terminal tasks skip it. An
|
|
581
|
-
orchestrator decides it for every task: `start_task` requires `plan` and a
|
|
582
|
-
short `plan_reason`, stored on the task and shown on its card ("Planned:
|
|
583
|
-
touches 6 files across gates and UI" / "No plan: one-line default change"),
|
|
584
|
-
and **Usage → Compare** splits cost into planned and not. When the plan step
|
|
585
|
-
finishes, the orchestrator gets one event with the plan file's path and a
|
|
586
|
-
3-5 line summary; the handoff to implementation stays automatic.
|
|
587
|
-
|
|
588
|
-
**Plan only.** A task started with `plan_only` (`start_task`'s `plan_only`,
|
|
589
|
-
`aos task --plan-only`, or `plan_only: true` on `POST /api/tasks`) writes its
|
|
590
|
-
plan on the planning model and then stops: no model switch, no implement
|
|
591
|
-
prompt, even when one model does every role. It sits in a **Plan ready**
|
|
592
|
-
state (`plan_phase` `awaiting`, stored on the task, so it survives a restart)
|
|
593
|
-
that isn't blocked, doesn't count as needing you, and holds no agent process
|
|
594
|
-
(its worker is released; the conversation resumes on approval). The
|
|
595
|
-
orchestrator gets one event with the plan's path, a summary and that the task
|
|
596
|
-
is waiting. **Approve** on the task row (plan viewable there), `POST
|
|
597
|
-
/api/tasks/<id>/approve-plan`, or the orchestrator's `approve_plan(task,
|
|
598
|
-
message?)` continues it on the implementation model, once; a message amends
|
|
599
|
-
the plan and is recorded on the task's brief as a scope change, so the review
|
|
600
|
-
judges against it. Reject with **Drop**. A chat task on this machine only; it
|
|
601
|
-
can't move until approved.
|
|
602
|
-
|
|
603
|
-
**Per task.** The task draft has a **Model** chip ("Routed by role", or one
|
|
604
|
-
model); `POST /api/tasks` and the orchestrator's `start_task` take `model`, as
|
|
605
|
-
`"provider/model"` for the task's own roles or a map such as
|
|
606
|
-
`{"implement": "zai/glm-5.3"}`, and `plan`. Its gate review and scope check
|
|
607
|
-
never follow a task's model, only Settings and its workspace, so work can't
|
|
608
|
-
pick its own reviewer. **Resume on model…** on a task's row
|
|
609
|
-
(or `POST /api/tasks/<id>/model`, or the orchestrator's `set_task_model`)
|
|
610
|
-
moves a paused task to another model: same conversation, same worktree, and
|
|
611
|
-
it keeps that model from then on. Each turn records the model, provider and
|
|
612
|
-
role it ran as.
|
|
613
|
-
|
|
614
|
-
**Providers.** `anthropic` is your Claude login and needs no key. Others are
|
|
615
|
-
Anthropic-compatible endpoints (OpenAI-compatible ones can be defined and
|
|
616
|
-
tested, but Claude Code can't run on them): a base URL, where the key lives,
|
|
617
|
-
the models, and Claude Code's opus/sonnet/haiku tiers mapped to them so
|
|
618
|
-
subagents and background calls stay on that provider. DeepSeek
|
|
619
|
-
(`https://api.deepseek.com/anthropic`) and Z.ai GLM
|
|
620
|
-
(`https://api.z.ai/api/anthropic`) ship as presets, off until given a key.
|
|
621
|
-
Claude stays the default: another provider runs only where a role or a task
|
|
622
|
-
picks it. If a role's provider is turned off or its key can't be read,
|
|
623
|
-
helper runs, reviews and the orchestrator fall back to the role's default
|
|
624
|
-
rather than fail. **Test provider** sends one tiny request and says whether it
|
|
625
|
-
answered. Changing models, providers or the test runs only from this machine
|
|
626
|
-
or over Tailscale, never from a paired device: a provider's URL and key
|
|
627
|
-
reference decide where a key is sent.
|
|
628
|
-
|
|
629
|
-
The key is never stored by AgentOS. A provider names where to read it: a
|
|
630
|
-
tokenvault entry (`tv get <project> <desc>`), a macOS Keychain item
|
|
631
|
-
(`security find-generic-password -s <service> -w`) or an environment
|
|
632
|
-
variable named `*_API_KEY` (not `ANTHROPIC_`, `CLAUDE_` or `AGENTOS_`). It's read when an agent launches and goes only into that agent's
|
|
633
|
-
environment (`ANTHROPIC_BASE_URL` with `ANTHROPIC_AUTH_TOKEN` or
|
|
634
|
-
`ANTHROPIC_API_KEY`), never its command line, the database, a prompt or a
|
|
635
|
-
log; a terminal task's shell reads it from the reference itself. Errors that
|
|
636
|
-
might echo a key are scrubbed of it.
|
|
637
|
-
|
|
638
|
-
**Usage → Daily: the usage ledger and the ratchet.** Every Claude response
|
|
639
|
-
an AgentOS chat makes (subagents and the orchestrator included) is written to
|
|
640
|
-
`usage_calls` as it arrives, with its input, cache read, 5-minute and 1-hour
|
|
641
|
-
cache write and output tokens, its model, its context size and the
|
|
642
|
-
session, task, workspace and role it belongs to. Terminal sessions, which no
|
|
643
|
-
worker watches, are read from Claude's transcripts, and gate reviews come
|
|
644
|
-
from `check_runs`; the same message id is never counted twice. Tokens are
|
|
645
|
-
_weighted_ by the API's price ratios (Opus input 1, 5-minute write 1.25,
|
|
646
|
-
1-hour write 2, cache read 0.1, output 5; Sonnet half, Haiku a quarter), an
|
|
647
|
-
estimate of how much of the plan's limits a call uses.
|
|
648
|
-
|
|
649
|
-
The 5-hour and weekly windows come from the `rate_limit_event`s Claude Code
|
|
650
|
-
sends on each response (the `anthropic-ratelimit-unified-*` headers), stored
|
|
651
|
-
with their time in `rate_limit_readings` with no extra calls. An event
|
|
652
|
-
carries one window, so the weekly figure can go stale: when it is over 6
|
|
653
|
-
hours old it is estimated from recorded usage and marked `~`.
|
|
654
|
-
|
|
655
|
-
A rollup job inside the server (no model) writes one `usage_rollups` row per
|
|
656
|
-
day: weighted total, cost per merged PR, cost per _clean_-merged PR (the
|
|
657
|
-
headline: cost against quality), review cost per PR, the share of calls above
|
|
658
|
-
200k context, model mix, orchestrator share, the weekly %, and the quality
|
|
659
|
-
readings from existing data: first-try clean-merge rate (the first reviewed
|
|
660
|
-
head passed review and CI, no gate failures, no later head), fix rounds per
|
|
661
|
-
PR, gate escalations, scope changes, and escapes (a revert or fix PR touching
|
|
662
|
-
the same files within 3 days, from `gh`; unknown rather than zero if `gh`
|
|
663
|
-
fails). Spans of days are ratios of sums. The baseline is the first week of
|
|
664
|
-
rollups and only ever moves to a better value. One phone alert fires (and
|
|
665
|
-
stays quiet until it clears) when the 3-day cost per merged PR is over
|
|
666
|
-
baseline by 15%, or the clean-merge rate is under it by 10 points, naming the
|
|
667
|
-
supporting metric that moved most. The margins, the days averaged and the
|
|
668
|
-
minimum merges to judge are settings (`usage.alert_cost_pct`,
|
|
669
|
-
`usage.alert_clean_points`, `usage.avg_days`, `usage.alert_min_merges`,
|
|
670
|
-
`usage.baseline_days`, `usage.weekly_stale_hours`). A dev server doesn't run
|
|
671
|
-
it unless `AGENTOS_USAGE_ROLLUP=on`; `AGENTOS_USAGE_ROLLUP=off` turns it off
|
|
672
|
-
in production. `POST /api/usage/rollups` runs the nightly pass now. Task
|
|
673
|
-
cards show each task's weighted cost so far.
|
|
674
|
-
|
|
675
|
-
**Usage by model.** **Usage → By model** shows tokens and estimated cost per
|
|
676
|
-
role and per model in the plan's 5-hour window and the last 24 hours, from
|
|
677
|
-
the price table in Settings → Models (dollars per million input, cache
|
|
678
|
-
write, cache read and output tokens, editable).
|
|
679
|
-
|
|
680
|
-
**How to read the comparison.** **Usage → Compare** (or ⌘K → Model
|
|
681
|
-
comparison, or the sidebar menu) puts Claude-only tasks (the baseline,
|
|
682
|
-
including everything from before routing) beside routed ones, per provider,
|
|
683
|
-
over 7, 30 or 90 days, with a total column. A task counts under the provider
|
|
684
|
-
its own planning and implementation turns ran on; its reviews and checks add
|
|
685
|
-
to its cost wherever they ran. For each column: tasks, turns, tokens,
|
|
686
|
-
estimated dollars (list prices; for Claude, what the API would charge),
|
|
687
|
-
Claude window points per task (how far each turn moved the plan's 5-hour
|
|
688
|
-
window, an estimate), agent time, CI green on the first push, review passed
|
|
689
|
-
on the first try, gate failures and rework commits per task (heads checked
|
|
690
|
-
after the first review), and merged or dropped. Outcomes come from the
|
|
691
|
-
orchestrator's checks, so tasks it didn't run have cost but no outcome. A
|
|
692
|
-
column with fewer than 10 tasks is marked a small sample: a hint, not a
|
|
693
|
-
result. Parity means the routed column's outcome rows match the baseline's
|
|
694
|
-
while its window points per task drop. Why it works this way:
|
|
695
|
-
[docs/decisions/2026-10-11-model-routing.md](docs/decisions/2026-10-11-model-routing.md).
|
|
696
|
-
|
|
697
|
-
## New sessions
|
|
698
|
-
|
|
699
|
-
⌥N (the **+** on a phone) opens a draft: a composer in the current project, or
|
|
700
|
-
the one you used last. Nothing exists until you send. Then the session is
|
|
701
|
-
made, and with a worktree its setup runs in the chat, stage by stage: fetch,
|
|
702
|
-
worktree, env files, dependencies, the project's setup script, with a log
|
|
703
|
-
tail. The first message waits in the queue until setup is done, so the agent
|
|
704
|
-
never starts on half-installed dependencies, and a restart can't lose it. The
|
|
705
|
-
worktree starts on a temporary branch, renamed from the first message once
|
|
706
|
-
it has a title (it keeps the temporary name if there's nothing to name it
|
|
707
|
-
from).
|
|
708
|
-
|
|
709
|
-
⌥N reuses the project's empty draft; a draft you've typed in stays in the
|
|
710
|
-
sidebar's **Drafts** until you send or discard it. A new draft takes the
|
|
711
|
-
agent, model and access you were using; a project's own agent and model win.
|
|
712
|
-
Chats with no project run in `~/.agent-os/scratch`.
|
|
713
|
-
|
|
714
|
-
A task or session started from a prompt gets a short name: the brief's first
|
|
715
|
-
heading at once (when the prompt points at a readable `.md` brief), then a
|
|
716
|
-
2-6 word title from a quick Haiku call a few seconds later. A chat opened as
|
|
717
|
-
"Session 4" is named after its first message. Anything named by hand, a card's
|
|
718
|
-
title, a schedule's name or `--name` is kept as given.
|
|
719
|
-
|
|
720
|
-
The agent starts once its worktree has its dependencies. On macOS the main
|
|
721
|
-
checkout's `node_modules` (and workspace ones like `apps/web/node_modules`)
|
|
722
|
-
are cloned with `cp -Rc` when the lockfile matches, and a spare clone waits in
|
|
723
|
-
`~/.agent-os/spare` so the next task takes it by rename, in milliseconds.
|
|
724
|
-
Otherwise, and on Linux, the lockfile's frozen install runs (`npm ci`,
|
|
725
|
-
`pnpm/yarn/bun --frozen-lockfile`), then a plain install, always with
|
|
726
|
-
devDependencies even though the server runs with `NODE_ENV=production`. A
|
|
727
|
-
project's `agentos.json` can name exactly what to copy and clone, and its
|
|
728
|
-
`setup` commands (without a `clone`) replace the install; see
|
|
729
|
-
[Project config](#project-config-agentosjson).
|
|
730
|
-
Starting a task returns at once and shows **Setting up** until the agent
|
|
731
|
-
launches; a restart in the middle resumes it. A failed setup shows on the task
|
|
732
|
-
and is in the agent's first prompt (redacted, as data).
|
|
733
|
-
|
|
734
|
-
Tasks don't review their own PRs. The orchestrator's independent review of
|
|
735
|
-
the PR's exact head commit is the one review: `sign_off` and a stack's
|
|
736
|
-
**Land** refuse a PR without a passing one, and a PR body with AI
|
|
737
|
-
attribution. A head AgentOS rebased itself is covered by the review of the
|
|
738
|
-
head it rebased while the PR's own patch is unchanged. A task may still run
|
|
739
|
-
`/do-code-review` (the project's `.claude/skills/do-code-review`, or Claude
|
|
740
|
-
Code's `/code-review`) on a risky change before its PR; it's no longer
|
|
741
|
-
required. The review agents are in [.claude/agents](.claude/agents) and the
|
|
742
|
-
skill in [.claude/skills](.claude/skills/README.md).
|
|
743
|
-
|
|
744
|
-
Requires the GitHub CLI (`gh`) signed in, and a project with a GitHub remote.
|
|
745
|
-
|
|
746
|
-

|
|
747
|
-
|
|
748
|
-
Tracking PRs stays inside GitHub's GraphQL quota (5,000 points an hour).
|
|
749
|
-
Only running tasks are asked about: a finished task's PR comes from the
|
|
750
|
-
database. One `gh pr list --state open` per repository a minute covers every
|
|
751
|
-
running task with an open PR, and a branch without one is asked about on its
|
|
752
|
-
own at most every 5 minutes. A merge or sign-off always asks fresh. When gh
|
|
753
|
-
reports a rate limit, every gh call backs off until the limit resets, and gh
|
|
754
|
-
failures are logged as `[gh] ...`, at most once a minute for each kind.
|
|
755
|
-
|
|
756
|
-
### Project config (`agentos.json`)
|
|
757
|
-
|
|
758
|
-
A project describes how its sessions run in `agentos.json` at the repository
|
|
759
|
-
root. Every field is optional:
|
|
760
|
-
|
|
761
|
-
```json
|
|
762
|
-
{
|
|
763
|
-
"ports": { "RAILS_PORT": 3000, "VITE_PORT": 3100 },
|
|
764
|
-
"env": { "STORAGE_PROVIDER": "disk" },
|
|
765
|
-
"copy": [".env", "config/master.key"],
|
|
766
|
-
"clone": ["node_modules"],
|
|
767
|
-
"setup": ["bin/setup"],
|
|
768
|
-
"dev": "bin/dev",
|
|
769
|
-
"ready": { "url": "/up", "port": "RAILS_PORT" },
|
|
770
|
-
"test": "bundle exec rspec",
|
|
771
|
-
"browse": { "login": "/dev/login", "map": "docs/browsing.md" },
|
|
772
|
-
"database": { "from": "app_development", "env": "DATABASE_NAME" },
|
|
773
|
-
"notes": ["Kill your own server by port: lsof -ti tcp:$RAILS_PORT"],
|
|
774
|
-
"merge": { "method": "squash" },
|
|
775
|
-
"deploy": {
|
|
776
|
-
"deployed": "~/.deploy/deployed",
|
|
777
|
-
"health": "https://app.example.com/up"
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
`merge` sets the project's merge method and branch clean-up (see
|
|
783
|
-
[Merging settings](#tasks)).
|
|
784
|
-
|
|
785
|
-
`deploy` is how [Away mode](#orchestrator) checks a merge went live: `deployed`
|
|
786
|
-
is a file your deploy writes the deployed commit to, `failed` (optional) one it
|
|
787
|
-
writes a commit whose deploy failed to, and `health` (optional) a URL that
|
|
788
|
-
answers 2xx once the server is back (a path alone is this AgentOS server's own,
|
|
789
|
-
as this repository's `agentos.json` uses with `scripts/autodeploy`'s state
|
|
790
|
-
files). A merge counts as deployed once the `deployed` commit contains it and
|
|
791
|
-
`health` answers; as failed when the `failed` commit contains it, the server
|
|
792
|
-
doesn't come back within 3 minutes (or, when its health answer has a
|
|
793
|
-
`startedAt`, hasn't restarted since the merge), or it isn't deployed `within_minutes`
|
|
794
|
-
(30) after the merge. No `deployed` file on the machine means nothing deploys
|
|
795
|
-
there, and nothing is checked.
|
|
796
|
-
|
|
797
|
-
Every task and worktree session takes a **port slot**, and each named port is
|
|
798
|
-
`base + slot` (a project with no `ports` still gets a `PORT` from 3100), so
|
|
799
|
-
parallel sessions never share a dev server port. The slot is kept on the
|
|
800
|
-
session and freed when it's done or deleted (an archived or merged one's once
|
|
801
|
-
its agent has stopped). The ports and
|
|
802
|
-
`env` are exported to the setup commands, the agent and its terminals; a port
|
|
803
|
-
always wins over an `env` entry of the same name, and `env` can't set names a
|
|
804
|
-
shell, runtime, git or the agent reads (`PATH`, `NODE_OPTIONS`, `TERM`,
|
|
805
|
-
`GIT_*`, `ANTHROPIC_*`, `AGENTOS_*`…): a config that sets one, such as
|
|
806
|
-
`NODE_OPTIONS` or `TERM`, is refused, and the error names the variable and why. The agent's brief gets a
|
|
807
|
-
short **Running this project** section: its ports, the dev command, how to
|
|
808
|
-
wait for `ready` in one backgrounded call, the test command, the login URL on
|
|
809
|
-
its own port, the browsing map and the notes. Only declared fields are
|
|
810
|
-
mentioned, and `env` values never are. Keep secrets out of the file: it is
|
|
811
|
-
committed.
|
|
812
|
-
|
|
813
|
-
With `database`, every task and worktree session gets its **own Postgres
|
|
814
|
-
database**, a copy of `from` named `<from>_aos_<hash of the session>`, made
|
|
815
|
-
before setup runs and exported as `env` (default `DATABASE_NAME`, plus
|
|
816
|
-
`PGPORT`/`DATABASE_PORT` and `PGHOST`/`DATABASE_HOST` when `port`/`host` are
|
|
817
|
-
set), so one session's migrations never reach another's. It's copied with
|
|
818
|
-
`pg_dump` into a database created from `template0`, not `createdb -T <from>`,
|
|
819
|
-
which refuses while the dev server is connected to `from`. Only a local server
|
|
820
|
-
is used (`host` may be `localhost`, `127.0.0.1`, `::1` or one socket folder), and
|
|
821
|
-
`from` must be a plain database name. A copy left by an earlier start of the
|
|
822
|
-
same session is reused. When Postgres isn't answering or the copy fails, the
|
|
823
|
-
session still starts, without one, and its brief says so. The copy is dropped
|
|
824
|
-
when the session is done, merged, dropped, moved away or deleted, and only one AgentOS made
|
|
825
|
-
for that session (its comment names the session): `from` and any other
|
|
826
|
-
database are never touched.
|
|
827
|
-
|
|
828
|
-
The file is validated against
|
|
829
|
-
[agentos.schema.json](lib/project-config/agentos.schema.json); a broken one
|
|
830
|
-
fails the setup with every bad field named, rather than being ignored. Without
|
|
831
|
-
an `agentos.json`, a `.dispatch.json` is read with the same meanings, then the
|
|
832
|
-
legacy `.agent-os/worktrees.json` and `.agent-os.json`. The config is always
|
|
833
|
-
read from the main checkout, so an agent can't change its own environment by
|
|
834
|
-
editing it, and the merge gates flag an edit to it as agent config. The full
|
|
835
|
-
schema, including the fields later releases implement (MCP servers, apps,
|
|
836
|
-
recipes), is in
|
|
837
|
-
[docs/decisions/2026-10-10-agentos-json.md](docs/decisions/2026-10-10-agentos-json.md).
|
|
838
|
-
|
|
839
|
-
### iOS simulator for tasks
|
|
840
|
-
|
|
841
|
-
On a Mac, a task on an iOS app can boot a simulator of its own, put a debug
|
|
842
|
-
build of its worktree on it, tap through screens, read the accessibility tree
|
|
843
|
-
and take screenshots, to check a UI fix before anyone installs a release
|
|
844
|
-
build. A project opts in with a `simulator` block in `agentos.json`:
|
|
845
|
-
|
|
846
|
-
```json
|
|
847
|
-
{
|
|
848
|
-
"ports": { "METRO_PORT": 8100 },
|
|
849
|
-
"simulator": {
|
|
850
|
-
"start": "npm run sim",
|
|
851
|
-
"app": "com.example.app",
|
|
852
|
-
"device": "iPhone 17"
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
`start` runs in the task's worktree with `SIMULATOR_UDID` (and the task's
|
|
858
|
-
ports) in its environment. It builds if needed, installs the app on that
|
|
859
|
-
device and exits 0 once the app is up; AgentOS builds nothing itself, because
|
|
860
|
-
the project knows its native fingerprint and what to cache. `app` is the
|
|
861
|
-
bundle id, and `device` the model (default `iPhone 17`).
|
|
862
|
-
|
|
863
|
-
A project without the block gets nothing: no brief lines and no tools. A task
|
|
864
|
-
declares nothing and chooses nothing; it runs:
|
|
865
|
-
|
|
866
|
-
```bash
|
|
867
|
-
aos sim open [--no-wait] # make its simulator, run start; waits until it is up
|
|
868
|
-
aos sim status | close
|
|
869
|
-
aos sim snapshot -i # accessibility tree with @e1, @e2... refs
|
|
870
|
-
aos sim click @e3 | fill @e5 "text" | scroll | swipe | back | home | find | wait
|
|
871
|
-
aos sim screenshot [path] # prints a PNG path under ~/.agent-os/simulators/<task>/shots
|
|
872
|
-
aos sim logs --last 2m # the app's recent log
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
- **Driving** is [agent-device](https://github.com/callstack/agent-device) (MIT),
|
|
876
|
-
pinned and installed on first use into `~/.agent-os/tools`, with its own
|
|
877
|
-
state dir. Only the commands above are accepted; the device and session are
|
|
878
|
-
filled in by the server, so a task can only reach its own simulator.
|
|
879
|
-
- **One simulator per task**, cloned from a base device AgentOS makes once on
|
|
880
|
-
the newest iOS runtime. At most two are booted at once across all tasks
|
|
881
|
-
(`AGENTOS_SIMULATOR_MAX`); one idle for 15 minutes is shut down and boots
|
|
882
|
-
again on its next command. The XCTest runner builds the first time per Xcode
|
|
883
|
-
version (minutes), then is cached.
|
|
884
|
-
- **Cleanup**: a task's simulator is deleted when it is done, signed off,
|
|
885
|
-
dropped, deleted or moved away, and shut down when stopped. A sweep at start
|
|
886
|
-
and every 5 minutes also removes those of tasks that ended or crashed.
|
|
887
|
-
Devices are named with a per-database prefix and only those are ever
|
|
888
|
-
touched, so a dev instance and the real one never reap each other's.
|
|
889
|
-
- **Mac only.** On any other machine, including a linked one a task was moved
|
|
890
|
-
to, the brief says the simulator is unavailable and `aos sim` says the same;
|
|
891
|
-
nothing is forwarded between machines.
|
|
892
|
-
- **Optional.** No merge, review or release check reads simulator state.
|
|
893
|
-
Screenshots are written outside the worktree, and the brief tells the task
|
|
894
|
-
not to put them in commits or PR bodies unless they belong there.
|
|
895
|
-
|
|
896
|
-
### Stacks
|
|
897
|
-
|
|
898
|
-
A project whose board is linked to LumifyHub can run the whole board as
|
|
899
|
-
**stacked** tasks: **Tasks → From the board → Run as stack** shows the plan
|
|
900
|
-
first, then starts it. A card starts as soon as every card it's blocked by has
|
|
901
|
-
a PR open (not merged), on top of the card it depends on most: its branch is
|
|
902
|
-
cut from that card's pushed branch, and its PR targets it, so each PR shows
|
|
903
|
-
only its own commits. At most three run at once without a PR (adjustable).
|
|
904
|
-
|
|
905
|
-
Merging goes bottom-up. Signing off a card whose parent hasn't merged is
|
|
906
|
-
refused; after a parent merges, every card stacked on it (grandchildren
|
|
907
|
-
included) is rebased and its PR retargeted, and its agent is told. This works
|
|
908
|
-
with every merge method: only the child's own commits are replayed onto the
|
|
909
|
-
default branch, whether the parent was squashed, rebased or merged with a
|
|
910
|
-
merge commit. **Land**
|
|
911
|
-
merges every PR in order after checking all of them are open and green, and
|
|
912
|
-
waits for each restacked PR's checks before merging it. A chat card's agent
|
|
913
|
-
is stopped between turns before anything merges (Land waits up to 5 minutes
|
|
914
|
-
for a running turn, and refuses, naming the card, if it doesn't end), and
|
|
915
|
-
what it's sent during the land waits in its queue. A conflict stops on
|
|
916
|
-
that card with the exact command to fix it.
|
|
917
|
-
|
|
918
|
-
### Recipes
|
|
919
|
-
|
|
920
|
-
A recipe is a saved brief: a project, a kind (a **task**, which ends in a PR
|
|
921
|
-
and goes through the merge gates, or a **session**, which has no PR), and a
|
|
922
|
-
short brief that says what to run and how to judge it. The steps belong in a
|
|
923
|
-
script in the repo; the recipe says "run this, judge the result". Every run
|
|
924
|
-
is a fresh task or session, so nothing piles up in one long conversation.
|
|
925
|
-
|
|
926
|
-
Open **Settings → Recipes** (the sidebar's **⋯** menu, the workspace menu, or
|
|
927
|
-
⌘K → Recipes or "Run recipe: …"). **Run** takes an optional one-off note and
|
|
928
|
-
shows exactly what will be sent. **Save as recipe** on a session's menu or a
|
|
929
|
-
finished task prefills the form from what it did, with a suggested brief to
|
|
930
|
-
use or ignore.
|
|
931
|
-
|
|
932
|
-
- **Carried between runs.** A run ends with one line,
|
|
933
|
-
`RESULT: ok|fail key=value … | one sentence`. The next run's brief gets a
|
|
934
|
-
short block (at most 600 characters) with the last run's outcome and
|
|
935
|
-
those values, such as a build number; after a failure it also carries the
|
|
936
|
-
last good run. With no line, a cheap model summarises the run, and with
|
|
937
|
-
that unavailable the state alone is recorded. **Start the next run fresh**
|
|
938
|
-
drops the block.
|
|
939
|
-
- **One run at a time.** A run while another is going is refused, and a
|
|
940
|
-
double tap starts one run.
|
|
941
|
-
- **Timers.** A recipe can run hourly, daily, on weekdays, weekly or on a
|
|
942
|
-
cron expression, never more than once an hour; a timer that fires while a
|
|
943
|
-
run is going is skipped. Only you set timers: the orchestrator can't.
|
|
944
|
-
- **Orchestrator.** `recipes`, `run_recipe`, `recipe_save` and `recipe_update`
|
|
945
|
-
tools, and the workspace's recipes in its brief and digest. A run goes
|
|
946
|
-
through the same brakes, Pause and task queue as a task it started
|
|
947
|
-
itself, and a recipe is never an approval: it can't pass a gate. The
|
|
948
|
-
orchestrator gets an event when a run it started finishes, and for any
|
|
949
|
-
failure.
|
|
950
|
-
- **History.** Each recipe lists its runs: when, how triggered and by whom,
|
|
951
|
-
outcome, the reported values, cost, duration, and links to the session and
|
|
952
|
-
PR, with totals.
|
|
953
|
-
|
|
954
|
-
### Plans in pieces
|
|
955
|
-
|
|
956
|
-
A plan ends with a `## Pieces` block, one piece by default. AgentOS accepts
|
|
957
|
-
more only for one of four reasons, written on the piece: (a) it spans
|
|
958
|
-
repositories, (b) one part must be live (merged and deployed) before the
|
|
959
|
-
other can merge, (c) one part needs you mid-way, (d) one part must be
|
|
960
|
-
revertible on its own (a migration that rewrites rows, or an auth change
|
|
961
|
-
mixed with other work). Diff size is never a reason. A piece without a
|
|
962
|
-
standing reason is folded into the one before it, and a repository with no
|
|
963
|
-
deploy check can't claim (b); this is a deterministic check, not the model's
|
|
964
|
-
word.
|
|
965
|
-
|
|
966
|
-
A one-piece plan behaves as always. With several, piece 1 continues in the
|
|
967
|
-
planning task with only its slice, and the rest wait as rows (no worktree,
|
|
968
|
-
port or queue slot) until what they wait on has merged or deployed, then start
|
|
969
|
-
as tasks through the usual brakes and queue. A plan waiting for approval
|
|
970
|
-
shows its pieces: **Merge into ①** or **Drop** a piece (undoable until you
|
|
971
|
-
approve), **Approve with note…**, or **Reject**. Each piece's review judges
|
|
972
|
-
its slice; the last also checks the whole plan was built. **Tasks** shows
|
|
973
|
-
the feature's pieces and what each waits on, with **Release anyway** for one
|
|
974
|
-
held by a dropped or failed predecessor. A plan-only task that reaches
|
|
975
|
-
approval sends a "Plan ready: name, N pieces" push, and a finished feature
|
|
976
|
-
sends one "Feature merged" push naming it.
|
|
977
|
-
|
|
978
|
-
### Schedules
|
|
979
|
-
|
|
980
|
-
A schedule starts agent work at set times without you opening AgentOS:
|
|
981
|
-
**Schedules** in the workspace menu, the sidebar's **⋯** menu, or ⌘K. Each has a
|
|
982
|
-
name, a time (presets for hourly, daily, weekdays and weekly, or any
|
|
983
|
-
five-field cron expression, in America/Chicago unless you pick another zone),
|
|
984
|
-
a project and a prompt, and starts one of:
|
|
985
|
-
|
|
986
|
-
- **Task** and **Session**: now recipes (Settings > Recipes) with a timer.
|
|
987
|
-
Migration 81 turned each existing recurring task or session schedule into a
|
|
988
|
-
recipe, with its run history; the Schedules form no longer offers them. A
|
|
989
|
-
one-off that still has one of these kinds is left as it was. To undo the
|
|
990
|
-
move, stop AgentOS and run
|
|
991
|
-
`npx tsx scripts/revert-schedules-move.ts --db <path to agent-os.db>`.
|
|
992
|
-
- **Orchestrator**: the prompt, posted to the workspace's orchestrator as a
|
|
993
|
-
message from "Schedule <name>", marked as a scheduled prompt so it never
|
|
994
|
-
counts as an approval.
|
|
995
|
-
- **Message**: the prompt, sent to a session you pick the way `aos send`
|
|
996
|
-
does, marked the same way. A chat whose agent has exited (idle chats stop
|
|
997
|
-
after 30 minutes) is started again to take it. The run records `delivered`,
|
|
998
|
-
`queued` or why it failed. **Schedule check-ins** in a session's **⋯** menu
|
|
999
|
-
fills one in. The session is stored by id, so renaming it changes nothing.
|
|
1000
|
-
An archived session fails the run until it's back. When the session is
|
|
1001
|
-
deleted or a finished task, the schedule
|
|
1002
|
-
turns itself off on the first run that finds it gone and says so once, in
|
|
1003
|
-
the orchestrator's chat and on your phone; point it at another session or
|
|
1004
|
-
delete it. The orchestrator isn't a target (it has its own kind):
|
|
1005
|
-
`aos schedule add --session <the orchestrator>` makes an orchestrator
|
|
1006
|
-
schedule, so it follows whichever session is the workspace's orchestrator.
|
|
1007
|
-
A schedule an agent made with `aos schedule add` stays in that agent's
|
|
1008
|
-
workspace and reaches its target labelled as that agent's request, not
|
|
1009
|
-
yours.
|
|
1010
|
-
|
|
1011
|
-
The server checks once a minute and claims each run in SQLite before starting
|
|
1012
|
-
it, so a time never runs twice. If AgentOS was off when runs were due, it
|
|
1013
|
-
runs only the most recent one when it comes back, marked **caught up**. A run
|
|
1014
|
-
is **skipped** while the schedule's previous task or session is still working,
|
|
1015
|
-
and while the workspace's orchestrator is paused. A run that fails to start is
|
|
1016
|
-
recorded with why, noted in the orchestrator's chat and sent to your phone
|
|
1017
|
-
(see below), once per failing streak: not again until a run starts. Every run stays in the
|
|
1018
|
-
schedule's history with a link to what it started. **Run now** runs one
|
|
1019
|
-
immediately, even while the last run is still working (Pause still holds it).
|
|
1020
|
-
Task and session runs go through the orchestrator's brakes, like its own
|
|
1021
|
-
starts: a braked run is skipped with the brake's reason and tried again on the
|
|
1022
|
-
next minute until the brake lifts or a newer run is due (Run now obeys them
|
|
1023
|
-
too, once). A schedule whose project has moved to another workspace fails
|
|
1024
|
-
until it's edited. Removing a schedule keeps its history.
|
|
1025
|
-
|
|
1026
|
-
**One-off schedules.** Pick **Once** in the form (a date and time in the
|
|
1027
|
-
schedule's zone), or `aos schedule add --at "tomorrow 10am" ...` (an ISO time,
|
|
1028
|
-
`in 2h`, a weekday; America/Chicago unless `--tz`). It works with every kind,
|
|
1029
|
-
runs once and then turns itself off; the run stays in its history, marked
|
|
1030
|
-
done, and the schedule moves to the **Done** group. Upcoming one-offs are
|
|
1031
|
-
listed first with their run time, how far off it is and who made it (you or an
|
|
1032
|
-
orchestrator), each with Run now, Edit and Cancel; the count shows in the
|
|
1033
|
-
sidebar menu and the orchestrator's header. The same claim-in-SQLite rule
|
|
1034
|
-
applies, so it never runs twice, and one AgentOS missed while off runs when it
|
|
1035
|
-
returns, marked **caught up**. Pause holds a one-off until you resume rather
|
|
1036
|
-
than skipping it, and the brakes apply as for any run. A past time is refused.
|
|
1037
|
-
Run now uses up its only run; edit it to a new time to run it again. An
|
|
1038
|
-
orchestrator makes its own with `remind_me`: at least ten minutes ahead, 20
|
|
1039
|
-
pending at most, in its own workspace, and it arrives as a scheduled prompt,
|
|
1040
|
-
never an approval.
|
|
1041
|
-
|
|
1042
|
-
Task and session schedules run at most hourly, message schedules at most
|
|
1043
|
-
every 10 minutes and skip a run (`still working`) while their session is
|
|
1044
|
-
still busy, mid-turn or holding a queued message. A task schedule waits
|
|
1045
|
-
while any task it started is unfinished (working, waiting on you, in review
|
|
1046
|
-
or failing checks), so it can't stack up pull requests. Only one server
|
|
1047
|
-
process runs schedules (it holds a lease in the database); a dev server runs
|
|
1048
|
-
none unless `AGENTOS_SCHEDULES=on`, and `AGENTOS_SCHEDULES=off` stops them in
|
|
1049
|
-
production.
|
|
1050
|
-
|
|
1051
|
-
### Machine load
|
|
1052
|
-
|
|
1053
|
-
The dot in the sidebar header is the machine's load: green, amber (load
|
|
1054
|
-
over 1.5 per core, or memory pressure warning) or red (over 3 per core, or
|
|
1055
|
-
critical pressure), with some slack before it drops back. Tap it for the
|
|
1056
|
-
numbers and the sessions using the most CPU; a busy session's row shows its
|
|
1057
|
-
own share ("3.1 cores · 4.2 GB"), summed over its terminal's process tree
|
|
1058
|
-
from one `ps` every 30 seconds.
|
|
1059
|
-
|
|
1060
|
-
Nothing is ever stopped, paused or queued. Instead, before an agent starts a
|
|
1061
|
-
whole test suite, type check, build, repo-wide lint, `xcodebuild` or
|
|
1062
|
-
`pytest`, the Claude hook AgentOS installs tells it what heavy commands are
|
|
1063
|
-
already running and how loaded the machine is, so it can narrow the run or
|
|
1064
|
-
wait. Targeted runs (`vitest run lib/x.test.ts`) are left alone. `aos heavy
|
|
1065
|
-
-- <cmd>` does the same for anything else. When the load stays red for two
|
|
1066
|
-
minutes, one message goes to your phone and to each workspace orchestrator,
|
|
1067
|
-
naming the top sessions and their heavy commands; the next one waits until
|
|
1068
|
-
it has been green for ten minutes. `AGENTOS_LOAD=off` turns it all off.
|
|
1069
|
-
|
|
1070
|
-
### Tmux cleanup
|
|
1071
|
-
|
|
1072
|
-
Deleting, finishing, dropping or marking a session done ends its tmux session
|
|
1073
|
-
and every process its panes started, on the machine it runs on. At start,
|
|
1074
|
-
AgentOS also ends tmux sessions it created (its `<agent>-<uuid>` and
|
|
1075
|
-
`aos-chat-<uuid>` names, or the `AGENTOS_SESSION_ID` it sets at launch) whose
|
|
1076
|
-
session no longer exists, and logs each as `[tmux-sweep]`. Sessions it didn't
|
|
1077
|
-
create are never touched. The sweep runs only in production mode (a dev or test instance shares the tmux server but not the database); `AGENTOS_TMUX_SWEEP=off` skips it.
|
|
1078
|
-
|
|
1079
|
-
### Phone notifications
|
|
1080
|
-
|
|
1081
|
-
Two things reach your phone: a schedule run that failed, and whatever an
|
|
1082
|
-
agent sends on purpose with `aos notify "<text>"` (the orchestrator's morning
|
|
1083
|
-
report, a real milestone). Status changes don't; that's the in-app Needs-you
|
|
1084
|
-
list. Each source sends at most one a minute: anything more inside the minute
|
|
1085
|
-
waits and goes out as one message (kept in the database, so a restart
|
|
1086
|
-
doesn't lose it; past 20 waiting, more are dropped), and the same text twice
|
|
1087
|
-
goes once.
|
|
1088
|
-
|
|
1089
|
-
Set it up in **Settings → Notifications** (⌘K → Phone notifications, or the
|
|
1090
|
-
line at the bottom of Schedules), one of:
|
|
1091
|
-
|
|
1092
|
-
- **Command**: `AGENTOS_NOTIFY_CMD` in the server's environment, a shell
|
|
1093
|
-
command that gets the message on stdin. It wins when set. For example, a
|
|
1094
|
-
Telegram sender on another machine:
|
|
1095
|
-
`AGENTOS_NOTIFY_CMD='ssh -o BatchMode=yes me@box "NOTIFY_SOURCE=agentos ~/bin/notify"'`.
|
|
1096
|
-
- **Telegram**: a bot token and your chat id. The token is kept in the
|
|
1097
|
-
database and never shown again, returned by the API or logged.
|
|
1098
|
-
|
|
1099
|
-
#### Push to the phone app
|
|
1100
|
-
|
|
1101
|
-
A machine linked to Connect sends to the paired phones that have registered
|
|
1102
|
-
for push, instead of Telegram. A push is generic: the title "AgentOS", a
|
|
1103
|
-
category word and a link to open, with no session or task text (a plan's or
|
|
1104
|
-
feature's name is the one exception, scrubbed to a single line) (the payload is a
|
|
1105
|
-
versioned envelope, so an encrypted body can replace it later). It goes
|
|
1106
|
-
through the Connect relay, signed with the machine key; AgentOS keeps a
|
|
1107
|
-
`push_outbox` that retries with backoff, forgets tokens the relay reports as
|
|
1108
|
-
dead, and caps alerting pushes at 10 an hour per device (one summary after).
|
|
1109
|
-
|
|
1110
|
-
- **What pushes**: an ask raised, a session waiting on you for a minute,
|
|
1111
|
-
a task ready for review, CI or a review failing, a plan ready to approve,
|
|
1112
|
-
a feature merged, and the Away digest. Merged is off by default. Each device picks its
|
|
1113
|
-
categories (`PATCH /api/devices/me/push`). A task mirrored from another
|
|
1114
|
-
machine never pushes from here.
|
|
1115
|
-
- **Quiet rules**: waiting, review, CI and merged don't push while you're at
|
|
1116
|
-
this Mac (keyboard or mouse used in the last 3 minutes). While Away is on,
|
|
1117
|
-
everything except asks folds into the 10-minute digest.
|
|
1118
|
-
- **Fallback**: agent messages, the Away digest and machine alerts (load,
|
|
1119
|
-
schedules) go to Telegram when push isn't configured or fails. The newer
|
|
1120
|
-
events are push-only. If Connect says push isn't set up yet, AgentOS
|
|
1121
|
-
stops asking for 10 minutes and uses Telegram meanwhile.
|
|
1122
|
-
- **Check it**: `aos push test` sends a real push to each registered phone
|
|
1123
|
-
and prints what came back; Settings → Devices shows each device's push
|
|
1124
|
-
state, categories, last error and a Send test button.
|
|
1125
|
-
|
|
1126
|
-
## Agent network
|
|
1127
|
-
|
|
1128
|
-
Sessions started by AgentOS can find and talk to each other without you,
|
|
1129
|
-
through the `aos` command on their PATH (any agent CLI can use it; Claude is
|
|
1130
|
-
also briefed on it):
|
|
1131
|
-
|
|
1132
|
-
In a chat, a message from another session shows as theirs, with the
|
|
1133
|
-
sender's name, rather than as one you typed.
|
|
1134
|
-
|
|
1135
|
-
```bash
|
|
1136
|
-
aos peers # sessions, their project, what each is doing
|
|
1137
|
-
aos send <session> "message" # wakes the other session with the message
|
|
1138
|
-
aos inbox # read messages sent to you
|
|
1139
|
-
aos history <session> # your conversation with a session
|
|
1140
|
-
aos spawn <project> "prompt" # start a new agent session in a project
|
|
1141
|
-
aos task <project> "prompt" # start a background task that ends in a PR
|
|
1142
|
-
# (both take --name "..." before the prompt;
|
|
1143
|
-
# task takes --after <task|any> to queue it;
|
|
1144
|
-
# a task opens as a chat, --terminal for tmux)
|
|
1145
|
-
aos move <session> <machine> # carry a task on there ("here" brings it back)
|
|
1146
|
-
aos stack <project> [--plan] # run the project's board as stacked tasks
|
|
1147
|
-
aos stacks # every stack and where each card is
|
|
1148
|
-
aos schedules # every schedule, its next run and last outcome
|
|
1149
|
-
aos schedule run <name> # run a schedule now
|
|
1150
|
-
aos schedule add --session <s> --every 30m "prompt" # check in on a session
|
|
1151
|
-
aos notify "text" # push a message to your phone
|
|
1152
|
-
aos push test # a real test push to the paired phones, and the result
|
|
1153
|
-
aos heavy -- npm test # run it now; says first what else heavy is running
|
|
1154
|
-
aos stop <session> # stop its turn now; the next message runs as a new turn
|
|
1155
|
-
aos done <session> # finished: merge through the gates, archive
|
|
1156
|
-
aos done --all-idle # the same for every idle session around you
|
|
1157
|
-
aos docs [query] # LumifyHub pages, when the workspace is linked
|
|
1158
|
-
```
|
|
1159
|
-
|
|
1160
|
-
Messages are delivered by typing them into the recipient's terminal, labelled
|
|
1161
|
-
as coming from another agent, and a rate limit stops two agents looping.
|
|
1162
|
-
Delivery is checked against the pane: the text has to reach the input and
|
|
1163
|
-
leave it on Enter (retried once as a bracketed paste), and a menu on screen is
|
|
1164
|
-
never typed into. `aos send` says `delivered`, `queued` (the agent was busy and
|
|
1165
|
-
will see it after its turn) or `FAILED: <why>` with exit code 2; a chat session
|
|
1166
|
-
counts as delivered once its worker records the message. Either way the
|
|
1167
|
-
message waits in the recipient's `aos inbox`.
|
|
1168
|
-
|
|
1169
|
-
Sessions are addressed by name, `project/name`, id or a unique id prefix, and
|
|
1170
|
-
renaming one keeps its old names: a message to "Session 3" still reaches the
|
|
1171
|
-
session now called "orchestrator", and `aos peers` shows
|
|
1172
|
-
`orchestrator (was Session 3)`. If another session has since taken the old
|
|
1173
|
-
name, the current owner wins and `aos send` says so. A name that matches more
|
|
1174
|
-
than one session is refused with the candidates listed. The
|
|
1175
|
-
**Messages** panel shows every conversation and lets you message any session.
|
|
1176
|
-
Sessions on other machines can receive messages but not yet send them.
|
|
1177
|
-
|
|
1178
|
-
## The sidebar
|
|
1179
|
-
|
|
1180
|
-
One flat list of every session in the current workspace, each row naming its
|
|
1181
|
-
project underneath. The workspace's orchestrator comes first, as a compact row
|
|
1182
|
-
with its state and how many of its asks wait on you; it starts pinned there,
|
|
1183
|
-
and unpinning it from its ⋯ menu (remembered per workspace) drops it among the
|
|
1184
|
-
other sessions. Sessions sit on shelves: **Pinned** (pin from a row's ⋯
|
|
1185
|
-
menu), **Needs you** (an approval, a question, a terminal waiting for input, a
|
|
1186
|
-
message typed and never sent, a failed task, or the orchestrator's open asks,
|
|
1187
|
-
each with a badge), **Working**,
|
|
1188
|
-
and **Done**. Done is folded to one row, "3 done today · 12 earlier", until you
|
|
1189
|
-
tap it (open or folded is remembered per device); open, it lists newest first,
|
|
1190
|
-
ten at a time. The orchestrator's row carries small chips for the work it
|
|
1191
|
-
started: running, in review, blocked and queued, each left out at zero. Tap
|
|
1192
|
-
them to show only that orchestrator's work on the same shelves; the "Showing
|
|
1193
|
-
… work ×" chip at the top clears it (it resets on reload). A session an
|
|
1194
|
-
orchestrator started has a small orchestrator icon on its row. A purple dot marks a session that
|
|
1195
|
-
finished or changed since you last opened it. Search matches
|
|
1196
|
-
titles and project names, and the project filter narrows the list to one
|
|
1197
|
-
project and holds that project's own actions: new session, terminal, dev
|
|
1198
|
-
server, settings, board, workspace.
|
|
1199
|
-
|
|
1200
|
-
Every session has its own address, `/?session=<id>`: open it in a new tab,
|
|
1201
|
-
on a phone or from a bookmark and that session opens; **Copy link** in a row's
|
|
1202
|
-
⋯ menu or the pane's menu copies it. The address bar follows the focused
|
|
1203
|
-
pane's session, each switch is a history entry so back and forward move
|
|
1204
|
-
between sessions, and an address for an archived or unknown session says so
|
|
1205
|
-
and opens the usual home. A device that isn't paired is sent through pairing
|
|
1206
|
-
and comes back to the same session; the address grants nothing by itself.
|
|
1207
|
-
|
|
1208
|
-
**Settings** (the sidebar's ⋯ menu, or ⌘K → Settings) holds every setting in
|
|
1209
|
-
one place: **Merging** (the approval switch, merge method and branch
|
|
1210
|
-
clean-up, with the projects that override them), **Workspaces** (each one's
|
|
1211
|
-
running task limit), **Devices & access** (pairing, passkeys, network),
|
|
1212
|
-
**Notifications** (this browser's sound and alerts, and your phone) and
|
|
1213
|
-
**Schedules**. Each section has its own address, `/?settings=<section>`
|
|
1214
|
-
(`merging`, `workspaces`, `devices`, `notifications`, `schedules`), beside
|
|
1215
|
-
`?session=`; on a phone the sections are a list, then the one you open.
|
|
1216
|
-
|
|
1217
|
-
Rows move the moment a session changes state: the server pushes every change
|
|
1218
|
-
over `/ws/status` (behind the same device gate as the terminal). The same
|
|
1219
|
-
stream says when anything else on screen changed: database triggers mark the
|
|
1220
|
-
tables written (sessions, stacks, schedules, asks, bus messages and more,
|
|
1221
|
-
whoever wrote them), and the git panel's folders are polled once on the
|
|
1222
|
-
server for every viewer. The browser refetches only what moved, so an idle tab
|
|
1223
|
-
makes about one request a minute instead of forty. Messages are numbered: a
|
|
1224
|
-
reconnecting phone picks up what it missed, or takes a fresh snapshot when
|
|
1225
|
-
it's been away too long. While the socket is down, the old polling carries on.
|
|
1226
|
-
API JSON is gzipped and the sockets use permessage-deflate.
|
|
1227
|
-
|
|
1228
|
-
**AgentOS reads OSC 7501**, the [Program Status
|
|
1229
|
-
Protocol](https://www.superlogical.com/rex/docs/build/program-status): a
|
|
1230
|
-
program in a terminal session can say it's `working`, `blocked` (on a
|
|
1231
|
-
permission, a question or a sign-in), `done`, or failed, and that wins over
|
|
1232
|
-
reading the screen. Blocked lands on Needs you with Approve, Answer or Sign in
|
|
1233
|
-
and the program's message (plain text, capped); done gets the unread dot.
|
|
1234
|
-
|
|
1235
|
-
```sh
|
|
1236
|
-
printf '\e]7501;state=blocked:kind=permission:app=me:msg=%s\e\\' "$(printf 'Apply 3 changes?' | base64)"
|
|
1237
|
-
```
|
|
1238
|
-
|
|
1239
|
-
tmux drops sequences it doesn't know, so AgentOS watches each local terminal
|
|
1240
|
-
session through tmux control mode: one long-lived, read-only `tmux -C`
|
|
1241
|
-
client per session (tmux 3.2 or newer) pushes the pane's raw output, which
|
|
1242
|
-
carries the reports, and keeps a copy of its screen for reading status, so
|
|
1243
|
-
nothing polls `capture-pane` or taps panes with `pipe-pane`. A screen is kept
|
|
1244
|
-
only while its pane prints, and it's checked against tmux again whenever
|
|
1245
|
-
output settles. A session's terminal views share one `tmux attach` (it runs at
|
|
1246
|
-
the smallest view's size), and a view that can't keep up is held back and sent
|
|
1247
|
-
the current screen once it catches up. On older tmux, or with
|
|
1248
|
-
`AGENTOS_TMUX_CONTROL=off`, AgentOS falls back to copying each session's
|
|
1249
|
-
output to itself (`tmux pipe-pane`, through a unix socket only you can reach;
|
|
1250
|
-
needs `nc`) and reading screens with `capture-pane`. Claude Code sessions AgentOS starts on this
|
|
1251
|
-
machine report through hooks it installs (`~/.agent-os/claude-status-hooks.json`,
|
|
1252
|
-
passed with `--settings`). Not covered yet: sessions on other machines (their
|
|
1253
|
-
output never passes through this one), Codex and the other agents (Codex only
|
|
1254
|
-
notifies when a turn ends, with nothing for "working" or "blocked"; they keep
|
|
1255
|
-
reading the screen), and the terminfo
|
|
1256
|
-
`Pst` capability (the pane's terminal is tmux's).
|
|
1257
|
-
|
|
1258
|
-
The screen covers what no report says. A selection menu at the bottom of a
|
|
1259
|
-
terminal (Claude Code's "Enter to select · ↑/↓ to navigate", a Codex "› 1."
|
|
1260
|
-
list) lands on Needs you with **Answer** and its question, for sessions
|
|
1261
|
-
without the hooks. Text typed into a Claude Code or Codex input box and left
|
|
1262
|
-
unchanged for a minute while the agent sits idle lands there with **Unsent**
|
|
1263
|
-
and the text, so a message that never got sent doesn't wait for hours. The
|
|
1264
|
-
dim suggested prompt in the same box doesn't count. Claude Code fires no hook
|
|
1265
|
-
when a question is dismissed with Esc, so a question report clears once its
|
|
1266
|
-
input box is back on screen.
|
|
1267
|
-
|
|
1268
|
-
## Workspaces
|
|
1269
|
-
|
|
1270
|
-
Group projects into workspaces (e.g. Work, Personal) from the workspace
|
|
1271
|
-
switcher at the top of the sidebar, which also picks the workspace the list
|
|
1272
|
-
shows and holds the current one's orchestrator, clean-up, Archived and
|
|
1273
|
-
LumifyHub link. Move a project in from the project filter's menu. Deleting a
|
|
1274
|
-
workspace keeps its projects.
|
|
1275
|
-
|
|
1276
|
-
### Done
|
|
1277
|
-
|
|
1278
|
-
**Done** (a session's ⋯ menu; `aos
|
|
1279
|
-
done`; or the orchestrator's `done` tool) is for finished work, where
|
|
1280
|
-
**Stop** keeps everything and **Drop** rejects it. A task with an open PR
|
|
1281
|
-
merges first, only through the orchestrator's gates at the judged commit; a
|
|
1282
|
-
failing gate refuses with its name and nothing merges. A task already
|
|
1283
|
-
merged, or a session with no PR, just cleans up. If GitHub can't be read, or
|
|
1284
|
-
a task that had a PR has none now, done refuses rather than guess.
|
|
1285
|
-
|
|
1286
|
-
Cleanup stops the agent and archives the session. The worktree goes only if
|
|
1287
|
-
nothing in it would be lost: never with uncommitted changes; after a merge,
|
|
1288
|
-
only when every commit on it is in the merged PR or on a remote; otherwise
|
|
1289
|
-
only when its branch has no commits of its own. Anything else is kept and the
|
|
1290
|
-
reply says why, naming the uncommitted files. Files setup copied in (env
|
|
1291
|
-
files, agentos.json's `copy`) don't count while they still hold exactly what
|
|
1292
|
-
was copied; setup records them, hashed from the main checkout, in the
|
|
1293
|
-
worktree's git directory. Cloned dependencies are gitignored, so never count. A sign-off from Tasks or the orchestrator follows the same
|
|
1294
|
-
rule. A branch merged on GitHub is deleted on origin when origin's tip is the
|
|
1295
|
-
commit that merged.
|
|
1296
|
-
|
|
1297
|
-
Archived sessions leave the sidebar, the needs-you count, session statuses
|
|
1298
|
-
and the orchestrator's view, but are never deleted; the **Archived** view
|
|
1299
|
-
(the workspace switcher for one workspace, or ⋯ in the sidebar header for
|
|
1300
|
-
all of them) lists
|
|
1301
|
-
them with Unarchive, which only puts the entry back.
|
|
1302
|
-
|
|
1303
|
-
**Clean up idle sessions** (the workspace switcher) first shows what will happen
|
|
1304
|
-
to each idle or stopped session, and `aos done --all-idle` does the same
|
|
1305
|
-
sweep. A clean-up never merges: open PRs are listed for their own done.
|
|
1306
|
-
`aos done` reaches only sessions in the caller's workspace (or project).
|
|
1307
|
-
|
|
1308
|
-
### Session logs
|
|
1309
|
-
|
|
1310
|
-
When a task merges, AgentOS writes a log of it. It costs no model tokens: the
|
|
1311
|
-
log is a fixed template filled from records AgentOS already holds, and nothing
|
|
1312
|
-
that makes it can call a model (a test holds it to that). Dropped tasks get
|
|
1313
|
-
none.
|
|
1314
|
-
|
|
1315
|
-
A log holds:
|
|
1316
|
-
|
|
1317
|
-
- **Header:** task name and id, workspace, project, branch, PR number and
|
|
1318
|
-
link, merge commit, start, merge time and duration.
|
|
1319
|
-
- **Brief:** the task's prompt and every scope change recorded on it, in
|
|
1320
|
-
order.
|
|
1321
|
-
- **Plan:** `.agentos/plan.md`, read at the merge before cleanup removes the
|
|
1322
|
-
worktree. A task with no plan says so.
|
|
1323
|
-
- **Timeline:** PR opened, gate failures per commit (CI with the failing
|
|
1324
|
-
check's name, review, scope), each review and scope verdict per commit with
|
|
1325
|
-
its text trimmed, escalations to you and your answers, and the merge.
|
|
1326
|
-
- **Usage:** tokens, estimated cost and model per role, from the usage ledger.
|
|
1327
|
-
- **Outcome:** the task agent's last message, verbatim.
|
|
1328
|
-
- **Links:** the PR and the session.
|
|
1329
|
-
|
|
1330
|
-
Text from tasks, CI and reviewers is untrusted. It is written inside fences
|
|
1331
|
-
or code spans, so it can't add headings or links to the document, and
|
|
1332
|
-
credential-shaped strings are redacted.
|
|
1333
|
-
|
|
1334
|
-
The database is the record: `session_logs`, one row per task, never deleted.
|
|
1335
|
-
Each log is also written to a file, `YYYY-MM-DD-<task>.md`, in the first of:
|
|
1336
|
-
|
|
1337
|
-
1. the project's **Session log folder** (project settings; an absolute or `~`
|
|
1338
|
-
path),
|
|
1339
|
-
2. the workspace's **HQ folder** (workspace settings):
|
|
1340
|
-
`<hq>/docs/sessions/<project>/`,
|
|
1341
|
-
3. `~/.agent-os/session-logs/<workspace>/<project>/`.
|
|
1342
|
-
|
|
1343
|
-
A folder inside the project's repository is refused, because repositories can
|
|
1344
|
-
be public, and the files are never written into one.
|
|
1345
|
-
|
|
1346
|
-
The HQ folder is a git repo. Saving it checks that it is a repository, that it
|
|
1347
|
-
isn't inside or equal to any of the workspace's project repos, and (with one
|
|
1348
|
-
`gh repo view --json visibility`) that its GitHub remote isn't public; the
|
|
1349
|
-
result is stored. When a log lands in the HQ it is committed there as
|
|
1350
|
-
`session log: <task name>`, only that file, whatever else is changed in the
|
|
1351
|
-
tree, and pushed to the current branch's upstream only if the repo is
|
|
1352
|
-
private. A failed commit or push is logged and never reaches the merge; the
|
|
1353
|
-
next push carries the earlier commit. Commits to one HQ run one at a time.
|
|
1354
|
-
The database row is the record either way.
|
|
1355
|
-
|
|
1356
|
-
The same dialog lists the project's logs, newest first, and a merged task's
|
|
1357
|
-
view has a **Log** tab. The orchestrator reads them with its `logs` tool: the
|
|
1358
|
-
latest headers of a workspace or project, or one log in full. It only reads.
|
|
1359
|
-
|
|
1360
|
-
The log is made once per task, in the background, from wherever the merge
|
|
1361
|
-
happens: a sign-off, Done, a stack landing, or a merge seen on GitHub. A
|
|
1362
|
-
failure is logged and never reaches the merge. For tasks that merged before
|
|
1363
|
-
this existed:
|
|
1364
|
-
|
|
1365
|
-
```bash
|
|
1366
|
-
npx tsx scripts/backfill-session-logs.ts --db <path to agent-os.db> [--no-gh] [--dry] [--reexport]
|
|
1367
|
-
```
|
|
1368
|
-
|
|
1369
|
-
Their plan files are gone, so those logs say "plan not kept". Files go to the
|
|
1370
|
-
same destinations as above; into an HQ the backfill commits once at the end.
|
|
1371
|
-
`--reexport` writes no new logs: it rewrites the files of logs already in the
|
|
1372
|
-
database to where they'd go now, from the stored markdown (no rebuild, no
|
|
1373
|
-
model), leaves the old files in place, and does nothing on a second run.
|
|
1374
|
-
|
|
1375
|
-
### Orchestrator
|
|
1376
|
-
|
|
1377
|
-
Each workspace has one standing orchestrator, pinned at the top of its
|
|
1378
|
-
section: a chat that runs the work across the workspace's projects. It's made
|
|
1379
|
-
the first time you open it, works from a scratch folder in
|
|
1380
|
-
`~/.agent-os/orchestrators/<workspace>`, and is never swept with idle sessions.
|
|
1381
|
-
Its brief lists the workspace's projects (path, board, default branch), what
|
|
1382
|
-
it may do on its own, and the lines that always come back to you as asks.
|
|
1383
|
-
|
|
1384
|
-
- **Tools:** `sessions` (every session's status, activity, task, PR, CI and
|
|
1385
|
-
stack position), `read` (the end of a terminal or chat) and `cards` (the
|
|
1386
|
-
linked boards' cards), plus acting tools: `send`, `start_task`,
|
|
1387
|
-
`start_session`, `stack`, `stack_status`, `land`, `drop`, `stop`, `done`, `move`,
|
|
1388
|
-
`add_project` (a git repository on the machine becomes a project of the
|
|
1389
|
-
workspace, or the project already at that path moves in from no workspace;
|
|
1390
|
-
folders that hold secrets and projects of another workspace are refused),
|
|
1391
|
-
`note`,
|
|
1392
|
-
`rule_add`, `rule_remove`, `review`, `sign_off`, `arm`, `disarm`, `remind_me`, `reminders`, `cancel_reminder`, `ask_saad`, `question` and `resolve_question`. All are served in-process to its chat, refuse
|
|
1393
|
-
any target outside its workspace, and validate their arguments. The one
|
|
1394
|
-
exception is other orchestrators: `orchestrators` lists every workspace and
|
|
1395
|
-
whether it has one, and `message_orchestrator` messages another workspace's
|
|
1396
|
-
current orchestrator by workspace name (never any other session there). It
|
|
1397
|
-
arrives marked as from that orchestrator and fenced as untrusted, so it can't
|
|
1398
|
-
approve, answer an ask or pass a gate. The route
|
|
1399
|
-
they call answers only the orchestrator's worker, by a per-orchestrator
|
|
1400
|
-
secret.
|
|
1401
|
-
- **Shell:** it runs anything without asking, so an orchestrator can read and
|
|
1402
|
-
investigate on its own: `gh pr list`, `gh run view --log-failed`, `gh api`
|
|
1403
|
-
GETs, `curl`, `sqlite3 -readonly`, git in any repository, a skill's scripts,
|
|
1404
|
-
pipes and chains. Its chat worker refuses a deny list before a command runs,
|
|
1405
|
-
naming the tool to use instead: merging, closing or retargeting a PR
|
|
1406
|
-
(`gh pr merge`, `gh pr close`, `gh pr edit --base`, and `gh api` or `curl`
|
|
1407
|
-
calls that merge a pull request or PATCH or PUT one; use `sign_off`, `arm`,
|
|
1408
|
-
`land` or `drop`), `aos move`, `aos done` and `aos stop` (use `move`, `done` and
|
|
1409
|
-
`stop`, which are scoped to the workspace and held by Pause), `git push` (`quick_pr` pushes), writing in a project
|
|
1410
|
-
repository or task worktree other than its own open quick changes (git
|
|
1411
|
-
commit, reset, checkout, rebase, merge, stash, clean or rm there, and `rm`,
|
|
1412
|
-
`mv`, `cp`, `tee` or a redirect into one), writing AgentOS's state (`sqlite3`
|
|
1413
|
-
on its database without `-readonly` or with INSERT, UPDATE, DELETE, DROP,
|
|
1414
|
-
ALTER or ATTACH; files under `~/.agent-os` outside its folder), stopping
|
|
1415
|
-
AgentOS or its tmux (`pkill`/`kill` aimed at it, `tmux kill-*`, a
|
|
1416
|
-
`launchctl` or `serviceman` restart), and reading secret files (the same
|
|
1417
|
-
list as the write guard; tokenvault is allowed, through `tv` and its folders). It reads each
|
|
1418
|
-
command line the way a shell would, so these are caught inside pipes,
|
|
1419
|
-
chains and `$( )`, behind `bash -c`, `sh -c`, `env`, `sudo`, `xargs`,
|
|
1420
|
-
`find -exec` or an absolute path, and after a `cd` or `git -C`. **The deny
|
|
1421
|
-
list catches honest mistakes, not a determined bypass:** a script or
|
|
1422
|
-
`python -c` can do any of these where no parser sees it, and an
|
|
1423
|
-
orchestrator reads other sessions' text, which can carry planted
|
|
1424
|
-
instructions. The trade-off was chosen on 2026-10-11, for an orchestrator
|
|
1425
|
-
that does its own investigating instead of handing it to him.
|
|
1426
|
-
- **Triage:** its brief has it decide each job: small and clear is a task with
|
|
1427
|
-
`plan: false` and the steps written into the brief; big, unclear or
|
|
1428
|
-
cross-cutting is `plan: true`; work outside a repository is a
|
|
1429
|
-
`start_session` (Sonnet) with the steps in the prompt. It puts
|
|
1430
|
-
implementation on Opus only when the work needs deep reasoning, and says why.
|
|
1431
|
-
- **Files:** it reads anywhere and writes (Write, Edit, NotebookEdit) anywhere
|
|
1432
|
-
except a project repository, a task worktree (`~/.agent-os/worktrees`),
|
|
1433
|
-
AgentOS's own install and state (all of `~/.agent-os` but its own folder),
|
|
1434
|
-
secret files (`.env`, keys, `~/.ssh`, `~/.aws`, `gh`'s token, tokenvault and
|
|
1435
|
-
the like; the list the merge gates use for secrets handling,
|
|
1436
|
-
`lib/secret-paths.ts`), and files that make something run: dotfiles and
|
|
1437
|
-
dot-folders at the top of its home (shell, git, Claude and tool config, the
|
|
1438
|
-
usage brake's `limits.json`), `~/Library`, `/Library/LaunchAgents` and
|
|
1439
|
-
`LaunchDaemons`, git's `core.hooksPath` folders (yours and each project's),
|
|
1440
|
-
the folders on `PATH`, and any `.git`, `.claude`, `.mcp.json`, `CLAUDE.md`
|
|
1441
|
-
or `AGENTS.md`, since writing one would give it a shell or run code in
|
|
1442
|
-
AgentOS's own git; and the temporary checkouts a review or rebase runs in.
|
|
1443
|
-
Code changes to a project go through a task, so its PR still passes CI,
|
|
1444
|
-
review and scope. It is a list of exceptions, not of allowed places: a
|
|
1445
|
-
script a hook or scheduled job runs from an ordinary folder can still be
|
|
1446
|
-
written, so an orchestrator misled by text it read could run code that way.
|
|
1447
|
-
That's accepted: task agents can already write anywhere, so an allowlist
|
|
1448
|
-
on the orchestrator alone would buy little.
|
|
1449
|
-
- **MCP servers.** An orchestrator runs without prompts, so an MCP server's
|
|
1450
|
-
tools are refused unless its workspace allows the server. Settings →
|
|
1451
|
-
Workspaces lists the servers configured on the machine (names only: user
|
|
1452
|
-
servers, connectors and plugin servers; never their env, headers or tokens)
|
|
1453
|
-
as toggles, and `linear` is allowed by default. A ticked server adds a bare
|
|
1454
|
-
`mcp__<server>` rule to the orchestrator's allowed tools, covering all its
|
|
1455
|
-
tools. Its worker ends at its next turn boundary when the list changes, and
|
|
1456
|
-
the next one starts with the new permissions. Sending a message, email or
|
|
1457
|
-
post through a server stays a hard line the brief sends to `ask_saad`.
|
|
1458
|
-
Its chat worker checks every write in a PreToolUse hook before it runs, on
|
|
1459
|
-
the path with symlinks and `..` resolved as the system will, and refuses
|
|
1460
|
-
with the reason; the repositories are read from the database on each write,
|
|
1461
|
-
so a project added since counts. A worker from before an update picks the
|
|
1462
|
-
new permissions up at its handover to the new build.
|
|
1463
|
-
- **Brakes:** every start is refused, with the reason, while 4 sessions run in
|
|
1464
|
-
the workspace, after 6 starts in an hour (`orch_max_running` and
|
|
1465
|
-
`orch_max_starts_per_hour` on `workspaces`), or when the account's 5-hour
|
|
1466
|
-
usage window would run out before it resets (read from the statusline's
|
|
1467
|
-
`~/.claude/.context-cost/limits.json`, as dispatch does;
|
|
1468
|
-
`AGENTOS_LIMITS_FILE` overrides). A missing file, or one not sampled for 15
|
|
1469
|
-
minutes, refuses too. Each card of a stack it starts is braked as its own
|
|
1470
|
-
start. A brake writes one note and pauses only new starts.
|
|
1471
|
-
- **Review and sign-off:** `review` runs a fresh `claude -p` on a
|
|
1472
|
-
symlink-free detached checkout of the PR's exact head commit. It loads no
|
|
1473
|
-
settings or MCP servers from anywhere (so nothing the PR ships runs), gets
|
|
1474
|
-
an allowlisted environment, can only Read/Grep/Glob inside the checkout,
|
|
1475
|
-
and follows the repo's review agents' rules (`.claude/agents/review-*.md`), or its review skill, as the base branch has them. The verdict
|
|
1476
|
-
is stored against that sha. A diff over 80k characters is reviewed in up
|
|
1477
|
-
to 6 parts of whole files, each against the task, and fails if any part
|
|
1478
|
-
does; a single file over 80k, or more than 6 parts, goes to you. A task from a card also gets a scope check against the card.
|
|
1479
|
-
When you change a running task's scope, the orchestrator tells it with
|
|
1480
|
-
`send` and `scope_change`, which records the message on the task's brief
|
|
1481
|
-
(`brief_amendments`, logged in its chat): the review and the card's scope
|
|
1482
|
-
check judge the PR against the brief as amended, the amendments winning,
|
|
1483
|
-
and a "Scope change" note in the PR body counts only where one backs it.
|
|
1484
|
-
`sign_off` merges only that commit (with the project's merge method) when CI is green and settled on
|
|
1485
|
-
it (2 minutes with no new check), its review passed, nothing is
|
|
1486
|
-
`BLOCKED:` or waiting, the diff stays in scope (no secrets, not only
|
|
1487
|
-
lockfiles, nothing outside the repo, within the card) and its stack parent
|
|
1488
|
-
has merged, and the PR body carries no AI attribution. `land` judges each item again at its own head right before
|
|
1489
|
-
merging it. A gate failing again on a different commit, or a repo with no
|
|
1490
|
-
CI, goes to you as an ask, and the orchestrator stops merging that task.
|
|
1491
|
-
A failure counts once per task, gate and head commit, however many times
|
|
1492
|
-
and from wherever that commit is judged (the armed watcher, CI finishing,
|
|
1493
|
-
`sign_off`, `done`, `land`); a pass in between doesn't reset it. A review
|
|
1494
|
-
that blocks counts when its verdict arrives, whether or not `sign_off`
|
|
1495
|
-
ever reads it.
|
|
1496
|
-
- **Arm to merge:** `arm` (or **Arm merge** on a task's row) makes AgentOS
|
|
1497
|
-
merge the task's PR itself the moment every `sign_off` gate passes at its
|
|
1498
|
-
head, starting the review once CI is green, so nothing polls `sign_off`.
|
|
1499
|
-
The orchestrator hears one event: merged, or the failure (counted once per
|
|
1500
|
-
head; the task stays armed for the fix, and one that goes to you disarms
|
|
1501
|
-
it). The flag is in the database, so it survives a restart; `disarm`
|
|
1502
|
-
takes it back.
|
|
1503
|
-
- **Automatic rebase:** when the base moves and GitHub says a task's PR now
|
|
1504
|
-
conflicts (read in the PR poll AgentOS already makes), `git merge-tree`
|
|
1505
|
-
checks it against the base. Clean, and with the task's agent idle and
|
|
1506
|
-
nothing uncommitted, AgentOS rebases it in the task's worktree and pushes
|
|
1507
|
-
only its branch with `--force-with-lease`, and the orchestrator's review
|
|
1508
|
-
carries over when the PR's own patch is
|
|
1509
|
-
unchanged (same `git patch-id`; a fresh review starts otherwise). Your
|
|
1510
|
-
merge approval carries over a rebase the same way (any rebase, the task's
|
|
1511
|
-
own included): when the PR's patch is unchanged it follows the new head,
|
|
1512
|
-
else you're asked again, naming the new head and what changed, and an open
|
|
1513
|
-
merge ask follows its PR's head. Not
|
|
1514
|
-
clean: the task is asked once to rebase. Either way, one event. Main and
|
|
1515
|
-
other tasks' branches are never written.
|
|
1516
|
-
- **Tasks woken on CI failures and reviews:** a check failing on a task's
|
|
1517
|
-
PR head messages the task's own agent once per head, with the check, the
|
|
1518
|
-
failing step and about 60 lines of `gh run view --log-failed` around the
|
|
1519
|
-
error, telling it to fix the root cause and push. A review or comment from a collaborator (not the account gh runs
|
|
1520
|
-
as, which is the task's own) wakes it once per review or comment; one
|
|
1521
|
-
GraphQL query per repository a minute fingerprints the open task PRs, and
|
|
1522
|
-
only a PR whose fingerprint moved has its reviews read. Comment-only wakes
|
|
1523
|
-
stop at 10 a task; changes requested always wake. A task mid-turn gets it
|
|
1524
|
-
queued, the orchestrator hears one line per wake, and merged, closed,
|
|
1525
|
-
dropped or done tasks aren't watched. On by default; **Settings →
|
|
1526
|
-
Workspaces** turns it off per workspace.
|
|
1527
|
-
- **External PRs and sessions:** `review` and `sign_off` also take an open
|
|
1528
|
-
PR no AgentOS task owns (dispatch, a person, dependabot) by `#N`,
|
|
1529
|
-
`owner/repo#N` or URL, as long as its repository is one of the workspace's
|
|
1530
|
-
projects; anything else is refused. The same gates apply, except that a
|
|
1531
|
-
draft waits and a "blocked", "do not merge" or "wip" label holds it (there's
|
|
1532
|
-
no session to read). `sessions` also lists tmux
|
|
1533
|
-
sessions AgentOS didn't start that work in a workspace repository or a
|
|
1534
|
-
worktree of it, here or on another machine, with their branch's PR; `read`
|
|
1535
|
-
reads them, and nothing else acts on them.
|
|
1536
|
-
- **Merge approvals** (Settings → Merging, off by default): on, any change to
|
|
1537
|
-
CI config, build and hook scripts, agent config, deploy scripts, secrets
|
|
1538
|
-
handling or AgentOS's security code, and any diff over 80k characters, goes
|
|
1539
|
-
to you as an ask whatever the gates say, and merges once on your passkey.
|
|
1540
|
-
Off, those merge through the gates like any other PR. Switching it off
|
|
1541
|
-
again needs your passkey.
|
|
1542
|
-
- **Decision log:** `note` and the brakes and escalations write to
|
|
1543
|
-
`orchestrator_notes`, and each line shows in its chat.
|
|
1544
|
-
- **Standing rules:** what it must keep knowing ("never merge #111", "briefs
|
|
1545
|
-
must not name third-party products") it keeps with `rule_add` and drops with
|
|
1546
|
-
`rule_remove`, in `orchestrator_rules`. You see, add, edit and remove them in
|
|
1547
|
-
Settings → Workspaces, each change with your passkey (any process on the
|
|
1548
|
-
machine reaches the route, and a rule of yours is your word to it). A rule restricts or informs; it never approves an
|
|
1549
|
-
ask, passes a gate or clears a hard line. Yours are marked as from you and
|
|
1550
|
-
only you remove them; the ones it kept, like its decision-log lines, come
|
|
1551
|
-
back to it fenced as its own notes, so text it read can't pass for your
|
|
1552
|
-
word. Rule text is redacted when stored, and removed rules stay on record.
|
|
1553
|
-
- **Where things stand:** every start appends a digest read straight from
|
|
1554
|
-
the database, no model involved: the standing rules, open asks, running
|
|
1555
|
-
tasks with their PR, armed-to-merge and gate state, recently gated external
|
|
1556
|
-
PRs, the queue, schedules and the newest decision-log lines, capped at about
|
|
1557
|
-
4k tokens. The brief tells it this is authoritative over what it remembers.
|
|
1558
|
-
- **Fresh starts:** fed events nonstop, an orchestrator never idles and can't
|
|
1559
|
-
compact itself. Once its context passes a threshold (200k tokens by
|
|
1560
|
-
default, Settings → Workspaces → Fresh starts), its worker ends the
|
|
1561
|
-
conversation at the next turn boundary and the next one starts a new
|
|
1562
|
-
conversation with the brief and digest, telling the agent in one line why.
|
|
1563
|
-
Nothing is lost, because its state lives in the database; its chat goes on,
|
|
1564
|
-
with a line where it started over. The same can be switched on for chat
|
|
1565
|
-
tasks (off by default: a task keeps only its brief and its branch).
|
|
1566
|
-
**Start fresh by hand:** after a deploy that changes its permissions or
|
|
1567
|
-
brief, use **New conversation (keep brief)** in the orchestrator's row menu or type `/fresh`
|
|
1568
|
-
in its chat (task chats have both too). It takes the threshold's path: an
|
|
1569
|
-
idle agent starts over at once; a running turn finishes first, with a
|
|
1570
|
-
"Starts fresh after this turn" line. The next conversation launches with
|
|
1571
|
-
the brief, permissions and workspace state the server has then.
|
|
1572
|
-
- **Events:** the server sends it one short line per event (a PR opened, CI
|
|
1573
|
-
failed, or green once it has settled so `sign_off`'s ci gate passes, a `BLOCKED:` line, a merge, a session needing input, a
|
|
1574
|
-
stack step, a task idle 30 minutes with no PR, a review's verdict). Events
|
|
1575
|
-
wait while its turn runs, duplicates fold, and at most one batched message
|
|
1576
|
-
goes out per 30 seconds. What it has been told is kept in
|
|
1577
|
-
`orchestrator_events`, so a restart resends nothing. Set
|
|
1578
|
-
`AGENTOS_ORCHESTRATOR=off` to stop events.
|
|
1579
|
-
- **Asks:** what's yours to decide sits on its asks list
|
|
1580
|
-
(`orchestrator_asks`): what it raises with `ask_saad` (a decision, or
|
|
1581
|
-
something crossing a hard line: public or outbound, money, irreversible,
|
|
1582
|
-
credentials, a product call), and every escalated gate and brake. There's
|
|
1583
|
-
one open ask per subject (a task, the brakes, a title), so an escalation
|
|
1584
|
-
that repeats updates its ask. Each shows as a card under its row and at the
|
|
1585
|
-
top of its chat, with Approve, Decline and Reply. Open asks are its
|
|
1586
|
-
needs-you: the amber dot, one count each in "N need you", and the
|
|
1587
|
-
notifications. Your answer reaches it as an event (`ask "<title>":
|
|
1588
|
-
approved`). An approval covers that one item only: a held task's approval
|
|
1589
|
-
lets `sign_off` merge it once, at the commit you approved (a new commit
|
|
1590
|
-
asks again), and a brake's lets one start through, for that brake only.
|
|
1591
|
-
The orchestrator can ask you itself whether to merge a PR the gates won't
|
|
1592
|
-
pass, by giving `ask_saad` the task and its head sha: the ask is pinned to
|
|
1593
|
-
that commit, opens with what AgentOS knows about it (files changed,
|
|
1594
|
-
sensitive files, the review's verdict) and links the PR, and your approval
|
|
1595
|
-
lets `sign_off` merge it once, at that commit. It never rewrites an ask the
|
|
1596
|
-
gates raised. An approved merge (either kind, and an armed task's) needs no
|
|
1597
|
-
review or other gate of that commit: only an AI-attributed PR body, failing
|
|
1598
|
-
CI (running CI waits), a head that moved off the commit, and on the
|
|
1599
|
-
orchestrator's own ask a stack parent not merged yet, still stop it.
|
|
1600
|
-
An ask closes itself when its task is merged or dropped, or the brakes
|
|
1601
|
-
lift. At most 10 are open per workspace, titles that say the same thing
|
|
1602
|
-
fold into one, a declined subject isn't asked again for 6 hours, and a
|
|
1603
|
-
"decision" that reads as money, outbound, irreversible or credentials is
|
|
1604
|
-
filed as that hard line.
|
|
1605
|
-
- **Readable when busy:** in the orchestrator's chat, anything AgentOS
|
|
1606
|
-
sent it (a PR opened, CI green, a task woken, a schedule, a task's message)
|
|
1607
|
-
and the orchestrator's reply fold into one line: an icon, the task, how the
|
|
1608
|
-
reply starts, the time. Tap to read the whole reply; runs of them group as
|
|
1609
|
-
"5 updates". What you typed, and the replies to it, stay whole, and so do
|
|
1610
|
-
your own decisions and any turn waiting on an approval or showing a plan or
|
|
1611
|
-
an error. Where a message came from is recorded when it is delivered;
|
|
1612
|
-
older messages are told apart by their opening text only.
|
|
1613
|
-
A **Needs you** strip above the composer lists everything waiting on you
|
|
1614
|
-
until it is resolved: open asks (escalated gates and brakes too), questions
|
|
1615
|
-
the orchestrator put to you in chat, its chat waiting on an approval, and
|
|
1616
|
-
tasks that need you. Each is one line that jumps to the ask, the message or
|
|
1617
|
-
the session, with a count when collapsed. The orchestrator marks a chat
|
|
1618
|
-
question with `question` (the same words while open are the same
|
|
1619
|
-
question) and clears it with `resolve_question`; the strip's Reply quotes
|
|
1620
|
-
it into the composer and resolves it when you send. Dismissing an item
|
|
1621
|
-
hides it on all your devices and resolves nothing: an ask stays open on
|
|
1622
|
-
its list and in the counts. Questions about a task clear when it is merged
|
|
1623
|
-
or dropped.
|
|
1624
|
-
- **Proof it's you:** only this machine, the tailnet, or a paired device you
|
|
1625
|
-
switched to "Can approve" in Devices may answer asks or pause. Approving a
|
|
1626
|
-
hard line, a gate, a brake or a new passkey, Resume, changing the
|
|
1627
|
-
orchestrator's standing rules, and switching merge approvals off, also
|
|
1628
|
-
need a
|
|
1629
|
-
passkey (Touch ID or Face ID, user verification required) on a challenge
|
|
1630
|
-
bound to that one ask at its current commit or brake: single use, two
|
|
1631
|
-
minutes. Agents on this machine reach every route but can't make your
|
|
1632
|
-
authenticator sign, and neither `aos` nor the orchestrator's tools can
|
|
1633
|
-
answer or resume. Passkeys belong to the host they were made on
|
|
1634
|
-
(localhost or the tailnet's https name), so add one on
|
|
1635
|
-
each in Devices. The first one on an install is trusted on first use,
|
|
1636
|
-
once: every later one needs a code from a device that has one, even after
|
|
1637
|
-
every passkey is revoked. Revoking always needs a passkey, the last one
|
|
1638
|
-
included, and every add or revoke becomes an ask (declining a new one
|
|
1639
|
-
revokes it, with your passkey). If none is left, run `agent-os passkeys
|
|
1640
|
-
reset` yourself in a terminal on the machine: it refuses inside an
|
|
1641
|
-
AgentOS session or an agent's shell, asks you to type a confirmation, and
|
|
1642
|
-
makes the next passkey first-use again. Browsers offer passkeys only over
|
|
1643
|
-
https or on localhost.
|
|
1644
|
-
- **Header line and Pause:** its row reads like "Orchestrator · 3 running ·
|
|
1645
|
-
1 in review · 1 ask" from live data, and tapping it opens the chat, where
|
|
1646
|
-
the same line carries Pause/Resume. While paused it acts on nothing:
|
|
1647
|
-
events queue, acting tools refuse, and its stack starts and lands hold.
|
|
1648
|
-
It can still read, note and ask. Resume delivers what queued, folded.
|
|
1649
|
-
- **Away mode:** turn it on when you step away, from the coffee cup in the
|
|
1650
|
-
sidebar header or ⌘K ("Away for 4 hours"), for 1 hour, 4 hours, until
|
|
1651
|
-
morning (8 AM) or until you're back. It's kept in `away_periods` (who set
|
|
1652
|
-
it, when, when it ends), the cup turns amber while it's on (with an amber dot on the phone's menu button), and it
|
|
1653
|
-
ends on its own at its time. It works only through orchestrators that
|
|
1654
|
-
had a real turn (yours or theirs, not an AgentOS event or a status poll)
|
|
1655
|
-
in the last hour, set in Settings → Workspaces → Away mode; the rest
|
|
1656
|
-
aren't told or woken. Each active orchestrator gets an event, and a line
|
|
1657
|
-
leading its digest, saying you're away and to follow work through rather
|
|
1658
|
-
than only react. Every 10 minutes a sweep, written as code with no model
|
|
1659
|
-
scanning, runs a review again when a PR is green and settled but its
|
|
1660
|
-
review never started or was lost (once seen on two sweeps, so the
|
|
1661
|
-
orchestrator had its turn), checks after each merge whether the project's
|
|
1662
|
-
`deploy` went live and the server came back, and sends the orchestrator
|
|
1663
|
-
one digest of what's new: tasks quiet 30 minutes with no PR (or failing
|
|
1664
|
-
CI), idle tasks with uncommitted work and no PR, and your own sessions
|
|
1665
|
-
idle with uncommitted work. Only sessions with a real turn inside the
|
|
1666
|
-
window are listed, never stopped, finished or archived ones, and nothing
|
|
1667
|
-
goes out when nothing changed. The sweep messages no session itself: the
|
|
1668
|
-
orchestrator decides what needs work, and asks one of your sessions at
|
|
1669
|
-
most once to wrap up. Away's own messages don't count as activity. Paused
|
|
1670
|
-
workspaces aren't swept. Hard lines still become asks, and while you're
|
|
1671
|
-
away the asks raised, failed deploys and any `aos notify` an orchestrator
|
|
1672
|
-
sends reach your phone as one message per sweep. Turning it off (or its running out) builds "While you were
|
|
1673
|
-
away" from the database, for the workspaces Away reached: what merged and
|
|
1674
|
-
deployed with PR links, what the sweep flagged that's still in flight and
|
|
1675
|
-
which gate or blocker holds it, what failed, was dropped
|
|
1676
|
-
or was escalated, and what waits on you (an explicit "none"). It opens on
|
|
1677
|
-
whichever device is open until you dismiss it, a short summary goes to
|
|
1678
|
-
your phone, and the report stays in Settings → Audits (`away/`).
|
|
1679
|
-
|
|
1680
|
-
## LumifyHub (optional)
|
|
1681
|
-
|
|
1682
|
-
AgentOS works fully without a LumifyHub account. Connecting one, from a
|
|
1683
|
-
workspace's menu, gives its docs and boards a home you can open anywhere and
|
|
1684
|
-
share with anyone:
|
|
1685
|
-
|
|
1686
|
-
- A workspace links to a LumifyHub workspace, and a project to a board in it.
|
|
1687
|
-
Tasks get a card on the project's board that moves as the task runs, and a
|
|
1688
|
-
card in To Do can be started as a task.
|
|
1689
|
-
- **Docs** lists the linked workspace's pages and reads them; editing happens
|
|
1690
|
-
in LumifyHub.
|
|
1691
|
-
- A markdown file open in the file explorer can be published as a page marked
|
|
1692
|
-
as coming from the repo. Publishing again updates the same page, and so does
|
|
1693
|
-
merging a task that changed the file. It goes one way: edits made in
|
|
1694
|
-
LumifyHub are overwritten.
|
|
1695
|
-
- Agents can list, read and create those pages with `aos docs`, `aos doc <id>`
|
|
1696
|
-
and `aos doc new "<title>" --file <path>`. The token stays in AgentOS.
|
|
1697
|
-
|
|
1698
|
-
What maps to what, and the API it uses: [docs/lumifyhub.md](docs/lumifyhub.md).
|
|
1699
|
-
|
|
1700
|
-
## Machines
|
|
1701
|
-
|
|
1702
|
-
Open the sidebar menu (⋯) → **Machines** and add one with a name and an ssh
|
|
1703
|
-
target (`user@host` or an alias from `~/.ssh/config`). Requirements: key-based
|
|
1704
|
-
ssh from the machine running AgentOS (no password prompts), and tmux on the
|
|
1705
|
-
remote machine. **Test connection** checks both.
|
|
1706
|
-
|
|
1707
|
-
New projects can then pick a machine. Their sessions run in tmux there and
|
|
1708
|
-
attach over one reused ssh connection per machine. Folders match across
|
|
1709
|
-
machines relative to `~`, so `~/dev/app` on your laptop and on a server are
|
|
1710
|
-
the same project.
|
|
1711
|
-
|
|
1712
|
-
On other machines, files, git, worktrees, dev servers and summarize are not
|
|
1713
|
-
available yet; the terminal, status, rename and send-keys are.
|
|
1714
|
-
|
|
1715
|
-
### Sessions on a linked machine
|
|
1716
|
-
|
|
1717
|
-
Once a machine is linked (below), this AgentOS talks to the AgentOS running
|
|
1718
|
-
there instead of running tmux over ssh. That machine owns its sessions: their
|
|
1719
|
-
processes, chats and state. This one is a client of it, over HTTP and
|
|
1720
|
-
WebSockets with the link's device token, and only ever connects out to it.
|
|
1721
|
-
|
|
1722
|
-
- The sessions its AgentOS runs are sessions here: each is mirrored (same id)
|
|
1723
|
-
as soon as it's listed, with the same row, menu and address as a local one.
|
|
1724
|
-
One whose folder is in a project you have for that machine sits with that
|
|
1725
|
-
project's sessions; the rest are listed under the machine's name. Its
|
|
1726
|
-
orchestrator and its tasks stay with it. **Elsewhere** is only for tmux
|
|
1727
|
-
sessions no AgentOS started.
|
|
1728
|
-
- From the menu, rename, done (and its undo) and delete run on that machine.
|
|
1729
|
-
Fork, fresh start, move to project and check-ins are shown disabled with
|
|
1730
|
-
the reason. A new session started here on that machine is started by its
|
|
1731
|
-
AgentOS and opens in chat, like a local one.
|
|
1732
|
-
- A session for a project here can start on a linked machine too: pick it in
|
|
1733
|
-
the draft's Machine chip. That machine finds the project by its repository
|
|
1734
|
-
or folder, or clones it, as for tasks. A machine that can't (no remote, a
|
|
1735
|
-
folder there that isn't a repository, no clone access, unreachable) is
|
|
1736
|
-
greyed out in the chip with the reason. The session sits with the project
|
|
1737
|
-
here.
|
|
1738
|
-
- Starts are safe to retry: the draft's id is the start's key and the
|
|
1739
|
-
session's id, so a resend, or this machine retrying a start whose answer
|
|
1740
|
-
was lost, gets the session the first one made instead of a second.
|
|
1741
|
-
- Terminals attach through its `/ws/terminal`, relayed by this server; one
|
|
1742
|
-
connection per session however many views show it, with flow control
|
|
1743
|
-
carried through. A dropped connection retries for two minutes, then says so.
|
|
1744
|
-
- Chats open in the chat view through its `/ws/chat`, retried the same way
|
|
1745
|
-
while the chat stays open. Messages from agents
|
|
1746
|
-
(`aos send`) and schedules aren't relayed to another machine's chats: they'd
|
|
1747
|
-
arrive there as yours.
|
|
1748
|
-
- The orchestrator asks that machine for a linked task's screen before
|
|
1749
|
-
clearing it; a screen it can't get counts as possibly blocked.
|
|
1750
|
-
- ssh stays for machines that aren't linked. A row that can't be opened
|
|
1751
|
-
(the machine unreachable, a name tmux can't attach) is greyed out and says why.
|
|
1752
|
-
|
|
1753
|
-
### Tasks on another machine
|
|
1754
|
-
|
|
1755
|
-
A machine that runs its own AgentOS can take tasks. Run AgentOS there (any
|
|
1756
|
-
port behind your own front door, or plain 3011 on loopback plus a proxy), then
|
|
1757
|
-
press **Link** (the chain icon) next to it in Machines. Linking asks that
|
|
1758
|
-
AgentOS for a pairing code over ssh, where its loopback is trusted, and keeps
|
|
1759
|
-
the device token it gets back. The token goes straight into this machine's
|
|
1760
|
-
database and is never shown.
|
|
1761
|
-
|
|
1762
|
-
Once linked:
|
|
1763
|
-
|
|
1764
|
-
- **New task → Run on** picks the machine (`aos task --on devbox <project> ...`
|
|
1765
|
-
does the same). That machine creates the worktree, runs the agent and opens
|
|
1766
|
-
the PR on its own. This one lists the task in the sidebar and Tasks with its
|
|
1767
|
-
live state, opens its chat (or terminal) through that machine's AgentOS,
|
|
1768
|
-
and sends sign-off and drop to it (pinned to the reviewed commit). Opened
|
|
1769
|
-
while it still sets up, it shows that machine's setup steps and joins the
|
|
1770
|
-
agent once it starts. Tasks open as chats unless Terminal is picked.
|
|
1771
|
-
- **Move** carries a running task between this machine and a linked one,
|
|
1772
|
-
either way. The source stops the agent, commits anything uncommitted as
|
|
1773
|
-
`wip: moving to <machine>`, pushes the branch and hands over Claude's
|
|
1774
|
-
conversation. The target finds the project by its git remote, then by its
|
|
1775
|
-
folder relative to `~`, and clones it when it has neither. It checks the
|
|
1776
|
-
branch out (reusing a worktree still on it), rewrites the conversation's
|
|
1777
|
-
paths and resumes it with `--resume`. The source keeps its row, marked moved.
|
|
1778
|
-
If the target refuses, the agent resumes where it was. If the source can't
|
|
1779
|
-
tell whether it arrived (a timeout, a restart), the task stays **Moving**:
|
|
1780
|
-
press Move again to finish it, or **Resume here**, which first asks the
|
|
1781
|
-
other machine whether it arrived. Tasks waiting on you (an escalated gate or
|
|
1782
|
-
an open ask), card tasks and stacked tasks don't move yet.
|
|
1783
|
-
- **An orchestrator's task moves too**, by the orchestrator's `move` tool (`task`,
|
|
1784
|
-
`machine`; `here` brings it back) or the Move button. Its state is re-keyed to
|
|
1785
|
-
the task's new session id in the same step it lands: brief and scope changes,
|
|
1786
|
-
plan file and phase, gate checks and failure counts, asks, wakes, queue
|
|
1787
|
-
order, arm, model and provider. Both machines need an AgentOS that can do
|
|
1788
|
-
it, and the arriving one must have the task's provider. The machine it lands
|
|
1789
|
-
on holds it for the orchestrator's machine alone: only that device can
|
|
1790
|
-
merge, arm, message, stop, done or read it, and the arriving machine's own
|
|
1791
|
-
orchestrator, armed merges and bulk done skip it. The orchestrator keeps
|
|
1792
|
-
judging the PR at its head, reads BLOCKED lines and open approvals from the
|
|
1793
|
-
task's chat there, and gets PR, CI, idle and plan events by polling that
|
|
1794
|
-
machine. Its messages arrive marked as from the orchestrator, never as
|
|
1795
|
-
yours. The task can `aos send` its orchestrator back: the machine it runs
|
|
1796
|
-
on only stores the message, and the orchestrator's machine pulls it and
|
|
1797
|
-
delivers it as that task's own. Moving it back stops its agent there and
|
|
1798
|
-
removes its worktree when nothing in it would be lost. Guards (write guard, shell deny list, attribution settings) run in
|
|
1799
|
-
the task's worker on either machine, and the task can't merge or push to
|
|
1800
|
-
the default branch. Moving counts toward the brakes and Pause refuses the
|
|
1801
|
-
tool.
|
|
1802
|
-
- **A chat task moves the same way**, between turns: a running turn is let
|
|
1803
|
-
finish (up to 3 minutes, else the move is refused and it carries on here),
|
|
1804
|
-
and one waiting on your answer isn't moved. What's sent to it while its
|
|
1805
|
-
turn finishes waits in its queue and goes with it; once the move has packed
|
|
1806
|
-
the queue, sends are refused until it arrives. Its chat history, queue,
|
|
1807
|
-
access and plan settings go
|
|
1808
|
-
with it, and on arrival a chat worker resumes the same Claude conversation.
|
|
1809
|
-
Both machines need an AgentOS that can move chat tasks.
|
|
1810
|
-
|
|
1811
|
-
Works for any project. Requests to the other machine carry its device token,
|
|
1812
|
-
so it can sit behind a proxy that never trusts loopback. Linking needs this
|
|
1813
|
-
machine or the tailnet, and only pairs with the ssh target's own host name.
|
|
1814
|
-
The orchestrator starts tasks on its own machine and moves them afterwards.
|
|
53
|
+
Then open AgentOS, add a project from ⌘K, and press ⌥N. The [install guide](docs/guides/install.md) covers Windows (WSL2), the supported agents, first run and the `agent-os` commands. To use it from your phone, see the [phone guide](docs/guides/phone.md).
|
|
1815
54
|
|
|
1816
55
|
## Security
|
|
1817
56
|
|
|
1818
|
-
AgentOS gives whoever uses it a terminal as you, so it decides who that is
|
|
1819
|
-
|
|
1820
|
-
- **This machine** (localhost) is always trusted.
|
|
1821
|
-
- **Your tailnet** is trusted by default. Turn on "Require pairing on Tailscale
|
|
1822
|
-
too" in Devices if other people share it.
|
|
1823
|
-
- **Everything else** needs a paired device. That covers Wi-Fi, `AGENTOS_BIND`,
|
|
1824
|
-
and any reverse proxy, including `tailscale serve`. A request that arrives
|
|
1825
|
-
from localhost with proxy headers (`X-Forwarded-*`, `Forwarded`, `Via`,
|
|
1826
|
-
`X-Real-IP`, `CF-*`, `Tailscale-*` and similar) counts as coming from the
|
|
1827
|
-
proxy's client, not from this machine. A proxy that sends none of these
|
|
1828
|
-
looks like this machine itself, so put such a proxy behind
|
|
1829
|
-
`AGENTOS_AUTH=off` only if it does its own login.
|
|
1830
|
-
- A request from localhost must also be addressed to `localhost` or
|
|
1831
|
-
`127.0.0.1`. This stops a web page that has rebound its own domain to
|
|
1832
|
-
127.0.0.1 from counting as this machine.
|
|
1833
|
-
|
|
1834
|
-
Each paired device holds its own token. Only a hash of it is stored, and
|
|
1835
|
-
removing the device in Devices cuts its open terminals at once. A paired
|
|
1836
|
-
device can use AgentOS but can't add devices or change access; only this
|
|
1837
|
-
machine and the tailnet can.
|
|
1838
|
-
|
|
1839
|
-
AgentOS also refuses requests addressed to any other host name (DNS rebinding),
|
|
1840
|
-
and API calls or terminal connections made by another website's page. That
|
|
1841
|
-
Origin check holds whatever `AGENTOS_BIND` says: a page may write to the API
|
|
1842
|
-
or open a terminal, chat or status socket only from this machine's own
|
|
1843
|
-
addresses (loopback, the addresses it listens on, its tailnet name, its own
|
|
1844
|
-
Connect address and `AGENTOS_ALLOWED_HOSTS`). Another Connect machine's page
|
|
1845
|
-
is always refused. With `AGENTOS_BIND=0.0.0.0`, reaching it by a DNS name
|
|
1846
|
-
other than these needs that name in `AGENTOS_ALLOWED_HOSTS`.
|
|
1847
|
-
|
|
1848
|
-
| Variable | Effect |
|
|
1849
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
1850
|
-
| `AGENTOS_NETWORK=lan` | Listen on Wi-Fi too. Same as the switch in Devices. |
|
|
1851
|
-
| `AGENTOS_REQUIRE_PAIRING_ON_TAILNET=1` | Pairing on the tailnet too. |
|
|
1852
|
-
| `AGENTOS_BIND` | Listen on these addresses instead (e.g. `0.0.0.0`). Pairing still applies. |
|
|
1853
|
-
| `AGENTOS_ALLOWED_HOSTS` | Extra host names to answer to. |
|
|
1854
|
-
| `AGENTOS_TOKEN` | A device token for `aos` and the MCP server when they reach AgentOS over a network that needs pairing. |
|
|
1855
|
-
| `AGENTOS_AUTH=off` | No pairing, for when your own proxy does the login. Anyone who reaches the port gets a shell. |
|
|
1856
|
-
|
|
1857
|
-
### Connect
|
|
1858
|
-
|
|
1859
|
-
Connect (in development) reaches this machine from anywhere at
|
|
1860
|
-
`https://m-<16 hex>.runagentos.com`. `agent-os connect` links the machine with
|
|
1861
|
-
a code from runagentos.com: it makes an Ed25519 key in `~/.agent-os/connect`
|
|
1862
|
-
that never leaves the machine, and the Connect service makes the machine a
|
|
1863
|
-
Cloudflare tunnel. Running it again on a linked machine links the same tunnel
|
|
1864
|
-
again, keeping its address. The first-run setup's Devices step can link too: paste
|
|
1865
|
-
the code into the field (same code path, `POST /api/devices/connect`, local or
|
|
1866
|
-
tailnet callers only). `agent-os disconnect` (or the switch in Devices)
|
|
1867
|
-
turns it off.
|
|
1868
|
-
|
|
1869
|
-
While Connect is on, the production server:
|
|
1870
|
-
|
|
1871
|
-
- downloads cloudflared 2026.10.0 into `~/.agent-os/connect/bin` (never one
|
|
1872
|
-
from `PATH`) and checks its SHA-256 before making it executable or running it;
|
|
1873
|
-
- asks the Connect service for a fresh connector token on every start and hands
|
|
1874
|
-
it to `cloudflared tunnel run` in `TUNNEL_TOKEN` only, never in argv, a file
|
|
1875
|
-
or a log;
|
|
1876
|
-
- starts cloudflared only once AgentOS holds the tunnel port and has seen a
|
|
1877
|
-
connection to it arrive on its own listener, and stops it if the listener
|
|
1878
|
-
goes, so it never publishes something else on that port;
|
|
1879
|
-
- runs cloudflared in a process group of its own under a small watchdog that
|
|
1880
|
-
kills it the moment AgentOS exits, however it exits, and kills one a crash
|
|
1881
|
-
left behind (from `cloudflared.pid`) before starting another;
|
|
1882
|
-
- restarts cloudflared with backoff if it exits, and stops it when Connect is
|
|
1883
|
-
turned off. Devices shows its state.
|
|
1884
|
-
|
|
1885
|
-
Cloudflare's edge ends TLS and cloudflared delivers requests to a listener of
|
|
1886
|
-
their own on `127.0.0.1:<port + 433>`. That listener never trusts anything as
|
|
1887
|
-
this machine, the tailnet or `AGENTOS_AUTH=off`: every request needs a paired
|
|
1888
|
-
device. It answers only the machine's own host name and drops
|
|
1889
|
-
`X-Forwarded-Host`, `Forwarded` and `X-Original-Host` before anything reads
|
|
1890
|
-
them.
|
|
1891
|
-
|
|
1892
|
-
| Variable | Effect |
|
|
1893
|
-
| ---------------------- | ------------------------------------------------------------------- |
|
|
1894
|
-
| `AGENTOS_CONNECT=0` | Never start Connect in this process. |
|
|
1895
|
-
| `AGENTOS_CONNECT_PORT` | The tunnel's loopback port (default AgentOS's port + 433). |
|
|
1896
|
-
| `AGENTOS_CONNECT_DIR` | Where the key and cloudflared live (default `~/.agent-os/connect`). |
|
|
1897
|
-
|
|
1898
|
-
### Demo mode
|
|
1899
|
-
|
|
1900
|
-
`AGENTOS_DEMO=1` runs a server that is safe to show to strangers, over fake
|
|
1901
|
-
data. Every request is refused unless it is on a read allowlist
|
|
1902
|
-
(`lib/security/demo.ts`): no terminals, no exec or send-keys, no git, tmux or
|
|
1903
|
-
file writes, nothing that starts a session, task, dev server or other process.
|
|
1904
|
-
Reads of sessions, chats, diffs and PRs work, as does pairing a device from
|
|
1905
|
-
this machine. A few writes stay database-only: pinning a session, Done
|
|
1906
|
-
(archives, with no merge or clean-up), answering an ask, and removing your own
|
|
1907
|
-
device. A chat message gets a short canned reply instead of an agent, and the
|
|
1908
|
-
device list and network settings show no addresses. Visitors share one demo,
|
|
1909
|
-
so what they type (a chat message, an answer to an ask) is kept and shown as
|
|
1910
|
-
fixed text, and the database goes back to its seed every 10 minutes, in
|
|
1911
|
-
place, while it keeps serving.
|
|
1912
|
-
File reads stay inside the demo's home, and the server refuses to start in
|
|
1913
|
-
demo mode unless `HOME` and `DB_PATH` both sit under `AGENTOS_DEMO_ROOT`, so
|
|
1914
|
-
it can never serve a real machine's sessions.
|
|
1915
|
-
|
|
1916
|
-
```bash
|
|
1917
|
-
npm run demo # seed fake data, serve it on 127.0.0.1:3340
|
|
1918
|
-
npm run build && npm run demo -- --prod
|
|
1919
|
-
AGENTOS_BIND=0.0.0.0 AGENTOS_ALLOWED_HOSTS=demo.example.com AGENTOS_AUTH=off npm run demo -- --prod
|
|
1920
|
-
```
|
|
1921
|
-
|
|
1922
|
-
The seed (`scripts/screenshots/seed.ts`) needs git and tmux, and works on macOS
|
|
1923
|
-
and Linux. `AGENTOS_DEMO_ROOT` (default `/tmp/agentos-demo`) and
|
|
1924
|
-
`AGENTOS_DEMO_PORT` move it; keep the root short, as tmux socket paths are
|
|
1925
|
-
limited to about 100 characters.
|
|
57
|
+
AgentOS gives whoever uses it a terminal as you, so it decides who that is. This machine (localhost) is always trusted. Your tailnet is trusted by default. Everything else, including Wi-Fi and any reverse proxy, needs a paired device. `AGENTOS_AUTH=off` removes pairing and gives anyone who reaches the port a shell, so use it only behind a proxy that does its own login. The full model, Connect and demo mode are in [docs/reference/security.md](docs/reference/security.md).
|
|
1926
58
|
|
|
1927
59
|
## Development
|
|
1928
60
|
|
|
1929
61
|
```bash
|
|
1930
|
-
npm run screenshots # regenerate screenshots/ from a demo instance with fake data
|
|
1931
62
|
npm run dev # http://localhost:3011
|
|
1932
63
|
npm test # vitest
|
|
1933
64
|
npm run lint # eslint
|
|
1934
65
|
npm run check # typecheck, lint, format and tests: what CI runs
|
|
1935
|
-
|
|
1936
|
-
scripts/redeploy --rollback # put the previous build back and restart
|
|
66
|
+
npm run screenshots # regenerate screenshots/ from a demo instance with fake data
|
|
1937
67
|
```
|
|
1938
68
|
|
|
1939
|
-
|
|
1940
|
-
handler in `app/api/` may not call a `*Sync` child_process or fs function:
|
|
1941
|
-
ESLint refuses it. Use the async form with a timeout.
|
|
1942
|
-
|
|
1943
|
-
Every git process starts in `lib/git-exec.ts`: `git()`, `gitResult()`,
|
|
1944
|
-
`gitOk()` and named operations (`revParse`, `worktreeAdd`, `gitPush`, …). It
|
|
1945
|
-
sets one timeout, kills a hung git, never lets git prompt, keeps the server's
|
|
1946
|
-
tmux out of hooks and rejects with a `GitError` carrying the exit code and
|
|
1947
|
-
stderr. ESLint refuses git spawned anywhere else in `lib/`, `app/` or
|
|
1948
|
-
`server.ts`.
|
|
1949
|
-
|
|
1950
|
-
`scripts/redeploy` (and `scripts/autodeploy`, which calls it) builds into
|
|
1951
|
-
`.next-build` while the live server keeps serving `.next`. The build is swapped
|
|
1952
|
-
in by rename only once it finished with a `BUILD_ID`, right before the restart.
|
|
1953
|
-
The build it replaced is kept in `.next-prev` for one rollback, and a failed
|
|
1954
|
-
build leaves `.next` as it was.
|
|
1955
|
-
|
|
1956
|
-
`scripts/autodeploy` waits for a merge to be 2 minutes old
|
|
1957
|
-
(`AGENTOS_SETTLE`), so a burst of merges is one build and one restart. Before
|
|
1958
|
-
it pulls, `scripts/wait-quiet` waits for the reviews and task setups the
|
|
1959
|
-
server runs (`GET /api/busy`, loopback or tailnet only) to finish, for up to
|
|
1960
|
-
10 minutes (`AGENTOS_QUIET_WAIT`), then deploys anyway. A review a restart
|
|
1961
|
-
cuts off anyway runs again when the server starts, and its verdict arrives
|
|
1962
|
-
as usual.
|
|
1963
|
-
|
|
1964
|
-
`scripts/perf/loadtest.mjs` starts a throwaway AgentOS (its own HOME,
|
|
1965
|
-
database, port and tmux socket) with N idle sessions and reports RSS, file
|
|
1966
|
-
descriptors, child processes and spawns per minute; `scripts/perf/execs.mjs
|
|
1967
|
-
--pid <pid>` counts what any server spawns, grouped by command. Both count
|
|
1968
|
-
spawns with eslogger (macOS) or strace (Linux) and need passwordless sudo for
|
|
1969
|
-
that one number. Run them on a machine with room to spare.
|
|
1970
|
-
|
|
1971
|
-
A pre-commit hook (`.husky/pre-commit`, installed by `npm install`) checks
|
|
1972
|
-
only what is staged, in well under two seconds: `prettier --check`, failing
|
|
1973
|
-
with the `npx prettier --write …` command that fixes it, and a scan of the
|
|
1974
|
-
added lines for token-shaped secrets with the merge gate's own rule
|
|
1975
|
-
(`scripts/hooks/staged-secrets.ts` reads `secretsIn` from
|
|
1976
|
-
`lib/orchestrator/diff.ts`), naming the file and line. Build a test's fake
|
|
1977
|
-
token at runtime (`"gh" + "p_" + "a".repeat(36)`) rather than writing it
|
|
1978
|
-
whole. It also refuses words that must not land in this public repo
|
|
1979
|
-
(`scripts/hooks/banned-terms.ts`): a short built-in list, plus, when it
|
|
1980
|
-
exists, a private list of regexes kept outside the repo in
|
|
1981
|
-
`~/.config/agent-os/banned-names` (`AGENTOS_BANNED_NAMES` overrides the
|
|
1982
|
-
path), checked against added lines and file names; `scripts/check` runs the
|
|
1983
|
-
same check over every tracked file. It runs in the worktrees AgentOS makes too, which have no `.husky/_`
|
|
1984
|
-
until `npm install` runs in them. Typecheck, lint and tests are left to CI.
|
|
1985
|
-
In an emergency, `git commit --no-verify` skips it. CI runs `scripts/check --build` on every pull request and push to main,
|
|
1986
|
-
which also holds the app's first-load JavaScript to a budget
|
|
1987
|
-
(`scripts/check-bundle.mjs`; `npx next experimental-analyze` shows what's in
|
|
1988
|
-
it).
|
|
1989
|
-
|
|
1990
|
-
### Session audits
|
|
1991
|
-
|
|
1992
|
-
`/session-audit`, a Claude Code skill kept locally (gitignored, not in this
|
|
1993
|
-
repo), audits the sessions AgentOS started (tasks, chats, orchestrators and their review
|
|
1994
|
-
agents) for token waste and repeated manual work. Its extractor,
|
|
1995
|
-
`scripts/session-audit/audit.ts`, streams the sessions' Claude transcripts and
|
|
1996
|
-
AgentOS's own records (checks, notes, asks, messages) into a compact summary
|
|
1997
|
-
in a couple of seconds: context read per turn, peak context, the biggest tool
|
|
1998
|
-
outputs, repeated commands and errors, files re-read, polling loops, rebases,
|
|
1999
|
-
hand-stripped trailers and dev-instance clean-ups. The skill reads only that
|
|
2000
|
-
summary, ranks a plan by impact and effort, compares it with the last audit,
|
|
2001
|
-
and offers to start the fixes you pick as tasks.
|
|
2002
|
-
|
|
2003
|
-
### Code health audits
|
|
2004
|
-
|
|
2005
|
-
`/code-health-audit`, also a local skill, audits this codebase for
|
|
2006
|
-
performance, security and duplication. A script does the measuring first:
|
|
2007
|
-
`scripts/code-health/prepass.ts` collects `npm audit` and outdated
|
|
2008
|
-
packages, first-load bundle sizes from the last build, the largest files and
|
|
2009
|
-
functions, copied blocks, ESLint by rule, API routes that are public or
|
|
2010
|
-
change state without a `lib/security` guard, commands a shell parses and SQL
|
|
2011
|
-
built from strings, secret shapes and machine details (redacted), and slow
|
|
2012
|
-
tests from a vitest JSON, into one compact summary, and compares its numbers
|
|
2013
|
-
with the last audit's. The skill reads that summary and the lines it points
|
|
2014
|
-
at, writes a ranked plan (calling out work a script could do instead of a
|
|
2015
|
-
model), and starts the fixes you pick as tasks.
|
|
2016
|
-
|
|
2017
|
-
Audits quote session content and name weak spots, so they stay on your
|
|
2018
|
-
machine in `~/.agent-os/audits/<kind>/` (session audits in `sessions/`,
|
|
2019
|
-
code health in `code-health/`;
|
|
2020
|
-
`AGENTOS_AUDITS_DIR` moves the root) and never in a repo. Settings → Audits
|
|
2021
|
-
(or ⌘K, "Session audits") lists every kind's markdown reports, newest first,
|
|
2022
|
-
and renders them.
|
|
2023
|
-
|
|
2024
|
-
## Mobile access
|
|
2025
|
-
|
|
2026
|
-
**At home:** open **Settings → Devices & access** from the menu, turn on "Allow devices on this Wi-Fi",
|
|
2027
|
-
tap "Add a device", and scan the QR code with your phone. Laptops open the link
|
|
2028
|
-
and type the code. Each code works once, for 10 minutes.
|
|
2029
|
-
|
|
2030
|
-
**Anywhere:** install [Tailscale](https://tailscale.com) on this machine and
|
|
2031
|
-
your phone and sign in to the same account. Devices then shows a Tailscale
|
|
2032
|
-
link, which works from anywhere and is encrypted. If your tailnet has HTTPS
|
|
2033
|
-
certificates turned on, AgentOS also serves `https://<machine>.ts.net:3443`
|
|
2034
|
-
with Tailscale's own certificate (renewed daily). Passkeys, the clipboard
|
|
2035
|
-
and notifications need that secure address. Plain HTTP on 3011 keeps
|
|
2036
|
-
working. `AGENTOS_TAILNET_HTTPS=0` turns it off, and
|
|
2037
|
-
`AGENTOS_TAILNET_HTTPS_PORT` moves it. Use Wi-Fi access only on
|
|
2038
|
-
networks you trust, because traffic on Wi-Fi is not encrypted.
|
|
69
|
+
A pre-commit hook checks formatting, secrets and banned terms on what is staged. Route handlers may not call `*Sync` functions, and git runs only through `lib/git-exec.ts`. More in [docs/reference/development.md](docs/reference/development.md).
|
|
2039
70
|
|
|
2040
71
|
## License
|
|
2041
72
|
|