@awebai/oats 0.39.4 → 0.40.2

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.
@@ -33,13 +33,13 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
33
33
  "harnesses":["pi","claude","codex"],"sessionBackends":["tmux"],"launchOptions":["yolo"],
34
34
  "remote":["spawn","retire","status","session","session-start","session-restart","launch-config","roster","harvest","schedule","session-upload","operations",
35
35
  "readiness","instance-events","instance-git","lifecycle-plans"],
36
- "features":["retire-home","session-start","session-restart","launch-config","schedule","session-upload","operations","instance-git",
36
+ "features":["retire-home","session-start","session-restart","launch-config","schedule","schedule-host-caps","session-upload","operations","instance-git",
37
37
  "instance-git-remote","souls-declarations","lifecycle-plans","retire-retention","readiness","spawn-preview","instance-events",
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-3","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
41
41
  "preview-composed-from","observe-max-age","spawn-preview-max-age","capability-show","capture-file","workspace-identity",
42
- "server-connect","capability-route","servers-per-workspace","operator-default-soul"],
42
+ "server-connect","capability-route","servers-per-workspace","operator-default-soul","waiting-on-you"],
43
43
  "automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
44
44
  "readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2,
45
45
  "capabilityShowApi":1}
@@ -68,6 +68,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
68
68
  | `session-start`, `session-restart` | `oats session start/restart --home` | |
69
69
  | `launch-config` | `oats launch-config …`; the selection flags on start and restart | |
70
70
  | `schedule` | `oats schedule …` | `scheduleApi: 2` |
