@awebai/oats 0.30.3 → 0.32.0

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.
@@ -34,6 +34,8 @@ souls:
34
34
 
35
35
  host:
36
36
  name: ana-laptop # which workspace triggers and schedules run here
37
+ session: # terminal defaults for NEW launches on this host (0.31)
38
+ tmuxSession: oats-agents # the tmux session new tmux instances open in
37
39
  triggers:
38
40
  disabled: [platform/nightly-review]
39
41
  schedules:
@@ -48,6 +50,11 @@ launch-configs: # named ways this host starts a har
48
50
  CLAUDE_CONFIG_DIR: { fromEnv: PERSONAL_CLAUDE_DIR }
49
51
  model: opus
50
52
  yolo: false
53
+ mine:
54
+ harness: claude
55
+ default: true # this host's baseline for every claude launch (0.32)
56
+ env:
57
+ CLAUDE_CONFIG_DIR: /home/ana/.claude-personal
51
58
  ```
52
59
 
53
60
  Schema: [`oats-local.schema.json`](oats-local.schema.json). Unknown keys are
@@ -64,10 +71,11 @@ refused (`E_WORKSPACE_SCHEMA`).
64
71
  | `souls.teams` | Which teams each soul joins here: `"*"` applies to every soul; a soul's own entry (its name, or `<package>/<soul>`) adds to it. Every soul is also in its default team. Written by `oats soul teams <soul>\|'*' --add … --remove …`. |
65
72
  | `souls.default` | A per-soul override of `defaultTeam`; it must be one of that soul's teams here (`E_TEAM_NOT_ELIGIBLE`). Written by `oats soul teams <soul> --default <label>`. |
66
73
  | `souls.disabled` | Souls not run on this machine; a spawn is refused with `E_SOUL_DISABLED`. A bare name disables every soul of that name; `<package>/<soul>` or `<member>/<soul>` disables one. |
74
+ | `session.tmuxSession` | The tmux session new tmux instances open their windows in (0.31). Absent: `OATS_TMUX_SESSION`, else `PI_AGENTS_TMUX_SESSION` (the pre-0.31 variable), else `oats-agents`. `session: { tmuxSession: pi-agents }` keeps the pre-0.31 layout. `oats inspect --json` reports it as `session`. |
67
75
  | `host.name` | This machine's name. A workspace trigger or schedule runs only on the host named by its `runsOn` ([schedules.md](schedules.md)). |
68
76
  | `automations.trust` | The workspace triggers and schedules (`<member>/<id>`) this host agrees to run, or `"*"` for every one the workspace places here (0.30). Absent or empty: none runs. See [Who runs workspace automations](#who-runs-workspace-automations). |
69
77
  | `triggers.disabled`, `schedules.disabled` | Workspace triggers and schedules (`<member>/<id>`) this host does not run, without a commit. Written by `oats trigger disable` / `oats schedule disable`. |
70
- | `launch-configs.<name>` | A named way to start a harness on this host, chosen at spawn or session start, never by the soul. See [Launch configurations](#launch-configurations). |
78
+ | `launch-configs.<name>` | A named way to start a harness on this host, chosen at spawn or session start, never by the soul. `default: true` makes it this host's baseline for its harness (0.32). See [Launch configurations](#launch-configurations). |
71
79
  | `souls.launch` | This machine's launch preference per soul (0.30): `"*"` for every soul, a soul's own entry (its name, or `<package>/<soul>`) over it. A value is a `launch-configs` name or an inline `{ harness, model? }`. It overrides the soul's own `launch:`; explicit spawn flags win over both. See [Launch preferences](#launch-preferences). |
72
80
 
73
81
  How teams are resolved, and what a messaging provider does with them, is in
@@ -78,8 +86,9 @@ How teams are resolved, and what a messaging provider does with them, is in
78
86
  An entry has `harness` (`pi` \| `claude` \| `codex`, required), `executable`
79
87
  (a bare name looked up on `PATH`, or a path relative to this deployment
80
88
  directory), `args` (literal, no shell), `env` (a literal string, or
81
- `{ fromEnv: NAME }` resolved on the host at start), `model` and `yolo`. A
82
- launch configuration is a host choice: a soul never names one.
89
+ `{ fromEnv: NAME }` resolved on the host at start), `model`, `yolo` and
90
+ `default` (0.32; see [the harness default](#the-harness-default)). A launch
91
+ configuration is a host choice: a soul never names one.
83
92
 
84
93
  - Select one with `--launch-config <name>` on `oats spawn`,
85
94
  `oats session start` and `oats session restart`. A named configuration is
@@ -101,6 +110,50 @@ launch configuration is a host choice: a soul never names one.
101
110
  - The old key `runtime` is still read as `harness`, with a
102
111
  `deprecated-runtime-name` warning.
103
112
 
113
+ ### The harness default
114
+
115
+ `default: true` makes a configuration this host's baseline for its harness
116
+ (0.32, feature `launch-config-default`). Use it for what every launch of a
117
+ harness on this machine needs, whatever soul or preference chose it: an
118
+ account directory (`CLAUDE_CONFIG_DIR`), a wrapper `executable`, an argument.
119
+
120
+ - **When it applies:** a new launch that picks the harness without naming a
121
+ configuration: a soul's `launch:`, an inline `souls.launch` preference,
122
+ `--harness` (on a spawn, or on `session start|restart` of an existing
123
+ home), `--reselect-launch`, and the host default (`pi`). It supplies the
124
+ executable, args, env and `yolo`. A `yolo` recorded from a default stays
125
+ with it: a later `--launch-config none` or another harness does not carry
126
+ it over.
127
+ - **The model** comes from whatever picked the harness (`--model`, then the
128
+ preference); the default's `model` is the last fallback, before the
129
+ harness's own.
130
+ - **A named configuration runs as declared**: `--launch-config <name>` or a
131
+ `souls.launch` name never inherits from the default. `--launch-config none`
132
+ (or a `souls.launch` entry of `none`) asks for the bare harness and
133
+ bypasses it.
134
+ - **One per harness.** A second `default: true` for the same harness is
135
+ refused (`E_LAUNCH_CONFIG_INVALID`, naming both); move it by clearing the
136
+ old one first.
137
+ - **Existing homes keep their launch** until `--reselect-launch` or a
138
+ respawn, like any change of preference. Declaring a default is such a
139
+ change: `oats readiness --home` warns `launch-changed` on each existing
140
+ home the default would now apply to, until it is restarted with
141
+ `--reselect-launch` or respawned.
142
+ - **It is visible.** `oats launch-config list` marks it; `spawn --preview`,
143
+ `launch-config preview`, `instance.json` and `oats inspect --home` say when
144
+ a launch's configuration came from the default (`launchConfigDefault`).
145
+ A default with `yolo: true` turns yolo on for every launch of that harness
146
+ here: the preview shows it.
147
+ - **Every kernel that reads the deployment needs OATS 0.32+.** OATS 0.31
148
+ and older refuse the whole `oats-local.yaml` (`E_WORKSPACE_SCHEMA`) once a
149
+ configuration declares `default`.
150
+
151
+ `oats-claude-config` (a one-line file naming the claude binary, found walking
152
+ up from the deployment) is no longer read. A new claude launch with one in
153
+ reach is refused (`E_CLAUDE_CONFIG_REMOVED`) naming the file: declare the
154
+ name it holds as the claude default (`executable: <name>`, `default: true`)
155
+ and delete the file. Homes launched with it keep their recorded executable.
156
+
104
157
  ### Launch preferences
105
158
 
106
159
  A soul says what its role should run on, and each machine may override it
@@ -125,9 +178,11 @@ souls:
125
178
  harness and replaces only its model.
126
179
  - A **launch configuration** name runs that configuration's full recipe. An
127
180
  **inline or soul preference** runs its harness the way this host starts it
128
- without a configuration (the executable on `PATH`, no args, no env), with
129
- its `model`. A preference without `model` uses the harness's own model; it
130
- never borrows a lower layer's.
181
+ without a configuration: [the harness default](#the-harness-default) if
182
+ one is declared, else the executable on `PATH` with no args and no env,
183
+ with the preference's `model`. A preference without `model` uses the
184
+ harness default's model, else the harness's own; it never borrows a lower
185
+ layer's.
131
186
  - **A missing harness is refused**, never replaced: `E_HARNESS_UNAVAILABLE`
132
187
  names the layer that chose it and the fix (install the harness, or override
133
188
  it here in `souls.launch`). `oats souls` still lists the soul, with the
@@ -271,5 +326,10 @@ oats spawn <soul> --preview # the exact modules, teams and provider payloads
271
326
  oats doctor # this deployment's files and the lock
272
327
  ```
