@awebai/oats 0.30.2 → 0.31.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,130 @@
1
+ # OATS 0.31.0
2
+
3
+ ## Changed
4
+
5
+ - **New tmux instances open in the tmux session `oats-agents`, not
6
+ `pi-agents`.** Running instances are unaffected: a home launched before 0.31
7
+ keeps the `pi-agents` session it recorded, `session start` and `restart` put
8
+ it back there, a self-retire kills its recorded window, and `oats status` and
9
+ the Desktop read each home in its recorded session. To keep new instances in
10
+ `pi-agents`, add one line to the deployment's `oats-local.yaml`:
11
+
12
+ ```yaml
13
+ session: { tmuxSession: pi-agents }
14
+ ```
15
+
16
+ `OATS_TMUX_SESSION` also sets it, and `PI_AGENTS_TMUX_SESSION` is still
17
+ honoured, read when the spawn runs. `oats inspect --json` reports the session
18
+ a new spawn would open in as `session`.
19
+ - **Routed ssh calls share one connection per host and notice a dead link.**
20
+ Every `--server` call, `server roster` and `session attach --server` runs
21
+ ssh with `ControlMaster=auto`, `ControlPath=~/.oats/ssh/%C` and
22
+ `ControlPersist=60`, plus `ServerAliveInterval=15` and
23
+ `ServerAliveCountMax=3`. The probe, the command and an attached viewer
24
+ ride one authenticated connection. A viewer whose link dies ends within
25
+ about 60 s with a message naming the command to reattach, instead of
26
+ hanging. These options override any `Control*` settings for the host in
27
+ `~/.ssh/config`. When the control socket path would not fit the 104-byte
28
+ limit or has characters ssh reads as syntax, or `~/.oats/ssh` is not
29
+ private to you, calls run with connection sharing off
30
+ (`ControlPath=none`) and the command warns once per server
31
+ ([servers.md](../servers.md#connections)).
32
+ - **The version probe is asked once per process** per server and target.
33
+ - A server too old for `oats session` is refused with the tmux command for
34
+ the instance's recorded session and window, not a fixed `oats` session.
35
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.32.0`** (was `<0.31.0`), so
36
+ it runs against this release's kernel.
37
+ - **oats.aweb 1.17.3** (catalog and workspace pin, and the bundled mirror).
38
+ Session-delivery guidance matches aw 1.36.15's host wake broker: it
39
+ presents either a line naming what is waiting or the full mail/chat event,
40
+ and recovery after an uncertain crash, compaction or restart goes by exact
41
+ message id (`aw mail show --message-id <id> --json`) or paged
42
+ `aw mail inbox --show-all --json` with `--cursor`. A resident grant's
43
+ `renew: launch` re-registers the wake broker with the new grant, and grant
44
+ seats start with `aw whoami`.
45
+
46
+ ## Added
47
+
48
+ - **Any instance on a server is controllable from here, whoever spawned it.**
49
+ `retire --server` and the routed `session` commands (inspect, attach,
50
+ start, restart, upload), `inspect`, `operation` and `launch-config` reach
51
+ an instance with no saved route by `--home`, or by a name the server's
52
+ roster lists once. A name two homes share there is refused with
53
+ `E_AMBIGUOUS`, listing the homes. Roster rows gain `addressable`, and
54
+ `savedRoute` is information only ([servers.md](../servers.md#run-there)).
55
+ - **The Desktop's reads and lifecycle plans route to the instance's
56
+ machine.** `readiness`, `instance events`, `instance git`, `instance diff`,
57
+ `instance stop --plan|--apply` and `retire --plan` (and its guarded apply)
58
+ take `--server <id>`; the host's kernel does the work, Git included, and
59
+ its envelope is relayed unchanged. A host that does not advertise the
60
+ feature is refused with `E_REMOTE_INCOMPATIBLE` before anything is sent.
61
+ `version --json` lists them in `remote`: `readiness`, `instance-events`,
62
+ `instance-git`, `lifecycle-plans`
63
+ ([desktop-cli-api.md](../desktop-cli-api.md#routed-reads-and-plans)).
64
+ - **Remote roster rows carry the host's facts.** `oats server roster --json`
65
+ instance rows relay `identity`, `identityAddress`, `teams`, `startedAt`,
66
+ `createdAt`, `model`, `runtimeState`, `parentInstance`, `siblingInstance`,
67
+ `relation`, `relativeTo` and `spawnOrigin` from the host's `status --json`,
68
+ `null` where the host does not supply them
69
+ ([desktop-cli-api.md](../desktop-cli-api.md#the-remote-roster-oats-server-roster---json)).
70
+ - **A remote terminal in the Desktop reconnects after a lost link.** A
71
+ viewer that ssh ends with exit 255 keeps its tab and scrollback, goes
72
+ inert, and re-attaches with backoff (1, 2, 4, 8, 15 s, then every 30 s),
73
+ with **Reconnect now** to try at once. It stops with the reason when the
74
+ server says the session is gone or refuses the attach, at once when ssh
75
+ cannot be run on this computer, or after four
76
+ attempts in a row in which this computer's `oats` gave no answer
77
+ ([desktop.md](../desktop.md#remote-terminals)). Reconnecting never touches
78
+ the agent on the server.
79
+
80
+ ## Fixed
81
+
82
+ - **`oats status` reads liveness from each instance's recorded tmux socket**,
83
+ not the caller's `$TMUX` server. Outside the agents' tmux (every `status
84
+ --server` over ssh, cron, a plain terminal) running instances read
85
+ `running: false`, and a row on the default server could read `true`. Now
86
+ `running` agrees with `session inspect`; a recorded server that cannot be
87
+ read gives `running: null` with `runtimeState: "unreachable"` (#347).
88
+ - **`session attach --server` exits 255 when ssh fails before the viewer
89
+ opens** (the version probe, or resolving a name through the host's roster),
90
+ as it does when ssh fails under the viewer; it exited 1, so a caller that
91
+ reconnects on 255 gave up on a flapping link. Every other refusal still
92
+ exits 1. An ssh failure now names the host: `ssh to <host> failed: …`
93
+ (#349).
94
+ - **An `E_SSH` envelope says when ssh never started.** A routed `--json`
95
+ command whose ssh could not be run on this machine (not installed, not
96
+ executable) answers `E_SSH` with `error.details: {"sshStarted": false}`, so
97
+ a caller can stop retrying; a failed link carries no details, as before.
98
+
99
+ ## Removed
100
+
101
+ - **Herdr.** tmux is the only session backend; cross-machine work goes over
102
+ ssh routing (`--server`), which never used Herdr. Nothing is migrated
103
+ silently: everything that meets Herdr refuses with `E_HERDR_REMOVED`, whose
104
+ message says what to do.
105
+ - `oats spawn --backend herdr` or `--herdr-socket` (local, or routed with
106
+ `--server`, refused before the server is contacted) and `oats server add
107
+ --herdr`: remove the flag, or use tmux. `--backend tmux` is still accepted,
108
+ and `oats version --json` advertises `sessionBackends: ["tmux"]`.
109
+ - A schedule's `backend: herdr` or a trigger's `spawn.backend: herdr`: the
110
+ message names the file and key; remove it, or use tmux. A stored local job
111
+ that names it is reported invalid and never runs.
112
+ - `session inspect`, `attach`, `input`, `start`, `restart` and `instance
113
+ stop` on an instance opened in Herdr: retire it (`oats retire <name>`) and
114
+ spawn a new instance; it opens in tmux. `oats status` lists such an
115
+ instance with `running: null`, `runtimeState: "unsupported"` and the
116
+ refusal as `runtimeError`, and never fails because of it.
117
+ - `oats retire` of an instance opened in Herdr works without Herdr. When a
118
+ process still works in its home it refuses, naming the pids: stop its
119
+ Herdr pane (for example `herdr --session oats server stop`), then retire.
120
+ - A server registration or saved route that records `herdrPath` still
121
+ loads; the field is ignored.
122
+ - **The Desktop** no longer offers Herdr as a session backend and never
123
+ opens a Herdr terminal; it refuses one with `E_HERDR_REMOVED`, including
124
+ for a remote host still on an older OATS. An instance opened in Herdr
125
+ stays in the roster with the reason; its Open, Start and Restart are
126
+ disabled, and Remove (retire) still works, so it can be retired and
127
+ spawned again in tmux.
128
+
129
+ An idle Herdr server that OATS started (session `oats`) is not OATS state;
130
+ stop it with `herdr --session oats server stop`.
package/docs/schedules.md CHANGED
@@ -54,7 +54,10 @@ both are evaluated by the croner library.
54
54
  launchConfig?, harness?, model?, yolo?, wake?}` — every due minute launches
55
55
  one disposable instance of `agent` with the options `oats spawn` takes.
56
56
  `agentsRoot`, when given, must be the deployment's `agents/` root; `repo`
57
- is the work repository, as `--repo`. `model` is a model id (a letter or
57
+ is the work repository, as `--repo`. `backend` is `tmux`; `backend: herdr`
58
+ is refused (`E_HERDR_REMOVED`, naming the file and key: Herdr was removed in
59
+ 0.31.0), and a stored job that names it is reported invalid and never runs.
60
+ `model` is a model id (a letter or
58
61
  digit, then letters, digits and `. _ : / @ + - [ ]`, at most 128
59
62
  characters) or `@native-default`; `agent` and `repo` never start with `-`,
60
63
  so no value can be read as an option of the child `oats spawn`. Each run is
package/docs/servers.md CHANGED
@@ -26,13 +26,59 @@ oats server remove build
26
26
  - `--path` prepends directories to the minimal remote PATH of every routed
27
27
  command (`~/.local/bin:/opt/pi/bin`), where the remote spawn looks for the
28
28
  harness binary.
29
- - `--herdr` records the remote Herdr path in the registration and in each
30
- saved route. Routed commands do not pass it on; the remote kernel finds
31
- `herdr` on its own PATH.
29
+ - `--herdr` is refused (`E_HERDR_REMOVED`): Herdr was removed in 0.31.0. A
30
+ registration or saved route that still records `herdrPath` loads; the field
31
+ is ignored, never written or printed.
32
32
  - `--label` sets a display name. `--replace` overwrites an existing id.
33
33
  - Registrations live in `~/.oats/servers.json` on this machine, never in a
34
34
  repository.
35
35
 
36
+ ## Connections
37
+
38
+ Every routed ssh call carries these options, before `--` and the host:
39
+
40
+ | Option | Why |
41
+ |---|---|
42
+ | `BatchMode=yes`, `ConnectTimeout=15` | a prompt or an unreachable host fails fast |
43
+ | `ServerAliveInterval=15`, `ServerAliveCountMax=3` | a link that died without a reset ends the call, an attached viewer included, within about 60 s |
44
+ | `ControlMaster=auto`, `ControlPath=~/.oats/ssh/%C`, `ControlPersist=60` | every call to one host (the probe, the command, a viewer, concurrent Desktop reads) shares one authenticated connection, kept 60 s after its last client |
45
+
46
+ - Options given with `-o` override `~/.ssh/config`, so these replace any
47
+ `ControlMaster`, `ControlPath` or `ControlPersist` set there for the host.
48
+ Everything else in your ssh config (users, keys, `ProxyJump`, host
49
+ verification) applies as usual; a `ProxyJump` host is reached once per
50
+ master.
51
+ - `~/.oats/ssh` (under `OATS_HOME_DIR` when set) is created mode 0700. OATS
52
+ does not use it if it is owned by another user or writable by group or
53
+ others.
54
+ - ssh binds the control socket at the path plus 17 bytes, within the 104-byte
55
+ socket path limit of macOS, so the directory path may be at most 45 bytes.
56
+ `/Users/<name>/.oats/ssh` fits for any name up to 23 characters. The path
57
+ must also be one ssh reads literally: letters, digits and `. _ / + , : @ =
58
+ -` only (a space, `%`, `$`, a quote or `#` is ssh syntax).
59
+ - When the path does not fit, has other characters, or the directory is not
60
+ private, calls run with `ControlPath=none` instead: no connection sharing,
61
+ including any your ssh config sets up; keepalives still apply. The command
62
+ warns once per server on stderr, naming the path and the fix (an
63
+ `OATS_HOME_DIR` or `HOME` that is shorter or plain).
64
+ - A master left stale by a network drop is replaced by ssh on the next call.
65
+ - The version probe (`oats version --json`) is asked once per process per
66
+ server and target. `server add --replace` to another target forgets it.
67
+ - When ssh fails under an attached viewer (a lost link, or one never made),
68
+ `session attach --server` exits 255 and says that, if the link was lost,
69
+ the instance keeps running on the server, with the command to reattach.
70
+ When ssh fails before the viewer opens (the version probe, or a name
71
+ resolved through the host's roster), it also exits 255, with `oats: ssh to
72
+ <host> failed: …` (`--json`: the `E_SSH` envelope). Every other refusal
73
+ (an incompatible host, an unknown or ambiguous name, bad arguments) exits 1,
74
+ and so does an ssh that cannot be run on this machine at all: no link can
75
+ come back.
76
+ - Every routed command reports ssh's own failure as `E_SSH` (`ssh to <host>
77
+ failed: …`). When ssh never started on this machine, the `--json` envelope
78
+ says so with `error.details: {"sshStarted": false}`; without details, ssh
79
+ ran and the link failed
80
+ ([desktop-cli-api.md](desktop-cli-api.md#ssh-failures-e_ssh)).
81
+
36
82
  ## Run there
37
83
 
38
84
  ```bash
@@ -50,14 +96,18 @@ sent as text or on stdin, never as paths.
50
96
  |---|---|---|
51
97
  | `spawn` | registered workspace | the requested harness, backend and yolo option; `launch-config` for `--launch-config`; `schedule` for a wake schedule |
52
98
  | `status` | registered workspace | |
53
- | `retire` | saved route | `retire-home` to retire by exact home |
54
- | `session inspect`, `session attach` | saved route | `session` |
55
- | `session start`, `session restart` | saved route | `session-start`; `session-restart`; `launch-config` when `--launch-config`, `--harness` or `--yolo` is given |
56
- | `session upload` | saved route | `session-upload` |
99
+ | `retire` | the instance's home | `retire-home` to retire by exact home |
100
+ | `session inspect`, `session attach` | the instance's home | `session` |
101
+ | `session start`, `session restart` | the instance's home | `session-start`; `session-restart`; `launch-config` when `--launch-config`, `--harness` or `--yolo` is given |
102
+ | `session upload` | the instance's home | `session-upload` |
57
103
  | `okf harvest --instance <name>` | the saved home | `harvest` |
58
104
  | `schedule ...` | registered workspace | `schedule` |
59
- | `launch-config list\|set\|remove\|preview` | `--dir`, a saved route, or the registered workspace | `launch-config` |
60
- | `inspect`, `operation run` | `--dir`, a saved route, or the registered workspace | `operations` |
105
+ | `launch-config list\|set\|remove\|preview` | `--dir`, the instance's home, or the registered workspace | `launch-config` |
106
+ | `inspect`, `operation run` | `--dir`, the instance's home, or the registered workspace | `operations` |
107
+ | `readiness` | the instance's home, `--dir`, or the registered workspace | `readiness` (`readinessApi: 2`) |
108
+ | `instance events` | the instance's home | `instance-events-2` (`eventsApi: 2`) |
109
+ | `instance git`, `instance diff` | the instance's home; the Git runs there | `instance-git` (`instanceGitApi: 1`) |
110
+ | `instance stop --plan\|--apply`, `retire --plan`, `retire --plan-revision … --idempotency-key …` | the instance's home | `lifecycle-plans` (`lifecycleApi: 1`) |
61
111
 
62
112
  Before every routed command except `status`, the kernel reads the server's
63
113
  `oats version --json`. A remote OATS older than 0.22.1 is refused, and so is a
@@ -66,19 +116,36 @@ checked against the harness it would use (the flag, or the soul's default as
66
116
  the remote roster reports it). A host that advertises no harness list is
67
117
  assumed to run only pi and claude, on tmux, with no launch options.
68
118
 
69
- **Addressing an instance.** Instance commands take `--instance <name>` (spawned
70
- from this machine) or `--home </remote/home>`. The home is the identity; use it
71
- when two souls on the host own same-named instances. A name and a home that
72
- disagree are refused (`E_HOME_MISMATCH`).
73
-
74
- **`--dir` with `--server`.** For `inspect`, `operation` and `launch-config`,
75
- an explicit `--dir` names a directory on the server and travels as is. Every
76
- other routed command refuses `--dir`; its scope comes from the registration.
119
+ **Addressing an instance.** Instance commands (`retire`, the `session`
120
+ commands, `inspect`, `operation`, `launch-config`) reach any instance on the
121
+ server, whoever spawned it, by `--home </remote/home>` or by name
122
+ (`--instance <name>`, or the positional name of `retire`):
123
+
124
+ - A name spawned from this machine resolves through its saved route.
125
+ - Any other name resolves through the server's roster (one `status --json`
126
+ in the registered workspace) to its one home. A name that two homes carry
127
+ there is `E_AMBIGUOUS`, listing the homes (`error.details.candidates`);
128
+ pass `--home`. A name the roster does not list is `E_SNAPSHOT_UNKNOWN`.
129
+ - A `--home` no saved route owns is sent through the registration's target;
130
+ the server's kernel decides whether it is an instance home and its refusal
131
+ is relayed as is.
132
+ - The home is the identity; use it when two souls on the host own
133
+ same-named instances. A name and a home that disagree are refused
134
+ (`E_HOME_MISMATCH`).
135
+
136
+ **`--dir` with `--server`.** For `inspect`, `operation`, `launch-config`,
137
+ `readiness`, `instance`, and a `retire --plan` or guarded retire apply, an
138
+ explicit `--dir` names a directory on the server and travels as is. Every
139
+ other routed command refuses `--dir`; its scope comes from the
140
+ registration.
77
141
 
78
142
  **Not routed.** `session input` runs on the execution host, where schedules
79
143
  and messaging capabilities call it. `session restart --stop-grace` is refused
80
144
  with `--server`; the remote default applies. `session attach --print` shows
81
- the ssh command without running it.
145
+ the ssh command without running it. A server without the `session` commands
146
+ (before 0.22.2) is refused with the tmux command to attach there directly,
147
+ naming the session and window its roster records for the instance (else
148
+ `pi-agents`, that kernel's default).
82
149
 
83
150
  ## The roster and harvest
84
151
 
@@ -91,8 +158,13 @@ The **roster** is what the Desktop shows: one group per server id and route
91
158
  target (host and workspace), with the registration (present or not), the
92
159
  probe result, the remote souls, the instances joined with saved routes
93
160
  (`savedRoute`, `running` or `null` when unknown, `retirePending`,
94
- `rollbackIncomplete`, `missingRemotely`), and `retireFailures` (deferred
95
- self-retirements that failed there). A removed or edited registration keeps
161
+ `rollbackIncomplete`, `missingRemotely`, `addressable`), and `retireFailures`
162
+ (deferred self-retirements that failed there). Each instance row also relays
163
+ the host's own facts from its `status --json`: `identity`,
164
+ `identityAddress`, `teams`, `startedAt`, `createdAt`, `model`,
165
+ `runtimeState`, `parentInstance`, `siblingInstance`, `relation`,
166
+ `relativeTo` and `spawnOrigin`. A fact the host does not supply is `null`
167
+ (an older host, or a saved route the host no longer lists). A removed or edited registration keeps
96
168
  its group from the saved routes. State is pulled on every call within
97
169
  `--per-target` (default 20 s) of a total `--budget` (default 45 s); a group
98
170
  not reached is reported with `E_ROSTER_BUDGET`. `--server <id>` narrows it.
@@ -103,8 +175,8 @@ instance's saved home on the server and relays its envelope.
103
175
  ## What this machine keeps
104
176
 
105
177
  A **saved route** per remote instance, under `~/.oats/remote/<server>/`,
106
- written at spawn: the ssh host, workspace, oats path, optional Herdr path and
107
- PATH prefix, and the remote home. Retire, harvest and session commands use
178
+ written at spawn: the ssh host, workspace, oats path and PATH prefix, and the
179
+ remote home. Retire, harvest and session commands use
108
180
  the saved route, not the current registration, so editing or removing a
109
181
  registration never orphans a remote home. The route is removed when the
110
182
  remote kernel reports the home gone.
@@ -123,8 +195,5 @@ remote kernel reports the home gone.
123
195
 
124
196
  ## Limits
125
197
 
126
- - Lifecycle actions need a saved route from this machine. An instance the
127
- remote reports but that was spawned elsewhere appears in the roster with
128
- `savedRoute: false` and is read-only here.
129
198
  - No Git runs over SSH: repository operations always run on the server, by
130
199
  its kernel, in its deployment.
@@ -29,6 +29,7 @@ that model.
29
29
  | Instance operating doc | `<home>/AGENTS.md` (generated) |
30
30
  | Instance skills | `<home>/.agents/skills/` |
31
31
  | Instance modules | `<home>/.oats/modules/<capability>/` (the copies this instance runs) |
32
+ | Instance `oats` | `<home>/.oats/bin/oats` (a link to the kernel that last launched it) |
32
33
  | Instance record | `<home>/instance.json` (`modules`, `providers`, `workspace`, `teams`) |
33
34
 
34
35
  ## Soul anatomy
@@ -84,8 +85,8 @@ launch: { harness: claude, model: claude-opus-5-5 } # optional (0.30): what th
84
85
 
85
86
  Schema: [`soul.schema.json`](soul.schema.json). Which teams a soul joins is the
86
87
  deployment's choice (`oats-local.yaml`, [workspaces.md](workspaces.md#teams)),
87
- and the backend, yolo and launch configuration are spawn-time host choices
88
- (`--backend`, `--yolo`, `--launch-config`, or a launch configuration in
88
+ and yolo and the launch configuration are spawn-time host choices
89
+ (`--yolo`, `--launch-config`, or a launch configuration in
89
90
  `oats-local.yaml`), not soul identity. The harness and model are a soul's
90
91
  *preference* at most (`launch:`), which each machine overrides and spawn flags
91
92
  (`--harness`, `--model`) win over.
@@ -126,6 +127,7 @@ full copy** of every capability the soul resolved to:
126
127
  <skill>/SKILL.md # one level deep, where every harness discovers skills
127
128
  .claude/skills → ../.agents/skills
128
129
  .oats/modules/<capability>/ # the whole capability: oats.json, bin/, injects/, skills/ (hooks run from here)
130
+ .oats/bin/oats → <kernel>/bin/oats.mjs # the kernel that last launched this home: first on the harness's PATH
129
131
  work/ # worktree, checkout symlink, attached tree, or private directory
130
132
  TASK.md # briefing and task
131
133
  instance.json # provenance (below); `soulDir` = the soul directory hooks get as OATS_SOUL
@@ -244,7 +246,10 @@ it would bind, `providers` (the `--provider` map exactly as given) and
244
246
  the apply refuses with `E_DECISION_STALE` if a member
245
247
  moved in between. `--provider <cap> key=value` (repeatable; dotted keys nest)
246
248
  must name a capability the soul resolves (`E_CAPABILITY_MISSING` otherwise) and
247
- needs a workspace deployment. The full DTOs are in
249
+ needs a workspace deployment. Spawn takes one soul and the flags it reads:
250
+ another positional, or a flag it does not know, is `E_BAD_ARGS` naming the
251
+ argument, before anything is created (a bare `key=value` is a provider
252
+ setting given without `--provider <capability>`). The full DTOs are in
248
253
  [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
249
254
 
250
255
  Examples of spawn hooks:
@@ -147,7 +147,8 @@ export function uploadAttachment({ file, home, server, instance }, io = {}) {
147
147
  if (!home) throw err("E_BAD_ARGS", "session upload needs --home </absolute/instance> or --server <id> with --instance <name> or --home");
148
148
  return { ...receiveAttachment(home, name, bytes, { maxBytes }), name, home: realpathSync(home), source: abs };
149
149
  }
150
- const route = resolveRoute(server, { instance, home }, "session upload");
150
+ io = { ...io, serverId: server };
151
+ const route = resolveRoute(server, { instance, home }, "session upload", io);
151
152
  const remote = checkRemote(route.target, io);
152
153
  // Both lists must carry the token; a probe without the arrays is an old kernel.
153
154
  const advertises = (list) => Array.isArray(list) && list.includes("session-upload");