@awebai/oats 0.22.17 → 0.22.19

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,228 @@
1
+ # Proposal: manage server agents from an iPhone
2
+
3
+ Status: proposed, September 7, 2026. Requested by Juan following a feasibility
4
+ assessment. This document authorizes no implementation, deployment, or changes
5
+ to running teams. Recommendations below are not shipped functionality.
6
+
7
+ ## Purpose and recommendation
8
+
9
+ Let an operator manage OATS agents on a server and talk to them from an iPhone.
10
+ Agents keep working when the phone is locked, disconnected, or switched off.
11
+ Screenshots and files are part of the first useful experience.
12
+
13
+ Start with one server reached privately through Tailscale and a mobile web app
14
+ that can be installed on the iPhone Home Screen. Build on the existing OATS
15
+ control and session contracts. A native iPhone app can consume the same service
16
+ later if sharing, voice, notifications, or other native integration justify it.
17
+
18
+ The substantial work is making the client/server boundary explicit and making
19
+ conversation delivery reliable. A narrow management and terminal client is
20
+ feasible without rewriting the kernel. A polished conversation experience is
21
+ more than embedding the Desktop terminal in a small screen.
22
+
23
+ This extends the [architecture reassessment](2026-09-07-architecture-reassessment.md)
24
+ and its interpretation of Juan's KB direction: standard components with clear
25
+ contracts, replaceable service providers, and preserved identity and knowledge
26
+ across runtime changes. It does not supersede those contracts.
27
+
28
+ ## What exists and what is missing
29
+
30
+ Assessment baseline: source main `d20f082`, with Desktop behavior inspected in
31
+ the lead worktree containing that main. These are reusable pieces, not a claim
32
+ that a remote mobile API already exists.
33
+
34
+ | Area | Existing basis | Work needed for mobile |
35
+ | --- | --- | --- |
36
+ | Lifecycle and configuration | CLI-backed roster, spawn/start, model choices, retirement and schedule operations | Authenticated network access with exact target selection and truthful results |
37
+ | Terminal access | Session adapters, HTTP pane capture/input, Electron PTY attachment | Network streaming, resize, reconnect and bounded viewer lifetime outside Electron |
38
+ | Attachments | Desktop file/image ingestion and execution-host upload | Browser or native file selection, authenticated upload and visible transfer state |
39
+ | Provider operations | Scoped inspection and declared capability operations | Reuse the same discovery and invocation routes in mobile views |
40
+ | Conversations | aweb is the intended owner of durable identity, messages and delivery | Qualify the human/agent conversation, reply and replay contracts before promising chat |
41
+ | Background operation | Durable agent sessions and OATS scheduling mechanisms | Qualify service, scheduler and message wake operation with every GUI closed |
42
+
43
+ The current [Desktop HTTP backend](../../packages/desktop/server/oats-web.mjs)
44
+ binds to loopback and checks local Host/Origin values. It assumes a trusted
45
+ local client; it is not an authenticated remote service. Live terminal data,
46
+ resize and attachment calls currently cross
47
+ [Electron IPC](../../packages/desktop/main.mjs). Exposing the current port
48
+ unchanged or running Electron headlessly would not complete the required work.
49
+
50
+ ## Architecture and ownership
51
+
52
+ ```mermaid
53
+ flowchart TD
54
+ Phone[iPhone: mobile web or native client] -->|HTTPS over Tailscale| Service[OATS host control service]
55
+ Desktop[Desktop client] -->|Shared control contracts| Service
56
+ Service --> Kernel[Existing OATS CLI and kernel]
57
+ Kernel --> Sessions[Session adapters: tmux or Herdr]
58
+ Service --> Messaging[Selected messaging provider: aweb initially]
59
+ Kernel --> Providers[Selected knowledge and task capabilities]
60
+ ```
61
+
62
+ The service is a proposed independently runnable form of the existing control
63
+ backend, with the necessary terminal transport moved out of Electron. It
64
+ delegates lifecycle mutations to the supported OATS CLI. Desktop adoption can
65
+ be incremental; mobile must not introduce a second lifecycle implementation.
66
+
67
+ | Component | Responsibility |
68
+ | --- | --- |
69
+ | OATS kernel | Construction, lifecycle, runtime/model configuration, host placement, schedules and capability resolution |
70
+ | Host control service | Authenticated client access, scoped command dispatch, bounded live observations, attachments and terminal viewers |
71
+ | Session backend | Durable execution, attach/detach, literal input, resize and terminal observation |
72
+ | Messaging provider | Durable identity, conversations, events, delivery and any missing human/agent messaging semantics |
73
+ | Knowledge provider | Knowledge representation, access, harvest and promotion policy |
74
+ | Phone and Desktop | Operator interaction and presentation of the same contracts |
75
+
76
+ Use one control service for the selected deployment on a host, not a process
77
+ per agent or client. Existing schedulers and message wake mechanisms remain
78
+ responsible for their work; do not build another scheduler or wake broker in
79
+ the mobile client. Share bounded subscriptions and roster refreshes where
80
+ possible. Open terminal viewers only when needed, apply output backpressure,
81
+ and detach abandoned viewers without stopping agents.
82
+
83
+ tmux and Herdr remain interchangeable session adapters. Mobile access does not
84
+ require a Herdr migration. Start with a direct connection to one execution
85
+ host; later add saved server endpoints or reuse OATS registered-host routing.
86
+ SSH credentials for routed hosts stay on the routing server, not the phone.
87
+
88
+ ## Private access through Tailscale
89
+
90
+ Install the ordinary Tailscale app on the iPhone and connect the server to the
91
+ same tailnet. Use Tailscale Serve to proxy the loopback service over HTTPS,
92
+ accessible only through the tailnet's access rules. No embedded VPN or public
93
+ Funnel endpoint is needed. This is a proposed deployment choice, not a current
94
+ OATS installation instruction. [Tailscale Serve documentation](https://tailscale.com/docs/features/tailscale-serve)
95
+
96
+ Network membership must map to an explicit authorized operator. For an initial
97
+ single-owner deployment, choose a simple revocable application session or
98
+ validated Tailscale identity; do not add a second account platform. If trusting
99
+ Serve's identity headers, keep the backend reachable only through the trusted
100
+ local proxy path and follow its header-handling requirements. Configure the
101
+ actual HTTPS Host/Origin rather than removing the existing checks.
102
+ [Tailscale identity headers](https://tailscale.com/docs/features/tailscale-serve#identity-headers)
103
+
104
+ Authorize every action, terminal attachment and upload against a registered
105
+ workspace and exact instance home. Keep runtime permission settings such as
106
+ `yolo` separate from who may control the agent. Exclude multi-tenant hosting
107
+ and elaborate permissions administration from the first version.
108
+
109
+ ## Phone experience
110
+
111
+ The primary screen lists workspaces and their agents, with readable names and
112
+ running, stopped, needs-input or unknown states. A disconnected server is
113
+ unreachable, not evidence that its agents stopped. Do not infer needs-input
114
+ or task completion solely from terminal activity.
115
+
116
+ An agent page offers conversation, start controls and session details. Starting
117
+ a stopped agent uses its existing home and shows runtime/model options and
118
+ effective permission mode. Make a launch override distinct from editing future
119
+ defaults. A running agent's model does not silently change because a setting
120
+ was edited. Interrupt and retirement are explicit actions with their existing
121
+ semantics; retirement is not relabeled as a harmless Stop button.
122
+
123
+ The composer supports text and selected photos/files with visible upload state.
124
+ Upload to the agent's execution host, preserve the destination while a transfer
125
+ is in flight, and submit only when attachments are ready. Failed transfers stay
126
+ visible and must not silently redirect to another agent. Start with a file/photo
127
+ picker; system dictation can supply text. A native share extension and realtime
128
+ spoken conversation are later options.
129
+
130
+ Terminal access is a secondary view for interactive harness prompts and tools.
131
+ Provide touch-accessible Escape, interrupt and other essential terminal keys.
132
+ Avoid exposing tmux session names, Desktop split layouts or backend chrome.
133
+ Pasting or uploading into a terminal does not automatically press Enter; a
134
+ conversation Send action is an explicit submission.
135
+
136
+ ## Reliable conversations and background behavior
137
+
138
+ Terminal input is useful across harnesses, but a pane transcript is not a
139
+ durable conversation model. Rich chat should use the selected messaging
140
+ provider, initially aweb. Missing durable reply, read-state or replay semantics
141
+ belong in that provider's contract, not an OATS-specific message database or an
142
+ ANSI-output parser. An installation without those capabilities can still offer
143
+ management and a terminal, with chat availability described honestly.
144
+
145
+ Before qualifying chat, establish the following behavior:
146
+
147
+ - The operator has their own sender identity; the client does not impersonate
148
+ the destination agent. Replies remain associated with the conversation.
149
+ - Stable message IDs and replay cursors allow reconnect and missed-message
150
+ retrieval. Retry after an uncertain send does not submit the same request
151
+ twice. Offline text remains a draft until explicitly sent; do not queue
152
+ terminal keystrokes or destructive controls for automatic replay.
153
+ - Accepted, delivered and acted-on are different observations. An input write
154
+ or agent wake is not proof of a completed task. Unknown outcomes remain
155
+ unknown until the existing operation can be reconciled.
156
+ - Delivery and wake continue on the server while clients are absent. Waking a
157
+ running harness and starting a stopped instance are different actions;
158
+ follow explicit policy before a message starts an agent or consumes compute.
159
+
160
+ iOS normally suspends background apps. Do not depend on an always-connected
161
+ phone SSE, WebSocket or SSH session. The server retains state; a notification
162
+ signals new activity, and reopening the client fetches authoritative updates.
163
+ Notifications can be delayed or disabled and are not delivery receipts.
164
+ [Apple background execution documentation](https://developer.apple.com/documentation/uikit/extending-your-app-s-background-execution-time)
165
+
166
+ Home Screen web apps support Web Push on iOS/iPadOS 16.4 and later, with
167
+ permission requested through user interaction. Native applications can use
168
+ APNs. Both require a server-side notification path and device testing. Keep
169
+ sensitive message bodies out of lock-screen notifications by default. Private
170
+ application access can remain on Tailscale while the server makes outbound
171
+ connections to the push service; test this combination on a real phone rather
172
+ than assuming VPN reconnection or notification behavior.
173
+ [WebKit Web Push documentation](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)
174
+ [Apple remote notification documentation](https://developer.apple.com/documentation/usernotifications/setting-up-a-remote-notification-server)
175
+
176
+ ## Proposed delivery sequence
177
+
178
+ 1. **Prove the service boundary.** Extract only what an independent client
179
+ needs, retain existing CLI dispatch and add authenticated network access.
180
+ Qualify one server over Tailscale, including operation with Desktop closed.
181
+ 2. **Deliver mobile management and terminal access.** Workspace/agent list,
182
+ start with model choices, explicit lifecycle actions, terminal input/output,
183
+ screenshots/files and reconnect. Reuse existing schedule controls and
184
+ provider inspection. This is useful without claiming polished chat.
185
+ 3. **Qualify conversations and notifications.** Resolve the messaging-provider
186
+ contract gaps, then implement conversation presentation, replay, send
187
+ deduplication and opt-in notifications. Prove delivery while the app sleeps.
188
+ 4. **Decide on native and broader hosting.** Use actual phone experience to
189
+ choose whether native sharing, voice and polish justify another client.
190
+ Expand to multiple servers through existing routes or explicit endpoints;
191
+ avoid adding a fleet control platform as a prerequisite.
192
+
193
+ The browser client is the recommended first delivery vehicle, not an
194
+ architectural dependency. If native is chosen first, the same server and
195
+ messaging requirements apply. No calendar estimate is justified until the
196
+ service extraction and conversation-contract gaps have been scoped.
197
+
198
+ ## Acceptance before calling it usable
199
+
200
+ - With Desktop closed and the phone locked or disconnected, existing agents
201
+ keep running and an explicitly enabled test schedule and message wake work.
202
+ No production team or harvest schedule is changed to demonstrate this.
203
+ - Start the intended stopped home with the chosen runtime/model. Same-named
204
+ instances elsewhere cannot receive its actions. Unreachable hosts display
205
+ uncertainty and retrying an interrupted mutation does not blindly repeat it.
206
+ - Upload an actual iPhone screenshot and a file to the execution host; verify
207
+ complete bytes and destination, and demonstrate a visible transfer failure.
208
+ - Switch Wi-Fi/cellular, lock/unlock, and reconnect. Conversation history catches
209
+ up without duplicate sends. A terminal reconnect can show a bounded current
210
+ capture; it must not pretend to recover a durable conversation transcript.
211
+ - Close terminal views and disconnect abruptly without killing agent sessions
212
+ or accumulating PTYs. Repeated reconnects and slow clients leave bounded
213
+ memory/process use; share observations instead of polling each agent per view.
214
+ - Test a real notification, denied notification permission, expired client
215
+ access and tailnet disconnection. The UI preserves drafts and accurately
216
+ states what it knows, without showing an unsuccessful send as delivered.
217
+ - A different knowledge provider's declared operations remain usable without
218
+ mobile code assuming OKF, a particular file layout or a universal harvester.
219
+
220
+ Open implementation decisions are the initial operator authentication method,
221
+ the existing aweb contracts that need extension, notification ownership under
222
+ the messaging provider, and the precise terminal stream protocol. Settle them
223
+ with narrow proofs before expanding scope. This proposal changes neither
224
+ current deployment defaults nor the priority of ongoing reliability work.
225
+
226
+ Related contracts: [provider operations](operations-contract.md),
227
+ [Desktop CLI API](../desktop-cli-api.md), [servers](../servers.md),
228
+ [schedules](../schedules.md), and [Desktop](../desktop.md).
@@ -0,0 +1,164 @@
1
+ # Launch configurations and launch recipes
2
+
3
+ A **launch configuration** is a named way to start a harness, declared per
4
+ scope under `launch-configs:` in `oats-config.yaml` (see
5
+ docs/configuration.md): runtime, an executable, literal arguments,
6
+ environment (literals or `{fromEnv}` references), model, yolo. It is
7
+ independent of any soul; a soul may name one as its default
8
+ (`launch-config:` in soul.yaml, `oats soul set --launch-config`), and a
9
+ spawn, start or restart selects one by name.
10
+
11
+ A **launch recipe** is what a start is made of, recorded in the instance's
12
+ `instance.json` under `launch` beside the rendered `command`:
13
+
14
+ ```json
15
+ {
16
+ "version": 1,
17
+ "runtime": "claude",
18
+ "launchConfig": "personal", "launchConfigSource": "/scope",
19
+ "executable": "/scope/tools/claude-wrapper.sh",
20
+ "executableDeclared": "./tools/claude-wrapper.sh", "executableResolvedFrom": "relative to /scope",
21
+ "args": ["--settings", "/abs/settings.json"],
22
+ "env": { "KEY": { "fromEnv": "SRC" }, "LIT": "plain" },
23
+ "model": "claude-opus-5", "yolo": true,
24
+ "hooks": {
25
+ "launch": { "claude": "--dangerously-load-development-channels plugin:aweb-channel@awebai-marketplace" },
26
+ "env": { "AWEB_DELIVERY": "session" },
27
+ "contributions": [{ "capability": "oats.aweb", "layer": "messaging", "level": "/scope", "settings": { "delivery": "session" }, "trust": { "trusted": true, "integrity": "sha256-..." }, "launch": { "claude": "..." }, "env": ["AWEB_DELIVERY"] }]
28
+ },
29
+ "prompt": { "kind": "task-file", "file": "TASK.md" }
30
+ }
31
+ ```
32
+
33
+ The rendered `command` is produced by one renderer from the recipe. With no
34
+ configuration it is byte-identical to what spawn rendered before recipes
35
+ existed, so an older kernel starts such a home unchanged, and the golden
36
+ matrix freezes that. Configuration `args` go after the runtime's own
37
+ options and before capability launch arguments (for claude and codex the
38
+ `--` separator keeps them from consuming the task; for pi they follow the
39
+ task, like capability arguments). Every argument and literal value is
40
+ single-quoted: spaces, quotes and metacharacters are literal.
41
+
42
+ ## Environment references
43
+
44
+ `{fromEnv: SRC}` renders as `NAME="$SRC"` in the command: the persisted
45
+ command, the pending receipt and every answer carry the reference, never a
46
+ value. At start the execution host checks each source variable is set
47
+ (`E_LAUNCH_ENV_MISSING` before anything is created or stopped) and hands the
48
+ source variables to the pane only (tmux `-e`; a Herdr launch exports them in
49
+ the launched shell). Literal values are non-secret by contract but no answer
50
+ shows them: `list` and `preview` redact every environment value.
51
+
52
+ ## Selection rules
53
+
54
+ - A named configuration is a unit. `--launch-config NAME` with a `--runtime`
55
+ that disagrees with the configuration's runtime is refused
56
+ (`E_LAUNCH_CONFIG_MISMATCH`) before anything happens; the same runtime may
57
+ be repeated; `--model` and `--yolo` override the configuration's fields.
58
+ - Without `--launch-config`: a spawn takes the soul's `launch-config` default
59
+ or none; an existing home keeps its recorded configuration, except that
60
+ `--runtime` alone deliberately leaves it behind and renders the new
61
+ runtime's defaults (no old executable or args are carried).
62
+ - Model: explicit, else the configuration's, else on an existing home the
63
+ recorded model when the runtime is unchanged, else the runtime's native
64
+ default; a spawn without either resolves the soul's preference for the
65
+ runtime. A model never crosses runtimes.
66
+ - Executable: the configuration's (bare name on PATH; a path resolved against
67
+ the declaring scope when relative) or the runtime's default (claude through
68
+ `oats-claude-config`). It must be a regular executable file; it is never
69
+ run to probe it. Capability runtime-package requirements are checked with
70
+ the runtime's default binary, as at spawn.
71
+
72
+ ## Capability boundary
73
+
74
+ Spawn hooks contribute `launch` arguments keyed by runtime and `env` values.
75
+ Launch arguments are runtime-specific by construction; environment is
76
+ runtime-neutral by contract. The recipe records both with per-capability
77
+ provenance (settings and trust at spawn). A later start on the same runtime
78
+ reuses them. A runtime switch reuses the environment and needs the new
79
+ runtime's launch arguments from the same capabilities: a capability that
80
+ answered arguments for the old runtime and none for the new one refuses the
81
+ switch (`E_LAUNCH_PREPARATION`) with the remedy (change that capability's
82
+ setting, or the provider declares a `launch` hook). Spawn hooks are never
83
+ re-run by a start or restart.
84
+
85
+ ## `oats launch-config preview`
86
+
87
+ Read-only; nothing is locked or started. `--home ABS` describes an existing
88
+ home under a selection (`selection.source`: `frozen` when nothing was
89
+ selected, `config` when re-resolved, `frozen-command` for a home that
90
+ predates recipes, whose selection needs the restart conversion);
91
+ `--soul NAME [--dir SCOPE] [--agents-root ABS]` describes a new instance.
92
+ Answer: `{context, selected, selection:{source, launchConfig, runtime,
93
+ model, yolo}, runtime, model, modelSource, yolo, launchConfig,
94
+ launchConfigSource, executable:{path, declared, resolvedFrom}, argv,
95
+ environment:[{name, redacted|fromEnv}], command (redacted rendering),
96
+ prompt:{kind:"task-file", file:"TASK.md"}, hooks (redacted),
97
+ preflight:[{check: executable|environment|model|capabilities, ok, detail}],
98
+ ok}`. The TASK body is never included.
99
+
100
+ ## Starting and restarting an existing home
101
+
102
+ `oats session start --home ABS` runs the recorded recipe as it is (a
103
+ `--model` re-renders the model in place and the recipe follows). With
104
+ `--launch-config`, `--runtime` or `--yolo` the recipe is re-resolved by the
105
+ same planner preview uses, against the home's recorded context, and every
106
+ check runs before anything is observed: the recipe's shape, the executable
107
+ (regular file, executable), the references (set on this host), the
108
+ capabilities' contributions (below), the runtime packages. A recorded
109
+ reference is re-checked on every start path, model-only starts included,
110
+ and the pane receives the source's value under the kernel alias.
111
+
112
+ `oats session restart --home ABS [same flags] [--stop-grace SECONDS]` stops
113
+ the running harness and starts again in place under the one per-home lock:
114
+
115
+ 1. Every preflight above, first. A refusal leaves the harness running.
116
+ 2. The stop: SIGTERM to every process under the pane's launcher (a wrapper
117
+ that does not exec, the harness, their children), then a bounded wait
118
+ (default 20 s) for the signalled processes to be gone and the session to
119
+ read as a bare shell or stopped. Nothing is escalated: a harness still
120
+ there when the wait ends is reported (`E_SESSION_STOP_FAILED`, with the
121
+ processes still running) and nothing is launched. Elapsed time is never
122
+ taken as exit; a turn interruption is never taken as exit.
123
+ 3. The in-place start, with the pending receipt carrying the new recipe,
124
+ runtime and yolo, exactly as a start does; `.oats-restart.json` keeps the
125
+ stop's facts (what was signalled, when, whether exit was observed).
126
+
127
+ What the harnesses do on SIGTERM, from their installed sources and
128
+ documentation as read by the operating lead on 2026-09-07 (no live process
129
+ signalled): pi (@earendil-works/pi-coding-agent 0.84.2) registers
130
+ SIGTERM/SIGHUP handlers that end tracked children, dispose extensions and
131
+ exit; Claude Code's documentation makes Ctrl-C state-dependent (interrupt,
132
+ clear, double-press exit) and does not establish that an external SIGTERM
133
+ runs its SessionEnd hook; Codex's documentation establishes no SIGTERM
134
+ cleanup guarantee. So the contract is the request and the observation, not
135
+ a promise that a harness flushes its latest conversation: OATS preserves
136
+ the home, work, identity and notes; an old native conversation's unsaved
137
+ state is the harness's own. Wrappers should exec the harness or forward
138
+ signals. A longer `--stop-grace` can accommodate hook cleanup.
139
+
140
+ ## Homes that predate recipes
141
+
142
+ A home with a recorded `command` and no `launch` is converted narrowly when
143
+ a start selects something: only the kernel's own generated shapes are
144
+ recognized (identity environment, the binary, the runtime's template
145
+ arguments, `--model`, yolo, the task prompt). Other environment is kept and
146
+ attributed to the capability whose recorded declaration (`environment`,
147
+ `environmentNamespaces` in `capabilityRuntime`) owns it, so a session-delivery
148
+ home switches runtime with its `AWEB_DELIVERY` intact; spawn hooks are never
149
+ re-run. Any other argument is unclassified: the start is refused, naming the
150
+ arguments, unless an active trusted capability declares a `launch` hook that
151
+ prepares the launch anew (then its answer replaces them). The conversion is
152
+ recorded (`launch.legacy`) by the start that uses it.
153
+
154
+ ## The `launch` hook
155
+
156
+ A capability may declare `hooks.launch`. It runs on a start or restart of an
157
+ existing home (never at spawn, never spawn's identity work), side-effect-free
158
+ by contract, with `OATS_RUNTIME` set to the target runtime and
159
+ `OATS_PREVIOUS_RUNTIME` to the recorded one, and answers `{launch:{<runtime>:
160
+ args}, env:{...}}` for that runtime; its answer replaces the capability's
161
+ recorded contribution. Without it, a capability that contributed
162
+ runtime-specific arguments at spawn cannot follow a runtime change
163
+ (`E_LAUNCH_PREPARATION`), and a capability the scope no longer trusts has its
164
+ recorded arguments withheld the same way.
@@ -20,6 +20,14 @@ prints exactly one JSON object on stdout:
20
20
  `version` is the installed package's exact semver (e.g. `0.20.0`).
21
21
  Desktop 0.22 accepts `desktopApi === 1` and semver `>=0.22.0 <0.23.0`.
22
22
 
23
+ Optional features are negotiated from the probe's `features` array. Starting
24
+ an existing home requires `session-start`; named launch configurations and
25
+ runtime/permission overrides require `launch-config`; restarting a running
26
+ home also requires `session-restart`. Desktop checks the corresponding
27
+ `remote` entries before offering these operations for a server. The router
28
+ then probes the execution host before sending a mutation. An absent feature
29
+ means an update is needed; it is not inferred from the version number.
30
+
23
31
  The band is widened one kernel minor at a time, after confirming this v1
24
32
  surface is unchanged, and always admits the kernel published by the same
25
33
  release — Desktop and the CLI are built from one tag, so a band excluding its
@@ -36,7 +44,65 @@ no progress prose (progress goes to stderr):
36
44
 
37
45
  ## Mutations exposed to Desktop v1
38
46
 
39
- Only two:
47
+ The commands below use the same envelope. Additional capability operations
48
+ are described in [the operations contract](design/operations-contract.md).
49
+
50
+ ### Existing-home launch and restart
51
+
52
+ ```text
53
+ oats session start --home /absolute/home [--server id] \
54
+ [--launch-config name] [--runtime pi|claude|codex] \
55
+ [--model id] [--yolo|--no-yolo] --json
56
+ oats session restart --home /absolute/home [the same options] --json
57
+ ```
58
+
59
+ Desktop addresses the exact existing home from the selected workspace's
60
+ roster. Restart is one kernel command. The kernel owns configuration
61
+ validation, stop observation, the lifecycle lock, launch recovery and session
62
+ metadata. Desktop does not implement restart by retiring and spawning.
63
+ Failure or timeout requires a fresh status check before retrying: a lost
64
+ response does not establish that launch failed.
65
+
66
+ For a remote home, its saved route supplies the execution host even if its
67
+ registration has subsequently changed. The remote kernel validates the new
68
+ configuration before stopping the current harness. A missing feature fails
69
+ before any stop/start command is sent.
70
+
71
+ ### Launch configurations
72
+
73
+ ```text
74
+ oats launch-config list [--dir /scope | --home /home | --soul name --agents-root /scope/agents] --json
75
+ oats launch-config set name --file /private/definition.json [--keep-env] --dir /scope --json
76
+ oats launch-config remove name --dir /scope --json
77
+ oats launch-config preview (--home /home | --soul name --agents-root /scope/agents --dir /scope) \
78
+ [--launch-config name] [--runtime runtime] [--model id] [--yolo|--no-yolo] --json
79
+ ```
80
+
81
+ All accept `--server id`. Scope edits follow the registration; inspection and
82
+ preview of an existing home follow its saved route. A local definition file
83
+ is serialized to SSH stdin and read on the host with `--file -`; the local
84
+ filename is never passed to the server as though it existed there.
85
+
86
+ The list result supplies `context`, `selected` and `configurations`. Each
87
+ configuration has a name, runtime, executable, literal argument array,
88
+ environment, model, permission choice and declaring `source`. Environment
89
+ literals appear as `{ "redacted": true }`; references appear as
90
+ `{ "fromEnv": "VARIABLE_NAME" }`. Optional executable/model/yolo fields can be
91
+ null. An editor must not write redaction markers back. `--keep-env`, with
92
+ `env` omitted from the replacement definition, copies the effective named
93
+ configuration's environment once into the complete replacement.
94
+
95
+ Preview is read-only and returns a redacted invocation plus `preflight`
96
+ checks. A successful inspection envelope can contain `result.ok: false`:
97
+ the selected launch is not ready. Desktop displays the failed checks rather
98
+ than treating successful inspection as permission to launch. Environment
99
+ references resolve on the execution host at launch, including subsequent
100
+ starts of the saved recipe. Editing a named definition does not change a
101
+ running instance or silently update its frozen launch recipe. Select the
102
+ configuration explicitly on a later start/restart to apply the new definition.
103
+
104
+ See [launch configuration syntax](configuration.md) and
105
+ [the Desktop start/restart workflow](desktop-instance-start.md).
40
106
 
41
107
  ### `oats spawn <agent> … --json`
42
108
 
@@ -5,25 +5,61 @@ The Desktop roster is the place to return to it:
5
5
 
6
6
  - A running row opens its terminal.
7
7
  - A stopped row offers **Start…**. Clicking the row opens the same dialog.
8
+ - A running row's action menu offers **Restart with…** to change harness or launch configuration in the same home.
8
9
  - The hierarchy's action popover offers **Start…** for a stopped instance.
9
10
  - An unknown status is shown as unknown, not as permission to launch another process.
10
11
 
11
- The Start dialog names the existing instance, runtime and host. Enter a model
12
- or leave the field blank to retain its recorded choice. Available local model
12
+ The Start/Restart dialog names the existing instance, runtime and host. Choose
13
+ a named launch configuration or keep the recorded launch. Without a selected
14
+ configuration, the harness can also be changed directly. A named configuration
15
+ fixes its harness; model and permission choices can override its defaults.
16
+ Enter a model or leave the field blank to use the selected launch's default.
17
+ An old harness's model is not carried to a different harness. Available local model
13
18
  suggestions are advisory; a model ID can also be typed. Start uses the saved
14
19
  briefing and state in a new harness conversation; it does not resume an old
15
20
  harness conversation ID. After the launch appears in the roster, Desktop
16
21
  opens the instance's terminal.
17
22
 
18
- If the instance is already running when the dialog checks, its action becomes
23
+ If an ordinary Start dialog finds the instance already running, its action becomes
19
24
  **Open terminal**. A failed or timed-out start requires **Refresh status** before
20
25
  another attempt, because the launch may have succeeded before the reply was
21
26
  lost. Changing workspaces dismisses the dialog and prevents a delayed launch
22
27
  reply from opening a terminal in the wrong workspace.
23
28
 
29
+ **Restart with…** is explicit: after validating the new configuration, the
30
+ kernel stops the current harness and starts the selected one. It does not
31
+ retire the instance, rerun identity creation or replace its worktree. A stop
32
+ that cannot be confirmed does not authorize another launch. Save in-progress
33
+ work before restarting; the old harness conversation is not transferred to
34
+ another harness.
35
+
36
+ Restart requests termination and waits for the current process to stop before
37
+ launching its replacement. If stopping times out, it leaves the instance for
38
+ inspection instead of forcing a kill or launching a second harness. OATS
39
+ preserves the home and work; it cannot guarantee that a harness or custom
40
+ wrapper saves all of its in-flight conversation state. Wrappers should
41
+ `exec` the harness or forward termination signals correctly.
42
+
43
+ **Preview invocation** asks the execution host for the resolved command and
44
+ displays it as text, with environment values redacted. It never launches an
45
+ agent. **Manage launch configurations** in the dialog creates or updates named
46
+ configurations at the displayed scope, including executable/wrapper, a JSON
47
+ argument list, environment references, model and permissions. Saving a
48
+ configuration changes its definition; applying it to an existing home requires
49
+ an explicit Start or Restart. When editing redacted environment values, keep
50
+ **Preserve the saved environment** selected or enter a complete replacement.
51
+ Use the server's workspace to manage configurations defined on that server.
52
+
24
53
  Desktop sends `POST /api/start/<instance>?ws=…&home=…` (and `server=…` for a
25
54
  remote instance). The backend resolves that exact roster identity and calls
26
55
  `oats session start --home <absolute-home> [--server <id>] [--model <model>] --json`.
56
+ Launch choices add `--launch-config`, `--runtime` or `--yolo`/`--no-yolo`.
57
+ Restart uses `POST /api/restart/<instance>?ws=…&home=…` and the single kernel
58
+ command `oats session restart` with the same selectors and choices.
59
+ Configuration inspection and editing use `POST /api/launch-configs?ws=…`,
60
+ routed through `oats launch-config list/set/remove/preview`. These features
61
+ require the CLI's `launch-config` and `session-restart` capabilities; old
62
+ clients/hosts receive an update explanation instead of unsupported arguments.
27
63
  The installed CLI must advertise `session-start`; remote starting also needs
28
64
  the remote operation. The execution host checks the actual saved session
29
65
  before launch. Desktop does not scaffold a home or execute a launcher itself.
@@ -84,6 +84,34 @@
84
84
  "required": ["name"],
85
85
  "additionalProperties": false
86
86
  },