273
328
 
274
- Environment: `OATS_REMOTE_CACHE` relocates the fetch cache;
275
- `OATS_PACKAGE_CATALOG` names an alternative package catalog file.
329
+ Environment: `OATS_REMOTE_CACHE` relocates the fetch cache (which also holds
330
+ the bounded parsed-read cache and the observations `--max-age` reuses; all of
331
+ it is safe to delete); `OATS_PACKAGE_CATALOG` names an alternative package
332
+ catalog file. The read verbs (`status`, `workspace status`, `souls`,
333
+ `capabilities`, `inspect`, and the read forms of `teams` and `soul teams`)
334
+ take `--max-age <seconds>` to reuse a remote head observed that recently
335
+ ([Observation reuse](desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311)).
@@ -30,14 +30,15 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
30
30
 
31
31
  ```json
32
32
  {"schemaVersion":1,"name":"@awebai/oats","version":"0.30.0","desktopApi":1,
33
- "harnesses":["pi","claude","codex"],"sessionBackends":["tmux","herdr"],"launchOptions":["yolo"],
34
- "remote":["spawn","retire","status","session","session-start","session-restart","launch-config","roster","harvest","schedule","session-upload","operations"],
33
+ "harnesses":["pi","claude","codex"],"sessionBackends":["tmux"],"launchOptions":["yolo"],
34
+ "remote":["spawn","retire","status","session","session-start","session-restart","launch-config","roster","harvest","schedule","session-upload","operations",
35
+ "readiness","instance-events","instance-git","lifecycle-plans"],
35
36
  "features":["retire-home","session-start","session-restart","launch-config","schedule","session-upload","operations","instance-git",
36
37
  "instance-git-remote","souls-declarations","lifecycle-plans","retire-retention","readiness","spawn-preview","instance-events",
37
38
  "instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
38
39
  "workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
39
40
  "team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
40
- "preview-composed-from"],
41
+ "preview-composed-from","observe-max-age"],
41
42
  "automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
42
43
  "readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2}
43
44
  ```
