@awebai/oats 0.25.9 → 0.27.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 +8 -6
- package/bin/oats.mjs +648 -1755
- package/capabilities/oats-authoring/oats-package.json +2 -2
- package/capabilities/oats-authoring/oats.json +2 -2
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +279 -93
- package/capabilities/oats-aweb/injects/aweb.md +7 -2
- package/capabilities/oats-aweb/lib/binding-wire.mjs +89 -13
- package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
- package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
- package/capabilities/oats-aweb/oats.json +8 -4
- package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
- package/capabilities/oats-jira/oats.json +2 -2
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
- package/capabilities/oats-linear/oats.json +2 -2
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
- package/capabilities/oats-review/oats.json +3 -2
- package/docs/capabilities.md +229 -58
- package/docs/capability-manifest.schema.json +29 -9
- package/docs/configuration.md +17 -5
- package/docs/conventions.md +18 -28
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
- package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
- package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
- package/docs/design/2026-09-20-redesign-program-board.md +2 -2
- package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +34 -1
- package/docs/design/2026-09-24-phase-d-plan.md +57 -0
- package/docs/design/2026-09-25-teams-contract.md +226 -0
- package/docs/design/README.md +3 -3
- package/docs/design/launch-configurations.md +20 -16
- package/docs/design/operations-contract.md +27 -10
- package/docs/desktop-cli-api.md +604 -271
- package/docs/desktop-instance-start.md +3 -3
- package/docs/desktop.md +7 -13
- package/docs/execution-targets.md +16 -18
- package/docs/first-team.md +15 -18
- package/docs/implementation.md +31 -62
- package/docs/integrations.md +64 -33
- package/docs/knowledge-capability-authoring.md +1 -1
- package/docs/knowledge-reference/package-craft.md +10 -8
- package/docs/knowledge-theory.md +1 -1
- package/docs/knowledge.md +10 -11
- package/docs/layers.md +16 -17
- package/docs/oats-local.schema.json +30 -1
- package/docs/oats-membership.schema.json +5 -3
- package/docs/oats-package.schema.json +2 -2
- package/docs/oats-workspace.schema.json +1 -1
- package/docs/{official-marketplace.md → official-catalog.md} +15 -16
- package/docs/packages.md +76 -53
- package/docs/release-notes/v0.22.0.md +1 -1
- package/docs/release-notes/v0.23.1.md +1 -1
- package/docs/release-notes/v0.26.0.md +670 -0
- package/docs/release-notes/v0.27.0.md +100 -0
- package/docs/schedules.md +54 -132
- package/docs/servers.md +4 -4
- package/docs/soul.schema.json +11 -4
- package/docs/souls-and-instances.md +60 -47
- package/docs/workspaces.md +80 -58
- package/injects/instance-boundary.md +2 -2
- package/injects/work-attached.md +1 -1
- package/injects/work-workspace.md +2 -2
- package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
- package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
- package/lib/capability-contract.mjs +110 -0
- package/lib/config-data.mjs +2 -2
- package/lib/core.mjs +947 -5023
- package/lib/deprecation.mjs +24 -0
- package/lib/digest.mjs +12 -0
- package/lib/instance-inspect.mjs +397 -0
- package/lib/instance-lifecycle.mjs +3 -4
- package/lib/instance-resolution.mjs +212 -26
- package/lib/instruction-composition.mjs +0 -20
- package/lib/materialize.mjs +6 -4
- package/lib/operator-dispatch.mjs +33 -13
- package/lib/packages.mjs +25 -190
- package/lib/process-group.mjs +1 -1
- package/lib/provider-binding.mjs +4 -2
- package/lib/provider-reasons.mjs +3 -68
- package/lib/remote.mjs +1 -1
- package/lib/resolve.mjs +204 -68
- package/lib/schedule.mjs +136 -292
- package/lib/servers.mjs +70 -38
- package/lib/{portable-shape.mjs → shape.mjs} +4 -3
- package/lib/tree-copy.mjs +44 -0
- package/lib/workspace.mjs +132 -20
- package/package-catalog.json +6 -6
- package/package.json +1 -1
- package/packages/record/lib/session-roots.mjs +8 -6
- package/skills/integration-authoring/SKILL.md +48 -40
- package/skills/oats-getting-started/SKILL.md +105 -110
- package/skills/oats-support/SKILL.md +2 -2
- package/skills/soul-craft/SKILL.md +13 -6
- package/bin/oats-pi-sdk-host.mjs +0 -17
- package/docs/2026-09-03-architecture-proposal.md +0 -642
- package/docs/artifact-approvals.schema.json +0 -7
- package/docs/captured-invocation-context.schema.json +0 -7
- package/docs/captured-resolution.schema.json +0 -7
- package/docs/design/package-engine-contract.md +0 -813
- package/docs/design/package-runtime-api.md +0 -588
- package/docs/desktop-succession.md +0 -57
- package/docs/execution-capsule.schema.json +0 -108
- package/docs/first-team-demo.md +0 -92
- package/docs/knowledge-migration.md +0 -147
- package/docs/migration-from-oas.md +0 -103
- package/docs/oats-config.schema.json +0 -172
- package/docs/oats-lock-v3.schema.json +0 -7
- package/docs/oats-lock.schema.json +0 -175
- package/docs/operating-team-migration.md +0 -470
- package/docs/portable.schema.json +0 -2512
- package/docs/provider-check-input.schema.json +0 -7
- package/docs/rebuild-to-v2.md +0 -511
- package/docs/workspace-adoption.md +0 -74
- package/injects/framework-workspace.md +0 -7
- package/injects/local-soul.md +0 -19
- package/injects/oats-portable.md +0 -20
- package/injects/oats.md +0 -11
- package/injects/portable-instance-boundary.md +0 -39
- package/injects/portable-work-directory.md +0 -29
- package/lib/artifact-approvals.mjs +0 -120
- package/lib/artifact-tree.mjs +0 -141
- package/lib/capability-artifacts.mjs +0 -179
- package/lib/capability-execution.mjs +0 -15
- package/lib/capability-inputs.mjs +0 -39
- package/lib/capability-provenance.mjs +0 -231
- package/lib/captured-action-shape.mjs +0 -21
- package/lib/captured-admission-shape.mjs +0 -20
- package/lib/captured-binding-file.mjs +0 -36
- package/lib/captured-dispatch.mjs +0 -66
- package/lib/captured-instance-index.mjs +0 -277
- package/lib/captured-invocation-context.mjs +0 -130
- package/lib/captured-launch-request.mjs +0 -66
- package/lib/captured-operation-process.mjs +0 -15
- package/lib/captured-pi-custody.mjs +0 -29
- package/lib/captured-pi-host.mjs +0 -167
- package/lib/captured-pi-outcome.mjs +0 -172
- package/lib/captured-resolutions.mjs +0 -275
- package/lib/captured-scaffold.mjs +0 -87
- package/lib/captured-selector.mjs +0 -28
- package/lib/captured-session-backend.mjs +0 -52
- package/lib/captured-source-receipt-file.mjs +0 -72
- package/lib/helper-injection-policy.mjs +0 -104
- package/lib/legacy-lock-codec.mjs +0 -106
- package/lib/manifest-settings.mjs +0 -84
- package/lib/package-closure.mjs +0 -48
- package/lib/package-materialization.mjs +0 -83
- package/lib/pi-sdk-host.mjs +0 -229
- package/lib/portable-artifacts.mjs +0 -115
- package/lib/portable-choices.mjs +0 -82
- package/lib/portable-composition.mjs +0 -136
- package/lib/portable-digest.mjs +0 -105
- package/lib/portable-identity.mjs +0 -40
- package/lib/portable-lock.mjs +0 -117
- package/lib/portable-onboarding-request.mjs +0 -49
- package/lib/portable-onboarding.mjs +0 -256
- package/lib/portable-package-preparation.mjs +0 -188
- package/lib/portable-policy.mjs +0 -44
- package/lib/portable-soul.mjs +0 -42
- package/lib/portable-state.mjs +0 -80
- package/lib/prepare-composition.mjs +0 -170
- package/lib/prepared-bindings.mjs +0 -92
- package/lib/prepared-resources.mjs +0 -127
- package/lib/provider-binding-broker.mjs +0 -65
- package/lib/provider-binding-wire.mjs +0 -116
- package/lib/readiness.mjs +0 -225
- package/lib/repository-observation.mjs +0 -226
- package/lib/resolution-shape.mjs +0 -393
- package/lib/schedule-capsule.mjs +0 -206
- package/lib/soul-constraints.mjs +0 -40
- package/lib/source-projection.mjs +0 -84
- package/lib/source-spec.mjs +0 -189
- package/lib/workspace-definition.mjs +0 -126
- package/lib/workspace-discovery.mjs +0 -146
- package/skills/oats/SKILL.md +0 -162
- package/skills/oats-config/SKILL.md +0 -164
- package/skills/oats-packages/SKILL.md +0 -184
- package/skills/oats-portable/SKILL.md +0 -115
- package/skills/oats-portable-artifacts/SKILL.md +0 -63
|
@@ -40,7 +40,6 @@ name: release-manager # must equal the directory name
|
|
|
40
40
|
description: Cuts, verifies and announces releases.
|
|
41
41
|
work: worktree # worktree | checkout | directory | workspace
|
|
42
42
|
team: engineering # optional label; else the repo's default (oats-membership.yaml); else unassigned
|
|
43
|
-
private: true # optional: not discoverable; spawnable only from this repo
|
|
44
43
|
|
|
45
44
|
capabilities: # WHERE each capability comes from — a location, never a version
|
|
46
45
|
acme-release-tooling: { from: here } # here = this soul's own repo
|
|
@@ -62,16 +61,19 @@ compatibility: # optional floors on PACKAGE versions
|
|
|
62
61
|
| Key | Meaning |
|
|
63
62
|
|---|---|
|
|
64
63
|
| `name`, `description`, `work` | Required. `work` is the work mode below. |
|
|
65
|
-
| `team` | A label declared in the workspace's `teams:`; may add `defaults.byTeam` capabilities. Never gates or restricts. |
|
|
66
|
-
| `private` |
|
|
64
|
+
| `team` | A label, or a list of labels (the first the primary), declared in the workspace's `teams:`; each may add `defaults.byTeam` capabilities and is an eligible messaging team. Never gates or restricts. |
|
|
65
|
+
| `private` | **Ignored since 0.26.0:** souls have no private mode. Every soul of a confirmed member is listed and spawnable; a soul that still carries the field gets a `soul-private-ignored` warning. Remove it. |
|
|
67
66
|
| `capabilities` | `<cap>: { from: here \| <repo key> \| package }` or `<cap>: off`. Composed over `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[team]`; the soul wins. |
|
|
68
67
|
| `knowledge` / `messaging` / `tasks` | The slot's provider payload (true of every instance of the soul), or `none`. Merged with `oats-local.yaml` `settings.<cap>` and `spawn --provider <cap>`; the provider's `binding` contract validates the result — and refuses keys it does not declare. For `oats.okf` 2.1.3 the admitted keys are its settings (`bindings-file`, `state-dir`, `harvest-runtime`, `harvest-model`); the soul's `owns`/`reads` live in `souls/<name>/okf.json`, which OKF reads from the soul directory. |
|
|
69
68
|
| `compatibility` | `<cap>: <semver range>` checked against the locked package version (`E_COMPATIBILITY`). |
|
|
70
69
|
|
|
71
70
|
Schema: [`soul.schema.json`](soul.schema.json). Not in v2: `kind`, `type`,
|
|
72
|
-
`repo`, `
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
`repo`, `harness`, `model`, `backend`, `yolo`, `launch-config`, `children`,
|
|
72
|
+
`requires`, `source:`, `stores.inherit`. Runtime, model, backend, yolo and the
|
|
73
|
+
launch configuration are spawn-time host choices (`--harness`, `--model`,
|
|
74
|
+
`--backend`, `--yolo`, `--launch-config`, or a launch configuration in
|
|
75
|
+
`oats-local.yaml`), not soul identity: a soul is model-agnostic as an artifact.
|
|
76
|
+
A child-spawn policy is a spawn flag too (`--no-child-spawns`).
|
|
75
77
|
|
|
76
78
|
A soul never runs by itself. It is incarnated as an instance. Editing a soul
|
|
77
79
|
is a code change, reviewed in its repo.
|
|
@@ -84,9 +86,10 @@ souls — is ordinary capability content: **`oats.core`** (package
|
|
|
84
86
|
`defaults.capabilities: { oats.core: { from: package } }`; a soul may say
|
|
85
87
|
`oats.core: off`. **`oats.setup`** (same package) carries the whole-architecture
|
|
86
88
|
knowledge an onboarding expert needs. Neither is kernel magic; the kernel still
|
|
87
|
-
composes its own instance-boundary and work-mode briefings
|
|
88
|
-
`oats.core`
|
|
89
|
-
|
|
89
|
+
composes its own instance-boundary and work-mode briefings, and the "You run
|
|
90
|
+
on OATS" briefing is `oats.core`'s inject (the kernel ships no copy since 0.26:
|
|
91
|
+
a soul without `oats.core` gets no OATS operating instructions, and
|
|
92
|
+
`oats doctor --soul` says so).
|
|
90
93
|
|
|
91
94
|
## Instance anatomy
|
|
92
95
|
|
|
@@ -102,7 +105,6 @@ full copy** of every capability the soul resolved to:
|
|
|
102
105
|
|
|
103
106
|
```text
|
|
104
107
|
<agents-root>/<soul>/instances/<instance>/
|
|
105
|
-
soul → ../../soul # the soul, for reference (read-only)
|
|
106
108
|
AGENTS.md # generated: soul AGENTS.md + kernel/work-mode blocks + each module's inject
|
|
107
109
|
CLAUDE.md → AGENTS.md
|
|
108
110
|
.agents/skills/ # canonical skill tree — soul skills + <capability>/<skill>/ full copies
|
|
@@ -111,7 +113,7 @@ full copy** of every capability the soul resolved to:
|
|
|
111
113
|
.oats/modules/<capability>/ # the whole capability: oats.json, bin/, injects/, skills/ (hooks run from here)
|
|
112
114
|
work/ # worktree, checkout symlink, attached tree, or private directory
|
|
113
115
|
TASK.md # briefing and task
|
|
114
|
-
instance.json # provenance (below)
|
|
116
|
+
instance.json # provenance (below); `soulDir` = the soul directory hooks get as OATS_SOUL
|
|
115
117
|
STATE.md, log.md, notes/ # optional, from the knowledge capability
|
|
116
118
|
```
|
|
117
119
|
|
|
@@ -187,6 +189,11 @@ oats spawn release-manager --preview --json # decide ever
|
|
|
187
189
|
oats spawn release-manager --provider oats.aweb identity.source=/abs/path/to/retained/.aw # instance-level payload
|
|
188
190
|
```
|
|
189
191
|
|
|
192
|
+
An instance is named `<soul>-<purpose>` by default, or exactly `--name <slug>`.
|
|
193
|
+
Names are unique per deployment (a workspace-model deployment has one agents
|
|
194
|
+
root): a derived name in use gets `-2`, `-3`…; an explicit `--name` in use is
|
|
195
|
+
refused (`E_INSTANCE_NAME_TAKEN`).
|
|
196
|
+
|
|
190
197
|
From a deployment (where `oats-local.yaml` is), a spawn: reads the local file →
|
|
191
198
|
discovers the workspace over its remotes and confirms membership → finds the
|
|
192
199
|
soul among the confirmed members (or `external:`; an ambiguous bare name is
|
|
@@ -194,7 +201,7 @@ soul among the confirmed members (or `external:`; an ambiguous bare name is
|
|
|
194
201
|
`<agents-root>/<soul>/souls/<commit12>/` at its commit (the home links that
|
|
195
202
|
directory; `<agents-root>/<soul>/soul` points at the current one) → resolves
|
|
196
203
|
every capability by
|
|
197
|
-
`from:` (member = latest, package = locked
|
|
204
|
+
`from:` (member = latest, package = locked) → creates the home →
|
|
198
205
|
**materializes each module whole** into `.oats/modules/` and copies its skills
|
|
199
206
|
into `.agents/skills/` (a transaction: any failure leaves nothing behind) →
|
|
200
207
|
composes `AGENTS.md` → records `modules`/`providers`/`workspace` in
|
|
@@ -282,9 +289,9 @@ an agent's environment variables — is operator-origin and appears top-level.
|
|
|
282
289
|
Agents spawning sub-agents should pass `--parent "$OATS_INSTANCE"` (or the
|
|
283
290
|
relation that fits).
|
|
284
291
|
|
|
285
|
-
If the workspace has a messaging
|
|
292
|
+
If the workspace has a messaging capability such as aweb, spawned instances
|
|
286
293
|
can also receive identities and coordinate with each other automatically. The
|
|
287
|
-
|
|
294
|
+
tasks capability can provide shared work state while messaging provides conversation.
|
|
288
295
|
|
|
289
296
|
### Retire
|
|
290
297
|
|
|
@@ -296,13 +303,13 @@ evidence but never waits for a model or GitHub: independent processing and
|
|
|
296
303
|
source-targeted inspection continue after the home disappears.
|
|
297
304
|
|
|
298
305
|
`oats retire <instance> --self` lets an instance retire itself when the human
|
|
299
|
-
or briefing says it is done. A live
|
|
306
|
+
or briefing says it is done. A live harness cannot give a stable final
|
|
300
307
|
inspection of its own work, so the calling process inspects, runs, and removes
|
|
301
308
|
nothing: it records the intent beside its home as
|
|
302
309
|
`.oats-retire-pending-<instance>.json` and starts a detached completion, then returns so the instance can report final
|
|
303
310
|
status before its tmux window dies a few seconds later. The completion then
|
|
304
311
|
retires the instance exactly as an external `oats retire` would: quiesce the
|
|
305
|
-
|
|
312
|
+
harness, preserve uncommitted work, run retire hooks, repair lineage, remove
|
|
306
313
|
the worktree and the home. Success leaves nothing behind: the home and the
|
|
307
314
|
marker are gone. A failure writes `.oats-retired-<instance>.json` beside the
|
|
308
315
|
retained home (plus the usual quarantine marker when hooks reported incomplete
|
|
@@ -316,7 +323,7 @@ follow. Every mode sits inside the same home/work boundary, which the generated
|
|
|
316
323
|
instructions state first (`injects/instance-boundary.md`):
|
|
317
324
|
|
|
318
325
|
- `<instance-home>` — the gitignored instance directory, `$OATS_INSTANCE_HOME` —
|
|
319
|
-
holds the brain (`AGENTS.md
|
|
326
|
+
holds the brain (the composed `AGENTS.md`; there is no soul link), the task, the provenance
|
|
320
327
|
(`instance.json`) and the episodic state (`STATE.md`, `log.md`, `notes/`), and
|
|
321
328
|
is where OATS operational/lifecycle commands — and the commands of whatever
|
|
322
329
|
capabilities are active, `aw` among them when aweb messaging is — are run,
|
|
@@ -325,9 +332,11 @@ instructions state first (`injects/instance-boundary.md`):
|
|
|
325
332
|
- `<instance-home>/work` — the repository or workspace view — is where
|
|
326
333
|
repository reading, editing, building, testing, git and commits happen, to the
|
|
327
334
|
extent the mode below permits.
|
|
328
|
-
- The home
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
- The home has no soul link: the composed `AGENTS.md` already carries the
|
|
336
|
+
soul's instructions, and `instance.json` `soulDir` records the (read-only,
|
|
337
|
+
per-commit) soul directory every hook and dispatched command receives as
|
|
338
|
+
`OATS_SOUL`. Durable soul edits go through tracked paths under `work/` under
|
|
339
|
+
the applicable review rules. OKF v2 harvest edits external
|
|
331
340
|
owned knowledge, not canonical soul files or skills.
|
|
332
341
|
|
|
333
342
|
Agents move between the two as the task needs; the boundary is what each
|
|
@@ -390,7 +399,7 @@ directory; without it, the deployment directory (where `oats-local.yaml` is) is
|
|
|
390
399
|
used. No implicit fallback changes the other modes.
|
|
391
400
|
|
|
392
401
|
`--work-dir` and `--branch` are rejected. Canonical instructions, skill
|
|
393
|
-
composition, provider trust and
|
|
402
|
+
composition, provider trust and harness preflight still apply. No worktree setup
|
|
394
403
|
runs. Retirement preserves nonempty work in verified recovery storage beside the
|
|
395
404
|
home (`workRecovery.path/work`) before deleting it, including files created by
|
|
396
405
|
hooks; directory work has no disposable-root exemptions. The work-root cannot be
|
|
@@ -460,7 +469,7 @@ they are stated separately:
|
|
|
460
469
|
compatibility aliases for the separately published pi extension.
|
|
461
470
|
- **Lifecycle hooks**: `OATS_INSTANCE_HOME` and `OATS_HOME`, alongside the rest of
|
|
462
471
|
the hook contract. `OATS_HOME` predates `OATS_INSTANCE_HOME` and is kept because
|
|
463
|
-
shipped capability hooks read it; it is **not** exported to
|
|
472
|
+
shipped capability hooks read it; it is **not** exported to harness sessions.
|
|
464
473
|
|
|
465
474
|
Neither is `OATS_HOME_DIR`, which is the package store root — do not conflate
|
|
466
475
|
them.
|
|
@@ -472,8 +481,7 @@ with **`E_NO_CANONICAL_ROOT`** and creates nothing.
|
|
|
472
481
|
|
|
473
482
|
### Deployment prerequisite: the agents directory must be operator-owned
|
|
474
483
|
|
|
475
|
-
The canonical deployment (the agents root
|
|
476
|
-
homes under them) **must be owned by the operator and not writable by untrusted
|
|
484
|
+
The canonical deployment (the agents root and the instance homes under it) **must be owned by the operator and not writable by untrusted
|
|
477
485
|
users or processes.** OATS validates resolved destinations and re-checks the home
|
|
478
486
|
immediately before creating anything in it, but it cannot defeat a concurrent
|
|
479
487
|
local attacker who already has write access there: Node offers no
|
|
@@ -486,33 +494,38 @@ something the kernel can close from inside.
|
|
|
486
494
|
Default layout:
|
|
487
495
|
|
|
488
496
|
```text
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
497
|
+
<deployment>/
|
|
498
|
+
oats-local.yaml
|
|
499
|
+
agents/
|
|
500
|
+
docs-expert/ # a workspace soul, defined in a member repository's
|
|
501
|
+
souls/<commit12>/ # souls/docs-expert/ and copied here per commit
|
|
493
502
|
instances/
|
|
494
|
-
|
|
495
|
-
scratch-agent/
|
|
496
|
-
soul/
|
|
503
|
+
memory-harvest/ # a capability-defined agent: only instances/, no soul
|
|
497
504
|
instances/
|
|
498
505
|
```
|
|
499
506
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
507
|
+
A capability-defined agent (declared by a package or member module, such as
|
|
508
|
+
the OKF harvester) homes under the agents root exactly like a soul; its
|
|
509
|
+
directory holds only `instances/`. A name that is both a workspace soul and a
|
|
510
|
+
capability agent is ambiguous (`E_SOUL_AMBIGUOUS`).
|
|
511
|
+
|
|
512
|
+
There are no local souls. A soul is a member repository's `souls/<name>`
|
|
513
|
+
(`soul.yaml` + `AGENTS.md`); author it there and run `oats sync`. OATS 0.25
|
|
514
|
+
and earlier kept local souls and capability-agent homes under
|
|
515
|
+
`<scope>/local-agents/`: this kernel never reads, spawns into or retires from
|
|
516
|
+
that directory; it only detects it. Onboarding refuses into a directory that
|
|
517
|
+
holds one, and `oats status` and `oats doctor` report it once, as the
|
|
518
|
+
`legacy-local-agents` problem naming the instances found there; retire them
|
|
519
|
+
with the 0.25 kernel, or delete the directory once they are stopped.
|
|
520
|
+
|
|
521
|
+
A *captured home* (spawned through 0.24–0.25's captured path, removed in 0.26:
|
|
522
|
+
its `instance.json` records `executionBinding`, `incarnationId` or `captured`) is
|
|
523
|
+
reported by `oats status` and `oats doctor` as the `legacy-captured-home`
|
|
524
|
+
problem. It has no 0.26 runtime: start, restart, inspect/readiness/operation
|
|
525
|
+
`--home` and its in-home commands refuse it (`E_UNSUPPORTED_MODE`). `oats retire`
|
|
526
|
+
still works; it warns once per capability whose retire hook did NOT run, since
|
|
527
|
+
identities and memberships those capabilities created are not revoked — remove
|
|
528
|
+
them with the provider's own tooling. Re-spawn the soul from the deployment.
|
|
516
529
|
|
|
517
530
|
Alternative agents-root layouts are planned but not built. Today the default
|
|
518
531
|
layout is the only implemented layout.
|
package/docs/workspaces.md
CHANGED
|
@@ -8,7 +8,6 @@ contracts the kernel is built against are in
|
|
|
8
8
|
[design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
|
|
9
9
|
a full worked example (an imaginary company with three teams) is in
|
|
10
10
|
[design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
|
|
11
|
-
Moving an existing 0.24.x deployment: [rebuild-to-v2.md](rebuild-to-v2.md).
|
|
12
11
|
|
|
13
12
|
## The rule
|
|
14
13
|
|
|
@@ -19,7 +18,7 @@ versioned.**
|
|
|
19
18
|
| Source kind | `from:` | Versioned | Trust |
|
|
20
19
|
|---|---|---|---|
|
|
21
20
|
| Member repo | `<repo key>` or `here` | no — always the member's **latest** default-branch state | membership (the reciprocal handshake) |
|
|
22
|
-
| Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity |
|
|
21
|
+
| Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity | the declaration in `packages:` (no separate approval) |
|
|
23
22
|
|
|
24
23
|
A soul names each capability **with where it comes from — a location, never a
|
|
25
24
|
version**. The workspace's `packages:` says which version; materialization
|
|
@@ -39,8 +38,8 @@ machine (`oats-local.yaml`); the lock (`oats-lock.json`) sits beside
|
|
|
39
38
|
`oats-local.yaml` and is identical on every machine that synced the same
|
|
40
39
|
workspace commit. Schemas: [`oats-workspace.schema.json`](oats-workspace.schema.json),
|
|
41
40
|
[`oats-membership.schema.json`](oats-membership.schema.json),
|
|
42
|
-
[`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json)
|
|
43
|
-
[
|
|
41
|
+
[`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json);
|
|
42
|
+
the lock's format is in [packages](packages.md#lock-v3). The JSON schemas encode
|
|
44
43
|
shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
|
|
45
44
|
the two `packages:` value forms) live in the kernel's `validateWorkspace` /
|
|
46
45
|
`validateSoul`, which are the authority.
|
|
@@ -61,7 +60,7 @@ members: # repo refs, NO @revision (E_WORKSPAC
|
|
|
61
60
|
|
|
62
61
|
packages: # the ONLY versioned things
|
|
63
62
|
oats.framework: v1.1.3 # bare version → resolves through the official catalog
|
|
64
|
-
oats.okf: v2.1.
|
|
63
|
+
oats.okf: v2.1.5
|
|
65
64
|
acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
|
|
66
65
|
|
|
67
66
|
teams: # labels, declared once so they cannot drift
|
|
@@ -141,8 +140,9 @@ declaration; it travels with the soul into the per-commit soul cache. The
|
|
|
141
140
|
carry only the binding's settings keys (`bindings-file`, `state-dir`,
|
|
142
141
|
`harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` there are refused by
|
|
143
142
|
the provider, not read (a soul payload grammar is an OKF follow-up). Every
|
|
144
|
-
`souls/*/soul.yaml` in a member is
|
|
145
|
-
|
|
143
|
+
`souls/*/soul.yaml` in a member is listed and spawnable — souls have no
|
|
144
|
+
private mode (`private:` in a soul.yaml is ignored since 0.26.0, with a
|
|
145
|
+
`soul-private-ignored` warning). A soul's
|
|
146
146
|
`name` must equal its directory name; the first of two souls declaring one
|
|
147
147
|
name (by path) is listed, the second is a problem.
|
|
148
148
|
|
|
@@ -151,7 +151,8 @@ name (by path) is listed, the second is a problem.
|
|
|
151
151
|
The capability manifest is the one file that did not change (see
|
|
152
152
|
[capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
|
|
153
153
|
`^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
|
|
154
|
-
`layer`, and may read `private: true`
|
|
154
|
+
`layer`, and may read `private: true` (a **repo-owned** capability) and
|
|
155
|
+
`team: <label>`. `version` is
|
|
155
156
|
informational for member capabilities — a materialized copy is identified by
|
|
156
157
|
its content digest.
|
|
157
158
|
|
|
@@ -188,7 +189,14 @@ with the right name, is not admission.
|
|
|
188
189
|
model as a repo's committed `.agents/skills/`: whoever can push to the repo
|
|
189
190
|
decides what runs, and the branch's latest state is what runs. No per-operator
|
|
190
191
|
trust lists, no per-capability approval for members. Packages come from
|
|
191
|
-
*outside* that boundary and
|
|
192
|
+
*outside* that boundary, and **declaring one in the workspace's `packages:` is
|
|
193
|
+
the trust decision** (human decision, 2026-09-24): people install a package only
|
|
194
|
+
when they trust it, so there is no second, per-version approval step. The lock
|
|
195
|
+
is reproducibility, not approval — it pins the exact commit and content
|
|
196
|
+
integrity, and `oats sync` refuses drift (a moved tag, changed content, an
|
|
197
|
+
edited capability list). A spawn admits only a locked package the workspace
|
|
198
|
+
**still declares**: one removed from `packages:` but left in a stale lock is
|
|
199
|
+
`E_PACKAGE_MISSING { reason: "undeclared" }` until `oats sync` drops it.
|
|
192
200
|
|
|
193
201
|
**The handshake is observed with the operator's own Git read access, in one
|
|
194
202
|
access context.** The kernel reads both halves over the remotes
|
|
@@ -210,10 +218,11 @@ its capabilities unresolvable (`E_NOT_A_MEMBER` / `E_MEMBERSHIP_UNCONFIRMED`).
|
|
|
210
218
|
Reading the workspace repo *is* being in the workspace — a workspace's access
|
|
211
219
|
control is Git's.
|
|
212
220
|
|
|
213
|
-
**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
**Repo-owned capabilities.** `private: true` in a capability's `oats.json`
|
|
222
|
+
makes it **repo-owned**: it is listed like any other capability (with
|
|
223
|
+
`private: true`; the human table marks it "(repo-owned)"), and it is usable
|
|
224
|
+
only by souls of the same repo (`E_CAPABILITY_PRIVATE` otherwise). Souls have
|
|
225
|
+
no private mode: every soul of a confirmed member is listed and spawnable.
|
|
217
226
|
|
|
218
227
|
**External souls.** `external:` adopts a soul by reference from a repo that is
|
|
219
228
|
**not** a member, pinned to a full commit. No handshake is asked for and none is
|
|
@@ -226,7 +235,7 @@ its slots. An `external[].team` overrides the soul's own `team`.
|
|
|
226
235
|
A repository may be a **member** (it completed the handshake; its `souls/*` and
|
|
227
236
|
`capabilities/*` are member-tier: latest state, trusted by membership) **and** a
|
|
228
237
|
**package publisher** (its `oats-package/` is consumed only through
|
|
229
|
-
`packages:`: versioned
|
|
238
|
+
`packages:`: versioned and locked). The two never collapse:
|
|
230
239
|
|
|
231
240
|
- `from: <repo key>` looks **only** under `<repo>/capabilities/<name>/oats.json`
|
|
232
241
|
at the member's latest state. It never looks inside `oats-package/`. A name
|
|
@@ -244,31 +253,32 @@ So the framework's own souls say `oats.okf: { from: package }` even though
|
|
|
244
253
|
member soul that is the expert in that capability (`okf-expert`, `aweb-expert`,
|
|
245
254
|
…), discoverable at latest state like any member soul.
|
|
246
255
|
|
|
247
|
-
## Packages, lock,
|
|
256
|
+
## Packages, lock, catalog
|
|
248
257
|
|
|
249
258
|
`packages:` values have exactly two forms:
|
|
250
259
|
|
|
251
260
|
- a **bare version** (`v2.1.3`, `2.1.3`, `v1.0.0-rc.1`) — resolved through the
|
|
252
261
|
official catalog (`package-catalog.json` in the `oats` repo; the reviewed
|
|
253
|
-
|
|
262
|
+
list, see [official-catalog.md](official-catalog.md)). This is
|
|
254
263
|
the only way a package becomes *pinnable by id*.
|
|
255
264
|
- **`git:<repo>@<ref>`** — a direct package ref: `<repo>` is any ref the kernel
|
|
256
265
|
understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
|
|
257
266
|
`file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
|
|
258
267
|
`oats-package/` inside that repo.
|
|
259
268
|
|
|
260
|
-
Both are packages: versioned
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
Both are packages: versioned and locked. A ref that resolves to a **branch** is
|
|
270
|
+
refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable. A
|
|
271
|
+
tag that moved (same version string, different commit), or content that no
|
|
272
|
+
longer matches the locked integrity, fails with `E_PACKAGE_INTEGRITY` on the
|
|
273
|
+
next `oats sync`.
|
|
274
|
+
|
|
275
|
+
**There is no package approval** (human decision, 2026-09-24). Declaring a
|
|
276
|
+
package in `packages:` is the trust decision; `oats sync` asks nothing and
|
|
277
|
+
`--approve` is `E_BAD_ARGS`. `oats sync` confirms membership, resolves every
|
|
278
|
+
`packages:` entry to a commit + content digest, writes `oats-lock.json`
|
|
279
|
+
(lockfileVersion 3), creates `agents/` if absent, reports what changed and
|
|
280
|
+
exits `0`. A lock written by an earlier kernel may still carry an `approved`
|
|
281
|
+
record per entry: it is ignored, and the next write drops it. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
|
|
272
282
|
`packages:` in the workspace file when it is tracked by the current checkout,
|
|
273
283
|
else print the line to add — the workspace file is shared through Git. Details:
|
|
274
284
|
[packages.md](packages.md).
|
|
@@ -282,8 +292,8 @@ the workspace's slot default):
|
|
|
282
292
|
|
|
283
293
|
```
|
|
284
294
|
from: package → some locked package provides `name` else E_PACKAGE_MISSING (run `oats sync`)
|
|
285
|
-
→
|
|
286
|
-
→
|
|
295
|
+
→ read its manifests at the locked commit; the lock's capability list must match else E_PACKAGE_INTEGRITY
|
|
296
|
+
→ copy; record package/version/commit/digest
|
|
287
297
|
from: <repo> → <repo> is a CONFIRMED member else E_NOT_A_MEMBER / E_MEMBERSHIP_UNCONFIRMED
|
|
288
298
|
(or `here`) → it has capabilities/<name>/oats.json else E_CAPABILITY_MISSING
|
|
289
299
|
→ not private, unless <repo> is the soul's own else E_CAPABILITY_PRIVATE
|
|
@@ -308,13 +318,12 @@ Nothing is symlinked, nothing is shared between instances.
|
|
|
308
318
|
```
|
|
309
319
|
<agents-root>/<soul>/instances/<instance>/
|
|
310
320
|
├── AGENTS.md # composed: soul AGENTS.md + kernel/work-mode blocks + each module's inject
|
|
311
|
-
│ # (
|
|
321
|
+
│ # (the "You run on OATS" block is oats.core's inject; the kernel ships no copy)
|
|
312
322
|
├── CLAUDE.md → AGENTS.md
|
|
313
323
|
├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
|
|
314
324
|
├── .claude/skills → ../.agents/skills
|
|
315
325
|
├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
|
|
316
326
|
├── instance.json # modules{}, providers{}, workspace{} recorded here
|
|
317
|
-
├── soul → <agents-root>/<soul>/soul # read-only reference
|
|
318
327
|
├── TASK.md
|
|
319
328
|
└── work/
|
|
320
329
|
```
|
|
@@ -347,10 +356,16 @@ not exclude anything.
|
|
|
347
356
|
## Teams
|
|
348
357
|
|
|
349
358
|
`teams:` declares labels once (`global`, `engineering`, …) so they cannot drift
|
|
350
|
-
into typos. A soul
|
|
351
|
-
|
|
352
|
-
`
|
|
353
|
-
|
|
359
|
+
into typos. A soul carries `team:` — one label or a list (`team: [engineering,
|
|
360
|
+
reviewers]`, the first the primary) — else its repo's default from
|
|
361
|
+
`oats-membership.yaml` (same shape), else `unassigned`; a capability carries one
|
|
362
|
+
label. A label not declared in `teams:` is `E_TEAM_UNKNOWN` (the item is still
|
|
363
|
+
listed); a declared label without a `messaging.byTeam` entry is the
|
|
364
|
+
`unmapped-team-label` warning. `defaults.byTeam.<team>.capabilities` adds
|
|
365
|
+
capabilities additively for souls with that label, for each label in order
|
|
366
|
+
(`off` removes; two labels that disagree are `E_TEAM_CONFLICT`). Each label is
|
|
367
|
+
an *eligible* messaging team the provider may join on request — see
|
|
368
|
+
[capabilities.md](capabilities.md#several-team-labels). **A
|
|
354
369
|
label never gates, restricts, changes trust or partitions the knowledge
|
|
355
370
|
store** — it organises and can supply defaults. The messaging provider's payload
|
|
356
371
|
(private teams, channels) lives under `messaging:`, so "team" means one thing.
|
|
@@ -364,9 +379,13 @@ store** — it organises and can supply defaults. The messaging provider's paylo
|
|
|
364
379
|
| A fact about **this spawn** | `oats spawn … --provider <cap> key=value` (repeatable; dotted keys nest) → `instance.json.providers.<cap>` | `--provider oats.aweb identity.source=/abs/path/to/retained/.aw` |
|
|
365
380
|
|
|
366
381
|
The merged payload is `workspace.messaging` (messaging slot only; its base
|
|
367
|
-
keys
|
|
368
|
-
|
|
369
|
-
|
|
382
|
+
keys, with `byTeam` stripped) ⊕ soul slot payload ⊕ `local.settings[cap]` ⊕
|
|
383
|
+
`spawn.providers[cap]` — objects deep-merge, later wins on scalars and arrays.
|
|
384
|
+
**No `byTeam[<label>]` is merged into it, the primary's included** (teams
|
|
385
|
+
amendment K): each label's `base ⊕ byTeam[label]` reaches the provider only as
|
|
386
|
+
that label's entry in `OATS_TEAMS` (the preview's `teams`). So `settings.team`
|
|
387
|
+
(and `OATS_TEAM_ID`) is the personal team if the host, the soul or the spawn
|
|
388
|
+
set one; empty means the provider's own default. The provider's own `binding` contract
|
|
370
389
|
(`normalize → bind → check`) runs over the merged payload exactly as before.
|
|
371
390
|
Two teams, two messaging identities, one workspace:
|
|
372
391
|
|
|
@@ -378,18 +397,21 @@ messaging:
|
|
|
378
397
|
cloud: { team: aweb:example.cloud }
|
|
379
398
|
```
|
|
380
399
|
|
|
381
|
-
A soul with `team: cloud` hands its messaging provider
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
the
|
|
391
|
-
|
|
392
|
-
|
|
400
|
+
A soul with `team: cloud` hands its messaging provider the eligible team
|
|
401
|
+
`{ label: cloud, team: aweb:example.cloud, mapped: true, payload: { team: aweb:example.cloud, … } }`
|
|
402
|
+
in `OATS_TEAMS`; its settings carry no `team` unless the host, soul or spawn set
|
|
403
|
+
one. A label under `byTeam` that is not declared in `teams:` is
|
|
404
|
+
`E_WORKSPACE_SCHEMA`. **Joining an eligible team is the provider's explicit
|
|
405
|
+
act.** `spawn --preview` shows the merged `settings.<cap>` and the `teams`, so
|
|
406
|
+
the delivery is verifiable, and `instance.json` records both. With oats.aweb
|
|
407
|
+
1.13.1 (which reads `team` from its settings and ignores `OATS_TEAMS`) the
|
|
408
|
+
primary identity therefore mints into the personal team: the `.aw` root's
|
|
409
|
+
active team, or the one the host set. What the payload does not change is
|
|
410
|
+
**where the `.aw` root is found**: the hook still searches
|
|
411
|
+
bounded candidates, first hit wins — the instance home, the Git repository
|
|
412
|
+
containing it, the soul's work repository and the Git repository containing
|
|
413
|
+
it, then the deployment directory (`OATS_WORKSPACE`); never the user home or
|
|
414
|
+
above the deployment — and that root must hold a membership of the named team (the deployment's `.aw`
|
|
393
415
|
joined to every team its labels name is the simple layout). On oats.aweb
|
|
394
416
|
1.11.2 `team` was ignored (the root's active team won), so `byTeam` there is
|
|
395
417
|
a recorded intent only.
|
|
@@ -428,7 +450,7 @@ The deployment directory is **yours to choose** (decision 9) — an existing fol
|
|
|
428
450
|
```
|
|
429
451
|
~/acme/ ← the directory you chose
|
|
430
452
|
├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
|
|
431
|
-
├── oats-lock.json ← exact commit + integrity
|
|
453
|
+
├── oats-lock.json ← exact commit + integrity per package
|
|
432
454
|
├── agents/ ← instance homes (each self-contained) + fetched soul sources
|
|
433
455
|
├── platform/ ← clone of github.com/acme/platform (only if someone works IN it; may live elsewhere — see clones:)
|
|
434
456
|
└── tools/
|
|
@@ -454,8 +476,8 @@ the host repo readable (it holds declarations, no secrets) or grant access.
|
|
|
454
476
|
|
|
455
477
|
Two things keep the standalone spawn useful rather than hollow: `oats.core`
|
|
456
478
|
(the framework's own operational package) is the kernel's default here as
|
|
457
|
-
well, resolved from the official catalog through the operator's own lock
|
|
458
|
-
|
|
479
|
+
well, resolved from the official catalog through the operator's own lock like any
|
|
480
|
+
package (a soul may say `oats.core: off`); and the
|
|
459
481
|
operator's `oats-local.yaml` may name the repo directly (`workspace: <member
|
|
460
482
|
ref>` — the kernel notices it is a member whose workspace it cannot read and
|
|
461
483
|
falls back to the standalone view — or `standalone: <repo ref>` to ask for
|
|
@@ -467,8 +489,8 @@ an explicit `standalone:` header its next steps say so and name that one repo.
|
|
|
467
489
|
member capability's hooks and command scripts run on every operator's machine at
|
|
468
490
|
spawn, gated by nothing but the handshake. In a mixed public/private
|
|
469
491
|
organisation keep **souls only** in public members and let executable
|
|
470
|
-
capabilities come from packages (
|
|
471
|
-
private members.
|
|
492
|
+
capabilities come from packages (declared in `packages:`, pinned by the lock)
|
|
493
|
+
or from private members.
|
|
472
494
|
|
|
473
495
|
**Hosting the workspace file when some members are private.** Everyone who
|
|
474
496
|
can read the workspace file sees the member list. So: a public member never
|
|
@@ -502,12 +524,12 @@ installed-capability tier (`.agents/capabilities/installed/`) and
|
|
|
502
524
|
`E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
|
|
503
525
|
ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
|
|
504
526
|
There is no converter and no dual-schema reader: a 0.24.x kernel keeps
|
|
505
|
-
spawning 0.24.x deployments
|
|
527
|
+
spawning 0.24.x deployments.
|
|
506
528
|
|
|
507
529
|
## Related
|
|
508
530
|
|
|
509
531
|
- [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
|
|
510
|
-
[Configuration (`oats-local.yaml`)](configuration.md)
|
|
532
|
+
[Configuration (`oats-local.yaml`)](configuration.md)
|
|
511
533
|
- [Capability manifests](capabilities.md) · [Contracts](layers.md) ·
|
|
512
534
|
[Desktop CLI API — workspace model](desktop-cli-api.md#workspace-model-workspaceapi-2)
|
|
513
535
|
- [Design navigation](design/README.md)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
## Your two directories
|
|
2
2
|
|
|
3
3
|
**`<instance-home>` is where this session starts** — the specific gitignored OATS
|
|
4
|
-
instance directory you woke up in, given to your
|
|
4
|
+
instance directory you woke up in, given to your harness and to every lifecycle
|
|
5
5
|
hook as `$OATS_INSTANCE_HOME`. It is not your user home (`~`), not the repository
|
|
6
6
|
root, and not the work tree. Anything that says "your home" means this directory.
|
|
7
7
|
|
|
@@ -20,7 +20,7 @@ root, and not the work tree. Anything that says "your home" means this directory
|
|
|
20
20
|
- **Soul work is repository work.** If your TASK is to change soul content that
|
|
21
21
|
lives in this repository, that is ordinary code work — do it on tracked paths
|
|
22
22
|
under `work/`, reviewed like the rest. How your own learnings reach your soul
|
|
23
|
-
is your knowledge
|
|
23
|
+
is your knowledge capability's business, and its instructions below say so if you
|
|
24
24
|
have one.
|
|
25
25
|
|
|
26
26
|
**`<instance-home>/work` is your repository or workspace view** — whatever your
|
package/injects/work-attached.md
CHANGED
|
@@ -8,7 +8,7 @@ their branch and their uncommitted state. You are a guest in their workspace.
|
|
|
8
8
|
- Keep your changes and commits **small and clearly attributable** (your
|
|
9
9
|
instance name in commit messages where ambiguity is possible).
|
|
10
10
|
- Do not touch files the owner is mid-editing unless your task says so; when
|
|
11
|
-
in doubt, coordinate through your messaging
|
|
11
|
+
in doubt, coordinate through your messaging capability or your spawner.
|
|
12
12
|
- Retiring you never removes the shared tree — cleanup of the tree is the
|
|
13
13
|
owner's concern, not yours.
|
|
14
14
|
|
|
@@ -6,8 +6,8 @@ cross-repo coordinator: your product is routing, analysis, and coordination —
|
|
|
6
6
|
not code changes.
|
|
7
7
|
|
|
8
8
|
- **Read freely across all member repos; never edit or commit inside them.**
|
|
9
|
-
Repo changes are routed to that repo's own agents (see `oats status
|
|
10
|
-
your
|
|
9
|
+
Repo changes are routed to that repo's own agents (see `oats status` in the
|
|
10
|
+
deployment, your tasks capability, or messaging) or to the human.
|
|
11
11
|
- No git state operations in any member repo: no branch switching, no
|
|
12
12
|
commits, no worktrees, no resets.
|
|
13
13
|
- Your own working state lives in your instance home, not in any member repo,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Bounded descriptor-backed reads
|
|
1
|
+
/** Bounded descriptor-backed reads of kernel-owned files (manifests, instance records). */
|
|
2
2
|
import { constants, closeSync, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
3
3
|
import { oatsError } from "./errors.mjs";
|
|
4
4
|
|
|
@@ -8,19 +8,19 @@ export function readPortableBytes(path, { missingCode = "resource-not-found", in
|
|
|
8
8
|
catch (error) {
|
|
9
9
|
if (error.code !== "ENOENT") throw error;
|
|
10
10
|
if (allowMissing) return null;
|
|
11
|
-
throw oatsError(missingCode, "
|
|
11
|
+
throw oatsError(missingCode, "metadata file is absent");
|
|
12
12
|
}
|
|
13
|
-
if (!stat.isFile()) throw oatsError(invalidCode, "
|
|
14
|
-
if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "
|
|
13
|
+
if (!stat.isFile()) throw oatsError(invalidCode, "metadata file must be a regular file");
|
|
14
|
+
if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "metadata file byte limit exceeded");
|
|
15
15
|
const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
16
16
|
try {
|
|
17
17
|
const before = fstatSync(fd);
|
|
18
|
-
if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "
|
|
18
|
+
if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "metadata file changed during opening");
|
|
19
19
|
const buffer = Buffer.alloc(stat.size + 1);
|
|
20
20
|
let count = 0, size;
|
|
21
21
|
while (count < buffer.length && (size = readSync(fd, buffer, count, buffer.length - count, null)) > 0) count += size;
|
|
22
22
|
const after = fstatSync(fd);
|
|
23
|
-
if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "
|
|
23
|
+
if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "metadata file changed during reading");
|
|
24
24
|
return buffer.subarray(0, count);
|
|
25
25
|
} finally { closeSync(fd); }
|
|
26
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/** Bounded data codecs
|
|
2
|
-
* No source resolution, filesystem access,
|
|
1
|
+
/** Bounded data codecs: strict JSON decoding and canonical JSON (config documents,
|
|
2
|
+
* instance records, provider answers). No source resolution, filesystem access,
|
|
3
|
+
* getters, or caller serialization hooks. */
|
|
3
4
|
import { oatsError } from "./errors.mjs";
|
|
4
5
|
|
|
5
6
|
const DEFAULTS = Object.freeze({ maxBytes: 8 * 1024 * 1024, maxDepth: 64, maxEntries: 100_000 });
|
|
@@ -39,16 +40,6 @@ export function decodeUtf8(input, where = "input") {
|
|
|
39
40
|
}
|
|
40
41
|
export const compareUtf8 = (a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
41
42
|
|
|
42
|
-
/** Freeze a bounded JSON observation, never caller accessors or cyclic objects. */
|
|
43
|
-
export function freezeJson(value) {
|
|
44
|
-
canonicalJson(value);
|
|
45
|
-
const seen = new WeakSet();
|
|
46
|
-
const freeze = (item) => {
|
|
47
|
-
if (!item || typeof item !== "object" || seen.has(item)) return;
|
|
48
|
-
seen.add(item); for (const child of Object.values(item)) freeze(child); Object.freeze(item);
|
|
49
|
-
};
|
|
50
|
-
freeze(value); return value;
|
|
51
|
-
}
|
|
52
43
|
|
|
53
44
|
/** Canonical JSON includes exactly one final LF. Emit sorted keys directly:
|
|
54
45
|
* JSON.stringify on a rebuilt object would reorder integer-looking keys. */
|