@awebai/oats 0.39.0 → 0.39.2
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/docs/design/2026-10-01-instance-cloning.md +106 -0
- package/docs/desktop-cli-api.md +11 -0
- package/docs/integrations.md +8 -2
- package/docs/knowledge-theory.md +4 -2
- package/docs/official-catalog.md +3 -2
- package/docs/packages.md +2 -2
- package/docs/release-notes/v0.39.1.md +42 -0
- package/docs/release-notes/v0.39.2.md +64 -0
- package/docs/souls-and-instances.md +24 -0
- package/lib/instance-inspect.mjs +51 -5
- package/lib/instance-resolution.mjs +15 -2
- package/lib/remote.mjs +4 -2
- package/package-catalog.json +9 -3
- package/package.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Instance cloning: the `oats.cloning` package
|
|
2
|
+
|
|
3
|
+
Status: **DECIDED 2026-10-01** by the maintainer with the steward (the transcript handling revised
|
|
4
|
+
2026-10-02 by the human: "cloning just works"). Shipped as `oats.cloning` 1.0.0
|
|
5
|
+
([awebai/oats-cloning](https://github.com/awebai/oats-cloning)), compatibility `oats >=0.34.0`.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
[Knowledge theory](../knowledge-theory.md#reusing-working-understanding-context-handoffs-and-cloning)
|
|
9
|
+
accepted cloning in principle and left it open: harvesting preserves individual lessons, not the
|
|
10
|
+
combined working picture of a long-running instance. A clone should carry selected references,
|
|
11
|
+
verified observations and provisional reasoning marked as such, under its own identity, credentials
|
|
12
|
+
and ownership, and copying it promotes nothing to shared knowledge.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
1. **A capability, not a kernel change.** Everything cloning needs already exists in the kernel:
|
|
16
|
+
`oats status` and `instance.json` to find and describe the source, `oats capture`/`oats recall`
|
|
17
|
+
for its transcript, `oats spawn` with `--relation`/`--relative-to`, the launch flags and `--base`,
|
|
18
|
+
`--preview` then `--expect-decision`, `oats session upload`/`session start`, and
|
|
19
|
+
`oats retire --self`. No config key, manifest field, hook variable or lock format changes. The
|
|
20
|
+
capability has no lifecycle hooks, so it cannot break a composing soul's spawn.
|
|
21
|
+
2. **Three roles.** The *requester* (an instance with the capability, or the operator from the
|
|
22
|
+
deployment with `--soul oats.cloning/cloner`) runs
|
|
23
|
+
`oats cloning request <source> --goal … --relation independent|child|sibling|parent`. That spawns
|
|
24
|
+
a short-lived *cloner* (the package soul `oats.cloning/cloner`, `knowledge: none`), which reads the
|
|
25
|
+
source (`dossier`), writes a brief and a plan (`/plan-clone`), spawns the *clone* (`spawn`:
|
|
26
|
+
preview, apply, verify), reports and retires itself. The clone is an ordinary instance of the
|
|
27
|
+
source's soul; nothing about it is special to the kernel.
|
|
28
|
+
3. **The requester chooses only what is theirs:** the goal, the relation and its anchor (the source
|
|
29
|
+
by default, never the cloner, which retires), the name, whether the transcript is read, and the
|
|
30
|
+
work base. `independent` is the kernel's `unrelated` and takes no anchor. The cloner decides what
|
|
31
|
+
the clone carries, and asks the requester only when a choice is genuinely theirs.
|
|
32
|
+
4. **The clone carries a curated brief, not a copy.** A generated preamble (a provenance block and
|
|
33
|
+
fixed "you are a clone" text) and the cloner's body under fixed headings: the goal verbatim,
|
|
34
|
+
what is verified (each with its evidence), decisions and why, working understanding marked
|
|
35
|
+
provisional, open threads and who owns them, work state, where to look (transcript turn ids are
|
|
36
|
+
citations), and what was not carried. Inherited material is not new evidence: the clone notes it
|
|
37
|
+
only after re-verifying it. Its commitments, PRs, threads and children stay with the source
|
|
38
|
+
unless the brief says they were handed over.
|
|
39
|
+
5. **Read-only on the source, bounded.** `dossier` copies a filtered `instance.json` (never the
|
|
40
|
+
command, environment, hooks, `capabilityMeta` or any credential locator), `TASK.md`, `STATE.md`,
|
|
41
|
+
`log.md` and `notes/**/*.md` as regular files contained in the home, with size budgets. It never
|
|
42
|
+
reads identity material, `.oats*`, attachments or the regenerated `AGENTS.md`. Work state is read
|
|
43
|
+
with read-only git: branch, HEAD, ahead/behind, commit subjects, changed paths and a diffstat,
|
|
44
|
+
never diff content; a directory work tree is only listed.
|
|
45
|
+
6. **The transcript is included by default, through a temporary record.** `--transcript exclude`
|
|
46
|
+
opts out. The cloner captures the source into a private record in its own home
|
|
47
|
+
(`oats capture --root`, 0700), copies the host's capture ignore list into it (and captures nothing
|
|
48
|
+
if that list exists but cannot be read), reads it with `oats recall --root`, and deletes it. The
|
|
49
|
+
host's own record is never written, so a source the host chose not to capture stays uncaptured.
|
|
50
|
+
7. **Security requirements, each tested.**
|
|
51
|
+
- *Redaction, not refusal.* Before the clone can see anything, `spawn` redacts the whole assembled
|
|
52
|
+
brief, preamble included, with a fixed set of patterns: PEM private keys, provider tokens and
|
|
53
|
+
keys, JWTs, Bearer tokens, credentials in URLs, and uppercase assignments such as
|
|
54
|
+
`API_TOKEN=…` or `PASSWORD: …`. Each match becomes `[redacted:<pattern>]`; the receipt and the
|
|
55
|
+
answer list `{line, pattern}`, never the value. Where a pattern's boundary is ambiguous it
|
|
56
|
+
redacts more rather than less. Redaction is pattern-limited: a form no pattern recognises
|
|
57
|
+
survives it, so the `/plan-clone` exclusions stay the policy and redaction is the seatbelt.
|
|
58
|
+
- *The brief lives only in the clone's home, mode 0600.* `claude` and `codex` launches pass
|
|
59
|
+
`TASK.md` as an argument, visible to other local users. So the clone is spawned `--no-launch`
|
|
60
|
+
with only the preamble and a pointer in its task, the brief is attached with
|
|
61
|
+
`oats session upload` (0600), `TASK.md` is made 0600, and only then is the session started.
|
|
62
|
+
`spawn` verifies the modes, the attachment's sha256 and the provenance block byte for byte.
|
|
63
|
+
- *A fresh messaging identity.* `spawn` never passes identity settings, and after the start checks
|
|
64
|
+
that the clone's alias is its own instance name and its identity differs from the source's
|
|
65
|
+
(`E_CLONE_IDENTITY` otherwise; a resident identity tied to the source's is refused before the
|
|
66
|
+
spawn).
|
|
67
|
+
- *The plan cannot escalate.* `spawn` enforces that the plan matches the request and the source:
|
|
68
|
+
the soul, relation, anchor and name; the source's launch settings (yolo and child spawns are never
|
|
69
|
+
escalated); `oats.okf harvest=off` carried when the source had it, never turned on; a base only
|
|
70
|
+
for worktree souls.
|
|
71
|
+
8. **Cleanup survives retirement.** Retirement keeps a changed home in recovery storage, so the
|
|
72
|
+
cloner cannot rely on its home being deleted: once the apply has run, it empties `clone/` except
|
|
73
|
+
`receipt.json` (which holds no source text), and a later run first removes an earlier run's
|
|
74
|
+
leftovers. A refusal before the apply deletes the temporary record and keeps the brief and plan
|
|
75
|
+
for a retry. Known limit: a cloner killed mid-run and then retired from outside leaves its
|
|
76
|
+
`clone/` (0700) in recovery storage.
|
|
77
|
+
9. **Packaging.** Its own repository and package, `oats.cloning`, mirrored and pinned in this
|
|
78
|
+
repository like the other official packages, with a member expert soul (`oats-cloning-expert`)
|
|
79
|
+
beside the package soul. **Not a workspace default:** only the souls that request clones compose
|
|
80
|
+
it (`oats.cloning: { from: package }`), and it becomes a default later on evidence of use. The
|
|
81
|
+
workspace pins it as `git:github.com/awebai/oats-cloning@v1.0.0` until every deployment's CLI
|
|
82
|
+
carries the catalog entry, because a bare version is `E_PACKAGE_MISSING` on a CLI without it.
|
|
83
|
+
|
|
84
|
+
## Rejected
|
|
85
|
+
- **A raw transcript fork** (resuming or forking the source's harness session). It copies identity
|
|
86
|
+
statements, home paths, secrets and half-finished reasoning as if settled, is harness-specific,
|
|
87
|
+
and carries no notion of what is still true. The brief is a curated reading, not a replay.
|
|
88
|
+
- **Copying `notes/` into the clone.** Notes are the source's evidence; copied, they would be
|
|
89
|
+
harvested a second time as the clone's own.
|
|
90
|
+
- **The brief in `TASK.md`.** World-readable by default and passed on the command line.
|
|
91
|
+
- **A refuse-on-match secret scan.** It stops a clone over one stray token; redaction removes the
|
|
92
|
+
value and still reports where it was.
|
|
93
|
+
- **An operator consent step per clone** (dropped by the human), and an acknowledgement flag for
|
|
94
|
+
sources the host does not capture. Both are procedural gates: same-user agents can already read
|
|
95
|
+
the host record, and the temporary record makes neither necessary.
|
|
96
|
+
- **Shipping inside `oats.framework`** (deployable with a bare pin today). Rejected for a standalone
|
|
97
|
+
package identity, as the human asked. **A second package inside this repository** was rejected
|
|
98
|
+
too: a `git:` pin always reads `oats-package/`, so it would carry the costs of both options.
|
|
99
|
+
- **A workspace default** in v1: no evidence yet that every soul needs it.
|
|
100
|
+
|
|
101
|
+
## Out of scope (v1)
|
|
102
|
+
Cloning across machines or from a retired instance; cloning into a different soul (that is a
|
|
103
|
+
handoff); asking a live source to write its own handoff; carrying work-tree content, uncommitted
|
|
104
|
+
changes, attachments, schedules, joined teams or children; a Desktop "Clone" action; kernel
|
|
105
|
+
provenance fields such as `clonedFrom`; the knowledge harvester skipping inherited material in a
|
|
106
|
+
clone's transcript (the preamble marks it instead).
|
package/docs/desktop-cli-api.md
CHANGED
|
@@ -468,6 +468,17 @@ confirmed member (`evidence: {repoKey, workspace, commit}`); `fail` when not
|
|
|
468
468
|
(the remedy names `oats-membership.yaml`); `unknown` when the workspace could
|
|
469
469
|
not be read. External souls and standalone views are `not-applicable`,
|
|
470
470
|
`required: false` (a standalone item carries `evidence.standaloneReason`).
|
|
471
|
+
A package soul is trusted through the workspace's `packages:` pin, never
|
|
472
|
+
through membership ([the non-collapse rule](workspaces.md#member-tier-vs-package-tier-the-non-collapse-rule)):
|
|
473
|
+
its item is `package <package>/<soul>` (producer `workspace lock`,
|
|
474
|
+
`evidence: {repoKey, workspace, commit, version, integrity, from}` with
|
|
475
|
+
`from.kind: "package"`). It passes when the package is declared and locked
|
|
476
|
+
and ships the soul at the locked commit, and the soul's copy is intact: for
|
|
477
|
+
`--soul`, the copy a spawn would link matches the locked digest; for
|
|
478
|
+
`--home`, the home's copy matches the digest it recorded, and a lock still
|
|
479
|
+
at that commit records the same digest. The copy is digested on every read.
|
|
480
|
+
It fails otherwise, with `code: "E_PACKAGE_INTEGRITY"` on a digest mismatch
|
|
481
|
+
and the remedy naming `oats sync` or a fresh spawn.
|
|
471
482
|
|
|
472
483
|
**`providers`** (producer `provider binding check`): for each module whose
|
|
473
484
|
manifest declares `binding`, the kernel runs its `binding.check` and relays
|
package/docs/integrations.md
CHANGED
|
@@ -41,7 +41,7 @@ arrives from.
|
|
|
41
41
|
# oats-workspace.yaml: one default per slot, for every soul
|
|
42
42
|
packages:
|
|
43
43
|
oats.okf: v4.1.1
|
|
44
|
-
oats.aweb: v1.21.
|
|
44
|
+
oats.aweb: v1.21.1
|
|
45
45
|
oats.linear: v1.0.1
|
|
46
46
|
oats.jira: v1.0.1
|
|
47
47
|
defaults:
|
|
@@ -89,7 +89,13 @@ operations in full.
|
|
|
89
89
|
`knowledge-maintainer` package soul reviews the resulting PRs.
|
|
90
90
|
- **`oats.aweb`** mints a messaging identity for each instance at spawn and
|
|
91
91
|
removes it at retire, contributes the aweb messaging skills, and wires the
|
|
92
|
-
channel so sessions are woken by mail.
|
|
92
|
+
channel so sessions are woken by mail. Under `delivery: channel`, Claude Code
|
|
93
|
+
loads the channel plugin with `--dangerously-load-development-channels`,
|
|
94
|
+
since `aweb-channel` is not on its approved channel list, so every such
|
|
95
|
+
Claude start waits at Claude Code's
|
|
96
|
+
development-channels confirmation until someone answers it in the
|
|
97
|
+
instance's terminal; the start's warnings and readiness say so
|
|
98
|
+
(`channel-dev-confirmation`). `oats aweb roster` lists the team:
|
|
93
99
|
its membership certificates and workspaces, one entry per alias with its
|
|
94
100
|
sources, status and kind (global identities first), and says when either
|
|
95
101
|
source is incomplete. After `oats server connect`, `oats aweb connect
|
package/docs/knowledge-theory.md
CHANGED
|
@@ -230,13 +230,15 @@ Directory bases use their own recoverable publication mechanism. Receipts state
|
|
|
230
230
|
|
|
231
231
|
## Reusing working understanding: context handoffs and cloning
|
|
232
232
|
|
|
233
|
-
Harvesting preserves individual lessons, not the combined working picture that makes a long-running instance effective
|
|
233
|
+
Harvesting preserves individual lessons, not the combined working picture that makes a long-running instance effective. A handoff or clone carries selected references, verified observations and labeled provisional reasoning; that is not a knowledge store, and copying it promotes nothing.
|
|
234
|
+
|
|
235
|
+
Cloning exists as the official `oats.cloning` package ([souls and instances](souls-and-instances.md#cloning-an-instance)). A clone is a new instance of the source's soul with its own identity, credentials and ownership. What it carries is a curated brief written by a short-lived cloner for the new goal: what was re-verified, with its evidence; decisions and their reasons; working understanding marked provisional; open threads and who owns them; and where to look, with transcript turn ids as citations. The cloner leaves secrets out of the brief, a pattern-based redaction pass catches the common forms that slip through, and the brief is attached to the clone as a 0600 file, never placed in its task text. A clone does not carry the raw transcript, the source's `notes/`, the content of its work tree or its uncommitted changes (a worktree clone starts from the source's committed branch, the soul's default or a requested ref), or its commitments: those stay with the source unless the brief says they were handed over. Inherited material is not new evidence; the clone records it in its own notes only after re-verifying it.
|
|
234
236
|
|
|
235
237
|
## Implementation boundaries
|
|
236
238
|
|
|
237
239
|
The model's settled positions are those above: short- and long-running instances are both legitimate; the default is centralized, per-soul knowledge open to reviewed structural evolution; capabilities own their model and runtime behavior; the doctrine preserves expertise, not code descriptions or task residue; and structural change preserves provenance and running work. These are not CLI flags or configuration schemas.
|
|
238
240
|
|
|
239
|
-
The default OKF capability implements consultation, capture, independent harvest and maintainer review; it does not provide automatic per-soul provisioning, a co-located profile, automatic speciation, redirects or context cloning. Proving the kernel's flexibility needs a genuinely different organization and learning model, not only Git and directory storage within OKF. Updating this document does not migrate existing deployments.
|
|
241
|
+
The default OKF capability implements consultation, capture, independent harvest and maintainer review; it does not provide automatic per-soul provisioning, a co-located profile, automatic speciation, redirects or context cloning. Context cloning is a separate package, `oats.cloning`, not part of the knowledge capability. Proving the kernel's flexibility needs a genuinely different organization and learning model, not only Git and directory storage within OKF. Updating this document does not migrate existing deployments.
|
|
240
242
|
|
|
241
243
|
## Related documentation
|
|
242
244
|
|
package/docs/official-catalog.md
CHANGED
|
@@ -11,9 +11,10 @@ or workspace membership alone does not make a package official.
|
|
|
11
11
|
|---|---|---|---|
|
|
12
12
|
| `oats.framework` | `oats-framework/v1.5.0` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
|
|
13
13
|
| `oats.okf` | `v4.1.1` | `oats.okf` (knowledge), `oats.okf-harvest`, `oats.okf-maintenance` | `knowledge-harvester`, `knowledge-maintainer` |
|
|
14
|
-
| `oats.aweb` | `v1.21.
|
|
15
|
-
| `oats.engineering` | `v1.
|
|
14
|
+
| `oats.aweb` | `v1.21.1` | `oats.aweb` (messaging) | |
|
|
15
|
+
| `oats.engineering` | `v1.8.0` | `oats.engineering-expert`, `oats.developer`, `oats.code-review`, `oats.maintainer` | `code-reviewer` |
|
|
16
16
|
| `oats.authoring` | `v1.0.3` | `oats.authoring` | |
|
|
17
|
+
| `oats.cloning` | `v1.0.1` | `oats.cloning` | `cloner` |
|
|
17
18
|
| `oats.jira` | `v1.0.1` | `oats.jira` (tasks) | |
|
|
18
19
|
| `oats.linear` | `v1.0.1` | `oats.linear` (tasks) | |
|
|
19
20
|
|
package/docs/packages.md
CHANGED
|
@@ -76,7 +76,7 @@ members:
|
|
|
76
76
|
packages:
|
|
77
77
|
oats.framework: v1.5.0
|
|
78
78
|
oats.okf: v4.1.1
|
|
79
|
-
oats.aweb: v1.21.
|
|
79
|
+
oats.aweb: v1.21.1
|
|
80
80
|
teams:
|
|
81
81
|
platform: { team: "platform:acme.aweb.ai", description: Platform engineering }
|
|
82
82
|
defaults:
|
|
@@ -134,7 +134,7 @@ Declaring a package in the workspace's `packages:` is the trust decision
|
|
|
134
134
|
## `oats package add | remove`
|
|
135
135
|
|
|
136
136
|
```bash
|
|
137
|
-
oats package add oats.aweb v1.21.
|
|
137
|
+
oats package add oats.aweb v1.21.1 # a catalog version
|
|
138
138
|
oats package add acme.tools git:github.com/acme/tools@v0.4.0
|
|
139
139
|
oats package remove acme.tools
|
|
140
140
|
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# OATS 0.39.1
|
|
2
|
+
|
|
3
|
+
## Changed
|
|
4
|
+
|
|
5
|
+
- **oats.aweb 1.21.1: a Claude Code start says that it will wait at Claude
|
|
6
|
+
Code's development-channels confirmation** (awebai/oats-aweb#44). Under
|
|
7
|
+
`delivery: channel`, Claude Code loads the `aweb-channel` plugin with
|
|
8
|
+
`--dangerously-load-development-channels`, because the plugin is not on
|
|
9
|
+
Claude Code's approved channel list. Claude Code then stops at its "Loading
|
|
10
|
+
development channels" confirmation before the session starts, and waits
|
|
11
|
+
until someone answers it in the instance's terminal. Unattended starts
|
|
12
|
+
(Desktop starts and restarts, automations, successors) waited there with
|
|
13
|
+
nothing saying so. Now:
|
|
14
|
+
- every spawn or start that adds the flag carries the warning
|
|
15
|
+
`channel-dev-confirmation` once;
|
|
16
|
+
- `oats readiness` reports it, without changing the status, for a home
|
|
17
|
+
whose last start was Claude Code under `channel`.
|
|
18
|
+
|
|
19
|
+
Nothing answers the confirmation on the human's behalf. Launch arguments
|
|
20
|
+
and settings are unchanged. `--channels` registers only plugins on Claude
|
|
21
|
+
Code's approved list; for a plugin not on it, Claude Code prints a startup
|
|
22
|
+
warning and the channel does not register. That list is Anthropic's
|
|
23
|
+
default, or a Team/Enterprise organization's managed
|
|
24
|
+
`allowedChannelPlugins`, which replaces the default and requires
|
|
25
|
+
`channelsEnabled: true`.
|
|
26
|
+
|
|
27
|
+
## Fixed
|
|
28
|
+
|
|
29
|
+
- **`oats readiness` no longer fails package souls on membership**
|
|
30
|
+
(awebai/oats#533). A package soul (`oats.okf/knowledge-maintainer`,
|
|
31
|
+
`oats.engineering/code-reviewer`, …) failed its `member` check with
|
|
32
|
+
"member <its package's repository>: not a member of the workspace", and the
|
|
33
|
+
remedy asked to list the package's repository in `members:`. A package
|
|
34
|
+
soul is trusted through the workspace's `packages:` pin, never through
|
|
35
|
+
membership (see [the non-collapse rule](../workspaces.md#member-tier-vs-package-tier-the-non-collapse-rule)).
|
|
36
|
+
Its `member` item is now `package <package>/<soul>`. It passes when the
|
|
37
|
+
package is declared and locked, ships the soul at the locked commit, and
|
|
38
|
+
the soul's copy matches its digest: for `--soul`, the copy a spawn would
|
|
39
|
+
link against the lock; for `--home`, the home's copy against the digest it
|
|
40
|
+
recorded, and the lock against that same digest while it still pins the
|
|
41
|
+
same commit. A mismatch fails with `E_PACKAGE_INTEGRITY`. Member and
|
|
42
|
+
external souls are unchanged. Spawn already applied this rule.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# OATS 0.39.2
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
|
|
5
|
+
- **oats.cloning 1.0.1**, a new official package (catalog entry, workspace
|
|
6
|
+
member and pin, and the bundled mirror): clone an instance for a new goal.
|
|
7
|
+
`oats cloning request <source>` spawns the package soul
|
|
8
|
+
`oats.cloning/cloner`, which reads the source and writes a curated,
|
|
9
|
+
redacted brief, then spawns the clone with it attached and a fresh
|
|
10
|
+
identity. The clone gets no raw transcript, notes or work content. See
|
|
11
|
+
[instance cloning](../design/2026-10-01-instance-cloning.md).
|
|
12
|
+
- The framework's oats-expert, oats-kernel-expert, oats-desktop-expert,
|
|
13
|
+
integrations-expert, oats-operator-expert and oats-setup-admin souls take
|
|
14
|
+
it (`oats.cloning: { from: package }`); there is no workspace default.
|
|
15
|
+
- The workspace pins it as `git:github.com/awebai/oats-cloning@v1.0.1`
|
|
16
|
+
until every deployment's CLI carries this catalog entry; then the pin
|
|
17
|
+
becomes the bare `v1.0.1`.
|
|
18
|
+
- 1.0.1 over 1.0.0 (the first pin on main): secret assignments are
|
|
19
|
+
redacted in any case and as JSON or YAML keys (`password=…`,
|
|
20
|
+
`client_secret: …`, `"PASSWORD": "…"`), a quoted value to its real
|
|
21
|
+
closing quote. The README states what redaction does not catch, and that
|
|
22
|
+
under `delivery: channel` a Claude Code cloner or clone waits at Claude
|
|
23
|
+
Code's development-channels confirmation until a human answers it.
|
|
24
|
+
- Operators: run `oats sync` to lock the package; new spawns of those
|
|
25
|
+
souls get the capability. Running instances keep what they were spawned
|
|
26
|
+
with.
|
|
27
|
+
|
|
28
|
+
## Changed
|
|
29
|
+
|
|
30
|
+
- **Desktop: terminal text in the White and Solarized themes renders at the
|
|
31
|
+
stroke weight of a native macOS terminal with font smoothing.** Chromium
|
|
32
|
+
draws dark text on a light background lighter, so these themes' terminal text
|
|
33
|
+
looked fainter than the same font in a native terminal. Each theme now sets
|
|
34
|
+
its own text weight; Dark is unchanged, and bold text stays bold.
|
|
35
|
+
- **oats.engineering 1.8.0** (catalog and workspace pin, and the bundled
|
|
36
|
+
mirrors; includes 1.6.0 and 1.7.0):
|
|
37
|
+
- **New capability `oats.maintainer`**: the whole maintainer job, generic.
|
|
38
|
+
It covers the overview of what's open, launching work, a direction and
|
|
39
|
+
architecture gate, review and merge at an exact head, release planning
|
|
40
|
+
and shipping with a verification checklist, cross-review between peer
|
|
41
|
+
maintainers, keeping the project clean, and a living roadmap,
|
|
42
|
+
architectural calls and coherence rules kept in the maintainer's
|
|
43
|
+
knowledge. Skills: `/maintainer-intake`, `/launch-work`,
|
|
44
|
+
`/direction-gate`, `/pr-review`, `/plan-release`, `/ship-release`,
|
|
45
|
+
`/cross-review-peer`, `/keep-it-clean`.
|
|
46
|
+
- **A maintainer is not an expert; no soul holds both roles.** The
|
|
47
|
+
maintainer launches work without leading it: `/launch-work` delegates it
|
|
48
|
+
to a live expert whose work it continues, or spawns the best-suited expert
|
|
49
|
+
with `--relation unrelated` as the effort's lead, never as its own child.
|
|
50
|
+
The lead coordinates the developers and experts it needs.
|
|
51
|
+
- **Knowledge and state.** Maintainers and experts consult their knowledge
|
|
52
|
+
before deciding, and keep their instance state accurate.
|
|
53
|
+
- **Experts tell a standing maintainer what they start** when a human
|
|
54
|
+
launches them directly.
|
|
55
|
+
- **Experts give unrelated work to a new expert** (1.6.0). New work goes to
|
|
56
|
+
a live expert only when it continues that expert's work or that expert's
|
|
57
|
+
context helps. Otherwise the expert spawns a new one: as its child when
|
|
58
|
+
the work is part of its effort, or with `--relation unrelated` when it is
|
|
59
|
+
independent, reporting to whoever asked.
|
|
60
|
+
- **Developers prefer this machine's reviewer override** (1.7.0). When
|
|
61
|
+
picking the reviewer's model, `/run-the-review-loop` reads what the
|
|
62
|
+
machine will launch (`launch.effective`, including an `oats-local.yaml`
|
|
63
|
+
`souls.launch` override), not only the soul's default. It keeps the
|
|
64
|
+
override when that runs on another harness than the developer's.
|
|
@@ -387,6 +387,30 @@ If the workspace has a messaging capability such as aweb, spawned instances
|
|
|
387
387
|
can also receive identities and coordinate with each other automatically. The
|
|
388
388
|
tasks capability can provide shared work state while messaging provides conversation.
|
|
389
389
|
|
|
390
|
+
### Cloning an instance
|
|
391
|
+
|
|
392
|
+
A clone is a new instance of an existing instance's soul that starts from a
|
|
393
|
+
curated brief of what that instance knows, for a new goal. It is not a copy of
|
|
394
|
+
the source's home. Cloning is the official
|
|
395
|
+
[`oats.cloning`](https://github.com/awebai/oats-cloning) package, not a kernel
|
|
396
|
+
verb; a soul that may request clones composes it
|
|
397
|
+
(`oats.cloning: { from: package }`). An instance with the capability runs:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
oats cloning request <source> --goal-file goal.md --relation independent|child|sibling|parent \
|
|
401
|
+
[--relative-to <instance>] [--name <slug>] [--transcript exclude] [--base source|default|<ref>]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
From the deployment directory the operator adds `--soul oats.cloning/cloner`.
|
|
405
|
+
The relation is required and is the kernel's spawn relation of the clone to the
|
|
406
|
+
anchor (`--relative-to`, the source by default); `independent` is the kernel's
|
|
407
|
+
`unrelated` and takes no anchor. The request spawns a short-lived
|
|
408
|
+
`oats.cloning/cloner`, which reads the source (its home files, work state and,
|
|
409
|
+
unless excluded, its transcript), writes the brief, spawns the clone with its
|
|
410
|
+
own identity, reports and retires itself. Only instances on this host can be
|
|
411
|
+
cloned. [Reusing working understanding](knowledge-theory.md#reusing-working-understanding-context-handoffs-and-cloning)
|
|
412
|
+
says what a clone carries and what it does not.
|
|
413
|
+
|
|
390
414
|
### Retire
|
|
391
415
|
|
|
392
416
|
Retirement runs active capability retire hooks in reverse spawn order before the home disappears. The aweb
|
package/lib/instance-inspect.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { accessSync, constants as fsConstants, existsSync, readFileSync, realpat
|
|
|
15
15
|
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { capabilityManifests, sessionDefaults, homeLaunchLayers, instanceSoulDir, launchConfigsAt, launchFolderTrust, launchReportFor, manifestOperations, parseYamlNested, servedIdentityOf, teamEnv, upgradeHomeMeta, withConfigFile } from "./core.mjs";
|
|
18
|
-
import { agentDirOf, discoverOrStandalone, ensureWorkspaceSoul, findSoulEntry, liveTeams, prepareInstance } from "./instance-resolution.mjs";
|
|
18
|
+
import { agentDirOf, discoverOrStandalone, ensureWorkspaceSoul, findSoulEntry, liveTeams, prepareInstance, soulCopyDigests } from "./instance-resolution.mjs";
|
|
19
19
|
import { declaredSettings } from "./capability-contract.mjs";
|
|
20
20
|
import { kernelCompatibility } from "./resolve.mjs";
|
|
21
21
|
import { discoveredTeamKeys, isRemovedTeamKeys, isTeamRefusal, recordedTeams, reportRows, soulKeyOf, teamKeyOf, soulTeams, teamModel, teamProblems } from "./teams.mjs";
|
|
@@ -133,7 +133,8 @@ export async function homeTarget(home, meta, { remoteOptions, discover = true, l
|
|
|
133
133
|
kind: "instance", home: realHome, meta, deployment, agentsRoot, teams: live.teams, defaultTeam: live.defaultTeam ?? null, teamsSource: live.source, launch, launchCurrent, session: newSessionDefaults(deployment),
|
|
134
134
|
recordedDefaultTeam: recordedTeams(meta).defaultTeam, teamModel: model, teamKey,
|
|
135
135
|
subject: { kind: "instance", instance: meta.instance, home, soul: meta.agent },
|
|
136
|
-
soul: { name: meta.agent, repoKey: ws.soul?.repoKey ?? null, commit: ws.soul?.commit ?? null, external, path: null, soulDir, definition, problems: [...problems, ...loadProblems]
|
|
136
|
+
soul: { name: meta.agent, repoKey: ws.soul?.repoKey ?? null, commit: ws.soul?.commit ?? null, external, path: null, soulDir, definition, problems: [...problems, ...loadProblems],
|
|
137
|
+
...(obj(ws.soul?.package) && typeof ws.soul.package.id === "string" ? { package: { id: ws.soul.package.id, soul: ws.soul.name ?? meta.agent, version: ws.soul.package.version ?? null, commit: ws.soul.package.commit ?? ws.soul.commit ?? null, integrity: ws.soul.package.digest ?? null } } : {}) },
|
|
137
138
|
workspace: { key: ws.key ?? null, name: typeof ws.name === "string" ? ws.name : discovery?.workspace?.name ?? null, deployment, commit: ws.commit ?? null, standalone: ws.standalone === true },
|
|
138
139
|
modules: Object.keys(modules).sort().map((name) => ({ name, from: modules[name]?.from ?? null, manifest: manifests[name] ?? null, dir: join(realHome, ".oats", "modules", name) })),
|
|
139
140
|
payloads: obj(meta.providers) ? meta.providers : {},
|
|
@@ -189,7 +190,8 @@ export async function soulTarget(contextDir, soul, { remoteOptions } = {}) {
|
|
|
189
190
|
kind: "soul", home: null, meta: null, deployment, agentsRoot: join(deployment, "agents"), teams, defaultTeam, teamsSource: "live", teamModel: model, teamKey, launch, session: newSessionDefaults(deployment),
|
|
190
191
|
subject: { kind: "soul", soul: soulEntry.name, repoKey: soulEntry.repoKey ?? null, commit: soulEntry.commit ?? null },
|
|
191
192
|
soul: { name: soulEntry.name, repoKey: soulEntry.repoKey ?? null, commit: soulEntry.commit ?? null, external: soulEntry.external === true, path: soulEntry.path ?? null,
|
|
192
|
-
soulDir, definition: soulEntry.definition ?? null, problems: [], ...(soulCopyError ? { copyError: soulCopyError } : {})
|
|
193
|
+
soulDir, definition: soulEntry.definition ?? null, problems: [], ...(soulCopyError ? { copyError: soulCopyError } : {}),
|
|
194
|
+
...(typeof soulEntry.package === "string" ? { package: { id: soulEntry.package, soul: soulEntry.name, version: soulEntry.version ?? null, commit: soulEntry.commit ?? null, integrity: soulEntry.digest ?? null } } : {}) },
|
|
193
195
|
workspace: { key: discovery?.key ?? null, name: discovery?.workspace?.name ?? null, deployment, commit: discovery?.commit ?? null, standalone: discovery?.standalone === true },
|
|
194
196
|
modules: (res?.modules || []).map((m) => ({ name: m.name, from: m.from ?? null, manifest: m.manifest ?? null, dir: null, module: m })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
195
197
|
payloads: obj(res?.payloads) ? res.payloads : {}, payloadOrigins: obj(res?.payloadOrigins) ? res.payloadOrigins : {}, slots: obj(res?.slots) ? res.slots : Object.fromEntries(LAYERS.map((l) => [l, null])), slotsFrom: obj(res?.slotsFrom) ? res.slotsFrom : {},
|
|
@@ -404,6 +406,48 @@ export function runProviderCheck(t, mod, dir, { timeoutMs = 30000 } = {}) {
|
|
|
404
406
|
|| (res.status === "ready" && res.problems.length)) return invalid();
|
|
405
407
|
return { outcome: "result", result: { status: res.status, problems: codeMessages(res.problems), warnings: codeMessages(res.warnings ?? []) } };
|
|
406
408
|
}
|
|
409
|
+
/** The `member` item of a package soul. A package soul is trusted through the workspace's
|
|
410
|
+
* `packages:` pin (the lock's commit and integrity), never through membership: its
|
|
411
|
+
* repository is not a member and need not be (docs/workspaces.md, the non-collapse rule).
|
|
412
|
+
* It passes when the discovery lists the soul from the lock (the package declared and
|
|
413
|
+
* locked, the soul shipped at the locked commit) and the soul's copy is intact: for a
|
|
414
|
+
* soul, the copy a spawn would link matches the locked digest; for a home, its copy
|
|
415
|
+
* matches the digest it recorded, and a lock still at that commit records the same one.
|
|
416
|
+
* A copy is digested every read: a complete copy is reused, never refetched. */
|
|
417
|
+
function packageSoulItem(t, d) {
|
|
418
|
+
const p = t.soul.package;
|
|
419
|
+
const subject = `package ${p.id}/${p.soul}`;
|
|
420
|
+
const from = { kind: "package", package: p.id, version: p.version, commit: p.commit, integrity: p.integrity };
|
|
421
|
+
const evidence = { repoKey: t.soul.repoKey, workspace: d?.key ?? null, commit: p.commit, version: p.version, integrity: p.integrity, from };
|
|
422
|
+
if (!d) return item(subject, "unknown", { producer: "workspace discovery", reason: t.discoveryError ? `the workspace could not be read: ${t.discoveryError.message}` : "no workspace observation", evidence });
|
|
423
|
+
if (d.standalone === true) return item(subject, "not-applicable", { required: false, producer: "workspace discovery", reason: `standalone view (${d.standaloneReason ?? "explicit"}): the workspace's packages: is not read, so the package pin is recorded, not observed`, evidence });
|
|
424
|
+
const shipped = (d.packageSouls || []).filter((s) => s.package === p.id);
|
|
425
|
+
const live = shipped.find((s) => s.name === p.soul);
|
|
426
|
+
if (!live) {
|
|
427
|
+
return item(subject, "fail", { producer: "workspace lock", evidence,
|
|
428
|
+
reason: shipped.length ? `the version of ${p.id} locked in this workspace does not ship the soul ${p.soul}` : `${p.id} is not locked in this workspace's packages:`,
|
|
429
|
+
remedy: shipped.length ? `pin a release of ${p.id} that ships ${p.soul} in the workspace's packages:, then oats sync` : `declare ${p.id} in the workspace's packages:, then oats sync` });
|
|
430
|
+
}
|
|
431
|
+
const integrity = (reason, remedy) => item(subject, "fail", { producer: "workspace lock", code: "E_PACKAGE_INTEGRITY", reason, evidence, remedy });
|
|
432
|
+
const lockRemedy = "oats sync (a lock that was edited: remove the entry and sync again)";
|
|
433
|
+
const copy = t.soul.copyError;
|
|
434
|
+
if (copy?.code === "E_PACKAGE_INTEGRITY") return integrity(copy.message, lockRemedy);
|
|
435
|
+
const dir = t.soul.soulDir;
|
|
436
|
+
if (!dir) return item(subject, "unknown", { producer: "workspace lock", reason: "the soul's copy is not available, so its integrity cannot be checked", evidence });
|
|
437
|
+
const expected = t.home ? "recorded" : "locked";
|
|
438
|
+
let digests;
|
|
439
|
+
try { digests = soulCopyDigests(dir); }
|
|
440
|
+
catch (e) { return integrity(`the soul's copy at ${dir} cannot be digested: ${e.message}`, `move ${dir} aside, then spawn again (a fresh copy is fetched and verified)`); }
|
|
441
|
+
if (!digests.includes(p.integrity)) {
|
|
442
|
+
return integrity(`the soul's copy at ${dir} digests ${digests.at(-1)}, not the ${expected} ${p.integrity}${t.home ? "" : " (the lock's)"}`,
|
|
443
|
+
t.home ? `the copy changed after its spawn, and a spawn reuses it: move ${dir} aside, then spawn a new instance of ${p.id}/${p.soul} (a fresh copy is fetched and verified against the lock)` : `move ${dir} aside, then spawn again (a fresh copy is fetched and verified against the lock); if the lock was edited: ${lockRemedy}`);
|
|
444
|
+
}
|
|
445
|
+
if (t.home && live.commit === p.commit && live.digest !== p.integrity) {
|
|
446
|
+
return integrity(`the lock records ${live.digest} for the same commit ${String(p.commit).slice(0, 12)}; this home recorded ${p.integrity} at its spawn`, lockRemedy);
|
|
447
|
+
}
|
|
448
|
+
return item(subject, "pass", { producer: "workspace lock", evidence });
|
|
449
|
+
}
|
|
450
|
+
|
|
407
451
|
/** The total time the providers check may take in one readiness read. */
|
|
408
452
|
export const PROVIDER_CHECK_BUDGET_MS = 60_000;
|
|
409
453
|
|
|
@@ -430,9 +474,11 @@ export async function readinessDocument(t, { selector = null, remoteOptions, cat
|
|
|
430
474
|
}
|
|
431
475
|
configured.push(...teamItems(t), ...launchItems(t), ...folderTrustItems(t));
|
|
432
476
|
// member — the soul's member repository is a confirmed member of the workspace
|
|
433
|
-
// (oats-membership.yaml backlink observed over the remotes)
|
|
477
|
+
// (oats-membership.yaml backlink observed over the remotes); a package soul's is its
|
|
478
|
+
// package instead (packageSoulItem).
|
|
434
479
|
const d = t.discovery;
|
|
435
|
-
if (t.soul.
|
|
480
|
+
if (t.soul.package) member.push(packageSoulItem(t, d));
|
|
481
|
+
else if (t.soul.external) member.push(item(`soul ${t.soul.name}`, "not-applicable", { required: false, producer: "workspace discovery", reason: "an external soul is declared by the workspace; it has no member repository", evidence: { repoKey: t.soul.repoKey } }));
|
|
436
482
|
else if (!d) member.push(item(`member ${t.soul.repoKey ?? "?"}`, "unknown", { producer: "workspace discovery", reason: t.discoveryError ? `the workspace could not be read: ${t.discoveryError.message}` : "no workspace observation", evidence: { repoKey: t.soul.repoKey } }));
|
|
437
483
|
// A standalone view is an allowed mode (decision 10): membership cannot be confirmed
|
|
438
484
|
// there by definition, so it is not a readiness requirement (as for an external soul).
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Nothing here reads `oats-config.yaml`, an installed-capability directory or a
|
|
18
18
|
* per-soul `source:` — those do not exist in this model. */
|
|
19
|
-
import { existsSync, readFileSync, readdirSync, lstatSync, realpathSync } from "node:fs";
|
|
19
|
+
import { existsSync, readFileSync, readdirSync, lstatSync, readlinkSync, realpathSync } from "node:fs";
|
|
20
20
|
import { join, resolve as resolvePath, dirname, relative, isAbsolute, sep } from "node:path";
|
|
21
21
|
import { oatsError } from "./errors.mjs";
|
|
22
22
|
import { loadLocal, discoverWorkspace, discoverRepo, standaloneRepo, observeWorkspace } from "./workspace.mjs";
|
|
@@ -25,7 +25,7 @@ import { BY_TEAM_REMOVED, isRemovedTeamKeys, isTeamRefusal, memberNameOf, record
|
|
|
25
25
|
import { launchLayers } from "./launch-preference.mjs";
|
|
26
26
|
import { declaredSettings } from "./capability-contract.mjs";
|
|
27
27
|
import { materialize, moduleSkills, MODULES_DIR, SKILLS_DIR } from "./materialize.mjs";
|
|
28
|
-
import { fetchRemoteTree } from "./remote.mjs";
|
|
28
|
+
import { contentDigest, fetchRemoteTree } from "./remote.mjs";
|
|
29
29
|
import { mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync, symlinkSync, statSync } from "node:fs";
|
|
30
30
|
import { tmpdir } from "node:os";
|
|
31
31
|
import { spawnSync } from "node:child_process";
|
|
@@ -232,6 +232,19 @@ async function fetchSoulSource(prepared, dest) {
|
|
|
232
232
|
if (!existsSync(join(dest, "CLAUDE.md"))) symlinkSync("AGENTS.md", join(dest, "CLAUDE.md"));
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/** The digests a soul copy may carry as its source was fetched (fetchSoulSource): the copy itself, and,
|
|
236
|
+
* when it holds the CLAUDE.md → AGENTS.md alias, the copy without it, since fetchSoulSource adds that
|
|
237
|
+
* alias to a source that has none. A package soul's copy is intact when one of them is its digest. */
|
|
238
|
+
export function soulCopyDigests(dir) {
|
|
239
|
+
const options = { allowSymlinks: SOUL_ALIAS_SYMLINK };
|
|
240
|
+
const alias = join(dir, "CLAUDE.md");
|
|
241
|
+
let aliased = false;
|
|
242
|
+
try { aliased = lstatSync(alias).isSymbolicLink() && readlinkSync(alias) === "AGENTS.md"; } catch { aliased = false; }
|
|
243
|
+
const digests = [contentDigest(dir, options)];
|
|
244
|
+
if (aliased) digests.push(contentDigest(dir, { ...options, omit: (rel) => rel === "CLAUDE.md" }));
|
|
245
|
+
return digests;
|
|
246
|
+
}
|
|
247
|
+
|
|
235
248
|
/** A preview's soul source. A preview writes NOTHING in the deployment (K6b), so it
|
|
236
249
|
* never populates the per-commit cache or moves the `soul` pointer: it reads the
|
|
237
250
|
* cache entry for the prepared commit when one is already complete, else fetches
|
package/lib/remote.mjs
CHANGED
|
@@ -2139,7 +2139,8 @@ function createDigest() {
|
|
|
2139
2139
|
}
|
|
2140
2140
|
|
|
2141
2141
|
/** Same digest as fetchRemoteTree, computed over a local directory. Symlinks and
|
|
2142
|
-
* non-regular entries are refused (E_REMOTE_TREE_UNSAFE); a missing dir → E_REMOTE_PATH_MISSING.
|
|
2142
|
+
* non-regular entries are refused (E_REMOTE_TREE_UNSAFE); a missing dir → E_REMOTE_PATH_MISSING.
|
|
2143
|
+
* `omit(relPath, absPath)` → true leaves an entry out (what a copy gained after its fetch). */
|
|
2143
2144
|
function posixNormalize(p) {
|
|
2144
2145
|
const out = [];
|
|
2145
2146
|
for (const seg of p.split("/")) { if (!seg || seg === ".") continue; if (seg === "..") { if (out.length && out.at(-1) !== "..") out.pop(); else out.push(".."); } else out.push(seg); }
|
|
@@ -2155,7 +2156,7 @@ function posixNormalize(p) {
|
|
|
2155
2156
|
*/
|
|
2156
2157
|
export const OATS_ALIAS_SYMLINK = (relPath) => typeof relPath === "string" && (relPath === "CLAUDE.md" || relPath.endsWith("/CLAUDE.md"));
|
|
2157
2158
|
|
|
2158
|
-
export function contentDigest(dir, { allowSymlinks = null } = {}) {
|
|
2159
|
+
export function contentDigest(dir, { allowSymlinks = null, omit = null } = {}) {
|
|
2159
2160
|
if (typeof dir !== "string" || !isAbsolute(dir)) throw fail("E_REPO_REF", "contentDigest requires an absolute directory path", { path: dir });
|
|
2160
2161
|
let st;
|
|
2161
2162
|
try { st = lstatSync(dir); } catch { throw fail("E_REMOTE_PATH_MISSING", `${dir} does not exist`, { path: dir }); }
|
|
@@ -2165,6 +2166,7 @@ export function contentDigest(dir, { allowSymlinks = null } = {}) {
|
|
|
2165
2166
|
for (const name of readdirSync(abs)) {
|
|
2166
2167
|
if (!rel && name === ".git") continue; // a local checkout's metadata is not content
|
|
2167
2168
|
const p = join(abs, name), r = rel ? `${rel}/${name}` : name, s = lstatSync(p);
|
|
2169
|
+
if (typeof omit === "function" && omit(r, p)) continue;
|
|
2168
2170
|
if (s.isSymbolicLink()) {
|
|
2169
2171
|
// Refused by default (contract §1). The same narrow opt-in fetchRemoteTree
|
|
2170
2172
|
// honours (a relative, non-escaping alias such as CLAUDE.md → AGENTS.md)
|
package/package-catalog.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"oats.aweb": {
|
|
10
10
|
"url": "https://github.com/awebai/oats-aweb.git",
|
|
11
|
-
"ref": "v1.21.
|
|
11
|
+
"ref": "v1.21.1",
|
|
12
12
|
"path": "oats-package"
|
|
13
13
|
},
|
|
14
14
|
"oats.jira": {
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"oats.engineering": {
|
|
30
30
|
"url": "https://github.com/awebai/oats-engineering.git",
|
|
31
|
-
"ref": "v1.
|
|
31
|
+
"ref": "v1.8.0",
|
|
32
|
+
"path": "oats-package"
|
|
33
|
+
},
|
|
34
|
+
"oats.cloning": {
|
|
35
|
+
"url": "https://github.com/awebai/oats-cloning.git",
|
|
36
|
+
"ref": "v1.0.1",
|
|
32
37
|
"path": "oats-package"
|
|
33
38
|
},
|
|
34
39
|
"oats.framework": {
|
|
@@ -65,6 +70,7 @@
|
|
|
65
70
|
"oats.okf-maintenance": "oats.okf",
|
|
66
71
|
"oats.engineering-expert": "oats.engineering",
|
|
67
72
|
"oats.developer": "oats.engineering",
|
|
68
|
-
"oats.code-review": "oats.engineering"
|
|
73
|
+
"oats.code-review": "oats.engineering",
|
|
74
|
+
"oats.maintainer": "oats.engineering"
|
|
69
75
|
}
|
|
70
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.2",
|
|
4
4
|
"description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|