@awebai/oats 0.24.6 → 0.24.8
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/bin/oats.mjs +300 -16
- package/docs/design/2026-09-20-redesign-program-board.md +3 -3
- package/docs/design/2026-09-22-desktop-parity-seams.md +10 -6
- package/docs/desktop-cli-api.md +406 -0
- package/docs/release-notes/v0.24.7.md +81 -0
- package/docs/release-notes/v0.24.8.md +107 -0
- package/lib/core.mjs +224 -17
- package/lib/instance-events.mjs +76 -0
- package/lib/instance-git.mjs +225 -0
- package/lib/instance-lifecycle.mjs +181 -0
- package/lib/portable-soul.mjs +5 -1
- package/lib/readiness.mjs +141 -0
- package/lib/schedule.mjs +22 -3
- package/package.json +1 -1
package/docs/desktop-cli-api.md
CHANGED
|
@@ -46,6 +46,412 @@ no progress prose (progress goes to stderr):
|
|
|
46
46
|
- success (exit 0): `{"schemaVersion":1,"ok":true,"result":{...}}`
|
|
47
47
|
- failure (nonzero exit): `{"schemaVersion":1,"ok":false,"error":{"code":"...","message":"..."}}`
|
|
48
48
|
|
|
49
|
+
## Souls and sources (`oats inspect --json`, `soulsApi: 1`, OATS 0.24.7+)
|
|
50
|
+
|
|
51
|
+
Every entry in `result.souls[]` carries what the soul's **own `soul.yaml`
|
|
52
|
+
declares**, parsed by the kernel — a consumer never parses YAML and never
|
|
53
|
+
infers a field that is not there:
|
|
54
|
+
|
|
55
|
+
- `soulsApi: 1`
|
|
56
|
+
- `declarations: { requires, defaults, knowledge, teams, resources, children }` — each
|
|
57
|
+
the declared object, or `null` when the section is absent (`children`
|
|
58
|
+
since 0.24.8: `{spawn: boolean}`, see readiness policy).
|
|
59
|
+
- `provenance: { kind, source, revision, path, workspaceRevision } | null` —
|
|
60
|
+
where this soul copy came from, as recorded by the kernel when it created
|
|
61
|
+
it (`oats onboard` records `packaged-definition` or
|
|
62
|
+
`exported-edition-copy`). Souls created before 0.24.7 or authored by hand
|
|
63
|
+
read `null`; render that as *unrecorded*, not as local or as anything else.
|
|
64
|
+
- `readiness` — the soul's **declared sources**, joined against
|
|
65
|
+
`result.capabilities[]` from the same payload. Distinct from launchability
|
|
66
|
+
(`oats spawn`) and adoption (`oats prepare`); never a green "Ready".
|
|
67
|
+
- `source: "recorded" | "unrecorded"`
|
|
68
|
+
- `requirements: [{ capability, source, installed, approved, active, version }] | null`
|
|
69
|
+
(`null` = nothing declared). `installed: false` = not in the inventory;
|
|
70
|
+
`approved`/`active`/`version` are `null` when there is no inventory row.
|
|
71
|
+
- `status: "undeclared" | "sources-installed" | "sources-missing" | "unknown"`
|
|
72
|
+
- `declarationProblems: [{ code, message }]` — an unreadable file reports why;
|
|
73
|
+
the soul is still listed.
|
|
74
|
+
|
|
75
|
+
`result.sources` is the scope's **portable source context**: the distinct
|
|
76
|
+
provenance sources its souls record.
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{"soulsApi":1,"kind":"recorded-provenance","note":null,
|
|
80
|
+
"items":[{"kind":"exported-edition-copy","source":"git:https://…/oats.git","revision":"<sha>","path":"souls/oats-setup-expert","workspaceRevision":"<sha>","souls":["oats-setup-expert"]}]}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`kind: "none-recorded"` (empty `items`, explanatory `note`) means no soul in the
|
|
84
|
+
scope records a portable **source address** — a soul may still carry a
|
|
85
|
+
`provenance` of kind `packaged-definition` with `source: null`. Say "no portable
|
|
86
|
+
source recorded"; do not infer "local" or "authored" from this state.
|
|
87
|
+
Workspace imports adopted onto a deployment will appear here at their pinned
|
|
88
|
+
revisions when that adoption is recorded on the deployment; nothing is
|
|
89
|
+
enumerated from a source repository that the deployment does not record.
|
|
90
|
+
|
|
91
|
+
## Instance Git state (`oats instance git|diff`, `instanceGitApi: 1`, OATS 0.24.7+)
|
|
92
|
+
|
|
93
|
+
Read-only observation of one instance's **work tree**. Truth comes from the
|
|
94
|
+
tree — the branch the tree is on, not the branch recorded at spawn (that is
|
|
95
|
+
reported under `recorded` with a `drift` flag). Fixed-argv `git`, no shell.
|
|
96
|
+
|
|
97
|
+
Address the instance qualified: `oats instance git <instance> --dir <scope>`
|
|
98
|
+
resolves the name under the scope's agents roots (team roots included) and
|
|
99
|
+
**refuses when several homes match** (`E_AMBIGUOUS_INSTANCE`, `details.candidates`);
|
|
100
|
+
pass `--home <abs>` to pick one. Unknown → `E_SESSION_UNKNOWN`; retired or
|
|
101
|
+
un-materialized tree → `E_NO_WORKTREE`.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{"instanceGitApi":1,"instance":"dev-1","agent":"dev","home":"/abs/home","workMode":"worktree",
|
|
105
|
+
"observation":{"revision":"<HEAD oid|unborn>","indexRevision":"<index tree oid>","at":"<iso>","worktree":"/abs/work","branch":"feat/y","detached":false,"unborn":false},
|
|
106
|
+
"recorded":{"branch":"feat/x","repo":"/abs/repo","drift":true},
|
|
107
|
+
"upstream":{"ref":"origin/feat/y","ahead":1,"behind":0},
|
|
108
|
+
"base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"<oid>","ahead":2,"behind":0},
|
|
109
|
+
"remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream|origin"},
|
|
110
|
+
"summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
|
|
111
|
+
"files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt"}],
|
|
112
|
+
"notes":[]}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `upstream` and `base` are **two separate comparisons**. No upstream →
|
|
116
|
+
`upstream: {ref:null, ahead:null, behind:null}` — unknown, **not 0/0**. `base`
|
|
117
|
+
is against the merge-base with the default branch (`origin/HEAD`, else a
|
|
118
|
+
well-known name; `source` says which); none found → all `null` plus a note.
|
|
119
|
+
- Status is porcelain v2, NUL-delimited: renames/copies carry `origPath`;
|
|
120
|
+
paths with spaces/newlines are intact. `kind` ∈ changed | renamed | copied |
|
|
121
|
+
unmerged | untracked. Ignored files are not listed.
|
|
122
|
+
- `files[].id` is **opaque**, minted under (`revision`, `indexRevision`). It is
|
|
123
|
+
the only way to ask for a diff.
|
|
124
|
+
- `remote` (0.24.8+): the branch's configured remote (`source: branch-upstream`),
|
|
125
|
+
else `origin`, else `null` — never invented. `host`/`path` are **parsed** from
|
|
126
|
+
the URL (ssh/https forms; `.git` stripped) so an ADE can choose a forge backend
|
|
127
|
+
and a `owner/repo` **without running Git**; a local path has `host: null`. No
|
|
128
|
+
network, no forge knowledge in the kernel.
|
|
129
|
+
|
|
130
|
+
`oats instance diff <instance> --file <id> --revision <rev> [--index-revision <idx>] --json`
|
|
131
|
+
returns a bounded unified diff:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{"instanceGitApi":1,"observation":{…},"file":{"id":"…","kind":"changed","xy":".M","path":"README.md","origPath":null},
|
|
135
|
+
"against":"<captured revision oid>","binary":false,"bytes":2683,"truncated":false,"limit":262144,"patch":"diff --git …",
|
|
136
|
+
"readOnly":{"helpers":"disabled","optionalLocks":"off","objectsWritten":0}}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `against` is the **captured revision oid** for tracked changes (working tree
|
|
140
|
+
vs that exact commit, index included — never the moving `HEAD`) and `empty`
|
|
141
|
+
for untracked files. Binary → `binary: true`, empty patch. Over 256 KiB →
|
|
142
|
+
`truncated: true` at the byte limit.
|
|
143
|
+
- **Read-only, helper-free, consistent across the read** (`readOnly` echoes
|
|
144
|
+
it): the observed tree may carry a hostile repo config, so external diff,
|
|
145
|
+
textconv, fsmonitor and hooks are disabled and the caller's Git environment
|
|
146
|
+
and global config are not inherited; `--no-optional-locks` means no index
|
|
147
|
+
refresh and no object is written (`ls-files --stage` hash, not `write-tree`).
|
|
148
|
+
After producing the patch the CLI re-checks HEAD, index and the file's own
|
|
149
|
+
content against the observation and refuses `E_STALE_OBSERVATION` if any
|
|
150
|
+
moved mid-read — the result is never internally inconsistent.
|
|
151
|
+
- If HEAD or the index moved since the id was minted, or the id is not in the
|
|
152
|
+
current observation, the CLI **refuses** with `E_STALE_OBSERVATION` and
|
|
153
|
+
attaches the current `observation` in `error.details` — re-observe, never
|
|
154
|
+
render a diff against a tree that is not the one on screen. A path in
|
|
155
|
+
`--file` is `E_BAD_ARGS`.
|
|
156
|
+
|
|
157
|
+
No forge (PR/checks/reviews) data here: forge connections are an ADE/workstation integration (P1 decision), read by the Desktop server through the forge's own CLI; the kernel only reports the instance's `remote` so the ADE can pick a backend.
|
|
158
|
+
|
|
159
|
+
## Instance events (`oats instance events`, `eventsApi: 1`, OATS 0.24.8+) — K7
|
|
160
|
+
|
|
161
|
+
Typed lifecycle events per instance, **written by the kernel action that made
|
|
162
|
+
them true**, with the receipt it produced. Nothing is inferred from
|
|
163
|
+
transcripts, TASK/STATE files or prose. Append-only, two logs: `<home>/.oats-events.jsonl`
|
|
164
|
+
and `<workspace>/.agents/events/<agent>--<instance>.jsonl` (survives the
|
|
165
|
+
home's removal, so a retired instance's `retired` event is still readable).
|
|
166
|
+
|
|
167
|
+
`oats instance events <instance> [--limit <n>] [--since <iso>] [--home <abs>] [--dir <d>] --json`
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{"eventsApi":1,"instance":"dev-1","home":"/abs/home","count":7,"returned":7,"truncated":false,
|
|
171
|
+
"events":[{"eventsApi":1,"at":"<iso>","instance":"dev-1","home":"/abs/home","producer":"kernel","kind":"spawned","data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","runtime":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}},
|
|
172
|
+
{"…":"launched | restarted | stopped | stop-refused | retire-planned | worktree-retained | worktree-removed | branch-deleted | retired | child-spawn-refused"}],
|
|
173
|
+
"lastEvent":{"kind":"stopped","at":"<iso>","producer":"kernel"},
|
|
174
|
+
"waitingOnYou":null,
|
|
175
|
+
"notes":["…"]}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- `kind` is a closed set (unknown kinds are refused at write). `producer` is
|
|
179
|
+
`kernel` for lifecycle facts; a capability may append its own events with
|
|
180
|
+
its id as producer (the write API is `appendEvent`, not the renderer).
|
|
181
|
+
- **`waitingOnYou` is `null` unless a producer reported it** (`data.waitingOnYou:
|
|
182
|
+
true` with a `reason`). `null` means *unknown*, not "not waiting". Today no
|
|
183
|
+
kernel path claims it; the Active overview keeps rendering unknown until a
|
|
184
|
+
producer (a messaging or review capability) does.
|
|
185
|
+
- Window is bounded (`--limit`, default 200; `truncated` says so). A torn line
|
|
186
|
+
appears as `kind: "unreadable"` rather than vanishing.
|
|
187
|
+
|
|
188
|
+
## Schedule run history (`scheduleApi: 2`, OATS 0.24.8+) — K8
|
|
189
|
+
|
|
190
|
+
`oats schedule show|list --json` entries gain **`recentRuns`**: the last 50
|
|
191
|
+
settled runs (newest first) — every `lastRun` the scheduler recorded once its
|
|
192
|
+
outcome settled (`ended | stopped | blocked | invalid | delivered | skipped |
|
|
193
|
+
unknown …`, never `active`/`starting`), exactly as the producer wrote it,
|
|
194
|
+
deduplicated per run. Where the run launched or targeted an instance, a
|
|
195
|
+
`transcript: {instance, home, kind: "session"}` pointer says which home's
|
|
196
|
+
session to open (the existing `oats session` surface); the kernel does not
|
|
197
|
+
copy transcripts. `nextRun`/`lastRun`/`executionStatus` are unchanged. The
|
|
198
|
+
Schedules view (frame 08) renders `recentRuns` as the recent-runs list and the
|
|
199
|
+
transcript pointer as the handoff; captured-policy definitions are preserved
|
|
200
|
+
as they are (definition fields are untouched by this addition).
|
|
201
|
+
|
|
202
|
+
## Spawn preview (`oats spawn … --preview`, `spawnPreviewApi: 1`, OATS 0.24.8+)
|
|
203
|
+
|
|
204
|
+
The Spawn modal's fields are backed by the kernel's own decision, taken **before
|
|
205
|
+
any side effect**: `oats spawn <agent> [same flags as a real spawn] --preview --json`
|
|
206
|
+
runs every preflight a spawn runs (placement, composition, resources,
|
|
207
|
+
executable, runtime packages, child-spawn policy) and returns what the spawn
|
|
208
|
+
*would* do — then returns without creating a home, branch or worktree.
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{"spawnPreviewApi":1,"preview":true,"agent":"dev","kind":"persistent","instance":"dev-fix-login","home":"/abs/agents/dev/instances/dev-fix-login",
|
|
212
|
+
"repo":"/abs/repo","work":"worktree","runtime":"claude","model":"opus","modelSource":"soul","launchConfig":null,"yolo":false,"backend":"tmux",
|
|
213
|
+
"branch":"agents/dev-fix-login","base":{"ref":"HEAD","oid":"<oid>"},"worktree":"/abs/agents/dev/instances/dev-fix-login/work",
|
|
214
|
+
"relation":null,"parentInstance":null,"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"…"}}},
|
|
215
|
+
"executable":"/abs/bin/claude","capabilities":["oats.core"],"skills":["oats-operate","oats-souls"],"task":"…"}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- **Name / work area**: `instance` is the canonical name (`<agent>-<purpose>`,
|
|
219
|
+
de-duplicated with `-2`, `-3`…); `home` and `worktree` are the canonical
|
|
220
|
+
paths. The renderer never derives paths.
|
|
221
|
+
- **Branch / base** (worktree mode): `branch` defaults to `agents/<instance>`
|
|
222
|
+
(`--branch <name>` overrides; validated); `base` is `--base <ref>` resolved
|
|
223
|
+
to its commit oid (default `HEAD`). `E_BRANCH_EXISTS` and `E_BASE_UNKNOWN`
|
|
224
|
+
are refused in preview and in apply, before anything exists. Apply creates
|
|
225
|
+
the worktree **from that exact oid**.
|
|
226
|
+
- **Model**: `model`/`modelSource` are the resolved selection. Omitting
|
|
227
|
+
`--model` **inherits** the launch configuration's or soul's preference;
|
|
228
|
+
`--model @native-default` is the explicit "use the runtime's own default"
|
|
229
|
+
(`modelSource: "native default (explicit)"`). These are different requests
|
|
230
|
+
and the UI must not relabel one as the other.
|
|
231
|
+
- **Policy**: `policy.childSpawns` is what this instance will record (soul
|
|
232
|
+
declaration / spawn option / default), enforced later by the spawn route for
|
|
233
|
+
its children (see readiness).
|
|
234
|
+
- **Apply** = the same command without `--preview`; the same inputs yield the
|
|
235
|
+
same decisions (instance, branch, base oid). If the world moved between
|
|
236
|
+
preview and apply (name taken, branch created, base gone) the apply refuses
|
|
237
|
+
with the same typed codes — the preview is a statement, not a reservation.
|
|
238
|
+
- Not in preview (later K6 follow-ups): attach-knowledge refs from the
|
|
239
|
+
knowledge provider (05 excluded, attach stays), auto-PR intent (ADE-owned,
|
|
240
|
+
P1).
|
|
241
|
+
|
|
242
|
+
## Readiness quartet, signatures, enforced policy (`oats readiness`, `readinessApi: 1`, OATS 0.24.8+)
|
|
243
|
+
|
|
244
|
+
`oats readiness [--soul <name>] [--home <abs>] [--verify-signatures] [--policy] [--dir <d>] --json`
|
|
245
|
+
is the first-run readiness view (frame 09) and the Capabilities readiness rows
|
|
246
|
+
(frame 04). Every fact is derived from the **same** data `oats inspect` reports
|
|
247
|
+
— never a second opinion — and rolled into four checks:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{"readinessApi":1,"subject":{"kind":"soul","name":"dev"},"at":"<iso>",
|
|
251
|
+
"checks":{
|
|
252
|
+
"installed": {"status":"pass","items":[{"subject":"oats.core","status":"pass","required":true,"reason":null,"producer":"oats list","evidence":{"version":"1.1.3","integrity":"sha256-…","origin":"installed"},"remedy":null}]},
|
|
253
|
+
"trusted": {"status":"fail","items":[{"subject":"oats.core","status":"fail","required":true,"reason":"executable surface not approved","producer":"artifact approval","evidence":{"integrity":"sha256-…"},"remedy":"oats trust oats.core",
|
|
254
|
+
"signature":{"status":"unknown","signer":null,"reason":"signature verification needs a network fetch; pass --verify-signatures"}}]},
|
|
255
|
+
"configured":{"status":"pass","items":[{"subject":"oats.core activation","status":"pass","required":true,"producer":"oats-config.yaml","evidence":{"target":"declared","level":"/abs"},"remedy":null}]},
|
|
256
|
+
"enrolled": {"status":"not-applicable","items":[{"subject":"workspace membership","status":"not-applicable","required":false,"producer":"oats.yaml","reason":"standalone deployment: no workspace declared in oats.yaml"}]}},
|
|
257
|
+
"summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0},
|
|
258
|
+
"notes":["…"]}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- Each check is `pass | fail | unknown | not-applicable`; items carry
|
|
262
|
+
`subject, status, required, reason, producer, evidence, remedy`. **"Ready" is
|
|
263
|
+
`summary.ready`**: every *required* item passes (or is not-applicable) and
|
|
264
|
+
there is at least one required item — never inferred from an empty set.
|
|
265
|
+
- **`installed`**: artifact present, locked, integrity matches. **`trusted`**:
|
|
266
|
+
executable approval of the exact artifact (`oats trust`). Separately,
|
|
267
|
+
`signature {status: verified | unsigned | unknown | invalid | not-applicable,
|
|
268
|
+
signer: {id, label} | null, reason}` — the source commit's **verified Git
|
|
269
|
+
signature**, named signer or nothing. It is `unknown` unless
|
|
270
|
+
`--verify-signatures` (a network fetch of that one commit; `git log %G?`);
|
|
271
|
+
a catalog URL, repository owner or byte hash is never a signer. Render
|
|
272
|
+
"Trusted · signed by <label>" only for `verified`.
|
|
273
|
+
- **`configured`**: activation for the subject, runtime-package requirements
|
|
274
|
+
(`missingRequires`), runtime-settings problems. **`enrolled`**: workspace
|
|
275
|
+
**member admission** (decision §3) — `not-applicable` for a standalone
|
|
276
|
+
deployment (no `workspace:` in `oats.yaml`), `unknown` until admission is
|
|
277
|
+
verified against the workspace observation, `pass`/`fail` when it is. Never
|
|
278
|
+
login, never team registration; "Skip" leaves it not-applicable, never pass.
|
|
279
|
+
- Subject: `--soul <name>` scopes required items to the soul's declared
|
|
280
|
+
requirements; without it, to the scope's active capabilities.
|
|
281
|
+
|
|
282
|
+
`--policy` adds the **enforced** policy view with origins:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
"policy":{"childSpawns":{"allowed":false,"enforced":true,"origin":{"kind":"soul","detail":"children.spawn: false in soul.yaml"}},
|
|
286
|
+
"worktrees":{"allowed":true,"mode":"worktree","enforced":true,"origin":{"kind":"work-mode","detail":"work: worktree"}}}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`childSpawns` is **enforced by the spawn route**: `soul.yaml` may declare
|
|
290
|
+
`children: {spawn: false}`; `oats spawn --allow-child-spawns | --no-child-spawns`
|
|
291
|
+
overrides per spawn; the result is recorded in `instance.json`
|
|
292
|
+
`policy.childSpawns {allowed, origin}`. A spawn with `--parent <p>` (or
|
|
293
|
+
`--relation child --relative-to <p>`) under a parent whose recorded policy is
|
|
294
|
+
off refuses **`E_CHILD_SPAWNS_DISABLED`** (`details.parent`, `details.policy`)
|
|
295
|
+
before anything is created. Absent policy (pre-0.24.8 instances) = allowed,
|
|
296
|
+
reported as `origin.kind: "default"`. With `--home <abs>` the policy is the
|
|
297
|
+
instance's recorded (enforced) one; with only `--soul` it is the declaration
|
|
298
|
+
(`enforced: false`). It is a lifecycle-authority claim, not an OS sandbox —
|
|
299
|
+
the UI says so.
|
|
300
|
+
|
|
301
|
+
## Lifecycle plans — Stop and Remove (`lifecycleApi: 1`, OATS 0.24.8+)
|
|
302
|
+
|
|
303
|
+
The Desktop's Stop and Remove confirmations render **plans**: a read-only
|
|
304
|
+
statement of what the action would touch, with the facts a human needs, and a
|
|
305
|
+
`planRevision` hashed from the facts that make the action safe. Apply carries
|
|
306
|
+
the revision back; if reality moved, apply **refuses with the fresh plan**
|
|
307
|
+
(`E_PLAN_STALE`, `details.plan`) instead of acting on a world the human did
|
|
308
|
+
not see. An `idempotencyKey` makes a retried apply return the first receipt.
|
|
309
|
+
|
|
310
|
+
### `oats instance stop <instance> --plan [--no-recursive] [--home <abs>] [--dir <d>] --json`
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{"lifecycleApi":1,"action":"stop","instance":"dev-1","home":"/abs/home","recursive":true,"at":"<iso>",
|
|
314
|
+
"targets":[{"instance":"dev-1-child","agent":"dev","home":"/abs/child","depth":1,"workMode":"worktree","launched":true,
|
|
315
|
+
"session":{"state":"unknown","present":true,"backend":"tmux","established":true},
|
|
316
|
+
"work":{"observed":true,"revision":"<oid>","branch":"feat/x","detached":false,"drift":false,"changed":2,"untracked":1,"upstream":{"ref":null,"ahead":null,"behind":null},"base":{"ref":"origin/main","ahead":1,"behind":0},"remote":{"host":"github.com","path":"acme/one"}},
|
|
317
|
+
"retiring":false,"stopPending":false,"midTask":true}],
|
|
318
|
+
"skipped":[],"planRevision":"<24 hex>","notes":[]}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- `targets` are the instance's **recorded descendants deepest-first, then the
|
|
322
|
+
instance** (recorded parentage — `parentInstance` — is the only relation the
|
|
323
|
+
kernel knows). `--no-recursive` lists them under `skipped` instead.
|
|
324
|
+
- `session.state` is the backend's word: `shell`/`stopped`/`not-launched` are
|
|
325
|
+
idle; `unknown` means a non-shell process is running whose identity tmux
|
|
326
|
+
cannot name (the ordinary state of a launched harness). If the state **could
|
|
327
|
+
not be established**, `established:false`, `present:null`,
|
|
328
|
+
`state:"unestablished"`, with a `reason` — render that as unknown, never as
|
|
329
|
+
idle.
|
|
330
|
+
- `work` is K1's observation (`observed:false` with a `reason` when there is no
|
|
331
|
+
work tree or it cannot be read — not "clean").
|
|
332
|
+
- `midTask` is **reported** activity: `true` (running session or dirty work),
|
|
333
|
+
`false` (established idle and observed clean), or `"unknown"`.
|
|
334
|
+
|
|
335
|
+
### `oats instance stop <instance> --apply --plan-revision <rev> --idempotency-key <key> [--no-recursive] [--grace-ms <n>] --json`
|
|
336
|
+
|
|
337
|
+
Quiesces each target (SIGTERM to the harness processes, bounded wait, **never
|
|
338
|
+
escalated**), children first, under a per-home stop marker; retains home, work
|
|
339
|
+
tree, transcript and launch configuration so `oats session restart` brings the
|
|
340
|
+
instance back. Refuses `E_PLAN_STALE` (fresh plan attached),
|
|
341
|
+
`E_INSTANCE_RETIRING`, `E_LIFECYCLE_BUSY`.
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{"lifecycleApi":1,"action":"stop","instance":"dev-1","home":"/abs/home","idempotencyKey":"k","planRevision":"<rev>","at":"<iso>",
|
|
345
|
+
"ok":false,"results":[{"instance":"dev-1-child","home":"/abs/child","ok":false,"code":"E_SESSION_STOP_FAILED","message":"…still running after 1500 ms; nothing was escalated","stillRunning":[4242]},
|
|
346
|
+
{"instance":"dev-1","home":"/abs/home","ok":true,"stopped":true,"alreadyIdle":false,"state":"shell"}],
|
|
347
|
+
"retained":["home","work","transcript","launch"],"replayed":false}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`ok:false` means at least one target is still running; the receipt says which
|
|
351
|
+
pid. Nothing was killed harder. A replay (`replayed:true`) is the recorded
|
|
352
|
+
receipt for that key, not a second action.
|
|
353
|
+
|
|
354
|
+
### `oats retire <instance> --plan [--home <abs>] [--dir <d>] --json`
|
|
355
|
+
|
|
356
|
+
What Remove would touch, with the design's defaults. Read-only.
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{"lifecycleApi":1,"action":"retire","instance":"dev-1","home":"/abs/home","at":"<iso>",
|
|
360
|
+
"facts":{"session":{…},"work":{…K1 summary…},"workMode":"worktree","repo":"/abs/repo","recordedBranch":"agents/dev-1",
|
|
361
|
+
"children":[{"instance":"dev-1-child","agent":"dev","home":"/abs/child","session":{…}}],"pullRequest":"unknown"},
|
|
362
|
+
"defaults":{"retainWorktree":true,"deleteBranch":false,"stopChildren":true,"retainChildren":true},
|
|
363
|
+
"planRevision":"<24 hex>","notes":["the worktree is on feat/x, not the recorded agents/dev-1; branch actions use the worktree's branch", "…"]}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`pullRequest` is **always `"unknown"` from the kernel**: forge facts belong to
|
|
367
|
+
the ADE's connection (P1). Branch actions use the **worktree's** branch
|
|
368
|
+
(`facts.work.branch`), never `recordedBranch`.
|
|
369
|
+
|
|
370
|
+
### `oats retire <instance> [--discard-worktree] [--delete-branch] --json` — retention is the default (K3b)
|
|
371
|
+
|
|
372
|
+
Plain `retire` now **retains** a worktree-mode instance's work: the worktree
|
|
373
|
+
cannot stay under the removed home, so it is **re-homed** with
|
|
374
|
+
`git worktree move` to `<workspace>/.agents/worktrees/<repo>/<branch>` (a
|
|
375
|
+
`-2`, `-3` suffix if taken; detached → `detached-<oid12>`), with staged,
|
|
376
|
+
unstaged and untracked state intact, and the repository knows the new
|
|
377
|
+
location. The receipt says so:
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{"retired":"dev-1","retention":{"worktree":"retained","movedTo":"/ws/.agents/worktrees/repo/feat-x","branch":"feat/x","detachedAt":null,"recordedBranch":"agents/dev-1"},
|
|
381
|
+
"worktreeRemoved":false,"branchDeleted":false, "workRecovery":{…}}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
- `--discard-worktree` restores removal (`retention.worktree: "removed"`).
|
|
385
|
+
- `--delete-branch` deletes the **worktree's verified branch**
|
|
386
|
+
(`retention.branchDeleted`), never the recorded spawn name, and implies
|
|
387
|
+
discarding the worktree (a checked-out branch cannot be deleted).
|
|
388
|
+
- A failed move keeps the home and refuses `E_WORK_PRESERVATION_FAILED` —
|
|
389
|
+
nothing is lost; retry or pass `--discard-worktree`.
|
|
390
|
+
- Non-worktree modes report `retention: null`. Quarantine/rollback paths keep
|
|
391
|
+
their removal semantics.
|
|
392
|
+
- The Remove dialog's "also delete worktree / branch" checkboxes map to these
|
|
393
|
+
two flags; the kernel never touches a PR.
|
|
394
|
+
- **Guarded apply** (what a GUI sends): `oats retire <i> --plan-revision <rev>
|
|
395
|
+
--idempotency-key <key> [--discard-worktree] [--delete-branch] --json`. The
|
|
396
|
+
revision is revalidated against a fresh plan first — facts moved →
|
|
397
|
+
`E_PLAN_STALE` with `details.plan` (re-render, re-confirm; nothing retired);
|
|
398
|
+
a repeated key **replays** the recorded receipt (`replayed: true`, JSON-v1
|
|
399
|
+
envelope) instead of retiring twice. A first retire prints its raw receipt
|
|
400
|
+
(pre-existing shape) with `planRevision`/`idempotencyKey`/`replayed:false`
|
|
401
|
+
added. Mint the key server-side per confirmation intent and keep it for that
|
|
402
|
+
intent's retries.
|
|
403
|
+
- **Children first, kernel-owned.** The plan's `facts.children` are stopped
|
|
404
|
+
by the kernel before retirement (bounded SIGTERM, never escalated) and
|
|
405
|
+
retained; the receipt lists `childrenStopped[]`. A child still running
|
|
406
|
+
after the grace **refuses the whole retirement** — `E_CHILDREN_RUNNING`
|
|
407
|
+
with `details.childrenStopped` (pids) and `details.plan`; nothing retired.
|
|
408
|
+
- **Branch deletion is bound to the confirmed branch.** The kernel re-verifies
|
|
409
|
+
the worktree's branch at the moment of deletion, after hooks (which may
|
|
410
|
+
mutate the tree); a mismatch deletes nothing and reports
|
|
411
|
+
`retention.branchDeletionSkipped {expected, actual, reason}`.
|
|
412
|
+
- **Ambiguous parentage is reported, never acted on.** Recorded parentage is
|
|
413
|
+
a bare name; if a child's parent name resolves to several homes under the
|
|
414
|
+
root, that child appears under `ambiguous[]` — `plan.ambiguous` on a stop
|
|
415
|
+
plan, `plan.facts.ambiguous` on a retire plan — with the reason, and is
|
|
416
|
+
excluded from `targets`/`children`.
|
|
417
|
+
- **Stop replay horizon**: stop receipts are stored **per idempotency key**
|
|
418
|
+
(`<home>/.oats-stop-receipt.<key>.json`); any earlier key replays its own
|
|
419
|
+
receipt for as long as the home exists. Retire receipts live beside the
|
|
420
|
+
instances directory and replay after the home is gone.
|
|
421
|
+
|
|
422
|
+
### Feature advertisement — gate every new command on the probe
|
|
423
|
+
|
|
424
|
+
`oats version --json` `features` now lists: `catalog`, `instance-git`,
|
|
425
|
+
`instance-git-remote`, `souls-declarations`, `lifecycle-plans`,
|
|
426
|
+
`retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
|
|
427
|
+
`schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
|
|
428
|
+
`lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
|
|
429
|
+
`scheduleHistoryApi`). **Gate on these, never on a version string and never by
|
|
430
|
+
optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
|
|
431
|
+
and *retires*. Absent feature → the view is unavailable.
|
|
432
|
+
|
|
433
|
+
## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
|
|
434
|
+
|
|
435
|
+
A live instance's composed `AGENTS.md` is generated at spawn and outranks any
|
|
436
|
+
mail or tracked file *in the running context*. When a soul changes (a role or
|
|
437
|
+
budget amendment) and a respawn is not possible or wanted, an operator
|
|
438
|
+
refreshes the home in place:
|
|
439
|
+
|
|
440
|
+
- `oats session recompose --home <abs> [--dry-run] --json` → `{home, instance,
|
|
441
|
+
agent, soulDir, contextDir, changed, dryRun, blocks[{source,file}], previous,
|
|
442
|
+
note}`. Same composer spawn used, the home's own `soul` link and recorded
|
|
443
|
+
context/work mode. `changed:false` is a no-op (no receipt). On change the
|
|
444
|
+
prior text is retained as `previous` (`<home>/.oats-agents-md.<stamp>.previous`),
|
|
445
|
+
`instance.json` gains `instructions[]`/`recomposedAt`, and a `recomposed`
|
|
446
|
+
event is appended.
|
|
447
|
+
- **Nothing is signalled or restarted** — the harness re-reads on its own
|
|
448
|
+
schedule; the receipt's `note` says so. Refuses a retiring home
|
|
449
|
+
(`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
|
|
450
|
+
(`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
|
|
451
|
+
- Gate on `features.includes("session-recompose")`. It is an **operator
|
|
452
|
+
action** (the human or the instance's parent), never something a Desktop
|
|
453
|
+
poll or an agent runs on itself.
|
|
454
|
+
|
|
49
455
|
## Mutations exposed to Desktop v1
|
|
50
456
|
|
|
51
457
|
The commands below use the same envelope. Additional capability operations
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# OATS v0.24.7 — instance Git observation, soul declarations, and no hollow agents
|
|
2
|
+
|
|
3
|
+
Kernel/Pi/Desktop **0.24.7**. Tag `v0.24.7` → commit `c923d81b` (this notes commit; the last code change is `912aaee0`, PR #57). The npm tarball's `gitHead` is `c923d81b`; the version-bump commit lands after the tag (PR #58). A from-the-tag verifier should expect `gitHead == c923d81b`. Two additive read-only contracts for the Desktop
|
|
4
|
+
(`instanceGitApi: 1`, `soulsApi: 1`), one spawn refusal that closes a
|
|
5
|
+
first-team-path defect, and four Desktop parity slices.
|
|
6
|
+
|
|
7
|
+
## Kernel
|
|
8
|
+
|
|
9
|
+
- **`oats instance git <instance> [--home] [--dir] [--json]`** and
|
|
10
|
+
**`oats instance diff <instance> --file <id> --revision <rev> [--index-revision <idx>] [--json]`** (K1) —
|
|
11
|
+
read-only Git observation of one instance's work tree. Truth from the tree:
|
|
12
|
+
the branch the tree is on (the spawn-recorded branch is reported as
|
|
13
|
+
`recorded` with a `drift` flag). Porcelain v2 NUL status with renames keeping
|
|
14
|
+
both paths; **upstream and default-branch merge-base as two separate
|
|
15
|
+
comparisons** — no upstream is `null`, never 0/0. Opaque file ids minted
|
|
16
|
+
under (HEAD, index); a diff is taken against the *captured* revision oid and
|
|
17
|
+
refuses `E_STALE_OBSERVATION` (fresh observation attached) if HEAD, index or
|
|
18
|
+
the file's content moved, before **or during** the read. Bounded (256 KiB,
|
|
19
|
+
binary flagged). Qualified addressing: several homes with one name refuse
|
|
20
|
+
`E_AMBIGUOUS_INSTANCE` with candidates; retired/unmaterialized tree is
|
|
21
|
+
`E_NO_WORKTREE`.
|
|
22
|
+
**Hardened after a consumer-side adversarial probe**: the observed tree is
|
|
23
|
+
worked in by an agent, so its repo config is input — external diff, textconv,
|
|
24
|
+
fsmonitor and hooks are disabled, the caller's Git environment and global/
|
|
25
|
+
system config are not inherited, optional locks are off (no index refresh),
|
|
26
|
+
and the index revision is a hash of `ls-files --stage` (no `write-tree`, no
|
|
27
|
+
object written). The response states it: `readOnly: {helpers, optionalLocks, objectsWritten}`.
|
|
28
|
+
- **`oats inspect --json` souls carry their declarations** (K4, `soulsApi: 1`):
|
|
29
|
+
`declarations` (requires/defaults/knowledge/teams/resources, parsed
|
|
30
|
+
kernel-side, `null` when absent), `provenance` (recorded by `oats onboard`
|
|
31
|
+
from now on; `null` = *unrecorded*, never inferred), `readiness` (declared
|
|
32
|
+
sources joined against the same payload's capability inventory — separate
|
|
33
|
+
from launchability and adoption; never a green "Ready"), and
|
|
34
|
+
`result.sources` — the scope's portable source context, with an honest
|
|
35
|
+
`none-recorded` state.
|
|
36
|
+
- **No hollow agents.** `oats create` declares `oats.core` by default; composition
|
|
37
|
+
honours the declaration by suppressing the kernel's legacy skills — but nothing
|
|
38
|
+
on that path activates the replacement, so a `spawn` produced an agent with
|
|
39
|
+
**no operational curriculum and no warning** (second-operator finding on 0.24.6).
|
|
40
|
+
Now `spawn` refuses `E_REQUIREMENT_INACTIVE` before creating anything, with
|
|
41
|
+
the remedy (`oats install oats.framework` if needed, then
|
|
42
|
+
`oats use oats.core --soul <name>`) and the opt-out (remove the declaration).
|
|
43
|
+
`create` states the activation step up front (`next-step` note,
|
|
44
|
+
`declaredCapabilities` in `--json`). `create` stays declaration, not acquisition.
|
|
45
|
+
|
|
46
|
+
## Desktop (parity slices 1b, 2a, 3, 4, 6a, 7a)
|
|
47
|
+
|
|
48
|
+
- **1b** shared contextual right panel (Instance · Git & GitHub · Soul), collapsed
|
|
49
|
+
rail, per-workspace preferences, temporary focus mode.
|
|
50
|
+
- **2a** Git & GitHub panel on K1: worktree/branch/drift, upstream and base named
|
|
51
|
+
separately, real file counts and rename paths, bounded read-only diff; stale
|
|
52
|
+
→ re-observe, never rendered. The legacy background `gitState` collector —
|
|
53
|
+
which ran Git against every instance tree on every roster poll without helper
|
|
54
|
+
controls and substituted healthy zeros on failure — is **removed**. Desktop
|
|
55
|
+
Git reads are the K1 route only. GitHub/PR card is *unavailable* pending the
|
|
56
|
+
P1 decision: forge connections are an ADE/workstation integration, not a capability (working names `oats.git`/`oats.forge` retired).
|
|
57
|
+
- **3** Souls + Sources on K4: declarations, recorded provenance (`null` renders
|
|
58
|
+
*Unrecorded*, never *Local*), "Not declared" vs "Not reported" distinguished,
|
|
59
|
+
sources-installed ≠ Ready.
|
|
60
|
+
- **4** Capabilities: official catalog via `oats catalog` (0.24.6+) plus the
|
|
61
|
+
deployment's classic inventory; aliases are mappings not exports, refs are refs,
|
|
62
|
+
override catalogs conspicuously labelled; "Add capability" copies the schema's
|
|
63
|
+
exact `oats install <package>` argv and never executes it.
|
|
64
|
+
- **6a** Spawn modal to the redesign on existing seams: two-column layout,
|
|
65
|
+
grouped soul chooser, provider/model with reported-vs-assumed provenance,
|
|
66
|
+
launch configuration restored (an already-supported property the renderer had
|
|
67
|
+
regressed out of), fields that need K6 rendered disabled with their seam named.
|
|
68
|
+
- **7a** Active overview (frame 07) on the roster: anonymous count-labelled
|
|
69
|
+
relation groups (the roster reports no group names), activity `unknown` until K7.
|
|
70
|
+
|
|
71
|
+
Desktop CLI floor for the new views is **0.24.7** (`instanceGitApi`/`soulsApi`
|
|
72
|
+
gates); an older CLI renders those views as *unavailable*, never as empty-healthy.
|
|
73
|
+
|
|
74
|
+
## Also
|
|
75
|
+
|
|
76
|
+
- Board and seams doc dates corrected (2026-09-21, not 09-22 — lead clock error
|
|
77
|
+
caught by the second operator). Pinned SHAs unaffected.
|
|
78
|
+
- Second-operator `launch` re-run on 0.24.6 passed both halves (publish with an
|
|
79
|
+
executable launch selection; hard runtime row refuses attributed).
|
|
80
|
+
- OKF 2.1.3 bug list (deferred with harvest): `check` per-cause reasons; retire
|
|
81
|
+
leaves the per-source `okf-<id>` schedule definition enabled.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# OATS v0.24.8 — lifecycle plans, readiness, spawn preview, instance events, and guarded Stop/Remove in the Desktop
|
|
2
|
+
|
|
3
|
+
Kernel/Pi/Desktop **0.24.8**. Tag `v0.24.8` → the commit carrying these notes
|
|
4
|
+
(the last code change is `508fda04`, PR #69). The npm tarball's `gitHead` is
|
|
5
|
+
the tagged commit; the version-bump commit lands after the tag. All new
|
|
6
|
+
contracts are **advertised**: consumers gate on `oats version --json`
|
|
7
|
+
`features[]` names and the API integers below — never on the version number,
|
|
8
|
+
never by optimistic invocation.
|
|
9
|
+
|
|
10
|
+
## Kernel — the remaining Desktop-parity seams (K3, K5, K6, K7, K8)
|
|
11
|
+
|
|
12
|
+
- **Lifecycle plans** (K3, `lifecycleApi: 1`, feature `lifecycle-plans`):
|
|
13
|
+
`oats instance stop <i> --plan|--apply` and `oats retire <i> --plan`, then the
|
|
14
|
+
**guarded apply** `oats retire <i> --plan-revision <rev> --idempotency-key <key>
|
|
15
|
+
[--discard-worktree] [--delete-branch]`. A plan states producer facts (ordered
|
|
16
|
+
targets deepest-first, session/work facts, recorded children, `midTask:
|
|
17
|
+
"unknown"` never inferred) under a 24-hex `planRevision`; apply re-reads and
|
|
18
|
+
refuses `E_PLAN_STALE` (fresh plan attached) when facts moved. Keys replay
|
|
19
|
+
their own receipt instead of acting twice — stop receipts are stored **per
|
|
20
|
+
key**, retire receipts survive the home's removal.
|
|
21
|
+
- **Retention by default** (feature `retire-retention`): a plain retire
|
|
22
|
+
re-homes the worktree to `<workspace>/.agents/worktrees/<repo>/<branch>`
|
|
23
|
+
instead of deleting it; `--discard-worktree` removes; `--delete-branch`
|
|
24
|
+
deletes the branch the tree is **verified** to be on and implies discard.
|
|
25
|
+
Receipt `retention {worktree: retained|removed|absent, movedTo, branch,
|
|
26
|
+
recordedBranch, branchDeleted?, branchDeletionSkipped?}`; a failed move is
|
|
27
|
+
`E_WORK_PRESERVATION_FAILED` with the home kept.
|
|
28
|
+
- **Children first, kernel-owned.** A guarded retire stops the plan's recorded
|
|
29
|
+
children (bounded SIGTERM, never escalated) and retains them; a child still
|
|
30
|
+
running refuses the whole retirement — `E_CHILDREN_RUNNING`, nothing retired.
|
|
31
|
+
- **Branch deletion is bound to the confirmed branch**: re-verified at deletion,
|
|
32
|
+
after hooks (which may mutate the tree); a mismatch deletes nothing and
|
|
33
|
+
reports `branchDeletionSkipped {expected, actual, reason}`.
|
|
34
|
+
- **Ambiguous parentage is reported, never acted on**: parent edges are bare
|
|
35
|
+
names; one that resolves to several homes appears under `ambiguous[]`
|
|
36
|
+
(`plan.ambiguous` on stop, `plan.facts.ambiguous` on retire).
|
|
37
|
+
- A pre-plan CLI does not understand `--plan` and would retire on it; that is
|
|
38
|
+
exactly why the feature is advertised — consumers must gate.
|
|
39
|
+
- **Readiness** (K5, `readinessApi: 1`): `oats readiness [--soul] [--home]
|
|
40
|
+
[--verify-signatures] [--policy] --json` — the quartet
|
|
41
|
+
installed · trusted · configured · enrolled, each `pass|fail|unknown|not-applicable`
|
|
42
|
+
with items `{subject, required, reason, producer, evidence, remedy}`;
|
|
43
|
+
`summary.ready` is never vacuous. Signatures are `unknown` unless
|
|
44
|
+
`--verify-signatures` (an explicit one-commit fetch; `verified|unsigned|invalid`
|
|
45
|
+
with the signer named). Enrolled = workspace-member admission.
|
|
46
|
+
- **Enforced child policy** (K5): soul `children: {spawn: bool}`, spawn
|
|
47
|
+
`--allow-child-spawns|--no-child-spawns`, recorded as `instance.json`
|
|
48
|
+
`policy.childSpawns {allowed, origin}`; a spawn with `--parent` under an off
|
|
49
|
+
policy refuses `E_CHILD_SPAWNS_DISABLED`. `inspect --json` souls carry
|
|
50
|
+
`declarations.children`; capability rows carry `commit`.
|
|
51
|
+
- **Spawn preview** (K6, `spawnPreviewApi: 1`): `oats spawn … --preview --json`
|
|
52
|
+
states the decision — instance name, home, worktree, branch, base `{ref, oid}`,
|
|
53
|
+
runtime/model/launch config, work mode, composition sources, policy — without
|
|
54
|
+
creating anything. `--base <ref>` picks the base; `@native-default` names the
|
|
55
|
+
runtime's own default explicitly.
|
|
56
|
+
- **Instance events** (K7, `eventsApi: 1`, feature `instance-events`):
|
|
57
|
+
`oats instance events <i> [--limit] [--since] --json` — typed, producer-
|
|
58
|
+
attributed lifecycle events (spawned, launched, restarted, stopped,
|
|
59
|
+
stop-refused, retire-planned, retired, worktree-retained, branch-deleted,
|
|
60
|
+
child-spawn-refused, recomposed) from the home's log and the workspace's
|
|
61
|
+
retained copy; truncation and unreadable lines are visible, never smoothed.
|
|
62
|
+
- **Schedule run history** (K8, `scheduleApi: 2`, feature `schedule-history`):
|
|
63
|
+
schedules report `recentRuns` — settled runs only, producer order, ≤ 50.
|
|
64
|
+
- **Instruction refresh** (feature `session-recompose`): `oats session recompose
|
|
65
|
+
--home <abs> [--dry-run] --json` recomposes a **live** home's `AGENTS.md`
|
|
66
|
+
from its current soul with the same composer spawn used; previous text is
|
|
67
|
+
retained beside it, `instance.json` and a `recomposed` event record it,
|
|
68
|
+
nothing is restarted. An operator action for when a role changes and a
|
|
69
|
+
respawn is not possible or wanted.
|
|
70
|
+
- Remote workspaces: `oats instance git|diff` reach a remote instance through
|
|
71
|
+
the negotiated route (K1 remote), refusing rather than guessing where no
|
|
72
|
+
route exists.
|
|
73
|
+
|
|
74
|
+
## Desktop (parity slices 2b and 2c)
|
|
75
|
+
|
|
76
|
+
- **2b — Connections.** Settings → Connections with an inline "Connect
|
|
77
|
+
GitHub" (device flow through `gh`, credential custody stays with `gh`; the
|
|
78
|
+
auth pane is a closed key set with redaction). The Git & GitHub panel gains an
|
|
79
|
+
observation-bound PR card: informational, correlated to the exact K1
|
|
80
|
+
revision/branch/remote, never authority. Forge connections are an **ADE
|
|
81
|
+
integration**, not a capability.
|
|
82
|
+
- **2c — Stop and Remove.** The instance menu offers *Stop…* and *Remove
|
|
83
|
+
instance…*; both open a plan-backed confirmation showing the kernel's exact
|
|
84
|
+
targets, skipped/ambiguous children, session and work facts, the real branch
|
|
85
|
+
(recorded-branch drift noted separately) and retained-by-default choices.
|
|
86
|
+
One guarded local route; the plan is read from the CLI, each confirmation
|
|
87
|
+
gets an opaque reference, the idempotency key is minted on the first
|
|
88
|
+
confirmation and kept for that intent's retries, one apply at a time; lost
|
|
89
|
+
responses are shown as **unknown outcome**, never "no effect". Stale plans
|
|
90
|
+
require a new confirmation; a refused child stop names the child. The old
|
|
91
|
+
unguarded retire route now answers `E_PLAN_REQUIRED`.
|
|
92
|
+
- Everything above is **unavailable** (not hidden, not guessed) when the
|
|
93
|
+
installed CLI does not advertise the corresponding feature.
|
|
94
|
+
|
|
95
|
+
## Also
|
|
96
|
+
|
|
97
|
+
- Lessons recorded: consumers gate on advertised features, never on
|
|
98
|
+
optimistic invocation; a declaration that suppresses a default must be
|
|
99
|
+
enforced; read-only Git observation is a security boundary.
|
|
100
|
+
- Verification budget: one full gate per change is PR CI; locally only the
|
|
101
|
+
affected suites. Desktop-only changes run the Desktop suites plus the
|
|
102
|
+
focused suite.
|
|
103
|
+
|
|
104
|
+
## Upgrade
|
|
105
|
+
|
|
106
|
+
`npm i -g @awebai/oats@0.24.8`, then `oats doctor`. Desktops pinned to an
|
|
107
|
+
older CLI keep working; new controls appear as the CLI's advertised features do.
|