@awebai/oats 0.30.3 → 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.
package/docs/desktop.md CHANGED
@@ -110,6 +110,26 @@ registered remote workspace the timer and definitions live on that server, so
110
110
  they do not depend on the Mac staying awake. See [Schedules](schedules.md) for
111
111
  the CLI, cron semantics, observed outcomes and recovery commands.
112
112
 
113
+ ## Remote terminals
114
+
115
+ A terminal for an instance on a registered server is a viewer over ssh
116
+ (`oats session attach --server`). When the link dies, ssh ends the viewer with
117
+ exit 255 within about a minute, and the tab reconnects by itself. It keeps its
118
+ place and scrollback, stops taking input, and shows "Disconnected from
119
+ <server>" with a countdown and a **Reconnect now** button. Attempts wait 1, 2,
120
+ 4, 8 and 15 s, then 30 s each for as long as the tab is open. A successful
121
+ attach resets that. Tmux redraws the screen when the viewer attaches again.
122
+
123
+ Reconnecting stops, with a message and "Close this tab", when the server
124
+ answers that the session is gone, the instance is unknown, the terminal limit
125
+ is reached or the app's backend changed, and at once when ssh cannot be run on
126
+ this computer. It stops after four attempts in a
127
+ row in which OATS on this computer gave no answer, since that is more likely
128
+ the local CLI failing than the link. It also stops when the viewer ends with
129
+ any other exit code. Closing the tab ends the reconnecting. Reconnecting
130
+ never stops or restarts the agent on the server: only the local ssh viewer
131
+ ends.
132
+
113
133
  ## Attach files and screenshots
114
134
 
115
135
  Drop a file onto an agent terminal to insert its path into that agent's draft.
@@ -35,39 +35,56 @@ variables point at are described in
35
35
 
36
36
  ## Backends
37
37
 
38
- `oats spawn --backend tmux|herdr` chooses the backend (default `tmux`). The
39
- backend binary must be installed on the execution host. The chosen session
40
- target is recorded twice: in `instance.json` and in an independent lifecycle
41
- receipt. Every session command checks that the two agree
42
- (`E_RUNTIME_AUTHORITY_MISMATCH` otherwise).
38
+ tmux is the only session backend. `oats spawn --backend tmux` is accepted (it
39
+ is the default); `tmux` must be installed on the execution host. A launched
40
+ home keeps the session it recorded: `session start` and `restart` never
41
+ re-read the defaults. The session target is recorded twice: in
42
+ `instance.json` and in an independent lifecycle receipt. Every session command
43
+ checks that the two agree (`E_RUNTIME_AUTHORITY_MISMATCH` otherwise).
43
44
 
44
45
  ### tmux
45
46
 