87
+ "launch-configs": {
88
+ "type": "object",
89
+ "description": "Named ways to start a harness, independent of any soul. The closest scope declaring a name provides the whole entry (no merging between scopes). Selected by name at spawn or session start/restart; explicit flags override its fields.",
90
+ "propertyNames": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
91
+ "additionalProperties": {
92
+ "type": "object",
93
+ "additionalProperties": false,
94
+ "required": ["runtime"],
95
+ "properties": {
96
+ "runtime": { "type": "string", "enum": ["pi", "claude", "codex"], "description": "The harness family this configuration starts; it decides the kernel's own baseline arguments (skills, instructions, task) and how model/yolo are expressed." },
97
+ "executable": { "type": "string", "minLength": 1, "description": "The program to run instead of the runtime's default binary: a bare name is looked up on PATH on the execution host; a path containing a slash is resolved against the declaring scope's directory when relative. Checked to exist and be executable before any start; never executed just to probe it." },
98
+ "args": { "type": "array", "items": { "type": "string" }, "description": "Extra arguments, each passed literally (no shell interpretation): native configuration files or profiles go here as ordinary arguments." },
99
+ "env": {
100
+ "type": "object",
101
+ "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
102
+ "additionalProperties": {
103
+ "oneOf": [
104
+ { "type": "string" },
105
+ { "type": "object", "additionalProperties": false, "required": ["fromEnv"], "properties": { "fromEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" } } }
106
+ ]
107
+ },
108
+ "description": "Environment for the harness. A string is a literal (non-secret by contract; still redacted in every answer). {fromEnv: NAME} is resolved from the execution host's environment at start time; the reference, never the value, is recorded. A missing reference refuses the start before anything stops."
109
+ },
110
+ "model": { "type": "string", "minLength": 1, "description": "Model for this configuration's runtime; overrides the soul default when this configuration is selected." },
111
+ "yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." }
112
+ }
113
+ }
114
+ },
87
115
  "agent-types": {
88
116
  "type": "object",
89
117
  "description": "Agent families declared by name; membership is `type: <name>` in each soul.yaml.",