@awebai/oats 0.31.0 → 0.33.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.
@@ -60,7 +60,8 @@
60
60
  "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."
61
61
  },
62
62
  "model": { "type": "string", "minLength": 1, "description": "Model for this configuration's harness; overrides the soul default when this configuration is selected." },
63
- "yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." }
63
+ "yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." },
64
+ "default": { "type": "boolean", "description": "This host's baseline for its harness (0.32, feature launch-config-default): a new launch that picks this harness without naming a configuration (a soul's or souls.launch preference, --harness, the host default) runs this configuration's executable, args, env and yolo; its model is the last fallback. At most one per harness. `--launch-config none` bypasses it. OATS 0.31 and older refuse the key." }
64
65
  }
65
66
  }
66
67
  },
@@ -11,8 +11,8 @@ 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.3` | `oats.aweb` (messaging) | |
15
- | `oats.engineering` | `v1.3.0` | `oats.engineering-expert`, `oats.developer`, `oats.code-review` | `code-reviewer` |
14
+ | `oats.aweb` | `v1.17.5` | `oats.aweb` (messaging) | |
15
+ | `oats.engineering` | `v1.4.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) | |
18
18
  | `oats.linear` | `v1.0.1` | `oats.linear` (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.3
79
+ oats.aweb: v1.17.5
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.3 # a catalog version
137
+ oats package add oats.aweb v1.17.5 # 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
  ```
@@ -69,8 +69,11 @@ Found by the rehearsals, and fixed before the tag:
69
69
  ## After the tag
70
70
  - **Flag day:** the oats.engineering release with the souls' `launch:` (experts on Claude Code +
71
71
  Opus 5.5; `code-reviewer` on Codex + Astra; compat `>=0.30.0`), and committing the shared team id.
72
- - **0.30.1:**
73
- - the kernel items that miss the tag: retire of homes whose session is gone, automations.trust;
72
+ - **0.30.1:** one release, cut when the Desktop load work has merged (the kernel read-path PR with
73
+ `observe-max-age` and `observation.localRevision`, Desktop #321 and #322), or on 2026-09-30 18:00 UTC,
74
+ whichever comes first; what has not merged by then goes to 0.30.2. Retire of homes whose session is
75
+ gone (#299) and automations.trust (#300) shipped in 0.30.0. The kernel items below have no owner yet:
76
+ each rides 0.30.1 only if it merges before the cut.
74
77
  - deployment-scope capability commands without `--soul`;
75
78
  - the case-8 pre-check (`E_TEAM_UNCONFIGURED`).
76
79
  - the kernel refuses an unrecognised spawn argument (a bare `join=oats` without
@@ -79,5 +82,5 @@ Found by the rehearsals, and fixed before the tag:
79
82
  FIRST on the instance's PATH and records it in the launch recipe, so plain `oats` inside an
80
83
  instance is the kernel that made it (two kernels side by side: a 0.30 home ran the global 0.24.6
81
84
  and `oats aweb` was `E_UNKNOWN_COMMAND`; the injected instructions tell agents to run plain `oats`).
82
- - **Parked:** item L (`git rm agents/`, cli-dev-v2-native). It needs the GitHub `workflow` token
83
- scope from the human on the lead's machine.
85
+ - **Done:** item L merged in #314 (d4f68da9): the remaining legacy `agents/` soul trees and the
86
+ `validate:okf` gate are removed.
@@ -0,0 +1,137 @@
1
+ # OATS 0.32.0
2
+
3
+ ## Added
4
+
5
+ - **A default launch configuration per harness.** `default: true` on a
6
+ launch configuration in `oats-local.yaml` makes it this machine's baseline
7
+ for its harness. Every new launch of that harness that names no
8
+ configuration runs its executable, args, env and `yolo`: a soul's own
9
+ `launch:`, an inline `souls.launch` preference, `--harness`, the host
10
+ default. For example, every claude instance on a machine runs with its
11
+ `CLAUDE_CONFIG_DIR`:
12
+
13
+ ```yaml
14
+ launch-configs:
15
+ mine:
16
+ harness: claude
17
+ default: true
18
+ env: { CLAUDE_CONFIG_DIR: /home/me/.claude-personal }
19
+ ```
20
+
21
+ The model still comes from whatever chose the harness; the default's
22
+ `model` is only the last fallback. A named configuration runs as declared,
23
+ and `--launch-config none` asks for the bare harness. One default per
24
+ harness (a second is refused, naming both). `oats launch-config list`,
25
+ `spawn --preview`, `launch-config preview`, `instance.json` and
26
+ `oats inspect --home` say when a launch came from the default
27
+ (`launchConfigDefault`), yolo included. Existing instances keep their
28
+ launch until `--reselect-launch` or a respawn, and meanwhile show the
29
+ `launch-changed` readiness warning. Feature
30
+ `launch-config-default`
31
+ ([configuration.md](../configuration.md#the-harness-default)).
32
+ **Declaring a default needs OATS 0.32+ for every kernel that reads this
33
+ deployment:** OATS 0.31 and older refuse the whole `oats-local.yaml`
34
+ (`E_WORKSPACE_SCHEMA`). A routed `launch-config set --server` with
35
+ `default: true` to an older host is refused (`E_REMOTE_INCOMPATIBLE`)
36
+ before anything is sent.
37
+
38
+ - **Every instance a server reports is first-class in the Desktop, whoever
39
+ spawned it.** A remote row opens its terminal, starts, restarts, stops and
40
+ retires, and shows its readiness, activity, Git and diffs, just like a local
41
+ one; only its pull request is not read here, since the forge reads this
42
+ computer's clones. Stop and Remove keep their confirmations: the plan and
43
+ its guarded apply run on the instance's machine. Every command goes to the
44
+ server by the instance's home (`--server <id> --home <path>`), never by a
45
+ bare name, so two instances with the same name stay apart. While a read is
46
+ in flight, the view says "Reading from <server>…". When the server refuses,
47
+ it shows the server's code and message under a headline that usually names
48
+ the server; it never falls back to a local read. A remote stop or remove
49
+ that loses its link reads as an unknown outcome, never a failure, and the
50
+ server's roster is read again at once
51
+ ([desktop.md](../desktop.md#instances-on-servers)).
52
+ - **A row that can't be opened says why, on the row.** The roster shows a short
53
+ reason where it said "state unknown": Herdr no longer supported, gone from
54
+ <server>, not reachable on <server>, or <server> not reached. The full
55
+ sentence stays in the row's tooltip and its actions menu. For an instance a
56
+ server no longer lists, it names the command that removes it from this
57
+ computer (`oats server forget <server> --instance <name>`).
58
+ - **This computer's OATS 0.31 decides which server rows can be used.** The
59
+ Desktop accepts OATS CLIs up to 0.32.x. With a local OATS before 0.31, which
60
+ does not report that fact, an instance spawned from this computer stays
61
+ usable through its saved route, as before; its routed reads and plans ask you
62
+ to update OATS here.
63
+ - **A team card lists its members wherever they run.** On a local workspace's
64
+ Teams page, each team with a provider id lists every instance in it: this
65
+ workspace's, and every registered server's whose messaging identity is in
66
+ that team. They are grouped by machine ("This computer" first), with each
67
+ one's state in words. A member's name shows its roster row, where every
68
+ action lives; **Terminal** opens a running member there. A server whose
69
+ last roster read failed keeps its last-known members under "<server> · not
70
+ reached"; servers with nothing to show are named under the page head. The
71
+ card's count reads "N members · M on other machines". The Desktop reads
72
+ this from what it already holds and runs nothing new
73
+ ([desktop-teams.md](../../packages/desktop/docs/desktop-teams.md#team-members)).
74
+ - **The spawn dialog asks "Where to run" up front** (it was "Run on", under
75
+ Developer settings). "This computer" comes first, then each registered
76
+ server. A server is disabled, with the reason, when it wasn't reached or
77
+ the roster knows only an older registration of it. With a server chosen,
78
+ the dialog says where the instance will run, and the relationship picker
79
+ lists only that server's instances: a relation never crosses machines. When
80
+ the server refuses the spawn (a soul it doesn't offer, for example), the
81
+ dialog says so in the server's own words.
82
+ - **The Desktop keeps and sets a harness's default launch configuration.**
83
+ Editing a configuration marked `default: true` saves it with its default
84
+ intact, and with an OATS that has `launch-config-default` the editor offers
85
+ "Default for <harness> on this machine" for this computer's own
86
+ configurations. A remote host's default survives editing too, but is set on
87
+ that host. A second default for the same harness is refused in OATS's own
88
+ words; unset the old one first.
89
+
90
+ - **Faster read verbs, and `--max-age <s>` to reuse recent remote heads**
91
+ (feature `observe-max-age`). `status`, `workspace status`, `souls`,
92
+ `capabilities` and `inspect` start far fewer git processes, cache what
93
+ they parse by commit, and start the members' remote reads together with the
94
+ host's. With `--max-age` they reuse a head observed in the last `<s>`
95
+ seconds and report `observation {observedAt, reused, localRevision}`
96
+ (`localRevision` changes whenever the local configuration read for the
97
+ answer does). See
98
+ [Observation reuse](../desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311).
99
+
100
+ ## Fixed
101
+
102
+ - **A remote spawn with an empty opening instruction starts it waiting**, as
103
+ the dialog says. It was refused ("--task needs a value").
104
+ - **Focus stays put on the Workspace's Teams, Capabilities and Setup pages.**
105
+ Every roster poll rebuilt the page, since the workspace status it compared
106
+ carries a fresh observation time each time, so keyboard focus (and the
107
+ caret in the add-team form) fell back to the page every few seconds.
108
+ - **A click on a Desktop instance's actions menu does what it says.** With the
109
+ pointer over the open menu, the row no longer counted as hovered, so its
110
+ tools, the menu included, went hidden and a click on an item did nothing;
111
+ the keyboard still worked.
112
+ - **The Stop and Remove confirmations show only their own choices.** Stop
113
+ showed Remove's "delete the worktree" and "delete the local branch" options
114
+ (enabled, and ignored), and Remove showed Stop's "include recorded children".
115
+
116
+ - **A remote read that times out no longer leaves its ssh or https helper
117
+ running.** The kernel killed git but not the ssh or `git-remote-https` it
118
+ had started, which stayed connected to an unreachable host until its own
119
+ connection gave up. Git now runs as its own process group, and a timeout
120
+ (or the end of the command) kills the whole group.
121
+
122
+ - **A capability command whose provider dies of a signal exits as the shell
123
+ reports it** (128 + the signal number, 130 for a Ctrl-C), not 1.
124
+
125
+ - **`oats --help` piped into another program is no longer cut off at 8 KB.**
126
+ The top-level usage (about 20 KB) now arrives whole, including the
127
+ "Observation reuse" section; exit codes are unchanged.
128
+
129
+ ## Removed
130
+
131
+ - **`oats-claude-config` is no longer read.** The one-line file that named
132
+ the claude binary for every deployment below it is replaced by the claude
133
+ default launch configuration. A new claude launch with one in reach is
134
+ refused with `E_CLAUDE_CONFIG_REMOVED`, naming the file and the
135
+ configuration to declare instead (`harness: claude`, `executable: <the
136
+ name it held>`, `default: true`); then delete the file. Instances already
137
+ launched with it keep their recorded executable.
@@ -0,0 +1,174 @@
1
+ # OATS 0.33.0
2
+
3
+ ## Added
4
+
5
+ - **`oats spawn <soul> --preview --max-age <seconds>`** (feature
6
+ `spawn-preview-max-age`). A preview reuses member heads this machine
7
+ observed at most that many seconds ago, as the read verbs do since 0.32.0,
8
+ so a warm preview asks no remote. With the flag the preview's JSON carries
9
+ the read verbs' `observation {observedAt, reused, localRevision}` block;
10
+ without it the preview is unchanged. The decision still covers the heads
11
+ the preview used: an apply with `--expect-decision` observes live and
12
+ refuses `E_DECISION_STALE` when a reused head has moved, and a re-preview
13
+ then shows the new head. The apply refuses `--max-age` (`E_BAD_ARGS`), and
14
+ the refusal message of every form that refuses the flag now lists
15
+ `spawn --preview` among the read forms.
16
+
17
+ ## Changed
18
+
19
+ - **OATS fetches only what it reads from a remote** (awebai/oats#384). A
20
+ commit comes with its trees and its files up to 64 KiB; a larger file is
21
+ fetched when a read or a module needs it. The first spawn from a large
22
+ workspace host downloads megabytes instead of its whole tree (tsm: 13 MB in
23
+ 3 s, instead of 2.4 GB in 6.5 minutes). A server that cannot serve partial
24
+ fetches (a default `git daemon`, an old self-hosted git), or a local git
25
+ older than 2.45, gets whole trees as before, with one `oats: warning` per
26
+ repository.
27
+
28
+ - **Installing a soul's modules reads their files without a process per
29
+ file.** `oats spawn` copies each module's files from the remote cache
30
+ through the command's one `git cat-file --batch` reader per repository,
31
+ as the reads and discovery already do, instead of one `git cat-file blob`
32
+ per file. A Desktop developer spawn on this deployment ran 50 to 59
33
+ processes instead of 81 (34 per-file reads became 3 to 7 readers).
34
+ Budgets, digests, partial caches and errors are unchanged.
35
+
36
+ - **oats.engineering 1.4.0** (catalog and workspace pin, and the bundled
37
+ mirrors): the developer's loop REQUIRES loading `/understand-the-spec` and
38
+ `/execution-strategy` before implementation.
39
+
40
+ - **oats.aweb 1.17.5** (catalog and workspace pin, and the bundled mirror;
41
+ 1.17.4 and 1.17.5): a faster spawn and retire. The spawn hook mints the identity with one
42
+ `aw init --join-from` instead of `aw team invite` + `aw team join` +
43
+ `aw init`, so the invite token no longer appears in any argv. The aw floor
44
+ check stops at `aw version`'s version line instead of waiting on its GitHub
45
+ update check. Retire deletes by workspace id, with `aw wake deregister`
46
+ running at the same time. Measured spawn hook: 7.72 s to 4.62 s mean
47
+ (awebai/oats-aweb#31). Hook output, compensation and the aw floor (1.36.13)
48
+ are unchanged. The mint now has one 120 s budget where three calls had
49
+ about 210 s; raise `OATS_AWEB_JOIN_TIMEOUT_MS` on a slow network. Since the
50
+ hook no longer holds the invite token, 1.17.5 always records the alias it
51
+ requested and warns, without quoting the reply, when aw reports another
52
+ (awebai/oats-aweb#32).
53
+
54
+ - **Pressing Spawn (Cmd-Enter) closes the spawn dialog at once.** The spawn
55
+ completes in the background: a pending row ("Spawning…") appears in the
56
+ sidebar roster where the instance will stand, and is replaced in place by
57
+ the real row once the roster reports it. Outcomes arrive as notifications:
58
+ "<name> spawned" with Open, what didn't finish for a partial or incomplete
59
+ spawn (with View schedules for a wake that wasn't saved), and, for a
60
+ refused or failed spawn, the reason with **Reopen spawn**, which restores
61
+ the whole draft. An unknown outcome keeps the row, reading "Outcome
62
+ unknown", with Check result. Prepare reuses the dialog's fresh preview
63
+ instead of reading it again, and the kernel's `--expect-decision` still
64
+ refuses a decision that changed. Dialog previews use `--max-age 60` when the
65
+ CLI supports it (feature `spawn-preview-max-age`)
66
+ ([desktop-spawn-preview.md](../../packages/desktop/docs/desktop-spawn-preview.md#background-spawn-the-dialog-closes-on-spawn)).
67
+
68
+ - **The spawn dialog never waits on the preview while you edit.** The last
69
+ preview stays visible while it updates, Spawn stays pressable, and answers
70
+ are reused for 60 s (#378).
71
+
72
+ - **Core capabilities and Capabilities read as one system** on the soul page,
73
+ the capability page, the instance inspector and the spawn preview. Every
74
+ core row says why it's there, including a slot the soul empties or that has
75
+ no default. The capability page opened from a soul gains a "Why" row.
76
+ Workspace › Capabilities lists Repo owned before Packages (#377).
77
+
78
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.34.0`**, so it runs against
79
+ this release's kernel. Install the CLI and the Desktop 0.33.0 together: the
80
+ Desktop 0.32.x refuses a 0.33 CLI.
81
+
82
+ - **Unattended claude and codex launches** (awebai/oats#341,
83
+ [souls-and-instances.md](../souls-and-instances.md#unattended-launches-folder-trust)).
84
+ - Every codex launch passes `-c check_for_update_on_startup=false`, so
85
+ Codex's update prompt no longer blocks it.
86
+ - Codex does not apply a trusted parent to the folders below it. When
87
+ `~/.codex/config.toml` trusts the deployment or an ancestor, a codex
88
+ launch now trusts its new home for that session; the file is never
89
+ written.
90
+ - A claude or codex spawn whose home is not covered by the operator's
91
+ one-time trust of the deployment warns `the <harness> session will stop
92
+ at its folder-trust prompt: trust <deployment> once (…)`. `oats readiness`
93
+ reports the same as a `harness-trust` item in `checks.configured` (not
94
+ required).
95
+
96
+ - **The Desktop names an OATS cache problem, and keeps the roster through an
97
+ unreadable remote.** When the kernel can't read a remote
98
+ (`E_REMOTE_UNREADABLE`), the sidebar roster keeps the instances it last
99
+ observed instead of going empty. A cache problem (`details.reason: "cache"`)
100
+ reads "Couldn't refresh instances · OATS cache problem · observed <age>",
101
+ with the kernel's message in full (it names the lock file or the process
102
+ holding it, and the remedy) and Retry. With nothing observed yet, the failed
103
+ state shows that message. A network failure or a timeout reads "Couldn't
104
+ reach <host> · showing what was read <age>", with the raw code behind
105
+ Details. Of the kernel's structured `details`, only the reason and the
106
+ host of the remote cross to the window; the kernel's message is shown as
107
+ given, with the file or process it names
108
+ ([desktop-cli-api.md](../desktop-cli-api.md#workspace)).
109
+
110
+ ## Fixed
111
+
112
+ - **A background spawn never fails silently, and a soul can't be spawned
113
+ twice at once** (awebai/oats#383). While a spawn of a soul is in flight,
114
+ opening Spawn for it shows the press disabled with "A spawn of <soul> is in
115
+ progress" and a link to its pending row; it re-enables when the spawn
116
+ settles. After a window reload, a spawn whose outcome wasn't known yet comes
117
+ back as a pending row and its outcome is reported, a failure included, even
118
+ when it settled while another workspace was on screen; Reopen spawn then
119
+ restores the soul and the name. Quitting the Desktop mid-spawn
120
+ still loses an unreported failure
121
+ ([desktop-spawn-preview.md](../../packages/desktop/docs/desktop-spawn-preview.md#background-spawn-the-dialog-closes-on-spawn)).
122
+
123
+ - **A git that the system kills is no longer reported as a timeout**
124
+ (awebai/oats#387). A remote read reports `timeout` only when OATS's own
125
+ timer stopped git. When something else kills git (an out-of-memory kill
126
+ during a large fetch, for example), the error now reads `cannot read remote
127
+ <url> (killed): git was killed (signal SIGKILL)`, with `reason: killed` and
128
+ `details.signal`. Before, it said `(timeout)`.
129
+
130
+ - **Codex sessions see their instance** (awebai/oats#342). Codex can run tool
131
+ commands under its shared app-server daemon, without the session's
132
+ environment, so `oats aweb roster` inside Codex asked for `--soul`.
133
+ - A codex launch now sets the instance environment for tool commands with
134
+ `shell_environment_policy.set`: `OATS_INSTANCE`/`OATS_INSTANCE_HOME`, the
135
+ capabilities' launch environment (`AWEB_IDENTITY_HOME`, `AWEB_DELIVERY`)
136
+ and the launch configuration's literals.
137
+ - It also sets `PATH` with the home's `.oats/bin` first. The user's login
138
+ shell may put its profile's entries ahead of it.
139
+ - A capability command with no home in its environment finds the home from
140
+ its working directory.
141
+
142
+ - The partial-fetch tests now pass on a host whose git is older than 2.45
143
+ (awebai/oats#389). The kernel already fell back to whole trees there; the
144
+ tests now read the kernel's own git probe:
145
+ - on an older git they assert that fallback (whole trees, one warning per
146
+ repository);
147
+ - they skip the partial-cache mechanics that cannot happen there, and name
148
+ why;
149
+ - under CI, an older git fails those tests instead.
150
+
151
+ - **A killed fetch no longer leaves a remote cache unusable** (awebai/oats#386).
152
+ OATS ends git with SIGTERM first, so git removes its own lock files, and
153
+ SIGKILL only after a grace. A lock left by a git killed earlier is removed
154
+ once it is older than the longest fetch, with an `oats: warning` naming it.
155
+ A younger one is never removed: the error names the file and says it is
156
+ safe to remove once no oats or git process is running.
157
+
158
+ - **Ending git leaves no survivor in its process group** (awebai/oats#386). After
159
+ git's own output closes, OATS keeps checking git's process group until the grace
160
+ ends: a member that ignores SIGTERM and holds no pipe is still killed, and a
161
+ group seen empty is never signalled again (its id may already belong to another
162
+ process).
163
+
164
+ - **Processes making the first fetch from one remote at once all succeed**
165
+ (two spawns, two Desktop previews). Every write to a cache takes a
166
+ per-cache lock: a live holder is waited for, a dead one's lock is reclaimed,
167
+ and the cache repo appears whole. A failure to write the local cache is now
168
+ reported as `E_REMOTE_UNREADABLE` with the new reason `cache`, never as
169
+ `network`.
170
+
171
+ - Fetching a commit into the remote cache may take 10 minutes instead of 30 s, so
172
+ the first spawn from a large workspace host no longer fails with `cannot read
173
+ remote … (timeout)`; the timeout error names the fetch and the elapsed time
174
+ (awebai/oats#362).
@@ -204,6 +204,70 @@ the harness's own precedence. The `CLAUDE.md → AGENTS.md` and
204
204
  `.claude/skills → ../.agents/skills` aliases are kept. OATS composes
205
205
  instructions and pins model/provider settings; it excludes nothing.
206
206
 
207
+ ### Unattended launches: folder trust
208
+
209
+ Claude Code and Codex ask before they work in a folder they have not seen, and
210
+ every instance home is new. A launch stopped at that prompt waits for a human,
211
+ so the operator trusts the **deployment directory** (where `oats-local.yaml`
212
+ is) once per harness. OATS only reads the harnesses' configuration; it never
213
+ writes it.
214
+
215
+ - **Claude Code** looks for an accepted entry for its folder or an ancestor, up
216
+ to a git root. Instance homes are not inside a git repository, so one entry
217
+ for the deployment covers every home under it. To add it, run `claude` in the
218
+ deployment once and accept the prompt. That records
219
+ `projects["<deployment>"].hasTrustDialogAccepted` in `~/.claude.json`
220
+ (`$CLAUDE_CONFIG_DIR/.claude.json` when that is set).
221
+ - **Codex** applies only an exact entry: a trusted parent does not cover the
222
+ folders below it. To give the operator's consent, run `codex` in the
223
+ deployment once and choose "Trust and continue". That records
224
+ `[projects."<deployment>"] trust_level = "trusted"` in
225
+ `~/.codex/config.toml` (`$CODEX_HOME/config.toml`). With that entry, or one
226
+ for an ancestor of the deployment, each codex launch trusts its own new home
227
+ for that session (`-c 'projects={"<home>"={trust_level="trusted"}}'`) and
228
+ leaves the config file unchanged. A yolo launch always does this. The plan
229
+ re-reads the entry at every start.
230
+ - Every codex launch also passes `-c check_for_update_on_startup=false`, so
231
+ Codex's "Update available" choice cannot block it.
232
+
233
+ When a claude or codex home is not covered, the spawn says so (text and
234
+ `--json` `warnings`): `the <harness> session will stop at its folder-trust
235
+ prompt: trust <deployment> once (<the step>)`. `oats readiness` reports the
236
+ same in `checks.configured` (code `harness-trust`, not required).
237
+
238
+ ### Codex tool commands and the instance environment
239
+
240
+ Codex can run tool commands under its shared app-server daemon rather than as
241
+ children of the session OATS launched, and then they do not inherit the
242
+ session's environment. Codex (0.157.1) runs a session that has `-c` overrides
243
+ embedded, without the daemon, and every kernel codex launch has them, so an
244
+ OATS codex session does not appear in `codex agents`. So that the environment
245
+ does not depend on this, a codex launch also sets it for tool
246
+ commands explicitly with `-c shell_environment_policy.set.<NAME>="<value>"`:
247
+
248
+ - the instance: `OATS_INSTANCE`, `OATS_INSTANCE_HOME`, `PI_AGENT_INSTANCE`,
249
+ `PI_AGENT_HOME`;
250
+ - every capability's launch environment (for example the messaging
251
+ provider's identity home and delivery mode);
252
+ - the launch configuration's literal values. A reference's value never goes
253
+ on a command line.
254
+
255
+ `PATH` comes from the launching shell, with the home's `.oats/bin` first, so
256
+ only the execution passes it; the persisted command does not carry it. Codex
257
+ runs tool commands through the user's login shell, and a profile that prepends
258
+ directories puts those entries ahead of `.oats/bin`. A second `oats` in such a
259
+ directory is found first.
260
+
261
+ A capability command (`oats <namespace> …`) run with none of
262
+ `OATS_INSTANCE_HOME`, `PI_AGENT_HOME` or `OATS_HOME` set finds its instance
263
+ from the working directory. It uses the nearest enclosing directory laid out as
264
+ `<agents-root>/<soul>/instances/<name>` whose `instance.json` records that
265
+ name, and validates it like a home named by the environment. The walk uses the
266
+ directory as the shell names it (`$PWD`). That matters for an attached
267
+ instance, whose `work/` links into its owner's tree: below it, the physical
268
+ path is the owner's. A process that has no `$PWD` there would act as the
269
+ owner, so an attached instance runs capability commands from its home.
270
+
207
271
  ## Lifecycle
208
272
 
209
273
  ### Spawn
@@ -183,7 +183,7 @@ as `confirmed` or the reason it is not:
183
183
  | `not-listed` | the workspace does not list the repo |
184
184
  | `no-backlink` | no (or invalid) `oats-membership.yaml` at the member's default branch |
185
185
  | `backlink-elsewhere` | the member names a different workspace (a case-only difference is flagged: repo paths are case-sensitive identities) |
186
- | `cannot-read` | the operator cannot read the member (auth / not-found / network / timeout) |
186
+ | `cannot-read` | the operator cannot read the member (auth / not-found / network / timeout / killed: the system killed git, e.g. out of memory), or this machine's remote cache could not be written (cache) |
187
187
 
188
188
  An unconfirmed member contributes nothing but its row: its souls are invisible,
189
189
  its capabilities unresolvable (`E_NOT_A_MEMBER` / `E_MEMBERSHIP_UNCONFIRMED`).
@@ -34,6 +34,7 @@
34
34
  import { spawnSync } from "node:child_process";
35
35
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
36
36
  import { dirname, join } from "node:path";
37
+ import { recordLocalInput } from "./local-inputs.mjs";
37
38
  import YAML from "yaml";
38
39
  import { parseConfigData } from "./config-data.mjs";
39
40
 
@@ -192,8 +193,11 @@ export function soulOriginOf(index, soul) {
192
193
 
193
194
  export const snapshotPath = (dep) => join(dep, ".agents", "automations", "snapshot.json");
194
195
  export function readSnapshot(dep) {
196
+ let text;
197
+ try { text = readFileSync(snapshotPath(dep), "utf8"); } catch { recordLocalInput(snapshotPath(dep), null); return null; }
198
+ recordLocalInput(snapshotPath(dep), text);
195
199
  try {
196
- const doc = JSON.parse(readFileSync(snapshotPath(dep), "utf8"));
200
+ const doc = JSON.parse(text);
197
201
  return isObject(doc) && KIND_NAMES.every((k) => Array.isArray(doc[`${k}s`])) ? doc : null;
198
202
  } catch { return null; }
199
203
  }