46
- Each instance is a window named after the instance in the tmux session
47
- `pi-agents` (override with `PI_AGENTS_TMUX_SESSION`). The receipt records the
47
+ Each instance is a window named after the instance in a tmux session: the
48
+ deployment's `session.tmuxSession`, else `OATS_TMUX_SESSION`, else
49
+ `PI_AGENTS_TMUX_SESSION` (the pre-0.31 variable), else `oats-agents`. Before
50
+ 0.31 the default was `pi-agents`; a home launched then keeps the `pi-agents`
51
+ session it recorded, and `oats status` reads each home on its recorded tmux
52
+ socket and session, never the caller's `$TMUX` server: a caller outside the
53
+ agents' tmux (ssh, cron, a plain terminal) sees the same liveness as `session
54
+ inspect`. A recorded server that cannot be read gives `running: null` with
55
+ `runtimeState: "unreachable"`. The environment variables are read when the spawn runs, and `oats
56
+ inspect --json` reports the session a new spawn would open in as `session`
57
+ (`{tmuxSession}`). A spawn refuses an instance name that is a live window in the
58
+ session it would open in (`E_INSTANCE_NAME_TAKEN`); a live window of that name
59
+ in another session, such as a `pi-agents` window after the default moved, does
60
+ not block it. Session commands target each home's exact recorded window, so
61
+ the two never mix. The receipt records the
48
62
  session, window and socket. The spawn result prints the attach command.
49
63
 
50
- ### Herdr
51
-
52
- Each instance is a Herdr workspace with one pane. The receipt records the
53
- binary, socket, workspace id, pane id, terminal id and protocol. The terminal
54
- id distinguishes a replacement occupant of the same pane.
55
-
56
- - `--herdr-socket <path>` uses an operator-managed Herdr server. OATS never
57
- starts a different server when that socket cannot be inspected.
58
- - Without it, OATS uses `$XDG_CONFIG_HOME/herdr/sessions/oats/herdr.sock`
59
- (default `~/.config/...`) and starts `herdr --session oats server` if no
60
- server is running there.
61
- - The adapter speaks the Herdr socket API at an explicit protocol version,
62
- 20 or 22. A new session records the protocol its server reports (20 for
63
- Herdr 0.8, 22 for 0.9) and refuses any other; before 0.30.3 it always
64
- selected 20, so Herdr 0.9 could not be spawned on. A recorded target is
65
- never renegotiated: each call, the kernel's and the Desktop's, checks that
66
- the server's snapshot reports the recorded protocol.
67
- - Herdr cannot give a pane a command at creation, so OATS types the launch
68
- command into the pane's shell. It waits until the shell has drawn its prompt
69
- (`herdr pane read`, at most 10 s): text typed earlier is cut at the
70
- terminal's line-buffer limit.
64
+ ### Herdr (removed in 0.31.0)
65
+
66
+ OATS no longer supports Herdr. Everything that meets it refuses with
67
+ `E_HERDR_REMOVED`, whose message starts `Herdr is no longer supported by OATS
68
+ (removed in 0.31.0); tmux is the only session backend.` and says what to do:
69
+
70
+ - `oats spawn --backend herdr` or `--herdr-socket`, local or routed with
71
+ `--server` (refused before the server is contacted), and `oats server add
72
+ --herdr`: remove the flag, or use tmux.
73
+ - A schedule's `backend: herdr` or a trigger's `spawn.backend: herdr`: the
74
+ message names the file and key; remove it, or use tmux. A stored local job
75
+ that names it is reported invalid and never runs.
76
+ - `session inspect`, `attach`, `input`, `start`, `restart` and `instance stop`
77
+ on a home a Herdr-era kernel recorded (its `instance.json` records a
78
+ `sessionTarget` or `backend: "herdr"`, or its receipt records a session
79
+ target): retire it and spawn a new instance, which opens in tmux.
80
+ - `oats retire` of such a home needs no Herdr: when no process works in the
81
+ home it proceeds as for an absent session; when one does, it refuses and
82
+ names the pids, so stop its Herdr pane first (for example `herdr --session
83
+ oats server stop`).
84
+
85
+ `oats status` lists such a home with `running: null`, `runtimeState:
86
+ "unsupported"` and the refusal as `runtimeError`. A server registration or
87
+ saved route that records `herdrPath` still loads; the field is ignored.
71
88
 
72
89
  ## Lifecycle
73
90
 
@@ -83,8 +100,8 @@ id distinguishes a replacement occupant of the same pane.
83
100
 
84
101
  `session start` keeps the instance's identity, work tree and notes. It runs no
85
102
  spawn hooks and creates no new home. It runs the recorded launch recipe on the
86
- recorded tmux session or Herdr server; a `--no-launch` home starts on the
87
- default tmux server.
103
+ recorded tmux session; a `--no-launch` home starts on the default tmux
104
+ server.
88
105
 
89
106
  - `--model`, `--launch-config <name>|none`, `--harness` and `--yolo` /
90
107
  `--no-yolo` re-resolve the recipe against the home's recorded context and
@@ -118,18 +135,18 @@ oats session input --home /abs/home --text-file message.txt --json
118
135
  oats session attach --home /abs/home
119
136
  ```
120
137
 
121
- - **inspect** reports `backend`, `present` and `state`: the Herdr agent state
122
- when available, `unknown` for a live harness, `shell` for a fallback shell,
138
+ - **inspect** reports `backend`, `present` and `state`: `unknown` for a live
139
+ harness, `shell` for a fallback shell,
123
140
  `stopped` for an absent or dead terminal, or `not-launched`. An unavailable
124
141
  backend is an error (`E_SESSION_UNAVAILABLE`), never a stopped result.
125
142
  - **input** submits UTF-8 text (stdin or `--text-file`, at most 256 KiB, no
126
- NUL) followed by Enter: bracketed paste in tmux, `pane run` in Herdr. The
127
- text is never run by a shell. A fallback shell, a stopped session or a split
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
128
145
  tmux window is refused. `submitted: true` means the terminal accepted the
129
146
  text, not that the agent processed it. Wake schedules and messaging
130
147
  capabilities use this command ([schedules.md](schedules.md)).
131
- - **attach** is interactive and takes no `--json`. It opens a Herdr terminal
132
- viewer, or a temporary tmux session linked to the agent's window alone.
148
+ - **attach** is interactive and takes no `--json`. It opens a temporary tmux
149
+ session linked to the agent's window alone.
133
150
  Closing the viewer leaves the agent running.