@@ -47,10 +48,14 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
47
48
  accepted. The real gate is the feature list; its minimum is
48
49
  `packages-no-approval`.
49
50
  - `harnesses` is what `--harness` accepts; `sessionBackends` what `--backend`
50
- accepts. A host without the `harness` feature lists `runtimes` instead.
51
+ accepts: `["tmux"]` since 0.31.0, when Herdr was removed (`--backend herdr`
52
+ is refused with `E_HERDR_REMOVED`). A host without the `harness` feature
53
+ lists `runtimes` instead.
51
54
  - `remote` is the routed surface: the commands `--server <id>` sends to a
52
55
  registered server, plus `roster`. The Desktop checks the execution host's
53
- probe before a routed mutation.
56
+ probe before a routed mutation. From 0.31: `readiness`, `instance-events`,
57
+ `instance-git` and `lifecycle-plans` name the
58
+ [routed reads and plans](#routed-reads-and-plans).
54
59
  - In text mode the command prints `@awebai/oats <version> (desktop API v1)`.
55
60
 
56
61
  ### Features
@@ -90,6 +95,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
90
95
  | `desktop-facts` | the facts under [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290) | |
91
96
  | `launch-preference` | soul and local launch preferences; `launch`, `launchCurrent`, `launchFrom`; `--reselect-launch`; `key` on soul and agent rows ([Launch preferences](#soul-launch-preferences-feature-launch-preference-oats-0300)) | |
92
97
  | `preview-composed-from` | `composedFrom` on preview `modules[]` ([Composition](#the-preview)) | |
98
+ | `observe-max-age` | `--max-age <s>` on the read verbs and their `observation` block ([Observation reuse](#observation-reuse-feature-observe-max-age-oats-0311)) | |
93
99
 
94
100
  Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
95
101
  `workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
@@ -149,6 +155,20 @@ string for this: to support older kernels, use the spaced form.
149
155
  | `E_LOCAL_MISSING` | No `oats-local.yaml` in reach of `--dir` or the working directory |
150
156
  | `E_UNSUPPORTED_MODE` | A home or selector the kernel no longer runs (below) |
151
157
 
158
+ <a id="ssh-failures-e_ssh"></a>
159
+ ### ssh failures (`E_SSH`)
160
+
161
+ A routed command (`--server`, and `oats server check`) reports ssh's own
162
+ failure as `E_SSH`, message `ssh to <host> failed: …`:
163
+
164
+ - `error.details` is `{"sshStarted": false}` when ssh never started on this
165
+ machine (not installed, not executable): nothing reached the host, and
166
+ retrying cannot help.
167
+ - No `details`: ssh ran and the link failed (unreachable host, refused key,
168
+ lost connection, timeout); a retry may succeed.
169
+
170
+ (0.31.0; before it `E_SSH` never carried details.)
171
+
152
172
  Capability dispatch inside a home uses the home's module copies; from a
153
173
  deployment it resolves the module as `oats spawn --soul <x>` would and runs it
154
174
  with the soul's merged payload. `oats <namespace> --help --json` answers
@@ -162,6 +182,83 @@ an inherited `OATS_DEPLOYMENT` or `OATS_RESOLUTION` (`details.inherited`).
162
182
  homes in `problems[]`: `legacy-captured-home {code, instances, homes,
163
183
  message}` and `legacy-local-agents {code, dirs, instances, message}`.
164
184
 
185
+ <a id="observation-reuse-feature-observe-max-age-oats-0311"></a>
186
+ ### Observation reuse (feature `observe-max-age`, OATS 0.32.0)
187
+
188
+ Every read asks each remote for its current head (`git ls-remote`). With
189
+ `--max-age <seconds>` a read verb reuses a head this machine observed at most
190
+ that many seconds ago instead, so a refresh right after another costs no
191
+ network round trip. Gate the flag on the feature: an older kernel may ignore
192
+ it and answer live, without the block.
193
+
194
+ ```text
195
+ oats status | workspace status | souls | capabilities | inspect --soul|--home
196
+ | teams | soul teams <soul> … --max-age <seconds> --json
197
+ ```
198
+
199
+ - **Values:** whole seconds, `0` to `86400`. `0` is live: it reuses nothing.
200
+ Anything else is `E_BAD_ARGS` (`--max-age needs a value: whole seconds from 0
201
+ to 86400`, `--max-age takes whole seconds from 0 to 86400, got "<v>"`).
202
+ - **The block:** with the flag (`0` included) the document gains one key,
203
+ `observation: {observedAt, reused, localRevision}`: in the result of an
204
+ envelope, at the top level of the roster (after `agents`). `observedAt` is the
205
+ OLDEST remote head the answer used, so the answer is at least that fresh
206
+ everywhere; `reused` is `true` when any head came from an earlier observation.
207
+ A command that read no remote head reports the time it started and
208
+ `reused: false`. Without the flag the key is absent and every document is
209
+ exactly as before.
210
+ - **`localRevision`:** 24 lowercase hex characters, opaque. It digests every
211
+ piece of local configuration the kernel read for this answer:
212
+ `oats-local.yaml` (and each closer `oats-local.yaml` it looked for and did
213
+ not find), `oats-lock.json`, an `OATS_PACKAGE_CATALOG` file, and the
214
+ automations snapshot. Only what the verb actually read counts. The same inputs
215
+ give the same revision; any byte change, or one of those files appearing or
216
+ disappearing, gives another. It names no path and no content. Different verbs
217
+ read different inputs (`workspace status` also reads the automations
218
+ snapshot; `inspect --home` reads no lock), so compare revisions of the same
219
+ verb and arguments only. Keep what you
220
+ hold (catalogs, inspect results) keyed on it: a different revision means the
221
+ deployment's configuration changed outside you (a `teams` edit, a sync, a
222
+ hand edit). A kernel upgrade shows through the probe (`oats version --json`),
223
+ not through `localRevision`: the bundled catalog is not an input. Instance
224
+ homes, member clones and tmux are not inputs either, because the answer
225
+ itself reports them.
226
+ - **What is reused:** only remote heads (the commit a branch or tag named),
227
+ never local state. Instances, `oats-local.yaml` and the lock are read afresh
228
+ by every command. A member's backlink (`oats-membership.yaml`) is read at
229
+ the member's observed head, which may be a reused one: a backlink removed
230
+ less than `<s>` seconds ago can still show the member `confirmed` under
231
+ `--max-age <s>`. Only the backlink's comparison with this workspace is made
232
+ afresh. A reused head's
233
+ `observedAt` (for example `workspace.observedAt` in `workspace status`) is
234
+ the time it was observed, not now.
235
+ - **When a head is not reused:** it is older than the flag allows (or dated
236
+ more than 5 s in the future); it was observed through a different URL
237
+ spelling of the same repository (ssh vs https) or for a different ref; its
238
+ commit can no longer be fetched. Each is observed live, as without the flag.
239
+ A live observation that fails is the usual error, never an older head.
240
+ - **Refusals:** every other command, every edit form (`teams add|remove|default`,
241
+ `soul teams --add|--remove|--default|--clear-default`) and any `--server`
242
+ invocation refuse the flag before reading or writing anything, with
243
+ `E_BAD_ARGS` "--max-age is not accepted by \`oats <form>\`: only the read
244
+ verbs reuse observations (status, workspace status, souls, capabilities,
245
+ inspect --soul|--home, and the read forms of teams and soul teams)" and,
246
+ with `--server`, "--max-age cannot be combined with --server: observation
247
+ reuse is local to this machine".
248
+ A capability command's argv (`oats <namespace> …`) is its provider's: the
249
+ kernel neither reads nor refuses `--max-age` there. The same holds for
250
+ `capture`, `recall`, `setup` and `experimental`, which parse their own argv:
251
+ `capture`, `setup` and `experimental` refuse it as an unknown argument (not
252
+ `E_BAD_ARGS`), and `recall` ignores unknown flags.
253
+
254
+ The observations are kept under the remote cache
255
+ (`$OATS_REMOTE_CACHE`, default `~/.cache/oats/remotes`), in `.observed/`,
256
+ beside the bounded parsed-read cache in `.parsed/`. An observation record
257
+ keeps a digest of the fetch URL, never the URL. A parsed entry keeps repository
258
+ content as committed (member refs included), and a value that carries a
259
+ credential-bearing URL (userinfo on http(s), or `user:password@` on any
260
+ scheme) is never written. Deleting either is always safe.
261
+
165
262
  <a id="inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260"></a>
166
263
  ## Inspect, readiness and operation run
167
264
 
@@ -195,7 +292,7 @@ A module's origin (`from`) is `{kind: "member", repoKey, commit}` or `{kind:
195
292
  ### `oats inspect`
196
293
 
197
294
  ```text
198
- oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json
295
+ oats inspect (--home <abs> | --soul <name> [--dir <d>]) [--max-age <s>] --json
199
296
  ```
200
297
 
201
298
  An instance subject, abridged:
@@ -578,7 +675,7 @@ discovers the workspace over the network to check. Other errors: `E_USAGE`,
578
675
  ### `oats workspace status`
579
676
 
580
677
  ```text
581
- oats workspace status [--dir <d>] --json
678
+ oats workspace status [--dir <d>] [--max-age <s>] --json
582
679
  ```
583
680
 
584
681
  Read-only (it writes no lock):
@@ -627,8 +724,8 @@ Read-only (it writes no lock):
627
724
  ### `oats capabilities` and `oats souls`
628
725
 
629
726
  ```text
630
- oats capabilities [--dir <d>] --json
631
- oats souls [--dir <d>] --json
727
+ oats capabilities [--dir <d>] [--max-age <s>] --json
728
+ oats souls [--dir <d>] [--max-age <s>] --json
632
729
  ```
633
730
 
634
731
  Every item of every confirmed member, the external souls, and the locked
@@ -816,7 +913,7 @@ soul subject), `""` when unknown.
816
913
  ### `oats teams`
817
914
 
818
915
  ```text
819
- oats teams [--dir <d>] --json
916
+ oats teams [--dir <d>] [--max-age <s>] --json
820
917
  oats teams add <label> --team <id> [--description <d>] --json
821
918
  oats teams remove <label> --json
822
919
  oats teams default <label> --json
@@ -855,6 +952,7 @@ oats teams default <label> --json
855
952
 
856
953
  ```text
857
954
  oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--dir <d>] --json
955
+ oats soul teams <soul>|'*' [--dir <d>] [--max-age <s>] --json (the read form only)
858
956
  ```
859
957
 
860
958
  ```json
@@ -1116,6 +1214,12 @@ it to a temporary copy (`soulFetched: true`).
1116
1214
  (absent when nothing sets it) are the resolved selection.
1117
1215
  `backendStatus` is `{name, installed, started: false}`, `null` with
1118
1216
  `--no-launch`. `executable` is the resolved harness binary.
1217
+ - `launchConfigDefault` (0.32, feature `launch-config-default`) is `true`
1218
+ when `launchConfig` is this host's default for the harness (its
1219
+ executable, args, env and `yolo` apply without being chosen), `false`
1220
+ otherwise. Show it, and `yolo`, whenever it is `true`. An explicit
1221
+ `--launch-config none` asks for the bare harness and bypasses the default;
1222
+ to run the host's default, omit `--launch-config`.
1119
1223
  - `modelSource` is `"explicit"`, `"soul default"`, `"launch-config <name>"`,
1120
1224
  `"native default"` or `"native default (explicit)"` (`--model
1121
1225
  @native-default`). Omitting `--model` and asking for the native default are
@@ -1207,8 +1311,8 @@ with `--expect-decision` records the key and decision in `instance.json`.
1207
1311
 
1208
1312
  ```json
1209
1313
  {"instance":"rm-api","agent":"rm","home":"/w/agents/rm/instances/rm-api","work":"worktree","branch":"agents/rm-api","launched":true,"warnings":[],
1210
- "tmux":{"session":"pi-agents","window":"rm-api"},"repo":"/w/agents-repo","harness":"pi","model":null,"parent":null,"sibling":null,"relation":null,
1211
- "spawnOrigin":"operator","attach":"tmux attach -t pi-agents","decision":{"instance":"rm-api","revision":"c557d8ec9a272ba1c1739dc3"},"replayed":false,
1314
+ "tmux":{"session":"oats-agents","window":"rm-api"},"backend":"tmux","repo":"/w/agents-repo","harness":"pi","model":null,"parent":null,"sibling":null,"relation":null,
1315
+ "spawnOrigin":"operator","attach":"tmux attach -t oats-agents","decision":{"instance":"rm-api","revision":"c557d8ec9a272ba1c1739dc3"},"replayed":false,
1212
1316
  "wake":{"requested":false,"saved":null,"error":null},"launchConfig":null,
1213
1317
  "launch":{"version":2,"harness":"pi","launchConfig":null,"launchConfigSource":null,"executable":"/usr/local/bin/pi","executableDeclared":null,
1214
1318
  "executableResolvedFrom":"PATH","args":[],"env":{},"model":null,"hooks":{"launch":{},"env":{},"contributions":[]},"prompt":{"kind":"task-file","file":"TASK.md"}}}
@@ -1217,10 +1321,11 @@ with `--expect-decision` records the key and decision in `instance.json`.
1217
1321
  (`decision` is abridged: it is the full bound decision.)
1218
1322
 
1219
1323
  - Always present: `instance, agent, home, work, branch, launched, warnings
1220
- (array), tmux ({session, window} | null), repo, harness, model, parent,
1324
+ (array), tmux ({session, window} | null), backend ("tmux"), repo, harness,
1325
+ model, parent,
1221
1326
  sibling, relation, spawnOrigin (operator | instance), attach, launchConfig,
1222
1327
  launch` (the redacted recipe).
1223
- - When they apply: `sessionTarget` (Herdr), `yolo`, `decision` and
1328
+ - When they apply: `yolo`, `decision` and
1224
1329
  `replayed` (bound apply), `wake` (keyed apply), `wakeSchedule` and
1225
1330
  `wakeScheduleError` (a requested wake).
1226
1331
 
@@ -1297,7 +1402,7 @@ workspace-model fields (feature `instance-modules`):
1297
1402
  ```
1298
1403
 
1299
1404
  Abridged: the record also carries the launch recipe and command,
1300
- composition evidence, the capability runtime, the tmux or Herdr target,
1405
+ composition evidence, the capability runtime, the tmux target,
1301
1406
  lineage (`parentInstance`, `siblingInstance`, `relation`, `relativeTo`), and
1302
1407
  the keyed-spawn fields `decision`, `spawnIdempotencyKey`, `spawnCompleted` and
1303
1408
  `wake`; later starts add `restarts` and `restartCount`.
@@ -1326,10 +1431,11 @@ the keyed-spawn fields `decision`, `spawnIdempotencyKey`, `spawnCompleted` and
1326
1431
  ### The roster (`oats status --json`)
1327
1432
 
1328
1433
  ```text
1329
- oats status [--dir <d>] --json
1434
+ oats status [--dir <d>] [--max-age <s>] --json
1330
1435
  ```
1331
1436
 
1332
- Not an envelope: `{root, agents, workspace?, problems?, warnings?}`.
1437
+ Not an envelope: `{root, agents, observation?, workspace?, problems?, warnings?}`
1438
+ (`observation` only with [`--max-age`](#observation-reuse-feature-observe-max-age-oats-0311)).
1333
1439
 
1334
1440
  ```json
1335
1441
  {"root":"/w/agents",
@@ -1354,8 +1460,13 @@ Not an envelope: `{root, agents, workspace?, problems?, warnings?}`.
1354
1460
  description, dir, instances}`.
1355
1461
  - **Instance rows**: the home's `instance.json` (launch recipe and command
1356
1462
  redacted) plus `home` and `instance` (from the directory; a disagreeing
1357
- claim is kept as `recordedHome`/`recordedInstance`), `running` (`null` when
1358
- a Herdr session is unreachable, with `runtimeState`/`runtimeError`),
1463
+ claim is kept as `recordedHome`/`recordedInstance`), `running` (read from
1464
+ the row's recorded tmux socket and session, never the caller's `$TMUX`;
1465
+ `null` with `runtimeState: "unreachable"` and the tmux error as
1466
+ `runtimeError` when that server cannot be read; `null` for a home a
1467
+ Herdr-era kernel recorded, with `runtimeState: "unsupported"` and
1468
+ `runtimeError: "E_HERDR_REMOVED: …"`, the recorded `sessionTarget` staying
1469
+ in the row),
1359
1470
  `identity` when a provider recorded one, `rollbackIncomplete` and
1360
1471
  `retirePending` when present, and the Desktop facts below.
1361
1472
  - **`modules`** becomes drift rows `{name, from, commit, current, status,
@@ -1379,6 +1490,82 @@ restart, else `createdAt` for a launched home, else `null`. `modelFrom` is
1379
1490
  `"harness-default"`, or `null` for an older home. `identityAddress` is the
1380
1491
  messaging identity's `address` (else `alias`), or `null`.
1381
1492
 
1493
+ <a id="the-remote-roster-oats-server-roster---json"></a>
1494
+ ### The remote roster (`oats server roster --json`)
1495
+
1496
+ ```text
1497
+ oats server roster [--server <id>] [--per-target <ms>] [--budget <ms>] --json
1498
+ ```
1499
+
1500
+ An envelope; `result` is `{groups, bounds}` (remote `roster`,
1501
+ [servers.md](servers.md#the-roster-and-harvest)). One group per server id and
1502
+ route target:
1503
+
1504
+ ```json
1505
+ {"id":"build:3f2a…","server":"build","label":"Build box","registrationPresent":true,
1506
+ "target":{"sshHost":"build-host","workspace":"/srv/team","oatsPath":"oats"},
1507
+ "probe":{"ok":true},"agentsRoot":"/srv/team/agents",
1508
+ "souls":[{"name":"dev","harness":"claude","work":"worktree","agentsRoot":"/srv/team/agents"}],
1509
+ "instances":[{"server":"build","instance":"dev-a","agent":"dev","home":"/srv/team/agents/dev/instances/dev-a",
1510
+ "agentsRoot":"/srv/team/agents","harness":"claude","backend":"tmux","tmux":{"session":"oats-agents","window":"dev-a"},
1511
+ "running":true,"identity":{"alias":"dev-a","address":"acme/dev-a"},"identityAddress":"acme/dev-a",
1512
+ "teams":[{"label":"default","team":"acme:team"}],"startedAt":"2026-09-29T10:00:00.000Z","createdAt":"2026-09-29T09:58:12.004Z",
1513
+ "model":"opus","runtimeState":null,"parentInstance":"lead","siblingInstance":null,"relation":"child","relativeTo":"lead",
1514
+ "spawnOrigin":"instance","retirePending":false,"rollbackIncomplete":false,
1515
+ "savedRoute":false,"addressable":true,"missingRemotely":false}],
1516
+ "retireFailures":[]}
1517
+ ```
1518
+
1519
+ - **Instance rows** relay the host's own `status --json` row: `identity`,
1520
+ `identityAddress`, `teams`, `startedAt`, `createdAt`, `model`,
1521
+ `runtimeState`, `parentInstance`, `siblingInstance`, `relation`,
1522
+ `relativeTo` and `spawnOrigin` are always present, `null` when the host
1523
+ does not supply them (a host before 0.31, a fact it never recorded, or a
1524
+ saved route the host no longer lists). Nothing is derived on this side.
1525
+ - **`addressable`** (0.31): `true` for every row the host reports. Routed
1526
+ session and lifecycle commands reach it by `--home`, or by name when the
1527
+ name is unique on the host ([addressing](servers.md#run-there); a shared
1528
+ name is `E_AMBIGUOUS` with `error.details.candidates: [{agent, home}]`). A
1529
+ saved-route row the host did not list is addressable only while the host's
1530
+ answer is unknown (`missingRemotely: false`).
1531
+ - **`savedRoute`**: the instance was spawned from this machine and has a
1532
+ saved route here. Information only; no action depends on it.
1533
+ - `running` is `null` when unknown; `backend` is `tmux` for a row with a tmux
1534
+ target, else `null`; `tmux`, `sessionTarget` (the recorded target of a home
1535
+ a Herdr-era kernel opened) and `runtimeError` are as the host reports them.
1536
+
1537
+ <a id="routed-reads-and-plans"></a>
1538
+ ### Routed reads and plans (`--server`, 0.31)
1539
+
1540
+ The Desktop's per-instance reads and the lifecycle plans run on the
1541
+ instance's own machine: the local command, with `--server <id>` added.
1542
+
1543
+ | Command | `remote` entry | The host must advertise |
1544
+ |---|---|---|
1545
+ | `oats readiness --server <id> (--home <abs> \| --soul <n>) …` | `readiness` | `readiness`, `readinessApi: 2` |
1546
+ | `oats instance events <name> --server <id> …` | `instance-events` | `instance-events-2`, `eventsApi: 2` |
1547
+ | `oats instance git\|diff <name> --server <id> …` | `instance-git` | `instance-git`, `instanceGitApi: 1` |
1548
+ | `oats instance stop <name> --server <id> (--plan \| --apply …)` | `lifecycle-plans` | `lifecycle-plans`, `lifecycleApi: 1` |
1549
+ | `oats retire <name> --server <id> --plan`, and the guarded apply (`--plan-revision`, `--idempotency-key`) | `lifecycle-plans` | `lifecycle-plans`, `lifecycleApi: 1` |
1550
+
1551
+ - The flags are the local command's. The instance is addressed like every
1552
+ routed instance command ([servers.md](servers.md#run-there)): `--home` as
1553
+ given, else the name through its saved route or the host's roster, sent
1554
+ as `--home`. `--dir` names a directory on the host and travels as is;
1555
+ without it the registered workspace is sent (not for `readiness --home`,
1556
+ whose home is its own context). A retire plan and its guarded apply take
1557
+ `--dir` like the rest; an unguarded `retire --server` refuses it.
1558
+ - An instance with a saved route is reached through it, registration or not.
1559
+ A guarded retire apply whose name the host no longer lists is sent by name,
1560
+ so a repeated key gets the host's recorded receipt (or its refusal).
1561
+ - The host's envelope is relayed unchanged, success or failure: the same
1562
+ document the local command answers, with no routing keys added. The
1563
+ guarded retire apply is the routed `retire`, whose result carries
1564
+ `server` and `target` as before.
1565
+ - A host that does not advertise the feature and API number is refused with
1566
+ `E_REMOTE_INCOMPATIBLE`, naming both and the host's version, before
1567
+ anything is sent. A name two homes share on the host is `E_AMBIGUOUS`.
1568
+
1382
1569
  <a id="instance-git-state-oats-instance-gitdiff-instancegitapi-1-oats-0247"></a>
1383
1570
  ## Git and diff
1384
1571
 
@@ -1711,26 +1898,45 @@ accept `--server <id>`.
1711
1898
  {"context":"/w","level":"/w","file":"/w/oats-local.yaml","selected":null,
1712
1899
  "configurations":[{"name":"reviewers","harness":"claude","executable":null,"args":["--permission-mode","plan"],
1713
1900
  "env":{"ANTHROPIC_API_KEY":{"fromEnv":"REVIEW_KEY"},"REVIEW_MODE":{"redacted":true}},
1714
- "model":"opus","yolo":null,"source":"/w/oats-local.yaml","shadows":[]}]}
1901
+ "model":"opus","yolo":null,"default":false,"source":"/w/oats-local.yaml","shadows":[]}]}
1715
1902
  ```
1716
1903
 
1717
1904
  - **list**: `selected` is `null`, `{home, instance}` or `{soul, agentsRoot}`;
1718
1905
  `level` and `file` are `null` without an `oats-local.yaml` (the set is then
1719
1906
  empty). An environment literal is `{redacted: true}`, a reference
1720
- `{fromEnv}`; values never leave the file.
1907
+ `{fromEnv}`; values never leave the file. `default` (0.32, feature
1908
+ `launch-config-default`) is always a boolean: `true` marks this host's
1909
+ default for the configuration's harness.
1721
1910
  - **set**/**remove**: `{name, action, level, file, before, after,
1722
1911
  effective}`. `set --file` takes `{harness, executable?, args?, env?, model?,
1723
- yolo?}` (`-` reads stdin). `--keep-env` keeps the declared environment when
1724
- `env` is omitted. Errors: `E_LOCAL_MISSING`, `E_BAD_ARGS` (including
1725
- `--home`/`--soul`), `E_LAUNCH_CONFIG_UNKNOWN`, `E_LAUNCH_CONFIG_INVALID`,
1726
- `E_CONFIG_BROKEN`, `E_HOME_UNKNOWN`.
1912
+ yolo?, default?}` (`-` reads stdin); it replaces the whole entry and refuses
1913
+ a key it does not know, so an editor sends `default` back to keep it.
1914
+ `default: false` is written as its absence. A second `default: true` for a
1915
+ harness is `E_LAUNCH_CONFIG_INVALID` with `details: {harness,
1916
+ configurations: [<the declared one>, <this one>]}` and nothing is written;
1917
+ moving the default is two writes, never an automatic move. `--keep-env`
1918
+ keeps the declared environment when `env` is omitted. Routed with
1919
+ `--server`, a definition with `default: true` to a host that does not
1920
+ advertise `launch-config-default` is `E_REMOTE_INCOMPATIBLE` before
1921
+ anything is sent (`default: false` is dropped). Errors: `E_LOCAL_MISSING`,
1922
+ `E_BAD_ARGS` (including `--home`/`--soul`), `E_LAUNCH_CONFIG_UNKNOWN`,
1923
+ `E_LAUNCH_CONFIG_INVALID`, `E_CONFIG_BROKEN`, `E_HOME_UNKNOWN`,
1924
+ `E_REMOTE_INCOMPATIBLE`.
1727
1925
  - **preview** (read-only) answers `{context, selected, selection: {source,
1728
1926
  launchConfig, harness, model, yolo}, harness, model, modelSource, yolo,
1729
- launchConfig, launchConfigSource, executable: {path, declared,
1730
- resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
1927
+ launchConfig, launchConfigSource, launchConfigDefault, executable: {path,
1928
+ declared, resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
1731
1929
  true} | {name, reference: true}], command (redacted), prompt, hooks,
1732
1930
  preflight: [{check, ok, detail}], ok}`.
1733
1931
 
1932
+ `launchConfigDefault` (0.32) is `true` when `launchConfig` is this host's
1933
+ default for the harness rather than a chosen configuration; the spawn preview
1934
+ carries the same top-level field, `instance.json` records it as
1935
+ `launch.launchConfigDefault: true`, and `inspect --json` of a home answers
1936
+ `instance.launchConfig` and `instance.launchConfigDefault`. The closed `Launch`
1937
+ object is unchanged: its `effective.launchConfig` names the default
1938
+ configuration.
1939
+
1734
1940
  A successful envelope can carry `ok: false`: show the failed `preflight`
1735
1941
  checks. The prompt is named, never the task body. A home predating launch
1736
1942
  recipes answers its frozen command with `selection.source: "frozen-command"`
@@ -67,4 +67,6 @@ before launch. Desktop does not scaffold a home or execute a launcher itself.
67
67
  Status collection reads each instance's recorded tmux socket and session,
68
68
  with one query per socket per collection. A launcher shell with a harness
69
69
  child remains running; a fallback shell or dead pane is stopped. Errors that
70
- prevent a reliable observation remain unknown. Herdr uses its saved target.
70
+ prevent a reliable observation remain unknown. A row that records a Herdr
71
+ target (Herdr was removed in 0.31.0) is never observed: it shows the kernel's
72
+ `E_HERDR_REMOVED` text, and Open and Start are disabled.
package/docs/desktop.md CHANGED
@@ -110,6 +110,42 @@ registered remote workspace the timer and definitions live on that server, so
110
110
  they do not depend on the Mac staying awake. See [Schedules](schedules.md) for
111
111
  the CLI, cron semantics, observed outcomes and recovery commands.
112
112
 
113
+ ## Instances on servers
114
+
115
+ Every instance a registered server reports shows in its workspace's roster, whoever spawned it. You
116
+ can open its terminal, start, restart, stop and remove it, and read its readiness, activity, Git
117
+ and diffs, as for a local one. Only its pull request is not read here, since the forge reads this
118
+ computer's clones. Every command goes to the server by the instance's home (`--server <id> --home
119
+ <path>`), never by a bare name. Stop and Remove show the plan the server makes, and confirm
120
+ against it.
121
+
122
+ A read waits for the server: the view says "Reading from <server>…", and gives up after about
123
+ 45 seconds with "Couldn't reach <server>." When the server refuses, you see its code and message;
124
+ nothing is read from this computer in its place. A row that can't be opened says why on the row:
125
+ Herdr no longer supported, gone from <server>, not reachable on <server>, or <server> not reached.
126
+ For an instance a server no longer lists, the reason names the command that removes it from this
127
+ computer (`oats server forget <server> --instance <name>`).
128
+
129
+ ## Remote terminals
130
+
131
+ A terminal for an instance on a registered server is a viewer over ssh
132
+ (`oats session attach --server`). When the link dies, ssh ends the viewer with
133
+ exit 255 within about a minute, and the tab reconnects by itself. It keeps its
134
+ place and scrollback, stops taking input, and shows "Disconnected from
135
+ <server>" with a countdown and a **Reconnect now** button. Attempts wait 1, 2,
136
+ 4, 8 and 15 s, then 30 s each for as long as the tab is open. A successful
137
+ attach resets that. Tmux redraws the screen when the viewer attaches again.
138
+
139
+ Reconnecting stops, with a message and "Close this tab", when the server
140
+ answers that the session is gone, the instance is unknown, the terminal limit
141
+ is reached or the app's backend changed, and at once when ssh cannot be run on
142
+ this computer. It stops after four attempts in a
143
+ row in which OATS on this computer gave no answer, since that is more likely
144
+ the local CLI failing than the link. It also stops when the viewer ends with
145
+ any other exit code. Closing the tab ends the reconnecting. Reconnecting
146
+ never stops or restarts the agent on the server: only the local ssh viewer
147
+ ends.
148
+
113
149
  ## Attach files and screenshots
114
150
 
115
151
  Drop a file onto an agent terminal to insert its path into that agent's draft.