@awebai/oats 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/oats.mjs +128 -20
- package/docs/capabilities.md +22 -0
- package/docs/configuration.md +2 -2
- package/docs/design/2026-09-23-workspace-module-contracts.md +3 -2
- package/docs/desktop-cli-api.md +231 -7
- package/docs/first-team.md +20 -2
- package/docs/implementation.md +78 -2
- package/docs/integrations.md +1 -1
- package/docs/official-catalog.md +2 -2
- package/docs/packages.md +2 -2
- package/docs/release-notes/v0.33.0.md +174 -0
- package/docs/release-notes/v0.34.0.md +63 -0
- package/docs/souls-and-instances.md +64 -0
- package/docs/workspaces.md +1 -1
- package/lib/capability-show.mjs +208 -0
- package/lib/core.mjs +89 -17
- package/lib/harness-trust.mjs +139 -0
- package/lib/instance-inspect.mjs +16 -2
- package/lib/packages.mjs +1 -1
- package/lib/process-group.mjs +54 -0
- package/lib/remote.mjs +721 -112
- package/lib/resolve.mjs +56 -14
- package/package-catalog.json +2 -2
- package/package.json +1 -1
package/docs/desktop-cli-api.md
CHANGED
|
@@ -38,9 +38,10 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
|
|
|
38
38
|
"instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
|
|
39
39
|
"workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
|
|
40
40
|
"team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
|
|
41
|
-
"preview-composed-from","observe-max-age"],
|
|
41
|
+
"preview-composed-from","observe-max-age","spawn-preview-max-age","capability-show"],
|
|
42
42
|
"automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
|
|
43
|
-
"readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2
|
|
43
|
+
"readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2,
|
|
44
|
+
"capabilityShowApi":1}
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
- The Desktop accepts `desktopApi === 1` and a released `version` inside
|
|
@@ -96,6 +97,8 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
|
|
|
96
97
|
| `launch-preference` | soul and local launch preferences; `launch`, `launchCurrent`, `launchFrom`; `--reselect-launch`; `key` on soul and agent rows ([Launch preferences](#soul-launch-preferences-feature-launch-preference-oats-0300)) | |
|
|
97
98
|
| `preview-composed-from` | `composedFrom` on preview `modules[]` ([Composition](#the-preview)) | |
|
|
98
99
|
| `observe-max-age` | `--max-age <s>` on the read verbs and their `observation` block ([Observation reuse](#observation-reuse-feature-observe-max-age-oats-0311)) | |
|
|
100
|
+
| `spawn-preview-max-age` | `--max-age <s>` on `spawn --preview` and its `observation` block ([Observation reuse](#observation-reuse-feature-observe-max-age-oats-0311), [The preview](#the-preview)) | |
|
|
101
|
+
| `capability-show` | `oats capabilities show <name>` and its `--file` form, OATS 0.34.0 ([`oats capabilities show`](#oats-capabilities-show)) | `capabilityShowApi: 1` |
|
|
99
102
|
|
|
100
103
|
Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
|
|
101
104
|
`workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
|
|
@@ -194,6 +197,8 @@ it and answer live, without the block.
|
|
|
194
197
|
```text
|
|
195
198
|
oats status | workspace status | souls | capabilities | inspect --soul|--home
|
|
196
199
|
| teams | soul teams <soul> … --max-age <seconds> --json
|
|
200
|
+
oats spawn <soul> … --preview --max-age <seconds> --json (feature spawn-preview-max-age)
|
|
201
|
+
oats capabilities show <name> … --max-age <seconds> --json (feature capability-show)
|
|
197
202
|
```
|
|
198
203
|
|
|
199
204
|
- **Values:** whole seconds, `0` to `86400`. `0` is live: it reuses nothing.
|
|
@@ -206,7 +211,15 @@ oats status | workspace status | souls | capabilities | inspect --soul|--home
|
|
|
206
211
|
everywhere; `reused` is `true` when any head came from an earlier observation.
|
|
207
212
|
A command that read no remote head reports the time it started and
|
|
208
213
|
`reused: false`. Without the flag the key is absent and every document is
|
|
209
|
-
exactly as before.
|
|
214
|
+
exactly as before. A refusal (`E_SOUL_UNKNOWN`, any error envelope) never
|
|
215
|
+
carries the block.
|
|
216
|
+
- **Spawn preview** (feature `spawn-preview-max-age`, OATS 0.33.0): the
|
|
217
|
+
preview's `result` gains the same block, so you can say "as of
|
|
218
|
+
`<observedAt>`". The `decision` covers the heads the preview used, reused
|
|
219
|
+
or live: a reused head the member has since moved from makes the apply
|
|
220
|
+
(which always observes live) refuse `E_DECISION_STALE`, and the apply
|
|
221
|
+
records the head it observed, so the next preview under `--max-age` shows
|
|
222
|
+
it with a new `decision.revision`. The apply itself refuses the flag.
|
|
210
223
|
- **`localRevision`:** 24 lowercase hex characters, opaque. It digests every
|
|
211
224
|
piece of local configuration the kernel read for this answer:
|
|
212
225
|
`oats-local.yaml` (and each closer `oats-local.yaml` it looked for and did
|
|
@@ -237,12 +250,14 @@ oats status | workspace status | souls | capabilities | inspect --soul|--home
|
|
|
237
250
|
spelling of the same repository (ssh vs https) or for a different ref; its
|
|
238
251
|
commit can no longer be fetched. Each is observed live, as without the flag.
|
|
239
252
|
A live observation that fails is the usual error, never an older head.
|
|
240
|
-
- **Refusals:** every other command,
|
|
253
|
+
- **Refusals:** every other command, a spawn apply (with or without
|
|
254
|
+
`--expect-decision`; the form named is `spawn`), every edit form (`teams add|remove|default`,
|
|
241
255
|
`soul teams --add|--remove|--default|--clear-default`) and any `--server`
|
|
242
256
|
invocation refuse the flag before reading or writing anything, with
|
|
243
257
|
`E_BAD_ARGS` "--max-age is not accepted by \`oats <form>\`: only the read
|
|
244
258
|
verbs reuse observations (status, workspace status, souls, capabilities,
|
|
245
|
-
inspect --soul|--home, and the read
|
|
259
|
+
capabilities show, inspect --soul|--home, spawn --preview, and the read
|
|
260
|
+
forms of teams and soul teams)" and,
|
|
246
261
|
with `--server`, "--max-age cannot be combined with --server: observation
|
|
247
262
|
reuse is local to this machine".
|
|
248
263
|
A capability command's argv (`oats <namespace> …`) is its provider's: the
|
|
@@ -523,7 +538,25 @@ Feature `workspace-v2`, `workspaceApi: 2`. Model: [workspaces.md](workspaces.md)
|
|
|
523
538
|
`E_LOCAL_MISSING {dir, searched}`.
|
|
524
539
|
- The workspace is read over Git remotes with the operator's credentials,
|
|
525
540
|
never prompting: `E_REMOTE_UNREADABLE {url, reason: "auth" | "not-found" |
|
|
526
|
-
"network" | "timeout"}`.
|
|
541
|
+
"network" | "timeout" | "killed" | "cache" | "unknown"}`. `killed` (the
|
|
542
|
+
system killed git, for example out of memory) also carries `signal`.
|
|
543
|
+
`cache` (OATS 0.33.0) is local: the
|
|
544
|
+
remote cache on this machine could not be written (a git lock still held,
|
|
545
|
+
another oats process still writing it, or a lock file one left when it
|
|
546
|
+
died); `details.cacheDir` and, when known, `details.lock`,
|
|
547
|
+
`details.guard` or `details.holderPid` say which, and the message says
|
|
548
|
+
what to do.
|
|
549
|
+
- **How the Desktop reads it** (0.33.0). Of an `E_REMOTE_UNREADABLE` from
|
|
550
|
+
`status` / `workspace status`, the Desktop keeps the `message` (shown as
|
|
551
|
+
given) and only a bounded cause: `details.reason` (matching
|
|
552
|
+
`^[a-z][a-z-]{0,31}$`) and the host of `details.url`. No path, pid, lock,
|
|
553
|
+
`cacheDir` or other detail field crosses to the renderer. It keys only on
|
|
554
|
+
`code` + `details.reason`: `cache` words the roster "OATS cache
|
|
555
|
+
problem" with the message in full; `network` / `timeout` read "Couldn't
|
|
556
|
+
reach <host>"; any other reason keeps the generic wording. When the
|
|
557
|
+
deployment was observed before, the failed read keeps that observation:
|
|
558
|
+
`/api/panel` serves it with `error` (the message) and `errorCause {code,
|
|
559
|
+
reason, host?}`, and the roster shows it stale instead of empty.
|
|
527
560
|
- There is no package approval: declaring a package is the trust decision.
|
|
528
561
|
No payload carries `approvalNeeded`, `approval` or `approved`.
|
|
529
562
|
- A **standalone view** is a member repository whose workspace is not read
|
|
@@ -704,6 +737,14 @@ Read-only (it writes no lock):
|
|
|
704
737
|
```
|
|
705
738
|
|
|
706
739
|
- `members[]` and `packages[]` are the sync rows (packages from the lock).
|
|
740
|
+
- **Remote budget (0.33.1).** `oats workspace status` and `oats status` finish
|
|
741
|
+
their remote work within 12 s of their first remote read, whatever the
|
|
742
|
+
machine's load or another process holding a remote's cache. A member not
|
|
743
|
+
read by then is a `cannot-read` row whose `detail` ends `(timeout)`, and its
|
|
744
|
+
git is ended; the command still answers. The workspace definition itself
|
|
745
|
+
(the host) not read by then fails the command as any unreadable host does
|
|
746
|
+
(`E_REMOTE_UNREADABLE`, `reason: "timeout"`). Spawn, sync and every other
|
|
747
|
+
verb have no such budget.
|
|
707
748
|
- `declaredPackages`: the ids in `packages:` (standalone: the kernel's
|
|
708
749
|
default). `unsynced`: declared, not locked. `stale`: locked, no longer
|
|
709
750
|
declared. `external[]`: `{source, soul}`.
|
|
@@ -780,6 +821,175 @@ problem`, and (feature `launch-preference`) `key` and `launch`.
|
|
|
780
821
|
This document keeps `soulsApi: 1`; the probe's `soulsApi: 2` is the inspect
|
|
781
822
|
soul row's.
|
|
782
823
|
|
|
824
|
+
### `oats capabilities show`
|
|
825
|
+
|
|
826
|
+
Feature `capability-show`, `capabilityShowApi: 1`, OATS 0.34.0.
|
|
827
|
+
|
|
828
|
+
```text
|
|
829
|
+
oats capabilities show <name> [--member <repoKey> | --package <id>] [--dir <d>] [--max-age <s>] --json
|
|
830
|
+
oats capabilities show <name> [--member <repoKey> | --package <id>] --file <path> [--dir <d>] [--max-age <s>] --json
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
What one capability ships: its inject text and each skill's files, and one
|
|
834
|
+
file's text on request. The Desktop's capability page shows them without
|
|
835
|
+
reading clones or caches itself.
|
|
836
|
+
|
|
837
|
+
> **Every `text` and `description` is untrusted repository content.** Render
|
|
838
|
+
> it as plain text (`textContent`), or through a sanitising Markdown renderer
|
|
839
|
+
> that allows no raw HTML, no scripts and no remote images.
|
|
840
|
+
|
|
841
|
+
**Selection.** The rows are exactly the rows of `oats capabilities --json`
|
|
842
|
+
(one discovery, honouring `--max-age`), so the answer's `commit` equals that
|
|
843
|
+
row's `commit`.
|
|
844
|
+
|
|
845
|
+
- `<name>` alone selects the one row with that name.
|
|
846
|
+
- `--member <repoKey>` selects a member row of that repository: the key as a
|
|
847
|
+
row shows it, or any ref spelling of the same repository.
|
|
848
|
+
- `--package <id>` selects that package's row.
|
|
849
|
+
- No row: `E_CAPABILITY_UNKNOWN`, `details: {name}` plus `member` or
|
|
850
|
+
`package` when one was given. An unsynced package is not in the catalog, so
|
|
851
|
+
its capabilities are `E_CAPABILITY_UNKNOWN` until `oats sync`.
|
|
852
|
+
- More than one row: `E_CAPABILITY_AMBIGUOUS`, `details: {name, candidates}`,
|
|
853
|
+
each candidate `{kind, repoKey, origin}` (member) or `{kind, package,
|
|
854
|
+
origin}` (package). Choose one with `--member` or `--package`.
|
|
855
|
+
- `E_BAD_ARGS` for `--member` with `--package`, a missing or second name, an
|
|
856
|
+
unknown flag, and `--server` (the verb reads this machine's workspace only).
|
|
857
|
+
An unknown word after `oats capabilities` (`oats capabilities foo`) is
|
|
858
|
+
`E_BAD_ARGS` too.
|
|
859
|
+
|
|
860
|
+
**Trust path.** Every read goes through the remote cache at a full commit id;
|
|
861
|
+
nothing reads a working clone.
|
|
862
|
+
|
|
863
|
+
- A member capability is read from its member repository at the row's commit.
|
|
864
|
+
- A package capability is read at the locked commit, the same trust path
|
|
865
|
+
spawn uses. First comes spawn's lock check: when the package manifest at
|
|
866
|
+
that commit does not list the capability, or its capability list differs
|
|
867
|
+
from the lock's, the show refuses `E_PACKAGE_INTEGRITY` with spawn's
|
|
868
|
+
details. The full-tree content digest is not recomputed per show: `oats
|
|
869
|
+
sync` proved the lock's integrity over the tree of exactly that commit, and
|
|
870
|
+
the commit id content-addresses the tree.
|
|
871
|
+
|
|
872
|
+
**The show:**
|
|
873
|
+
|
|
874
|
+
```json
|
|
875
|
+
{"capabilityShowApi":1,"name":"oats.okf","kind":"package","repoKey":"github.com/awebai/oats-okf","package":"oats.okf","version":"4.0.5",
|
|
876
|
+
"commit":"26d8216f…","path":"oats-package/capabilities/oats-okf",
|
|
877
|
+
"inject":{"path":"injects/okf.md","bytes":2422,"text":"## Knowledge: OKF\n\nYou have two kinds of knowledge. …","binary":false,"truncated":false},
|
|
878
|
+
"skills":[{"name":"okf-consultation","path":"skills/okf-consultation","description":"Consulting your soul's knowledge with the `oats okf` CLI: …",
|
|
879
|
+
"files":[{"path":"skills/okf-consultation/SKILL.md","bytes":6947},{"path":"skills/okf-consultation/references/consult.md","bytes":4465}],
|
|
880
|
+
"filesTruncated":false},
|
|
881
|
+
{"name":"okf-instance-knowledge","path":"skills/okf-instance-knowledge","description":"Keeping this instance's own knowledge …",
|
|
882
|
+
"files":[{"path":"skills/okf-instance-knowledge/SKILL.md","bytes":4787}],"filesTruncated":false}],
|
|
883
|
+
"problems":[]}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
- `kind` is `member` or `package`. `repoKey` is set for both kinds; for a
|
|
887
|
+
package it is the repository the package is read from. `package` and
|
|
888
|
+
`version` are the package id and locked version, `null` for a member.
|
|
889
|
+
- `commit` is 40 hex and equals the catalog row's `commit`. `path` is the
|
|
890
|
+
capability directory, repository-relative.
|
|
891
|
+
- `inject` is `{path, bytes, text, binary, truncated}` or `null`. `skills`
|
|
892
|
+
is a list of `{name, path, description, files, filesTruncated}`, each file
|
|
893
|
+
`{path, bytes}`, or `null`. `problems` is a list of `{code, message,
|
|
894
|
+
path}`.
|
|
895
|
+
- With `--max-age` (`0` included) both the show and the `--file` answer gain
|
|
896
|
+
the [`observation`](#observation-reuse-feature-observe-max-age-oats-0311)
|
|
897
|
+
block `{observedAt, reused, localRevision}`.
|
|
898
|
+
|
|
899
|
+
**The `--file` answer:**
|
|
900
|
+
|
|
901
|
+
```json
|
|
902
|
+
{"capabilityShowApi":1,"name":"oats.okf","kind":"package","commit":"26d8216f…",
|
|
903
|
+
"file":{"path":"skills/okf-instance-knowledge/SKILL.md","bytes":4787,"text":"---\nname: okf-instance-knowledge\n…","binary":false,"truncated":false}}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Rules.**
|
|
907
|
+
|
|
908
|
+
- **Paths.** Every `path` in `inject`, `skills`, `file` and `problems` is
|
|
909
|
+
POSIX and relative to the capability directory, never the repository.
|
|
910
|
+
Every non-null `path` is a safe relative path: no `.`, `..` or `.git`
|
|
911
|
+
component (any case), no empty component, no `\`. A manifest's inject and
|
|
912
|
+
skill paths are reported as the module install reads them: a `\` is a
|
|
913
|
+
separator, and a leading `./` and trailing slashes are dropped
|
|
914
|
+
(`injects\guide.md` is `injects/guide.md`).
|
|
915
|
+
- **The inject** is the committed file exactly: untrimmed and untemplated.
|
|
916
|
+
(Spawn composes it raw and trimmed, with no settings substitution.)
|
|
917
|
+
- `inject: null`: the manifest declares no inject.
|
|
918
|
+
- Declared but unreadable (missing, a symlink, a directory, over the read
|
|
919
|
+
budget): `inject: {path, bytes: null, text: null, binary: false,
|
|
920
|
+
truncated: false}` and a `problems[]` entry with the remote's code
|
|
921
|
+
(`E_REMOTE_PATH_MISSING`, `E_REMOTE_TREE_UNSAFE`, `E_REMOTE_FILE_OVERSIZE`,
|
|
922
|
+
…). The show still answers ok.
|
|
923
|
+
- Declared as a path that is not a safe relative path (spawn refuses it):
|
|
924
|
+
`inject: {path: null, bytes: null, text: null, binary: false, truncated:
|
|
925
|
+
false}` and a problem with spawn's code (`E_CAPABILITY_MISSING` for a
|
|
926
|
+
member, `E_PACKAGE_MANIFEST` for a package) and `path: null`. The raw
|
|
927
|
+
manifest value appears only inside `message`, JSON-quoted. So
|
|
928
|
+
`inject.path` is `null` only with a problem.
|
|
929
|
+
- **Skills** are in the catalog row's order (by name, in codepoint order),
|
|
930
|
+
enumerated exactly as a spawn enumerates them. `skills` is `null` exactly
|
|
931
|
+
when the catalog row's `skills` is `null`, with a problem carrying spawn's
|
|
932
|
+
code (`E_CAPABILITY_MISSING` or `E_PACKAGE_MANIFEST`) or an `E_REMOTE_*`
|
|
933
|
+
code, and `path: null`. A skill whose path is not safe (a `.git`
|
|
934
|
+
directory) makes the skills unlistable the same way, in both answers.
|
|
935
|
+
- **Files.** `files` is every regular file under the skill directory,
|
|
936
|
+
recursively (no symlinks, no submodules), sorted by path in codepoint order.
|
|
937
|
+
At most 200 per skill; beyond that `filesTruncated` is `true`. `bytes` is
|
|
938
|
+
the blob size. When a skill's files cannot be listed (an unsafe entry name
|
|
939
|
+
in the tree, an unreadable remote), `files` is `null`, `filesTruncated` is
|
|
940
|
+
`false`, and a problem's `path` is the skill's `path`: "could not list"
|
|
941
|
+
never collapses into "listed nothing".
|
|
942
|
+
- **`description`** is the `description` key of SKILL.md's leading `---` YAML
|
|
943
|
+
front matter, when it is a string. It is parsed from the whole SKILL.md,
|
|
944
|
+
not from its 262144-byte text cut. It is `null` when the front matter is
|
|
945
|
+
absent or does not parse, the key is absent or not a string, or SKILL.md is
|
|
946
|
+
unreadable or binary (no problem is reported for it). It is cut to at most
|
|
947
|
+
1024 UTF-8 bytes on a code point boundary.
|
|
948
|
+
- **Text.** A file is `binary: true, text: null` when it contains a NUL byte
|
|
949
|
+
or is not valid UTF-8. Only the first 262144 + 3 bytes are examined, so the
|
|
950
|
+
cut is decided on the same bytes; when the file is longer, a valid sequence
|
|
951
|
+
they end inside of is not held against it. Otherwise `text` is the content cut to at
|
|
952
|
+
most 262144 UTF-8 bytes on a code point boundary, with `truncated: true`
|
|
953
|
+
when cut. A byte order mark is kept in the text. `bytes` is always the real
|
|
954
|
+
size.
|
|
955
|
+
- **Invariants** (pinned for the Desktop):
|
|
956
|
+
- `binary: true` ⇒ `text: null` and `truncated: false` (`truncated` is a
|
|
957
|
+
text-only flag).
|
|
958
|
+
- `truncated: true` ⇒ `text` is a string and `binary: false`.
|
|
959
|
+
- `bytes` is `null` only for an unreadable declared inject (with its
|
|
960
|
+
problem). In a `--file` answer it is always an integer.
|
|
961
|
+
- **Large files.** A file over the read budget (4 MiB) is listed with its
|
|
962
|
+
size. `--file` refuses it with `E_REMOTE_FILE_OVERSIZE`, passed through
|
|
963
|
+
unchanged: its `details.path` is repository-relative, not
|
|
964
|
+
capability-relative.
|
|
965
|
+
|
|
966
|
+
**`--file <path>`** reads one file the show lists, and nothing else.
|
|
967
|
+
|
|
968
|
+
- A syntactically unsafe path is `E_CAPABILITY_FILE_UNSAFE`, `details:
|
|
969
|
+
{path}`, before anything is read: absolute, empty, a `.`, `..` or `.git`
|
|
970
|
+
component (any case), an empty component, a trailing slash, a `\` or a NUL.
|
|
971
|
+
- Otherwise the path must be the inject's `path` or a path in some skill's
|
|
972
|
+
`files` as the show lists it (the 200 cap included). Anything else is
|
|
973
|
+
`E_CAPABILITY_FILE_UNKNOWN`, `details: {path, name}`. No other file of the
|
|
974
|
+
capability (`oats.json`, scripts, `bin/`) is readable through this verb.
|
|
975
|
+
- A file the show does not list (beyond the 200 cap, a symlink, under a skill
|
|
976
|
+
whose files cannot be listed) is `E_CAPABILITY_FILE_UNKNOWN` by design:
|
|
977
|
+
show "not available", not an error.
|
|
978
|
+
- A listed file the remote cannot read answers the remote's own code
|
|
979
|
+
(`E_REMOTE_FILE_OVERSIZE`, `E_REMOTE_PATH_MISSING`, `E_REMOTE_TREE_UNSAFE`,
|
|
980
|
+
…).
|
|
981
|
+
|
|
982
|
+
**Failures.** Every failure is exactly one error envelope on stdout with a
|
|
983
|
+
nonzero exit, as for every command: `E_BAD_ARGS`, `E_CAPABILITY_UNKNOWN`,
|
|
984
|
+
`E_CAPABILITY_AMBIGUOUS`, `E_PACKAGE_INTEGRITY`, `E_CAPABILITY_FILE_UNSAFE`,
|
|
985
|
+
`E_CAPABILITY_FILE_UNKNOWN`, an `E_REMOTE_*` code, and the workspace's own
|
|
986
|
+
(`E_LOCAL_MISSING`, `E_LOCK_SCHEMA`, …).
|
|
987
|
+
|
|
988
|
+
**Without `--json`** the show prints a short listing for an operator: the
|
|
989
|
+
inject's path and size, each skill with its files and sizes, and the
|
|
990
|
+
problems. `--file` prints the text; a binary file prints a one-line note
|
|
991
|
+
instead, and a truncated file prints its text followed by a one-line note.
|
|
992
|
+
|
|
783
993
|
<a id="desktop-facts-feature-desktop-facts-oats-0290"></a>
|
|
784
994
|
### Desktop facts
|
|
785
995
|
|
|
@@ -1162,7 +1372,7 @@ the result with `oats inspect --soul <name> --json`.
|
|
|
1162
1372
|
### The preview
|
|
1163
1373
|
|
|
1164
1374
|
```text
|
|
1165
|
-
oats spawn <soul> [the flags of a real spawn] --preview --json
|
|
1375
|
+
oats spawn <soul> [the flags of a real spawn] --preview [--max-age <s>] --json
|
|
1166
1376
|
```
|
|
1167
1377
|
|
|
1168
1378
|
Feature `spawn-preview-2`, `spawnPreviewApi: 2`. The preview runs every
|
|
@@ -1250,6 +1460,20 @@ it to a temporary copy (`soulFetched: true`).
|
|
|
1250
1460
|
`payloadRevision` (the merged payloads). `workspace` is the host key;
|
|
1251
1461
|
`standalone` marks a standalone view. `task` is the task text or `null`.
|
|
1252
1462
|
|
|
1463
|
+
**Observation reuse** (feature `spawn-preview-max-age`, OATS 0.33.0).
|
|
1464
|
+
- `--preview --max-age <s>` reuses member heads this machine observed at
|
|
1465
|
+
most `<s>` seconds ago, as the read verbs do ([Observation
|
|
1466
|
+
reuse](#observation-reuse-feature-observe-max-age-oats-0311): the same
|
|
1467
|
+
values, refusals and fallbacks to a live observation). With the flag (`0`
|
|
1468
|
+
included) the result gains `observation: {observedAt, reused,
|
|
1469
|
+
localRevision}`, shaped exactly as the read verbs' block; without it the
|
|
1470
|
+
preview is exactly as before, and no other field changes shape.
|
|
1471
|
+
- `decision.revision` covers the heads the preview used, reused or live.
|
|
1472
|
+
Apply never reuses (it refuses `--max-age`): a head that moved since the
|
|
1473
|
+
reused observation refuses `E_DECISION_STALE`, and the apply records what
|
|
1474
|
+
it observed, so re-preview under `--max-age` to get the new head and
|
|
1475
|
+
revision.
|
|
1476
|
+
|
|
1253
1477
|
**Provider settings.**
|
|
1254
1478
|
- `providers` is the `--provider` map as typed.
|
|
1255
1479
|
- `settings.<cap>`: the merged payload (manifest defaults, then workspace,
|
package/docs/first-team.md
CHANGED
|
@@ -80,6 +80,23 @@ Host-owned provider values (absolute paths, state roots) go under `settings:` in
|
|
|
80
80
|
`oats-local.yaml` afterwards — never in the workspace file, whose schema refuses
|
|
81
81
|
them. Do not commit `oats-local.yaml`.
|
|
82
82
|
|
|
83
|
+
**Trust the deployment once, for unattended launches.** Claude Code and Codex
|
|
84
|
+
ask before they work in a folder they have not seen, and every instance home is
|
|
85
|
+
new: a launch that stops at that prompt waits for a human. OATS never writes
|
|
86
|
+
the harnesses' configuration, so trust the deployment directory yourself, once
|
|
87
|
+
per harness you use:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cd ~/acme && claude # accept the folder-trust prompt, then quit
|
|
91
|
+
cd ~/acme && codex # choose "Trust and continue", then quit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
One entry covers every instance home under the deployment
|
|
95
|
+
([souls-and-instances.md](souls-and-instances.md#unattended-launches-folder-trust)
|
|
96
|
+
says how each harness applies it). Until then, a claude or codex spawn warns
|
|
97
|
+
that its session will stop at the folder-trust prompt, and so does
|
|
98
|
+
`oats readiness`.
|
|
99
|
+
|
|
83
100
|
## 3. Give the deployment a team
|
|
84
101
|
|
|
85
102
|
With a messaging capability in the soul's composition, every instance lives in
|
|
@@ -117,8 +134,9 @@ oats spawn backend-expert --purpose first-fix --task "Fix one small issue, run t
|
|
|
117
134
|
oats status
|
|
118
135
|
```
|
|
119
136
|
|
|
120
|
-
`--harness pi|claude|codex` picks the harness; complete any native
|
|
121
|
-
|
|
137
|
+
`--harness pi|claude|codex` picks the harness; complete any native
|
|
138
|
+
authentication prompt in the printed session (folder trust is the one-time step
|
|
139
|
+
in section 2). The instance home is
|
|
122
140
|
`agents/<soul>/instances/<instance>/`; `work/` is its repository view;
|
|
123
141
|
`.oats/modules/<cap>/` are the copied capabilities and `.agents/skills/<skill>/` their skills;
|
|
124
142
|
`instance.json` records `modules` (from, commit, digest), `providers` and
|
package/docs/implementation.md
CHANGED
|
@@ -30,7 +30,7 @@ published to npm. Its developer docs are in
|
|
|
30
30
|
| `lib/` | the kernel (below) |
|
|
31
31
|
| `injects/` | the kernel and work-mode instruction blocks composed into every instance |
|
|
32
32
|
| `skills/` | bootstrap skills shipped with the kernel |
|
|
33
|
-
| `capabilities/` | this repository's own member capabilities (`oats-workspace-experts`), discovered at the member's latest state |
|
|
33
|
+
| `capabilities/` | this repository's own member capabilities (`oats-desktop-ui`, `oats-workspace-experts`), discovered at the member's latest state |
|
|
34
34
|
| `mirrors/` | generated byte mirrors of the official packages' capabilities (for example `oats-okf*`, checked by `scripts/check-okf-mirror.mjs`): release-lane and test material, kept out of `capabilities/` so member discovery does not list them a second time; not shipped in the npm package |
|
|
35
35
|
| `oats-package/` | the `oats.framework` package |
|
|
36
36
|
| `souls/` | this repository's own souls (a workspace member) |
|
|
@@ -48,6 +48,7 @@ published to npm. Its developer docs are in
|
|
|
48
48
|
| `workspace.mjs` | workspace, membership and soul files; discovery |
|
|
49
49
|
| `resolve.mjs` | a soul's resolution: capabilities, slots, provenance |
|
|
50
50
|
| `packages.mjs` | `packages:`, the catalog, `oats sync`, `oats-lock.json` |
|
|
51
|
+
| `capability-show.mjs` | `oats capabilities show`: one catalog row's inject and skill files, read at its commit |
|
|
51
52
|
| `materialize.mjs` | copying modules into a home and composing it |
|
|
52
53
|
| `core.mjs` | spawn, retire, sessions, hooks, launch recipes, instance metadata |
|
|
53
54
|
| `instruction-composition.mjs` | the generated `AGENTS.md` |
|
|
@@ -58,6 +59,7 @@ published to npm. Its developer docs are in
|
|
|
58
59
|
| `tmux-config.mjs`, `session-*.mjs` | the tmux session backend and terminal input |
|
|
59
60
|
| `capability-contract.mjs`, `provider-binding.mjs` | manifest validation, the hook environment rules, the readiness wire |
|
|
60
61
|
| `servers.mjs` | routing commands to a registered server |
|
|
62
|
+
| `harness-trust.mjs` | reading (never writing) Claude's and Codex's folder trust for a launch |
|
|
61
63
|
|
|
62
64
|
The kernel is runtime-neutral: nothing in `lib/` depends on a harness or on
|
|
63
65
|
a provider. Provider behaviour lives in capabilities; the kernel supplies
|
|
@@ -93,7 +95,42 @@ gets the plain per-call behaviour. Within a session:
|
|
|
93
95
|
- a commit's tree is listed once (`git ls-tree -r -t -l`, bounded by
|
|
94
96
|
`TREE_INDEX_BUDGET`; anything odd falls back to the per-path reads), and
|
|
95
97
|
blobs come from one `git cat-file --batch` reader per cache repo (at most
|
|
96
|
-
12 open,
|
|
98
|
+
12 open, ended through `process-group.mjs` on timeout and at close);
|
|
99
|
+
`fetchRemoteTree` copies a module through it too, once `ensureBlobs` has
|
|
100
|
+
fetched what was missing (git re-reads its packs on a miss, so a reader
|
|
101
|
+
opened earlier finds the new blobs; one still missing answers `missing`,
|
|
102
|
+
never a fetch), with what is left of `TREE_BUDGET` as each read's bound;
|
|
103
|
+
a blob the reader answers `missing` or over its bound is read once more
|
|
104
|
+
alone, so the error is the one a copy without a session gives. A
|
|
105
|
+
command that ends normally awaits the close, so its readers are reaped
|
|
106
|
+
before it exits; a `process.exit` (every refusal) ends them in the
|
|
107
|
+
exit hook (`closeNow`), and the system reaps them once the process is gone;
|
|
108
|
+
- a session may have a `deadline` (`READ_REMOTE_BUDGET_MS`, 12 s after it
|
|
109
|
+
starts): the CLI gives one to `status` and `workspace status` only
|
|
110
|
+
(`readBudgetMs`; `OATS_READ_REMOTE_BUDGET_MS` overrides it for tests). Every
|
|
111
|
+
remote step then gets what is left of it instead of its own default: each
|
|
112
|
+
git call's timeout (`sessionExec`: ls-remote, fetch, ls-tree, the cache's
|
|
113
|
+
plumbing; none starts once nothing is left), the git version probe
|
|
114
|
+
(`readVersion`), the batch readers' answers, the cache write lock's wait,
|
|
115
|
+
the half-initialised cache's wait and the lock-race backoff. What the
|
|
116
|
+
deadline ends is a `timeout` (a peel or version it ended is never read as a
|
|
117
|
+
missing commit or an older git), so an unread member degrades as any
|
|
118
|
+
unreadable one. A cut wait never changes what it judges: past the deadline
|
|
119
|
+
no lock is taken or reclaimed (live, stale or unreadable), and a cache
|
|
120
|
+
directory waited for less than in full is not taken for a crash's leftover;
|
|
121
|
+
- every git child is ended with SIGTERM first and SIGKILL only after a
|
|
122
|
+
grace (`terminateGroup`): git removes its own lock files on SIGTERM, and
|
|
123
|
+
a git killed outright leaves one that blocks every later write. The
|
|
124
|
+
SIGKILL goes to the whole group even when git itself has exited, so a
|
|
125
|
+
descendant that ignores SIGTERM (ssh, a remote helper) still ends; but
|
|
126
|
+
never to a group seen empty, whose id may already lead an unrelated
|
|
127
|
+
group. Until git's `close` (`watchGroup`), a member holding its pipes
|
|
128
|
+
keeps the id ours; after it, the group is probed every 50 ms through the
|
|
129
|
+
grace (no pid is allocated while it is a live group's id): empty, and it
|
|
130
|
+
is never signalled again. git's pipes are drained on a kill, never
|
|
131
|
+
destroyed, so `close` keeps waiting for a pipe-holding descendant. The exit
|
|
132
|
+
hook cannot wait for a timer, so it waits a bounded 200 ms synchronously
|
|
133
|
+
(`reapOnExit`);
|
|
97
134
|
- discovery reads members eight at a time (`DISCOVERY_CONCURRENCY`) with
|
|
98
135
|
serial results: declaration order, the first failure in that order. The
|
|
99
136
|
observations and the member reads are two pools, so a discovery runs at
|
|
@@ -102,6 +139,45 @@ gets the plain per-call behaviour. Within a session:
|
|
|
102
139
|
shared pool would deadlock: a member read holding a slot waits on its
|
|
103
140
|
member's observation, which needs a slot of its own.
|
|
104
141
|
|
|
142
|
+
The cache repos are partial: a commit is fetched with all its trees and
|
|
143
|
+
only the blobs up to `SMALL_BLOB_LIMIT` (64 KiB), which covers every file
|
|
144
|
+
discovery reads, so listings and discovery stay local after one fetch. A
|
|
145
|
+
read that needs a larger blob, or a `fetchRemoteTree` of a module, fetches
|
|
146
|
+
the missing blobs first in one fetch by id (`ensureBlobs`), then applies the
|
|
147
|
+
budgets to their real sizes before anything is written. git never fetches a
|
|
148
|
+
blob lazily (`GIT_NO_LAZY_FETCH=1`, and no url is stored in the cache: each
|
|
149
|
+
fetch passes it with `-c remote.origin.url=`). A server without partial
|
|
150
|
+
fetches gets whole trees; the cache records that (`oats.fetch = full` in its
|
|
151
|
+
config) and the CLI prints the session's notice once, on stderr. Partial
|
|
152
|
+
caches need git 2.45 or later (`PARTIAL_FETCH_GIT`, the first git with
|
|
153
|
+
`GIT_NO_LAZY_FETCH`): with an older git every cache fetches whole trees, a
|
|
154
|
+
partial cache it meets is deleted and fetched again whole, and the same
|
|
155
|
+
notice says why.
|
|
156
|
+
|
|
157
|
+
Every write to a cache repo (its `git init`, config, fetches and pins) holds
|
|
158
|
+
the repo's cross-process write lock, `<cache>/.locks/<repo>.lock`
|
|
159
|
+
(`withCacheWriteLock`): an exclusive file holding `{pid, token, startedAt}`,
|
|
160
|
+
waited for while its holder lives (bounded by a whole fetch, then
|
|
161
|
+
`reason: "cache"` naming the pid), reclaimed when the holder is dead, and
|
|
162
|
+
released only by its owner. Reclaimers take a short guard,
|
|
163
|
+
`<lock>.reclaim`, and check under it that the lock is still the dead
|
|
164
|
+
record before removing it, so a reclaimer that paused cannot delete a
|
|
165
|
+
live process's new lock. A guard whose holder died is never removed
|
|
166
|
+
automatically (that removal would race the same way, with nothing left to
|
|
167
|
+
serialize it): every write refuses at once, `reason: "cache"` naming the
|
|
168
|
+
guard (`details.guard`), until a human removes it once no oats process is
|
|
169
|
+
running. Reads take no lock. A cache repo appears whole
|
|
170
|
+
(`git init` into a private directory, then a rename), so processes making
|
|
171
|
+
the first fetch of one remote all succeed. A git `*.lock` a write meets is
|
|
172
|
+
judged under that lock (`cacheGit`): older oats kernels take no write lock,
|
|
173
|
+
so it is retried briefly, then removed only when it is inside the cache
|
|
174
|
+
repo, a regular file and older than the longest fetch
|
|
175
|
+
(`GIT_FETCH_TIMEOUT_MS` plus a margin): a git killed mid-write. A removal
|
|
176
|
+
is said once as a warning. Anything else is `reason: "cache"` naming the
|
|
177
|
+
file and when it is safe to remove; so is any other local write failure
|
|
178
|
+
(a `FETCH_HEAD` git cannot open, a read-only or full disk), with git's own
|
|
179
|
+
words.
|
|
180
|
+
|
|
105
181
|
Across commands, `memoAtCommit` keeps parsed reads under
|
|
106
182
|
`<cache>/.parsed/<kernel fingerprint>/`, keyed by (repo key, full commit,
|
|
107
183
|
item). The items: `workspace` (the workspace file), `membership` (a member's
|
package/docs/integrations.md
CHANGED
package/docs/official-catalog.md
CHANGED
|
@@ -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.
|
|
15
|
-
| `oats.engineering` | `v1.
|
|
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.
|
|
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.
|
|
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
|
```
|