134
151
 
135
152
  ### Attachments
@@ -54,7 +54,7 @@ published to npm. Its developer docs are in
54
54
  | `schedule.mjs`, `schedule-host.mjs`, `triggers.mjs`, `automations.mjs` | schedules, triggers and the host timer |
55
55
  | `operator-dispatch.mjs` | capability commands run from a deployment, and its module store |
56
56
  | `instance-*.mjs` | inspection, lifecycle, events and Git views of an instance |
57
- | `herdr.mjs`, `tmux-config.mjs`, `session-*.mjs` | session backends and terminal input |
57
+ | `tmux-config.mjs`, `session-*.mjs` | the tmux session backend and terminal input |
58
58
  | `capability-contract.mjs`, `provider-binding.mjs` | manifest validation, the hook environment rules, the readiness wire |
59
59
  | `servers.mjs` | routing commands to a registered server |
60
60
 
@@ -41,7 +41,7 @@ arrives from.
41
41
  # oats-workspace.yaml: one default per slot, for every soul
42
42
  packages:
43
43
  oats.okf: v4.0.5
44
- oats.aweb: v1.17.1
44
+ oats.aweb: v1.17.3
45
45
  oats.linear: v1.0.1
46
46
  oats.jira: v1.0.1
47
47
  defaults:
@@ -64,6 +64,14 @@
64
64
  }
65
65
  }
66
66
  },
67
+ "session": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "description": "This host's terminal session defaults for NEW launches (0.31). A launched home keeps the session it recorded.",
71
+ "properties": {
72
+ "tmuxSession": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "The tmux session new tmux instances open their windows in. Absent: OATS_TMUX_SESSION, else PI_AGENTS_TMUX_SESSION (pre-0.31), else oats-agents." }
73
+ }
74
+ },
67
75
  "host": {
68
76
  "type": "object",
69
77
  "additionalProperties": false,
@@ -11,7 +11,7 @@ or workspace membership alone does not make a package official.
11
11
  |---|---|---|---|
12
12
  | `oats.framework` | `oats-framework/v1.4.1` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
13
13
  | `oats.okf` | `v4.0.5` | `oats.okf` (knowledge), `oats.okf-harvest`, `oats.okf-maintenance` | `knowledge-harvester`, `knowledge-maintainer` |
14
- | `oats.aweb` | `v1.17.1` | `oats.aweb` (messaging) | |
14
+ | `oats.aweb` | `v1.17.3` | `oats.aweb` (messaging) | |
15
15
  | `oats.engineering` | `v1.3.0` | `oats.engineering-expert`, `oats.developer`, `oats.code-review` | `code-reviewer` |
16
16
  | `oats.authoring` | `v1.0.3` | `oats.authoring` | |
17
17
  | `oats.jira` | `v1.0.1` | `oats.jira` (tasks) | |
package/docs/packages.md CHANGED
@@ -76,7 +76,7 @@ members:
76
76
  packages:
77
77
  oats.framework: v1.4.1
78
78
  oats.okf: v4.0.5
79
- oats.aweb: v1.17.1
79
+ oats.aweb: v1.17.3
80
80
  teams:
81
81
  platform: { team: "platform:acme.aweb.ai", description: Platform engineering }
82
82
  defaults:
@@ -134,7 +134,7 @@ Declaring a package in the workspace's `packages:` is the trust decision
134
134
  ## `oats package add | remove`
135
135
 
136
136
  ```bash
137
- oats package add oats.aweb v1.17.1 # a catalog version
137
+ oats package add oats.aweb v1.17.3 # a catalog version
138
138
  oats package add acme.tools git:github.com/acme/tools@v0.4.0
139
139
  oats package remove acme.tools
140
140
  ```
@@ -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.
@@ -85,8 +85,8 @@ launch: { harness: claude, model: claude-opus-5-5 } # optional (0.30): what th
85
85
 
86
86
  Schema: [`soul.schema.json`](soul.schema.json). Which teams a soul joins is the
87
87
  deployment's choice (`oats-local.yaml`, [workspaces.md](workspaces.md#teams)),
88
- and the backend, yolo and launch configuration are spawn-time host choices
89
- (`--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
90
90
  `oats-local.yaml`), not soul identity. The harness and model are a soul's
91
91
  *preference* at most (`launch:`), which each machine overrides and spawn flags
92
92
  (`--harness`, `--model`) win over.
@@ -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");