@awebai/oats 0.29.4 → 0.30.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 +12 -6
- package/bin/oats.mjs +194 -50
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
- package/capabilities/oats-aweb/injects/aweb.md +1 -1
- package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
- package/capabilities/oats-aweb/oats.json +5 -12
- package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
- package/capabilities/oats-code-review/injects/reviewer.md +26 -0
- package/capabilities/oats-code-review/oats.json +16 -0
- package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
- package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
- package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
- package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
- package/capabilities/oats-developer/injects/developer.md +38 -0
- package/capabilities/oats-developer/oats.json +17 -0
- package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
- package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
- package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
- package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
- package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
- package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
- package/capabilities/oats-engineering-expert/oats.json +17 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
- package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
- package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
- package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +8 -4
- package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
- package/capabilities/oats-okf/lib/inspection.mjs +26 -7
- package/capabilities/oats-okf/lib/sources.mjs +16 -2
- package/capabilities/oats-okf/lib/worker.mjs +5 -16
- package/capabilities/oats-okf/oats.json +6 -3
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
- package/capabilities/oats-okf-harvest/oats.json +3 -3
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +2 -2
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
- package/capabilities/oats-okf-maintenance/oats.json +2 -2
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +1 -1
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
- package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
- package/capabilities/oats-workspace-experts/oats.json +9 -0
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +6 -11
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +97 -117
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +76 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +61 -0
- package/docs/release-lane.md +77 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/schedules.md +280 -363
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +132 -215
- package/lib/automations.mjs +21 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/resolve.mjs +29 -87
- package/lib/schedule.mjs +1 -1
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +2 -2
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +9 -15
- package/package.json +1 -1
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
package/docs/desktop-cli-api.md
CHANGED
|
@@ -1,1944 +1,1954 @@
|
|
|
1
|
-
# Desktop CLI API
|
|
1
|
+
# Desktop CLI API
|
|
2
2
|
|
|
3
|
-
The contract between the
|
|
4
|
-
imports kernel code; it shells out (via `execFile`, argv, absolute binary — no
|
|
5
|
-
shell) to a discovered `oats` and speaks this JSON protocol. **API version, not
|
|
6
|
-
source adjacency, is authoritative.**
|
|
3
|
+
The JSON contract between the `oats` CLI and the OATS Desktop.
|
|
7
4
|
|
|
8
|
-
##
|
|
5
|
+
## Scope
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
The Desktop never imports kernel code. Its server runs a discovered `oats`
|
|
8
|
+
binary with `execFile` (absolute path, argv, no shell) and decodes the JSON it
|
|
9
|
+
prints, strictly. This page describes one current shape per command, as the
|
|
10
|
+
kernel emits it.
|
|
11
|
+
|
|
12
|
+
- **Versioning is by capability, never by version string.** The probe lists
|
|
13
|
+
`features` and per-API integers. Gate every read and mutation on them; an
|
|
14
|
+
absent feature means the kernel cannot do it.
|
|
15
|
+
- **A document carries its own integer** where one exists (`operationsApi`,
|
|
16
|
+
`readinessApi`, `eventsApi`, …). Dispatch on the payload's integer.
|
|
17
|
+
- **The envelope is authoritative over this prose.** If a captured payload
|
|
18
|
+
and this page disagree, this page is the bug.
|
|
19
|
+
- **Shapes are closed.** The Desktop decodes many documents with exact key
|
|
20
|
+
sets, so a new key is a contract change and is announced here first.
|
|
21
|
+
|
|
22
|
+
Conventions: paths are absolute; a `commit` is a full 40-hex id; `integrity`
|
|
23
|
+
and `digest` are `sha256-<hex>`; times are ISO-8601 UTC; repository keys are
|
|
24
|
+
canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
|
|
25
|
+
`/w` as the deployment and shorten ids and digests with `…`.
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
## The probe
|
|
28
|
+
|
|
29
|
+
`oats version --json` prints one object (not an envelope):
|
|
15
30
|
|
|
16
31
|
```json
|
|
17
|
-
{"schemaVersion":1,"name":"@awebai/oats","version":"
|
|
32
|
+
{"schemaVersion":1,"name":"@awebai/oats","version":"0.30.0","desktopApi":1,
|
|
33
|
+
"harnesses":["pi","claude","codex"],"sessionBackends":["tmux","herdr"],"launchOptions":["yolo"],
|
|
34
|
+
"remote":["spawn","retire","status","session","session-start","session-restart","launch-config","roster","harvest","schedule","session-upload","operations"],
|
|
35
|
+
"features":["retire-home","session-start","session-restart","launch-config","schedule","session-upload","operations","instance-git",
|
|
36
|
+
"instance-git-remote","souls-declarations","lifecycle-plans","retire-retention","readiness","spawn-preview","instance-events",
|
|
37
|
+
"instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
|
|
38
|
+
"workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
|
|
39
|
+
"team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference"],
|
|
40
|
+
"automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
|
|
41
|
+
"readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2}
|
|
18
42
|
```
|
|
19
43
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`packages
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
harness/permission overrides require `launch-config`; restarting a running
|
|
35
|
-
home also requires `session-restart`. Desktop checks the corresponding
|
|
36
|
-
`remote` entries before offering these operations for a server. The router
|
|
37
|
-
then probes the execution host before sending a mutation. An absent feature
|
|
38
|
-
means an update is needed; it is not inferred from the version number.
|
|
39
|
-
|
|
40
|
-
The band is widened one kernel minor at a time, after confirming this v1
|
|
41
|
-
surface is unchanged, and always admits the kernel published by the same
|
|
42
|
-
release — Desktop and the CLI are built from one tag, so a band excluding its
|
|
43
|
-
own kernel would degrade the shipped app to observation-only. Prereleases are
|
|
44
|
-
never accepted.
|
|
45
|
-
|
|
46
|
-
## Envelope
|
|
47
|
-
|
|
48
|
-
Every other `--json` command emits **exactly one JSON object on stdout** and
|
|
49
|
-
no progress prose (progress goes to stderr):
|
|
50
|
-
|
|
51
|
-
- success (exit 0): `{"schemaVersion":1,"ok":true,"result":{...}}`
|
|
52
|
-
- failure (nonzero exit): `{"schemaVersion":1,"ok":false,"error":{"code":"...","message":"..."}}`
|
|
53
|
-
|
|
54
|
-
Either may also carry `"warnings":[…]` (0.27.0+), present only when there is
|
|
55
|
-
something to say. The one warning so far is `deprecated-runtime-name` (see
|
|
56
|
-
[the harness rename](#the-harness-rename-feature-harness-oats-0270)). `oats status
|
|
57
|
-
--json`, whose document is not an envelope, carries the same `warnings` beside
|
|
58
|
-
its `problems`. In text mode the warning is one `oats: warning: …` line on stderr.
|
|
59
|
-
|
|
60
|
-
## Flags
|
|
61
|
-
|
|
62
|
-
A kernel command reads `--flag=value` exactly as `--flag value`, with the
|
|
63
|
-
same validation (0.27.3+; before, the inline form was silently ignored, so
|
|
64
|
-
`--harness=claude` spawned the default harness). The value is everything after
|
|
65
|
-
the first `=`. An empty `--flag=` is `E_BAD_ARGS` ("`--flag=` needs a value"),
|
|
66
|
-
and so is a value on a switch: `--yolo=false` is refused and never turns
|
|
67
|
-
yolo on. A capability command's own flags belong to its provider. They are
|
|
68
|
-
forwarded exactly as typed; the kernel reads only its dispatch flag (`--soul`)
|
|
69
|
-
in either form. There is no feature string: a caller that must work with
|
|
70
|
-
older kernels uses the spaced form.
|
|
71
|
-
|
|
72
|
-
## The harness rename (feature `harness`, OATS 0.27.0)
|
|
73
|
-
|
|
74
|
-
What starts an instance (pi, claude or codex) is its **harness**. 0.27.0 renames
|
|
75
|
-
the kernel's `runtime` to `harness` everywhere it means that:
|
|
76
|
-
|
|
77
|
-
- Outputs speak only the new names.
|
|
78
|
-
- Every input written before 0.27.0 still works. The rule is read either,
|
|
79
|
-
write new: the old spelling is read as the new one, and the next write
|
|
80
|
-
records the new one.
|
|
81
|
-
- A command that read an old spelling answers **one** warning:
|
|
82
|
-
`{"code":"deprecated-runtime-name","key":"runtime","replacement":"harness","sources":[…],"message":"…"}`.
|
|
83
|
-
`sources` names each place it read the old spelling (a flag, a file and its
|
|
84
|
-
key, a home).
|
|
85
|
-
- A pair that disagrees is refused rather than guessed, for example
|
|
86
|
-
`--harness pi --runtime claude`, or both keys with different values.
|
|
87
|
-
- A later release drops the old spellings.
|
|
88
|
-
|
|
89
|
-
Gate on the feature `harness`. A kernel without it speaks the old names: the
|
|
90
|
-
routed commands (`--server`) already translate for such a host, sending
|
|
91
|
-
`--runtime` and `runtime` keys to it and reading its `runtimes` list.
|
|
92
|
-
|
|
93
|
-
| Surface | Before 0.27.0 | 0.27.0 | Old spelling still accepted? |
|
|
94
|
-
|---|---|---|---|
|
|
95
|
-
| `oats version --json` | `runtimes: [pi, claude, codex]` | `harnesses: [...]`, feature `harness` | **Dropped**: no `runtimes` alias; gate on the feature |
|
|
96
|
-
| Flag on `spawn` (and `--preview`), `session start`/`restart`, `launch-config preview`, and their `--server` forms | `--runtime <h>` | `--harness <h>` | Yes, with the warning (the okf 2.1.5 harvest worker passes `--runtime` to spawn). Both flags disagreeing → `E_BAD_ARGS` |
|
|
97
|
-
| `oats status --json`: `agents[]` rows (soul default) and `agents[].instances[]` rows | `runtime` | `harness` | Output only |
|
|
98
|
-
| `oats status --json`: instance rows' `composition.materialized` | `runtimePackages`, `runtimePosture` | `harnessPackages`, `harnessPosture` | Output only |
|
|
99
|
-
| `oats inspect --json` `souls[]` rows, remote roster rows | `runtime` | `harness` | Output only; a pre-0.27 host's `runtime` rows are read as `harness` |
|
|
100
|
-
| `oats inspect --home --json` `instance` | `runtime` | `harness` | Output only |
|
|
101
|
-
| The launch plan's package check (`launch-config preview` `problems[]`) | `runtime-packages` | `harness-packages` | Output only |
|
|
102
|
-
| Soul `soul.yaml` (member and package souls) | `runtime:` | `harness:` | Yes, **without** a warning: released capabilities (oats.aweb 1.13.1) ship `runtime:`, and the operator cannot fix a provider's file. Both, disagreeing → `E_BAD_MANIFEST` |
|
|
103
|
-
| `oats-local.yaml` `launch-configs.<name>` | `runtime:` | `harness:` | Yes, with the warning. Both, disagreeing → `E_WORKSPACE_SCHEMA`. `launch-config set` writes `harness` (a `runtime` in its `--file` definition too) |
|
|
104
|
-
| `launch-config list/preview --json` rows and `selection` | `runtime` | `harness` | Output only |
|
|
105
|
-
| Home `instance.json` | `runtime`; launch recipe `launch` version 1 `{runtime}` | `harness`; recipe version **2** `{harness}` | Yes, with the warning naming the home: a 0.26.0 home inspects, starts, restarts and retires; its next start or restart records the new names |
|
|
106
|
-
| `oats spawn … --preview` decision | `effective.runtime` | `effective.harness` | Output only. The revision digests the key names, so a decision previewed by a 0.26 kernel is `E_DECISION_STALE` at apply (with the fresh decision) |
|
|
107
|
-
| `spawn`/`session` results, `spawned` event `data` | `runtime` | `harness` | Output only |
|
|
108
|
-
| Schedule definitions (`schedule add/update --spec-json`, stored jobs, `schedule list`) | `runtime` | `harness` | Yes, with the warning. A stored job is read in the new name and saved in it next time. Both, disagreeing → `E_SCHEDULE_INVALID` (a stored job: invalid on its own) |
|
|
109
|
-
| Schedule run records (`lastRun`, `recentRuns`) | `startedRuntime` | `startedHarness` | Output; a 0.26.0 record's `startedRuntime` is still read |
|
|
110
|
-
| Error codes | `E_UNSUPPORTED_RUNTIME`, `E_RUNTIME_PACKAGE`, `E_RUNTIME_RESOURCE_MISSING` | `E_UNSUPPORTED_HARNESS`, `E_HARNESS_PACKAGE`, `E_HARNESS_RESOURCE_MISSING` | Output only |
|
|
111
|
-
| Hook environment (spawn and launch hooks) | `OATS_RUNTIME`, `OATS_PREVIOUS_RUNTIME` | `OATS_HARNESS`, `OATS_PREVIOUS_HARNESS` | Both are set, with the same values, for released hooks |
|
|
112
|
-
| Capability manifest `requires[]` harness package | `runtime` | `harness` | Yes, **without** a warning (a provider's file); a row naming both is refused |
|
|
113
|
-
| Package verification `loadedBy` | `runtime-discovery` | `harness-discovery` | Output only |
|
|
114
|
-
|
|
115
|
-
Unchanged, because they do not name the harness: the session endpoint
|
|
116
|
-
vocabulary (`runtimeAuthority`, `runtimeState`/`runtimeError` in liveness,
|
|
117
|
-
`E_RUNTIME_ENDPOINT_UNKNOWN`, `E_RUNTIME_AUTHORITY_MISMATCH`,
|
|
118
|
-
`E_RUNTIME_QUIESCE_FAILED`); `capabilityRuntime`; the retirement baseline's
|
|
119
|
-
`runtime`; oats.okf's `harvest-runtime` setting; and the kernel's
|
|
120
|
-
"runtime-neutral" design. Hook stdin carries no `launch.runtime` (no 0.26 hook
|
|
121
|
-
emitter wrote it).
|
|
122
|
-
|
|
123
|
-
## Inspect, readiness and operation run on the workspace model (`operationsApi: 2`, `soulsApi: 2`, `readinessApi: 2`, OATS 0.26.0)
|
|
124
|
-
|
|
125
|
-
On a workspace deployment (an `oats-local.yaml` in reach of `--dir`), and for
|
|
126
|
-
any home whose `instance.json` records `modules`, these three commands read the
|
|
127
|
-
workspace model's own records and **never the classic config chain**. The probe
|
|
128
|
-
integers are the gate; there is no feature string. The probe's integer says
|
|
129
|
-
this kernel CAN answer the v2 shape. **Dispatch on the payload's own integer**.
|
|
130
|
-
There is no v1 shape any more (0.26.0 removed the classic chain's answers):
|
|
131
|
-
with no `oats-local.yaml` in reach and no `--home`, the three commands answer
|
|
132
|
-
`E_LOCAL_MISSING`; a `--home` whose `instance.json` records no `modules`
|
|
133
|
-
(spawned by an earlier kernel) answers `E_UNSUPPORTED_MODE` (re-spawn it from
|
|
134
|
-
the deployment); an unreadable `--home` answers `E_SESSION_UNKNOWN`.
|
|
135
|
-
|
|
136
|
-
**The captured/portable path was removed in 0.26.** A *captured home* (its `instance.json`
|
|
137
|
-
records `executionBinding`, `incarnationId` or `captured`: spawned through
|
|
138
|
-
0.24–0.25's `prepare` / `--deployment --resolution`) answers `E_UNSUPPORTED_MODE`
|
|
139
|
-
(`details: {home, captured: true}`) to these three commands, to `session
|
|
140
|
-
start|restart` and to its in-home commands; `oats retire` still works on it, and
|
|
141
|
-
its result's `warnings[]` names each capability whose retire hook did NOT run
|
|
142
|
-
(what it created is not revoked). `oats status --json` / `oats doctor --json`
|
|
143
|
-
name captured homes once, in `problems[]`, as `legacy-captured-home` `{instances,
|
|
144
|
-
homes, message}`. The captured selectors `--deployment`, `--resolution` and
|
|
145
|
-
`--artifact-set`, and an inherited `OATS_DEPLOYMENT`/`OATS_RESOLUTION`, are refused
|
|
146
|
-
by every command except `version` (`E_UNSUPPORTED_MODE`, `details.selector` or
|
|
147
|
-
`details.inherited`); `oats prepare` and `oats inspect --request` are removed
|
|
148
|
-
verbs (`E_UNKNOWN_COMMAND`, `details: {removed, replacement}`). The version
|
|
149
|
-
document no longer carries `capturedDispatchApi` / `capturedDispatchActions`.
|
|
150
|
-
|
|
151
|
-
| Command | Integer (probe and payload) | 0.25.x value |
|
|
44
|
+
- The Desktop accepts `desktopApi === 1` and a released `version` inside
|
|
45
|
+
`ACCEPT_RANGE` (`packages/desktop/cli-locator.mjs`); a prerelease is never
|
|
46
|
+
accepted. The real gate is the feature list; its minimum is
|
|
47
|
+
`packages-no-approval`.
|
|
48
|
+
- `harnesses` is what `--harness` accepts; `sessionBackends` what `--backend`
|
|
49
|
+
accepts. A host without the `harness` feature lists `runtimes` instead.
|
|
50
|
+
- `remote` is the routed surface: the commands `--server <id>` sends to a
|
|
51
|
+
registered server, plus `roster`. The Desktop checks the execution host's
|
|
52
|
+
probe before a routed mutation.
|
|
53
|
+
- In text mode the command prints `@awebai/oats <version> (desktop API v1)`.
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
| Feature | What it enables | API number |
|
|
152
58
|
|---|---|---|
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
`
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
59
|
+
| `retire-home` | `oats retire <instance> --home <abs>` | |
|
|
60
|
+
| `session-start`, `session-restart` | `oats session start/restart --home` | |
|
|
61
|
+
| `launch-config` | `oats launch-config …`; the selection flags on start and restart | |
|
|
62
|
+
| `schedule` | `oats schedule …` | `scheduleApi: 2` |
|
|
63
|
+
| `session-upload` | `oats session upload` (and the host's `session receive`) | |
|
|
64
|
+
| `operations` | `oats operation run`; `operations[]` in inspect | `operationsApi: 2` |
|
|
65
|
+
| `instance-git`, `instance-git-remote` | `oats instance git/diff`; the observation's `remote` | `instanceGitApi: 1` |
|
|
66
|
+
| `souls-declarations` | `souls[].declarations` in inspect | `soulsApi: 2` |
|
|
67
|
+
| `lifecycle-plans`, `retire-retention` | stop and retire plans and guarded applies; retire keeps a worktree | `lifecycleApi: 1` |
|
|
68
|
+
| `readiness` | `oats readiness` | `readinessApi: 2` |
|
|
69
|
+
| `spawn-preview`, `spawn-preview-2` | the no-write preview with a bound `decision` (gate on `-2`) | `spawnPreviewApi: 2` |
|
|
70
|
+
| `spawn-apply-2` | `--expect-decision` apply | `spawnApplyApi: 1` |
|
|
71
|
+
| `spawn-idempotency`, `spawn-idempotency-2` | `--idempotency-key`; recovery before placement (gate on `-2`) | `spawnApplyApi: 1` |
|
|
72
|
+
| `instance-events`, `instance-events-2` | the bounded events read (gate on `-2`) | `eventsApi: 2` |
|
|
73
|
+
| `schedule-history`, `schedule-read-2` | run history with identity and provenance (gate on `schedule-read-2`) | `scheduleHistoryApi: 3` |
|
|
74
|
+
| `workspace-v2` | `onboard`, `sync`, `package`, `workspace status`, `capabilities`, `souls` | `workspaceApi: 2` |
|
|
75
|
+
| `instance-modules` | `instance.json` `modules`/`providers`/`workspace`; module drift in status; preview `modules[]` | |
|
|
76
|
+
| `spawn-provider-payload` | `oats spawn … --provider <cap> <key>=<value>` | |
|
|
77
|
+
| `served-identity` | `decision.effective.providers`; the served `identity` in inspect and status | |
|
|
78
|
+
| `packages-no-approval` | no package approval anywhere | |
|
|
79
|
+
| `spawn-name` | `oats spawn --name <slug>` | |
|
|
80
|
+
| `settings-origins` | preview `settingsOrigins` | |
|
|
81
|
+
| `settings-declared` | `declares` on inspect capabilities and preview modules | |
|
|
82
|
+
| `capabilities-private` | `private` on `oats capabilities` rows | |
|
|
83
|
+
| `layers-from` | `layers.<slot>.from` in inspect | |
|
|
84
|
+
| `team-model-2` | every team field; `oats teams`, `oats soul teams` | `teamsApi: 1`, `soulTeamsApi: 1` (payload only) |
|
|
85
|
+
| `harness` | the harness names ([Harness input spellings](#the-harness-rename-feature-harness-oats-0270)) | |
|
|
86
|
+
| `package-souls` | package soul rows, `qualifiedName`, `packages[].souls` | |
|
|
87
|
+
| `triggers` | `oats trigger …` | `triggerApi: 1` (payload only) |
|
|
88
|
+
| `automations` | workspace triggers and schedules; `oats automations refresh` | `automationsApi: 1` |
|
|
89
|
+
| `desktop-facts` | the facts under [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290) | |
|
|
90
|
+
| `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)) | |
|
|
91
|
+
|
|
92
|
+
Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
|
|
93
|
+
`workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
|
|
94
|
+
`soulsApi: 1`, `teamsApi: 1`, `soulTeamsApi: 1`, `triggerApi: 1`.
|
|
95
|
+
|
|
96
|
+
**Gate on the probe, never by trying.** An older kernel can ignore an unknown
|
|
97
|
+
flag and act: without `lifecycle-plans`, `retire --plan` retires.
|
|
98
|
+
|
|
99
|
+
## The envelope and dispatch errors
|
|
100
|
+
|
|
101
|
+
Every `--json` command except those below prints exactly one JSON object on
|
|
102
|
+
stdout (progress goes to stderr):
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{"schemaVersion":1,"ok":true,"result":{}}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{"schemaVersion":1,"ok":false,"error":{"code":"E_BAD_ARGS","message":"--home needs an absolute instance home"}}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- Success exits 0, failure nonzero. `error.details` is present only when the
|
|
113
|
+
command has details.
|
|
114
|
+
- Either envelope may carry `warnings: [ … ]`, only when there is something
|
|
115
|
+
to say. The one warning is `deprecated-runtime-name`
|
|
116
|
+
([Harness input spellings](#the-harness-rename-feature-harness-oats-0270));
|
|
117
|
+
in text mode it is an `oats: warning: …` line on stderr.
|
|
118
|
+
- `<kernel command> --help --json` answers `{command, usage}` and runs
|
|
119
|
+
nothing.
|
|
120
|
+
|
|
121
|
+
| Not an envelope | stdout |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `oats version --json` | the probe |
|
|
124
|
+
| `oats status --json` | the [roster document](#the-roster-oats-status---json) |
|
|
125
|
+
| a first `oats retire --json`, and a deferred self-retire | the raw [retire receipt](#retire) |
|
|
126
|
+
|
|
127
|
+
<a id="flags"></a>
|
|
128
|
+
### Flag syntax
|
|
129
|
+
|
|
130
|
+
A kernel command reads `--flag=value` exactly as `--flag value` (the value is
|
|
131
|
+
everything after the first `=`). `E_BAD_ARGS` for an empty `--flag=`, a value
|
|
132
|
+
on a switch (`--yolo=false` never turns yolo on) and a value that is itself an
|
|
133
|
+
option (`--model=--yolo`). A capability command's own flags are forwarded as
|
|
134
|
+
typed; the kernel reads only its dispatch flag (`--soul`). There is no feature
|
|
135
|
+
string for this: to support older kernels, use the spaced form.
|
|
136
|
+
|
|
137
|
+
### Dispatch errors
|
|
138
|
+
|
|
139
|
+
| Code | When |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `E_UNKNOWN_COMMAND` | No kernel command or capability namespace matches; an unknown capability subcommand; a [removed verb](#removed-verbs-and-flags) (`details: {removed, replacement}`) |
|
|
142
|
+
| `E_CAPABILITY_INACTIVE` | In a home: the namespace's module is not one of the home's recorded capabilities |
|
|
143
|
+
| `E_CAPABILITY_BLOCKED` | In a home: the manifest claiming the namespace is not a workspace module copy of that home. There is no package trust gate |
|
|
144
|
+
| `E_CAPABILITY_BROKEN` | The manifest command is not a non-empty string, its script is missing or outside the module, or the dispatcher failed |
|
|
145
|
+
| `E_DUPLICATE_NAMESPACE` | Two modules claim the namespace |
|
|
146
|
+
| `E_CONFIG_BROKEN` | The home's `instance.json` or the deployment's configuration cannot be read |
|
|
147
|
+
| `E_LOCAL_MISSING` | No `oats-local.yaml` in reach of `--dir` or the working directory |
|
|
148
|
+
| `E_UNSUPPORTED_MODE` | A home or selector the kernel no longer runs (below) |
|
|
149
|
+
|
|
150
|
+
Capability dispatch inside a home uses the home's module copies; from a
|
|
151
|
+
deployment it resolves the module as `oats spawn --soul <x>` would and runs it
|
|
152
|
+
with the soul's merged payload. `oats <namespace> --help --json` answers
|
|
153
|
+
`{capability, namespace, command, commands, description, help}`.
|
|
154
|
+
|
|
155
|
+
`E_UNSUPPORTED_MODE` covers a home with no recorded `modules` (re-spawn it), a
|
|
156
|
+
captured home (`details: {home, captured: true}`), the selectors
|
|
157
|
+
`--deployment`, `--resolution` and `--artifact-set` (`details.selector`), and
|
|
158
|
+
an inherited `OATS_DEPLOYMENT` or `OATS_RESOLUTION` (`details.inherited`).
|
|
159
|
+
`oats version` and `oats retire` still work. `oats status --json` names such
|
|
160
|
+
homes in `problems[]`: `legacy-captured-home {code, instances, homes,
|
|
161
|
+
message}` and `legacy-local-agents {code, dirs, instances, message}`.
|
|
162
|
+
|
|
163
|
+
<a id="inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260"></a>
|
|
164
|
+
## Inspect, readiness and operation run
|
|
165
|
+
|
|
166
|
+
These answer about one **subject**: an instance home (`--home <abs>`) or a
|
|
167
|
+
soul of a deployment (`--soul <name> [--dir <d>]`).
|
|
168
|
+
|
|
169
|
+
| Command | Integer |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `oats inspect --json` | `operationsApi: 2`; each `souls[]` row `soulsApi: 2` |
|
|
172
|
+
| `oats readiness --json` | `readinessApi: 2` |
|
|
173
|
+
| `oats operation run --json` | `operationsApi: 2` |
|
|
174
|
+
|
|
175
|
+
**Addressing.**
|
|
176
|
+
- `--home` reads the home's `instance.json` and its module copies under
|
|
177
|
+
`<home>/.oats/modules/`. The home must be
|
|
178
|
+
`<deployment>/agents/<soul>/instances/<name>` with
|
|
179
|
+
`<deployment>/oats-local.yaml` exactly there, else `E_HOME_MISMATCH
|
|
180
|
+
{home, expected}`. A `--dir`, `--agents-root` or `--soul` that disagrees
|
|
181
|
+
with it is `E_HOME_MISMATCH`. An unreadable home is `E_SESSION_UNKNOWN`; a
|
|
182
|
+
home without `modules`, or a captured one, is `E_UNSUPPORTED_MODE`.
|
|
183
|
+
- `--soul` resolves the soul exactly as a spawn would (discovery, the soul's
|
|
184
|
+
`capabilities:` plus workspace defaults, the lock). No `oats-local.yaml` is
|
|
185
|
+
`E_LOCAL_MISSING`; an `--agents-root` other than `<deployment>/agents` is
|
|
186
|
+
`E_SOUL_UNKNOWN`.
|
|
187
|
+
- Neither is `E_BAD_ARGS`. `PI_AGENTS_ROOT` is ignored.
|
|
188
|
+
|
|
189
|
+
A module's origin (`from`) is `{kind: "member", repoKey, commit}` or `{kind:
|
|
190
|
+
"package", package, version, commit, integrity, repoKey}`.
|
|
191
|
+
|
|
192
|
+
<a id="oats-inspect---home-----soul----dir----json--operationsapi-2"></a>
|
|
193
|
+
### `oats inspect`
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
An instance subject, abridged:
|
|
190
200
|
|
|
191
201
|
```json
|
|
192
|
-
{"operationsApi":2,"kernel":"0.
|
|
193
|
-
"subject":{"kind":"instance","instance":"
|
|
194
|
-
"workspace":{"key":"github.com/
|
|
195
|
-
"souls":[{"soulsApi":2,"name":"
|
|
196
|
-
"
|
|
197
|
-
"declarations":{"requires":null,"defaults":null,"knowledge":{"owns":"
|
|
198
|
-
|
|
199
|
-
"declarationProblems":[],
|
|
200
|
-
"instructions":{"file":"/w/agents/release-manager/souls/461b9c24929c/AGENTS.md","text":"# release-manager\n…","truncated":false}}],
|
|
202
|
+
{"operationsApi":2,"kernel":"0.30.0",
|
|
203
|
+
"subject":{"kind":"instance","instance":"rm-1","home":"/w/agents/rm/instances/rm-1","soul":"rm"},
|
|
204
|
+
"workspace":{"key":"github.com/nw/agents","name":"northwind","deployment":"/w","commit":"66566512…","standalone":false},
|
|
205
|
+
"souls":[{"soulsApi":2,"name":"rm","repoKey":"github.com/nw/agents","commit":"66566512…","kind":null,"path":null,
|
|
206
|
+
"description":"Cuts releases.","work":"worktree","harness":null,"model":null,
|
|
207
|
+
"declarations":{"requires":null,"defaults":null,"knowledge":{"owns":"rm","reads":[]},"resources":null,"children":null,"capabilities":{"oats.okf":{"from":"package"}}},
|
|
208
|
+
"declarationProblems":[],"instructions":{"file":"/w/agents/rm/souls/66566512168e/AGENTS.md","text":"# rm\n","truncated":false}}],
|
|
201
209
|
"layers":{"knowledge":{"id":"oats.okf","from":"workspace"},"messaging":{"id":null,"from":null},"tasks":{"id":null,"from":null}},
|
|
202
|
-
"capabilities":[
|
|
203
|
-
{"
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
{"
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
|
|
214
|
-
"soulDir":"/w/agents/
|
|
215
|
-
"instructions":{"file":"/w/agents/
|
|
216
|
-
"sources":[{"source":"
|
|
217
|
-
"identity":null,
|
|
210
|
+
"capabilities":[{"id":"oats.okf","version":"2.1.3","layer":"knowledge","command":"okf",
|
|
211
|
+
"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"ab897841…","integrity":"sha256-bada35…","repoKey":"github.com/awebai/oats-okf"},
|
|
212
|
+
"composedFrom":null,"dir":"/w/agents/rm/instances/rm-1/.oats/modules/oats.okf","settings":{"owns":"rm","reads":[]},
|
|
213
|
+
"declares":["state-dir"],"compatibility":{"ok":true,"range":">=0.24.0","kernel":"0.30.0"},"missingRequires":[],
|
|
214
|
+
"operations":[{"name":"status","kind":"view","command":"status","context":"home","description":"Knowledge status","args":[],
|
|
215
|
+
"argv":["okf","status"],"available":true,"reason":null}]}],
|
|
216
|
+
"capabilitiesOff":[],
|
|
217
|
+
"teams":[{"label":"eng","team":null,"default":true,"from":"shared"},{"label":"mine","team":"mine:ana.aweb.ai","default":false,"from":"local"}],
|
|
218
|
+
"defaultTeam":{"label":"eng","team":null,"from":"soul"},"teamsSource":"live",
|
|
219
|
+
"recordedDefaultTeam":{"label":"eng","team":null,"from":"soul"},
|
|
220
|
+
"knowledge":{"provider":"oats.okf","version":"2.1.3","operations":[{"name":"status","kind":"view","available":true,"reason":null}]},
|
|
221
|
+
"instance":{"home":"/w/agents/rm/instances/rm-1","instance":"rm-1","agent":"rm","harness":"pi","model":null,"yolo":null,"launched":false,
|
|
222
|
+
"createdAt":"2026-09-28T10:08:01.281Z","resolution":"abacbdb5a7975098d77007c8","soulDir":"/w/agents/rm/souls/66566512168e",
|
|
223
|
+
"instructions":{"file":"/w/agents/rm/instances/rm-1/AGENTS.md","text":"…","truncated":false,
|
|
224
|
+
"sources":[{"source":"capability:oats.okf","file":"/w/agents/rm/instances/rm-1/.oats/modules/oats.okf/injects/okf.md"}]}},
|
|
225
|
+
"identity":null,
|
|
226
|
+
"problems":[]}
|
|
218
227
|
```
|
|
219
228
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
229
|
+
| Key | Meaning |
|
|
230
|
+
|---|---|
|
|
231
|
+
| `subject` | `{kind: "instance", instance, home, soul}` or `{kind: "soul", soul, repoKey, commit}` |
|
|
232
|
+
| `workspace` | `{key, name, deployment, commit, standalone}`; for a home, `name` is the recorded name (`null` if the spawn predates it) |
|
|
233
|
+
| `souls` | exactly the subject's soul |
|
|
234
|
+
| `layers` | `{knowledge, messaging, tasks}`, each `{id, from}` |
|
|
235
|
+
| `capabilities`, `capabilitiesOff` | the resolved modules (by id) and the ones the soul turned off |
|
|
236
|
+
| `teams`, `defaultTeam`, `teamsSource`, `recordedDefaultTeam` | [Where teams appear](#where-teams-appear); `recordedDefaultTeam` is home only |
|
|
237
|
+
| `knowledge` | `{provider, version, operations: [{name, kind, available, reason}]}` for the knowledge slot (`null`s and `[]` when empty) |
|
|
238
|
+
| `instance` | `null` for a soul; the home's facts above. `instructions.sources` lists each composed inject in order |
|
|
239
|
+
| `identity` | home only (absent for a soul): the served identity a messaging provider recorded, `{…, provider}`, or `null` |
|
|
240
|
+
| `problems` | below |
|
|
241
|
+
|
|
242
|
+
**Soul row** (`soulsApi: 2`). For a home it is read from the recorded
|
|
243
|
+
`soulDir` (`path` and `kind` are `null`); for a soul it is the member's current
|
|
244
|
+
definition (`kind` is `member` or `external`, `path` inside the member).
|
|
245
|
+
`declarations` is `{requires, defaults, knowledge, resources, children,
|
|
246
|
+
capabilities}`, each the soul.yaml section as written or `null`.
|
|
247
|
+
`instructions` is `{file, text, truncated}` of the soul's `AGENTS.md`, or
|
|
248
|
+
`null` before a spawn has copied the soul. `declarationProblems` holds
|
|
249
|
+
`soul-declarations-unreadable` when soul.yaml does not parse.
|
|
250
|
+
|
|
251
|
+
**Layers.** `id` is the capability filling the slot or `null`. `from`
|
|
252
|
+
(feature `layers-from`) is `"soul"` or `"workspace"`, `null` for an empty slot
|
|
253
|
+
or a home spawned before it was recorded.
|
|
254
|
+
|
|
255
|
+
**Capability rows.**
|
|
256
|
+
- `dir` is the home's module copy, `null` for a soul.
|
|
257
|
+
- `composedFrom` (feature `desktop-facts`): `"workspace"` or `"soul"` for a
|
|
258
|
+
soul subject; `null` for a home.
|
|
259
|
+
- `settings` is the merged provider payload; `declares` (feature
|
|
260
|
+
`settings-declared`) the manifest's setting keys, sorted.
|
|
261
|
+
- `compatibility` is `{ok, range, kernel}` (`range` is the manifest's
|
|
262
|
+
`compatibility.oats` or `null`). A soul's resolution refuses an incompatible
|
|
263
|
+
module, so `ok: false` appears only for a home.
|
|
264
|
+
- `missingRequires`: `{command, why, install}` for each manifest `requires`
|
|
265
|
+
command absent from `PATH`.
|
|
266
|
+
- `operations[]`: the declared operation (`name`, `kind`, `command`,
|
|
267
|
+
`context`, `description`, `args`) plus `argv`, `available` and `reason`
|
|
268
|
+
(for example a missing command, or a `context: "home"` operation on a soul).
|
|
269
|
+
|
|
270
|
+
**`capabilitiesOff[]`** (feature `desktop-facts`): `{id, off: true, from:
|
|
271
|
+
"soul", reason, slot?, overrides}`, sorted by id. `reason` is `"off"` (the
|
|
272
|
+
soul wrote `<id>: off`) or `"slot-none"` (the soul wrote `<slot>: none`,
|
|
273
|
+
emptying the slot the workspace filled with `<id>`). `overrides` is the layer
|
|
274
|
+
whose default was turned off (`"workspace"`). `[]` for a home.
|
|
275
|
+
|
|
276
|
+
**Problems:** `soul-declarations-unreadable`, `module-manifest-invalid`,
|
|
277
|
+
`module-missing {capability}`, `capability-incompatible {capability, range,
|
|
278
|
+
kernel}`, and for a home whose discovery failed, that error's `{code,
|
|
279
|
+
message}`.
|
|
280
|
+
|
|
281
|
+
A soul whose resolution is refused is an inspect error with the resolver's
|
|
282
|
+
code and details (`E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`,
|
|
283
|
+
`E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA`, `E_REQUIREMENT_INACTIVE`,
|
|
284
|
+
`E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE`); readiness reports the same condition
|
|
285
|
+
as an item. Soul lookup errors are those of [spawn](#spawn-errors).
|
|
286
|
+
|
|
287
|
+
<a id="oats-readiness---home-----soul----dir----policy---json--readinessapi-2"></a>
|
|
288
|
+
### `oats readiness`
|
|
289
|
+
|
|
290
|
+
```text
|
|
291
|
+
oats readiness (--home <abs> | --soul <name> [--dir <d>]) [--policy] --json
|
|
292
|
+
```
|
|
269
293
|
|
|
270
294
|
```json
|
|
271
295
|
{"readinessApi":2,
|
|
272
|
-
"subject":{"kind":"soul","soul":"
|
|
273
|
-
"selector":{"kind":"soul","soul":"
|
|
296
|
+
"subject":{"kind":"soul","soul":"rm","repoKey":"github.com/nw/agents","commit":"66566512…"},
|
|
297
|
+
"selector":{"kind":"soul","soul":"rm","agentsRoot":null,"dir":"/w"},
|
|
298
|
+
"at":"2026-09-28T10:07:57.549Z",
|
|
274
299
|
"checks":{
|
|
275
|
-
"installed":{"status":"pass","items":[
|
|
276
|
-
{"
|
|
277
|
-
|
|
300
|
+
"installed":{"status":"pass","items":[{"subject":"oats.okf","status":"pass","required":true,"reason":null,"producer":"workspace resolution",
|
|
301
|
+
"evidence":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"ab897841…","integrity":"sha256-bada35…","repoKey":"github.com/awebai/oats-okf"}},
|
|
302
|
+
"remedy":null,"capability":{"id":"oats.okf"}}]},
|
|
278
303
|
"configured":{"status":"not-applicable","items":[]},
|
|
279
|
-
"member":{"status":"pass","items":[
|
|
280
|
-
{"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
{"
|
|
284
|
-
|
|
285
|
-
"result":{"status":"needs-configuration","problems":[{"code":"needs-configuration","message":"setting state-dir is required (absolute host path)"}],"warnings":[]}}]}},
|
|
304
|
+
"member":{"status":"pass","items":[{"subject":"member github.com/nw/agents","status":"pass","required":true,"reason":null,
|
|
305
|
+
"producer":"workspace discovery","evidence":{"repoKey":"github.com/nw/agents","workspace":"github.com/nw/agents","commit":"66566512…"},"remedy":null}]},
|
|
306
|
+
"providers":{"status":"fail","items":[{"subject":"oats.okf","status":"fail","required":true,"reason":"setting state-dir is required",
|
|
307
|
+
"producer":"provider binding check","evidence":null,"remedy":null,
|
|
308
|
+
"result":{"status":"needs-configuration","problems":[{"code":"needs-configuration","message":"setting state-dir is required"}],"warnings":[]},
|
|
309
|
+
"capability":{"id":"oats.okf"}}]}},
|
|
286
310
|
"summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0,
|
|
287
311
|
"byCapability":[{"capability":{"id":"oats.okf"},"checks":{"installed":"pass","configured":"not-applicable","member":"not-applicable","providers":"fail"},"ownReady":false,"ready":false}],
|
|
288
312
|
"subjectBlockers":[]},
|
|
289
|
-
"notes":["
|
|
313
|
+
"notes":["ready means every REQUIRED check passes; it is never inferred from an empty set"]}
|
|
290
314
|
```
|
|
291
315
|
|
|
292
|
-
|
|
293
|
-
`
|
|
294
|
-
|
|
295
|
-
`
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
not-applicable
|
|
302
|
-
|
|
303
|
-
`
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
message}]
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
The reason is the first problem's message (`null` on pass).
|
|
349
|
-
`authorization-required` and `unavailable` were added in 0.26.0 (additive):
|
|
350
|
-
treat an unrecognized status as `unknown` and show `result` as sent.
|
|
351
|
-
- `warnings` is always present (`[]` when the provider sends none). It
|
|
352
|
-
**never changes the status** and is not counted in `summary`. A ready
|
|
353
|
-
binding can still say, for example, that end-to-end encryption is off:
|
|
354
|
-
show it next to the pass. A `warnings` that is not an array of `{code,
|
|
355
|
-
message}` strings makes the whole answer `unknown`, the same as a
|
|
356
|
-
malformed `problems`.
|
|
357
|
-
- A provider that cannot answer is `unknown`, with `item.problems` carrying
|
|
358
|
-
its error `{code, message}` and `result: null`. That covers:
|
|
359
|
-
- a refusal (`ok:false`, whose code is relayed);
|
|
360
|
-
- an invalid answer (`provider-unavailable`, see the wire below);
|
|
361
|
-
- a timeout;
|
|
362
|
-
- a module tree that cannot be made available.
|
|
363
|
-
- **One time budget per readiness read**: 60 s for all provider checks
|
|
364
|
-
together, and at most 30 s for each. Checks the budget does not reach are
|
|
365
|
-
not run; they are `unknown` with code `time-budget-exhausted`.
|
|
366
|
-
- For `--home` the check runs from the home's module copy, as the home's
|
|
367
|
-
hooks do. For `--soul` it runs from the module in the deployment's module
|
|
368
|
-
store (the tree `oats <ns> …` dispatch uses). A store tree is used only
|
|
369
|
-
while its content digest matches the digest verified when it was fetched
|
|
370
|
-
at the locked commit. A drifted tree is fetched again, and a fetch that
|
|
371
|
-
does not verify is `E_PACKAGE_INTEGRITY` (the item is `unknown` with that
|
|
372
|
-
code).
|
|
373
|
-
- A module without `binding` has no item; the check is `not-applicable`
|
|
374
|
-
when there are none.
|
|
375
|
-
- This check reads the provider; it does not bind. A spawn's fail-closed
|
|
376
|
-
hooks are unchanged.
|
|
377
|
-
- **Removed:** `trusted` and its `signature` block (declaring a package in
|
|
378
|
-
`packages:` is the trust decision). `--verify-signatures` answers
|
|
379
|
-
`E_BAD_ARGS`. `enrolled` is now `member`.
|
|
380
|
-
|
|
381
|
-
`--policy` is **kept**: it means the same without the chain. With `--home` it
|
|
382
|
-
is the instance's recorded, enforced policy (`instance.json` `policy`, plus
|
|
383
|
-
the recorded work mode). With `--soul` it is the soul's declaration
|
|
384
|
-
(`children.spawn`, `work`), `enforced: false`. The shape is unchanged:
|
|
316
|
+
- `selector` echoes the arguments byte-exact: `{kind: "soul", soul,
|
|
317
|
+
agentsRoot, dir}` or `{kind: "home", home, soul, agentsRoot}`.
|
|
318
|
+
- Four checks, each `{status, items}`. An item is `{subject, status,
|
|
319
|
+
required, reason, producer, evidence, remedy}`, plus `capability: {id}` on a
|
|
320
|
+
per-capability item and the keys named below. Statuses are `pass | fail |
|
|
321
|
+
unknown | not-applicable`.
|
|
322
|
+
- A check's status rolls up its required items (any `fail` → `fail`, else any
|
|
323
|
+
`unknown` → `unknown`, else `pass`; `not-applicable` with none required).
|
|
324
|
+
- `summary.ready` is true when every required item passes or is
|
|
325
|
+
not-applicable and at least one required item exists; the counts are over
|
|
326
|
+
required items. `byCapability[]` is `{capability, checks, ownReady,
|
|
327
|
+
ready}` (`ready` also needs no subject blocker). `subjectBlockers[]` is
|
|
328
|
+
`{check, subject, status}` for each failing required item not about a
|
|
329
|
+
capability. `notes` is prose.
|
|
330
|
+
|
|
331
|
+
**`installed`**, one item per module with `evidence: {from}`. `--home`
|
|
332
|
+
(producer `instance modules`): passes when the home's copy holds `oats.json`
|
|
333
|
+
(remedy on failure: spawn a new instance). `--soul` (producer `workspace
|
|
334
|
+
resolution`): each resolved module. A resolution refusal is one failing item about the soul
|
|
335
|
+
with `code`, the message as `reason`, the details as `evidence` and a remedy
|
|
336
|
+
naming `oats sync`; team refusals go under `configured` instead.
|
|
337
|
+
|
|
338
|
+
**`configured`.** Producer `capability manifest`: each manifest `requires`
|
|
339
|
+
command, `evidence: {command}`, the manifest's `install` hint as remedy.
|
|
340
|
+
Producer `team model`: the soul's [team readiness items](#team-readiness-items).
|
|
341
|
+
|
|
342
|
+
**`member`** (producer `workspace discovery`): the soul's repository is a
|
|
343
|
+
confirmed member (`evidence: {repoKey, workspace, commit}`); `fail` when not
|
|
344
|
+
(the remedy names `oats-membership.yaml`); `unknown` when the workspace could
|
|
345
|
+
not be read. External souls and standalone views are `not-applicable`,
|
|
346
|
+
`required: false` (a standalone item carries `evidence.standaloneReason`).
|
|
347
|
+
|
|
348
|
+
**`providers`** (producer `provider binding check`): for each module whose
|
|
349
|
+
manifest declares `binding`, the kernel runs its `binding.check` and relays
|
|
350
|
+
the answer as `item.result: {status, problems, warnings}`. The request,
|
|
351
|
+
environment and validation are in
|
|
352
|
+
[capabilities.md](capabilities.md#readiness-check-bindingcheck).
|
|
353
|
+
|
|
354
|
+
| `result.status` | item `status` |
|
|
355
|
+
|---|---|
|
|
356
|
+
| `ready` | `pass` |
|
|
357
|
+
| `needs-configuration`, `authorization-required` | `fail` |
|
|
358
|
+
| `unavailable` | `unknown` |
|
|
359
|
+
|
|
360
|
+
- `reason` is the first problem's message. `warnings` is always present and
|
|
361
|
+
never changes the status.
|
|
362
|
+
- A provider that cannot answer is `unknown` with `result: null` and
|
|
363
|
+
`item.problems: [{code, message}]`: its refusal code,
|
|
364
|
+
`provider-unavailable`, `resource-not-found` (the check executable is not a
|
|
365
|
+
regular file in the module, or a member module checked from `--soul`),
|
|
366
|
+
`provider-not-qualified`, or a module-store error.
|
|
367
|
+
- One budget per read: 60 s total, 30 s per check; an unreached check is
|
|
368
|
+
`unknown` with `time-budget-exhausted`.
|
|
369
|
+
|
|
370
|
+
**`--policy`** adds `policy` and a note:
|
|
385
371
|
|
|
386
372
|
```json
|
|
387
|
-
|
|
388
|
-
|
|
373
|
+
{"childSpawns":{"allowed":true,"enforced":true,"origin":{"kind":"default","detail":"no recorded policy: children allowed (pre-0.24.8 instance)"}},
|
|
374
|
+
"worktrees":{"allowed":false,"mode":"directory","enforced":true,"origin":{"kind":"work-mode","detail":"work: directory"}}}
|
|
389
375
|
```
|
|
390
376
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
377
|
+
With `--home` it is the recorded, enforced policy; with `--soul`, the soul's
|
|
378
|
+
declaration (`children.spawn`, `work`) with `enforced: false`. The spawn route
|
|
379
|
+
enforces `childSpawns`: a child spawn under a parent whose policy is off is
|
|
380
|
+
`E_CHILD_SPAWNS_DISABLED {parent, policy}`, before anything is created.
|
|
394
381
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
"action":{"kind":"readiness"}}}
|
|
382
|
+
### `oats operation run`
|
|
383
|
+
|
|
384
|
+
```text
|
|
385
|
+
oats operation run <layer>:<name> (--home <abs> | --soul <name> [--dir <d>]) [--arg k=v …] --json
|
|
400
386
|
```
|
|
401
387
|
|
|
402
388
|
```json
|
|
403
|
-
{"
|
|
404
|
-
"
|
|
389
|
+
{"operationsApi":2,"operation":"knowledge:status","capability":"oats.okf","version":"2.1.3","argv":["okf","status"],
|
|
390
|
+
"cwd":"/w/agents/rm/instances/rm-1","target":{"home":"/w/agents/rm/instances/rm-1","instance":"rm-1"},
|
|
391
|
+
"result":{"documents":[{"label":"Working state","kind":"markdown","text":"…"}]}}
|
|
405
392
|
```
|
|
406
393
|
|
|
407
|
-
|
|
408
|
-
- `
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
394
|
+
- `<layer>` is `knowledge`, `messaging` or `tasks`; `<name>` matches
|
|
395
|
+
`[a-z][a-z0-9-]*` (`E_BAD_ARGS` otherwise).
|
|
396
|
+
- The provider is the module filling the slot. `cwd` is the home for a
|
|
397
|
+
`context: "home"` operation, else the deployment; `target` is `{home,
|
|
398
|
+
instance}` or `null`.
|
|
399
|
+
- A launched `instance` or `home` named by the provider's result is repeated
|
|
400
|
+
at the top level; `stderr` appears when the provider wrote any.
|
|
401
|
+
- A `view` operation must answer `{documents: [{label, kind?, path?, text?}]}`
|
|
402
|
+
(`kind` `markdown` or `text`), else `E_OPERATION_RESULT`.
|
|
403
|
+
- Errors: `E_OPERATION_UNKNOWN`, `E_OPERATION_UNAVAILABLE` (empty slot, or a
|
|
404
|
+
home operation without `--home`), `E_CAPABILITY_REQUIRES`, `E_BAD_ARGS`
|
|
405
|
+
(undeclared or missing `--arg`), `E_CAPABILITY_BROKEN`. A provider's `ok:
|
|
406
|
+
false` is relayed with its code and `details: {exit, envelope,
|
|
407
|
+
unconfirmed?}`. `E_OPERATION_TIMEOUT` (240 s) and `E_OPERATION_RESULT` are
|
|
408
|
+
unconfirmed outcomes: `details: {exit, signal, unconfirmed: true,
|
|
409
|
+
envelope?, stderr?, cleanup?}`.
|
|
410
|
+
|
|
411
|
+
`--server <id>` routes inspect and operation run when the destination
|
|
412
|
+
advertises `operations`.
|
|
413
|
+
|
|
414
|
+
**Knowledge operations.** Discover a knowledge provider's operations from
|
|
415
|
+
inspect; the provider's version owns their result shapes. For oats.okf, see
|
|
416
|
+
[knowledge.md](knowledge.md#knowledge-operations).
|
|
417
|
+
|
|
418
|
+
<a id="workspace-model-workspaceapi-2"></a>
|
|
419
|
+
## Workspace
|
|
420
|
+
|
|
421
|
+
Feature `workspace-v2`, `workspaceApi: 2`. Model: [workspaces.md](workspaces.md).
|
|
422
|
+
|
|
423
|
+
- Each command needs an `oats-local.yaml` found walking up from `--dir`, else
|
|
424
|
+
`E_LOCAL_MISSING {dir, searched}`.
|
|
425
|
+
- The workspace is read over Git remotes with the operator's credentials,
|
|
426
|
+
never prompting: `E_REMOTE_UNREADABLE {url, reason: "auth" | "not-found" |
|
|
427
|
+
"network" | "timeout"}`.
|
|
428
|
+
- There is no package approval: declaring a package is the trust decision.
|
|
429
|
+
No payload carries `approvalNeeded`, `approval` or `approved`.
|
|
430
|
+
- A **standalone view** is a member repository whose workspace is not read
|
|
431
|
+
(`standalone:` in `oats-local.yaml`, or an unreadable host): its own souls
|
|
432
|
+
plus `oats.core`. Documents then carry `standalone: true`; otherwise the key
|
|
433
|
+
is absent.
|
|
434
|
+
|
|
435
|
+
### Removed verbs and flags
|
|
436
|
+
|
|
437
|
+
A removed verb answers, before any namespace can claim it:
|
|
436
438
|
|
|
437
439
|
```json
|
|
438
|
-
{"
|
|
439
|
-
"cwd":"/w/agents/release-manager/instances/release-manager-x",
|
|
440
|
-
"target":{"home":"/w/agents/release-manager/instances/release-manager-x","instance":"release-manager-x"},
|
|
441
|
-
"result":{"documents":[{"label":"Working state (STATE.md)","kind":"markdown","text":"…"}]}}
|
|
440
|
+
{"schemaVersion":1,"ok":false,"error":{"code":"E_UNKNOWN_COMMAND","message":"unknown command \"install\" — removed by the workspace model v2; use oats sync","details":{"removed":"install","replacement":"oats sync"}}}
|
|
442
441
|
```
|
|
443
442
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
443
|
+
| Removed | Code | Replacement |
|
|
444
|
+
|---|---|---|
|
|
445
|
+
| `install`, `restore` | `E_UNKNOWN_COMMAND` | `oats sync` |
|
|
446
|
+
| `init` | `E_UNKNOWN_COMMAND` | `oats-local.yaml` + `oats sync` |
|
|
447
|
+
| `use` | `E_UNKNOWN_COMMAND` | soul.yaml `capabilities:` + workspace defaults |
|
|
448
|
+
| `trust` | `E_UNKNOWN_COMMAND` | declaring the package in `packages:` |
|
|
449
|
+
| `list` | `E_UNKNOWN_COMMAND` | `oats workspace status` / `oats capabilities` |
|
|
450
|
+
| `catalog` | `E_UNKNOWN_COMMAND` | `oats package add <id> <version>` |
|
|
451
|
+
| `remove` | `E_UNKNOWN_COMMAND` | `oats package remove <id>` |
|
|
452
|
+
| `migrate` | `E_UNKNOWN_COMMAND` | a rebuild |
|
|
453
|
+
| `config` | `E_UNKNOWN_COMMAND` | `oats-local.yaml` and `oats-workspace.yaml` |
|
|
454
|
+
| `create`, `type` | `E_UNKNOWN_COMMAND` | the soul's `soul.yaml` in its member repository |
|
|
455
|
+
| `inject` | `E_UNKNOWN_COMMAND` | the capability's inject in its member repository |
|
|
456
|
+
| `prepare` | `E_UNKNOWN_COMMAND` | `oats onboard` / `oats sync`; `oats spawn <soul> --preview` |
|
|
457
|
+
| `inspect --request` | `E_UNKNOWN_COMMAND` | `oats onboard / oats sync; oats spawn --preview` |
|
|
458
|
+
| `session recompose` | `E_UNKNOWN_COMMAND` (no details) | a re-spawn |
|
|
459
|
+
| `readiness --verify-signatures` | `E_BAD_ARGS` | none |
|
|
460
|
+
| `sync --approve`, `onboard --approve` | `E_BAD_ARGS` (`details: {flag}`) | none |
|
|
461
|
+
| `status --team` | `E_BAD_ARGS` | `oats status` in the deployment |
|
|
462
|
+
| `spawn --instance` | `E_BAD_ARGS` | `--purpose` or `--name` |
|
|
463
|
+
| `spawn --ephemeral`, `--instructions-file`, `--def-file` | `E_BAD_ARGS` | a soul in a member repository |
|
|
464
|
+
| `session … --native-record` | `E_BAD_ARGS` | none |
|
|
465
|
+
|
|
466
|
+
`details.replacement` is the kernel's prose (shortened above): show it, do not
|
|
467
|
+
parse it.
|
|
468
|
+
|
|
469
|
+
<a id="oats-onboard-onboardapi-2"></a>
|
|
470
|
+
### `oats onboard`
|
|
448
471
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
- the receipt rules and the `E_OPERATION_TIMEOUT` / `E_OPERATION_RESULT`
|
|
453
|
-
unconfirmed outcomes.
|
|
472
|
+
```text
|
|
473
|
+
oats onboard [<dir>] --workspace <repo ref> [--json]
|
|
474
|
+
```
|
|
454
475
|
|
|
455
|
-
|
|
456
|
-
`
|
|
476
|
+
Writes `<dir>/oats-local.yaml` (`{schemaVersion: 2, workspace: <ref>}`),
|
|
477
|
+
creates `<dir>/agents/`, then runs the `oats sync` body. It creates no soul
|
|
478
|
+
and spawns nothing. `<dir>` defaults to the working directory (`--dir` is the
|
|
479
|
+
same argument, given once). The ref is checked before anything is written.
|
|
457
480
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
481
|
+
```json
|
|
482
|
+
{"onboardApi":2,"local":"/w/oats-local.yaml","dir":"/w","agents":"/w/agents","lock":"/w/oats-lock.json",
|
|
483
|
+
"sync":{"syncApi":1,"workspace":{"name":"acme","key":"github.com/acme/agents"},"members":[],"packages":[],"changes":[],"problems":[],"warnings":[]},
|
|
484
|
+
"hosting":{"host":"github.com/acme/agents","hostIsMember":true,"rule":"If any member is private, host oats-workspace.yaml in a private repo …"},
|
|
485
|
+
"next":{"clone":[{"key":"github.com/acme/agents","name":"agents","url":"https://github.com/acme/agents.git","dir":"/w/agents-repo","present":false,"host":true},
|
|
486
|
+
{"key":"github.com/acme/platform","name":"platform","url":"https://github.com/acme/platform.git","dir":"/w/platform","present":true,"host":false}],
|
|
487
|
+
"spawn":"oats spawn oats-operator-expert --dir /w",
|
|
488
|
+
"souls":["oats-operator-expert","platform-engineer","release-manager"]}}
|
|
489
|
+
```
|
|
461
490
|
|
|
462
|
-
|
|
491
|
+
- `sync` is the full [`oats sync`](#oats-sync) report (abridged above).
|
|
492
|
+
`standalone: true` is present on a standalone view.
|
|
493
|
+
- `hosting`: whether the host is itself a member, and the hosting rule (the
|
|
494
|
+
kernel cannot see forge visibility).
|
|
495
|
+
- `next.clone[]`: one row per confirmed member (or, standalone, the
|
|
496
|
+
repository itself): `{key, name, url, dir, present, host}`. `dir` is the
|
|
497
|
+
existing clone (the `clones:` entry, else `<dir>/<name>`), else where to
|
|
498
|
+
clone it: `<dir>/<name>`, or `<dir>/agents-repo` for a member named
|
|
499
|
+
`agents`. `url` is `null` when unknown.
|
|
500
|
+
- `next.spawn` is `oats spawn oats-operator-expert --dir <dir>` when the
|
|
501
|
+
workspace has a soul by that name, else `null`. `next.souls` is the first
|
|
502
|
+
three soul names, sorted.
|
|
503
|
+
- Errors: `E_BAD_ARGS` (usage, no `--workspace`, a repeated directory, an
|
|
504
|
+
unknown flag), `E_REPO_REF`, `E_ALREADY_ONBOARDED {local, dir}` (this
|
|
505
|
+
directory already has `oats-local.yaml`), `E_ONBOARD_FAILED {dir}`. A
|
|
506
|
+
failure before the workspace was read removes what onboarding created and
|
|
507
|
+
carries `details: {…, dir, rolledBack: true}`; a later failure keeps the
|
|
508
|
+
files and carries `details: {…, dir, local}`.
|
|
509
|
+
|
|
510
|
+
### `oats sync`
|
|
463
511
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
rows; the soul's declarations are in `oats souls --json`.
|
|
512
|
+
```text
|
|
513
|
+
oats sync [--dir <d>] --json
|
|
514
|
+
```
|
|
468
515
|
|
|
469
|
-
|
|
516
|
+
Discovers the workspace, confirms membership, resolves `packages:`, writes
|
|
517
|
+
`oats-lock.json` (lockfileVersion 3), takes the automations snapshot and
|
|
518
|
+
reports. It creates `agents/` if missing.
|
|
470
519
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
520
|
+
```json
|
|
521
|
+
{"syncApi":1,"automations":{"triggers":3,"schedules":2,"problems":1,"takenAt":"2026-09-26T19:58:09.281Z"},
|
|
522
|
+
"workspace":{"name":"acme","key":"github.com/acme/agents","url":"https://github.com/acme/agents.git","commit":"45b86f64…",
|
|
523
|
+
"observedAt":"2026-09-26T19:58:07.810Z","local":"/w/oats-local.yaml","lock":"/w/oats-lock.json"},
|
|
524
|
+
"members":[{"key":"github.com/acme/tools","name":"tools","commit":"19839f9e…","confirmed":true,"status":"confirmed","detail":null,
|
|
525
|
+
"souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
|
|
526
|
+
{"key":"github.com/acme/billing","name":"billing","commit":"8f2c0d1e…","confirmed":false,"status":"no-backlink",
|
|
527
|
+
"detail":"github.com/acme/billing has no oats-membership.yaml","souls":[],"capabilities":[],"publishes":null}],
|
|
528
|
+
"packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"ab897841…","integrity":"sha256-bada35…",
|
|
529
|
+
"capabilities":["oats.okf"],"souls":["knowledge-maintainer"]}],
|
|
530
|
+
"changes":[{"id":"oats.okf","from":null,"to":"2.1.3","commit":"ab897841…"}],
|
|
531
|
+
"problems":[],"warnings":[]}
|
|
532
|
+
```
|
|
474
533
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
534
|
+
- `automations`: counts from the snapshot this sync took.
|
|
535
|
+
- `workspace.name` is `standalone:<repo>` on a standalone view.
|
|
536
|
+
- `members[]`: `status` is `confirmed | not-listed | no-backlink |
|
|
537
|
+
backlink-elsewhere | cannot-read`, `detail` explains an unconfirmed row.
|
|
538
|
+
`publishes` reports a member's `oats-package/` (informational).
|
|
539
|
+
- `packages[]` are the lock rows; `souls` (feature `package-souls`) the
|
|
540
|
+
package souls it records. `changes[]`: `{id, from, to, commit}`, `to: null`
|
|
541
|
+
when a package left.
|
|
542
|
+
- `problems[]`: `{code, path, message, repoKey?, …}`, for example
|
|
543
|
+
`E_WORKSPACE_SCHEMA`, `E_REMOTE_*`, `E_SOUL_AMBIGUOUS` (colliding package
|
|
544
|
+
souls), `E_PACKAGE_MISSING` (a standalone catalog gap: `{code, id, reason:
|
|
545
|
+
"no-catalog", catalog, path, message}`), `E_AUTOMATION_SCHEMA` and
|
|
546
|
+
`E_AUTOMATION_DUPLICATE` (with `kind`). A problem never aborts the sync.
|
|
547
|
+
- `warnings[]`: `soul-private-ignored {code, soul, repoKey, path, message}`
|
|
548
|
+
for a soul.yaml still carrying `private`.
|
|
549
|
+
- Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA {path, problems}`,
|
|
550
|
+
`E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING`,
|
|
551
|
+
`E_PACKAGE_INTEGRITY`, `E_PACKAGE_MANIFEST`, `E_REPO_REF`, `E_BAD_ARGS`.
|
|
552
|
+
|
|
553
|
+
### `oats package add` and `remove`
|
|
480
554
|
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
"recorded":{"branch":"feat/x","repo":"/abs/repo","drift":true},
|
|
485
|
-
"upstream":{"ref":"origin/feat/y","ahead":1,"behind":0},
|
|
486
|
-
"base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"<oid>","ahead":2,"behind":0},
|
|
487
|
-
"remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream|origin"},
|
|
488
|
-
"summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
|
|
489
|
-
"files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt","additions":84,"deletions":3,"binary":false}],
|
|
490
|
-
"notes":[]}
|
|
555
|
+
```text
|
|
556
|
+
oats package add <id> <version | git:<repo>@<ref>> [--dir <d>] --json
|
|
557
|
+
oats package remove <id> [--dir <d>] --json
|
|
491
558
|
```
|
|
492
559
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
is against the merge-base with the default branch (`origin/HEAD`, else a
|
|
496
|
-
well-known name; `source` says which); none found → all `null` plus a note.
|
|
497
|
-
- Status is porcelain v2, NUL-delimited: renames/copies carry `origPath`;
|
|
498
|
-
paths with spaces/newlines are intact. `kind` ∈ changed | renamed | copied |
|
|
499
|
-
unmerged | untracked. Ignored files are not listed.
|
|
500
|
-
- `files[].id` is **opaque**, minted under (`revision`, `indexRevision`). It is
|
|
501
|
-
the only way to ask for a diff.
|
|
502
|
-
- `files[].additions`, `deletions` and `binary` (0.29.1, additive;
|
|
503
|
-
`instanceGitApi` stays 1) are each file's line counts.
|
|
504
|
-
- They come from one `git diff <revision> --numstat -z -M` per observation:
|
|
505
|
-
the working tree against the observed commit, **staged and unstaged
|
|
506
|
-
combined**. That is the baseline of the status letters and of
|
|
507
|
-
`oats instance diff`.
|
|
508
|
-
- An unborn tree counts against the empty tree. A rename counts on its new
|
|
509
|
-
`path`.
|
|
510
|
-
- A binary file is `additions: null, deletions: null, binary: true`. An
|
|
511
|
-
untracked file (no baseline; its contents are not read) and a submodule
|
|
512
|
-
are all `null`.
|
|
513
|
-
- If the count itself fails, every entry is `null` and `notes` says line
|
|
514
|
-
counts are unavailable. `null` means unknown, never zero.
|
|
515
|
-
- `remote` (0.24.8+): the branch's configured remote (`source: branch-upstream`),
|
|
516
|
-
else `origin`, else `null` — never invented. `host`/`path` are **parsed** from
|
|
517
|
-
the URL (ssh/https forms; `.git` stripped) so an ADE can choose a forge backend
|
|
518
|
-
and a `owner/repo` **without running Git**; a local path has `host: null`. No
|
|
519
|
-
network, no forge knowledge in the kernel.
|
|
520
|
-
|
|
521
|
-
`oats instance diff <instance> --file <id> --revision <rev> [--index-revision <idx>] --json`
|
|
522
|
-
returns a bounded unified diff:
|
|
560
|
+
Edits `packages:` only when `oats-workspace.yaml` is tracked by the checkout
|
|
561
|
+
found from `--dir`; otherwise it reports the change to make.
|
|
523
562
|
|
|
524
563
|
```json
|
|
525
|
-
{"
|
|
526
|
-
"against":"<captured revision oid>","binary":false,"bytes":2683,"truncated":false,"limit":262144,"patch":"diff --git …",
|
|
527
|
-
"readOnly":{"helpers":"disabled","optionalLocks":"off","objectsWritten":0}}
|
|
564
|
+
{"action":"add","id":"oats.aweb","value":"v1.17.1","previous":null,"edited":true,"file":"/w/agents-repo/oats-workspace.yaml"}
|
|
528
565
|
```
|
|
529
566
|
|
|
530
|
-
- `against` is the **captured revision oid** for tracked changes (working tree
|
|
531
|
-
vs that exact commit, index included — never the moving `HEAD`) and `empty`
|
|
532
|
-
for untracked files. Binary → `binary: true`, empty patch. Over 256 KiB →
|
|
533
|
-
`truncated: true` at the byte limit.
|
|
534
|
-
- **Read-only, helper-free, consistent across the read** (`readOnly` echoes
|
|
535
|
-
it): the observed tree may carry a hostile repo config, so external diff,
|
|
536
|
-
textconv, fsmonitor and hooks are disabled and the caller's Git environment
|
|
537
|
-
and global config are not inherited; `--no-optional-locks` means no index
|
|
538
|
-
refresh and no object is written (`ls-files --stage` hash, not `write-tree`).
|
|
539
|
-
After producing the patch the CLI re-checks HEAD, index and the file's own
|
|
540
|
-
content against the observation and refuses `E_STALE_OBSERVATION` if any
|
|
541
|
-
moved mid-read — the result is never internally inconsistent.
|
|
542
|
-
- If HEAD or the index moved since the id was minted, or the id is not in the
|
|
543
|
-
current observation, the CLI **refuses** with `E_STALE_OBSERVATION` and
|
|
544
|
-
attaches the current `observation` in `error.details` — re-observe, never
|
|
545
|
-
render a diff against a tree that is not the one on screen. A path in
|
|
546
|
-
`--file` is `E_BAD_ARGS`.
|
|
547
|
-
|
|
548
|
-
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.
|
|
549
|
-
|
|
550
|
-
## Instance events (`oats instance events`, `eventsApi: 1` → **2**, OATS 0.24.8+) — K7
|
|
551
|
-
|
|
552
|
-
Typed lifecycle events per instance, **written by the kernel action that made
|
|
553
|
-
them true**, with the receipt it produced. Nothing is inferred from
|
|
554
|
-
transcripts, TASK/STATE files or prose. Append-only, two logs: `<home>/.oats-events.jsonl`
|
|
555
|
-
and `<workspace>/.agents/events/<agent>--<instance>.jsonl` (survives the
|
|
556
|
-
home's removal, so a retired instance's `retired` event is still readable).
|
|
557
|
-
|
|
558
|
-
`oats instance events <instance> [--limit <n>] [--since <iso>] [--home <abs>] [--dir <d>] --json`
|
|
559
|
-
|
|
560
567
|
```json
|
|
561
|
-
{"
|
|
562
|
-
"events":[{"eventsApi":1,"at":"<iso>","instance":"dev-1","home":"/abs/home","producer":"kernel","kind":"spawned","data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","harness":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}},
|
|
563
|
-
{"…":"launched | restarted | stopped | stop-refused | retire-planned | worktree-retained | worktree-removed | branch-deleted | retired | child-spawn-refused"}],
|
|
564
|
-
"lastEvent":{"kind":"stopped","at":"<iso>","producer":"kernel"},
|
|
565
|
-
"waitingOnYou":null,
|
|
566
|
-
"notes":["…"]}
|
|
568
|
+
{"action":"add","id":"oats.aweb","value":"v1.17.1","edited":false,"file":null,"line":"packages:\n oats.aweb: v1.17.1","hint":"oats-workspace.yaml is not in this checkout; commit the change in the workspace repo, then `oats sync`"}
|
|
567
569
|
```
|
|
568
570
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
true` with a `reason`). `null` means *unknown*, not "not waiting". Today no
|
|
574
|
-
kernel path claims it; the Active overview keeps rendering unknown until a
|
|
575
|
-
producer (a messaging or review capability) does.
|
|
576
|
-
- Window is bounded (`--limit`, default 200; `truncated` says so). A torn line
|
|
577
|
-
appears as `kind: "unreadable"` rather than vanishing.
|
|
578
|
-
|
|
579
|
-
### Events API 2 (`eventsApi: 2`, feature `instance-events-2`, OATS 0.24.12+) — K7b
|
|
580
|
-
|
|
581
|
-
Gate a Desktop read on **both** `eventsApi === 2` and `"instance-events-2"` in
|
|
582
|
-
`features[]`. API 1 is not a sufficient fence for a bounded read: its reader
|
|
583
|
-
opened and read a source whole, followed symlinks, kept foreign rows and lost
|
|
584
|
-
torn lines and cleared claims silently. API 2:
|
|
585
|
-
|
|
586
|
-
- **Bounded, descriptor-safe read.** Each source (`home` =
|
|
587
|
-
`<home>/.oats-events.jsonl`, `workspace` = `<ws>/.agents/events/<agent>--<instance>.jsonl`)
|
|
588
|
-
is `lstat`ed first; anything but a regular file is **refused unopened**.
|
|
589
|
-
The open itself is `O_RDONLY|O_NOFOLLOW|O_NONBLOCK` and the descriptor is
|
|
590
|
-
`fstat`ed: it must be a regular file with the same device+inode lstat saw
|
|
591
|
-
(closes the lstat→open swap). At most the last 4 MiB is read by descriptor.
|
|
592
|
-
**Canonical source shape**: `{path: "home"|"workspace", status: "ok"|"absent"|"refused"|"tail", bytes}`
|
|
593
|
-
— `"tail"` means only the last 4 MiB was read (partial first line dropped);
|
|
594
|
-
there is no separate `tail` boolean.
|
|
595
|
-
- **Row fields.** `incarnation` is the writing home's `instance.json.createdAt`
|
|
596
|
-
(ISO) or `null` for rows written before the tag; the result's top-level
|
|
597
|
-
`incarnation` is the current home's `createdAt` or `null` if unreadable. A
|
|
598
|
-
row with `incarnation: null` never matches the current incarnation, so it
|
|
599
|
-
cannot contribute a current waiting claim. **An unknown current incarnation
|
|
600
|
-
(top-level `incarnation: null`) admits NO claim**: `waitingOnYou: null`,
|
|
601
|
-
`waitingClaims: []`, rows still returned as history. A consumer must refuse
|
|
602
|
-
a null-incarnation response that nevertheless carries claims. Dedup identity is
|
|
603
|
-
`producer|at|kind|incarnation|data`.
|
|
604
|
-
- **`waitingClaims[]` row shape**: `{producer: string, waiting: boolean, since: ISO, reason: string|null}`
|
|
605
|
-
— one row per producer with a claim in the current incarnation, INCLUDING
|
|
606
|
-
cleared ones (`waiting: false`, `reason: null`, `since` = the clearing row's
|
|
607
|
-
`at`). `waitingOnYou` = the newest `waiting: true` row or `null`.
|
|
608
|
-
- **Address history.** `--home <abs>` must be a home of exactly `<instance>` under
|
|
609
|
-
the scope (`E_HOME_MISMATCH` otherwise, like K1). Rows whose `instance`/`home`
|
|
610
|
-
are not the admitted address are dropped and counted (`integrity.foreignRows`).
|
|
611
|
-
Every row carries `incarnation` (the writing home's `instance.json.createdAt`);
|
|
612
|
-
the result echoes the current home's `incarnation`. Rows tagged with an earlier
|
|
613
|
-
incarnation ARE returned — they are this address's history — so a consumer can
|
|
614
|
-
label them "earlier instance at this address". No current home → no read
|
|
615
|
-
(archived access is a separate contract).
|
|
616
|
-
- **`waitingOnYou` is a producer STATE for the current incarnation**, decided per
|
|
617
|
-
producer by that producer's latest row that carries the field: an explicit
|
|
618
|
-
`false` clears, a row without the field does not; earlier incarnations never
|
|
619
|
-
contribute; computed over the FULL admitted read (a `--limit` window cannot
|
|
620
|
-
hide a clear). `waitingClaims[]` lists every producer's current claim
|
|
621
|
-
(`{producer, waiting, since, reason}`); `waitingOnYou` is the newest positive.
|
|
622
|
-
Still `null` today — no producer emits it.
|
|
623
|
-
- **Provenance and corruption never disappear.** Dedup is by
|
|
624
|
-
`producer|at|kind|incarnation|data` (the same facts from two producers are two
|
|
625
|
-
rows). `integrity.unreadableRows` counts torn/invalid lines regardless of
|
|
626
|
-
`--since` or the window. `count` = admitted rows after `--since`, `returned` =
|
|
627
|
-
the window, `truncated` = window cut OR any source read as a tail.
|
|
571
|
+
`remove` answers `value: null` (and `line: null` when not tracked). An id that
|
|
572
|
+
is not declared is `E_PACKAGE_MISSING {id, …}`; an untracked `remove`
|
|
573
|
+
discovers the workspace over the network to check. Other errors: `E_USAGE`,
|
|
574
|
+
`E_WORKSPACE_SCHEMA` (a bad id or value), `E_REPO_REF`.
|
|
628
575
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
"lastEvent":{"kind":"launched","at":"<iso>","producer":"kernel","incarnation":"<iso>"},
|
|
634
|
-
"waitingOnYou":null,"waitingClaims":[],"notes":[...]}
|
|
576
|
+
### `oats workspace status`
|
|
577
|
+
|
|
578
|
+
```text
|
|
579
|
+
oats workspace status [--dir <d>] --json
|
|
635
580
|
```
|
|
636
581
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
## Schedule run history (`scheduleApi: 2`, `scheduleHistoryApi: 2` → **3**, OATS 0.24.8+) — K8
|
|
640
|
-
|
|
641
|
-
`oats schedule show|list --json` entries gain **`recentRuns`**: the last 50
|
|
642
|
-
settled runs (newest first) — every `lastRun` the scheduler recorded once its
|
|
643
|
-
outcome settled (`ended | stopped | blocked | invalid | delivered | skipped |
|
|
644
|
-
unknown …`, never `active`/`starting`), exactly as the producer wrote it,
|
|
645
|
-
deduplicated per run. Where the run launched or targeted an instance, a
|
|
646
|
-
`transcript: {instance, home, kind: "session"}` pointer says which home's
|
|
647
|
-
session to open (the existing `oats session` surface); the kernel does not
|
|
648
|
-
copy transcripts. `nextRun`/`lastRun`/`executionStatus` are unchanged. The
|
|
649
|
-
Schedules view (frame 08) renders `recentRuns` as the recent-runs list and the
|
|
650
|
-
transcript pointer as the handoff (definition fields are untouched by this
|
|
651
|
-
addition). A stored captured definition (removed in 0.26) lists as `invalid`.
|
|
652
|
-
|
|
653
|
-
### History API 3 (`scheduleHistoryApi: 3`, feature `schedule-read-2`, OATS 0.24.13+) — K8b
|
|
654
|
-
|
|
655
|
-
Gate a Desktop history read on **both** `scheduleHistoryApi === 3` and
|
|
656
|
-
`"schedule-read-2"` in `features[]` (`scheduleApi` stays 2 — mutation verbs are
|
|
657
|
-
unchanged). API 2's reader keyed runs by outcome, read state files whole and
|
|
658
|
-
unchecked, echoed a stored `definition.id` without checking it, and named a
|
|
659
|
-
`transcript` that no reader backs. API 3:
|
|
660
|
-
|
|
661
|
-
- **Run identity is time, not outcome.** `runId = sha256(scheduledFor|startedAt|attemptId)[0:24]`.
|
|
662
|
-
A run's later facts update its one row; `transitions[]` keeps the outcome
|
|
663
|
-
sequence (`["started","unknown","ended"]`); `settled: boolean`
|
|
664
|
-
(`pending: true` is never settled); `recordedAt`. Pre-API-3 rows are returned
|
|
665
|
-
with `runId: null, legacy: true, settled: null, transitions: null` and are
|
|
666
|
-
never merged. `lastRun` carries the same `runId` as its history row.
|
|
667
|
-
- **Bounded, descriptor-safe state.** `oats-schedules.json` and
|
|
668
|
-
`.agents/schedules/state.json` are `lstat`ed (regular file only), opened
|
|
669
|
-
`O_NOFOLLOW|O_NONBLOCK`, `fstat`-verified (dev+ino), and read whole **only
|
|
670
|
-
within a 1 MiB budget** — over budget is a typed `E_SCHEDULE_STATE_OVERSIZE`
|
|
671
|
-
refusal with `details.source`, never truncated JSON. `list`/`show` carry
|
|
672
|
-
`integrity: {sources: [{path: "definitions"|"state", status: "ok"|"absent"|"refused"|"oversize"|"corrupt", bytes}]}`.
|
|
673
|
-
History is capped at 50 rows **at read** (`history: {status, stored, truncated}`);
|
|
674
|
-
one job's corrupt history (`history.status: "corrupt"`, `recentRuns: []`) or
|
|
675
|
-
bad identity (`unreadable: {code, message}`) never fails the other jobs in `list`.
|
|
676
|
-
- **Subject truth.** `list` and `show` echo `scope` (the resolved schedule-owning
|
|
677
|
-
workspace) and canonical `id`. A definition whose own `id` differs from its
|
|
678
|
-
key → `E_SCHEDULE_IDENTITY` (`details.key`, `details.declared`). IDs must match
|
|
679
|
-
`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$` (`E_BAD_ARGS` otherwise, before any read).
|
|
680
|
-
- **Session provenance, never a transcript.** The `transcript` key is gone.
|
|
681
|
-
Each run (and `lastRun`) carries
|
|
682
|
-
`session: {instance: string|null, home: string|null, incarnation: string|null, server: string|null, delivery: "launched"|"delivered-active"|"none"}`
|
|
683
|
-
— facts the recorder had at write time (an active-session wake names the
|
|
684
|
-
instance only when the input result did; `incarnation` = the home's
|
|
685
|
-
`instance.json.createdAt` at record time; `server` = the answering peer for a
|
|
686
|
-
remote command). **There is no reader behind this block**: a consumer renders
|
|
687
|
-
provenance and a precise unavailable reason. A read-only transcript verb is a
|
|
688
|
-
separate seam (K12), not implied by this API.
|
|
582
|
+
Read-only (it writes no lock):
|
|
689
583
|
|
|
584
|
+
```json
|
|
585
|
+
{"workspaceStatusApi":1,
|
|
586
|
+
"workspace":{"name":"northwind","key":"github.com/nw/agents","url":"https://github.com/nw/agents.git","commit":"66566512…",
|
|
587
|
+
"observedAt":"2026-09-28T10:07:51.783Z","local":"/w/oats-local.yaml",
|
|
588
|
+
"teams":[{"label":"eng","team":null,"description":"Platform engineering"},{"label":"oats","team":"oats:oats.aweb.ai","description":"The OATS project"}],
|
|
589
|
+
"file":{"path":"oats-workspace.yaml","url":"https://github.com/nw/agents/blob/66566512…/oats-workspace.yaml"}},
|
|
590
|
+
"members":[{"key":"github.com/nw/agents","name":"agents","commit":"66566512…","confirmed":true,"status":"confirmed","detail":null,
|
|
591
|
+
"souls":["rm"],"capabilities":["nw-house-style"],"publishes":null,"url":"https://github.com/nw/agents/tree/66566512…",
|
|
592
|
+
"membershipFile":{"path":"oats-membership.yaml","url":"https://github.com/nw/agents/blob/66566512…/oats-membership.yaml"}}],
|
|
593
|
+
"packages":[{"id":"oats.okf","version":"3.0.0","source":"catalog:oats.okf","commit":"ab897841…","integrity":"sha256-bada35…",
|
|
594
|
+
"capabilities":["oats.okf"],"souls":[],"latest":{"version":"4.0.4","ref":"v4.0.4"}}],
|
|
595
|
+
"declaredPackages":["oats.framework","oats.okf"],"unsynced":["oats.framework"],"stale":[],
|
|
596
|
+
"external":[{"source":"git:github.com/oss/experts@3c606e09…","soul":"security-reviewer"}],
|
|
597
|
+
"problems":[],"warnings":[],
|
|
598
|
+
"automations":{"host":"ana-laptop","snapshot":{"takenAt":"2026-09-28T10:00:00.000Z","problems":0},
|
|
599
|
+
"rows":[{"kind":"schedule","id":"agents/nightly","runsOn":"ana-laptop","owner":"github.com/ana","runsHere":true,"reason":null,"enabledHere":true,
|
|
600
|
+
"origin":{"kind":"workspace","repoKey":"github.com/nw/agents","path":"oats-schedules/nightly.yaml","commit":"66566512…","url":null,"localPath":null}}]},
|
|
601
|
+
"defaults":{"slots":{"knowledge":{"name":"oats.okf","from":"package"},"messaging":"none","tasks":null},
|
|
602
|
+
"capabilities":[{"name":"oats.core","from":"package","off":false}]},
|
|
603
|
+
"clones":[{"key":"github.com/nw/agents","name":"agents","path":"/w/agents-repo","rule":"convention"}],
|
|
604
|
+
"disabledSouls":[],"lock":{"path":"/w/oats-lock.json","lockfileVersion":3}}
|
|
690
605
|
```
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
606
|
+
|
|
607
|
+
- `members[]` and `packages[]` are the sync rows (packages from the lock).
|
|
608
|
+
- `declaredPackages`: the ids in `packages:` (standalone: the kernel's
|
|
609
|
+
default). `unsynced`: declared, not locked. `stale`: locked, no longer
|
|
610
|
+
declared. `external[]`: `{source, soul}`.
|
|
611
|
+
- `workspace.teams` (feature `team-model-2`): the shared teams `{label, team,
|
|
612
|
+
description}` by label; `[]` standalone.
|
|
613
|
+
- `problems`, `warnings`: as in sync.
|
|
614
|
+
- `automations` (feature `automations`): `{host, snapshot: {takenAt,
|
|
615
|
+
problems (a count)} | null, rows: [{kind, id, runsOn, owner, runsHere,
|
|
616
|
+
reason, enabledHere, origin, invalid?}]}`.
|
|
617
|
+
- Automation trust (0.30) adds to `warnings`:
|
|
618
|
+
- `{code: "automation-untrusted", kind, id, message, remedy}` for each row
|
|
619
|
+
whose reason is `untrusted`. `remedy` is the `oats-local.yaml` line.
|
|
620
|
+
- `{code: "automation-trust-stale", entry, message}` for a trust entry that
|
|
621
|
+
names no workspace trigger or schedule (a member may not have synced yet).
|
|
622
|
+
- The rest are [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
|
|
623
|
+
|
|
624
|
+
<a id="oats-capabilities---dir---json--capabilitiesapi-1--oats-souls---dir---json--soulsapi-1"></a>
|
|
625
|
+
### `oats capabilities` and `oats souls`
|
|
626
|
+
|
|
627
|
+
```text
|
|
628
|
+
oats capabilities [--dir <d>] --json
|
|
629
|
+
oats souls [--dir <d>] --json
|
|
698
630
|
```
|
|
699
631
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
- `Entry` (unreadable, `list` only) = `{id, scope, scheduleApi: 2, scheduleHistoryApi: 3, unreadable: {code, message}, history: {status: "corrupt", stored: null, truncated: false}, recentRuns: []}` — no definition fields.
|
|
705
|
-
- `history` = `{status: "ok", stored: integer, truncated: boolean}` | `{status: "corrupt", stored: null, truncated: false}`.
|
|
706
|
-
- `Run` (API 3 row) = producer-written fields (`scheduledFor, startedAt, kind, outcome, …`) + `{runId: string, legacy: false, settled: boolean, recordedAt: ISO, transitions: string[], session}`; `transitions[]` elements are outcome strings in write order, first element = the first recorded outcome.
|
|
707
|
-
- `Run` (legacy row) = producer-written fields + `{runId: null, legacy: true, settled: null, transitions: null, session}` — no `recordedAt`, no `key`.
|
|
708
|
-
- `Run` (corrupt element) = `{runId: null, legacy: true, corrupt: true}` only.
|
|
709
|
-
- `session` = `{instance: string|null, home: string|null, incarnation: ISO|null, server: string|null, delivery: "launched"|"delivered-active"|"none"}`; always present on rows and on `lastRun`.
|
|
710
|
-
- `runId` is opaque to consumers: never recompute, never dedup client-side.
|
|
711
|
-
- **Refusals** (`ok:false`): `E_SCHEDULE_STATE_OVERSIZE` / `E_SCHEDULE_INVALID` carry `error.details.source = {path, status, bytes}` (+ `field`); `E_SCHEDULE_IDENTITY` carries `error.details.key` and `error.details.declared`; `E_BAD_ARGS` (id shape) carries no details. A refusal has no `integrity` block — `list` refuses as a whole only when a scope file itself is unreadable.
|
|
712
|
-
- **Open path** (both files): `lstat` → regular file → `open(O_RDONLY|O_NOFOLLOW|O_NONBLOCK)` → `fstat` regular + same dev/ino + `size ≤ 1 MiB` → read exactly `fstat.size` bytes by descriptor (a file that grows past the budget between lstat and fstat is refused, never partially read).
|
|
713
|
-
|
|
714
|
-
## Spawn preview (`oats spawn … --preview`, `spawnPreviewApi: 1`, OATS 0.24.8+)
|
|
715
|
-
|
|
716
|
-
The Spawn modal's fields are backed by the kernel's own decision, taken **before
|
|
717
|
-
any side effect**: `oats spawn <agent> [same flags as a real spawn] --preview --json`
|
|
718
|
-
runs every preflight a spawn runs (placement, composition, resources,
|
|
719
|
-
executable, harness packages, child-spawn policy) and returns what the spawn
|
|
720
|
-
*would* do — then returns without creating a home, branch or worktree.
|
|
632
|
+
Every item of every confirmed member, the external souls, and the locked
|
|
633
|
+
packages' capabilities and souls, sorted by name, then origin. Both carry
|
|
634
|
+
`workspace: {name, key, commit}`, `problems` and, on a standalone view,
|
|
635
|
+
`standalone: true`.
|
|
721
636
|
|
|
722
637
|
```json
|
|
723
|
-
{"
|
|
724
|
-
"
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
638
|
+
{"soulsApi":1,"workspace":{"name":"northwind","key":"github.com/nw/agents","commit":"66566512…"},
|
|
639
|
+
"souls":[
|
|
640
|
+
{"name":"writer","origin":"member github.com/nw/mkt @ 46b3494a","kind":"member","repoKey":"github.com/nw/mkt","commit":"46b3494a…",
|
|
641
|
+
"teams":[{"label":"mine","team":"mine:ana.aweb.ai","default":true,"from":"local"},{"label":"global","team":null,"default":false,"from":"shared"}],
|
|
642
|
+
"defaultTeam":{"label":"mine","team":"mine:ana.aweb.ai","from":"deployment"},
|
|
643
|
+
"private":false,"path":"souls/writer","work":"directory","description":"Drafts campaigns.","harness":"pi","model":null,"harnessFrom":"kernel-default",
|
|
644
|
+
"file":{"path":"souls/writer/soul.yaml","url":null},"spawnable":true,"problem":null},
|
|
645
|
+
{"name":"knowledge-maintainer","qualifiedName":"oats.okf/knowledge-maintainer","origin":"package oats.okf v4.0.4","kind":"package","package":"oats.okf",
|
|
646
|
+
"version":"4.0.4","repoKey":"github.com/awebai/oats-okf","commit":"a4ccca02…","teams":null,"defaultTeam":null,"private":false,
|
|
647
|
+
"path":"oats-package/souls/knowledge-maintainer","work":"directory","description":"Reviews harvested knowledge.","harness":"pi","model":null,
|
|
648
|
+
"harnessFrom":"kernel-default","file":{"path":"oats-package/souls/knowledge-maintainer/soul.yaml","url":null},
|
|
649
|
+
"spawnable":false,"problem":{"code":"E_TEAM_UNKNOWN","message":"team \"reviewers\" is not declared (oats-local.yaml#/souls/teams/…)"}}],
|
|
650
|
+
"problems":[]}
|
|
728
651
|
```
|
|
729
652
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
and
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
`
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
`
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
`
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
- **
|
|
816
|
-
`
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
this, not on `spawn-idempotency`, whose replay could be blocked by
|
|
843
|
-
`E_BRANCH_EXISTS`): key recovery runs **first**, right after the name is
|
|
844
|
-
decided and before any placement/branch/base/preflight/backend work — so a
|
|
845
|
-
retry of a spawn that created its explicit branch still reaches its receipt.
|
|
846
|
-
The key-bearing home records `spawnCompleted:false` at its first write and
|
|
847
|
-
`true` only after launch + lineage + final events; a same-key retry of an
|
|
848
|
-
unfinished spawn refuses **`E_SPAWN_INCOMPLETE`** (`details.{instance, home,
|
|
849
|
-
launched}`; remedy is the session surface, never another spawn). The key
|
|
850
|
-
lives in the home by design: durable across the GUI's restart, gone with a
|
|
851
|
-
retired home — after a retire, "check result" is a roster question. The wake
|
|
852
|
-
outcome is recorded (`wake {requested, saved, error}`) and returned on
|
|
853
|
-
replay; `saved:null` means *not recorded* (crash in the interval) — render
|
|
854
|
-
"Agent created; wake outcome unavailable — check Schedules", never
|
|
855
|
-
saved/not-saved without the record.
|
|
856
|
-
- **Retention stays clean**: the completion marker and the wake record are
|
|
857
|
-
kernel writes to `instance.json` made after the spawn's retirement baseline;
|
|
858
|
-
the kernel re-stamps the baseline's home fingerprint after each, so a fresh
|
|
859
|
-
keyed home retires with **no** `changed instance-home bytes` — only the
|
|
860
|
-
agent's own changes ever read as work to recover.
|
|
861
|
-
- **Idempotent apply** (0.24.10+, feature `spawn-idempotency`): `spawn …
|
|
862
|
-
--expect-decision <rev> --idempotency-key <key>` records the key and the
|
|
863
|
-
decision in the new home's `instance.json`; a **retry with the same key**
|
|
864
|
-
replays the recorded receipt (`replayed: true`, same instance/home, no second
|
|
865
|
-
spawn, no wake re-saved) — found by key across the soul's instances, never by
|
|
866
|
-
name (the planned name may have been auto-suffixed past it, which is exactly
|
|
867
|
-
the retry case). The same key with a *different* decision refuses
|
|
868
|
-
**`E_IDEMPOTENCY_CONFLICT`** (`details.instance/home` of the prior spawn); a
|
|
869
|
-
different key with a fresh decision is a genuinely new confirmation. Mint the
|
|
870
|
-
key server-side on the first confirmation and keep it for that intent's
|
|
871
|
-
retries (as 2c does); a lost response is a replay, never a guess by name.
|
|
872
|
-
- Still absent (named follow-ups, not parity-done): attach-knowledge node refs
|
|
873
|
-
(provider contract), auto-PR (P1/ADE write approval), branch enumeration
|
|
874
|
-
(producer seam).
|
|
875
|
-
|
|
876
|
-
## Readiness quartet (`readinessApi: 1`) — removed in 0.26.0
|
|
877
|
-
|
|
878
|
-
The quartet (`installed | trusted | configured | enrolled`), its signature
|
|
879
|
-
verification (`--verify-signatures`, feature `readiness-verify`) and the
|
|
880
|
-
scope subject were removed with the classic config chain. `oats readiness`
|
|
881
|
-
answers only [`readinessApi: 2`](#oats-readiness---home---soul---dir---policy---json-readinessapi-2);
|
|
882
|
-
`--verify-signatures` is `E_BAD_ARGS`.
|
|
883
|
-
|
|
884
|
-
### Enforced child-spawn policy (`--policy`)
|
|
885
|
-
|
|
886
|
-
`childSpawns` is **enforced by the spawn route**: `soul.yaml` may declare
|
|
887
|
-
`children: {spawn: false}`; `oats spawn --allow-child-spawns | --no-child-spawns`
|
|
888
|
-
overrides per spawn; the result is recorded in `instance.json`
|
|
889
|
-
`policy.childSpawns {allowed, origin}`. A spawn with `--parent <p>` (or
|
|
890
|
-
`--relation child --relative-to <p>`) under a parent whose recorded policy is
|
|
891
|
-
off refuses **`E_CHILD_SPAWNS_DISABLED`** (`details.parent`, `details.policy`)
|
|
892
|
-
before anything is created. Absent policy (pre-0.24.8 instances) = allowed,
|
|
893
|
-
reported as `origin.kind: "default"`. With `--home <abs>` the policy is the
|
|
894
|
-
instance's recorded (enforced) one; with only `--soul` it is the declaration
|
|
895
|
-
(`enforced: false`). It is a lifecycle-authority claim, not an OS sandbox —
|
|
896
|
-
the UI says so.
|
|
897
|
-
|
|
898
|
-
## Lifecycle plans — Stop and Remove (`lifecycleApi: 1`, OATS 0.24.8+)
|
|
899
|
-
|
|
900
|
-
The Desktop's Stop and Remove confirmations render **plans**: a read-only
|
|
901
|
-
statement of what the action would touch, with the facts a human needs, and a
|
|
902
|
-
`planRevision` hashed from the facts that make the action safe. Apply carries
|
|
903
|
-
the revision back; if reality moved, apply **refuses with the fresh plan**
|
|
904
|
-
(`E_PLAN_STALE`, `details.plan`) instead of acting on a world the human did
|
|
905
|
-
not see. An `idempotencyKey` makes a retried apply return the first receipt.
|
|
906
|
-
|
|
907
|
-
### `oats instance stop <instance> --plan [--no-recursive] [--home <abs>] [--dir <d>] --json`
|
|
653
|
+
**Capability rows:** `name`, `origin` (display text), `kind`, then for a
|
|
654
|
+
member `repoKey, commit, private, path, layer, version`, for a package
|
|
655
|
+
`package, version, commit, private: false, layer`; plus the Desktop facts
|
|
656
|
+
`description, skills, commands, hooks, file, tree`. `private: true` (feature
|
|
657
|
+
`capabilities-private`) marks a capability usable only by its own repository's
|
|
658
|
+
souls (`E_CAPABILITY_PRIVATE` otherwise). An unsynced package's capabilities
|
|
659
|
+
are absent until `sync`.
|
|
660
|
+
|
|
661
|
+
**Soul rows:** `name, origin, kind (member | external | package), repoKey,
|
|
662
|
+
commit, teams, defaultTeam, private (always false), path, work, description`,
|
|
663
|
+
plus the Desktop facts `harness, model, harnessFrom, file, spawnable,
|
|
664
|
+
problem`, and (feature `launch-preference`) `key` and `launch`.
|
|
665
|
+
- `key` is the soul key that `souls.teams`, `souls.default` and `souls.launch`
|
|
666
|
+
use, and that `oats soul teams <key>` takes: `qualifiedName` for a package
|
|
667
|
+
soul, the bare `name` for a member or external soul. Two member souls that
|
|
668
|
+
share a bare name share one entry (spawning that name is
|
|
669
|
+
`E_SOUL_AMBIGUOUS`).
|
|
670
|
+
- `launch` is a [Launch](#the-launch-report-launch).
|
|
671
|
+
- `teams` and `defaultTeam` (feature `team-model-2`) are a
|
|
672
|
+
[TeamRow](#the-team-row-teamrow) list and a
|
|
673
|
+
[DefaultTeam](#the-default-defaultteam); both `null` when the soul's teams
|
|
674
|
+
do not resolve (`problem` names the `E_TEAM_*` code).
|
|
675
|
+
- Package souls (feature `package-souls`) add `qualifiedName`
|
|
676
|
+
(`<package>/<soul>`), `package` and `version`. Spawn one by
|
|
677
|
+
`qualifiedName` (the bare name when unique). Its instances live under
|
|
678
|
+
`agents/<package>--<soul>/` with `.` written `-` (for example
|
|
679
|
+
`oats-okf--knowledge-maintainer`), which is its agent `name` in the roster.
|
|
680
|
+
|
|
681
|
+
This document keeps `soulsApi: 1`; the probe's `soulsApi: 2` is the inspect
|
|
682
|
+
soul row's.
|
|
683
|
+
|
|
684
|
+
<a id="desktop-facts-feature-desktop-facts-oats-0290"></a>
|
|
685
|
+
### Desktop facts
|
|
686
|
+
|
|
687
|
+
Feature `desktop-facts`: facts the kernel reports so the Desktop never derives
|
|
688
|
+
them. Gate each field below on it.
|
|
689
|
+
|
|
690
|
+
| Document | Fields |
|
|
691
|
+
|---|---|
|
|
692
|
+
| `oats inspect --soul` | `capabilities[].composedFrom`, `capabilitiesOff[]` |
|
|
693
|
+
| `oats souls` rows | `harness`, `model`, `harnessFrom`, `spawnable`, `problem`, `file` |
|
|
694
|
+
| `oats capabilities` rows | `layer`, `description`, `skills`, `commands`, `hooks`, `file`, `tree` |
|
|
695
|
+
| `oats workspace status` | `workspace.file`, `members[].url`, `members[].membershipFile`, `packages[].latest`, `defaults`, `clones`, `disabledSouls`, `lock` |
|
|
696
|
+
| `oats status` instance rows | `startedAt`, `modelFrom`, `identityAddress`; `modules[].current.version` on member rows |
|
|
697
|
+
|
|
698
|
+
- **Souls.** `harness`, `model`, `harnessFrom`: what a spawn starts with when
|
|
699
|
+
no selection flag is given, else `harness: "pi"`, `model: null`,
|
|
700
|
+
`harnessFrom: "kernel-default"` (`"soul"` when the definition names one; a
|
|
701
|
+
v2 soul.yaml cannot). `spawnable`/`problem`: whether a spawn here would
|
|
702
|
+
refuse, resolved without spawning, writing or reaching past the sync cache;
|
|
703
|
+
`problem` is `{code, message}` or `null` (`E_SOUL_DISABLED`,
|
|
704
|
+
`E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE`, `E_CAPABILITY_*`, `E_PACKAGE_*`,
|
|
705
|
+
`E_LOCK_SCHEMA`, `E_REMOTE_*`, …). `file`: `{path, url}` of soul.yaml.
|
|
706
|
+
- **Capabilities.** `layer` on every row, `null` outside the slots.
|
|
707
|
+
`description` or `null`. `skills`, `commands`, `hooks`: names, sorted
|
|
708
|
+
(`skills` is `null` when they cannot be listed). `file`: `{path, url}` of
|
|
709
|
+
`oats.json`, or `null` when unreadable. `tree`: a member capability's Git
|
|
710
|
+
tree id at the member commit; `null` for a package (its fingerprint is the
|
|
711
|
+
lock's `integrity`). Package facts are read from the sync cache; unreadable
|
|
712
|
+
manifests leave them `null`.
|
|
713
|
+
- **`defaults`**: the workspace file's defaults as declared. `slots.<slot>` is
|
|
714
|
+
`{name, from}`, `"none"` or `null`; `capabilities` are `{name, from, off}`
|
|
715
|
+
by name (`from` is `"package"`, `"here"` or a member key; `null` when off).
|
|
716
|
+
Standalone: every slot `null`, `capabilities: []`.
|
|
717
|
+
- **`clones[]`**: `{key, name, path, rule}` with `rule` `"clones"`,
|
|
718
|
+
`"convention"` or `null`. A path that is not the member's clone gives
|
|
719
|
+
`path: null, rule: null, problem: {code: "E_CLONE_MISMATCH", message}`.
|
|
720
|
+
- **`disabledSouls`**: `souls.disabled` as written. **`lock`**: `{path,
|
|
721
|
+
lockfileVersion}`.
|
|
722
|
+
- **`packages[].latest`**: `{version, ref}` when the kernel's bundled catalog
|
|
723
|
+
has a newer version of a catalog package; `null` otherwise and for `git:`
|
|
724
|
+
packages. No network (`OATS_PACKAGE_CATALOG` overrides the catalog).
|
|
725
|
+
- **`workspace.file`**, **`members[].url`**, **`members[].membershipFile`**:
|
|
726
|
+
`{path, url}` at the named commit (`workspace.file` is `null` standalone).
|
|
727
|
+
- **URLs** exist only for `github.com` (`…/blob/<commit>/<path>` or
|
|
728
|
+
`…/tree/<commit>`); any other host gives `url: null` with `path` set.
|
|
729
|
+
`path` is repository-relative.
|
|
730
|
+
|
|
731
|
+
<a id="team-model-v2-feature-team-model-2-oats-0300-replaces-feature-teams"></a>
|
|
732
|
+
## Teams
|
|
733
|
+
|
|
734
|
+
Feature `team-model-2`: gate every team field and verb on it. Design:
|
|
735
|
+
[team model v2](design/2026-09-27-team-model-v2.md); operator guide:
|
|
736
|
+
[workspaces.md](workspaces.md).
|
|
737
|
+
|
|
738
|
+
- **Shared teams** live in the committed `oats-workspace.yaml`:
|
|
739
|
+
`teams.<label> = {description?, team?}`. A shared team without `team` (the
|
|
740
|
+
provider id) is **unmapped**.
|
|
741
|
+
- **Local** configuration lives in `oats-local.yaml`: `teams.<label> = {team,
|
|
742
|
+
description?}`, `defaultTeam: <label>`, `souls.teams: {"*": [labels],
|
|
743
|
+
"<soul>": [labels]}` and `souls.default: {"<soul>": <label>}`. A soul key is
|
|
744
|
+
the spawn name (`<package>/<soul>` for a package soul). A label matches
|
|
745
|
+
`[a-z0-9][a-z0-9._-]*`.
|
|
746
|
+
- **Team ids.** A `team` value (in either file, and `oats teams add --team`)
|
|
747
|
+
matches `^[A-Za-z0-9][A-Za-z0-9._:@/+-]{0,255}$`: the kernel's safety rule
|
|
748
|
+
(never `-`-led, no whitespace or control characters, bounded). Otherwise
|
|
749
|
+
`E_WORKSPACE_SCHEMA` (a file) or `E_BAD_ARGS` (the verb). The messaging
|
|
750
|
+
provider validates its own id shape (oats.aweb: `<name>:<namespace>`).
|
|
751
|
+
- **Resolution.** The soul's default is `souls.default[soul] ??
|
|
752
|
+
defaultTeam`; its teams are that default plus `souls.teams["*"]` plus
|
|
753
|
+
`souls.teams[soul]`. A label in both files is a `team-label-collision`
|
|
754
|
+
warning, and the shared definition wins. An undeclared label is
|
|
755
|
+
`E_TEAM_UNKNOWN`; a `souls.default` outside the soul's teams is
|
|
756
|
+
`E_TEAM_NOT_ELIGIBLE`.
|
|
757
|
+
- **Removed keys** are `E_WORKSPACE_SCHEMA` with `reason: "removed-key"`:
|
|
758
|
+
`messaging.byTeam`, `defaults.byTeam`, a soul.yaml `team`, an
|
|
759
|
+
oats-membership.yaml `team`, and `byTeam` in any provider payload layer.
|
|
760
|
+
Other payload keys are opaque (a `team` setting passes through).
|
|
761
|
+
|
|
762
|
+
### The team row (`TeamRow`)
|
|
763
|
+
|
|
764
|
+
Exactly `{label, team, default, from}`:
|
|
908
765
|
|
|
909
766
|
```json
|
|
910
|
-
{"
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
"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"}},
|
|
914
|
-
"retiring":false,"stopPending":false,"midTask":true}],
|
|
915
|
-
"skipped":[],"planRevision":"<24 hex>","notes":[]}
|
|
767
|
+
[{"label":"antares","team":"antares:ana.aweb.ai","default":true,"from":"local"},
|
|
768
|
+
{"label":"oats","team":"oats:oats.aweb.ai","default":false,"from":"shared"},
|
|
769
|
+
{"label":"reviewers","team":null,"default":false,"from":"shared"}]
|
|
916
770
|
```
|
|
917
771
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
`state:"unestablished"`, with a `reason` — render that as unknown, never as
|
|
926
|
-
idle.
|
|
927
|
-
- `work` is K1's observation (`observed:false` with a `reason` when there is no
|
|
928
|
-
work tree or it cannot be read — not "clean").
|
|
929
|
-
- `midTask` is **reported** activity: `true` (running session or dirty work),
|
|
930
|
-
`false` (established idle and observed clean), or `"unknown"`.
|
|
931
|
-
|
|
932
|
-
### `oats instance stop <instance> --apply --plan-revision <rev> --idempotency-key <key> [--no-recursive] [--grace-ms <n>] --json`
|
|
933
|
-
|
|
934
|
-
Quiesces each target (SIGTERM to the harness processes, bounded wait, **never
|
|
935
|
-
escalated**), children first, under a per-home stop marker; retains home, work
|
|
936
|
-
tree, transcript and launch configuration so `oats session restart` brings the
|
|
937
|
-
instance back. Refuses `E_PLAN_STALE` (fresh plan attached),
|
|
938
|
-
`E_INSTANCE_RETIRING`, `E_LIFECYCLE_BUSY`.
|
|
772
|
+
`team` is `null` for an unmapped team. `default` is true on exactly the
|
|
773
|
+
soul's default row; the others are teams an instance may join (offered, never
|
|
774
|
+
joined automatically). `from` is `"shared"` or `"local"`. The default row
|
|
775
|
+
comes first, then the rest by label (codepoint order). Reports include
|
|
776
|
+
unmapped rows; `OATS_TEAMS` and `instance.json.teams` carry mapped rows only.
|
|
777
|
+
|
|
778
|
+
### The default (`DefaultTeam`)
|
|
939
779
|
|
|
940
780
|
```json
|
|
941
|
-
{"
|
|
942
|
-
"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]},
|
|
943
|
-
{"instance":"dev-1","home":"/abs/home","ok":true,"stopped":true,"alreadyIdle":false,"state":"shell"}],
|
|
944
|
-
"retained":["home","work","transcript","launch"],"replayed":false}
|
|
781
|
+
{"label":"antares","team":"antares:ana.aweb.ai","from":"deployment"}
|
|
945
782
|
```
|
|
946
783
|
|
|
947
|
-
`
|
|
948
|
-
|
|
949
|
-
|
|
784
|
+
`from` is `"deployment"` (`defaultTeam`) or `"soul"` (`souls.default`). It is
|
|
785
|
+
`null` only when no default is configured (with messaging active, that is
|
|
786
|
+
`E_TEAM_UNCONFIGURED`). An unmapped default is `{label, team: null, from}`,
|
|
787
|
+
the blocking problem `team-unmapped`.
|
|
788
|
+
|
|
789
|
+
<a id="where-teams-appear"></a>
|
|
790
|
+
### Where teams appear
|
|
791
|
+
|
|
792
|
+
| Document | Team fields |
|
|
793
|
+
|---|---|
|
|
794
|
+
| `oats spawn … --preview` | `teams` (unmapped included), `defaultTeam` |
|
|
795
|
+
| `oats inspect --soul` | `teams`, `defaultTeam`, `teamsSource: "live"` |
|
|
796
|
+
| `oats inspect --home` | `teams`, `defaultTeam`, `teamsSource` (`"live"`, or `"recorded"` when the workspace cannot be read; `teams: null` when none was recorded), `recordedDefaultTeam` |
|
|
797
|
+
| `oats readiness` | items under `checks.configured` |
|
|
798
|
+
| `oats souls` rows | `teams`, `defaultTeam` (`null` when they do not resolve) |
|
|
799
|
+
| `oats workspace status` | `workspace.teams` |
|
|
800
|
+
| `instance.json` | `teams` (mapped rows), `defaultTeam` |
|
|
801
|
+
|
|
802
|
+
A home's live teams are the files as they are now; a running instance keeps
|
|
803
|
+
its spawn-time default until respawned (readiness says so with
|
|
804
|
+
`default-team-changed`). No document carries a soul-level `team`, `labels`,
|
|
805
|
+
`primary`, `mapped` or `byTeam`, and no capability origin is `team:<label>`.
|
|
806
|
+
|
|
807
|
+
**Provider environment.** Hooks, commands, operations and provider checks get
|
|
808
|
+
the teams in `OATS_DEFAULT_TEAM`, `OATS_DEFAULT_TEAM_ID`,
|
|
809
|
+
`OATS_DEFAULT_TEAM_FROM`, `OATS_TEAMS` and `OATS_TEAMS_SOURCE`:
|
|
810
|
+
[capabilities.md](capabilities.md#teams-in-the-provider-environment).
|
|
811
|
+
`OATS_WORKSPACE_NAME` is the recorded workspace name (the discovered one for a
|
|
812
|
+
soul subject), `""` when unknown.
|
|
813
|
+
|
|
814
|
+
### `oats teams`
|
|
950
815
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
816
|
+
```text
|
|
817
|
+
oats teams [--dir <d>] --json
|
|
818
|
+
oats teams add <label> --team <id> [--description <d>] --json
|
|
819
|
+
oats teams remove <label> --json
|
|
820
|
+
oats teams default <label> --json
|
|
821
|
+
```
|
|
954
822
|
|
|
955
823
|
```json
|
|
956
|
-
{"
|
|
957
|
-
"
|
|
958
|
-
|
|
959
|
-
"
|
|
960
|
-
"
|
|
824
|
+
{"teamsApi":1,"deployment":"/w","defaultTeam":"antares",
|
|
825
|
+
"teams":[{"label":"antares","team":"antares:ana.aweb.ai","description":null,"from":"local","default":true,"at":"oats-local.yaml#/teams/antares"},
|
|
826
|
+
{"label":"reviewers","team":null,"description":null,"from":"shared","default":false,"at":"github.com/awebai/oats:oats-workspace.yaml#/teams/reviewers"}],
|
|
827
|
+
"souls":{"teams":{"*":["oats"],"oats-expert":["reviewers"]},"default":{"oats-expert":"oats"}},
|
|
828
|
+
"problems":[{"code":"team-unmapped","label":"reviewers","default":false,"severity":"warning","at":"github.com/awebai/oats:oats-workspace.yaml#/teams/reviewers",
|
|
829
|
+
"message":"shared team reviewers has no provider id yet","fix":"its owner runs `oats aweb setup`, then commits the id"}]}
|
|
961
830
|
```
|
|
962
831
|
|
|
963
|
-
`
|
|
964
|
-
|
|
965
|
-
|
|
832
|
+
- `defaultTeam` is the deployment's label (or `null`), not a `DefaultTeam`.
|
|
833
|
+
- `teams[]`: every declared team by label, `{label, team, description, from,
|
|
834
|
+
default, at}`; `at` is a pointer into `oats-local.yaml` or
|
|
835
|
+
`<workspace key>:oats-workspace.yaml#/teams/<label>`. A collision shows the
|
|
836
|
+
shared definition.
|
|
837
|
+
- `souls`: `souls.teams` and `souls.default` as written. `problems`: the
|
|
838
|
+
deployment's [team readiness items](#team-readiness-items).
|
|
839
|
+
- The verbs never call a provider. They validate, rewrite `oats-local.yaml` in
|
|
840
|
+
place, and answer the document plus `changed: bool`.
|
|
841
|
+
- **`add`**: the first team added also becomes `defaultTeam`. A label already
|
|
842
|
+
declared is `E_TEAM_EXISTS {label, from}`; a bad label or no `--team` is
|
|
843
|
+
`E_BAD_ARGS`.
|
|
844
|
+
- **`remove`**: a referenced label is `E_TEAM_IN_USE {label, usedBy}` (each
|
|
845
|
+
`"defaultTeam"`, `"souls.teams:<key>"` or `"souls.default:<key>"`); a shared
|
|
846
|
+
label is `E_TEAM_SHARED {label, at}` (a label in both files can be removed
|
|
847
|
+
locally); unknown is `E_TEAM_UNKNOWN {label}`.
|
|
848
|
+
- **`default`**: any declared label, else `E_TEAM_UNKNOWN`.
|
|
849
|
+
- A write that would introduce an unknown or ineligible reference is refused
|
|
850
|
+
with that code; an invalid result is `E_WORKSPACE_SCHEMA`.
|
|
851
|
+
|
|
852
|
+
### `oats soul teams`
|
|
966
853
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
cannot stay under the removed home, so it is **re-homed** with
|
|
971
|
-
`git worktree move` to `<workspace>/.agents/worktrees/<repo>/<branch>` (a
|
|
972
|
-
`-2`, `-3` suffix if taken; detached → `detached-<oid12>`), with staged,
|
|
973
|
-
unstaged and untracked state intact, and the repository knows the new
|
|
974
|
-
location. The receipt says so:
|
|
854
|
+
```text
|
|
855
|
+
oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--dir <d>] --json
|
|
856
|
+
```
|
|
975
857
|
|
|
976
858
|
```json
|
|
977
|
-
{"
|
|
978
|
-
"
|
|
859
|
+
{"soulTeamsApi":1,"soul":"oats-expert","key":"oats-expert","defaultTeam":{"label":"oats","team":"oats:oats.aweb.ai","from":"soul"},
|
|
860
|
+
"teams":[{"label":"oats","team":"oats:oats.aweb.ai","default":true,"from":"shared","via":["default","*"]},
|
|
861
|
+
{"label":"reviewers","team":null,"default":false,"from":"shared","via":["soul"]}],
|
|
862
|
+
"local":{"teams":["reviewers"],"default":"oats"},"all":["oats"]}
|
|
979
863
|
```
|
|
980
864
|
|
|
981
|
-
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
`
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
`
|
|
992
|
-
|
|
993
|
-
-
|
|
994
|
-
|
|
995
|
-
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
--idempotency-key <key> [--discard-worktree] [--delete-branch] --json`. The
|
|
1005
|
-
revision is revalidated against a fresh plan first — facts moved →
|
|
1006
|
-
`E_PLAN_STALE` with `details.plan` (re-render, re-confirm; nothing retired);
|
|
1007
|
-
a repeated key **replays** the recorded receipt (`replayed: true`, JSON-v1
|
|
1008
|
-
envelope) instead of retiring twice. A first retire prints its raw receipt
|
|
1009
|
-
(pre-existing shape) with `planRevision`/`idempotencyKey`/`replayed:false`
|
|
1010
|
-
added. Mint the key server-side per confirmation intent and keep it for that
|
|
1011
|
-
intent's retries.
|
|
1012
|
-
- **Children first, kernel-owned.** The plan's `facts.children` are stopped
|
|
1013
|
-
by the kernel before retirement (bounded SIGTERM, never escalated) and
|
|
1014
|
-
retained; the receipt lists `childrenStopped[]`. A child still running
|
|
1015
|
-
after the grace **refuses the whole retirement** — `E_CHILDREN_RUNNING`
|
|
1016
|
-
with `details.childrenStopped` (pids) and `details.plan`; nothing retired.
|
|
1017
|
-
- **Branch deletion is bound to the confirmed branch.** The kernel re-verifies
|
|
1018
|
-
the worktree's branch at the moment of deletion, after hooks (which may
|
|
1019
|
-
mutate the tree); a mismatch deletes nothing and reports
|
|
1020
|
-
`retention.branchDeletionSkipped {expected, actual, reason}`.
|
|
1021
|
-
- **Ambiguous parentage is reported, never acted on.** Recorded parentage is
|
|
1022
|
-
a bare name; if a child's parent name resolves to several homes under the
|
|
1023
|
-
root, that child appears under `ambiguous[]` — `plan.ambiguous` on a stop
|
|
1024
|
-
plan, `plan.facts.ambiguous` on a retire plan — with the reason, and is
|
|
1025
|
-
excluded from `targets`/`children`.
|
|
1026
|
-
- **Stop replay horizon**: stop receipts are stored **per idempotency key**
|
|
1027
|
-
(`<home>/.oats-stop-receipt.<key>.json`); any earlier key replays its own
|
|
1028
|
-
receipt for as long as the home exists. Retire receipts live beside the
|
|
1029
|
-
instances directory and replay after the home is gone.
|
|
1030
|
-
|
|
1031
|
-
### Feature advertisement — gate every new command on the probe
|
|
1032
|
-
|
|
1033
|
-
`oats version --json` `features` now lists: `instance-git`,
|
|
1034
|
-
`instance-git-remote`, `souls-declarations`, `lifecycle-plans`,
|
|
1035
|
-
`retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
|
|
1036
|
-
`schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
|
|
1037
|
-
`lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
|
|
1038
|
-
`scheduleHistoryApi`, `operationsApi`). In 0.26.0, `soulsApi`, `readinessApi`
|
|
1039
|
-
and `operationsApi` are **2** ([the workspace-model inspect](#inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260)). **Gate on these, never on a version string and never by
|
|
1040
|
-
optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
|
|
1041
|
-
and *retires*. Absent feature → the view is unavailable. (`catalog` was the
|
|
1042
|
-
0.24 `oats catalog` verb's flag; the verb is removed under the workspace model
|
|
1043
|
-
and the flag is no longer advertised — the official catalog is reached through
|
|
1044
|
-
`packages:` + `oats sync`, not a command.)
|
|
1045
|
-
|
|
1046
|
-
## Workspace model (`workspaceApi: 2`)
|
|
1047
|
-
|
|
1048
|
-
Features: **`workspace-v2`** (the declaration files, `sync`, `package`,
|
|
1049
|
-
`workspace status`, `capabilities`, `souls`; `init`/`use`/`install`/`restore`
|
|
1050
|
-
removed), **`instance-modules`** (`instance.json.modules` / `providers` /
|
|
1051
|
-
`workspace`; `status --json` module drift; preview `modules[]`),
|
|
1052
|
-
**`spawn-provider-payload`** (`oats spawn … --provider <cap> k=v`). The probe
|
|
1053
|
-
carries `workspaceApi: 2`. Model: [workspaces.md](workspaces.md).
|
|
1054
|
-
|
|
1055
|
-
Every command below needs a deployment with `oats-local.yaml` (walked up from
|
|
1056
|
-
`--dir`/cwd) — else `E_LOCAL_MISSING { dir, searched[] }` — and reads the
|
|
1057
|
-
workspace over Git remotes with the operator's credentials, never prompting
|
|
1058
|
-
(`E_REMOTE_UNREADABLE { url, reason: "auth"|"not-found"|"network"|"timeout" }`).
|
|
1059
|
-
Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
|
|
1060
|
-
`at`/`observedAt` is ISO-8601 UTC. Repo keys are canonical
|
|
1061
|
-
(`github.com/org/repo`; `local/<abs-path>` for file remotes).
|
|
1062
|
-
|
|
1063
|
-
### Removed verbs answer `E_UNKNOWN_COMMAND` with a replacement
|
|
1064
|
-
|
|
1065
|
-
`install`, `restore`, `init`, `use`, `trust`, `list`, `catalog`, `remove`,
|
|
1066
|
-
`migrate`, `config` — checked before capability dispatch, both modes:
|
|
865
|
+
- Rows are `TeamRow` plus `via`, a non-empty ordered subset of `"default"`,
|
|
866
|
+
`"*"` and `"soul"`. `key` is the soul's `souls.*` key; `local` its own
|
|
867
|
+
entries; `all` is `souls.teams["*"]`.
|
|
868
|
+
- For `'*'`: `soul` and `key` are `"*"`, `teams` the deployment default plus
|
|
869
|
+
`souls.teams["*"]`, `local.default: null`.
|
|
870
|
+
- Mutations answer the document plus `changed`. Unknown label:
|
|
871
|
+
`E_TEAM_UNKNOWN {label}`. A `--default` outside the soul's teams:
|
|
872
|
+
`E_TEAM_NOT_ELIGIBLE {soul, label, at}` (`at`: `oats-local.yaml#/souls/default/<key>`,
|
|
873
|
+
`/` written `~1`). `--default`/`--clear-default` with
|
|
874
|
+
`'*'`, or both together: `E_BAD_ARGS`. Soul lookup: `E_SOUL_UNKNOWN`,
|
|
875
|
+
`E_SOUL_AMBIGUOUS`.
|
|
876
|
+
- **Writes** (`oats teams add|remove|default`, `oats soul teams`) edit
|
|
877
|
+
`oats-local.yaml` in place and touch only the entries that change: comments
|
|
878
|
+
and styles elsewhere, including inline comments on sibling entries and flow
|
|
879
|
+
lists, are kept. Each verb re-reads the file and judges its refusals on it
|
|
880
|
+
as it is now, and writes only if the file did not change meanwhile (else it
|
|
881
|
+
redoes the edit on the new content). A file that keeps changing is
|
|
882
|
+
`E_LOCAL_CHANGED {path}`; nothing was written.
|
|
883
|
+
|
|
884
|
+
### The messaging provider's teams document
|
|
885
|
+
|
|
886
|
+
The messaging provider's `teams` operation (`messaging:teams`) is produced by
|
|
887
|
+
the provider (oats.aweb 1.17 or later), not the kernel:
|
|
1067
888
|
|
|
1068
889
|
```json
|
|
1069
|
-
{"
|
|
890
|
+
{"defaultTeam":{"label":"antares","team":"antares:ana.aweb.ai","from":"deployment"},
|
|
891
|
+
"eligible":[{"label":"oats","team":"oats:oats.aweb.ai","joined":true}],
|
|
892
|
+
"joined":[{"label":"oats","team":"oats:oats.aweb.ai","identityHome":"/w/agents/oe/instances/oe-1/.aw-teams/oats","receive":"live","since":"2026-09-28T09:00:00.000Z"}],
|
|
893
|
+
"left":[{"label":"reviewers","team":"reviewers:acme.aweb.ai","at":"2026-09-28T09:30:00.000Z","reason":"no-longer-eligible"}],
|
|
894
|
+
"at":"2026-09-28T10:00:00.000Z"}
|
|
1070
895
|
```
|
|
1071
896
|
|
|
1072
|
-
|
|
897
|
+
`defaultTeam` is the kernel's `DefaultTeam` from the environment. `eligible`
|
|
898
|
+
are the non-default `OATS_TEAMS` rows; `joined[].receive` is `live` or
|
|
899
|
+
`poll`; `left` holds the last 20 leaves. A join or leave answer adds
|
|
900
|
+
`actions: [{action: "join" | "leave", label, released?, receipt?}]`. There is
|
|
901
|
+
no `primary` and no `unmapped`: unmapped teams are kernel readiness items.
|
|
1073
902
|
|
|
1074
|
-
|
|
903
|
+
<a id="team-readiness-items"></a>
|
|
904
|
+
### Team readiness items
|
|
1075
905
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
`
|
|
1086
|
-
|
|
906
|
+
`oats teams` lists them under `problems[]` as `{code, severity: "failure" |
|
|
907
|
+
"warning", message, fix, …}`. `oats readiness` lists the soul's under
|
|
908
|
+
`checks.configured` with `subject` `"team <label>"` (or `"teams"`), `producer:
|
|
909
|
+
"team model"`, `code`, `reason`, `remedy`, `status: "fail"`, `required: true`
|
|
910
|
+
for a failure and `false` for a warning, plus the problem's own keys.
|
|
911
|
+
|
|
912
|
+
| Code | Severity | Keys | When |
|
|
913
|
+
|---|---|---|---|
|
|
914
|
+
| `E_TEAM_UNCONFIGURED` | failure | | messaging is active and the soul has no default |
|
|
915
|
+
| `team-unmapped` | failure if `default`, else warning | `label`, `default`, `at` | a shared team without `team` |
|
|
916
|
+
| `team-label-collision` | warning | `label`, `shared`, `local` (each `{team, description, at}`) | a label in both files |
|
|
917
|
+
| `default-team-changed` | warning | `recorded`, `current` | `--home` with live teams: the default changed since the spawn |
|
|
918
|
+
| `E_TEAM_UNKNOWN` | failure | `label`, `at` | a reference to an undeclared label |
|
|
919
|
+
| `E_TEAM_NOT_ELIGIBLE` | failure | `soul`, `label`, `at` | `souls.default` outside the soul's teams |
|
|
920
|
+
|
|
921
|
+
The last two are also spawn, preview and inspect refusals, with the same
|
|
922
|
+
details.
|
|
923
|
+
|
|
924
|
+
<a id="soul-launch-preferences-feature-launch-preference-oats-0300"></a>
|
|
925
|
+
## Launch preferences
|
|
926
|
+
|
|
927
|
+
Feature `launch-preference` (OATS 0.30.0): gate every field and flag below on
|
|
928
|
+
it. Design: [soul launch preferences](design/2026-09-28-soul-launch-preference.md).
|
|
929
|
+
Every object shape here is closed.
|
|
930
|
+
|
|
931
|
+
- **The soul** may declare `launch: {harness, model?}` in soul.yaml.
|
|
932
|
+
- `harness` is `pi`, `claude` or `codex`. `model` is a non-empty model id for
|
|
933
|
+
that harness.
|
|
934
|
+
- Nothing else is allowed (no args, env, yolo or executable; those stay host
|
|
935
|
+
facts, in launch configurations). Another key is `E_WORKSPACE_SCHEMA`.
|
|
936
|
+
- A package soul may declare one too.
|
|
937
|
+
- **The machine** may override it in `oats-local.yaml` `souls.launch`. A key is
|
|
938
|
+
a soul key (as for `souls.teams`: `"*"`, a bare soul name, or
|
|
939
|
+
`<package>/<soul>`). A value is a `launch-configs` name in the same file, or
|
|
940
|
+
an inline `{harness, model?}` with the soul's rules.
|
|
941
|
+
```yaml
|
|
942
|
+
souls:
|
|
943
|
+
launch:
|
|
944
|
+
"*": opus # every soul on this machine
|
|
945
|
+
oats-expert: opus # a launch configuration
|
|
946
|
+
oats.engineering/code-reviewer: {harness: codex} # an inline preference
|
|
947
|
+
```
|
|
948
|
+
- **Migration.** 0.29.x refuses an unknown soul.yaml key, and members are read
|
|
949
|
+
at their latest commit. A committed soul gains `launch:` only on the flag day
|
|
950
|
+
(every deployment of the workspace runs 0.30). Until then, use `souls.launch`
|
|
951
|
+
or `--launch-config`.
|
|
952
|
+
|
|
953
|
+
**Precedence** for a new selection. The first layer with a value decides:
|
|
954
|
+
1. The flags: `--launch-config` or `--harness` (`from: "flag"`). `--model`
|
|
955
|
+
alone keeps the next deciding layer's harness and replaces only its model.
|
|
956
|
+
2. `souls.launch.<key>` (`"local"`).
|
|
957
|
+
3. `souls.launch."*"` (`"local-default"`).
|
|
958
|
+
4. The soul's `launch` (`"soul"`).
|
|
959
|
+
5. The host default: `pi`, its own model, no configuration (`"host"`).
|
|
960
|
+
|
|
961
|
+
- A **launch configuration** name runs that configuration's full recipe.
|
|
962
|
+
- An **inline or soul preference** runs its harness with this host's baseline
|
|
963
|
+
for it (the executable the host resolves; no args, no env) and its `model`.
|
|
964
|
+
- A preference is a unit. Without `model`, the harness's own model runs; a
|
|
965
|
+
lower layer's model is never borrowed. A model never crosses harnesses
|
|
966
|
+
(`E_MODEL_UNKNOWN`, as before).
|
|
967
|
+
|
|
968
|
+
**Existing homes.** A home's recorded launch is frozen. A plain `session
|
|
969
|
+
start`/`restart` runs it unchanged. The precedence decides only a new
|
|
970
|
+
selection: a spawn, a start/restart with `--launch-config` or `--harness`, or
|
|
971
|
+
a start/restart with `--reselect-launch`, which applies the current layers
|
|
972
|
+
without naming anything. A start/restart with `--model` alone is not a new
|
|
973
|
+
selection: it keeps the recorded harness (and configuration) and replaces only
|
|
974
|
+
the model (`modelFrom: "start"`; `launchFrom` is unchanged). **A changed preference does not affect
|
|
975
|
+
a running or existing home until `--reselect-launch` or a respawn.** Readiness
|
|
976
|
+
shows the drift as `launch-changed`.
|
|
977
|
+
|
|
978
|
+
**Refusals** (spawn, preview, and a reselecting start):
|
|
979
|
+
- `E_HARNESS_UNAVAILABLE {harness, from, at, fix}`: the chosen harness has no
|
|
980
|
+
executable on this machine. There is no fallback to another harness. `fix`
|
|
981
|
+
is "install <harness>, or override it on this machine in oats-local.yaml
|
|
982
|
+
souls.launch" (from the soul or the host), "install <harness>, or change
|
|
983
|
+
oats-local.yaml souls.launch" (from local layers), or "install <harness>, or
|
|
984
|
+
choose another --harness / --launch-config" (from a flag).
|
|
985
|
+
- `E_LAUNCH_CONFIG_UNKNOWN {name, from, at}`: `souls.launch` names a launch
|
|
986
|
+
configuration this `oats-local.yaml` does not declare.
|
|
987
|
+
- `E_WORKSPACE_SCHEMA`: a malformed `launch` or `souls.launch`, path named.
|
|
988
|
+
|
|
989
|
+
### The launch report (`Launch`)
|
|
1087
990
|
|
|
1088
991
|
```json
|
|
1089
|
-
{"
|
|
1090
|
-
"
|
|
1091
|
-
"
|
|
1092
|
-
"
|
|
1093
|
-
"hosting":{"host":"github.com/acme/agents","hostIsMember":true,
|
|
1094
|
-
"rule":"If any member is private, host oats-workspace.yaml in a private repo that is not a public member (a dedicated <org>/workspace repo); public contributors then use the standalone case (from: here capabilities + oats.core)."},
|
|
1095
|
-
"next":{"clone":[{"key":"github.com/acme/agents","name":"agents","url":"https://github.com/acme/agents.git","dir":"/abs/acme-workspace/agents-repo"},
|
|
1096
|
-
{"key":"github.com/acme/platform","name":"platform","url":"https://github.com/acme/platform.git","dir":"/abs/acme-workspace/platform"}],
|
|
1097
|
-
"spawn":"oats spawn oats-setup-expert --dir /abs/acme-workspace"}}
|
|
992
|
+
{"declared":{"harness":"claude","model":"claude-opus-5-5"},
|
|
993
|
+
"effective":{"harness":"codex","model":null,"launchConfig":null},
|
|
994
|
+
"from":"local","at":"oats-local.yaml#/souls/launch/oats.engineering~1code-reviewer",
|
|
995
|
+
"problem":null}
|
|
1098
996
|
```
|
|
1099
997
|
|
|
1100
|
-
- `
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
`
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
`
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
`
|
|
1136
|
-
|
|
1137
|
-
|
|
998
|
+
- `declared`: the soul's own `launch` as `{harness, model}` (`model` may be
|
|
999
|
+
`null`), or `null` when the soul declares none.
|
|
1000
|
+
- `effective`: `{harness, model, launchConfig}`. `model` is the id passed to the
|
|
1001
|
+
harness, or `null` (the harness's own). `launchConfig` is a configuration
|
|
1002
|
+
name or `null`.
|
|
1003
|
+
- `from`: `"flag"`, `"local"`, `"local-default"`, `"soul"` or `"host"`; on a
|
|
1004
|
+
home, also `"recorded"` (a home from before 0.30).
|
|
1005
|
+
- `at`: where the deciding value lives, or `null` (a flag, the host):
|
|
1006
|
+
`"oats-local.yaml#/souls/launch/<key>"` (JSON-pointer escaped: `/` is `~1`),
|
|
1007
|
+
`"oats-local.yaml#/souls/launch/*"`, `"<repoKey>:<path>/soul.yaml#/launch"`,
|
|
1008
|
+
or `"package:<id>:<path>/soul.yaml#/launch"`.
|
|
1009
|
+
- `problem`: `null`, or `{code, message, fix}` when a spawn would refuse
|
|
1010
|
+
(`E_HARNESS_UNAVAILABLE`, `E_LAUNCH_CONFIG_UNKNOWN`, or `E_LAUNCH_EXECUTABLE`
|
|
1011
|
+
for a configuration whose own executable is missing). Only reports carry it;
|
|
1012
|
+
spawn and preview refuse instead. With `E_LAUNCH_CONFIG_UNKNOWN`,
|
|
1013
|
+
`effective` is the host default (`pi`, `null`, `null`): the named
|
|
1014
|
+
configuration launches nothing here.
|
|
1015
|
+
- In listings (`oats souls`, `inspect --soul`, `launchCurrent`) `model` is the
|
|
1016
|
+
configured id: no model catalogue is probed. The preview's `model` is the
|
|
1017
|
+
resolved one.
|
|
1018
|
+
|
|
1019
|
+
### Where the launch appears
|
|
1020
|
+
|
|
1021
|
+
- **`oats spawn … --preview --json`**: `launch`, flags applied. The existing
|
|
1022
|
+
`harness`, `model`, `launchConfig` equal `launch.effective`, and
|
|
1023
|
+
`decision.effective` binds them (a preference edited between preview and
|
|
1024
|
+
apply is `E_DECISION_STALE`).
|
|
1025
|
+
- **`oats inspect --soul <name>`** and **`oats souls` rows**: `launch` as a
|
|
1026
|
+
spawn with no flags would decide it here (`from` is never `"flag"`). A soul
|
|
1027
|
+
whose harness is missing still lists, with `problem` set. The souls rows'
|
|
1028
|
+
Desktop facts `harness`, `model`, `harnessFrom` equal `launch.effective`;
|
|
1029
|
+
`harnessFrom` is `"soul"`, `"local"`, `"local-default"` or `"kernel-default"`
|
|
1030
|
+
(the host default).
|
|
1031
|
+
- **`oats inspect --home <abs>`**: `launch` is the record (`from` the recorded
|
|
1032
|
+
layer; `declared` the soul's preference then), and `launchCurrent: Launch |
|
|
1033
|
+
null` is what `--reselect-launch` would choose now: the home's recorded soul
|
|
1034
|
+
copy's `launch` and this deployment's `souls.launch` as they are now (`null`
|
|
1035
|
+
when `oats-local.yaml` cannot be read).
|
|
1036
|
+
- **`instance.json`** (a spawn, and a start that makes a new selection):
|
|
1037
|
+
`launchFrom` (a `from` value), `launchAt` (its `at`) and `launchDeclared`
|
|
1038
|
+
(the soul's own preference then, `{harness, model}` or `null`). All three
|
|
1039
|
+
are absent on a home from before 0.30. A start with `--launch-config` or
|
|
1040
|
+
`--harness` records `launchFrom: "flag"`; a plain or `--model`-only start
|
|
1041
|
+
keeps them. `harness`, `model`, `launchConfig` and the recipe
|
|
1042
|
+
record the effective launch as before.
|
|
1043
|
+
- **`modelFrom`** (instance.json and roster rows) gains `"local"` and
|
|
1044
|
+
`"local-default"` (an inline override's model). `"soul"` is the soul's
|
|
1045
|
+
`launch.model`.
|
|
1046
|
+
- **Readiness** (`--home` only), in `checks.configured`: `launch-changed`, a
|
|
1047
|
+
warning (`required: false`), `subject: "launch"`, `producer: "launch
|
|
1048
|
+
preference"`, with `recorded` and `current` (each `{harness, model,
|
|
1049
|
+
launchConfig}`), `from` and `at` (the current layer's). `remedy`:
|
|
1050
|
+
"`oats session restart --reselect-launch`, or respawn". Only a home whose
|
|
1051
|
+
recorded launch a layer chose (`launchFrom` `local`, `local-default`,
|
|
1052
|
+
`soul` or `host`) is compared: one launched with explicit flags, or from
|
|
1053
|
+
before 0.30, never warns.
|
|
1054
|
+
- **`--reselect-launch`** with `--launch-config` is `E_BAD_ARGS` (choose one);
|
|
1055
|
+
with `--harness` the flag decides, as at spawn.
|
|
1056
|
+
|
|
1057
|
+
No verb writes `souls.launch`; it is plain YAML. A GUI that edits it checks
|
|
1058
|
+
the result with `oats inspect --soul <name> --json`.
|
|
1059
|
+
|
|
1060
|
+
## Spawn
|
|
1061
|
+
|
|
1062
|
+
### The preview
|
|
1138
1063
|
|
|
1139
|
-
```
|
|
1140
|
-
|
|
1141
|
-
"workspace":{"name":"acme","key":"github.com/acme/agents","url":"https://github.com/acme/agents.git","commit":"<oid>","observedAt":"<iso>",
|
|
1142
|
-
"local":"/abs/acme-workspace/oats-local.yaml","lock":"/abs/acme-workspace/oats-lock.json"},
|
|
1143
|
-
"members":[{"key":"github.com/acme/agents","name":"agents","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"global",
|
|
1144
|
-
"souls":["release-manager"],"capabilities":["acme-house-style"],"publishes":null},
|
|
1145
|
-
{"key":"github.com/acme/tools","name":"tools","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"engineering",
|
|
1146
|
-
"souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
|
|
1147
|
-
{"key":"github.com/acme/billing","name":"billing","commit":"<oid>","confirmed":false,"status":"no-backlink","detail":"github.com/acme/billing@… has no oats-membership.yaml","team":null,
|
|
1148
|
-
"souls":[],"capabilities":[],"publishes":null}],
|
|
1149
|
-
"packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"],"souls":[]},
|
|
1150
|
-
{"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"],"souls":["release-reviewer"]}],
|
|
1151
|
-
"changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>"}],
|
|
1152
|
-
"problems":[]}
|
|
1064
|
+
```text
|
|
1065
|
+
oats spawn <soul> [the flags of a real spawn] --preview --json
|
|
1153
1066
|
```
|
|
1154
1067
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
- `packages[].souls` (feature `package-souls`, 0.28.0): the names of the
|
|
1160
|
-
package souls the lock records for that package (`[]` when none).
|
|
1161
|
-
- `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
|
|
1162
|
-
the package was dropped from `packages:` and from the lock.
|
|
1163
|
-
- `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
|
|
1164
|
-
problems (`E_WORKSPACE_SCHEMA`, `E_TEAM_UNKNOWN`, `E_REMOTE_*`, …). Never an
|
|
1165
|
-
abort: an unreadable member directory is a problem of that member.
|
|
1166
|
-
- Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA { path, problems[] }`,
|
|
1167
|
-
`E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING` (catalog has no
|
|
1168
|
-
such id), `E_PACKAGE_INTEGRITY { why: "branch" | locked/observed }`,
|
|
1169
|
-
`E_PACKAGE_MANIFEST`, `E_REPO_REF`, `E_BAD_ARGS` (`--approve`: package
|
|
1170
|
-
approval was removed).
|
|
1171
|
-
|
|
1172
|
-
### `oats package add <id> <version|git:<repo>@<ref>> | remove <id> [--dir] --json`
|
|
1173
|
-
|
|
1174
|
-
Edits `packages:` **only** when `oats-workspace.yaml` is tracked by the Git
|
|
1175
|
-
checkout walked up from `--dir`; otherwise reports the line to add.
|
|
1068
|
+
Feature `spawn-preview-2`, `spawnPreviewApi: 2`. The preview runs every
|
|
1069
|
+
preflight a spawn runs and writes nothing, on success or refusal. It reads the
|
|
1070
|
+
soul from the per-commit cache (`agents/<soul>/souls/<commit12>/`) or fetches
|
|
1071
|
+
it to a temporary copy (`soulFetched: true`).
|
|
1176
1072
|
|
|
1177
1073
|
```json
|
|
1178
|
-
{"
|
|
1179
|
-
{"
|
|
1074
|
+
{"modules":[
|
|
1075
|
+
{"name":"nw-tools","from":{"kind":"member","repoKey":"github.com/nw/agents","commit":"66566512…"},"layer":null,"private":false,"declares":[],
|
|
1076
|
+
"changedSince":{"instance":"rm-2","was":"45b86f64…"}},
|
|
1077
|
+
{"name":"oats.okf","from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"ab897841…","integrity":"sha256-bada35…","repoKey":"github.com/awebai/oats-okf"},
|
|
1078
|
+
"layer":"knowledge","private":false,"declares":["state-dir"],"changedSince":false}],
|
|
1079
|
+
"teams":[{"label":"eng","team":"eng:nw.aweb.ai","default":true,"from":"shared"}],
|
|
1080
|
+
"defaultTeam":{"label":"eng","team":"eng:nw.aweb.ai","from":"soul"},
|
|
1081
|
+
"resolution":"abacbdb5a7975098d77007c8","declRevision":"068a0d3f1311a9e84e9aff2e","payloadRevision":"81a368006c610194aa35dbe0",
|
|
1082
|
+
"workspace":"github.com/nw/agents","standalone":false,"providers":{},
|
|
1083
|
+
"settings":{"nw-tools":{},"oats.okf":{"owns":"rm"}},
|
|
1084
|
+
"settingsOrigins":{"nw-tools":{},"oats.okf":{"/owns":{"kind":"soul","at":"soul.yaml#/knowledge"}}},
|
|
1085
|
+
"spawnPreviewApi":2,"preview":true,"agent":"rm","kind":"persistent","instance":"rm-api","home":"/w/agents/rm/instances/rm-api",
|
|
1086
|
+
"repo":"/w/agents-repo","work":"worktree","subject":{"soul":"rm","agentsRoot":null,"dir":"/w"},
|
|
1087
|
+
"decision":{"instance":"rm-api","home":"/w/agents/rm/instances/rm-api","branch":"agents/rm-api","base":{"ref":"HEAD","oid":"66566512…"},
|
|
1088
|
+
"effective":{"repo":"/w/agents-repo","work":"worktree","harness":"pi","model":null,"launchConfig":null,"yolo":null,"backend":"tmux",
|
|
1089
|
+
"childSpawns":true,"relation":null,"providers":{"nw-tools":{},"oats.okf":{"owns":"rm"}}},
|
|
1090
|
+
"resolution":"abacbdb5a7975098d77007c8","revision":"c557d8ec9a272ba1c1739dc3"},
|
|
1091
|
+
"preflight":{"status":"complete","budgetMs":20000,"elapsedMs":53},"backendStatus":{"name":"tmux","installed":true,"started":false},
|
|
1092
|
+
"harness":"pi","model":null,"modelSource":"native default","launchConfig":null,"backend":"tmux",
|
|
1093
|
+
"branch":"agents/rm-api","base":{"ref":"HEAD","oid":"66566512…"},"worktree":"/w/agents/rm/instances/rm-api/work",
|
|
1094
|
+
"relation":null,"parentInstance":null,"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"no spawn option: children allowed"}}},
|
|
1095
|
+
"executable":"/usr/local/bin/pi",
|
|
1096
|
+
"capabilities":[{"name":"nw-tools","origin":"member:github.com/nw/agents@66566512…"},{"name":"oats.okf","origin":"package:oats.okf@2.1.3"}],
|
|
1097
|
+
"skills":["release-checklist",{"name":"okf","source":"module:oats.okf"}],
|
|
1098
|
+
"task":null,"soulFetched":true}
|
|
1180
1099
|
```
|
|
1181
1100
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
`
|
|
1190
|
-
`
|
|
1191
|
-
|
|
1192
|
-
|
|
1101
|
+
**Placement.**
|
|
1102
|
+
- `instance`: `<agent>-<purpose>` with `--purpose`, else `<agent>-<n>`,
|
|
1103
|
+
de-duplicated across the deployment; or exactly `--name`
|
|
1104
|
+
([Instance names](#instance-names)). `home` and `worktree` (worktree mode,
|
|
1105
|
+
else `null`) are canonical: never derive paths.
|
|
1106
|
+
- `repo`: `--repo`, else the `clones:` entry, else `<deployment>/<member>`.
|
|
1107
|
+
- `branch` defaults to `agents/<instance>` (`--branch` overrides); `base` is
|
|
1108
|
+
`--base` (default `HEAD`) resolved to `oid`. `E_BRANCH_EXISTS` and
|
|
1109
|
+
`E_BASE_UNKNOWN` refuse preview and apply alike.
|
|
1110
|
+
- `subject` echoes `{soul, agentsRoot, dir}` byte-exact.
|
|
1111
|
+
|
|
1112
|
+
**Launch.**
|
|
1113
|
+
- `harness`, `model`, `modelSource`, `launchConfig`, `backend`, `yolo`
|
|
1114
|
+
(absent when nothing sets it) are the resolved selection.
|
|
1115
|
+
`backendStatus` is `{name, installed, started: false}`, `null` with
|
|
1116
|
+
`--no-launch`. `executable` is the resolved harness binary.
|
|
1117
|
+
- `modelSource` is `"explicit"`, `"soul default"`, `"launch-config <name>"`,
|
|
1118
|
+
`"native default"` or `"native default (explicit)"` (`--model
|
|
1119
|
+
@native-default`). Omitting `--model` and asking for the native default are
|
|
1120
|
+
different requests.
|
|
1121
|
+
- `preflight`: `{status: "complete" | "timeout", budgetMs, elapsedMs}`; all
|
|
1122
|
+
native probes share one 20 s budget.
|
|
1123
|
+
- `policy.childSpawns` is what the instance will record.
|
|
1124
|
+
|
|
1125
|
+
**Composition.**
|
|
1126
|
+
- `modules[]` (feature `instance-modules`): `{name, from, layer, private,
|
|
1127
|
+
declares, changedSince}`; `from` is what `instance.json` will record.
|
|
1128
|
+
`changedSince` is `null` (no previous instance), `false` (unchanged since the
|
|
1129
|
+
newest one) or `{instance, was}`.
|
|
1130
|
+
- `capabilities[]` (`{name, origin}`, `origin` `package:<id>@<v>` or
|
|
1131
|
+
`member:<repoKey>@<commit>`) and `skills[]` (the soul's own skills as
|
|
1132
|
+
strings, module skills as `{name, source: "module:<cap>"}`) are display
|
|
1133
|
+
only: bind to `modules[]` and `decision`.
|
|
1134
|
+
- `resolution` (24 hex) hashes `declRevision` (the declarations) and
|
|
1135
|
+
`payloadRevision` (the merged payloads). `workspace` is the host key;
|
|
1136
|
+
`standalone` marks a standalone view. `task` is the task text or `null`.
|
|
1137
|
+
|
|
1138
|
+
**Provider settings.**
|
|
1139
|
+
- `providers` is the `--provider` map as typed.
|
|
1140
|
+
- `settings.<cap>`: the merged payload (manifest defaults, then workspace,
|
|
1141
|
+
soul, `oats-local.yaml` `settings.<cap>`, `--provider`).
|
|
1142
|
+
- `settingsOrigins.<cap>` (feature `settings-origins`) maps each leaf pointer
|
|
1143
|
+
(`/identity/mode`) to `{kind, at}`: `kind` is `manifest-default | workspace |
|
|
1144
|
+
soul | host | spawn`, `at` names the place.
|
|
1145
|
+
- `--provider <cap> <key>=<value>` (feature `spawn-provider-payload`,
|
|
1146
|
+
repeatable, `a.b=c` nests): a malformed pair is `E_BAD_ARGS`; a capability
|
|
1147
|
+
the soul does not resolve is `E_CAPABILITY_MISSING {capability, soul,
|
|
1148
|
+
modules}`.
|
|
1149
|
+
|
|
1150
|
+
### The decision
|
|
1151
|
+
|
|
1152
|
+
`decision` is `{instance, home, branch, base, effective, resolution,
|
|
1153
|
+
revision}`. `effective` (feature `spawn-apply-2`) is `{repo, work, harness,
|
|
1154
|
+
model, launchConfig, yolo, backend, childSpawns, relation, providers}`;
|
|
1155
|
+
`relation` is `null` or `{kind, anchor: {instance, agentsRoot}}`; `providers`
|
|
1156
|
+
(feature `served-identity`) equals `settings`. `revision` (24 hex) hashes the
|
|
1157
|
+
decision.
|
|
1158
|
+
|
|
1159
|
+
Apply with `oats spawn <soul> … --expect-decision <revision> --json`. Any drift
|
|
1160
|
+
refuses `E_DECISION_STALE` with the fresh `details.decision`; nothing is
|
|
1161
|
+
created. Without `--expect-decision` the CLI keeps its interactive
|
|
1162
|
+
auto-suffix.
|
|
1163
|
+
|
|
1164
|
+
### Apply
|
|
1165
|
+
|
|
1166
|
+
Feature `spawn-apply-2`, `spawnApplyApi: 1`. The Desktop gates on
|
|
1167
|
+
`spawn-preview-2`, `spawn-apply-2` and `spawn-idempotency-2`.
|
|
1168
|
+
|
|
1169
|
+
- Backend startup runs only after the decision check and the placement
|
|
1170
|
+
reservation; a missing backend binary is refused before placement.
|
|
1171
|
+
- The home is reserved with a non-recursive `mkdir`. A concurrent loser
|
|
1172
|
+
refuses `E_PLACEMENT_TAKEN {instance, home}` having touched nothing. Names
|
|
1173
|
+
are deployment-wide: a same-name race with another soul ends in
|
|
1174
|
+
`E_INSTANCE_NAME_TAKEN` (for `--name`) or `E_PLACEMENT_TAKEN`.
|
|
1175
|
+
- A refused child spawn appends `child-spawn-refused` to the parent's log (on
|
|
1176
|
+
apply only).
|
|
1177
|
+
|
|
1178
|
+
**Idempotency** (feature `spawn-idempotency-2`): `--idempotency-key <key>`
|
|
1179
|
+
with `--expect-decision` records the key and decision in `instance.json`.
|
|
1180
|
+
- Recovery runs right after naming, before placement or preflight. A retry
|
|
1181
|
+
with the same key replays the receipt (`replayed: true`, no second spawn,
|
|
1182
|
+
no second wake). The same key with another decision is
|
|
1183
|
+
`E_IDEMPOTENCY_CONFLICT {instance, home}`.
|
|
1184
|
+
- `spawnCompleted` is `false` until launch, lineage and events are done; a
|
|
1185
|
+
retry of an unfinished spawn is `E_SPAWN_INCOMPLETE {instance, home,
|
|
1186
|
+
launched}` (recover through the session surface).
|
|
1187
|
+
- The key lives in the home. Mint it on the first confirmation and keep it
|
|
1188
|
+
for that intent's retries.
|
|
1189
|
+
- `wake: {requested, saved, error}` is recorded and replayed; `saved: null`
|
|
1190
|
+
means the outcome was not recorded.
|
|
1191
|
+
|
|
1192
|
+
**Result** (`oats spawn <soul> … --json`):
|
|
1193
1193
|
|
|
1194
1194
|
```json
|
|
1195
|
-
{"
|
|
1196
|
-
"
|
|
1197
|
-
"
|
|
1198
|
-
"
|
|
1199
|
-
"
|
|
1200
|
-
|
|
1201
|
-
"stale":[],
|
|
1202
|
-
"external":[{"source":"git:github.com/oss-collective/experts@<oid>","soul":"security-reviewer","team":"unassigned"}],
|
|
1203
|
-
"problems":[],
|
|
1204
|
-
"warnings":[]}
|
|
1195
|
+
{"instance":"rm-api","agent":"rm","home":"/w/agents/rm/instances/rm-api","work":"worktree","branch":"agents/rm-api","launched":true,"warnings":[],
|
|
1196
|
+
"tmux":{"session":"pi-agents","window":"rm-api"},"repo":"/w/agents-repo","harness":"pi","model":null,"parent":null,"sibling":null,"relation":null,
|
|
1197
|
+
"spawnOrigin":"operator","attach":"tmux attach -t pi-agents","decision":{"instance":"rm-api","revision":"c557d8ec9a272ba1c1739dc3"},"replayed":false,
|
|
1198
|
+
"wake":{"requested":false,"saved":null,"error":null},"launchConfig":null,
|
|
1199
|
+
"launch":{"version":2,"harness":"pi","launchConfig":null,"launchConfigSource":null,"executable":"/usr/local/bin/pi","executableDeclared":null,
|
|
1200
|
+
"executableResolvedFrom":"PATH","args":[],"env":{},"model":null,"hooks":{"launch":{},"env":{},"contributions":[]},"prompt":{"kind":"task-file","file":"TASK.md"}}}
|
|
1205
1201
|
```
|
|
1206
1202
|
|
|
1207
|
-
`
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1203
|
+
(`decision` is abridged: it is the full bound decision.)
|
|
1204
|
+
|
|
1205
|
+
- Always present: `instance, agent, home, work, branch, launched, warnings
|
|
1206
|
+
(array), tmux ({session, window} | null), repo, harness, model, parent,
|
|
1207
|
+
sibling, relation, spawnOrigin (operator | instance), attach, launchConfig,
|
|
1208
|
+
launch` (the redacted recipe).
|
|
1209
|
+
- When they apply: `sessionTarget` (Herdr), `yolo`, `decision` and
|
|
1210
|
+
`replayed` (bound apply), `wake` (keyed apply), `wakeSchedule` and
|
|
1211
|
+
`wakeScheduleError` (a requested wake).
|
|
1212
|
+
|
|
1213
|
+
<a id="instance-names"></a>
|
|
1214
|
+
### Instance names
|
|
1215
|
+
|
|
1216
|
+
Feature `spawn-name`. `--name <slug>` is the exact name, with no prefix.
|
|
1217
|
+
- `--name` with `--purpose`, or without a value: `E_BAD_ARGS`.
|
|
1218
|
+
- A name that is not a slug (lowercase letters and digits, single dashes),
|
|
1219
|
+
equals a soul name, or exceeds 64 characters (derived names included, with
|
|
1220
|
+
their suffix) is `E_INSTANCE_NAME_INVALID`.
|
|
1221
|
+
- A name any soul's `instances/` holds, or a live tmux window carries, is
|
|
1222
|
+
`E_INSTANCE_NAME_TAKEN {instance, home, session?}`; a typed name never gets
|
|
1223
|
+
a silent `-2`.
|
|
1224
|
+
- The name is part of the decision.
|
|
1225
|
+
|
|
1226
|
+
<a id="spawn-errors"></a>
|
|
1227
|
+
### Spawn errors
|
|
1228
|
+
|
|
1229
|
+
| Code | Details | When |
|
|
1230
|
+
|---|---|---|
|
|
1231
|
+
| `E_USAGE`, `E_BAD_ARGS` | | no soul; bad, contradictory or removed flags |
|
|
1232
|
+
| `E_LOCAL_MISSING`, `E_NO_DEPLOYMENT` | | no `oats-local.yaml`; no `agents/` root |
|
|
1233
|
+
| `E_SOUL_UNKNOWN` | `{name, members, packages}` | no such soul, or not at `--agents-root` |
|
|
1234
|
+
| `E_SOUL_AMBIGUOUS` | `{name, repos, qualified}` | several souls answer the bare name; use one of `qualified` |
|
|
1235
|
+
| `E_SOUL_DISABLED` | `{name, qualifiedName, entry}` | listed in `souls.disabled` |
|
|
1236
|
+
| `E_UNKNOWN_AGENT` | | the resolved soul is not under the deployment's agents root |
|
|
1237
|
+
| `E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE` | `{label, at}`, `{soul, label, at}` | the soul's teams do not resolve |
|
|
1238
|
+
| `E_NOT_A_MEMBER`, `E_MEMBERSHIP_UNCONFIRMED` | | the soul's repository is not a confirmed member |
|
|
1239
|
+
| `E_CAPABILITY_MISSING`, `E_CAPABILITY_PRIVATE`, `E_CAPABILITY_INCOMPATIBLE`, `E_COMPATIBILITY` | | a capability cannot be resolved |
|
|
1240
|
+
| `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`, `E_LOCK_SCHEMA` | | the lock does not provide it (standalone: `{…, standalone: true, reason: "no-catalog", catalog}`) |
|
|
1241
|
+
| `E_SLOT_CONFLICT`, `E_SKILL_DUPLICATE` | | the composition conflicts |
|
|
1242
|
+
| `E_WORKSPACE_SCHEMA` | `{path, key, reason}` | a removed key or invalid payload |
|
|
1243
|
+
| `E_CLONE_MISSING`, `E_CLONE_MISMATCH` | | the member's clone is missing or wrong |
|
|
1244
|
+
| `E_REQUIREMENT_INACTIVE` | `{soul, capabilities, context, remedy}` | a declared requirement is not active |
|
|
1245
|
+
| `E_CHILD_SPAWNS_DISABLED` | `{parent, policy}` | the parent's policy is off |
|
|
1246
|
+
| `E_PARENT_NOT_FOUND`, `E_RELATIVE_NOT_FOUND` | | the anchor name matches no instance |
|
|
1247
|
+
| `E_RELATIVE_AMBIGUOUS` | | the anchor matches several instances (`--relative-root` picks one) or a same-named instance would shadow the edge |
|
|
1248
|
+
| `E_BRANCH_EXISTS`, `E_BASE_UNKNOWN` | | |
|
|
1249
|
+
| `E_INSTANCE_NAME_INVALID`, `E_INSTANCE_NAME_TAKEN` | see above | |
|
|
1250
|
+
| `E_DECISION_STALE` | `{decision}` | |
|
|
1251
|
+
| `E_PLACEMENT_TAKEN`, `E_IDEMPOTENCY_CONFLICT` | `{instance, home}` | |
|
|
1252
|
+
| `E_SPAWN_INCOMPLETE` | `{instance, home, launched}` | |
|
|
1253
|
+
| `E_LAUNCH_*`, `E_MODEL_UNKNOWN`, `E_UNSUPPORTED_HARNESS` | | the launch selection is refused |
|
|
1254
|
+
| `E_SCHEDULE_INVALID` | | a bad wake (`--wake-json`, `--wake-file`, `--wake-*`) |
|
|
1255
|
+
| `E_SPAWN_FAILED` | | anything else |
|
|
1256
|
+
|
|
1257
|
+
## `instance.json` and the roster
|
|
1258
|
+
|
|
1259
|
+
<a id="instancejson"></a>
|
|
1260
|
+
### `instance.json`
|
|
1261
|
+
|
|
1262
|
+
Written by the spawn; read by the roster and every `--home` command. The
|
|
1263
|
+
workspace-model fields (feature `instance-modules`):
|
|
1211
1264
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1265
|
+
```json
|
|
1266
|
+
{"agent":"rm","kind":"persistent","instance":"rm-api","home":"/w/agents/rm/instances/rm-api","soulDir":"/w/agents/rm/souls/66566512168e",
|
|
1267
|
+
"repo":"/w/agents-repo","work":"worktree","branch":"agents/rm-api","harness":"pi","modelFrom":"harness-default","spawnOrigin":"operator",
|
|
1268
|
+
"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"no spawn option: children allowed"}}},
|
|
1269
|
+
"modules":{"oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"ab897841…","integrity":"sha256-bada35…","repoKey":"github.com/awebai/oats-okf"},
|
|
1270
|
+
"commit":"ab897841…","digest":"sha256-9a0e…","materializedAt":"2026-09-28T10:08:01.100Z"}},
|
|
1271
|
+
"providers":{"oats.okf":{"owns":"rm","state-dir":"/Users/ana/.oats/okf"}},
|
|
1272
|
+
"workspace":{"key":"github.com/nw/agents","name":"northwind","deployment":"/w","commit":"66566512…","resolution":"abacbdb5a7975098d77007c8","standalone":false,
|
|
1273
|
+
"soul":{"id":"github.com/nw/agents#rm","repoKey":"github.com/nw/agents","commit":"66566512…"},
|
|
1274
|
+
"layers":{"knowledge":{"capability":"oats.okf","from":"workspace"},"messaging":null,"tasks":null}},
|
|
1275
|
+
"teams":[{"label":"mine","team":"mine:ana.aweb.ai","default":false,"from":"local"}],
|
|
1276
|
+
"defaultTeam":{"label":"eng","team":null,"from":"soul"},
|
|
1277
|
+
"capabilities":[{"id":"oats.okf","layer":"knowledge","command":"okf","origin":"package:oats.okf@2.1.3","level":"/w/agents/rm/instances/rm-api",
|
|
1278
|
+
"settings":{"owns":"rm","state-dir":"/Users/ana/.oats/okf"},"settingsOrigins":{},"provenance":["package oats.okf v2.1.3"],
|
|
1279
|
+
"skills":["/w/agents/rm/instances/rm-api/.agents/skills/oats.okf/okf"],"hooks":["retire","spawn"],"trusted":true}],
|
|
1280
|
+
"skills":[{"name":"release-checklist","source":"soul"},{"name":"okf","source":"module:oats.okf"}],
|
|
1281
|
+
"createdAt":"2026-09-28T10:08:01.281Z"}
|
|
1282
|
+
```
|
|
1214
1283
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
(
|
|
1284
|
+
Abridged: the record also carries the launch recipe and command,
|
|
1285
|
+
composition evidence, the capability runtime, the tmux or Herdr target,
|
|
1286
|
+
lineage (`parentInstance`, `siblingInstance`, `relation`, `relativeTo`), and
|
|
1287
|
+
the keyed-spawn fields `decision`, `spawnIdempotencyKey`, `spawnCompleted` and
|
|
1288
|
+
`wake`; later starts add `restarts` and `restartCount`.
|
|
1289
|
+
|
|
1290
|
+
- `modules.<cap>`: `{from, commit, digest, materializedAt}`; `digest` hashes
|
|
1291
|
+
the copy at `<home>/.oats/modules/<cap>/`. Module skills are copied to
|
|
1292
|
+
`<home>/.agents/skills/<cap>/<skill>/`.
|
|
1293
|
+
- `providers.<cap>`: the merged payload (`{}` when none).
|
|
1294
|
+
- `workspace`: `{key, name, deployment, commit, resolution, standalone, soul,
|
|
1295
|
+
layers}`. `name` is recorded, and every hook, command and operation of the
|
|
1296
|
+
home receives it as `OATS_WORKSPACE_NAME`. `soul.id` is `<repoKey>#<soul>`,
|
|
1297
|
+
or `package:<id>#<soul>` for a package soul, which also records `name`,
|
|
1298
|
+
`qualifiedName` and `package: {id, version, commit, digest, path}`.
|
|
1299
|
+
`layers.<slot>` is `{capability, from}` or `null`. A standalone spawn
|
|
1300
|
+
records `standalone: true` and the member's key.
|
|
1301
|
+
- `teams` (mapped rows, as the providers received them) and `defaultTeam`
|
|
1302
|
+
are never rewritten.
|
|
1303
|
+
- `soulDir` is the soul the instance incarnates; hooks receive it as
|
|
1304
|
+
`OATS_SOUL`. Homes carry no `soul` link.
|
|
1305
|
+
- `modelFrom`: see the roster. `trigger` (a triggered instance): `{id, key,
|
|
1306
|
+
source, repo, number, url, event, headSha, observedAt, eventFile}`.
|
|
1307
|
+
`capabilityMeta.<cap>.identity`: the served identity a provider recorded.
|
|
1308
|
+
|
|
1309
|
+
<a id="the-roster-oats-status---json"></a>
|
|
1310
|
+
### The roster (`oats status --json`)
|
|
1218
1311
|
|
|
1219
|
-
|
|
1312
|
+
```text
|
|
1313
|
+
oats status [--dir <d>] --json
|
|
1314
|
+
```
|
|
1220
1315
|
|
|
1221
|
-
|
|
1222
|
-
capabilities, sorted by name then origin. Souls have no private mode (their
|
|
1223
|
-
`private` is always `false`); a repo-owned capability is listed with
|
|
1224
|
-
`private: true` — usable only by its own repo's souls. The Desktop shows its
|
|
1225
|
-
"Repo owned" section when `version --json` lists the `capabilities-private`
|
|
1226
|
-
feature. `origin` is the
|
|
1227
|
-
human string (`member <key> @ <8-char commit>` | `package <id> v<version>` |
|
|
1228
|
-
`external <key> @ <commit>`); `kind` is the machine field. `team` is the label
|
|
1229
|
-
or `"unassigned"`.
|
|
1316
|
+
Not an envelope: `{root, agents, workspace?, problems?, warnings?}`.
|
|
1230
1317
|
|
|
1231
1318
|
```json
|
|
1232
|
-
{"
|
|
1233
|
-
"
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1319
|
+
{"root":"/w/agents",
|
|
1320
|
+
"agents":[{"name":"rm","description":"Cuts releases.","work":"worktree","kind":"persistent","dir":"/w/agents/rm",
|
|
1321
|
+
"soulSource":{"repoKey":"github.com/nw/agents","commit":"66566512…","path":"souls/rm","current":"66566512…","status":"current"},
|
|
1322
|
+
"instances":[{"agent":"rm","instance":"rm-api","home":"/w/agents/rm/instances/rm-api","harness":"pi","launched":true,
|
|
1323
|
+
"createdAt":"2026-09-28T10:08:01.281Z","modelFrom":"harness-default","startedAt":"2026-09-28T10:08:01.281Z","identityAddress":null,"running":true,
|
|
1324
|
+
"modules":[{"name":"oats.okf","from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"ab897841…","integrity":"sha256-bada35…","repoKey":"github.com/awebai/oats-okf"},
|
|
1325
|
+
"commit":"ab897841…","current":{"commit":"ab897841…","version":"2.1.3"},"status":"current"}],
|
|
1326
|
+
"soul":{"repoKey":"github.com/nw/agents","commit":"66566512…","current":"66566512…","status":"current"}}]}],
|
|
1327
|
+
"workspace":{"reachable":true}}
|
|
1239
1328
|
```
|
|
1240
1329
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1330
|
+
- **Agent rows**: the soul's recorded definition plus `dir` and `instances`,
|
|
1331
|
+
and (feature `launch-preference`) `key`: the soul key, as on
|
|
1332
|
+
[soul rows](#oats-capabilities-and-oats-souls), or `null` when no instance
|
|
1333
|
+
records a workspace soul;
|
|
1334
|
+
`soulSource` (`{repoKey, commit, path, current?, status?}`, `current` or
|
|
1335
|
+
`moved`) for a workspace soul; `retireFailures[]` (`{instance, completedAt,
|
|
1336
|
+
error, incomplete, retry, resultPath}`) when a deferred self-retire failed.
|
|
1337
|
+
A capability agent's row is `{name, kind: "capability", capability,
|
|
1338
|
+
description, dir, instances}`.
|
|
1339
|
+
- **Instance rows**: the home's `instance.json` (launch recipe and command
|
|
1340
|
+
redacted) plus `home` and `instance` (from the directory; a disagreeing
|
|
1341
|
+
claim is kept as `recordedHome`/`recordedInstance`), `running` (`null` when
|
|
1342
|
+
a Herdr session is unreachable, with `runtimeState`/`runtimeError`),
|
|
1343
|
+
`identity` when a provider recorded one, `rollbackIncomplete` and
|
|
1344
|
+
`retirePending` when present, and the Desktop facts below.
|
|
1345
|
+
- **`modules`** becomes drift rows `{name, from, commit, current, status,
|
|
1346
|
+
reason?}` when the workspace was read. `status` is `current`, `moved` or
|
|
1347
|
+
`missing` (`reason`: `capability-absent`, `package-absent`, or the member's
|
|
1348
|
+
unconfirmed reason; `current: null`). `current` is `{commit, version}`
|
|
1349
|
+
(`version` on member rows is a Desktop fact). A package module without a
|
|
1350
|
+
lock reads `current`.
|
|
1351
|
+
- **`soul`** is the soul source's drift `{repoKey, commit, current, status,
|
|
1352
|
+
reason?}`; a package soul adds `package`, `version`, `currentVersion`
|
|
1353
|
+
(`missing` reasons: `package-absent`, `soul-absent`).
|
|
1354
|
+
- **`workspace`**: `{reachable: true}`, or `{reachable: false, code, reason,
|
|
1355
|
+
message}` (modules then stay the recorded map). Absent without
|
|
1356
|
+
`oats-local.yaml`.
|
|
1357
|
+
- `problems`: the legacy-home rows ([dispatch errors](#dispatch-errors)).
|
|
1358
|
+
`warnings`: envelope warnings. `--team` is `E_BAD_ARGS` (an envelope).
|
|
1359
|
+
|
|
1360
|
+
**Desktop facts** (feature `desktop-facts`): `startedAt` is the last start or
|
|
1361
|
+
restart, else `createdAt` for a launched home, else `null`. `modelFrom` is
|
|
1362
|
+
`"soul"`, `"spawn"` or `"start"` (an explicit `--model`), `"launch-config"`,
|
|
1363
|
+
`"harness-default"`, or `null` for an older home. `identityAddress` is the
|
|
1364
|
+
messaging identity's `address` (else `alias`), or `null`.
|
|
1365
|
+
|
|
1366
|
+
<a id="instance-git-state-oats-instance-gitdiff-instancegitapi-1-oats-0247"></a>
|
|
1367
|
+
## Git and diff
|
|
1368
|
+
|
|
1369
|
+
Feature `instance-git` (`instance-git-remote` for `remote`),
|
|
1370
|
+
`instanceGitApi: 1`. A read-only observation of one instance's work tree: the
|
|
1371
|
+
branch the tree is on, not the recorded one (reported under `recorded`).
|
|
1251
1372
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
it is unique). The Souls page shows it as "from package <id> <version>".
|
|
1257
|
-
Its instances home under `agents/<package>--<soul>/` (`.` in the package id
|
|
1258
|
-
becomes `-`), and that directory is the agent `name` in `oats status --json`
|
|
1259
|
-
(`agents[].name`, e.g. `oats-okf--knowledge-maintainer`). A
|
|
1260
|
-
problem about a package soul carries `package` (and `repoKey: null`); its
|
|
1261
|
-
`path` is `package:<id>:<path in the repo>`.
|
|
1373
|
+
```text
|
|
1374
|
+
oats instance git <instance> [--home <abs>] [--dir <d>] --json
|
|
1375
|
+
oats instance diff <instance> --file <id> --revision <rev> [--index-revision <idx>] [--home <abs>] [--dir <d>] --json
|
|
1376
|
+
```
|
|
1262
1377
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1378
|
+
`<instance>` is resolved under the deployment's agents root. Several homes of
|
|
1379
|
+
that name: `E_AMBIGUOUS_INSTANCE {candidates: [{root, agent, home}]}` (pass
|
|
1380
|
+
`--home`; a wrong one is `E_HOME_MISMATCH`). Unknown: `E_SESSION_UNKNOWN`. No
|
|
1381
|
+
tree: `E_NO_WORKTREE`.
|
|
1266
1382
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1383
|
+
```json
|
|
1384
|
+
{"instanceGitApi":1,"instance":"dev-1","agent":"dev","home":"/w/agents/dev/instances/dev-1","workMode":"worktree",
|
|
1385
|
+
"observation":{"revision":"46c20668…","indexRevision":"3147fef2…","at":"2026-09-26T18:15:26.487Z","worktree":"/w/agents/dev/instances/dev-1/work",
|
|
1386
|
+
"branch":"feat/y","detached":false,"unborn":false},
|
|
1387
|
+
"recorded":{"branch":"agents/dev-1","repo":"/w/one","drift":true},
|
|
1388
|
+
"upstream":{"ref":"origin/feat/y","ahead":1,"behind":0},
|
|
1389
|
+
"base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"46c20668…","ahead":2,"behind":0},
|
|
1390
|
+
"remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream"},
|
|
1391
|
+
"summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
|
|
1392
|
+
"files":[{"id":"0d0cd6557e40c03eba2abd46","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt",
|
|
1393
|
+
"additions":84,"deletions":3,"binary":false}],
|
|
1394
|
+
"notes":[]}
|
|
1395
|
+
```
|
|
1271
1396
|
|
|
1272
|
-
|
|
1397
|
+
- `observation.revision` is the HEAD oid (or `unborn`); `branch` is `null`
|
|
1398
|
+
when detached.
|
|
1399
|
+
- `upstream` without one is all `null` (unknown, not zero). `base` compares
|
|
1400
|
+
with the default branch's merge-base; `source` is `origin/HEAD` or
|
|
1401
|
+
`well-known`; none found is all `null` plus a note.
|
|
1402
|
+
- `remote` (feature `instance-git-remote`): the branch's remote (`source:
|
|
1403
|
+
"branch-upstream"`), else `origin` (`"origin"`), else `null`; `host` and
|
|
1404
|
+
`path` are parsed from the URL (`host: null` for a local path). The kernel
|
|
1405
|
+
has no forge data.
|
|
1406
|
+
- `files[]` come from porcelain v2: `kind` is `changed | renamed | copied |
|
|
1407
|
+
unmerged | untracked`; renames and copies carry `origPath` and `score`;
|
|
1408
|
+
ignored files are omitted. `summary` counts rows per kind.
|
|
1409
|
+
- `files[].id` is opaque, minted under (`revision`, `indexRevision`); it is
|
|
1410
|
+
the only way to ask for a diff.
|
|
1411
|
+
- `additions`, `deletions`, `binary`: line counts of the working tree against
|
|
1412
|
+
the observed commit. A binary file is `{null, null, true}`; an untracked
|
|
1413
|
+
file or submodule is all `null`; if counting fails every entry is `null`
|
|
1414
|
+
with a note. `null` means unknown.
|
|
1415
|
+
|
|
1416
|
+
The diff answers `{instanceGitApi: 1, observation, file: {id, kind, xy,
|
|
1417
|
+
path, origPath}, against, binary, bytes, truncated, limit: 262144, patch,
|
|
1418
|
+
readOnly: {helpers: "disabled", optionalLocks: "off", objectsWritten: 0}}`.
|
|
1419
|
+
|
|
1420
|
+
- `against` is the observed revision (the working tree against that commit,
|
|
1421
|
+
index included) or `"empty"` for an untracked file. A binary file has an
|
|
1422
|
+
empty patch; over 256 KiB, `truncated: true`.
|
|
1423
|
+
- The read runs without external diff, textconv, fsmonitor, hooks, the
|
|
1424
|
+
caller's Git environment or global config, and writes nothing (`readOnly`).
|
|
1425
|
+
- If HEAD or the index moved, the id is not in the current observation, or
|
|
1426
|
+
anything moved during the read: `E_STALE_OBSERVATION` with
|
|
1427
|
+
`details.observation`. Re-observe; never render a diff of another tree.
|
|
1428
|
+
- A `--file` that is not 24 hex, or no `--revision`: `E_BAD_ARGS`. Git
|
|
1429
|
+
failure: `E_GIT_FAILED`.
|
|
1430
|
+
|
|
1431
|
+
## Events
|
|
1432
|
+
|
|
1433
|
+
Feature `instance-events-2`, `eventsApi: 2`: typed lifecycle events, written
|
|
1434
|
+
by the kernel action that made them true.
|
|
1273
1435
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1436
|
+
```text
|
|
1437
|
+
oats instance events <instance> [--limit <n>] [--since <iso>] [--home <abs>] [--dir <d>] --json
|
|
1438
|
+
```
|
|
1277
1439
|
|
|
1278
1440
|
```json
|
|
1279
|
-
{"
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
"
|
|
1286
|
-
"
|
|
1287
|
-
"spawnPreviewApi":2,"preview":true,"agent":"release-manager","instance":"release-manager-cut","home":"/abs/…",
|
|
1288
|
-
"decision":{"instance":"…","home":"…","branch":"…","base":{…},"effective":{…},"resolution":"6e3050c0d005879441ab017d","revision":"<24 hex>"},
|
|
1289
|
-
"…":"every Preview API 2 field as before"}
|
|
1441
|
+
{"eventsApi":2,"instance":"dev-1","home":"/w/agents/dev/instances/dev-1","incarnation":"2026-09-28T10:08:01.281Z",
|
|
1442
|
+
"count":1,"returned":1,"truncated":false,
|
|
1443
|
+
"integrity":{"unreadableRows":0,"foreignRows":0,"sources":[{"path":"home","status":"ok","bytes":612},{"path":"workspace","status":"ok","bytes":612}]},
|
|
1444
|
+
"events":[{"eventsApi":2,"at":"2026-09-28T10:08:02.000Z","instance":"dev-1","home":"/w/agents/dev/instances/dev-1","incarnation":"2026-09-28T10:08:01.281Z",
|
|
1445
|
+
"producer":"kernel","kind":"spawned",
|
|
1446
|
+
"data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","harness":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}}],
|
|
1447
|
+
"lastEvent":{"kind":"spawned","at":"2026-09-28T10:08:02.000Z","producer":"kernel","incarnation":"2026-09-28T10:08:01.281Z"},
|
|
1448
|
+
"waitingOnYou":null,"waitingClaims":[],"notes":["…"]}
|
|
1290
1449
|
```
|
|
1291
1450
|
|
|
1292
|
-
- `
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
- `
|
|
1300
|
-
|
|
1301
|
-
`
|
|
1302
|
-
`
|
|
1303
|
-
|
|
1304
|
-
`
|
|
1305
|
-
`
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
Written by materialization inside the spawn transaction; read back by
|
|
1335
|
-
`oats status --json` and the roster.
|
|
1451
|
+
- **Sources.** `home` is `<home>/.oats-events.jsonl`; `workspace` is
|
|
1452
|
+
`<deployment>/.agents/events/<agent>--<instance>.jsonl` (it survives the
|
|
1453
|
+
home). Each is `{path, status: "ok" | "absent" | "refused" | "tail",
|
|
1454
|
+
bytes}`. Only a regular file is opened (no symlinks, same device and inode
|
|
1455
|
+
after open), and at most its last 4 MiB is read (`"tail"`).
|
|
1456
|
+
- **Kinds:** `spawned`, `launched`, `restarted`, `stopped`, `stop-refused`,
|
|
1457
|
+
`retire-planned`, `retired`, `worktree-retained`, `worktree-removed`,
|
|
1458
|
+
`branch-deleted`, `child-spawn-refused`, `launch-warning` (0.30: a
|
|
1459
|
+
`launch` hook's warning at session start/restart, `data: {message}`),
|
|
1460
|
+
`recomposed` (from earlier kernels). `producer` is `kernel` or a capability id. Older rows may carry
|
|
1461
|
+
`eventsApi: 1`.
|
|
1462
|
+
- **Incarnation.** Each row carries the writing home's `createdAt` (or
|
|
1463
|
+
`null` for old rows); the top-level `incarnation` is the current home's (or
|
|
1464
|
+
`null`). Earlier incarnations are returned as this address's history.
|
|
1465
|
+
- **Address.** `--home` must be a home of `<instance>` (`E_HOME_MISMATCH`).
|
|
1466
|
+
Rows for another address are dropped and counted in
|
|
1467
|
+
`integrity.foreignRows`; torn or invalid lines are counted in
|
|
1468
|
+
`integrity.unreadableRows`. Duplicates are removed.
|
|
1469
|
+
- **Window.** `count` is the rows after `--since`; `returned` the window
|
|
1470
|
+
(`--limit`, default 200, 1–2000); `truncated` means rows were cut or a
|
|
1471
|
+
source was a tail. `lastEvent` is `{kind, at, producer, incarnation}` of
|
|
1472
|
+
the last returned row, or `null`.
|
|
1473
|
+
- **Waiting.** `waitingClaims[]` is `{producer, waiting, since, reason}` per
|
|
1474
|
+
producer with a claim in the current incarnation (cleared ones included).
|
|
1475
|
+
A producer's latest row with `data.waitingOnYou` decides. `waitingOnYou` is
|
|
1476
|
+
`{since, producer, reason}` of the newest positive claim, or `null`
|
|
1477
|
+
(unknown, not "not waiting"). No kernel path claims waiting today.
|
|
1478
|
+
- Errors: `E_SESSION_UNKNOWN`, `E_AMBIGUOUS_INSTANCE`, `E_HOME_MISMATCH`,
|
|
1479
|
+
`E_BAD_ARGS`, `E_EVENTS_FAILED`.
|
|
1480
|
+
|
|
1481
|
+
## Lifecycle: stop and retire
|
|
1482
|
+
|
|
1483
|
+
Feature `lifecycle-plans` (and `retire-retention`), `lifecycleApi: 1`. A
|
|
1484
|
+
**plan** lists what an action would touch, with a `planRevision` (24 hex)
|
|
1485
|
+
hashed from the facts that make it safe. Apply carries the revision back; if
|
|
1486
|
+
reality moved it refuses `E_PLAN_STALE` with the fresh `details.plan`. An
|
|
1487
|
+
idempotency key (`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`) makes a retried apply
|
|
1488
|
+
return the first receipt. Recorded parentage (`parentInstance`) is the only
|
|
1489
|
+
relation followed; a child whose parent name matches several homes is listed
|
|
1490
|
+
under `ambiguous` and never acted on.
|
|
1491
|
+
|
|
1492
|
+
### Stop
|
|
1336
1493
|
|
|
1337
|
-
```
|
|
1338
|
-
|
|
1339
|
-
"acme-release-tooling":{"from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},
|
|
1340
|
-
"commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"},
|
|
1341
|
-
"oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
|
|
1342
|
-
"commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"}},
|
|
1343
|
-
"providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
|
|
1344
|
-
"workspace":{"key":"github.com/acme/agents","name":"acme","deployment":"/Users/ana/acme-workspace","commit":"<oid>","resolution":"<24 hex>","standalone":false,
|
|
1345
|
-
"soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
|
|
1346
|
-
"…":"a package soul's workspace.soul also records package: {id, version, commit, digest, path}, and its id is package:<id>#<soul>",
|
|
1347
|
-
"capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
|
|
1494
|
+
```text
|
|
1495
|
+
oats instance stop <instance> --plan [--no-recursive] [--home <abs>] [--dir <d>] --json
|
|
1348
1496
|
```
|
|
1349
1497
|
|
|
1350
|
-
`workspace.name` (the workspace file's `name`) and `workspace.deployment` (the
|
|
1351
|
-
directory holding the deployment's `oats-local.yaml`) are recorded from 0.26.0,
|
|
1352
|
-
so a home answers them without discovery: `oats inspect --home` reports the
|
|
1353
|
-
recorded name, and `oats operation run --home` hands it to the provider as
|
|
1354
|
-
`OATS_WORKSPACE_NAME`. Homes spawned
|
|
1355
|
-
before 0.26.0 lack both; the name is then discovered, or `null`.
|
|
1356
|
-
|
|
1357
|
-
`workspace.standalone` is `true` when the instance was spawned from the
|
|
1358
|
-
**standalone view** (decisions 10/25: a *member* whose workspace could not be
|
|
1359
|
-
read — `workspace.key` is then the member repo's key, and `modules` holds the
|
|
1360
|
-
soul's `from: here` capabilities plus `oats.core`); `false` for a workspace
|
|
1361
|
-
spawn. The same view is marked `standalone: true` in `oats sync --json` (with
|
|
1362
|
-
`workspace.name` = `standalone:<repo>`) and in the roster. `capabilities[]` is
|
|
1363
|
-
the per-module row set (`toCapabilityRows`) that `oats inspect`/`status` read.
|
|
1364
|
-
|
|
1365
|
-
`soulDir` (0.26.0) is the absolute soul directory the instance incarnates — a
|
|
1366
|
-
workspace soul's per-commit copy `<deployment>/agents/<soul>/souls/<commit12>`, or
|
|
1367
|
-
the read-only soul inside a capability package — and is what every classic
|
|
1368
|
-
lifecycle hook and dispatched command receives as `OATS_SOUL`. Instance homes carry no `soul` link.
|
|
1369
|
-
|
|
1370
|
-
`digest` is the sha256 of the copied module tree (`<home>/.oats/modules/<cap>/`);
|
|
1371
|
-
`providers.<cap>` is the merged payload (manifest defaults ⊕ soul ⊕
|
|
1372
|
-
`oats-local.yaml` `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
|
|
1373
|
-
`<home>/.oats/modules/<cap>/` and `<home>/.agents/skills/<cap>/<skill>/`.
|
|
1374
|
-
|
|
1375
|
-
### `oats status [--dir] --json` — module drift
|
|
1376
|
-
|
|
1377
|
-
On a workspace deployment the roster does one discovery and rewrites each
|
|
1378
|
-
instance's `modules` from the recorded map into **drift rows**, and adds a
|
|
1379
|
-
top-level `workspace` reachability field:
|
|
1380
|
-
|
|
1381
1498
|
```json
|
|
1382
|
-
{"
|
|
1383
|
-
"
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
"workspace":{"reachable":true}}
|
|
1499
|
+
{"lifecycleApi":1,"action":"stop","instance":"dev-1","home":"/w/agents/dev/instances/dev-1","recursive":true,"at":"2026-09-28T11:00:00.000Z",
|
|
1500
|
+
"targets":[{"instance":"dev-1","agent":"dev","home":"/w/agents/dev/instances/dev-1","depth":0,"workMode":"worktree","launched":true,
|
|
1501
|
+
"session":{"state":"unknown","present":true,"backend":"tmux","established":true},
|
|
1502
|
+
"work":{"observed":true,"revision":"46c20668…","branch":"feat/x","detached":false,"drift":false,"changed":2,"untracked":1,
|
|
1503
|
+
"upstream":{"ref":null,"ahead":null,"behind":null},"base":{"ref":"origin/main","ahead":1,"behind":0},"remote":{"host":"github.com","path":"acme/one"}},
|
|
1504
|
+
"retiring":false,"stopPending":false,"midTask":true}],
|
|
1505
|
+
"skipped":[],"ambiguous":[],"planRevision":"9c1f2e3d4b5a69788796a5b4","notes":[]}
|
|
1390
1506
|
```
|
|
1391
1507
|
|
|
1392
|
-
- `
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
reason
|
|
1396
|
-
-
|
|
1397
|
-
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
carries `package`, `version` and `currentVersion`; `moved` means the package
|
|
1405
|
-
pin moved (another version/commit is locked now), `missing` has `reason`
|
|
1406
|
-
`package-absent` (no longer locked or declared) or `soul-absent` (the locked
|
|
1407
|
-
package no longer ships it). Text: `soul: <name> from package <id> v<version>
|
|
1408
|
-
@ <7-char> [package moved since (now v<version> @ <7-char>)]`.
|
|
1409
|
-
|
|
1410
|
-
### Triggers (feature `triggers`, OATS 0.28.0) — `oats trigger … --json` → `triggerApi: 1`
|
|
1411
|
-
|
|
1412
|
-
Event-driven spawns of a deployment ([schedules.md#triggers](schedules.md#triggers)).
|
|
1413
|
-
Definitions live in `oats-schedules.json` (`kind: "trigger"`); `oats schedule list`
|
|
1414
|
-
does not show them. From 0.29.0 a row's `id` is qualified (`local/<id>` here; a
|
|
1415
|
-
workspace trigger's is `<member>/<id>`) and the row carries the shared fields of
|
|
1416
|
-
[workspace triggers and schedules](#workspace-triggers-and-schedules-feature-automations-oats-0290-automationsapi-1).
|
|
1508
|
+
- `targets`: the recorded descendants, deepest first, then the instance
|
|
1509
|
+
(`depth: 0`). With `--no-recursive` descendants go to `skipped` (`{instance,
|
|
1510
|
+
agent, home, reason: "recursive=false"}`). `ambiguous[]`: `{instance, agent,
|
|
1511
|
+
home, reason}`.
|
|
1512
|
+
- `session.state` is the backend's word: `shell`, `stopped` and
|
|
1513
|
+
`not-launched` are idle; `unknown` is a running process tmux cannot name
|
|
1514
|
+
(the normal state of a harness). Not established: `{state:
|
|
1515
|
+
"unestablished", present: null, backend: null, established: false,
|
|
1516
|
+
reason}`; render it as unknown, never idle.
|
|
1517
|
+
- `work` is the Git observation summarized (`changed` counts changed,
|
|
1518
|
+
renamed, copied and unmerged rows), or `{observed: false, reason}`.
|
|
1519
|
+
- `midTask`: `true`, `false` or `"unknown"`.
|
|
1417
1520
|
|
|
1418
|
-
```
|
|
1419
|
-
|
|
1420
|
-
{"id":"okf-harvest-review","enabled":true,"kind":"trigger",
|
|
1421
|
-
"on":{"source":"github.pull_request","repo":"github.com/acme/knowledge","events":["opened","reopened","ready_for_review"],"labels":["okf-harvest"],"base":"main","poll":"2m"},
|
|
1422
|
-
"spawn":{"soul":"oats.okf/knowledge-maintainer","purpose":"review-pr-{number}","task":"…","teams":["okf"],"launchConfig":"reviewers","harness":"claude","model":"opus"},
|
|
1423
|
-
"concurrency":{"max":2,"perKey":1},"template":{"package":"oats.okf","version":"4.0.0","commit":"<oid>","template":"harvest-review"},
|
|
1424
|
-
"triggerApi":1,"scope":"/abs/deployment","createdAt":"<iso>","updatedAt":"<iso>"}]}
|
|
1521
|
+
```text
|
|
1522
|
+
oats instance stop <instance> --apply --plan-revision <rev> --idempotency-key <key> [--no-recursive] [--grace-ms <n>] [--home <abs>] --json
|
|
1425
1523
|
```
|
|
1426
1524
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
messaging }, wouldFire: [{ key, repo, number, event, url, held? }], pollError?, problems:
|
|
1448
|
-
[string], warnings: [string], spawned: false }`. It writes nothing. `ok`
|
|
1449
|
-
counts `problems` only; a credential the host timer cannot reach
|
|
1450
|
-
(`reachesHostTimer: false`) is a warning.
|
|
1451
|
-
- `oats schedule list --json` gains `triggers: { count, command: "oats trigger
|
|
1452
|
-
list" }`: the triggers it does not list.
|
|
1453
|
-
- The tick's `considered[]` gains trigger rows `{ workspace, trigger, action,
|
|
1454
|
-
… }` with `action` `not-due`, `poll-failed`, `polled` (`prs`, `matching`;
|
|
1455
|
-
nothing to fire), `held`, `fired` (`key`,
|
|
1456
|
-
`instance`, `home`), `spawn-failed` (`key`, `code`, `error`), `would-fire`
|
|
1457
|
-
(`--dry-run`) or `invalid`.
|
|
1458
|
-
- A triggered instance's `instance.json.trigger` is `{ id, key, source, repo,
|
|
1459
|
-
number, url, event, headSha, observedAt, eventFile }`; the event file is
|
|
1460
|
-
`OATS_TRIGGER_EVENT_FILE`.
|
|
1461
|
-
- Errors: `E_TRIGGER_INVALID { field }`, `E_TRIGGER_EXISTS`,
|
|
1462
|
-
`E_TRIGGER_UNKNOWN`, `E_BAD_ARGS` (`missing` / `parameters` for a template),
|
|
1463
|
-
`E_PACKAGE_MISSING`, `E_PACKAGE_MANIFEST`, `E_LOCAL_MISSING`.
|
|
1464
|
-
|
|
1465
|
-
### Workspace triggers and schedules (feature `automations`, OATS 0.29.0; `automationsApi: 1`)
|
|
1466
|
-
|
|
1467
|
-
See [schedules.md#workspace-triggers-and-schedules](schedules.md#workspace-triggers-and-schedules).
|
|
1468
|
-
`oats trigger list --json` and `oats schedule list --json` answer this machine's
|
|
1469
|
-
local items and every workspace item defined in a member the user can read. The
|
|
1470
|
-
Desktop renders these rows and never re-derives them. The two lists stay separate:
|
|
1471
|
-
a trigger never appears in `schedule list`, and a schedule never appears in
|
|
1472
|
-
`trigger list`.
|
|
1473
|
-
|
|
1474
|
-
- Both lists add:
|
|
1475
|
-
- `host: { name | null, ghUser: { <gh host>: <login> | null } }`: this machine's
|
|
1476
|
-
`oats-local.yaml` `host.name`, and who its `gh` is logged in as on every GitHub
|
|
1477
|
-
host the rows name (the owners'; a trigger's repository's) — `null` when `gh`
|
|
1478
|
-
is not authenticated there. The Desktop compares it with a row's `owner`.
|
|
1479
|
-
- `snapshot: { takenAt, problems } | null` (`null` until `oats sync` has found some).
|
|
1480
|
-
- `scheduler: { installed, active, registered, lastTick, maxConcurrent, … }`: the
|
|
1481
|
-
host tick (the same object as `oats schedule host status`). Nothing runs unless
|
|
1482
|
-
it is installed, active and this deployment is registered.
|
|
1483
|
-
- **Identity:**
|
|
1484
|
-
- `id`:
|
|
1485
|
-
- a trigger row's is always qualified (`local/<id>`, `<member>/<id>`);
|
|
1486
|
-
- a local schedule row keeps its bare id (the 0.28 contract);
|
|
1487
|
-
- a workspace schedule row's is `<member>/<id>`.
|
|
1488
|
-
- `qualifiedId` is always the qualified form, and `name` is the bare id.
|
|
1489
|
-
- Every verb accepts `local/<id>` or a bare local id.
|
|
1490
|
-
- **Shared fields in every row:**
|
|
1491
|
-
- `origin`: where the item is defined, and where to open it:
|
|
1492
|
-
- `{ kind: "local", path: "oats-schedules.json", url: null, localPath }`;
|
|
1493
|
-
- `{ kind: "workspace", repoKey, path, commit, url, localPath }`: `url` is the
|
|
1494
|
-
file's web URL at `commit` (`https://github.com/<owner>/<repo>/blob/<commit>/<path>`
|
|
1495
|
-
for a `github.com` member, else `null`); `localPath` is the file in this
|
|
1496
|
-
machine's clone of the member (`null` when the member is not cloned here).
|
|
1497
|
-
- `description`, `owner`, `runsOn`;
|
|
1498
|
-
- `runsHere`; `reason` (`null` | `host-unnamed` | `assigned-elsewhere` | `owner-mismatch`) with `reasonDetail`;
|
|
1499
|
-
- `enabledHere`;
|
|
1500
|
-
- `soul`: `{ name, origin } | null` (`null` for a command, wake or operation schedule). `origin` is where the name resolves, per the snapshot:
|
|
1501
|
-
- `{ kind: "member", repoKey, member }`: a soul in a workspace member;
|
|
1502
|
-
- `{ kind: "package", package, version }`: a soul of a locked package;
|
|
1503
|
-
- `{ kind: "external", repoKey, source }`: an external soul (`source` is the workspace's `external[].source` ref);
|
|
1504
|
-
- `{ kind: "ambiguous", candidates }`: a bare name several souls answer to (`candidates` is how many); a spawn needs the qualified name;
|
|
1505
|
-
- `null`: not found (or no snapshot yet).
|
|
1506
|
-
- `task`: the template, verbatim;
|
|
1507
|
-
- `teams`, `launchConfig`, `harness`, `model`, `concurrency`;
|
|
1508
|
-
- `lastRun`, `nextDue`;
|
|
1509
|
-
- `invalid?: { code, message, field? }`.
|
|
1510
|
-
- **A trigger row** also carries `kind: "trigger"`, `on`, `spawn`, and `template?`:
|
|
1511
|
-
- `on: { source: "github.pull_request", repo: "<host>/<owner>/<repo>", events: [opened | reopened | ready_for_review | labeled | synchronize], labels: [string], base?: string, poll: "<n>s|m|h" }` (`base` absent: any base branch);
|
|
1512
|
-
- `spawn: { soul, purpose, task, teams: [label], launchConfig?, harness?, model?, yolo?, backend? }`
|
|
1513
|
-
(`purpose` and `task` are templates over `{repo}`, `{number}`, `{url}`, `{event}`, `{trigger}`, `{headSha}`;
|
|
1514
|
-
`launchConfig` names a launch configuration in the running host's `oats-local.yaml`);
|
|
1515
|
-
- `template?: { package, version, commit, template }`: the package template it was added from.
|
|
1516
|
-
- `lastRun` is the last fired event: `{ at, instance, home, event, number, key }`.
|
|
1517
|
-
- `nextDue` is the next poll, only when it runs here; `null` before its first poll (it polls at the next tick).
|
|
1518
|
-
- **A schedule row** keeps every 0.28 field (`scheduleApi: 2`). Its `kind` is the run (`spawn` | `command` | `wake` | `operation`); it also carries `cron` and `tz`.
|
|
1519
|
-
- `nextDue` is the next minute, only when it runs here.
|
|
1520
|
-
- `teams` is `[]` and `concurrency` is `null`.
|
|
1521
|
-
- A workspace schedule another host runs carries its definition and placement only: `lastRun` and `nextDue` are `null`.
|
|
1522
|
-
- A spawn schedule's `launchConfig` names a launch configuration in the running host's `oats-local.yaml`.
|
|
1523
|
-
- **Naming:** `nextDue` is the one name for "when it next runs" in every trigger and
|
|
1524
|
-
schedule row. A schedule row still carries the 0.24 `nextRun` for older readers;
|
|
1525
|
-
they agree whenever it runs here.
|
|
1526
|
-
- **Actions:**
|
|
1527
|
-
- `enable` and `disable` on a workspace id edit `oats-local.yaml` `triggers.disabled` or `schedules.disabled`.
|
|
1528
|
-
- `update` and `remove` refuse it with `E_AUTOMATION_WORKSPACE { id, origin }`.
|
|
1529
|
-
- `schedule run` and `schedule reconcile` work when it runs here, else `E_AUTOMATION_NOT_HERE { id, reason, runsOn, owner }`.
|
|
1530
|
-
- **`oats trigger test <id>`** adds `placement: { runsOn, owner, host, runsHere, reason, detail?, enabledHere }`. Any reason, or disabled here, is a problem (`ok: false`).
|
|
1531
|
-
- **`oats schedule test <id> --json`** (local or workspace) → `{ test: { id, qualifiedId, kind,
|
|
1532
|
-
placement: { runsHere, reason, reasonDetail?, enabledHere, runsOn, owner, host },
|
|
1533
|
-
soul: { name, origin, resolves, error: { code, message } | null } | null, nextDue,
|
|
1534
|
-
spawned: false, problems: [string], ok } }`. `soul` is checked the way the run
|
|
1535
|
-
would start it (`oats spawn <soul> --preview`, which writes nothing); it is `null`
|
|
1536
|
-
for a command, wake or operation. `nextDue` is the next cron match whether or not
|
|
1537
|
-
this host runs it (`placement` says that). Not running here, disabled, invalid or a
|
|
1538
|
-
soul that does not resolve is a problem (`ok: false`). It spawns nothing and records
|
|
1539
|
-
nothing. Errors: `E_SCHEDULE_UNKNOWN`, `E_BAD_ARGS`.
|
|
1540
|
-
- **`oats trigger|schedule add … --workspace <member> --runs-on <host> --owner <host>/<login> --json`** answers `{ id, written, file: { member, repoKey, path, content, written? } }`.
|
|
1541
|
-
- Errors: `E_AUTOMATION_MEMBER` (not a confirmed member), `E_TRIGGER_EXISTS` or `E_SCHEDULE_EXISTS` (the file exists), and the kind's validation codes.
|
|
1542
|
-
- **`oats automations refresh --json`** answers `{ automationsApi, snapshot, triggers, schedules, problems: [...], takenAt }`.
|
|
1543
|
-
- **`oats sync --json`** gains `automations: { triggers, schedules, problems, takenAt }`. Discovery problems join `problems` (`E_AUTOMATION_SCHEMA`, `E_AUTOMATION_DUPLICATE`, each with `kind`, `repoKey` and `path`).
|
|
1544
|
-
- **`oats workspace status --json`** gains `automations: { host, snapshot, rows: [{ kind, id, runsOn, owner, runsHere, reason, enabledHere, origin, invalid? }] }`.
|
|
1545
|
-
- **The tick's `considered[]`** gains the trigger action `not-here` (`reason: owner-mismatch`, `detail`): a trigger naming this host that this host cannot run. A workspace schedule's row `id` is its state key, `<member>~<id>`. A failed snapshot refresh is `{ action: "error", error: "automations refresh: …" }`.
|
|
1546
|
-
|
|
1547
|
-
### Desktop facts (feature `desktop-facts`, OATS 0.29.0)
|
|
1548
|
-
|
|
1549
|
-
These are facts the Workspace view shows. The kernel reports them so the
|
|
1550
|
-
Desktop never works them out itself. Gate reading every field below on
|
|
1551
|
-
`desktop-facts` in `features[]`. No API integer changes, and every field is an
|
|
1552
|
-
addition to an existing row.
|
|
1553
|
-
|
|
1554
|
-
**`oats inspect --soul <name> --json`: why each capability is there**
|
|
1555
|
-
|
|
1556
|
-
- `capabilities[].composedFrom` says which layer put the module in the soul:
|
|
1557
|
-
`"workspace"` (`defaults.<slot>` or `defaults.capabilities`),
|
|
1558
|
-
`"team:<label>"` (`defaults.byTeam.<label>.capabilities`) or `"soul"` (the
|
|
1559
|
-
soul's own `capabilities:`). This is the same vocabulary as
|
|
1560
|
-
`layers.<slot>.from`. It is `null` on `inspect --home`, because a spawn does
|
|
1561
|
-
not record it. `from` stays the module's origin object (`{kind, repoKey,
|
|
1562
|
-
commit}` or the package object), so it is a separate key.
|
|
1563
|
-
- `capabilitiesOff[]` lists the capabilities the soul turned off, which a
|
|
1564
|
-
lower layer would otherwise have given it. They are not rows of
|
|
1565
|
-
`capabilities[]`, because those are resolved modules with operations. Each
|
|
1566
|
-
entry is `{ id, off: true, from: "soul", reason, slot?, overrides }`:
|
|
1567
|
-
- `reason: "off"`: the soul wrote `<id>: off` over a workspace or team
|
|
1568
|
-
default.
|
|
1569
|
-
- `reason: "slot-none"`: the soul wrote `<slot>: none` (`slot` names it),
|
|
1570
|
-
which emptied the slot the workspace filled with `<id>`.
|
|
1571
|
-
- `overrides`: the layer whose default was turned off (`"workspace"` or
|
|
1572
|
-
`"team:<label>"`).
|
|
1573
|
-
- Sorted by id. `[]` on `inspect --home`.
|
|
1574
|
-
|
|
1575
|
-
**`oats souls --json` rows**
|
|
1576
|
-
|
|
1577
|
-
- `harness`, `model`, `harnessFrom`: what a spawn of the soul starts with
|
|
1578
|
-
when no `--harness`/`--model` is given. A v2 `soul.yaml` cannot declare a
|
|
1579
|
-
harness or a model, so today this is always `harness: "pi"`, `model: null`
|
|
1580
|
-
(the harness's native model) and `harnessFrom: "kernel-default"`.
|
|
1581
|
-
`harnessFrom: "soul"` is reserved for a schema that lets a soul declare
|
|
1582
|
-
one.
|
|
1583
|
-
- `spawnable`, `problem`: whether a spawn here would refuse.
|
|
1584
|
-
- `problem` is `{ code, message }` when a spawn would refuse, else `null`.
|
|
1585
|
-
- The kernel resolves the soul exactly as a spawn does, but spawns nothing,
|
|
1586
|
-
writes nothing and reads only the sync cache.
|
|
1587
|
-
- Codes: `E_SOUL_DISABLED` (this machine's `souls.disabled`),
|
|
1588
|
-
`E_TEAM_CONFLICT`, `E_CAPABILITY_MISSING`, `E_CAPABILITY_PRIVATE`,
|
|
1589
|
-
`E_CAPABILITY_INCOMPATIBLE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`,
|
|
1590
|
-
`E_LOCK_SCHEMA`, `E_REMOTE_*`, and any other resolution refusal.
|
|
1591
|
-
- An `E_TEAM_UNKNOWN` problem in `problems[]` is informational. It does not
|
|
1592
|
-
make a soul unspawnable.
|
|
1593
|
-
- `file`: `{ path, url }`, the soul's `soul.yaml` in its repository (see
|
|
1594
|
-
**URLs** at the end of this section).
|
|
1595
|
-
|
|
1596
|
-
**`oats capabilities --json` rows**
|
|
1597
|
-
|
|
1598
|
-
- `layer` on every row. Package rows now carry it too, from the package
|
|
1599
|
-
manifest; `null` for a capability outside the slots.
|
|
1600
|
-
- `description`: the manifest's `description`, or `null`.
|
|
1601
|
-
- `skills`, `commands`, `hooks`: what the capability provides, by name,
|
|
1602
|
-
sorted.
|
|
1603
|
-
- `skills` is enumerated as a spawn would. It is `null` when the declared
|
|
1604
|
-
skills cannot be listed, which a spawn of it would refuse.
|
|
1605
|
-
- `commands` and `hooks` are the keys of the manifest's `commands` and
|
|
1606
|
-
`hooks`.
|
|
1607
|
-
- `file`: `{ path, url }`, the capability's `oats.json`, or `null` when the
|
|
1608
|
-
manifest cannot be read.
|
|
1609
|
-
- `tree`: a member capability's fingerprint, the Git tree id of its
|
|
1610
|
-
directory at the member commit. The same bytes give the same id. It is
|
|
1611
|
-
`null` on package rows, whose fingerprint is `integrity` in the lock (see
|
|
1612
|
-
`oats workspace status`).
|
|
1613
|
-
- A package whose manifests cannot be read at its locked commit leaves these
|
|
1614
|
-
facts `null` on its rows.
|
|
1615
|
-
- Package manifests are read at the locked commit from the sync cache. There
|
|
1616
|
-
is no network beyond what `sync` already fetched.
|
|
1617
|
-
|
|
1618
|
-
**`oats workspace status --json`**
|
|
1525
|
+
Children first: SIGTERM to the harness processes and a bounded wait
|
|
1526
|
+
(`--grace-ms`, 1–300000, default 20000), never escalated. Home, work,
|
|
1527
|
+
transcript and launch configuration are kept; `oats session restart` brings
|
|
1528
|
+
the instance back.
|
|
1529
|
+
|
|
1530
|
+
- The receipt is `{lifecycleApi: 1, action: "stop", instance, home,
|
|
1531
|
+
idempotencyKey, planRevision, at, ok, results, retained: ["home", "work",
|
|
1532
|
+
"transcript", "launch"], replayed: false}`. A result is `{instance, home,
|
|
1533
|
+
ok: true, stopped, alreadyIdle, state}` or `{instance, home, ok: false,
|
|
1534
|
+
code, message, stillRunning: [pid]}`.
|
|
1535
|
+
- `ok: false`: at least one target still runs (text mode exits 1).
|
|
1536
|
+
- A replay is the stored receipt (`<home>/.oats-stop-receipt.<key>.json`)
|
|
1537
|
+
with `replayed: true`.
|
|
1538
|
+
- Refusals: `E_BAD_ARGS` (no `--plan-revision`, a bad key, not exactly one of
|
|
1539
|
+
`--plan`/`--apply`), `E_PLAN_STALE`, `E_INSTANCE_RETIRING` and
|
|
1540
|
+
`E_LIFECYCLE_BUSY` (each with `details.plan`), `E_SESSION_UNKNOWN`,
|
|
1541
|
+
`E_AMBIGUOUS_INSTANCE`, `E_HOME_MISMATCH`, `E_LIFECYCLE_FAILED`.
|
|
1542
|
+
|
|
1543
|
+
<a id="retire"></a>
|
|
1544
|
+
### Retire
|
|
1619
1545
|
|
|
1620
|
-
```
|
|
1621
|
-
|
|
1622
|
-
"members":[{"…":"…","url":"https://github.com/acme/tools/tree/<oid>","membershipFile":{"path":"oats-membership.yaml","url":"https://github.com/acme/tools/blob/<oid>/oats-membership.yaml"}}],
|
|
1623
|
-
"packages":[{"id":"oats.okf","version":"3.0.0","source":"catalog:oats.okf","commit":"<oid>","…":"…","latest":{"version":"4.0.0","ref":"v4.0.0"}}],
|
|
1624
|
-
"defaults":{"slots":{"knowledge":{"name":"oats.okf","from":"package"},"messaging":"none","tasks":null},
|
|
1625
|
-
"capabilities":[{"name":"acme-house-style","from":"github.com/acme/agents","off":false}],
|
|
1626
|
-
"byTeam":{"engineering":{"capabilities":[{"name":"acme-house-style","from":null,"off":true},{"name":"acme-deploy","from":"package","off":false}]}}},
|
|
1627
|
-
"clones":[{"key":"github.com/acme/agents","name":"agents","path":"/abs/acme-workspace/agents","rule":"convention"},
|
|
1628
|
-
{"key":"github.com/acme/tools","name":"tools","path":null,"rule":null}],
|
|
1629
|
-
"disabledSouls":["release-reviewer"],
|
|
1630
|
-
"lock":{"path":"/abs/acme-workspace/oats-lock.json","lockfileVersion":3}}
|
|
1546
|
+
```text
|
|
1547
|
+
oats retire <instance> --plan [--home <abs>] [--dir <d>] --json
|
|
1631
1548
|
```
|
|
1632
1549
|
|
|
1633
|
-
- `defaults`: the workspace file's defaults, as declared rather than
|
|
1634
|
-
resolved for a soul.
|
|
1635
|
-
- `slots.<slot>` is `{ name, from }` when the workspace fills it, `"none"`
|
|
1636
|
-
when it empties it, and `null` when it says nothing.
|
|
1637
|
-
- `capabilities` and `byTeam.<label>.capabilities` are rows `{ name, from,
|
|
1638
|
-
off }`, sorted by name. `from` is the declared location (`"package"`,
|
|
1639
|
-
`"here"` or a member repo key); an `off` row has `from: null`.
|
|
1640
|
-
- Standalone: slots `null`, `capabilities: []` and `byTeam: {}`.
|
|
1641
|
-
- `clones`: this computer's clone of each member.
|
|
1642
|
-
- `path` is the absolute clone, or `null` when this machine has none.
|
|
1643
|
-
- `rule` names what found it: `"clones"` (the `oats-local.yaml` `clones:`
|
|
1644
|
-
entry) or `"convention"` (`<deployment>/<member name>`). It is `null`
|
|
1645
|
-
with no clone.
|
|
1646
|
-
- A path that is not the member's clone gives `path: null, rule: null,
|
|
1647
|
-
problem: { code: "E_CLONE_MISMATCH", message }`, the refusal a spawn
|
|
1648
|
-
would meet.
|
|
1649
|
-
- (`--repo` is a spawn option, so it plays no part here.)
|
|
1650
|
-
- `disabledSouls`: `oats-local.yaml` `souls.disabled`, as written.
|
|
1651
|
-
- `lock`: `{ path, lockfileVersion }`. The per-package commit is each
|
|
1652
|
-
`packages[]` row's `commit`, and its fingerprint is `integrity`.
|
|
1653
|
-
- `packages[].latest`: `{ version, ref }` when the official catalog shipped
|
|
1654
|
-
with this kernel has a newer version of a catalog-sourced package than the
|
|
1655
|
-
lock holds. It is `null` when the pin is current and for `git:` packages.
|
|
1656
|
-
It never reaches the network: the catalog is the kernel's own
|
|
1657
|
-
(`OATS_PACKAGE_CATALOG` overrides it, as for `sync`).
|
|
1658
|
-
- `workspace.file` is `{ path, url }` for the workspace file in the
|
|
1659
|
-
workspace repository (at `workspace.key` @ `workspace.commit`). It is
|
|
1660
|
-
`null` for a standalone deployment.
|
|
1661
|
-
- `members[].url` is the member repository at its commit.
|
|
1662
|
-
`members[].membershipFile` is `{ path, url }`.
|
|
1663
|
-
|
|
1664
|
-
**`oats status --json` instance rows**
|
|
1665
|
-
|
|
1666
|
-
- A member module's `modules[].current` gains `version` (the capability's
|
|
1667
|
-
manifest version at the current commit, `null` when it has none) beside
|
|
1668
|
-
`commit`, on `current` and `moved` rows. Package rows already carried it. On a `moved` row, the recorded `commit`/`from` and `current`
|
|
1669
|
-
together say what moved and to what.
|
|
1670
|
-
- `startedAt`: the last session start or restart (the session receipt). A
|
|
1671
|
-
home spawned with a launch and never restarted uses `createdAt`. A home
|
|
1672
|
-
never launched is `null`. `createdAt` stays the spawn time.
|
|
1673
|
-
- `modelFrom`: where the model the home runs came from.
|
|
1674
|
-
- `"soul"`: the soul's model preference.
|
|
1675
|
-
- `"spawn"` or `"start"`: an explicit `--model` on that command.
|
|
1676
|
-
- `"launch-config"`: a launch configuration's model.
|
|
1677
|
-
- `"harness-default"`: the harness's own model.
|
|
1678
|
-
- A start that reuses the recorded model keeps the recorded answer.
|
|
1679
|
-
- `null` for a home spawned before 0.29.0. `instance.json` records it as
|
|
1680
|
-
`modelFrom`.
|
|
1681
|
-
- `identityAddress`: the messaging identity's `address` (else `alias`) that
|
|
1682
|
-
the messaging capability recorded (`capabilityMeta.<messaging>.identity`),
|
|
1683
|
-
passed through unchanged. `null` otherwise.
|
|
1684
|
-
|
|
1685
|
-
**URLs.** Every `url` is a browsable page of
|
|
1686
|
-
the file (or of the repository, for a member) at the commit the row names.
|
|
1687
|
-
Only repositories on `github.com` have one (`https://github.com/<org>/<repo>/blob/<commit>/<path>`,
|
|
1688
|
-
or `/tree/<commit>`). Every other host and local repository gives `url:
|
|
1689
|
-
null`, with `path` still set. `path` is relative to that repository's root.
|
|
1690
|
-
|
|
1691
|
-
**Help.** `oats help` lists `spawn … [--provider <capability> <key>=<value>]`.
|
|
1692
|
-
|
|
1693
|
-
### Eligible teams (feature `teams`, OATS 0.26.0)
|
|
1694
|
-
|
|
1695
|
-
A soul's `team` may be a list of labels; the first is the primary
|
|
1696
|
-
([teams contract](design/2026-09-25-teams-contract.md)). Every label is an
|
|
1697
|
-
**eligible** team — which the messaging provider may join on an explicit
|
|
1698
|
-
request (a spawn provider setting, or its own join/leave verbs); the kernel
|
|
1699
|
-
joins nothing. One entry per label, in soul order:
|
|
1700
|
-
|
|
1701
1550
|
```json
|
|
1702
|
-
{"
|
|
1703
|
-
{"
|
|
1551
|
+
{"lifecycleApi":1,"action":"retire","instance":"dev-1","home":"/w/agents/dev/instances/dev-1","at":"2026-09-28T11:10:00.000Z",
|
|
1552
|
+
"facts":{"session":{"state":"shell","present":true,"backend":"tmux","established":true},
|
|
1553
|
+
"work":{"observed":true,"revision":"46c20668…","branch":"feat/x","detached":false,"drift":true,"changed":0,"untracked":1,
|
|
1554
|
+
"upstream":{"ref":null,"ahead":null,"behind":null},"base":{"ref":null,"ahead":null,"behind":null},"remote":null},
|
|
1555
|
+
"workMode":"worktree","repo":"/w/one","recordedBranch":"agents/dev-1",
|
|
1556
|
+
"children":[{"instance":"dev-1-child","agent":"dev","home":"/w/agents/dev/instances/dev-1-child","session":{"state":"shell","present":true,"backend":"tmux","established":true}}],
|
|
1557
|
+
"ambiguous":[],"pullRequest":"unknown"},
|
|
1558
|
+
"defaults":{"retainWorktree":true,"deleteBranch":false,"stopChildren":true,"retainChildren":true},
|
|
1559
|
+
"planRevision":"4e5f6a7b8c9d0e1f2a3b4c5d","notes":["the worktree is on feat/x, not the recorded agents/dev-1; …"]}
|
|
1704
1560
|
```
|
|
1705
1561
|
|
|
1706
|
-
|
|
1707
|
-
`
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
with
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
labels: [a, b], entries, paths }`.
|
|
1725
|
-
|
|
1726
|
-
### Probe
|
|
1562
|
+
- The plan changes nothing except appending a `retire-planned` event to the
|
|
1563
|
+
workspace log. `pullRequest` is always `"unknown"`. Branch actions use the
|
|
1564
|
+
worktree's branch, never `recordedBranch`.
|
|
1565
|
+
- A home spawned before 0.25.9 has no session receipt (0.30). Its
|
|
1566
|
+
`facts.session` is `{state: "absent", present: false, backend, established:
|
|
1567
|
+
true, note}` when the session is observably gone: instance.json records no
|
|
1568
|
+
launch, or the recorded tmux server is not running, or the recorded window
|
|
1569
|
+
is gone and no pane on that server works in the home, and in every case no
|
|
1570
|
+
live process on the host has its working directory in the home (`lsof`; a
|
|
1571
|
+
scan that cannot run counts as not absent). Retire then proceeds
|
|
1572
|
+
without quiescing (hooks run, work is preserved). Otherwise it stays
|
|
1573
|
+
`unestablished`, with a `note` saying why, and retire refuses with
|
|
1574
|
+
`E_RUNTIME_ENDPOINT_UNKNOWN`, `--force` included. `notes` repeats either
|
|
1575
|
+
case. Read an unknown `state` as not idle.
|
|
1576
|
+
|
|
1577
|
+
Plain `retire` keeps a worktree-mode instance's work: the worktree is moved
|
|
1578
|
+
(`git worktree move`) to `<deployment>/.agents/worktrees/<repo>/<branch>` (a
|
|
1579
|
+
`-2` suffix if taken; `detached-<oid12>` when detached), state intact.
|
|
1727
1580
|
|
|
1728
|
-
```
|
|
1729
|
-
|
|
1581
|
+
```text
|
|
1582
|
+
oats retire <instance> [--plan-revision <rev> --idempotency-key <key>] [--discard-worktree] [--delete-branch] [--home <abs>] --json
|
|
1730
1583
|
```
|
|
1731
1584
|
|
|
1732
|
-
A
|
|
1733
|
-
`workspace status`/`capabilities`/`souls` on `workspace-v2`; gate reading
|
|
1734
|
-
`instance.json.modules` and preview `modules[]` on `instance-modules`; gate
|
|
1735
|
-
`--provider` on `spawn-provider-payload`; gate reading `teams`, `teamsSource`,
|
|
1736
|
-
`labels` and `warnings[]` on `teams`; gate reading `declares` on
|
|
1737
|
-
`settings-declared`.
|
|
1738
|
-
|
|
1739
|
-
## Instruction refresh (`oats session recompose`) — removed in 0.26.0
|
|
1585
|
+
A first retire prints the **raw receipt**, not an envelope:
|
|
1740
1586
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1587
|
+
```json
|
|
1588
|
+
{"retired":"dev-1","agent":"dev",
|
|
1589
|
+
"retention":{"worktree":"retained","movedTo":"/w/.agents/worktrees/one/feat-x","branch":"feat/x","detachedAt":null,"recordedBranch":"agents/dev-1"},
|
|
1590
|
+
"worktreeRemoved":false,"branchDeleted":false,"removedDir":true,
|
|
1591
|
+
"workRecovery":{"path":"/w/.agents/recovered/dev-1-20260928T111000Z","classes":["untracked"],"bytes":2048,
|
|
1592
|
+
"outputs":{"paths":[{"path":"notes.md","bytes":2048}],"bytes":2048}},
|
|
1593
|
+
"childrenStopped":[{"instance":"dev-1-child","home":"/w/agents/dev/instances/dev-1-child","ok":true,"stopped":false,"alreadyIdle":true}],
|
|
1594
|
+
"planRevision":"4e5f6a7b8c9d0e1f2a3b4c5d","idempotencyKey":"r1","replayed":false}
|
|
1595
|
+
```
|
|
1746
1596
|
|
|
1747
|
-
|
|
1597
|
+
- `retention`: `{worktree: "retained" | "removed" | "absent", movedTo?,
|
|
1598
|
+
branch, detachedAt?, recordedBranch, branchDeleted?,
|
|
1599
|
+
branchDeletionSkipped?: {expected, actual, reason}}`, or `null` for a
|
|
1600
|
+
non-worktree mode.
|
|
1601
|
+
- `--discard-worktree` removes the worktree. `--delete-branch` deletes the
|
|
1602
|
+
worktree's verified branch (re-verified at deletion time) and implies
|
|
1603
|
+
discarding; a mismatch deletes nothing and reports
|
|
1604
|
+
`branchDeletionSkipped`.
|
|
1605
|
+
- `workRecovery` (or `workRecoveries[]`): `{path, classes, bytes, outputs?,
|
|
1606
|
+
repoCopy?}`; `outputs: {paths: [{path, bytes}], bytes}` names what was
|
|
1607
|
+
copied beyond tracked state, largest first.
|
|
1608
|
+
- When they apply: `rollbackIncomplete` and `retainedHome` (cleanup
|
|
1609
|
+
incomplete, home kept, exit 1), `forcedIncomplete`, `relinked`,
|
|
1610
|
+
`capabilityMeta`, `warnings`, `wakeSchedulesRemoved`.
|
|
1611
|
+
- A deferred self-retire (`--self`) prints `{retired, agent, deferred: true,
|
|
1612
|
+
pendingMarker, resultPath, logPath, completesInSec, completionPid}`, or
|
|
1613
|
+
`{…, alreadyScheduled: true, requestedAt}`.
|
|
1614
|
+
|
|
1615
|
+
**Guarded apply** (what the Desktop sends): `--plan-revision` and
|
|
1616
|
+
`--idempotency-key` together.
|
|
1617
|
+
- A used key replays its receipt as an **envelope** with `replayed: true`
|
|
1618
|
+
(receipts live beside the instances directory and outlive the home).
|
|
1619
|
+
- The revision is checked against a fresh plan: `E_PLAN_STALE {plan}`.
|
|
1620
|
+
- Children are stopped first (never escalated) and kept; `childrenStopped[]`
|
|
1621
|
+
lists them. One still running refuses everything: `E_CHILDREN_RUNNING
|
|
1622
|
+
{childrenStopped, plan}`.
|
|
1623
|
+
- A first guarded retire prints the raw receipt with `planRevision`,
|
|
1624
|
+
`idempotencyKey` and `replayed: false`.
|
|
1625
|
+
|
|
1626
|
+
Refusals (envelopes): `E_PLAN_STALE`, `E_CHILDREN_RUNNING`,
|
|
1627
|
+
`E_WORK_PRESERVATION_FAILED` (the home is kept; retry or
|
|
1628
|
+
`--discard-worktree`), `E_SESSION_UNKNOWN`, `E_AMBIGUOUS_INSTANCE`,
|
|
1629
|
+
`E_NO_ROOT`, `E_LIFECYCLE_FAILED`. A recovery whose Git status disagrees with
|
|
1630
|
+
the source's carries `details: {home, statusDisagreement: {repo, rows: [{path,
|
|
1631
|
+
source, recovery}], total}}` (the first 10 paths). Usage errors are text on
|
|
1632
|
+
stderr, not envelopes.
|
|
1633
|
+
|
|
1634
|
+
## Sessions and launch configurations
|
|
1635
|
+
|
|
1636
|
+
### Start and restart
|
|
1748
1637
|
|
|
1749
|
-
|
|
1750
|
-
|
|
1638
|
+
```text
|
|
1639
|
+
oats session start --home <abs> [--launch-config <name>|none] [--harness pi|claude|codex] [--model <id>] [--yolo|--no-yolo] [--server <id>] --json
|
|
1640
|
+
oats session restart --home <abs> [the same options] [--stop-grace <1-300 s>] --json
|
|
1641
|
+
```
|
|
1751
1642
|
|
|
1752
|
-
|
|
1643
|
+
Features `session-start`, `session-restart`, and `launch-config` for the
|
|
1644
|
+
selection flags. See [the start workflow](desktop-instance-start.md).
|
|
1645
|
+
|
|
1646
|
+
- The result is `{instance, agent, home, harness, backend, model,
|
|
1647
|
+
launchConfig, yolo, target, startedAt, restartCount, reused, warnings}`,
|
|
1648
|
+
plus `nativeRecordId` and `stop` (a restart's stop receipt) when they apply.
|
|
1649
|
+
- `warnings` (0.30) is always present: an array of strings, the warnings the
|
|
1650
|
+
capabilities' `launch` hooks returned for this start (as spawn's
|
|
1651
|
+
`warnings`), `[]` when there are none. Each is also appended to the
|
|
1652
|
+
instance's events as a `launch-warning` row, `data: {message}`. They are
|
|
1653
|
+
advisory: the start went ahead. Earlier kernels omit the field; read a
|
|
1654
|
+
missing `warnings` as `[]`.
|
|
1655
|
+
- Restart is one command: the kernel validates the new selection before
|
|
1656
|
+
stopping, and owns the stop, lock, launch recovery and metadata. Never
|
|
1657
|
+
restart by retiring and spawning.
|
|
1658
|
+
- A lost response does not mean the launch failed: check status before a
|
|
1659
|
+
retry. A remote home's saved route names its execution host.
|
|
1660
|
+
- Errors: `E_BAD_ARGS`, `E_SESSION_UNKNOWN`, `E_UNSUPPORTED_MODE`,
|
|
1661
|
+
`E_SESSION_START_BUSY`, `E_INSTANCE_RETIRING`, `E_LAUNCH_*`,
|
|
1662
|
+
`E_MODEL_UNKNOWN`, `E_UNSUPPORTED_HARNESS`, `E_SESSION_FAILED`.
|
|
1663
|
+
|
|
1664
|
+
### Upload
|
|
1753
1665
|
|
|
1754
1666
|
```text
|
|
1755
|
-
oats session
|
|
1756
|
-
[--launch-config name] [--harness pi|claude|codex] \
|
|
1757
|
-
[--model id] [--yolo|--no-yolo] --json
|
|
1758
|
-
oats session restart --home /absolute/home [the same options] --json
|
|
1667
|
+
oats session upload (--home <abs> | --server <id> --instance <name> | --server <id> --home <abs>) --file <path> --json
|
|
1759
1668
|
```
|
|
1760
1669
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
metadata. Desktop does not implement restart by retiring and spawning.
|
|
1765
|
-
Failure or timeout requires a fresh status check before retrying: a lost
|
|
1766
|
-
response does not establish that launch failed.
|
|
1670
|
+
Feature `session-upload`: copies a file (at most 64 MiB) into the instance's
|
|
1671
|
+
attachments. Remotely the bytes go on ssh stdin to the host's `session
|
|
1672
|
+
receive`, and the sha256 is verified.
|
|
1767
1673
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1674
|
+
The result is `{path, bytes, sha256, name, home, source}` (`path` is the
|
|
1675
|
+
stored file); a remote upload adds `server`, `instance` and `stderr?`. Errors:
|
|
1676
|
+
`E_BAD_ARGS`, `E_UPLOAD_TOO_LARGE`, `E_UPLOAD_FAILED` (including a remote
|
|
1677
|
+
sha256 mismatch; the remote file is left), `E_SESSION_UNKNOWN`,
|
|
1678
|
+
`E_REMOTE_INCOMPATIBLE`.
|
|
1772
1679
|
|
|
1773
1680
|
### Launch configurations
|
|
1774
1681
|
|
|
1775
1682
|
```text
|
|
1776
|
-
oats launch-config list [--dir
|
|
1777
|
-
oats launch-config set name --file
|
|
1778
|
-
oats launch-config remove name --dir
|
|
1779
|
-
oats launch-config preview (--home
|
|
1780
|
-
[--launch-config name] [--harness harness] [--model id] [--yolo|--no-yolo] --json
|
|
1683
|
+
oats launch-config list [--dir <d> | --home <abs> | --soul <name> [--dir <d>] [--agents-root <abs>]] --json
|
|
1684
|
+
oats launch-config set <name> --file <definition.json | -> [--keep-env] [--dir <d>] --json
|
|
1685
|
+
oats launch-config remove <name> [--dir <d>] --json
|
|
1686
|
+
oats launch-config preview (--home <abs> | --soul <name> [--dir <d>]) [--launch-config <name>|none] [--harness <h>] [--model <id>] [--yolo|--no-yolo] --json
|
|
1781
1687
|
```
|
|
1782
1688
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
filename is never passed to the server as though it existed there.
|
|
1787
|
-
|
|
1788
|
-
The list result supplies `context`, `selected` and `configurations`. Each
|
|
1789
|
-
configuration has a name, harness, executable, literal argument array,
|
|
1790
|
-
environment, model, permission choice and declaring `source`. Environment
|
|
1791
|
-
literals appear as `{ "redacted": true }`; references appear as
|
|
1792
|
-
`{ "fromEnv": "VARIABLE_NAME" }`. Optional executable/model/yolo fields can be
|
|
1793
|
-
null. An editor must not write redaction markers back. `--keep-env`, with
|
|
1794
|
-
`env` omitted from the replacement definition, copies the effective named
|
|
1795
|
-
configuration's environment once into the complete replacement.
|
|
1796
|
-
|
|
1797
|
-
Preview is read-only and returns a redacted invocation plus `preflight`
|
|
1798
|
-
checks. A successful inspection envelope can contain `result.ok: false`:
|
|
1799
|
-
the selected launch is not ready. Desktop displays the failed checks rather
|
|
1800
|
-
than treating successful inspection as permission to launch. Environment
|
|
1801
|
-
references resolve on the execution host at launch, including subsequent
|
|
1802
|
-
starts of the saved recipe. Editing a named definition does not change a
|
|
1803
|
-
running instance or silently update its frozen launch recipe. Select the
|
|
1804
|
-
configuration explicitly on a later start/restart to apply the new definition.
|
|
1805
|
-
|
|
1806
|
-
See [launch configuration syntax](configuration.md) and
|
|
1807
|
-
[the Desktop start/restart workflow](desktop-instance-start.md).
|
|
1808
|
-
|
|
1809
|
-
### `oats spawn <agent> … --json`
|
|
1810
|
-
|
|
1811
|
-
`result` fields (always present):
|
|
1812
|
-
|
|
1813
|
-
| field | type | meaning |
|
|
1814
|
-
| ---------- | --------------- | ------------------------------------------ |
|
|
1815
|
-
| `instance` | string | new instance name |
|
|
1816
|
-
| `agent` | string | soul/agent name |
|
|
1817
|
-
| `home` | string | absolute instance home path |
|
|
1818
|
-
| `work` | string | work mode (worktree/checkout/attached/workspace/directory) |
|
|
1819
|
-
| `branch` | string \| null | work branch when applicable |
|
|
1820
|
-
| `launched` | boolean | whether a tmux window was started |
|
|
1821
|
-
| `warnings` | string[] | non-fatal warnings (always an array) |
|
|
1822
|
-
| `tmux` | {session,window} \| null | tmux target |
|
|
1823
|
-
|
|
1824
|
-
Additional informative fields: `repo`, `harness`, `model`, `parent`,
|
|
1825
|
-
`sibling` (explicit sibling cluster link when a root-level sibling relation
|
|
1826
|
-
was declared, else null), `relation` (`child`/`sibling`/`parent` when a
|
|
1827
|
-
relation was declared at spawn, else null), `spawnOrigin`, `attach`.
|
|
1828
|
-
|
|
1829
|
-
Stable error codes: `E_USAGE`, `E_LOCAL_MISSING` (no `oats-local.yaml` in reach:
|
|
1830
|
-
a spawn needs a workspace deployment), `E_NO_DEPLOYMENT` (the deployment's
|
|
1831
|
-
`agents/` root is missing), `E_SOUL_UNKNOWN`, `E_UNKNOWN_AGENT`,
|
|
1832
|
-
`E_AMBIGUOUS_SOUL`, `E_PARENT_NOT_FOUND`, `E_RELATIVE_NOT_FOUND`,
|
|
1833
|
-
`E_RELATIVE_AMBIGUOUS` (a `--relative-to`/`--parent` anchor name matches
|
|
1834
|
-
multiple team instances — disambiguate with `--relative-root <agents-root>`
|
|
1835
|
-
— or the chosen anchor is shadowed by a same-named instance so the lineage
|
|
1836
|
-
edge would resolve wrongly), `E_BAD_ARGS`,
|
|
1837
|
-
`E_INSTANCE_NAME_INVALID`, `E_INSTANCE_NAME_TAKEN`, `E_SPAWN_FAILED`.
|
|
1838
|
-
|
|
1839
|
-
**Instance names** (0.26.0, feature `spawn-name`). By default the name is
|
|
1840
|
-
derived: `<agent>-<purpose>` with `--purpose <slug>`, else `<agent>-<n>`.
|
|
1841
|
-
`--name <slug>` (human decision 2026-09-24) is the explicit opt-in: the
|
|
1842
|
-
instance name is **exactly** `<slug>`, with no `<agent>-` prefix.
|
|
1843
|
-
|
|
1844
|
-
- `--name` and `--purpose` are mutually exclusive (`E_BAD_ARGS`), and
|
|
1845
|
-
`--name` needs a value (`E_BAD_ARGS`).
|
|
1846
|
-
- The name is never rewritten. Input that is not already a slug (lowercase
|
|
1847
|
-
letters and digits, single dashes between them) is
|
|
1848
|
-
`E_INSTANCE_NAME_INVALID`, and so is a name equal to any soul name of the
|
|
1849
|
-
deployment (souls on the agents root, and every soul the workspace
|
|
1850
|
-
declares, fetched or not). Soul and instance references stay unambiguous.
|
|
1851
|
-
- **Instance names are at most 64 characters** (0.26.0; the tightest
|
|
1852
|
-
consumer is the messaging alias, which allows 1–64). This covers every
|
|
1853
|
-
name, explicit and derived. A longer name is `E_INSTANCE_NAME_INVALID`
|
|
1854
|
-
("instance names are at most 64 characters"), in preview and apply alike,
|
|
1855
|
-
and is never truncated. For a derived name the refusal names the purpose
|
|
1856
|
-
to shorten, and the de-duplication suffix counts: when `<agent>-<purpose>`
|
|
1857
|
-
is taken and `<agent>-<purpose>-2` would exceed 64, the spawn is refused.
|
|
1858
|
-
- **Names are unique across the deployment.** An explicit name that any
|
|
1859
|
-
`<agents-root>/<soul>/instances/` already holds (including homes whose soul
|
|
1860
|
-
was since removed), or that a live window in the target tmux session carries
|
|
1861
|
-
(tmux backend, launched or `--no-launch`), is `E_INSTANCE_NAME_TAKEN`
|
|
1862
|
-
(`details.instance`, `details.home` or `details.session`). There is never a
|
|
1863
|
-
silent `-2` for a name the operator typed. These checks run after
|
|
1864
|
-
idempotency-key recovery (a keyed retry replays its receipt), and a
|
|
1865
|
-
concurrent spawn of another soul under the same name is caught after
|
|
1866
|
-
placement (see *Exclusive placement*). The invariant covers spawns through
|
|
1867
|
-
the CLI. Homes from earlier kernels may already share a name.
|
|
1868
|
-
- Derived names de-duplicate deployment-wide too (`-2`, `-3`, …), against
|
|
1869
|
-
every soul's instances and every soul name. Two souls never derive the same
|
|
1870
|
-
name (soul `a` with `--purpose b-c` against soul `a-b` with `--purpose c`).
|
|
1871
|
-
- `--preview` reports the final name (`instance`, `decision.instance`) and
|
|
1872
|
-
refuses with the same codes. The name is part of the decision revision, so
|
|
1873
|
-
`--expect-decision` binds it: another name under a confirmed decision is
|
|
1874
|
-
`E_DECISION_STALE`.
|
|
1875
|
-
|
|
1876
|
-
Dispatch-level failures (any `--json` command): `E_UNKNOWN_COMMAND` (no
|
|
1877
|
-
kernel subcommand or capability namespace matches, or unknown capability
|
|
1878
|
-
subcommand), `E_CAPABILITY_INACTIVE`, `E_CAPABILITY_BLOCKED` (untrusted),
|
|
1879
|
-
`E_CAPABILITY_BROKEN`, `E_DUPLICATE_NAMESPACE`, `E_CONFIG_BROKEN` — all still
|
|
1880
|
-
exactly one stdout envelope with a nonzero exit.
|
|
1881
|
-
|
|
1882
|
-
### Knowledge operations and OKF v2
|
|
1883
|
-
|
|
1884
|
-
Discover provider-declared operations rather than assuming a particular memory
|
|
1885
|
-
format. The knowledge capability's version owns its result shape; CLI API v1
|
|
1886
|
-
does not freeze the old OKF v1 `harvest: spawned|skipped` body for every provider.
|
|
1887
|
-
See [knowledge](knowledge.md) for the prepared OKF 2.0.0 version scope.
|
|
1888
|
-
|
|
1889
|
-
```bash
|
|
1890
|
-
oats operation run knowledge:inspect --home /absolute/source-home --json
|
|
1891
|
-
oats operation run knowledge:harvest --home /absolute/source-home --json
|
|
1892
|
-
```
|
|
1689
|
+
Feature `launch-config`. Configurations are a host choice in
|
|
1690
|
+
`oats-local.yaml` `launch-configs:` ([syntax](configuration.md)). All four
|
|
1691
|
+
accept `--server <id>`.
|
|
1893
1692
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
-
|
|
1899
|
-
- `acceptedView` (the registered snapshot, not a fresh read), `status` with
|
|
1900
|
-
capture/processing/delivery/acceptance receipts, and `scheduler` diagnostics;
|
|
1901
|
-
- `liveMemory: {available, reason, observedAt}` and labeled `documents`.
|
|
1902
|
-
|
|
1903
|
-
Live Markdown documents are `Working state (STATE.md)`, `Log (log.md)` and
|
|
1904
|
-
sorted `Pending note: <relative-name>`, including nested notes. Missing files
|
|
1905
|
-
are omitted; durable receipts follow as a text document. Only a live source
|
|
1906
|
-
whose pointer/metadata still matches may supply live memory. Retired, missing,
|
|
1907
|
-
reused or unverified homes return durable documents and explicit unavailability.
|
|
1908
|
-
Unsafe live documents fail instead of returning a partial success. Inspection is
|
|
1909
|
-
read-only and does not capture, refresh, schedule or launch a model.
|
|
1910
|
-
|
|
1911
|
-
The explicit preview limit is **256 KiB per document**, with `truncated: true`
|
|
1912
|
-
and original `bytes` for larger files. Smaller files are byte-exact. The complete
|
|
1913
|
-
JSON envelope drains stdout; consumers must not clip it at a small output-buffer
|
|
1914
|
-
limit. Provider `read` returns full Markdown, not this inspection preview.
|
|
1915
|
-
|
|
1916
|
-
After the home disappears, operate from durable deployment context:
|
|
1917
|
-
|
|
1918
|
-
```bash
|
|
1919
|
-
oats okf inspect --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
|
|
1920
|
-
oats okf refresh --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
|
|
1693
|
+
```json
|
|
1694
|
+
{"context":"/w","level":"/w","file":"/w/oats-local.yaml","selected":null,
|
|
1695
|
+
"configurations":[{"name":"reviewers","harness":"claude","executable":null,"args":["--permission-mode","plan"],
|
|
1696
|
+
"env":{"ANTHROPIC_API_KEY":{"fromEnv":"REVIEW_KEY"},"REVIEW_MODE":{"redacted":true}},
|
|
1697
|
+
"model":"opus","yolo":null,"source":"/w/oats-local.yaml","shadows":[]}]}
|
|
1921
1698
|
```
|
|
1922
1699
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1700
|
+
- **list**: `selected` is `null`, `{home, instance}` or `{soul, agentsRoot}`;
|
|
1701
|
+
`level` and `file` are `null` without an `oats-local.yaml` (the set is then
|
|
1702
|
+
empty). An environment literal is `{redacted: true}`, a reference
|
|
1703
|
+
`{fromEnv}`; values never leave the file.
|
|
1704
|
+
- **set**/**remove**: `{name, action, level, file, before, after,
|
|
1705
|
+
effective}`. `set --file` takes `{harness, executable?, args?, env?, model?,
|
|
1706
|
+
yolo?}` (`-` reads stdin). `--keep-env` keeps the declared environment when
|
|
1707
|
+
`env` is omitted. Errors: `E_LOCAL_MISSING`, `E_BAD_ARGS` (including
|
|
1708
|
+
`--home`/`--soul`), `E_LAUNCH_CONFIG_UNKNOWN`, `E_LAUNCH_CONFIG_INVALID`,
|
|
1709
|
+
`E_CONFIG_BROKEN`, `E_HOME_UNKNOWN`.
|
|
1710
|
+
- **preview** (read-only) answers `{context, selected, selection: {source,
|
|
1711
|
+
launchConfig, harness, model, yolo}, harness, model, modelSource, yolo,
|
|
1712
|
+
launchConfig, launchConfigSource, executable: {path, declared,
|
|
1713
|
+
resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
|
|
1714
|
+
true} | {name, reference: true}], command (redacted), prompt, hooks,
|
|
1715
|
+
preflight: [{check, ok, detail}], ok}`.
|
|
1716
|
+
|
|
1717
|
+
A successful envelope can carry `ok: false`: show the failed `preflight`
|
|
1718
|
+
checks. The prompt is named, never the task body. A home predating launch
|
|
1719
|
+
recipes answers its frozen command with `selection.source: "frozen-command"`
|
|
1720
|
+
and `hooks: null`; a selection on it is `E_LAUNCH_LEGACY`. Editing a
|
|
1721
|
+
definition never changes a running instance's recipe.
|
|
1722
|
+
|
|
1723
|
+
<a id="schedules-and-triggers"></a>
|
|
1724
|
+
## Schedules and triggers
|
|
1725
|
+
|
|
1726
|
+
Schedules (feature `schedule`, `scheduleApi: 2`; history feature
|
|
1727
|
+
`schedule-read-2`, `scheduleHistoryApi: 3`), triggers (feature `triggers`,
|
|
1728
|
+
`triggerApi: 1`) and workspace automations (feature `automations`,
|
|
1729
|
+
`automationsApi: 1`). Model: [schedules.md](schedules.md#triggers).
|
|
1730
|
+
|
|
1731
|
+
The lists stay separate: a trigger never appears in `schedule list`, nor a
|
|
1732
|
+
schedule in `trigger list`. Each answers this machine's local items and every
|
|
1733
|
+
workspace item of a readable member. Render the rows; never re-derive them.
|
|
1734
|
+
|
|
1735
|
+
### `oats schedule`
|
|
1925
1736
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1737
|
+
```text
|
|
1738
|
+
oats schedule list [--dir <d>] --json
|
|
1739
|
+
oats schedule show <id> --json
|
|
1740
|
+
```
|
|
1928
1741
|
|
|
1929
1742
|
```json
|
|
1930
|
-
{"
|
|
1743
|
+
{"scope":"/w","scheduleApi":2,"scheduleHistoryApi":3,
|
|
1744
|
+
"integrity":{"sources":[{"path":"definitions","status":"ok","bytes":1206},{"path":"state","status":"ok","bytes":4410}]},
|
|
1745
|
+
"host":{"name":"ana-laptop","ghUser":{"github.com":"ana"}},"triggers":{"count":4,"command":"oats trigger list"},
|
|
1746
|
+
"snapshot":{"takenAt":"2026-09-26T19:58:09.281Z","problems":1},
|
|
1747
|
+
"schedules":[{"id":"nightly","kind":"spawn","cron":"0 7 * * *","tz":"Europe/Madrid","agent":"rm","task":"Check the release branch.","purpose":"nightly",
|
|
1748
|
+
"enabled":true,"createdAt":"2026-09-26T19:58:09.380Z","updatedAt":"2026-09-26T19:58:09.380Z","scope":"/w","scheduleApi":2,"scheduleHistoryApi":3,
|
|
1749
|
+
"executionStatus":{"kind":"legacy","capture":"unknown","migrationRequired":true},
|
|
1750
|
+
"nextRun":"2026-09-29T05:00:00.000Z","nextDue":"2026-09-29T05:00:00.000Z","lastRun":null,
|
|
1751
|
+
"history":{"status":"ok","stored":1,"truncated":false},
|
|
1752
|
+
"recentRuns":[{"scheduledFor":"2026-09-28T05:00:00.000Z","startedAt":"2026-09-28T05:00:01.000Z","kind":"spawn","outcome":"ended",
|
|
1753
|
+
"runId":"3f9a0b1c2d3e4f5a6b7c8d9e","legacy":false,"settled":true,"recordedAt":"2026-09-28T05:40:00.000Z","transitions":["started","ended"],
|
|
1754
|
+
"session":{"instance":"rm-nightly","home":"/w/agents/rm/instances/rm-nightly","incarnation":"2026-09-28T05:00:01.000Z","server":null,"delivery":"launched"}}],
|
|
1755
|
+
"running":false,"name":"nightly","qualifiedId":"local/nightly",
|
|
1756
|
+
"origin":{"kind":"local","path":"oats-schedules.json","url":null,"localPath":"/w/oats-schedules.json"},
|
|
1757
|
+
"description":null,"owner":null,"runsOn":null,"runsHere":true,"reason":null,"enabledHere":true,
|
|
1758
|
+
"soul":{"name":"rm","origin":{"kind":"member","repoKey":"github.com/nw/agents","member":"agents"}},
|
|
1759
|
+
"teams":[],"launchConfig":null,"harness":null,"model":null,"concurrency":null}],
|
|
1760
|
+
"scheduler":{"installed":true,"active":true,"registered":true,"lastTick":"2026-09-28T11:59:00.000Z","maxConcurrent":2}}
|
|
1931
1761
|
```
|
|
1932
1762
|
|
|
1933
|
-
|
|
1934
|
-
|
|
1763
|
+
- `list`: `{scope, scheduleApi, scheduleHistoryApi, integrity, host,
|
|
1764
|
+
triggers, snapshot, schedules, scheduler}`; `triggers` counts the trigger
|
|
1765
|
+
definitions left out. `show <id>`: `{schedule}`, without `integrity`.
|
|
1766
|
+
- **A readable row**: the definition (`id, kind, cron, tz, enabled, …`, and
|
|
1767
|
+
`agent/task/purpose/harness` for a spawn, `argv/cwd` for a command, the
|
|
1768
|
+
message for a wake, the operation for an operation) plus `scope,
|
|
1769
|
+
scheduleApi, scheduleHistoryApi, executionStatus, nextRun, lastRun, history,
|
|
1770
|
+
recentRuns, running, attempt?, pendingWake?` and the
|
|
1771
|
+
[shared row fields](#automations-shared-rows).
|
|
1772
|
+
`executionStatus` is `{kind: "legacy" | "invalid", capture: "unknown",
|
|
1773
|
+
migrationRequired: true, reason?, intent?}`; only `legacy` runs.
|
|
1774
|
+
- **An unreadable row** (`list` only): `{id, scope, scheduleApi,
|
|
1775
|
+
scheduleHistoryApi, unreadable: {code, message}, history: {status:
|
|
1776
|
+
"corrupt", stored: null, truncated: false}, recentRuns: []}`. One bad job
|
|
1777
|
+
never fails the others.
|
|
1778
|
+
- `history`: `{status: "ok", stored, truncated}` or the corrupt form; capped
|
|
1779
|
+
at 50 rows.
|
|
1780
|
+
- **A run** (`recentRuns[]`, `lastRun`): the producer's fields
|
|
1781
|
+
(`scheduledFor, startedAt, kind, outcome, …`) plus `runId, legacy: false,
|
|
1782
|
+
settled, recordedAt, transitions, session`.
|
|
1783
|
+
- `runId` = `sha256(scheduledFor|startedAt|attemptId)[0:24]`, opaque: never
|
|
1784
|
+
recompute or dedupe by it. `lastRun` shares its history row's `runId`.
|
|
1785
|
+
- `transitions` is the outcome sequence; `settled` a boolean. `outcome` is
|
|
1786
|
+
the producer's word (`ended`, `stopped`, `blocked`, `invalid`,
|
|
1787
|
+
`delivered`, `skipped`, `unknown`, …).
|
|
1788
|
+
- A pre-API-3 row: `runId: null, legacy: true, settled: null, transitions:
|
|
1789
|
+
null`, `session`, no `recordedAt`. A corrupt element: `{runId: null,
|
|
1790
|
+
legacy: true, corrupt: true}`.
|
|
1791
|
+
- `session` is `{instance, home, incarnation, server, delivery: "launched"
|
|
1792
|
+
| "delivered-active" | "none"}`: recorded provenance, not a transcript
|
|
1793
|
+
reader.
|
|
1794
|
+
- `nextDue` is "when it next runs" in every row (`null` when another host
|
|
1795
|
+
runs it); a schedule row also keeps `nextRun`.
|
|
1796
|
+
- **Integrity.** `oats-schedules.json` and `.agents/schedules/state.json` are
|
|
1797
|
+
opened as regular files only, at most 1 MiB. `integrity.sources[]` is
|
|
1798
|
+
`{path: "definitions" | "state", status: "ok" | "absent" | "refused" |
|
|
1799
|
+
"oversize" | "corrupt", bytes}`.
|
|
1800
|
+
- Refusals: `E_SCHEDULE_STATE_OVERSIZE` and `E_SCHEDULE_INVALID` (`details:
|
|
1801
|
+
{source, field?}`), `E_SCHEDULE_IDENTITY` (`details: {key, declared}`), and
|
|
1802
|
+
`E_BAD_ARGS` for an id not matching `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`.
|
|
1803
|
+
`list` refuses as a whole only when a scope file is unreadable.
|
|
1804
|
+
|
|
1805
|
+
**Other verbs** (envelopes):
|
|
1806
|
+
- `add <id> (--file <spec.json> | --spec-json <json>)`, `update`, `enable`,
|
|
1807
|
+
`disable` → `{schedule}`. `run [--force]`, `remove [--force]`, `reconcile
|
|
1808
|
+
[--clear]` → the action's receipt. `tick [--dry-run] [--host]` →
|
|
1809
|
+
`{tickedAt, considered, scheduler}`; `host install | uninstall | status` →
|
|
1810
|
+
`{scheduler}`.
|
|
1811
|
+
- `test <id>` → `{test: {id, qualifiedId, kind, placement: {runsHere, reason,
|
|
1812
|
+
reasonDetail?, enabledHere, runsOn, owner, host}, soul: {name, origin,
|
|
1813
|
+
resolves, error} | null, nextDue, spawned: false, problems, ok}}`. The soul
|
|
1814
|
+
is checked as the run would start it; not running here, disabled, invalid
|
|
1815
|
+
or unresolved is a problem. It runs nothing.
|
|
1816
|
+
- Errors: `E_SCHEDULE_UNKNOWN`, `E_SCHEDULE_EXISTS`, `E_SCHEDULE_INVALID`,
|
|
1817
|
+
`E_SCHEDULE_RUNNING`, `E_SCHEDULE_DISABLED`, `E_SCHEDULE_UNRESOLVED`,
|
|
1818
|
+
`E_SCHEDULE_FAILED`, `E_LOCAL_MISSING`, `E_BAD_ARGS`.
|
|
1819
|
+
|
|
1820
|
+
### `oats trigger`
|
|
1821
|
+
|
|
1822
|
+
```text
|
|
1823
|
+
oats trigger list | show <id> | status [<id>] | test <id> | add (--file <json> | --from <package>:<template> [--set k=v]) | enable <id> | disable <id> | remove <id> --json
|
|
1935
1824
|
```
|
|
1936
1825
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1826
|
+
Event-driven spawns. Local definitions live in `oats-schedules.json` (`kind:
|
|
1827
|
+
"trigger"`).
|
|
1828
|
+
|
|
1829
|
+
- `list`: `{triggerApi, scope, host, snapshot, triggers, scheduler}`.
|
|
1830
|
+
`show`, `add`, `enable`, `disable` → `{trigger}`; `remove` → `{removed,
|
|
1831
|
+
live: [instance]}`. A stored definition that no longer validates carries
|
|
1832
|
+
`invalid: {code, message, field?}`.
|
|
1833
|
+
- **A trigger row**: the [shared row fields](#automations-shared-rows) plus
|
|
1834
|
+
`kind: "trigger", on, spawn, template?, enabled, triggerApi, scope,
|
|
1835
|
+
createdAt, updatedAt`.
|
|
1836
|
+
- `id` is always qualified (`local/<id>` or `<member>/<id>`); verbs accept
|
|
1837
|
+
`local/<id>` or a bare local id.
|
|
1838
|
+
- `on`: `{source: "github.pull_request", repo, events: [opened | reopened |
|
|
1839
|
+
ready_for_review | labeled | synchronize], labels, base?, poll}`.
|
|
1840
|
+
- `spawn`: `{soul, purpose, task, teams, launchConfig?, harness?, model?,
|
|
1841
|
+
yolo?, backend?}`. `purpose` and `task` template over `{repo}`,
|
|
1842
|
+
`{number}`, `{url}`, `{event}`, `{trigger}`, `{headSha}`. `teams` are
|
|
1843
|
+
distinct labels passed as `--provider <messaging cap> join=<labels>`; a
|
|
1844
|
+
soul without messaging refuses `E_TRIGGER_TEAMS {soul, teams}`.
|
|
1845
|
+
- `template?`: `{package, version, commit, template}`.
|
|
1846
|
+
- `lastRun`: `{at, instance, home, event, number, key}`; `nextDue`: the
|
|
1847
|
+
next poll here, `null` before the first.
|
|
1848
|
+
- `status [<id>]` → `{triggerApi, scope, triggers: [{id, name, enabled,
|
|
1849
|
+
runsHere, reason, enabledHere, repo, soul, concurrency: {max, perKey},
|
|
1850
|
+
liveCount, live: [{instance, home, repo, number, event}], lastPoll: {at, ok:
|
|
1851
|
+
true, prs, matching} | {at, ok: false, error} | null, nextPollAt, nextDue,
|
|
1852
|
+
pending: [{key, event, number, url, observedAt}], fired: [{key, at,
|
|
1853
|
+
instance, home, event, number}] (newest 50), firedTotal, lastError: {at,
|
|
1854
|
+
code, message, key?} | null}]}`. It writes nothing.
|
|
1855
|
+
- `test <id>` → `{triggerApi, id, ok, placement: {runsOn, owner, host,
|
|
1856
|
+
runsHere, reason, detail?, enabledHere}, gh: {ok, account,
|
|
1857
|
+
credentialSource, reachesHostTimer, note, detail}, repo: {key, readable,
|
|
1858
|
+
fullName, permissions: {push, maintain, admin}, canMerge} | {key, readable:
|
|
1859
|
+
false, error}, soul: {resolves, name, agent, messaging} | {resolves: false,
|
|
1860
|
+
name, error}, teams: {requested, undeclared | null, messaging}, wouldFire:
|
|
1861
|
+
[{key, repo, number, event, url, held?}], pollError?, problems, warnings,
|
|
1862
|
+
spawned: false}`. `ok` counts `problems` only; a credential the host timer
|
|
1863
|
+
cannot reach is a warning.
|
|
1864
|
+
- A triggered instance records `instance.json.trigger`; its event file is
|
|
1865
|
+
`OATS_TRIGGER_EVENT_FILE`.
|
|
1866
|
+
- Errors: `E_TRIGGER_INVALID {field}`, `E_TRIGGER_EXISTS`,
|
|
1867
|
+
`E_TRIGGER_UNKNOWN`, `E_TRIGGER_TEAMS`, `E_TRIGGER_POLL`,
|
|
1868
|
+
`E_TRIGGER_FAILED`, `E_BAD_ARGS`, `E_PACKAGE_MISSING`,
|
|
1869
|
+
`E_PACKAGE_MANIFEST`, `E_LOCAL_MISSING`.
|
|
1870
|
+
|
|
1871
|
+
<a id="automations-shared-rows"></a>
|
|
1872
|
+
### Shared row fields and workspace automations
|
|
1873
|
+
|
|
1874
|
+
Details: [schedules.md](schedules.md#workspace-triggers-and-schedules).
|
|
1875
|
+
|
|
1876
|
+
**Both lists** carry `host: {name | null, ghUser: {<gh host>: <login> |
|
|
1877
|
+
null}}` (this machine's `host.name` and its `gh` logins, compared with a
|
|
1878
|
+
row's `owner`), `snapshot: {takenAt, problems (a count)} | null` (the last
|
|
1879
|
+
`oats sync` snapshot), and `scheduler: {installed, active, registered,
|
|
1880
|
+
lastTick, maxConcurrent, …}` (nothing runs unless installed, active and
|
|
1881
|
+
registered).
|
|
1882
|
+
|
|
1883
|
+
**Every row** carries:
|
|
1884
|
+
- `id` (a local schedule keeps its bare id; a workspace item is
|
|
1885
|
+
`<member>/<id>`), `qualifiedId` (always qualified), `name` (the bare id).
|
|
1886
|
+
- `origin`: `{kind: "local", path: "oats-schedules.json", url: null,
|
|
1887
|
+
localPath}` or `{kind: "workspace", repoKey, path, commit, url,
|
|
1888
|
+
localPath}` (`url` for `github.com` only; `localPath` `null` when the
|
|
1889
|
+
member is not cloned here).
|
|
1890
|
+
- `description`, `owner`, `runsOn`, `runsHere`, `reason` (`null` |
|
|
1891
|
+
`host-unnamed` | `assigned-elsewhere` | `owner-mismatch` | `untrusted`),
|
|
1892
|
+
`reasonDetail`, `enabledHere`.
|
|
1893
|
+
- `untrusted` (0.30, [automations.trust](configuration.md#who-runs-workspace-automations)):
|
|
1894
|
+
placed on this host (`runsOn` and `owner` match) but `oats-local.yaml`
|
|
1895
|
+
`automations.trust` does not admit it, so it never runs here. Its
|
|
1896
|
+
`reasonDetail` names the line to add. Group it as needing attention, like
|
|
1897
|
+
`owner-mismatch`. A kernel before 0.30 never sends it; treat an unknown
|
|
1898
|
+
reason as "does not run here".
|
|
1899
|
+
- `soul: {name, origin} | null` (`null` for command, wake and operation
|
|
1900
|
+
schedules). `origin` is `{kind: "member", repoKey, member}`, `{kind:
|
|
1901
|
+
"package", package, version}`, `{kind: "external", repoKey, source}`,
|
|
1902
|
+
`{kind: "ambiguous", candidates (a count)}`, or `null`.
|
|
1903
|
+
- `task`, `teams`, `launchConfig`, `harness`, `model`, `concurrency`,
|
|
1904
|
+
`lastRun`, `nextDue`, `invalid?`. A schedule row's `kind` is its run
|
|
1905
|
+
(`spawn | command | wake | operation`), its `teams` is `[]` and
|
|
1906
|
+
`concurrency` `null`. A workspace item another host runs carries its
|
|
1907
|
+
definition and placement only.
|
|
1908
|
+
|
|
1909
|
+
**Workspace items:**
|
|
1910
|
+
- `enable`/`disable` edit `oats-local.yaml` `triggers.disabled` or
|
|
1911
|
+
`schedules.disabled`. `update` and `remove` refuse `E_AUTOMATION_WORKSPACE
|
|
1912
|
+
{id, origin}`. `schedule run`/`reconcile` elsewhere refuse
|
|
1913
|
+
`E_AUTOMATION_NOT_HERE {id, reason, runsOn, owner}`.
|
|
1914
|
+
- `oats trigger|schedule add … --workspace <member> --runs-on <host> --owner
|
|
1915
|
+
<host>/<login> --json` writes the file in the member clone and answers
|
|
1916
|
+
`{id, written, file: {member, repoKey, path, content, written?}}`. Errors:
|
|
1917
|
+
`E_AUTOMATION_MEMBER`, `E_TRIGGER_EXISTS`/`E_SCHEDULE_EXISTS`, and the
|
|
1918
|
+
kind's validation codes.
|
|
1919
|
+
- `oats automations refresh --json` → `{automationsApi, snapshot (the file's
|
|
1920
|
+
path), triggers, schedules (counts), problems, takenAt}`.
|
|
1921
|
+
- The tick's `considered[]` holds schedule rows and trigger rows `{workspace,
|
|
1922
|
+
trigger, action, …}` with actions `not-due`, `poll-failed`, `polled`
|
|
1923
|
+
(`prs`, `matching`), `held`, `fired` (`key`, `instance`, `home`),
|
|
1924
|
+
`spawn-failed` (`key`, `code`, `error`), `would-fire`, `invalid` and
|
|
1925
|
+
`not-here`. A workspace schedule's state key is `<member>~<id>`. A failed
|
|
1926
|
+
snapshot refresh is `{workspace, action: "error", error}`.
|
|
1927
|
+
|
|
1928
|
+
<a id="the-harness-rename-feature-harness-oats-0270"></a>
|
|
1929
|
+
## Harness input spellings
|
|
1930
|
+
|
|
1931
|
+
What starts an instance (pi, claude or codex) is its **harness** (feature
|
|
1932
|
+
`harness`), and every output uses that name. These inputs still accept the
|
|
1933
|
+
older `runtime` spelling: it is read as `harness`, and the next write records
|
|
1934
|
+
`harness`. A pair that disagrees is refused.
|
|
1935
|
+
|
|
1936
|
+
| Input | Old spelling | Warning | Both, disagreeing |
|
|
1937
|
+
|---|---|---|---|
|
|
1938
|
+
| `spawn` (and `--preview`), `session start`/`restart`, `launch-config preview`, and their `--server` forms | `--runtime <h>` | yes | `E_BAD_ARGS` |
|
|
1939
|
+
| `oats-local.yaml` `launch-configs.<name>` | `runtime:` | yes | `E_WORKSPACE_SCHEMA` (`reason: "harness-conflict"`) |
|
|
1940
|
+
| `launch-config set --file` | `runtime` | yes; written as `harness` | `E_LAUNCH_CONFIG_INVALID` |
|
|
1941
|
+
| a home's `instance.json` and launch recipe `version: 1` | `runtime` | yes, naming the home; the next start records `harness` | |
|
|
1942
|
+
| schedule definitions (`add`/`update`, stored jobs) | `runtime` | yes | `E_SCHEDULE_INVALID` |
|
|
1943
|
+
| schedule run records | `startedRuntime` | no | |
|
|
1944
|
+
| a flat soul.yaml (a capability-defined agent) | `runtime:` | no | `E_BAD_MANIFEST` |
|
|
1945
|
+
| a manifest `requires[]` harness-package row | `runtime` | no | a spawn problem |
|
|
1946
|
+
|
|
1947
|
+
One warning per command, however many old spellings it read:
|
|
1948
|
+
|
|
1949
|
+
```json
|
|
1950
|
+
{"code":"deprecated-runtime-name","key":"runtime","replacement":"harness","sources":["the --runtime flag (use --harness)"],"message":"`runtime` was renamed to `harness` in 0.27.0; the old name is still read here (the --runtime flag (use --harness)) and a later release drops it"}
|
|
1951
|
+
```
|
|
1942
1952
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1953
|
+
The `--server` routes translate for a host without the `harness` feature:
|
|
1954
|
+
they send `--runtime` and `runtime` keys and read its `runtimes` list.
|