@awebai/oats 0.25.2 → 0.25.3
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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Purpose:** the one accurate view of every work stream in the redesign, what is on main, what is in flight, who owns it, and what blocks it. Lead: `oats-expert` (redesign lead). Updated whenever anything merges, is returned, or reality changes. Older per-lane boards are superseded by this file.
|
|
4
4
|
|
|
5
|
-
**Last update:** 2026-09-23 20:00Z · **0.25.0 + 0.25.1 PUBLISHED** (workspace model A–C + team-review fixes) · Phase D plan next · **Desktop engineer paused by the human** (10B-0 uncommitted foundation preserved; resume is the human's call; 0.24.14 to be cut from a maintenance branch off `e5cdaf95` when its PR lands) · parity pipeline ⏸.
|
|
5
|
+
**Last update:** 2026-09-23 20:00Z · **0.25.0 + 0.25.1 + 0.25.2 PUBLISHED** (workspace model A–C + team-review fixes + operator-rebuild round) · Phase D plan next · **Desktop engineer paused by the human** (10B-0 uncommitted foundation preserved; resume is the human's call; 0.24.14 to be cut from a maintenance branch off `e5cdaf95` when its PR lands) · parity pipeline ⏸.
|
|
6
6
|
|
|
7
7
|
Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
|
|
8
8
|
|
|
@@ -564,3 +564,23 @@ in `next.clone` like any member that lacks a clone at the convention (the host
|
|
|
564
564
|
is a member; a soul that lives in it may need a work clone). Under an explicit
|
|
565
565
|
`oats-local.yaml` `standalone:` header the next steps say the view is standalone
|
|
566
566
|
and list only that repo.
|
|
567
|
+
|
|
568
|
+
### 0.25.3 — `OATS_SOUL_ID` (stable soul identity for providers)
|
|
569
|
+
|
|
570
|
+
The per-commit soul cache (0.25.1, M1) made `realpath(<home>/soul)` change with every
|
|
571
|
+
member commit; a provider that keyed durable state on that path (OKF 2.1.3 `owners.json`)
|
|
572
|
+
refused the next spawn (`E_OWNER`). "Members are latest" and "the owner is a path" cannot
|
|
573
|
+
both hold, so the kernel now hands hooks a **stable identity**:
|
|
574
|
+
|
|
575
|
+
- `OATS_SOUL_ID` in the `spawn` / `retire` / `launch` hook environment: for a workspace soul
|
|
576
|
+
`<repo key>#<soul name>` exactly as the canonical key is spelled (e.g.
|
|
577
|
+
`github.com/awebai/aweb#aweb-protocol-expert`, local fixtures `local//abs/path.git#name`);
|
|
578
|
+
for a classic soul the realpath of `agents/<name>/soul` (today's value — 0.24 deployments
|
|
579
|
+
unchanged). Also recorded as `instance.json.workspace.soul.id`.
|
|
580
|
+
- `OATS_SOUL` is the **content** the home links — for a workspace soul the per-commit
|
|
581
|
+
directory `agents/<name>/souls/<commit12>/`, never the swappable `agents/<name>/soul`
|
|
582
|
+
pointer. Providers read content from `OATS_SOUL` and key state on `OATS_SOUL_ID`.
|
|
583
|
+
- Provider contract (OKF 2.1.4): `owners[owner] = OATS_SOUL_ID ?? realpath(OATS_SOUL ?? home/soul)`;
|
|
584
|
+
a prior row whose value is a path under `agents/<same soul name>/(soul|souls/<commit>)` is
|
|
585
|
+
migrated to the id once, not refused; any other mismatch stays `E_OWNER`.
|
|
586
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# OATS v0.25.3 — `OATS_SOUL_ID`
|
|
2
|
+
|
|
3
|
+
Kernel/Pi **0.25.3**. Tag `v0.25.3` → the commit carrying these notes. No API change;
|
|
4
|
+
`features[]` unchanged; one additive hook environment variable and one additive
|
|
5
|
+
`instance.json` field.
|
|
6
|
+
|
|
7
|
+
## Kernel
|
|
8
|
+
|
|
9
|
+
- **`OATS_SOUL_ID`** — a soul's stable identity for capability hooks (`spawn`,
|
|
10
|
+
`retire`, `launch`): `<repo key>#<soul name>` for a workspace soul, the realpath of
|
|
11
|
+
`agents/<name>/soul` for a classic soul. Recorded as `instance.json.workspace.soul.id`.
|
|
12
|
+
Fixes the consequence of 0.25.1's per-commit soul cache found by the first outsider
|
|
13
|
+
rebuild: OKF 2.1.3 pins a soul's owner to `realpath(home/soul)`, which now changes with
|
|
14
|
+
every member commit → the next spawn of the same soul was refused `E_OWNER`. The provider
|
|
15
|
+
half (owners keyed by `OATS_SOUL_ID`, one-time migration of path pins) ships in OKF 2.1.4.
|
|
16
|
+
- **`OATS_SOUL` is the content the home links** — for a workspace soul the per-commit
|
|
17
|
+
directory, never the swappable `agents/<name>/soul` pointer (spawn and retire hooks agree).
|
|
18
|
+
|
|
19
|
+
Contract: `docs/design/2026-09-23-workspace-module-contracts.md` § "0.25.3 — OATS_SOUL_ID".
|
package/lib/core.mjs
CHANGED
|
@@ -640,7 +640,7 @@ const LAUNCH_CONFIG_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
|
640
640
|
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
641
641
|
/** Environment the kernel sets for every launch (identity, home, roots) and
|
|
642
642
|
* its reference aliases: a configuration may not name them. */
|
|
643
|
-
export const RESERVED_LAUNCH_ENV = new Set(["OATS_INSTANCE", "OATS_INSTANCE_HOME", "OATS_HOME", "OATS_AGENT", "OATS_SOUL", "OATS_ROOT", "OATS_CONTEXT", "OATS_WORKSPACE", "OATS_EVENT", "OATS_SETTINGS", "OATS_CLI_BIN", "PI_AGENT_INSTANCE", "PI_AGENT_HOME", "PI_AGENTS_ROOT"]);
|
|
643
|
+
export const RESERVED_LAUNCH_ENV = new Set(["OATS_INSTANCE", "OATS_INSTANCE_HOME", "OATS_HOME", "OATS_AGENT", "OATS_SOUL", "OATS_SOUL_ID", "OATS_ROOT", "OATS_CONTEXT", "OATS_WORKSPACE", "OATS_EVENT", "OATS_SETTINGS", "OATS_CLI_BIN", "PI_AGENT_INSTANCE", "PI_AGENT_HOME", "PI_AGENTS_ROOT"]);
|
|
644
644
|
export const LAUNCH_REF_PREFIX = "OATS_LAUNCH_REF_";
|
|
645
645
|
const reservedLaunchEnv = (n) => RESERVED_LAUNCH_ENV.has(n) || n.startsWith(LAUNCH_REF_PREFIX);
|
|
646
646
|
export function validateLaunchConfig(name, entry, where) {
|
|
@@ -4806,7 +4806,31 @@ function validateHookEnvironment(capabilityID, value, owners, declarations) {
|
|
|
4806
4806
|
return accepted;
|
|
4807
4807
|
}
|
|
4808
4808
|
|
|
4809
|
-
|
|
4809
|
+
/**
|
|
4810
|
+
* A soul's STABLE identity for capability hooks (OATS_SOUL_ID): what a provider
|
|
4811
|
+
* may key durable state on. Workspace soul: `<repo key>#<soul name>` — it does not
|
|
4812
|
+
* change when the member commits (the per-commit soul directory does) or when the
|
|
4813
|
+
* copy moves. Classic soul: the realpath of agents/<name>/soul, i.e. today's value,
|
|
4814
|
+
* so 0.24 deployments are unchanged. Order: an explicit `soulId` (a prepared spawn),
|
|
4815
|
+
* the home's recorded `instance.json.workspace.soul` (retire/launch of a workspace
|
|
4816
|
+
* home), else the path.
|
|
4817
|
+
*/
|
|
4818
|
+
export function stableSoulId({ soulId, home, soulDir, agentName } = {}) {
|
|
4819
|
+
if (typeof soulId === "string" && soulId) return soulId;
|
|
4820
|
+
if (home) {
|
|
4821
|
+
try {
|
|
4822
|
+
const meta = JSON.parse(readFileSync(join(home, "instance.json"), "utf8"));
|
|
4823
|
+
const s = meta?.workspace?.soul;
|
|
4824
|
+
if (typeof s?.id === "string" && s.id) return s.id;
|
|
4825
|
+
if (typeof s?.repoKey === "string" && s.repoKey) return `${s.repoKey}#${meta.agent ?? agentName ?? ""}`;
|
|
4826
|
+
} catch { /* not a workspace home */ }
|
|
4827
|
+
}
|
|
4828
|
+
if (soulDir) { try { return realpathSync(soulDir); } catch { return soulDir; } }
|
|
4829
|
+
return "";
|
|
4830
|
+
}
|
|
4831
|
+
export const workspaceSoulId = (repoKey, name) => `${repoKey}#${name}`;
|
|
4832
|
+
|
|
4833
|
+
export function runLifecycleHooks(event, { home, instance, agentName, soulDir, soulId, contextDir, workspaceDir, rootDir, resolved, priorMeta = {}, extraEnv = {}, assertRoots }) {
|
|
4810
4834
|
const results = { meta: {}, briefs: [], warnings: [], order: [], launch: {}, env: {}, failures: [], contributions: [] };
|
|
4811
4835
|
const envOwners = new Map();
|
|
4812
4836
|
const envDeclarations = new Map((resolved.capabilities || []).map((cap) => [cap.id, { names: new Set(cap.environment || []), namespaces: [...(cap.environmentNamespaces || [])] }]));
|
|
@@ -4834,7 +4858,7 @@ export function runLifecycleHooks(event, { home, instance, agentName, soulDir, c
|
|
|
4834
4858
|
// the package STORE root — do not conflate them.
|
|
4835
4859
|
OATS_EVENT: event, OATS_INSTANCE: instance, OATS_INSTANCE_HOME: home, OATS_HOME: home, OATS_AGENT: agentName,
|
|
4836
4860
|
OATS_CAPABILITY: cap.id, OATS_LAYER: cap.layer || "", OATS_ROOT: rootDir || "",
|
|
4837
|
-
OATS_SOUL: soulDir || "", OATS_CONTEXT: contextDir, OATS_WORKSPACE: workspaceDir || "", OATS_LEVEL: cap.level || "",
|
|
4861
|
+
OATS_SOUL: soulDir || "", OATS_SOUL_ID: stableSoulId({ soulId, home, soulDir, agentName }), OATS_CONTEXT: contextDir, OATS_WORKSPACE: workspaceDir || "", OATS_LEVEL: cap.level || "",
|
|
4838
4862
|
OATS_TEAM_NAME: resolved.team?.name || "", OATS_TEAM_ID: resolved.team?.id || "", OATS_TEAM_SCOPE: resolved.team?.scope || "",
|
|
4839
4863
|
...extraEnv,
|
|
4840
4864
|
// Hooks also run through direct core callers (not only bin/oats).
|
|
@@ -6938,8 +6962,12 @@ function* spawnBody(root, agent, o = {}) {
|
|
|
6938
6962
|
// Capability lifecycle hooks (spawn) — the knowledge integration scaffolds instance
|
|
6939
6963
|
// memory (STATE.md/log.md/notes/ are OKF conventions, not kernel ones); the
|
|
6940
6964
|
// messaging integration mints the comms identity. Kernel stays memory-agnostic.
|
|
6965
|
+
const preparedSoulId = o.prepared ? workspaceSoulId(o.prepared.soulEntry.repoKey, o.prepared.soulEntry.name) : undefined;
|
|
6966
|
+
// Hooks read the soul the HOME links (the per-commit directory for a workspace
|
|
6967
|
+
// soul), never the swappable agents/<name>/soul pointer: a provider that pins a
|
|
6968
|
+
// path must pin this instance's content, and OATS_SOUL_ID is what it keys on.
|
|
6941
6969
|
const hookRes = runLifecycleHooks("spawn", {
|
|
6942
|
-
home, instance, agentName: agent.name, soulDir, contextDir: repoAbs,
|
|
6970
|
+
home, instance, agentName: agent.name, soulDir: homeSoulTarget, soulId: preparedSoulId, contextDir: repoAbs,
|
|
6943
6971
|
workspaceDir: workspaceOf(root), rootDir: root, resolved: resolvedCfg,
|
|
6944
6972
|
extraEnv: { OATS_TASK: task, OATS_REPO: repoAbs, OATS_BRANCH: branch || "", OATS_WORK: work, OATS_RUNTIME: runtime, OATS_KIND: agent.kind || "persistent" },
|
|
6945
6973
|
});
|
|
@@ -7043,7 +7071,7 @@ function* spawnBody(root, agent, o = {}) {
|
|
|
7043
7071
|
catch (e) { return retainDirectory(e); }
|
|
7044
7072
|
try {
|
|
7045
7073
|
const comp = runLifecycleHooks("retire", {
|
|
7046
|
-
home, instance, agentName: agent.name, soulDir, contextDir: repoAbs,
|
|
7074
|
+
home, instance, agentName: agent.name, soulDir: homeSoulTarget, soulId: preparedSoulId, contextDir: repoAbs,
|
|
7047
7075
|
workspaceDir: workspaceOf(root), rootDir: root, resolved: resolvedCfg,
|
|
7048
7076
|
priorMeta: hookRes.meta || {},
|
|
7049
7077
|
});
|
|
@@ -7242,7 +7270,7 @@ ${task.trim() ? `\n## Task\n\n${task.trim()}\n` : "\nNo task was provided at spa
|
|
|
7242
7270
|
// digests) in instance.json before this metadata is assembled — carry them.
|
|
7243
7271
|
if (o.prepared) {
|
|
7244
7272
|
try { const prior = JSON.parse(readFileSync(join(home, "instance.json"), "utf8")); if (prior.modules) meta.modules = prior.modules; if (prior.providers) meta.providers = prior.providers; } catch { /* materialize wrote it; absent means nothing to carry */ }
|
|
7245
|
-
meta.workspace = { key: o.prepared.discovery?.key ?? null, commit: o.prepared.discovery?.commit ?? null, resolution: o.prepared.resolution.revision, standalone: o.prepared.discovery?.standalone === true, soul: { repoKey: o.prepared.soulEntry.repoKey, commit: o.prepared.soulEntry.commit, team: o.prepared.soulEntry.team ?? null } };
|
|
7273
|
+
meta.workspace = { key: o.prepared.discovery?.key ?? null, commit: o.prepared.discovery?.commit ?? null, resolution: o.prepared.resolution.revision, standalone: o.prepared.discovery?.standalone === true, soul: { id: workspaceSoulId(o.prepared.soulEntry.repoKey, o.prepared.soulEntry.name), repoKey: o.prepared.soulEntry.repoKey, commit: o.prepared.soulEntry.commit, team: o.prepared.soulEntry.team ?? null } };
|
|
7246
7274
|
}
|
|
7247
7275
|
const spawnWarnings = warnings;
|
|
7248
7276
|
|
|
@@ -9366,9 +9394,12 @@ export function retireInstance(root, name, o = {}) {
|
|
|
9366
9394
|
const resolved = meta.capabilityRuntime
|
|
9367
9395
|
? { capabilities: meta.capabilityRuntime }
|
|
9368
9396
|
: resolveOatsConfig(meta.repo, found.agent.name);
|
|
9397
|
+
// The soul this HOME links (a workspace home links its per-commit directory;
|
|
9398
|
+
// agents/<name>/soul may since point elsewhere) — the same value spawn's hook saw.
|
|
9399
|
+
const homeSoulLink = (() => { try { return realpathSync(join(found.home, "soul")); } catch { return null; } })();
|
|
9369
9400
|
hookResults = runLifecycleHooks("retire", {
|
|
9370
9401
|
home: found.home, instance: name, agentName: found.agent.name,
|
|
9371
|
-
soulDir: found.agent._soulDir || join(found.agent._dir, "soul"),
|
|
9402
|
+
soulDir: homeSoulLink || found.agent._soulDir || join(found.agent._dir, "soul"),
|
|
9372
9403
|
contextDir: meta.repo, workspaceDir: workspaceOf(root), rootDir: root, resolved, priorMeta: meta.capabilityMeta || {},
|
|
9373
9404
|
});
|
|
9374
9405
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
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",
|