@awebai/oats 0.31.0 → 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.
@@ -38,7 +38,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
38
38
  "instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
39
39
  "workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
40
40
  "team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
41
- "preview-composed-from"],
41
+ "preview-composed-from","observe-max-age"],
42
42
  "automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
43
43
  "readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2}
44
44
  ```
@@ -95,6 +95,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
95
95
  | `desktop-facts` | the facts under [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290) | |
96
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)) | |
97
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)) | |
98
99
 
99
100
  Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
100
101
  `workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
@@ -181,6 +182,83 @@ an inherited `OATS_DEPLOYMENT` or `OATS_RESOLUTION` (`details.inherited`).
181
182
  homes in `problems[]`: `legacy-captured-home {code, instances, homes,
182
183
  message}` and `legacy-local-agents {code, dirs, instances, message}`.
183
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
+
184
262
  <a id="inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260"></a>
185
263
  ## Inspect, readiness and operation run
186
264
 
@@ -214,7 +292,7 @@ A module's origin (`from`) is `{kind: "member", repoKey, commit}` or `{kind:
214
292
  ### `oats inspect`
215
293
 
216
294
  ```text
217
- oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json
295
+ oats inspect (--home <abs> | --soul <name> [--dir <d>]) [--max-age <s>] --json
218
296
  ```
219
297
 
220
298
  An instance subject, abridged:
@@ -597,7 +675,7 @@ discovers the workspace over the network to check. Other errors: `E_USAGE`,
597
675
  ### `oats workspace status`
598
676
 
599
677
  ```text
600
- oats workspace status [--dir <d>] --json
678
+ oats workspace status [--dir <d>] [--max-age <s>] --json
601
679
  ```
602
680
 
603
681
  Read-only (it writes no lock):
@@ -646,8 +724,8 @@ Read-only (it writes no lock):
646
724
  ### `oats capabilities` and `oats souls`
647
725
 
648
726
  ```text
649
- oats capabilities [--dir <d>] --json
650
- oats souls [--dir <d>] --json
727
+ oats capabilities [--dir <d>] [--max-age <s>] --json
728
+ oats souls [--dir <d>] [--max-age <s>] --json
651
729
  ```
652
730
 
653
731
  Every item of every confirmed member, the external souls, and the locked
@@ -835,7 +913,7 @@ soul subject), `""` when unknown.
835
913
  ### `oats teams`
836
914
 
837
915
  ```text
838
- oats teams [--dir <d>] --json
916
+ oats teams [--dir <d>] [--max-age <s>] --json
839
917
  oats teams add <label> --team <id> [--description <d>] --json
840
918
  oats teams remove <label> --json
841
919
  oats teams default <label> --json
@@ -874,6 +952,7 @@ oats teams default <label> --json
874
952
 
875
953
  ```text
876
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)
877
956
  ```
878
957
 
879
958
  ```json
@@ -1135,6 +1214,12 @@ it to a temporary copy (`soulFetched: true`).
1135
1214
  (absent when nothing sets it) are the resolved selection.
1136
1215
  `backendStatus` is `{name, installed, started: false}`, `null` with
1137
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`.
1138
1223
  - `modelSource` is `"explicit"`, `"soul default"`, `"launch-config <name>"`,
1139
1224
  `"native default"` or `"native default (explicit)"` (`--model
1140
1225
  @native-default`). Omitting `--model` and asking for the native default are
@@ -1346,10 +1431,11 @@ the keyed-spawn fields `decision`, `spawnIdempotencyKey`, `spawnCompleted` and
1346
1431
  ### The roster (`oats status --json`)
1347
1432
 
1348
1433
  ```text
1349
- oats status [--dir <d>] --json
1434
+ oats status [--dir <d>] [--max-age <s>] --json
1350
1435
  ```
1351
1436
 
1352
- 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)).
1353
1439
 
1354
1440
  ```json