71
+ | `schedule-host-caps` | both host-install cap options, their `default` / `none` reset semantics, and cap status reporting; no promise about a particular stored cap value | |
71
72
  | `session-upload` | `oats session upload` (and the host's `session receive`) | |
72
73
  | `operations` | `oats operation run`; `operations[]` in inspect | `operationsApi: 2` |
73
74
  | `instance-git`, `instance-git-remote` | `oats instance git/diff`; the observation's `remote` | `instanceGitApi: 1` |
@@ -106,6 +107,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
106
107
  | `server-connect` | `oats server connect`; `oats onboard --check`; `workspaceReadable` on `oats server check --json`, OATS 0.39.0 ([`oats server connect`](#oats-server-connect)) | |
107
108
  | `capability-route` | `oats <namespace> <command> … --server <id>` runs the capability command on the server, OATS 0.39.0 ([Capability commands on a server](#capability-commands-on-a-server)) | |
108
109
  | `operator-default-soul` | a capability command from a deployment without `--soul` runs as the first soul that provides its namespace (named on stderr); none is `E_BAD_ARGS`, OATS 0.39.0 ([capabilities.md](capabilities.md)) | |
110
+ | `waiting-on-you` | the `waiting` event kind and the session boundary rule; `oats instance waiting` and `oats instance attention`; `waitingOnYou` (with `message`) on `oats status --json` instance rows, on `oats session inspect --json` and in the events read, OATS 0.40.0 ([Waiting on you](#waiting-on-you)) | `eventsApi: 2` |
109
111
 
110
112
  Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
111
113
  `workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
@@ -148,7 +150,9 @@ stdout (progress goes to stderr):
148
150
  A kernel command reads `--flag=value` exactly as `--flag value` (the value is
149
151
  everything after the first `=`). `E_BAD_ARGS` for an empty `--flag=`, a value
150
152
  on a switch (`--yolo=false` never turns yolo on) and a value that is itself an
151
- option (`--model=--yolo`). A capability command's own flags are forwarded as
153
+ option (`--model=--yolo`). The one exception is the free-text `--message`
154
+ (`oats instance waiting`, `oats instance attention`): `--message=--text` is
155
+ accepted, and its value is only ever the message. A capability command's own flags are forwarded as
152
156
  typed; the kernel reads only its dispatch flag (`--soul`). There is no feature
153
157
  string for this: to support older kernels, use the spaced form.
154
158
 
@@ -539,7 +543,10 @@ oats operation run <layer>:<name> (--home <abs> | --soul <name> [--dir <d>]) [--
539
543
  home operation without `--home`), `E_CAPABILITY_REQUIRES`, `E_BAD_ARGS`
540
544
  (undeclared or missing `--arg`), `E_CAPABILITY_BROKEN`. A provider's `ok:
541
545
  false` is relayed with its code and `details: {exit, envelope,
542
- unconfirmed?}`. `E_OPERATION_TIMEOUT` (240 s) and `E_OPERATION_RESULT` are
546
+ unconfirmed?}`. A literal provider `error.details.unconfirmed: true` is
547
+ promoted to the outer details while its full envelope stays nested. Existing
548
+ retained-effect text checks remain for compatibility during migration.
549
+ `E_OPERATION_TIMEOUT` (240 s) and `E_OPERATION_RESULT` are
543
550
  unconfirmed outcomes: `details: {exit, signal, unconfirmed: true,
544
551
  envelope?, stderr?, cleanup?}`.
545
552
 
@@ -1613,7 +1620,7 @@ with `--expect-decision` records the key and decision in `instance.json`.
1613
1620
  `E_IDEMPOTENCY_CONFLICT {instance, home}`.
1614
1621
  - `spawnCompleted` is `false` until launch, lineage and events are done; a
1615
1622
  retry of an unfinished spawn is `E_SPAWN_INCOMPLETE {instance, home,
1616
- launched}` (recover through the session surface).
1623
+ launched, unconfirmed: true}` (recover through the session surface).
1617
1624
  - The key lives in the home. Mint it on the first confirmation and keep it
1618
1625
  for that intent's retries.
1619
1626
  - `wake: {requested, saved, error}` is recorded and replayed; `saved: null`
@@ -1682,11 +1689,17 @@ Feature `spawn-name`. `--name <slug>` is the exact name, with no prefix.
1682
1689
  | `E_INSTANCE_NAME_INVALID`, `E_INSTANCE_NAME_TAKEN` | see above | |
1683
1690
  | `E_DECISION_STALE` | `{decision}` | |
1684
1691
  | `E_PLACEMENT_TAKEN`, `E_IDEMPOTENCY_CONFLICT` | `{instance, home}` | |
1685
- | `E_SPAWN_INCOMPLETE` | `{instance, home, launched}` | |
1692
+ | `E_SPAWN_INCOMPLETE` | `{instance, home, launched, unconfirmed: true}` | |
1686
1693
  | `E_LAUNCH_*`, `E_MODEL_UNKNOWN`, `E_UNSUPPORTED_HARNESS` | | the launch selection is refused |
1687
1694
  | `E_LAUNCH_SHIM` | | the home's `oats` (`<home>/.oats/bin/oats`) cannot be written; the spawn is rolled back |
1688
1695
  | `E_SCHEDULE_INVALID` | | a bad wake (`--wake-json`, `--wake-file`, `--wake-*`) |
1689
- | `E_SPAWN_FAILED` | | anything else |
1696
+ | `E_SPAWN_FAILED` | `{unconfirmed: true}` when compensation cannot finish | anything else |
1697
+
1698
+ Spawn failure envelopes carry `error.details.unconfirmed: true` when an existing
1699
+ keyed spawn is incomplete (`E_SPAWN_INCOMPLETE`) or compensation cannot confirm
1700
+ cleanup. The keyed-spawn details retain `instance`, `home` and `launched`.
1701
+ Confirmed completed compensation does not set this marker. This is an additive
1702
+ producer migration; existing text-based compatibility checks remain.
1690
1703
 
1691
1704
  ## `instance.json` and the roster
1692
1705
 
@@ -1787,6 +1800,16 @@ Not an envelope: `{root, agents, observation?, workspace?, problems?, warnings?}
1787
1800
  in the row),
1788
1801
  `identity` when a provider recorded one, `rollbackIncomplete` and
1789
1802
  `retirePending` when present, and the Desktop facts below.
1803
+ - **`waitingOnYou`** (feature `waiting-on-you`): `{since, producer, reason,
1804
+ message}` when the row is `running: true` and a producer holds a live claim
1805
+ that the instance needs input from a human, else `null` (unknown, not "not
1806
+ waiting"). Its rules are the events read's ([Waiting on you](#waiting-on-you)):
1807
+ one bounded read of the home's log per running row. A remote roster row
1808
+ carries what the remote kernel reports; an older kernel omits the field.
1809
+ `running` is the window's presence, which a crashed harness's fallback
1810
+ shell or a retained dead pane keeps, so a row with a claim is checked
1811
+ against its session (as `oats session inspect` observes it) and reads
1812
+ `null` unless a harness runs there.
1790
1813
  - **`modules`** becomes drift rows `{name, from, commit, current, status,
1791
1814
  reason?}` when the workspace was read. `status` is `current`, `moved` or
1792
1815
  `missing` (`reason`: `capability-absent`, `package-absent`, or the member's
@@ -1908,6 +1931,15 @@ route target:
1908
1931
  `relativeTo` and `spawnOrigin` are always present, `null` when the host
1909
1932
  does not supply them (a host before 0.31, a fact it never recorded, or a
1910
1933
  saved route the host no longer lists). Nothing is derived on this side.
1934
+ - **`waitingOnYou`** (0.40.2, [Waiting on you](#waiting-on-you)) is on a row
1935
+ only when the host's kernel reports it: a row from a host before 0.40.0,
1936
+ and a saved route the host did not list, have no such key. Absent means
1937
+ "not reported", which is not `null` ("no claim"). When present it is `null`
1938
+ or `{since, producer, reason, message}`, passed through the kernel's read
1939
+ rule again on this side: a value that is not a claim (no valid `since` or
1940
+ `producer`) is `null`, and an unknown `reason` or an invalid `message` is
1941
+ `null` inside a claim that still counts. As on a local row, the host
1942
+ reports a claim only for a running instance.
1911
1943
  - **`addressable`** (0.31): `true` for every row the host reports. Routed
1912
1944
  session and lifecycle commands reach it by `--home`, or by name when the
1913
1945
  name is unique on the host ([addressing](servers.md#run-there); a shared
@@ -2146,34 +2178,169 @@ oats instance events <instance> [--limit <n>] [--since <iso>] [--home <abs>] [--
2146
2178
 
2147
2179
  - **Sources.** `home` is `<home>/.oats-events.jsonl`; `workspace` is
2148
2180
  `<deployment>/.agents/events/<agent>--<instance>.jsonl` (it survives the
2149
- home). Each is `{path, status: "ok" | "absent" | "refused" | "tail",
2181
+ home). The deployment is the one the home's spawn recorded, when that
2182
+ directory really holds the home at `agents/<agent>/instances/<instance>`;
2183
+ otherwise it is the fourth ancestor of the home as it was addressed. Each is `{path, status: "ok" | "absent" | "refused" | "tail",
2150
2184
  bytes}`. Only a regular file is opened (no symlinks, same device and inode
2151
2185
  after open), and at most its last 4 MiB is read (`"tail"`).
2152
2186
  - **Kinds:** `spawned`, `launched`, `restarted`, `stopped`, `stop-refused`,
2153
2187
  `retire-planned`, `retired`, `worktree-retained`, `worktree-removed`,
2154
2188
  `branch-deleted`, `child-spawn-refused`, `launch-warning` (0.30: a
2155
2189
  `launch` hook's warning at session start/restart, `data: {message}`),
2156
- `recomposed` (from earlier kernels). `producer` is `kernel` or a capability id. Older rows may carry
2157
- `eventsApi: 1`.
2190
+ `recomposed` (from earlier kernels), `waiting` (0.40: a producer's claim,
2191
+ [Waiting on you](#waiting-on-you)). `producer` is `kernel`, a capability id,
2192
+ or another producer id (`agent`). Older rows may carry `eventsApi: 1`.
2193
+ `launched` is written by spawn and, since 0.40, once per session start or
2194
+ restart, as soon as the session exists (`data: {harness, backend,
2195
+ launchConfig, phase, startId}`; spawn's row has neither `phase` nor
2196
+ `startId`). `phase` is `start` or `restart`, or `recovered` when a later
2197
+ start adopted an interrupted start's receipt that had no boundary; that row
2198
+ is dated at the receipt's launch time. The boundary is complete per log: a
2199
+ log that missed it gets a copy of the same row (same time and data), never
2200
+ a second one.
2158
2201
  - **Incarnation.** Each row carries the writing home's `createdAt` (or
2159
2202
  `null` for old rows); the top-level `incarnation` is the current home's (or
2160
2203
  `null`). Earlier incarnations are returned as this address's history.
2161
2204
  - **Address.** `--home` must be a home of `<instance>` (`E_HOME_MISMATCH`).
2205
+ A home has one address in storage, its real path (since 0.40.2): rows are
2206
+ written and matched under it, whatever spelling a writer or reader used (a
2207
+ deployment reached through a symlink, a symlinked agents root). The answer
2208
+ keeps the spelling it was asked in: the top-level `home` and every
2209
+ returned row's `home` are the home as the caller addressed it (`--home`,
2210
+ or the home found under `--dir`), the same string a status row carries.
2162
2211
  Rows for another address are dropped and counted in
2163
2212
  `integrity.foreignRows`; torn or invalid lines are counted in
2164
- `integrity.unreadableRows`. Duplicates are removed.
2213
+ `integrity.unreadableRows`. A row present in both logs is returned once;
2214
+ identical rows repeated within one log (a set, a clear and the same set in
2215
+ one millisecond) are all returned, as many as the log holding the most
2216
+ copies has.
2217
+ - **Rows from before 0.40.2**, in a deployment addressed through a symlink
2218
+ only. A stored `home` is never rewritten, and never matched under another
2219
+ spelling. A row an earlier kernel wrote under the lexical spelling (a
2220
+ spawn's rows, and the claims of a session that was spawned and never
2221
+ restarted) is foreign after the upgrade, and counted in
2222
+ `integrity.foreignRows`. What that means for a claim:
2223
+ - A claim that was live under the lexical spelling stops showing. Nothing
2224
+ brings that row back: the claim shows again only when a producer makes it
2225
+ anew (the next permission prompt or question, the agent's next
2226
+ `oats instance attention`). A restart starts a new session with no
2227
+ claim, as always.
2228
+ - A claim that stayed set because the restart that should have voided it
2229
+ was recorded under the real path (#583) is gone.
2230
+ - The rows a started or restarted session wrote under the real path, which
2231
+ `oats status --dir <symlink>` could not see, are read now. They cannot
2232
+ surface a stale claim: such a session wrote its clears and its session
2233
+ boundaries under the real path too, so that history is complete.
2165
2234
  - **Window.** `count` is the rows after `--since`; `returned` the window
2166
2235
  (`--limit`, default 200, 1–2000); `truncated` means rows were cut or a
2167
2236
  source was a tail. `lastEvent` is `{kind, at, producer, incarnation}` of
2168
2237
  the last returned row, or `null`.
2169
- - **Waiting.** `waitingClaims[]` is `{producer, waiting, since, reason}` per
2170
- producer with a claim in the current incarnation (cleared ones included).
2171
- A producer's latest row with `data.waitingOnYou` decides. `waitingOnYou` is
2172
- `{since, producer, reason}` of the newest positive claim, or `null`
2173
- (unknown, not "not waiting"). No kernel path claims waiting today.
2238
+ - **Waiting.** `waitingClaims[]` is `{producer, waiting, since, reason,
2239
+ message}` per producer with a live claim in the current incarnation
2240
+ (cleared ones included). A producer's latest row with `data.waitingOnYou`
2241
+ decides. `waitingOnYou` is `{since, producer, reason, message}` of the
2242
+ newest positive claim, or `null` (unknown, not "not waiting"). See
2243
+ [Waiting on you](#waiting-on-you) for the producers and the rules.
2174
2244
  - Errors: `E_SESSION_UNKNOWN`, `E_AMBIGUOUS_INSTANCE`, `E_HOME_MISMATCH`,
2175
2245
  `E_BAD_ARGS`, `E_EVENTS_FAILED`.
2176
2246
 
2247
+ ### Waiting on you
2248
+
2249
+ Feature `waiting-on-you` (OATS 0.40.0): an instance blocked on a human (a
2250
+ permission prompt, a question, an agent asking for an answer) says so through
2251
+ a producer's claim. **Claims are display-only:** nothing in the kernel acts
2252
+ on `waitingOnYou`. In particular `oats session input` behaves exactly as
2253
+ before whether or not a claim is set.
2254
+
2255
+ ```text
2256
+ oats instance waiting <set|clear> --producer <id> [--reason permission|question|attention] [--message <text>] [--home <abs>] [--dir <d>] --json
2257
+ oats instance attention [--message <text>] [--clear] --json
2258
+ ```
2259
+
2260
+ ```json
2261
+ {"eventsApi":2,"instance":"dev-1","home":"/w/agents/dev/instances/dev-1","producer":"oats.core","changed":true,
2262
+ "waitingOnYou":{"since":"2026-10-03T12:00:00.000Z","producer":"oats.core","reason":"permission","message":null}}
2263
+ ```
2264
+
2265
+ - **The row.** A claim is a `waiting` event, `data: {waitingOnYou: true,
2266
+ reason, message?}` or `{waitingOnYou: false}`, appended to both logs, with
2267
+ `producer` the caller's `--producer`.
2268
+ - **`waiting`.** `--producer` matches `^[a-z0-9][a-z0-9._/-]{0,63}$` and is
2269
+ not `kernel`. `set` needs `--reason`, one of `permission`, `question`,
2270
+ `attention` (closed). `clear` refuses `--reason` and `--message`. The home
2271
+ is `--home`, else `$OATS_INSTANCE_HOME`, else the instance home enclosing
2272
+ the working directory; it must be a home of its own name under the scope
2273
+ (`--dir`, else the agents root the home sits in).
2274
+ - **`attention`** is the agent's own claim, run by the instance from its
2275
+ home: sugar for `waiting set --producer agent --reason attention
2276
+ [--message]`, and `--clear` for `waiting clear --producer agent`. Its home
2277
+ comes only from `$OATS_INSTANCE_HOME`: it has no `--home` or `--dir` and
2278
+ never targets another instance. Unset, or not an instance home (no readable
2279
+ `instance.json`), is `E_USAGE`. `--clear` with `--message` is `E_BAD_ARGS`.
2280
+ - **`--message`**: one line of 1 to 200 characters (code points). Refused,
2281
+ as `E_BAD_ARGS` naming `--message`: control characters (`\p{Cc}`: C0, DEL,
2282
+ C1; so no newline, tab or ESC), the line and paragraph separators U+2028
2283
+ and U+2029, the bidi embeddings, overrides and isolates U+202A–202E and
2284
+ U+2066–2069, the invisible U+200B (zero width space), U+2060 (word
2285
+ joiner) and U+FEFF (BOM), and the tag characters U+E0000–E007F. Everything
2286
+ else is allowed, including ZWJ and ZWNJ (U+200C, U+200D: emoji sequences
2287
+ such as 👩‍💻, Persian and Indic text), the marks LRM, RLM and ALM (U+200E,
2288
+ U+200F, U+061C) and the soft hyphen. A message that
2289
+ starts with `--` goes inline, `--message=--deploy failed`: that value is
2290
+ only ever the message, never a flag (`--message=--clear` sets the message
2291
+ "--clear"). The spaced form `--message --deploy` reads `--deploy` as a
2292
+ flag and is `E_BAD_ARGS`. It is stored as given, only on a
2293
+ positive claim. The reader applies the same rule again: an invalid stored
2294
+ message (a hand-edited log) reads as `null`, and the claim still counts.
2295
+ A stored `reason` outside `permission`, `question`, `attention` reads as
2296
+ `null` too, and a row whose `producer` is neither `kernel` nor a valid
2297
+ producer id, or whose `at` is not a date, is no claim at all.
2298
+ - **Idempotent.** The verb reads the producer's live claim first and appends
2299
+ only on a change: a `set` whose reason or message differs from the live
2300
+ positive claim appends (`changed: true`), an identical one does not; a
2301
+ `clear` appends only over a live positive claim. The answer is
2302
+ `{eventsApi, instance, home, producer, changed, waitingOnYou}`, where
2303
+ `waitingOnYou` is that producer's resulting claim (`null` when it holds
2304
+ none) and `home` is the home as the caller addressed it (`--home`,
2305
+ `$OATS_INSTANCE_HOME` or the enclosing home); the row is stored under the
2306
+ real path, so a set and a clear through different spellings of one home
2307
+ meet. Concurrent writers append whole lines; the latest row decides.
2308
+ Each log is judged on its own, and success means both took the row: a
2309
+ write either log refused is `E_EVENTS_FAILED` naming it, and the next call
2310
+ (a retry) appends again to repair it.
2311
+ - **Session boundary.** A claim written before the incarnation's latest
2312
+ kernel `launched`, `restarted` or `stopped` row (in time order; rows of the
2313
+ same millisecond in append order) is not live: it belongs to an ended
2314
+ session and is not listed in `waitingClaims`. A crash
2315
+ while waiting reads `null` on the roster (not running), and the next start
2316
+ writes `launched`, which voids the claim.
2317
+ - **Producers.**
2318
+ - `oats.core`: the Claude Code emitter oats.core installs in a Claude
2319
+ instance's `<home>/.claude/settings.json` (`permission` on a permission
2320
+ prompt, `question` on AskUserQuestion or an MCP elicitation, cleared when
2321
+ the session moves on). See [capabilities.md](capabilities.md), "oats.core:
2322
+ needs input".
2323
+ - `agent`: the instance itself, through `oats instance attention`, and
2324
+ only through it: `oats instance waiting --producer agent` is
2325
+ `E_BAD_ARGS`. **Agent claims are cleared only by the agent (`--clear`)
2326
+ or a session boundary**; no hook clears them (a wake broker's paste is
2327
+ also a prompt submit).
2328
+ - **Where it shows.** `waitingOnYou` on the events read, on `oats status
2329
+ --json` instance rows (running rows only) and on `oats session inspect
2330
+ --json` (beside `state`, whose enum is unchanged; `null` unless the harness
2331
+ is running). Plain `oats status` prints `! needs input (<reason>):
2332
+ <message>` under a running instance's row while it holds a claim.
2333
+ - **Cost.** To compute the field, `oats status` reads the home log of each
2334
+ running row (the workspace log only when the home log is absent), with
2335
+ the same bounded read as the events read: at most the last 4 MiB, so a
2336
+ claim older than the last 4 MiB of a very busy log is not seen. Stopped
2337
+ rows read nothing.
2338
+ - **Local only.** Neither verb routes with `--server`: producers run on the
2339
+ instance's own host.
2340
+ - Errors: `E_BAD_ARGS`, `E_USAGE` (attention), `E_SESSION_UNKNOWN` (no
2341
+ readable `instance.json`), `E_HOME_MISMATCH`, `E_EVENTS_FAILED` (the write
2342
+ failed; nothing else is affected).
2343
+
2177
2344
  ## Lifecycle: stop and retire
2178
2345
 
2179
2346
  Feature `lifecycle-plans` (and `retire-retention`), `lifecycleApi: 1`. A
@@ -2340,6 +2507,42 @@ stderr, not envelopes.
2340
2507
 
2341
2508
  ## Sessions and launch configurations
2342
2509
 
2510
+ ### Input
2511
+
2512
+ ```text
2513
+ oats session input --home <abs> [--text-file <path>] --json
2514
+ ```
2515
+
2516
+ Input bytes come from stdin or the named file. The existing version-1 success
2517
+ answer is `{schemaVersion: 1, ok: true, result: {home, backend: "tmux",
2518
+ present: true, state, paneId, submitted: true, verified}}`. Session input runs
2519
+ on the execution host, including when the wake broker invokes it there;
2520
+ `--server` is not supported for input. The adapter sends one literal
2521
+ bracketed paste and one Enter after the existing input/authority/target checks.
2522
+
2523
+ `submitted` means terminal-operation success, **not model acceptance or
2524
+ processing**. `verified` is display observation only: `true` means a bounded
2525
+ look changed, possibly because of unrelated output or a dialog; `false` means
2526
+ unchanged, unreadable or exhausted observation. False never authorizes retry
2527
+ and is not proof of a pending draft or absence of effects. No `reason` is
2528
+ emitted; the `enter-not-taken` result from 0.39.4 is removed.
2529
+
2530
+ Read-only settling before Enter shares one monotonic 2-second budget starting
2531
+ when paste returns; up to two post-Enter looks share a 1-second budget. Probe
2532
+ timeouts and sleeps use the remaining budget. Observation failures produce
2533
+ `verified: false`, not input errors or extra keys. The original paste/key
2534
+ command timeouts and `E_SESSION_INPUT_FAILED` errors remain; the observation
2535
+ budgets do not bound those commands, failed buffer cleanup or OS scheduling.
2536
+ No busy-pane submission or exactly-once guarantee is provided. Generic command
2537
+ errors can still be uncertain after partial effects.
2538
+
2539
+ The Desktop terminal's authorized PTY writes are a separate stream; they do not
2540
+ consume this `verified` field. The Pi bridge does not interpret this result.
2541
+ Scheduler wake still records a nonthrowing input operation as delivered without
2542
+ adding acceptance/history fields. Actual broker acknowledgement and retry
2543
+ policy require their own consumer qualification; this result is not a native
2544
+ harness receipt. See [execution targets](execution-targets.md).
2545
+
2343
2546
  ### Start and restart
2344
2547
 
2345
2548
  ```text
@@ -2359,6 +2562,11 @@ selection flags. See [the start workflow](desktop-instance-start.md).
2359
2562
  instance's events as a `launch-warning` row, `data: {message}`. They are
2360
2563
  advisory: the start went ahead. Earlier kernels omit the field; read a
2361
2564
  missing `warnings` as `[]`.
2565
+ - A start or restart appends a `launched` event as soon as its session
2566
+ exists (0.40, `phase: "start"` or `"restart"`, `startId`), the session
2567
+ boundary that voids earlier waiting claims ([Waiting on you](#waiting-on-you)).
2568
+ A start whose metadata write failed has it already, so its adoption adds
2569
+ none to a log that holds it and copies it into a log that does not.
2362
2570
  - Restart is one command: the kernel validates the new selection before
2363
2571
  stopping, and owns the stop, lock, launch recovery and metadata. Never
2364
2572
  restart by retiring and spawning.
@@ -2498,6 +2706,25 @@ oats schedule show <id> --json
2498
2706
  [shared row fields](#automations-shared-rows).
2499
2707
  `executionStatus` is `{kind: "legacy" | "invalid", capture: "unknown",
2500
2708
  migrationRequired: true, reason?, intent?}`; only `legacy` runs.
2709
+ - **`attempt`** (present while a launch has no recorded result; reconcile
2710
+ resolves it): `{scheduledFor, startedAt, wallClock, error?, exited?,
2711
+ exitStatus?, exitSignal?}`. `error` (0.40.0) is the first run's cause. A
2712
+ `command` or `operation` attempt with `exited: true` (0.40.0) holds no host
2713
+ slot (`running: false`) but still blocks its own job; show it as needing
2714
+ `oats schedule reconcile <id>` (or `--clear`) either way.
2715
+ - **Schedule IDs**: local and workspace schedule definition names permit 1–100
2716
+ lowercase letters, digits and dashes; trigger names retain their 40-character
2717
+ limit. Desktop accepts these schedule names for creation, editing and row
2718
+ actions (enable, disable, test, run and reconcile), including qualified IDs.
2719
+ A spawn's derived instance name still has a 64-character limit.
2720
+ - **`description`** (0.40.0): the shared row field is the local definition's
2721
+ `description` when it has one, else `null` (a workspace schedule's comes
2722
+ from its file header). It is one line of at most 200 characters with no
2723
+ control characters; show it in place of the argv when present. Like `task`,
2724
+ it is untrusted text: render it as text. Desktop preserves it unchanged
2725
+ through edits to timing and other fields. A kernel before 0.40.0 sends
2726
+ `null` for every local schedule, and drops a `description` given to `oats
2727
+ schedule add` without refusing it.
2501
2728
  - **An unreadable row** (`list` only): `{id, scope, scheduleApi,
2502
2729
  scheduleHistoryApi, unreadable: {code, message}, history: {status:
2503
2730
  "corrupt", stored: null, truncated: false}, recentRuns: []}`. One bad job
@@ -2604,8 +2831,9 @@ Details: [schedules.md](schedules.md#workspace-triggers-and-schedules).
2604
2831
  null}}` (this machine's `host.name` and its `gh` logins, compared with a
2605
2832
  row's `owner`), `snapshot: {takenAt, problems (a count)} | null` (the last
2606
2833
  `oats sync` snapshot), and `scheduler: {installed, active, registered,
2607
- lastTick, maxConcurrent, …}` (nothing runs unless installed, active and
2608
- registered).
2834
+ lastTick, maxConcurrent, triggersMaxConcurrent, …}` (nothing runs unless installed, active and
2835
+ registered). `maxConcurrent` is the effective scheduled-job cap (default 5);
2836
+ `triggersMaxConcurrent` is the separate trigger cap, or `null` for no host cap.
2609
2837
 
2610
2838
  **Every row** carries:
2611
2839
  - `id` (a local schedule keeps its bare id; a workspace item is
@@ -139,35 +139,40 @@ oats session attach --home /abs/home
139
139
  harness, `shell` for a fallback shell,
140
140
  `stopped` for an absent or dead terminal, or `not-launched`. An unavailable
141
141
  backend is an error (`E_SESSION_UNAVAILABLE`), never a stopped result.
142
- - **input** submits UTF-8 text (stdin or `--text-file`, at most 256 KiB, no
143
- NUL) followed by Enter, as a bracketed paste. The text is never run by a
144
- shell. A fallback shell, a stopped session or a split
145
- tmux window is refused. The text is pasted once. Enter waits for the pane to
146
- settle (two identical captures, at least about 200 ms, longer for a larger
147
- paste, at most 2 s), and is judged by whether it changed the bottom 15 lines
148
- of the pane. The comparison is of bytes only, and the pane's text is never
149
- interpreted. Trailing spaces are ignored. When the pane was seen to change
150
- size (a resize, or a second client attaching), a reflow of the same content
151
- is not a change either: each side's content must already be on the other
152
- side's screen or in its history, so content that appeared or disappeared
153
- still counts. An Enter that changed nothing was swallowed, and is resent
154
- after a backoff, at most 3 Enters in total. The answer adds:
155
- - `submitted: true, verified: true`: an Enter was taken.
156
- - `submitted: false, verified: true, reason: "enter-not-taken"`: none of the
157
- 3 Enters changed the pane. The text stays in the agent's input box; it is
158
- not pasted again.
159
- - `submitted: true, verified: false`: a capture failed, so no further Enter
160
- was sent and the last one was not judged. As before, this means the
161
- terminal accepted the keys. When the pane cannot be read before the first
162
- resend, exactly one Enter was sent.
163
-
164
- `submitted` never means the agent processed the text. A pane that changes
165
- for another reason after Enter (a spinner, a clock, a human typing) reads as
166
- taken. A harness that shows no visible reaction to Enter receives up to two
167
- extra Enters; real harnesses (claude, codex, pi) redraw on submit. A call takes at most about 4 s plus its tmux calls. A failed paste or
168
- key send is `E_SESSION_INPUT_FAILED`, with no retry. Wake schedules and
169
- messaging capabilities use this command ([schedules.md](schedules.md)); a
170
- wake schedule records any answer as delivered.
142
+ Beside `state`, `waitingOnYou` (feature `waiting-on-you`) is a producer's
143
+ live claim that the instance needs input from a human, `{since, producer,
144
+ reason, message}`, or `null`; it is non-null only for a running harness
145
+ (docs/desktop-cli-api.md, "Waiting on you").
146
+ - **input** sends UTF-8 text (stdin or `--text-file`, at most 256 KiB, no
147
+ NUL) as exactly one bracketed paste followed by exactly one Enter. The text
148
+ is never run by a shell. Existing endpoint authority, fallback-shell,
149
+ stopped-session and split-window checks still refuse before input.
150
+
151
+ Before Enter, a size-based floor (200 ms plus 3 ms per KiB) and read-only
152
+ settling polls share one **monotonic 2-second observation budget**, starting
153
+ immediately after the paste command returns, before buffer cleanup. Sleeps
154
+ and capture timeouts are clipped to the remaining budget; a failed capture
155
+ or exhausted budget ends settling and proceeds to the single Enter. After
156
+ Enter, at most two read-only looks share a separate 1-second observation
157
+ budget. These deadlines do not bound the original paste/key commands, buffer
158
+ cleanup or OS scheduling. They authorize no further keys.
159
+
160
+ Successful terminal commands return `submitted: true` and `verified`, with
161
+ no `reason`. `verified: true` means only that the bounded display comparison
162
+ saw a changed look; `false` means unchanged, unreadable or exhausted
163
+ observation. The comparison retains its whitespace and resize/reflow
164
+ handling. Display movement can be unrelated output, a spinner or a dialog;
165
+ an unchanged display is not proof that no effects occurred or that a draft
166
+ is pending. **Neither value authorizes retry or proves model acceptance.**
167
+
168
+ A failed paste or key command remains `E_SESSION_INPUT_FAILED`; an
169
+ observational failure does not turn successful terminal operations into a
170
+ refusal. Command errors may themselves be uncertain after partial effects.
171
+ This transport offers no exactly-once guarantee and does not guarantee that
172
+ a busy pane accepts the input. Wake schedules retain their existing rule:
173
+ any nonthrowing input answer is recorded as delivered, meaning terminal
174
+ operations, not model processing. Broker delivery/ack policy and harness
175
+ acceptance evidence remain separate contracts.
171
176
  - **attach** is interactive and takes no `--json`. It opens a temporary tmux
172
177
  session linked to the agent's window alone.
173
178
  Closing the viewer leaves the agent running.
@@ -16,6 +16,8 @@ reference pages ([workspaces](workspaces.md), [souls and instances](souls-and-in
16
16
  - **`oats.framework`** (`oats-package/`): the `oats.core`, `oats.setup` and
17
17
  `oats.knowledge-theory` capabilities and the `knowledge-theory-expert` soul,
18
18
  released as a package under its own `oats-framework/v<version>` tags.
19
+ `oats.core` is the one with executables: its spawn and launch hook and the
20
+ Claude Code waiting emitter (`capabilities/oats-core/bin/`).
19
21
 
20
22
  The OATS Desktop (`packages/desktop/`) is an Electron app with a bundled,
21
23
  dependency-free localhost server; it is released with the kernel but not
@@ -54,6 +56,7 @@ published to npm. Its developer docs are in
54
56
  | `instruction-composition.mjs` | the generated `AGENTS.md` |
55
57
  | `teams.mjs`, `teams-verbs.mjs` | the team model and the `oats teams` verbs |
56
58
  | `schedule.mjs`, `schedule-host.mjs`, `triggers.mjs`, `automations.mjs` | schedules, triggers and the host timer |
59
+ | `schedule-command.mjs`, `schedule-command-child.mjs` | synchronous scheduler adapter and asynchronous child supervisor: bounded output, TERM/KILL escalation and observed exit; no changes to synchronous tick/lock callbacks |
57
60
  | `operator-dispatch.mjs` | capability commands run from a deployment, and its module store |
58
61
  | `instance-*.mjs` | inspection, lifecycle, events and Git views of an instance |
59
62
  | `tmux-config.mjs`, `session-*.mjs` | the tmux session backend and terminal input |
@@ -61,6 +64,56 @@ published to npm. Its developer docs are in
61
64
  | `servers.mjs` | routing commands to a registered server |
62
65
  | `harness-trust.mjs` | reading (never writing) Claude's and Codex's folder trust for a launch |
63
66
 
67
+ The schedule registry stores explicit concurrency caps, leaving the schedule
68
+ cap absent for its effective default of five. `readRegistry()` is a pure,
69
+ bounded atomic-file snapshot: it takes no lock and writes nothing, projecting
70
+ legacy implicit one to absent and `capsVersion: 2` only in memory. Its no-follow
71
+ regular-file descriptor accepts an atomic rename between stat and open, so it
72
+ returns a complete old or new snapshot; other bounded readers retain their
73
+ identity check. Invalid present schedule or trigger caps are refused.
74
+ Registration, unregistration and cap updates reread under `registry.lock` and
75
+ compare against the raw snapshot, so even unchanged membership persists the
76
+ migration. A successful write removing legacy one emits a stderr notice naming
77
+ the one-slot restoration command. `writeRegistry()` remains a raw atomic writer;
78
+ read-modify-write callers must supply the lock and reread. Later explicit one
79
+ stays explicit, including one reintroduced by an older writer: the stored
80
+ marker cannot distinguish its provenance. The trigger cap stays independent.
81
+
82
+ The shared mkdir lock retries every occupied-directory window within the
83
+ caller's retry deadline, including owner-file publication and removal. It never
84
+ removes a contender's lock or steals from a live, dead or unreadable owner;
85
+ persistent contention ends in the caller's existing busy error.
86
+
87
+ Spawn compensation returns its diagnostic and a structural uncertainty flag;
88
+ only an incomplete result marks the original error's `details.unconfirmed`.
89
+ CLI wrappers preserve that field, and the operation runner promotes a provider's
90
+ literal true marker while retaining the nested envelope. Message-based consumers
91
+ remain during the additive producer migration; do not replace stage evidence
92
+ with a substring check or infer uncertainty from a retained home alone.
93
+
94
+ The schedule child supervisor installs SIGINT/SIGTERM/SIGHUP handlers before
95
+ launch and removes them on settlement. All catchable shutdown signals share
96
+ its idempotent TERM/KILL path; the first stop cause is retained. The private
97
+ receipt records interruption independently of both the first stop cause and
98
+ the direct child's observed exit, so a signal during overflow cleanup still
99
+ keeps an otherwise valid envelope unconfirmed. Group probes
100
+ start at leader exit; an observed-empty group is permanently excluded from
101
+ later probes/signals. Polling cannot eliminate the gap before observation or
102
+ prove away PID reuse. Escaped sessions are outside the owned group, and
103
+ SIGKILL/OOM or unrecoverable supervisor death cannot be cleaned up by handlers;
104
+ a missing receipt supplies no child-exit evidence and cannot release a slot.
105
+
106
+ Session input keeps terminal operations separate from display observation.
107
+ The pre-Enter budget starts at paste completion, before best-effort buffer
108
+ cleanup; post-Enter observation starts after the single key command returns.
109
+ Both use a monotonic clock, clip sleeps and read-only capture subprocess
110
+ budgets, and stop observing on failure or exhaustion. Capture subprocesses use
111
+ SIGKILL on timeout so an ignored TERM cannot extend a probe; terminal command
112
+ timeouts/errors are unchanged. Screen comparison can set only the observational
113
+ `verified` boolean, never send another key or report terminal failure. Tests
114
+ use inert command runners and clocks; they do not qualify broker delivery or
115
+ harness acceptance.
116
+
64
117
  The kernel is runtime-neutral: nothing in `lib/` depends on a harness or on
65
118
  a provider. Provider behaviour lives in capabilities; the kernel supplies
66
119
  their contracts ([layers](layers.md)).
@@ -235,6 +288,15 @@ runs the full suite (sharded), `check`, `validate`, `pack:check` and the smoke
235
288
  test, and is the gate. Tests use local bare repositories and fakes; none
236
289
  contacts GitHub, aweb, Jira or Linear.
237
290
 
291
+ Desktop's standalone tests (`cd packages/desktop && npm ci && npm test`) must
292
+ load with only Desktop dependencies installed. Cross-package tests that import
293
+ both the kernel and Desktop belong under root `test/`; install dependencies at
294
+ the root and in `packages/desktop` before running those tests. The schedule
295
+ round-trip case is `node --test test/desktop-schedule-roundtrip.integration.mjs`.
296
+ The root runner includes this file with its existing Desktop dependency group.
297
+ With only root dependencies installed, it omits both Desktop suites and this
298
+ integration case and reports that coverage gap before and after the run.
299
+
238
300
  Tests pin behaviour, so a change that alters behaviour changes its test in the
239
301
  same commit. Never weaken an assertion to make a change pass.
240
302
 
@@ -99,7 +99,7 @@
99
99
  "disabled": {
100
100
  "description": "Workspace schedules this host does not run, by qualified id <member>/<id>, without a commit (`oats schedule disable <member>/<id>` writes it). Local schedules are enabled and disabled in oats-schedules.json.",
101
101
  "type": "array", "uniqueItems": true,
102
- "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,40}$" }
102
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,100}$" }
103
103
  }
104
104
  }
105
105
  },
@@ -111,7 +111,7 @@
111
111
  "description": "The workspace triggers and schedules this host agrees to run, by qualified id <member>/<id>, or \"*\" for every one the workspace places on this host (0.30). A workspace automation runs only when its runsOn is host.name, its owner is this host's gh account AND trust admits it; absent or empty, none runs. A host fact: the committed workspace file refuses it. Local triggers and schedules need no trust.",
112
112
  "oneOf": [
113
113
  { "const": "*" },
114
- { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,40}$" } }
114
+ { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,100}$" } }
115
115
  ]
116
116
  }
117
117
  }
@@ -9,10 +9,10 @@ or workspace membership alone does not make a package official.
9
9
 
10
10
  | package | release | capabilities | package souls |
11
11
  |---|---|---|---|
12
- | `oats.framework` | `oats-framework/v1.5.0` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
12
+ | `oats.framework` | `oats-framework/v1.6.1` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
13
13
  | `oats.okf` | `v4.1.1` | `oats.okf` (knowledge), `oats.okf-harvest`, `oats.okf-maintenance` | `knowledge-harvester`, `knowledge-maintainer` |
14
14
  | `oats.aweb` | `v1.21.1` | `oats.aweb` (messaging) | |
15
- | `oats.engineering` | `v1.8.0` | `oats.engineering-expert`, `oats.developer`, `oats.code-review`, `oats.maintainer` | `code-reviewer` |
15
+ | `oats.engineering` | `v1.8.1` | `oats.engineering-expert`, `oats.developer`, `oats.code-review`, `oats.maintainer` | `code-reviewer` |
16
16
  | `oats.authoring` | `v1.0.3` | `oats.authoring` | |
17
17
  | `oats.cloning` | `v1.0.1` | `oats.cloning` | `cloner` |
18
18
  | `oats.jira` | `v1.0.1` | `oats.jira` (tasks) | |
package/docs/packages.md CHANGED
@@ -51,7 +51,7 @@ packages:
51
51
  - **Bare version** (`v4.1.1`, `4.1.1`, `1.0.0-rc.1`): the id is looked up in
52
52
  the official catalog — `package-catalog.json` in the `oats` repo, or the file
53
53
  named by `OATS_PACKAGE_CATALOG` — which supplies the repo url, the tag
54
- convention (`v4.1.1` or `oats-framework/v1.5.0`) and the payload path. An id
54
+ convention (`v4.1.1` or `oats-framework/v1.6.1`) and the payload path. An id
55
55
  the catalog does not know is `E_PACKAGE_MISSING` ("use `git:<repo>@<ref>` for
56
56
  a package outside the catalog"). The catalog is the reviewed official list
57
57
  ([official-catalog.md](official-catalog.md)) and the only way a
@@ -74,7 +74,7 @@ members:
74
74
  - git:github.com/acme/agents
75
75
  - git:github.com/acme/platform
76
76
  packages:
77
- oats.framework: v1.5.0
77
+ oats.framework: v1.6.1
78
78
  oats.okf: v4.1.1
79
79
  oats.aweb: v1.21.1
80
80
  teams:
@@ -333,11 +333,11 @@ A soul that names one of the package's capabilities with
333
333
  "policy": "docs/official-catalog.md",
334
334
  "packages": {
335
335
  "oats.okf": { "url": "https://github.com/awebai/oats-okf.git", "ref": "v4.1.1", "path": "oats-package" },
336
- "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.5.0", "path": "oats-package" }
336
+ "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.6.1", "path": "oats-package" }
337
337
  }
338
338
  }
339
339
  ```
340
340
 
341
- `ref` carries the tag convention: a workspace's `oats.framework: v1.5.0`
342
- resolves to tag `oats-framework/v1.5.0`. Resolving through the catalog never
341
+ `ref` carries the tag convention: a workspace's `oats.framework: v1.6.1`
342
+ resolves to tag `oats-framework/v1.6.1`. Resolving through the catalog never
343
343
  advances a lock by itself: `oats sync` does, and says so.