@awebai/oats 0.40.1 → 0.41.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.
@@ -0,0 +1,120 @@
1
+ # OATS 0.40.2
2
+
3
+ ## Added
4
+
5
+ - **Unconfirmed spawn and operation failures carry structural evidence**
6
+ ([#548](https://github.com/awebai/oats/issues/548)). Incomplete keyed spawns
7
+ and failed spawn compensation set `error.details.unconfirmed: true`; the
8
+ operation wrapper promotes a provider's literal true marker while retaining
9
+ its complete envelope. Completed compensation stays unmarked. Existing
10
+ text fallbacks and scheduler slot/reconcile behavior remain during this
11
+ additive migration; older copied providers are not upgraded by this change.
12
+
13
+ ## Fixed
14
+
15
+ - **Schedule registry reads are lock-free and do not mutate state**
16
+ ([#579](https://github.com/awebai/oats/issues/579)). Status and other readers
17
+ use coherent atomic-file snapshots, including during registry writes. Legacy
18
+ cap migration is projected in memory and persisted only by locked mutations,
19
+ with a one-slot restoration notice on stderr. Invalid stored schedule caps
20
+ now refuse instead of silently increasing capacity; explicit CLI cap options
21
+ can repair them. Old hand-set and implicit ones cannot be distinguished, nor
22
+ can current kernels identify a one reintroduced by an older writer sharing
23
+ the registry; stop mixed-version writes and explicitly set the desired cap.
24
+ Shared directory locks wait through transient owner publication/removal within
25
+ their retry deadline without stealing locks.
26
+ - **OATS Desktop: "Needs input" shows on a remote instance, and a withheld
27
+ note reads as the reason.** Desktop hid every remote claim: a remote roster
28
+ row carries no `runtimeState`, and Desktop read "not reported" as "not
29
+ running" (#582). An unreported state no longer hides the claim; a row
30
+ reported stopped, unreachable or unsupported, or one whose server did not
31
+ answer, still shows none. When a claim's note holds a link or text such as
32
+ `token: …`, Desktop does not show it. The row's description, the terminal
33
+ tab's title and the hover card now show the reason in words, such as "Asked
34
+ you a question", where they showed "[Detail withheld]" (#584). The activity
35
+ view still marks such a note as withheld, and `oats status` prints it in
36
+ full.
37
+ - **A remote instance's "needs input" reaches the roster**
38
+ ([#582](https://github.com/awebai/oats/issues/582)). `oats server roster`
39
+ built a remote row from a fixed list of facts and dropped the host's
40
+ `waitingOnYou`. A row now carries it when, and only when, the host's kernel
41
+ reports it: a row from a host before 0.40.0, and a saved route the host did
42
+ not list, have no such key, so "not reported" stays distinct from `null`
43
+ ("no claim"). The value passes the kernel's read rule again on this side;
44
+ anything that is not a claim is `null`. See
45
+ [the remote roster](../desktop-cli-api.md#the-remote-roster-oats-server-roster---json).
46
+ - **"Needs input" in a deployment addressed through a symlink**
47
+ ([#583](https://github.com/awebai/oats/issues/583)). A home there has two
48
+ spellings: `oats status --dir <symlink>` addressed it lexically, while a
49
+ started or restarted session carries the real path. Event rows are keyed by
50
+ the home, so the session's claims never showed on status, a restart did not
51
+ void a claim written under the other spelling, and a clear through one
52
+ spelling found nothing. Rows are now stored and matched under the home's
53
+ real path, whichever spelling a writer or reader uses, and both spellings
54
+ use one workspace log, the deployment's own `.agents/events`. Answers keep
55
+ the spelling they were asked in: the `home` in `oats instance events --json`
56
+ (the top-level field and each row's) and in the `oats instance waiting` and
57
+ `oats instance attention` answers is the home as the caller addressed it.
58
+ Deployments not reached through a symlink see no change.
59
+
60
+ Rows an earlier kernel wrote under the lexical spelling are not rewritten
61
+ and not matched: after the upgrade they are foreign, counted in
62
+ `integrity.foreignRows`. In a symlinked deployment that means:
63
+ - a claim that was live under the lexical spelling (a session that was
64
+ spawned and never restarted) stops showing. It shows again only when it
65
+ is made anew: at the agent's next permission prompt, question or
66
+ `oats instance attention`. A restart starts a new session with no claim;
67
+ - a claim that stayed set because its restart was recorded under the real
68
+ path is gone, not stuck;
69
+ - the rows a started or restarted session wrote under the real path are
70
+ read now. They cannot show a stale claim: such a session wrote its clears
71
+ and its session boundaries under the real path too.
72
+ - **oats.core 2.4.1: the Claude Code emitter no longer speaks for another
73
+ instance** ([#584](https://github.com/awebai/oats/issues/584)). Its hook
74
+ script took the home from `$OATS_INSTANCE_HOME`. A Claude process that
75
+ loaded one home's `.claude/settings.json` while carrying another instance's
76
+ environment (a nested `claude -p`, a `claude -p` started with its working
77
+ directory in another home, a pane that inherited the variables) set and
78
+ cleared that other instance's claim, and its `Stop` and `SessionEnd` clears
79
+ could erase a real one. The launch hook now writes the home's real path
80
+ into each hook command, and the script acts only when `$OATS_INSTANCE_HOME`
81
+ names that home, through any spelling; otherwise it does nothing. Ships in
82
+ oats.framework 1.6.1 (compatibility unchanged; catalog and workspace pin
83
+ `oats-framework/v1.6.1`): a workspace's `oats.framework: v1.6.1` resolves
84
+ to that tag through the official catalog, and a workspace pinned to v1.6.0
85
+ keeps oats.core 2.4.0 until it moves the pin and syncs. A home picks it up
86
+ at its next spawn. See [capabilities](../capabilities.md).
87
+ - **A waiting row whose time is not a date is no claim**
88
+ ([#584](https://github.com/awebai/oats/issues/584)). The reader validated a
89
+ stored claim's producer, reason and message but passed its time through.
90
+ And `oats help` lists `[--dir <d>]` for `oats instance waiting`.
91
+ - **Catchable scheduler-supervisor shutdown cleans up its owned child group**
92
+ ([#580](https://github.com/awebai/oats/issues/580)). SIGINT, SIGTERM and SIGHUP
93
+ enter the existing bounded TERM/KILL cleanup once, preserving observed child
94
+ exit evidence and treating interrupted envelopes as unconfirmed. The group
95
+ is checked when its leader exits and never signalled again after it is seen
96
+ empty, reducing but not eliminating process-group ID reuse races. Escaped
97
+ sessions and unrecoverable supervisor deaths such as SIGKILL/OOM remain
98
+ outside that cleanup guarantee; no receipt means no proven child exit, so
99
+ the unknown attempt and host slot remain pending reconciliation.
100
+ - **Workspace schedule opt-outs and named trust accept 100-character IDs**
101
+ ([#581](https://github.com/awebai/oats/issues/581)). Host configuration now
102
+ accepts the full schedule name limit in `schedules.disabled` and
103
+ `automations.trust`, so enabling/disabling and trusting schedules with
104
+ 41–100-character IDs works through the CLI and workspace placement checks.
105
+ Trigger definitions and `triggers.disabled` retain their 40-character limit;
106
+ member syntax, allowed characters and uniqueness rules are unchanged.
107
+
108
+ ## Changed
109
+
110
+ - **Session input restores single-paste, single-Enter terminal semantics**
111
+ ([#562](https://github.com/awebai/oats/issues/562)). Corrects 0.39.4's
112
+ screen-derived extra Enters and `enter-not-taken` refusal after successful
113
+ terminal commands. Pre-Enter settling remains, with a shared monotonic
114
+ 2-second observation budget; at most two read-only post-Enter looks share
115
+ 1 second. Each probe and sleep is limited by its remaining budget.
116
+ `submitted: true` reports terminal-operation success; `verified` reports
117
+ display change only, and false never authorizes retry. Neither proves model
118
+ acceptance, pending draft state or absence of effects. Command errors and
119
+ authority checks are unchanged; busy-pane submission, exactly-once delivery
120
+ and generic-error retry uncertainty are not solved by this correction.
@@ -0,0 +1,238 @@
1
+ # OATS 0.41.0
2
+
3
+ ## Changed
4
+
5
+ - **The framework workspace has a separate maintainer soul.** The new
6
+ `oats-maintainer` soul is the OATS maintainer: direction, the roadmap,
7
+ architecture coherence, routing work to experts, review, merge and releases.
8
+ It composes `oats.maintainer` and carries the OATS-only `/oats-pr-review` and
9
+ `/git-tag-release` skills. `oats-expert` stays an expert, the generalist that
10
+ leads cross-area work, coordinates the domain experts and reports to
11
+ `oats-maintainer`. The other souls send direction, contract decisions and
12
+ merges to `oats-maintainer`, and read its knowledge node
13
+ (`oats/oats-maintainer`, which holds what the `oats-expert` node held).
14
+ - The `oats` knowledge store's main must declare the `oats-maintainer`
15
+ node before this reaches a deployment: souls that read or own it fail
16
+ their knowledge check with `E_CONFIG unresolved node:
17
+ oats/oats-maintainer` against a base without it.
18
+ - Operators: `oats sync`, then spawn `oats-maintainer` for maintainer work.
19
+ Running instances keep what they were spawned with.
20
+ - **Desktop terminals keep text readable (#602).** Desktop terminals now keep
21
+ text at a contrast of at least 4.5:1 with its background, so coloured text written for
22
+ another palette (a grey meant for a dark terminal, shown in White) stays
23
+ readable. The built-in palettes' colours on the terminal background are
24
+ unchanged; text on a coloured background may be drawn lighter or darker than
25
+ the program asked.
26
+ - **Desktop can match this computer's theme (#602).** A fourth theme choice,
27
+ **This computer**, shows Desktop's chrome and every terminal in the theme of
28
+ the computer that runs Desktop. On an Omarchy computer that is the current
29
+ Omarchy theme, followed when it changes, with no restart. Elsewhere it is
30
+ Dark or White by the system's appearance. Text colours taken from the host
31
+ are adjusted where needed to stay at least 4.5:1 on Desktop's surfaces; a
32
+ terminal's 16 colours are the host's own. If the host's theme cannot be
33
+ read, Desktop shows Dark or White and says so. A theme you chose does not
34
+ change: choose **Theme: This computer** in the command palette, or cycle to
35
+ it with the theme button.
36
+ - On an Omarchy computer Desktop reads the theme's colours file at start and
37
+ each time a window gets focus, whichever theme is chosen. It opens the file
38
+ without blocking and reads it only if it is a regular file, so a colours
39
+ file replaced by a named pipe, for example, cannot hang it (#612).
40
+ - On Linux, This computer is the default: a Desktop where no theme was ever
41
+ chosen starts in This computer (the computer's Omarchy theme, or the
42
+ system's light or dark appearance where there is none) instead of White.
43
+ That includes an existing install where the theme was never changed. If
44
+ you chose White at any time, it stays White. Choose another theme with the
45
+ theme button or the palette, and it stays. On other platforms the default
46
+ is still White.
47
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.42.0`**, so it runs against
48
+ this release's kernel; the Desktop 0.40.x refuses a 0.41 CLI.
49
+ - **Desktop's Remove has one choice, the worktree.** Remove no longer offers
50
+ to delete a branch: retirement leaves branches to the operator, and the
51
+ kernel refuses the flag. The dialog's one choice is **Also delete the
52
+ worktree**, and it says "Remove never deletes a branch." A home left by a
53
+ failed spawn that still owes its branch stays incomplete while that branch
54
+ exists, and Remove can no longer complete it: check the branch and delete it
55
+ with Git, then remove the home again.
56
+ - **New sessions run on an OATS-owned tmux server (#602).** The server is
57
+ `tmux -L oats`, so tools that restyle the default tmux server no longer
58
+ reach agent terminals. Running instances stay where they are. Plain
59
+ `tmux ls` and `tmux attach` no longer show new sessions: use `oats session
60
+ attach` or the `attach:` line that spawn prints. An existing instance moves
61
+ only when its window is recreated: a start after its window is gone or its
62
+ tmux server cannot be reached now opens on the OATS server instead of the
63
+ recorded socket. To move one deliberately, stop it, close its window and
64
+ start it: see
65
+ [Existing instances](../execution-targets.md#existing-instances).
66
+ - Operators: after upgrading, and after a reboot, start the `oats` server
67
+ from your own shell before anything else does: `tmux -L oats new-session
68
+ -d -s <session> -n hq` (`<session>` is the deployment's session name,
69
+ `oats-agents` by default). A tmux server keeps the environment of whoever
70
+ starts it: without this step the first Desktop-run or scheduled spawn
71
+ decides the ambient environment of later panes, and a spawn or start run
72
+ from inside an instance whose own tmux server cannot be read is refused
73
+ ([the environment of the server and its panes](../execution-targets.md#the-servers-start-environment)).
74
+ - Nothing of an instance's environment reaches a server or a pane through
75
+ the calls that create an agents' session or window: an instance creates
76
+ the session with a reduced copy of the environment of the server its
77
+ home records (a variable whose value holds a `$`, a line break or a
78
+ character tmux prints encoded is not carried), and a window with only
79
+ `PATH` and the locale names tmux's client needs
80
+ (`LANG`, `LC_ALL`, `LC_CTYPE`), which tmux does not hand on. Every
81
+ creator's environment goes without the names the kernel sets
82
+ (`OATS_INSTANCE`, `OATS_ROOT`, `PI_AGENTS_ROOT`, launch references and
83
+ the others the docs list). A server that already runs keeps the
84
+ environment it has: OATS does not certify where that came from.
85
+ - On each agent window it creates, OATS resets `window-style`,
86
+ `window-active-style` and `cursor-colour` to `default`, and removes
87
+ `COLORFGBG` from the pane, so the pane shows the colours of the terminal
88
+ that views it. It does not reset `pane-colours`: a palette set in your
89
+ tmux configuration still reaches agent panes
90
+ ([what OATS sets](../execution-targets.md#the-oats-tmux-server)).
91
+ - OATS sets nothing server-global on any server when it creates a session
92
+ or a window. `window-size latest` and `aggressive-resize on`, which it
93
+ used to set globally on the server it created its session on, are now set
94
+ on the windows it creates.
95
+ - The spawn result's `attach` is `tmux -S <socket> attach -t <session>` for
96
+ a launched spawn and `oats session attach --home <home>` for
97
+ `--no-launch`; a routed spawn (`--server`) prints `oats session attach
98
+ --server <id> --instance <instance>`. The JSON shapes are unchanged.
99
+ - The kernel no longer exports `launchEnvTmuxFlags` (it had no consumer).
100
+
101
+ ## Upgrade
102
+
103
+ **Retirement no longer deletes branches.** What to do:
104
+
105
+ - **`oats retire --delete-branch` is refused** with `E_BAD_ARGS` before
106
+ anything happens: no child or session is stopped, no hook runs, nothing is
107
+ written. Retire without it; the branch is left in the repository. Inspect it
108
+ there and delete it with Git if it is no longer wanted. Scripts that pass the
109
+ flag must drop it. A self-retire an older OATS recorded with the flag is
110
+ completed with the branch and the worktree left, and its `retired` event says
111
+ so.
112
+ - **A home a failed spawn left that still owes its branch** stays incomplete
113
+ while that branch exists, with the item `the branch the failed spawn created
114
+ is left: OATS does not delete it. Inspect it and delete it with Git if it is
115
+ not wanted, then retry`. The item names no branch; the CLI prints it on the
116
+ next line (the receipt's `retention.recordedBranch`, or the `branch` in the
117
+ retained home's `instance.json` when the worktree step did not run). While
118
+ Git cannot show the branch gone, the item is `git branch <b>: could not
119
+ verify whether it still exists (…)`. Delete the branch with Git, then retire
120
+ again.
121
+ - **A worktree whose branch has no commit yet:** `oats retire
122
+ --discard-worktree`, or a retire that has work to copy, on such a worktree
123
+ refuses with `E_WORK_INSPECTION_FAILED` and removes nothing. Make a commit,
124
+ switch the worktree to a branch that has one, or remove the worktree with
125
+ Git; then retire again.
126
+ - **A worktree Git refuses to read without your own `safe.directory`**
127
+ (typically one owned by another user) now refuses a retire that has work to
128
+ preserve or that removes the worktree, with `E_WORK_INSPECTION_FAILED`. A
129
+ plain retire of such a worktree with nothing to preserve retains it under a
130
+ `detached-unknown` name, without its branch in the receipt. Retire as the
131
+ owner, or with Git's ownership check satisfied for that path.
132
+
133
+ ## Fixed
134
+
135
+ - **A retire keeps what only the worktree has.** The recovery a retire writes
136
+ of a worktree is proven against the commit the worktree has checked out, and
137
+ is checked out at that commit on the worktree's own branch (detached, when
138
+ the branch's name is not one OATS carries). A clean worktree whose HEAD is
139
+ detached at a commit no ref of the repository reaches is preserved in a
140
+ recovery before `--discard-worktree` removes it, also when a retire hook made
141
+ that commit. HEAD is read again immediately before the worktree is removed:
142
+ if it moved since the retire's last inspection, the worktree is not removed
143
+ and the retire stops with `E_WORK_PRESERVATION_FAILED`; the home and the
144
+ worktree are kept, and so is any recovery the retire wrote.
145
+ - **A stopped instance's harness mark is readable in Solarized (#602).** In
146
+ the Desktop's roster, the harness mark of a stopped instance was drawn at
147
+ 4.0:1 in Solarized, under the 4.5:1 the rest of the app keeps. It is now
148
+ 4.7:1. The mark's block is slightly lighter in White and Solarized and
149
+ slightly darker in Dark.
150
+ - **Review threads reach an agent whose session is not on the default tmux
151
+ server (#602).** Sending a pull request's review threads to an agent's
152
+ terminal from the Desktop now uses the tmux socket the instance records, as
153
+ the Desktop's terminals do. Before, the paste always went to the default tmux
154
+ server: for an instance that records another socket it answered that the
155
+ paste failed, or it could go into a window of the same name on the default
156
+ server. An instance that records no socket is pasted to as before.
157
+ - **The Desktop names a retire refused at inspection.** When `oats retire`
158
+ refused a local instance with `E_WORK_INSPECTION_FAILED`, the Remove dialog
159
+ said "The lifecycle CLI is unavailable.", although the CLI had answered. It
160
+ now says: "Retirement was refused: the instance's home or work could not be
161
+ inspected. The home is kept; its session may already have been stopped, and
162
+ earlier retirement steps may have run." The outcome is still reported as
163
+ unknown, because the kernel can refuse after it stopped the children and the
164
+ session and ran the retire hooks. For an instance on a server the same
165
+ sentence replaces that headline, above the host's own message in Details.
166
+ For a local refusal the CLI's own message, which names the path and the
167
+ remedy, is in Details too (next entry). Part of
168
+ [#601](https://github.com/awebai/oats/issues/601).
169
+ - **The Desktop shows the kernel's own message when a local Stop or Remove
170
+ fails.** The dialog said only its own sentence for the error code; the
171
+ message the CLI answered with was dropped. It is now in the dialog's
172
+ **Details**, as the code and the message, under that sentence, as it already
173
+ was for an instance on a server. It is shown only when the installed CLI
174
+ answered with an error and the Desktop has a sentence for its code: a
175
+ timeout, an unreadable answer or an unknown code shows none. Closes
176
+ [#601](https://github.com/awebai/oats/issues/601).
177
+ - **The Desktop shows a kernel or host message as one plain line.** In the
178
+ Details of a refusal (Stop and Remove, the Git panel, Activity, readiness),
179
+ line feeds, the Unicode line and paragraph separators and tabs are collapsed
180
+ into single spaces. The C1 control characters, the bidirectional embedding,
181
+ override and isolate characters, the zero-width space, the word joiner, the
182
+ byte order mark and tag characters are each replaced by U+FFFD, the
183
+ replacement character. A message that holds a carriage return, DEL or
184
+ another ASCII control character other than tab and line feed (a message
185
+ with Windows line endings, for example), or that looks like a credential,
186
+ is withheld whole as `[Detail withheld]`, as before. A long message is cut
187
+ at 2048 characters. For readiness, the message is no longer in the "Couldn't
188
+ refresh" line's tooltip, which keeps the Desktop's sentence and the error
189
+ code; it stays in Details. In the Git panel a failed read's message, which
190
+ could span several lines, is now one line.
191
+ - **A tmux server started from the Desktop gets your environment, not the
192
+ Desktop's (#602).** A tmux server started from the Desktop (by a spawn or a
193
+ start, or by opening a terminal) no longer carries the Desktop's own runtime
194
+ settings. They made Electron programs started from an agent's pane run as
195
+ Node, and on the AppImage they pointed library and data paths into the
196
+ Desktop's mount, which exists only while that Desktop runs. Every program
197
+ the Desktop starts (the `oats` CLI, `tmux`, `gh`, the login shell that
198
+ resolves `PATH`) now gets your environment without them. One change in a
199
+ diagnostic: `probePath` in the backend's `GET /api/cli` answer no longer
200
+ lists the AppImage's own entries. A tmux server that was already running
201
+ keeps the environment it was started with (#616). Known limits are in the
202
+ Desktop README.
203
+ - **The Desktop builds a view's failure placeholder as text.** A tab whose
204
+ view fails to load or to mount shows the same words, built without markup.
205
+ - **`oats retire` no longer refuses a launched instance only because its
206
+ tmux socket file is missing.** After a reboot that cleared tmux's socket
207
+ directory, tmux answers `error connecting to <socket> (No such file or
208
+ directory)` for the socket the instance's session was recorded on, and
209
+ retire refused with `E_RUNTIME_QUIESCE_FAILED`. Retire now goes on when the
210
+ recorded server cannot be reached because its socket file is missing and no
211
+ process on the host works in the instance's home (any process whose working
212
+ directory is in the home, not only the harness). A tmux server keeps running
213
+ when its socket file is removed, so when a process still works in the home,
214
+ or the process scan (`lsof`) cannot run or does not complete, retire refuses
215
+ as before and the message says which. Such a server recreates its socket
216
+ when its process is sent `SIGUSR1`. The check needs `lsof`: on a host
217
+ without it, a retire whose recorded socket file is missing is still refused,
218
+ and the message says that `lsof` is missing; install it, then retire. The
219
+ scan counts only when `lsof` completed: one that was cut off (a timeout, too
220
+ much output, a signal) is no longer read as "no process", here, for a home
221
+ without its session receipt and for a home opened in Herdr; those retires
222
+ are refused. So is one whose listing names no process at all, which a
223
+ completed scan never prints. A window that is still running, or a server
224
+ that cannot be read for another reason, is refused as before.
225
+ [#620](https://github.com/awebai/oats/issues/620).
226
+
227
+ ## Removed
228
+
229
+ - **Three unused routes of the Desktop's local server.**
230
+ `GET /api/session/<instance>` (a capture of the instance's terminal),
231
+ `POST /api/keys/<instance>` (keys or a paste typed into it) and
232
+ `POST /api/interrupt/<instance>` (Ctrl-C) are gone. Nothing in the Desktop
233
+ used them: its terminals attach to the instance's session directly. They
234
+ ran `tmux` without the socket an instance records, so for an instance on
235
+ its own tmux server they would have reached another one. Each path now
236
+ answers as any unknown route does (`404`, `not found`).
237
+ `GET /api/chat/<instance>` is unchanged. Closes
238
+ [#609](https://github.com/awebai/oats/issues/609).
package/docs/schedules.md CHANGED
@@ -86,8 +86,19 @@ jobs) sets it so that its command jobs can be told apart.
86
86
  sends SIGTERM to the child's process group, allows two seconds for cleanup,
87
87
  then sends SIGKILL if the group remains. It observes the direct child's exit
88
88
  before returning; inherited output pipes cannot hold the tick indefinitely.
89
- A timeout leaves effects unconfirmed even if the child printed an envelope.
90
- Spawn previews use the same bounded runner.
89
+ SIGINT, SIGTERM or SIGHUP received by the supervisor enters that same cleanup
90
+ once; repeated signals do not bypass it. A timeout or interrupted supervisor
91
+ leaves effects unconfirmed even if the child printed an envelope. Spawn
92
+ previews use the same bounded runner.
93
+
94
+ Cleanup covers the owned process group. A descendant that creates its own
95
+ session can escape it; inherited pipes are bounded but that escaped process
96
+ is not terminated by this group cleanup. The supervisor checks the group when
97
+ the leader exits and never signals it after observing it empty. This reduces
98
+ the group-ID reuse window; it does not eliminate PID reuse races. SIGKILL,
99
+ OOM and other unrecoverable supervisor deaths cannot run JavaScript handlers:
100
+ cleanup is not guaranteed then. Without a private supervisor receipt, the
101
+ scheduler keeps the unknown attempt and its slot until reconciliation.
91
102
  - **wake** `{…, home, message}` — every due minute inspects the instance at
92
103
  `home`. Running: `message` is delivered once as terminal input (bracketed
93
104
  paste plus Enter), never an interrupt. Not running: the home is started with
@@ -273,9 +284,12 @@ add` / `oats schedule add` definitions need no trust.
273
284
 
274
285
  **Opting out on one host.** `oats trigger disable <member>/<id>` writes
275
286
  `triggers.disabled`, and `oats schedule disable <member>/<id>` writes
276
- `schedules.disabled`, in `oats-local.yaml`; `enable` removes the entry. A
277
- workspace definition is never edited or removed from the CLI (`update` and
278
- `remove` answer `E_AUTOMATION_WORKSPACE`): change the file in Git.
287
+ `schedules.disabled`, in `oats-local.yaml`; `enable` removes the entry. The
288
+ schedule part of a qualified ID accepts up to 100 characters in both
289
+ `schedules.disabled` and named `automations.trust` entries. Trigger definitions
290
+ and `triggers.disabled` keep their 40-character limit; the shared trust list
291
+ does not widen trigger IDs. A workspace definition is never edited or removed
292
+ from the CLI (`update` and `remove` answer `E_AUTOMATION_WORKSPACE`): change the file in Git.
279
293
 
280
294
  **Refresh.**
281
295
 
@@ -329,12 +343,27 @@ current choices. Invalid values fail with `E_BAD_ARGS` before registration or
329
343
  timer changes. `host status` reports the effective `maxConcurrent` and
330
344
  `triggersMaxConcurrent` (`null` when uncapped).
331
345
 
332
- The registry stores explicit choices only. On the first registry read after
333
- upgrade, a legacy stored `maxConcurrent: 1` without the new choice marker is
334
- migrated to the default under the registry lock; other explicit values survive.
335
- If you need a cap of one, run `oats schedule host install --max-concurrent 1`
336
- after upgrading. That explicit choice survives later reads and reinstalls.
337
- Set these values through the CLI; do not edit the registry by hand.
346
+ The registry stores explicit choices only. Reading status or the registry takes
347
+ no registry lock and creates or rewrites no files or directories. A reader
348
+ interprets a pre-migration stored `maxConcurrent: 1` as the default of five in
349
+ memory. Registration, unregistration and cap updates persist that migration
350
+ once, under the registry lock, even if the workspace membership is unchanged.
351
+ Other explicit values and the independent trigger cap survive.
352
+
353
+ A pre-migration hand-set one is indistinguishable from the old implicit one;
354
+ both follow that compatibility choice. When a write migrates one to the default,
355
+ it prints a notice on stderr with `oats schedule host install --max-concurrent 1`
356
+ to restore one if needed. Pure reads stay silent and JSON stdout is unchanged.
357
+ A later explicit one survives writes by current kernels. Older binaries sharing
358
+ the registry can write one back while preserving `capsVersion: 2`; there is no
359
+ provenance to distinguish that from a new explicit one. Stop mixed-version
360
+ writes and explicitly set the desired cap with the supported CLI.
361
+
362
+ A present invalid `maxConcurrent` is `E_SCHEDULE_INVALID`, not a fallback to
363
+ five. Correct it with `oats schedule host install --max-concurrent N` (or
364
+ `--max-concurrent default`) from the deployment, or with `--dir <deployment>`.
365
+ An absent value still means five. Set caps through the CLI; do not edit the
366
+ registry by hand.
338
367
 
339
368
  `oats schedule list --json` answers:
340
369
 
package/docs/servers.md CHANGED
@@ -277,7 +277,10 @@ with `--server`; the remote default applies. `session attach --print` shows
277
277
  the ssh command without running it. A server without the `session` commands
278
278
  (before 0.22.2) is refused with the tmux command to attach there directly,
279
279
  naming the session and window its roster records for the instance (else
280
- `pi-agents`, that kernel's default).
280
+ `pi-agents`, that kernel's default), and the tmux socket when the roster
281
+ records one. The remote command is one quoted word either way: `ssh -t
282
+ <host> 'tmux attach -t <target>'`, or `ssh -t <host> 'tmux -S <socket>
283
+ attach -t <target>'`.
281
284
 
282
285
  ## The roster and harvest
283
286
 
@@ -298,7 +301,9 @@ the host's own facts from its `status --json`: `identity`,
298
301
  `identityAddress`, `teams`, `startedAt`, `createdAt`, `model`,
299
302
  `runtimeState`, `parentInstance`, `siblingInstance`, `relation`,
300
303
  `relativeTo` and `spawnOrigin`. A fact the host does not supply is `null`
301
- (an older host, or a saved route the host no longer lists). A removed or edited registration keeps
304
+ (an older host, or a saved route the host no longer lists). A row also
305
+ carries `waitingOnYou` (needs input) when the host's kernel reports it, and
306
+ only then: an older host's row has no such key. A removed or edited registration keeps
302
307
  its group from the saved routes. State is pulled on every call within
303
308
  `--per-target` (default 20 s) of a total `--budget` (default 45 s); a group
304
309
  not reached is reported with `E_ROSTER_BUDGET`. `--server <id>` narrows it.
@@ -443,6 +443,18 @@ cannot run, retire refuses with
443
443
  `E_RUNTIME_ENDPOINT_UNKNOWN`, even with `--force`: stop that session yourself,
444
444
  then retire again. `oats retire <instance> --plan` says which case applies.
445
445
 
446
+ With a receipt, if the recorded window is still running, or its tmux server
447
+ cannot be read, retire refuses with `E_RUNTIME_QUIESCE_FAILED` and keeps the
448
+ home. When the recorded server cannot be reached because its socket file is
449
+ missing (after a reboot), retire proceeds only when no process on this host
450
+ works in the home (any process whose working directory is in the home, not
451
+ only the harness); a tmux server that lost its socket file still runs, and
452
+ recreates the socket when its process is sent `SIGUSR1`. That check needs
453
+ `lsof`: on a host without it, a retire whose recorded socket file is missing
454
+ is refused, and the message says that `lsof` is missing; install it, then
455
+ retire. A scan that does not complete (a timeout, for example) refuses the
456
+ same way, also for a home without its receipt.
457
+
446
458
  `oats retire <instance> --self` lets an instance retire itself when the human
447
459
  or briefing says it is done. A live harness cannot give a stable final
448
460
  inspection of its own work, so the calling process inspects, runs, and removes
@@ -461,17 +473,31 @@ When a retire hook reports incomplete cleanup, the home is quarantined before
461
473
  any worktree step: the worktree, its git admin entry and the branch stay
462
474
  exactly as they were, so the retry can reach the hook and the work it needs.
463
475
  The retry does the worktree step only once nothing else is outstanding:
464
- retain by default, remove with `--discard-worktree` or `--delete-branch`.
476
+ retain by default, remove with `--discard-worktree`.
465
477
  `--force` removes the home regardless, so it does the worktree step first. A
466
478
  work directory whose git admin entry is gone is never removed: the hooks still
467
479
  run, the home is kept, and `--force` refuses it until you move the directory
468
480
  out or delete it by hand.
469
481
 
470
- Retire never deletes a branch unless you pass `--delete-branch`, and then
471
- only the verified branch: not on a quarantine, its retry or `--force`. A
472
- spawn that fails deletes the branch it created only while the branch's tip
473
- is still where the spawn created it. If something was committed there, the
474
- branch is kept and the failure says so.
482
+ Retire never deletes a branch: not a plain retire, `--discard-worktree`, a
483
+ quarantine, its retry or `--force`. `--delete-branch` is refused with
484
+ `E_BAD_ARGS` before anything happens. The branch stays in the repository:
485
+ inspect it there, and delete it with Git if it is no longer wanted. A spawn
486
+ that fails deletes the branch it created only while the branch's tip is
487
+ still where the spawn created it. If something was committed there, the
488
+ branch is kept and the failure says so; the quarantine's retry stays
489
+ incomplete until the branch is deleted with Git.
490
+
491
+ Before it removes a worktree (`--discard-worktree`, or a failed spawn's
492
+ quarantine that owes it), a retire preserves a commit that only the worktree
493
+ reaches (HEAD detached at a commit no ref of the repository reaches) in a
494
+ recovery, and reads HEAD again immediately before the removal: if HEAD moved
495
+ since the retire's last inspection, the worktree is not removed and the
496
+ retire stops with `E_WORK_PRESERVATION_FAILED`; the home and the worktree are
497
+ kept, and so is any recovery the retire wrote. A worktree whose branch has no
498
+ commit yet cannot be read that way: a retire that would remove it, or that
499
+ has work of it to copy, refuses with `E_WORK_INSPECTION_FAILED` and removes
500
+ nothing.
475
501
 
476
502
  ## Work modes
477
503
 
@@ -54,7 +54,7 @@ members: # repo refs, NO @revision (E_WORKSPAC
54
54
  - git:github.com/acme/tools # a member that ALSO publishes a package (see below)
55
55
 
56
56
  packages: # the ONLY versioned things
57
- oats.framework: v1.6.0 # bare version → resolves through the official catalog
57
+ oats.framework: v1.6.1 # bare version → resolves through the official catalog
58
58
  oats.okf: v4.1.1
59
59
  acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
60
60