1355
1441
  {"root":"/w/agents",
@@ -1812,26 +1898,45 @@ accept `--server <id>`.
1812
1898
  {"context":"/w","level":"/w","file":"/w/oats-local.yaml","selected":null,
1813
1899
  "configurations":[{"name":"reviewers","harness":"claude","executable":null,"args":["--permission-mode","plan"],
1814
1900
  "env":{"ANTHROPIC_API_KEY":{"fromEnv":"REVIEW_KEY"},"REVIEW_MODE":{"redacted":true}},
1815
- "model":"opus","yolo":null,"source":"/w/oats-local.yaml","shadows":[]}]}
1901
+ "model":"opus","yolo":null,"default":false,"source":"/w/oats-local.yaml","shadows":[]}]}
1816
1902
  ```
1817
1903
 
1818
1904
  - **list**: `selected` is `null`, `{home, instance}` or `{soul, agentsRoot}`;
1819
1905
  `level` and `file` are `null` without an `oats-local.yaml` (the set is then
1820
1906
  empty). An environment literal is `{redacted: true}`, a reference
1821
- `{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.
1822
1910
  - **set**/**remove**: `{name, action, level, file, before, after,
1823
1911
  effective}`. `set --file` takes `{harness, executable?, args?, env?, model?,
1824
- yolo?}` (`-` reads stdin). `--keep-env` keeps the declared environment when
1825
- `env` is omitted. Errors: `E_LOCAL_MISSING`, `E_BAD_ARGS` (including
1826
- `--home`/`--soul`), `E_LAUNCH_CONFIG_UNKNOWN`, `E_LAUNCH_CONFIG_INVALID`,
1827
- `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`.
1828
1925
  - **preview** (read-only) answers `{context, selected, selection: {source,
1829
1926
  launchConfig, harness, model, yolo}, harness, model, modelSource, yolo,
1830
- launchConfig, launchConfigSource, executable: {path, declared,
1831
- resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
1927
+ launchConfig, launchConfigSource, launchConfigDefault, executable: {path,
1928
+ declared, resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
1832
1929
  true} | {name, reference: true}], command (redacted), prompt, hooks,
1833
1930
  preflight: [{check, ok, detail}], ok}`.
1834
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
+
1835
1940
  A successful envelope can carry `ok: false`: show the failed `preflight`
1836
1941
  checks. The prompt is named, never the task body. A home predating launch
1837
1942
  recipes answers its frozen command with `selection.source: "frozen-command"`
package/docs/desktop.md CHANGED
@@ -110,6 +110,22 @@ 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
+
113
129
  ## Remote terminals
114
130
 
115
131
  A terminal for an instance on a registered server is a viewer over ssh
@@ -44,6 +44,7 @@ published to npm. Its developer docs are in
44
44
  | module | owns |
45
45
  |---|---|
46
46
  | `remote.mjs` | repo refs, reading Git remotes, content digests |
47
+ | `local-inputs.mjs` | the local configuration a command read, for `observation.localRevision` |
47
48
  | `workspace.mjs` | workspace, membership and soul files; discovery |
48
49
  | `resolve.mjs` | a soul's resolution: capabilities, slots, provenance |
49
50
  | `packages.mjs` | `packages:`, the catalog, `oats sync`, `oats-lock.json` |
@@ -62,6 +63,63 @@ The kernel is runtime-neutral: nothing in `lib/` depends on a harness or on
62
63
  a provider. Provider behaviour lives in capabilities; the kernel supplies
63
64
  their contracts ([layers](layers.md)).
64
65
 
66
+ ### The remote read path
67
+
68
+ Every CLI command owns one read session (`createReadSession` in
69
+ `remote.mjs`, carried to every remote call as `remoteOptions.session`; the
70
+ CLI closes it when the command ends). A library caller without a session
71
+ gets the plain per-call behaviour. Within a session:
72
+
73
+ - a head is observed once per (cache repo, ref), and a commit peeled once; at
74
+ most eight observations run at once (`OBSERVE_LIMIT`), each holding its slot
75
+ for all its git work (the `ls-remote` and the fetch of the commit it names,
76
+ or the fetch of a reused record's commit);
77
+ - a whole-workspace discovery prefetches its members' heads together with the
78
+ host's (`prefetchMembers` in `workspace.mjs`): the member list comes from the
79
+ host's last observation record and the parsed `workspace` entry at that
80
+ commit, never from a git process, and the answer still uses the list at the
81
+ host commit observed now. A prefetched failure is adopted by the member's own
82
+ observation, not retried in the command. A prefetch no caller adopts (the
83
+ host could not be observed, or the member was dropped since) is abandoned:
84
+ one still queued runs no git. `observeWorkspace` alone (the `teams` reads,
85
+ `inspect --home`) never prefetches;
86
+ - closing the session (the end of the command, or `process.exit`) rejects
87
+ every queued observation and aborts every git child still running for it
88
+ (the session's `AbortSignal` rides every `runGit`). `runGit` starts git as
89
+ its own process group, so a timeout, an output overflow or an abort kills
90
+ git's ssh or remote helper with it; before a capability
91
+ command runs its provider, the CLI ends the idle batch readers
92
+ (`closeBatches`);
93
+ - a commit's tree is listed once (`git ls-tree -r -t -l`, bounded by
94
+ `TREE_INDEX_BUDGET`; anything odd falls back to the per-path reads), and
95
+ blobs come from one `git cat-file --batch` reader per cache repo (at most
96
+ 12 open, killed through `process-group.mjs` on timeout and at close);
97
+ - discovery reads members eight at a time (`DISCOVERY_CONCURRENCY`) with
98
+ serial results: declaration order, the first failure in that order. The
99
+ observations and the member reads are two pools, so a discovery runs at
100
+ most sixteen short-lived git processes at once (eight of them fetches at
101
+ most), plus up to twelve cat-file readers: twenty-eight git processes. One
102
+ shared pool would deadlock: a member read holding a slot waits on its
103
+ member's observation, which needs a slot of its own.
104
+
105
+ Across commands, `memoAtCommit` keeps parsed reads under
106
+ `<cache>/.parsed/<kernel fingerprint>/`, keyed by (repo key, full commit,
107
+ item). The items: `workspace` (the workspace file), `membership` (a member's
108
+ backlink outcome), `enumerate` (a member's souls and capabilities),
109
+ `package-soul` and `external-soul` (one soul file each; their error handling
110
+ differs), `package-manifests` (a package's manifests), `list` (a skill
111
+ listing) and `tree-oids` (the tree ids of a set of directories). An entry is only ever a pure function of those bytes and this kernel's
112
+ code, never local state and never a transient error; it is written
113
+ atomically, a corrupt one is a miss, and `pruneStores` bounds the store
114
+ (`PARSED_LIMITS`, least recently used first). `--max-age` adds the observation
115
+ store `<cache>/.observed/` ([Observation reuse](desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311)):
116
+ one record per (repo key, ref args, url digest), so two spellings of one repo
117
+ keep a record each; the url itself is never written. Adding a cached item
118
+ means choosing an item name unique to its producer (the item string its
119
+ call site passes to `memoAtCommit`, or to `atCommit` in `workspace.mjs`) and
120
+ adding it to `test/parsed-cache.test.mjs`; `test/read-path-scale.test.mjs` pins the member
121
+ scaling by call count.
122
+
65
123
  ## Tests and gates
66
124
 
67
125
  | command | what it checks |
@@ -60,7 +60,8 @@
60
60
  "description": "Environment for the harness. A string is a literal (non-secret by contract; still redacted in every answer). {fromEnv: NAME} is resolved from the execution host's environment at start time; the reference, never the value, is recorded. A missing reference refuses the start before anything stops."
61
61
  },
62
62
  "model": { "type": "string", "minLength": 1, "description": "Model for this configuration's harness; overrides the soul default when this configuration is selected." },
63
- "yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." }
63
+ "yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." },
64
+ "default": { "type": "boolean", "description": "This host's baseline for its harness (0.32, feature launch-config-default): a new launch that picks this harness without naming a configuration (a soul's or souls.launch preference, --harness, the host default) runs this configuration's executable, args, env and yolo; its model is the last fallback. At most one per harness. `--launch-config none` bypasses it. OATS 0.31 and older refuse the key." }
64
65
  }
65
66
  }
66
67
  },
@@ -69,8 +69,11 @@ Found by the rehearsals, and fixed before the tag:
69
69
  ## After the tag
70
70
  - **Flag day:** the oats.engineering release with the souls' `launch:` (experts on Claude Code +
71
71
  Opus 5.5; `code-reviewer` on Codex + Astra; compat `>=0.30.0`), and committing the shared team id.
72
- - **0.30.1:**
73
- - the kernel items that miss the tag: retire of homes whose session is gone, automations.trust;
72
+ - **0.30.1:** one release, cut when the Desktop load work has merged (the kernel read-path PR with
73
+ `observe-max-age` and `observation.localRevision`, Desktop #321 and #322), or on 2026-09-30 18:00 UTC,
74
+ whichever comes first; what has not merged by then goes to 0.30.2. Retire of homes whose session is
75
+ gone (#299) and automations.trust (#300) shipped in 0.30.0. The kernel items below have no owner yet:
76
+ each rides 0.30.1 only if it merges before the cut.
74
77
  - deployment-scope capability commands without `--soul`;
75
78
  - the case-8 pre-check (`E_TEAM_UNCONFIGURED`).
76
79
  - the kernel refuses an unrecognised spawn argument (a bare `join=oats` without
@@ -79,5 +82,5 @@ Found by the rehearsals, and fixed before the tag:
79
82
  FIRST on the instance's PATH and records it in the launch recipe, so plain `oats` inside an
80
83
  instance is the kernel that made it (two kernels side by side: a 0.30 home ran the global 0.24.6
81
84
  and `oats aweb` was `E_UNKNOWN_COMMAND`; the injected instructions tell agents to run plain `oats`).
82
- - **Parked:** item L (`git rm agents/`, cli-dev-v2-native). It needs the GitHub `workflow` token
83
- scope from the human on the lead's machine.
85
+ - **Done:** item L merged in #314 (d4f68da9): the remaining legacy `agents/` soul trees and the
86
+ `validate:okf` gate are removed.
@@ -0,0 +1,137 @@
1
+ # OATS 0.32.0
2
+
3
+ ## Added
4
+
5
+ - **A default launch configuration per harness.** `default: true` on a
6
+ launch configuration in `oats-local.yaml` makes it this machine's baseline
7
+ for its harness. Every new launch of that harness that names no
8
+ configuration runs its executable, args, env and `yolo`: a soul's own
9
+ `launch:`, an inline `souls.launch` preference, `--harness`, the host
10
+ default. For example, every claude instance on a machine runs with its
11
+ `CLAUDE_CONFIG_DIR`:
12
+
13
+ ```yaml
14
+ launch-configs:
15
+ mine:
16
+ harness: claude
17
+ default: true
18
+ env: { CLAUDE_CONFIG_DIR: /home/me/.claude-personal }
19
+ ```
20
+
21
+ The model still comes from whatever chose the harness; the default's
22
+ `model` is only the last fallback. A named configuration runs as declared,
23
+ and `--launch-config none` asks for the bare harness. One default per
24
+ harness (a second is refused, naming both). `oats launch-config list`,
25
+ `spawn --preview`, `launch-config preview`, `instance.json` and
26
+ `oats inspect --home` say when a launch came from the default
27
+ (`launchConfigDefault`), yolo included. Existing instances keep their
28
+ launch until `--reselect-launch` or a respawn, and meanwhile show the
29
+ `launch-changed` readiness warning. Feature
30
+ `launch-config-default`
31
+ ([configuration.md](../configuration.md#the-harness-default)).
32
+ **Declaring a default needs OATS 0.32+ for every kernel that reads this
33
+ deployment:** OATS 0.31 and older refuse the whole `oats-local.yaml`
34
+ (`E_WORKSPACE_SCHEMA`). A routed `launch-config set --server` with
35
+ `default: true` to an older host is refused (`E_REMOTE_INCOMPATIBLE`)
36
+ before anything is sent.
37
+
38
+ - **Every instance a server reports is first-class in the Desktop, whoever
39
+ spawned it.** A remote row opens its terminal, starts, restarts, stops and
40
+ retires, and shows its readiness, activity, Git and diffs, just like a local
41
+ one; only its pull request is not read here, since the forge reads this
42
+ computer's clones. Stop and Remove keep their confirmations: the plan and
43
+ its guarded apply run on the instance's machine. Every command goes to the
44
+ server by the instance's home (`--server <id> --home <path>`), never by a
45
+ bare name, so two instances with the same name stay apart. While a read is
46
+ in flight, the view says "Reading from <server>…". When the server refuses,
47
+ it shows the server's code and message under a headline that usually names
48
+ the server; it never falls back to a local read. A remote stop or remove
49
+ that loses its link reads as an unknown outcome, never a failure, and the
50
+ server's roster is read again at once
51
+ ([desktop.md](../desktop.md#instances-on-servers)).
52
+ - **A row that can't be opened says why, on the row.** The roster shows a short
53
+ reason where it said "state unknown": Herdr no longer supported, gone from
54
+ <server>, not reachable on <server>, or <server> not reached. The full
55
+ sentence stays in the row's tooltip and its actions menu. For an instance a
56
+ server no longer lists, it names the command that removes it from this
57
+ computer (`oats server forget <server> --instance <name>`).
58
+ - **This computer's OATS 0.31 decides which server rows can be used.** The
59
+ Desktop accepts OATS CLIs up to 0.32.x. With a local OATS before 0.31, which
60
+ does not report that fact, an instance spawned from this computer stays
61
+ usable through its saved route, as before; its routed reads and plans ask you
62
+ to update OATS here.
63
+ - **A team card lists its members wherever they run.** On a local workspace's
64
+ Teams page, each team with a provider id lists every instance in it: this
65
+ workspace's, and every registered server's whose messaging identity is in
66
+ that team. They are grouped by machine ("This computer" first), with each
67
+ one's state in words. A member's name shows its roster row, where every
68
+ action lives; **Terminal** opens a running member there. A server whose
69
+ last roster read failed keeps its last-known members under "<server> · not
70
+ reached"; servers with nothing to show are named under the page head. The
71
+ card's count reads "N members · M on other machines". The Desktop reads
72
+ this from what it already holds and runs nothing new
73
+ ([desktop-teams.md](../../packages/desktop/docs/desktop-teams.md#team-members)).
74
+ - **The spawn dialog asks "Where to run" up front** (it was "Run on", under
75
+ Developer settings). "This computer" comes first, then each registered
76
+ server. A server is disabled, with the reason, when it wasn't reached or
77
+ the roster knows only an older registration of it. With a server chosen,
78
+ the dialog says where the instance will run, and the relationship picker
79
+ lists only that server's instances: a relation never crosses machines. When
80
+ the server refuses the spawn (a soul it doesn't offer, for example), the
81
+ dialog says so in the server's own words.
82
+ - **The Desktop keeps and sets a harness's default launch configuration.**
83
+ Editing a configuration marked `default: true` saves it with its default
84
+ intact, and with an OATS that has `launch-config-default` the editor offers
85
+ "Default for <harness> on this machine" for this computer's own
86
+ configurations. A remote host's default survives editing too, but is set on
87
+ that host. A second default for the same harness is refused in OATS's own
88
+ words; unset the old one first.
89
+
90
+ - **Faster read verbs, and `--max-age <s>` to reuse recent remote heads**
91
+ (feature `observe-max-age`). `status`, `workspace status`, `souls`,
92
+ `capabilities` and `inspect` start far fewer git processes, cache what
93
+ they parse by commit, and start the members' remote reads together with the
94
+ host's. With `--max-age` they reuse a head observed in the last `<s>`
95
+ seconds and report `observation {observedAt, reused, localRevision}`
96
+ (`localRevision` changes whenever the local configuration read for the
97
+ answer does). See
98
+ [Observation reuse](../desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311).
99
+
100
+ ## Fixed
101
+
102
+ - **A remote spawn with an empty opening instruction starts it waiting**, as
103
+ the dialog says. It was refused ("--task needs a value").
104
+ - **Focus stays put on the Workspace's Teams, Capabilities and Setup pages.**
105
+ Every roster poll rebuilt the page, since the workspace status it compared
106
+ carries a fresh observation time each time, so keyboard focus (and the
107
+ caret in the add-team form) fell back to the page every few seconds.
108
+ - **A click on a Desktop instance's actions menu does what it says.** With the
109
+ pointer over the open menu, the row no longer counted as hovered, so its
110
+ tools, the menu included, went hidden and a click on an item did nothing;
111
+ the keyboard still worked.
112
+ - **The Stop and Remove confirmations show only their own choices.** Stop
113
+ showed Remove's "delete the worktree" and "delete the local branch" options
114
+ (enabled, and ignored), and Remove showed Stop's "include recorded children".
115
+
116
+ - **A remote read that times out no longer leaves its ssh or https helper
117
+ running.** The kernel killed git but not the ssh or `git-remote-https` it
118
+ had started, which stayed connected to an unreachable host until its own
119
+ connection gave up. Git now runs as its own process group, and a timeout
120
+ (or the end of the command) kills the whole group.
121
+
122
+ - **A capability command whose provider dies of a signal exits as the shell
123
+ reports it** (128 + the signal number, 130 for a Ctrl-C), not 1.
124
+
125
+ - **`oats --help` piped into another program is no longer cut off at 8 KB.**
126
+ The top-level usage (about 20 KB) now arrives whole, including the
127
+ "Observation reuse" section; exit codes are unchanged.
128
+
129
+ ## Removed
130
+
131
+ - **`oats-claude-config` is no longer read.** The one-line file that named
132
+ the claude binary for every deployment below it is replaced by the claude
133
+ default launch configuration. A new claude launch with one in reach is
134
+ refused with `E_CLAUDE_CONFIG_REMOVED`, naming the file and the
135
+ configuration to declare instead (`harness: claude`, `executable: <the
136
+ name it held>`, `default: true`); then delete the file. Instances already
137
+ launched with it keep their recorded executable.
@@ -34,6 +34,7 @@
34
34
  import { spawnSync } from "node:child_process";
35
35
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
36
36
  import { dirname, join } from "node:path";
37
+ import { recordLocalInput } from "./local-inputs.mjs";
37
38
  import YAML from "yaml";
38
39
  import { parseConfigData } from "./config-data.mjs";
39
40
 
@@ -192,8 +193,11 @@ export function soulOriginOf(index, soul) {
192
193
 
193
194
  export const snapshotPath = (dep) => join(dep, ".agents", "automations", "snapshot.json");
194
195
  export function readSnapshot(dep) {
196
+ let text;
197
+ try { text = readFileSync(snapshotPath(dep), "utf8"); } catch { recordLocalInput(snapshotPath(dep), null); return null; }
198
+ recordLocalInput(snapshotPath(dep), text);
195
199
  try {
196
- const doc = JSON.parse(readFileSync(snapshotPath(dep), "utf8"));
200
+ const doc = JSON.parse(text);
197
201
  return isObject(doc) && KIND_NAMES.every((k) => Array.isArray(doc[`${k}s`])) ? doc : null;
198
202
  } catch { return null; }
199
203
  }