@awebai/oats 0.27.2 → 0.28.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/bin/oats.mjs +185 -26
- package/capabilities/oats-okf/agents/memory-harvest/AGENTS.md +8 -3
- package/capabilities/oats-okf/bin/oats-okf.mjs +33 -28
- package/capabilities/oats-okf/injects/okf.md +29 -21
- package/capabilities/oats-okf/lib/config.mjs +5 -1
- package/capabilities/oats-okf/lib/consult.mjs +500 -0
- package/capabilities/oats-okf/lib/inspection.mjs +11 -3
- package/capabilities/oats-okf/lib/io.mjs +9 -2
- package/capabilities/oats-okf/lib/sources.mjs +15 -53
- package/capabilities/oats-okf/lib/stores.mjs +10 -7
- package/capabilities/oats-okf/lib/worker.mjs +9 -1
- package/capabilities/oats-okf/oats.json +13 -4
- package/capabilities/oats-okf/skills/okf/SKILL.md +14 -6
- package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +142 -0
- package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +86 -0
- package/docs/design/2026-09-24-phase-d-plan.md +11 -0
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +241 -0
- package/docs/design/2026-09-26-okf-knowledge-operations.md +389 -0
- package/docs/desktop-cli-api.md +93 -7
- package/docs/oats-local.schema.json +2 -1
- package/docs/oats-package.schema.json +39 -0
- package/docs/packages.md +67 -3
- package/docs/release-notes/v0.28.0.md +144 -0
- package/docs/schedules.md +99 -1
- package/docs/souls-and-instances.md +7 -3
- package/docs/workspaces.md +8 -2
- package/lib/core.mjs +22 -4
- package/lib/instance-inspect.mjs +4 -4
- package/lib/instance-resolution.mjs +62 -18
- package/lib/materialize.mjs +13 -0
- package/lib/packages.mjs +90 -6
- package/lib/resolve.mjs +20 -2
- package/lib/schedule.mjs +24 -11
- package/lib/triggers.mjs +545 -0
- package/lib/workspace.mjs +80 -3
- package/package-catalog.json +1 -1
- package/package.json +1 -1
package/docs/desktop-cli-api.md
CHANGED
|
@@ -54,6 +54,18 @@ something to say. The one warning so far is `deprecated-runtime-name` (see
|
|
|
54
54
|
--json`, whose document is not an envelope, carries the same `warnings` beside
|
|
55
55
|
its `problems`. In text mode the warning is one `oats: warning: …` line on stderr.
|
|
56
56
|
|
|
57
|
+
## Flags
|
|
58
|
+
|
|
59
|
+
A kernel command reads `--flag=value` exactly as `--flag value`, with the
|
|
60
|
+
same validation (0.27.3+; before, the inline form was silently ignored, so
|
|
61
|
+
`--harness=claude` spawned the default harness). The value is everything after
|
|
62
|
+
the first `=`. An empty `--flag=` is `E_BAD_ARGS` ("`--flag=` needs a value"),
|
|
63
|
+
and so is a value on a switch: `--yolo=false` is refused and never turns
|
|
64
|
+
yolo on. A capability command's own flags belong to its provider. They are
|
|
65
|
+
forwarded exactly as typed; the kernel reads only its dispatch flag (`--soul`)
|
|
66
|
+
in either form. There is no feature string: a caller that must work with
|
|
67
|
+
older kernels uses the spaced form.
|
|
68
|
+
|
|
57
69
|
## The harness rename (feature `harness`, OATS 0.27.0)
|
|
58
70
|
|
|
59
71
|
What starts an instance (pi, claude or codex) is its **harness**. 0.27.0 renames
|
|
@@ -667,7 +679,7 @@ unchecked, echoed a stored `definition.id` without checking it, and named a
|
|
|
667
679
|
```
|
|
668
680
|
|
|
669
681
|
**Exact shapes (API 3):**
|
|
670
|
-
- `schedule list --json` → `result = {scope, scheduleApi: 2, scheduleHistoryApi: 3, integrity, schedules: Entry[], scheduler}
|
|
682
|
+
- `schedule list --json` → `result = {scope, scheduleApi: 2, scheduleHistoryApi: 3, integrity, schedules: Entry[], triggers: {count, command: "oats trigger list"}, scheduler}` (`triggers`, 0.28.0: the trigger definitions this listing leaves out).
|
|
671
683
|
- `schedule show <id> --json` → `result = {schedule: Entry}` (one level of nesting; `integrity` is NOT on `show` — it is a scope fact reported by `list`).
|
|
672
684
|
- `Entry` (readable) = definition fields (`id, kind, home, message|operation, cron, enabled, …`) + `{scope, scheduleApi: 2, scheduleHistoryApi: 3, executionStatus, nextRun: ISO|null, lastRun: Run|null, history, recentRuns: Run[], running: boolean, attempt?, pendingWake?}`.
|
|
673
685
|
- `Entry` (unreadable, `list` only) = `{id, scope, scheduleApi: 2, scheduleHistoryApi: 3, unreadable: {code, message}, history: {status: "corrupt", stored: null, truncated: false}, recentRuns: []}` — no definition fields.
|
|
@@ -1115,8 +1127,8 @@ gate that on the absence of `packages-no-approval`.
|
|
|
1115
1127
|
"souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
|
|
1116
1128
|
{"key":"github.com/acme/billing","name":"billing","commit":"<oid>","confirmed":false,"status":"no-backlink","detail":"github.com/acme/billing@… has no oats-membership.yaml","team":null,
|
|
1117
1129
|
"souls":[],"capabilities":[],"publishes":null}],
|
|
1118
|
-
"packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"]},
|
|
1119
|
-
{"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"]}],
|
|
1130
|
+
"packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"],"souls":[]},
|
|
1131
|
+
{"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"],"souls":["release-reviewer"]}],
|
|
1120
1132
|
"changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>"}],
|
|
1121
1133
|
"problems":[]}
|
|
1122
1134
|
```
|
|
@@ -1125,6 +1137,8 @@ gate that on the absence of `packages-no-approval`.
|
|
|
1125
1137
|
`backlink-elsewhere` | `cannot-read`; `detail` explains an unconfirmed row.
|
|
1126
1138
|
`publishes` reports a member's `oats-package/` (informational — its
|
|
1127
1139
|
capabilities are **not** in `capabilities[]`; the non-collapse rule).
|
|
1140
|
+
- `packages[].souls` (feature `package-souls`, 0.28.0): the names of the
|
|
1141
|
+
package souls the lock records for that package (`[]` when none).
|
|
1128
1142
|
- `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
|
|
1129
1143
|
the package was dropped from `packages:` and from the lock.
|
|
1130
1144
|
- `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
|
|
@@ -1207,10 +1221,23 @@ or `"unassigned"`.
|
|
|
1207
1221
|
"souls":[
|
|
1208
1222
|
{"name":"release-manager","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
|
|
1209
1223
|
"team":"engineering","private":false,"path":"souls/release-manager","work":"worktree","description":"Cuts, verifies and announces releases."},
|
|
1210
|
-
{"name":"security-reviewer","origin":"external github.com/oss-collective/experts @ 9c4e1f2a","kind":"external",…}
|
|
1224
|
+
{"name":"security-reviewer","origin":"external github.com/oss-collective/experts @ 9c4e1f2a","kind":"external",…},
|
|
1225
|
+
{"name":"release-reviewer","qualifiedName":"acme.tools/release-reviewer","origin":"package acme.tools v0.4.0","kind":"package","package":"acme.tools","version":"0.4.0",
|
|
1226
|
+
"repoKey":"github.com/acme/tools","commit":"<oid>","team":"engineering","labels":["engineering"],"private":false,"path":"oats-package/souls/release-reviewer","work":"directory","description":"…"}],
|
|
1211
1227
|
"problems":[]}
|
|
1212
1228
|
```
|
|
1213
1229
|
|
|
1230
|
+
**Package souls** (feature `package-souls`, 0.28.0): a row with `kind:
|
|
1231
|
+
"package"` is a soul a locked package ships (listed only while the workspace
|
|
1232
|
+
declares the package). It carries `package`, `version` and `qualifiedName`
|
|
1233
|
+
(`<package>/<soul>`); spawn it by `qualifiedName` (the bare `name` works when
|
|
1234
|
+
it is unique). The Souls page shows it as "from package <id> <version>".
|
|
1235
|
+
Its instances home under `agents/<package>--<soul>/` (`.` in the package id
|
|
1236
|
+
becomes `-`), and that directory is the agent `name` in `oats status --json`
|
|
1237
|
+
(`agents[].name`, e.g. `oats-okf--knowledge-maintainer`). A
|
|
1238
|
+
problem about a package soul carries `package` (and `repoKey: null`); its
|
|
1239
|
+
`path` is `package:<id>:<path in the repo>`.
|
|
1240
|
+
|
|
1214
1241
|
Package capabilities of declared-but-unsynced packages are absent until `sync`.
|
|
1215
1242
|
(`oats souls --json` keeps `soulsApi: 1` in 0.26.0: its shape is unchanged.
|
|
1216
1243
|
The probe's `soulsApi` is **2** because it tracks the `oats inspect --json`
|
|
@@ -1261,9 +1288,13 @@ between preview and apply is `E_DECISION_STALE`):
|
|
|
1261
1288
|
key**: legal only at the top level of the workspace file's `messaging:`;
|
|
1262
1289
|
anywhere else in any payload layer (soul, `oats-local.yaml` `settings`,
|
|
1263
1290
|
`--provider`, at any depth) it is `E_WORKSPACE_SCHEMA { reason: "reserved-key", path, key }`.
|
|
1264
|
-
- Soul lookup: `E_SOUL_UNKNOWN { name, members[] }` (not among
|
|
1265
|
-
members or
|
|
1266
|
-
|
|
1291
|
+
- Soul lookup: `E_SOUL_UNKNOWN { name, members[], packages[] }` (not among
|
|
1292
|
+
confirmed members, externals or package souls), `E_SOUL_AMBIGUOUS { name,
|
|
1293
|
+
repos[], qualified[] }` (name one of `qualified`: `<member>/<soul>` or
|
|
1294
|
+
`<package>/<soul>`), `E_SOUL_DISABLED { name, qualifiedName, entry }` (the
|
|
1295
|
+
soul is in `oats-local.yaml` `souls.disabled`). A package soul whose fetched
|
|
1296
|
+
content does not match the lock is `E_PACKAGE_INTEGRITY { why:
|
|
1297
|
+
"soul-digest", package, soul, locked, observed }`. Resolution errors keep their codes (`E_NOT_A_MEMBER`,
|
|
1267
1298
|
`E_MEMBERSHIP_UNCONFIRMED { repoKey, reason }`, `E_CAPABILITY_MISSING { hint? }`,
|
|
1268
1299
|
`E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY { why: "capabilities", listed, locked }`,
|
|
1269
1300
|
`E_SLOT_CONFLICT { slot, modules[], reason? }`, `E_SKILL_DUPLICATE { name, modules[] }`,
|
|
@@ -1286,6 +1317,7 @@ Written by materialization inside the spawn transaction; read back by
|
|
|
1286
1317
|
"providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
|
|
1287
1318
|
"workspace":{"key":"github.com/acme/agents","name":"acme","deployment":"/Users/ana/acme-workspace","commit":"<oid>","resolution":"<24 hex>","standalone":false,
|
|
1288
1319
|
"soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
|
|
1320
|
+
"…":"a package soul's workspace.soul also records package: {id, version, commit, digest, path}, and its id is package:<id>#<soul>",
|
|
1289
1321
|
"capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
|
|
1290
1322
|
```
|
|
1291
1323
|
|
|
@@ -1341,6 +1373,60 @@ top-level `workspace` reachability field:
|
|
|
1341
1373
|
`oats-local.yaml` has no `workspace` field and `modules` as recorded.
|
|
1342
1374
|
- Text mode prints `modules: <cap> from <member|package …> @ <7-char>` lines
|
|
1343
1375
|
for non-current modules (`--verbose` for all).
|
|
1376
|
+
- `instances[].soul` is the soul source's drift: `{ repoKey, commit, current,
|
|
1377
|
+
status, reason? }`. For a **package soul** (feature `package-souls`) it also
|
|
1378
|
+
carries `package`, `version` and `currentVersion`; `moved` means the package
|
|
1379
|
+
pin moved (another version/commit is locked now), `missing` has `reason`
|
|
1380
|
+
`package-absent` (no longer locked or declared) or `soul-absent` (the locked
|
|
1381
|
+
package no longer ships it). Text: `soul: <name> from package <id> v<version>
|
|
1382
|
+
@ <7-char> [package moved since (now v<version> @ <7-char>)]`.
|
|
1383
|
+
|
|
1384
|
+
### Triggers (feature `triggers`, OATS 0.28.0) — `oats trigger … --json` → `triggerApi: 1`
|
|
1385
|
+
|
|
1386
|
+
Event-driven spawns of a deployment ([schedules.md#triggers](schedules.md#triggers)).
|
|
1387
|
+
Definitions live in `oats-schedules.json` (`kind: "trigger"`); `oats schedule list`
|
|
1388
|
+
does not show them.
|
|
1389
|
+
|
|
1390
|
+
```json
|
|
1391
|
+
{"triggerApi":1,"scope":"/abs/deployment","triggers":[
|
|
1392
|
+
{"id":"okf-harvest-review","enabled":true,"kind":"trigger",
|
|
1393
|
+
"on":{"source":"github.pull_request","repo":"github.com/acme/knowledge","events":["opened","reopened","ready_for_review"],"labels":["okf-harvest"],"base":"main","poll":"2m"},
|
|
1394
|
+
"spawn":{"soul":"oats.okf/knowledge-maintainer","purpose":"review-pr-{number}","task":"…","teams":["okf"],"harness":"claude","model":"opus"},
|
|
1395
|
+
"concurrency":{"max":2,"perKey":1},"template":{"package":"oats.okf","version":"4.0.0","commit":"<oid>","template":"harvest-review"},
|
|
1396
|
+
"triggerApi":1,"scope":"/abs/deployment","createdAt":"<iso>","updatedAt":"<iso>"}]}
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
- `list` → the document above; `show <id>`, `add`, `enable`, `disable` →
|
|
1400
|
+
`{ trigger }` (one row); a stored definition that no longer validates carries
|
|
1401
|
+
`invalid: { code, message }`. `remove <id>` → `{ removed, live: [instance] }`.
|
|
1402
|
+
- `status [<id>]` → `{ triggerApi, scope, triggers: [{ id, enabled, repo, soul,
|
|
1403
|
+
lastPoll: { at, ok, prs, matching } | { at, ok: false, error } | null,
|
|
1404
|
+
nextPollAt, pending: [{ key, event, number, url, observedAt }], fired: [{ key,
|
|
1405
|
+
at, instance, home, event, number }] (newest 50), firedTotal, live: [{
|
|
1406
|
+
instance, home, repo, number, event }], lastError: { at, code, message, key? } | null }] }`.
|
|
1407
|
+
- `test <id>` → `{ triggerApi, id, ok, gh: { ok, account, credentialSource:
|
|
1408
|
+
"keyring" | "config" | "env:<VAR>" | "unknown" | null, reachesHostTimer:
|
|
1409
|
+
boolean | null, note, detail }, repo: { key,
|
|
1410
|
+
readable, fullName, permissions: { push, maintain, admin }, canMerge } |
|
|
1411
|
+
{ key, readable: false, error }, soul: { resolves, name, agent, messaging } |
|
|
1412
|
+
{ resolves: false, name, error }, teams: { requested, undeclared | null,
|
|
1413
|
+
messaging }, wouldFire: [{ key, event, number, url, held? }], pollError?, problems:
|
|
1414
|
+
[string], warnings: [string], spawned: false }`. It writes nothing. `ok`
|
|
1415
|
+
counts `problems` only; a credential the host timer cannot reach
|
|
1416
|
+
(`reachesHostTimer: false`) is a warning.
|
|
1417
|
+
- `oats schedule list --json` gains `triggers: { count, command: "oats trigger
|
|
1418
|
+
list" }`: the triggers it does not list.
|
|
1419
|
+
- The tick's `considered[]` gains trigger rows `{ workspace, trigger, action,
|
|
1420
|
+
… }` with `action` `not-due`, `poll-failed`, `polled` (`prs`, `matching`;
|
|
1421
|
+
nothing to fire), `held`, `fired` (`key`,
|
|
1422
|
+
`instance`, `home`), `spawn-failed` (`key`, `code`, `error`), `would-fire`
|
|
1423
|
+
(`--dry-run`) or `invalid`.
|
|
1424
|
+
- A triggered instance's `instance.json.trigger` is `{ id, key, source, repo,
|
|
1425
|
+
number, url, event, headSha, observedAt, eventFile }`; the event file is
|
|
1426
|
+
`OATS_TRIGGER_EVENT_FILE`.
|
|
1427
|
+
- Errors: `E_TRIGGER_INVALID { field }`, `E_TRIGGER_EXISTS`,
|
|
1428
|
+
`E_TRIGGER_UNKNOWN`, `E_BAD_ARGS` (`missing` / `parameters` for a template),
|
|
1429
|
+
`E_PACKAGE_MISSING`, `E_PACKAGE_MANIFEST`, `E_LOCAL_MISSING`.
|
|
1344
1430
|
|
|
1345
1431
|
### Eligible teams (feature `teams`, OATS 0.26.0)
|
|
1346
1432
|
|
|
@@ -69,9 +69,10 @@
|
|
|
69
69
|
"additionalProperties": false,
|
|
70
70
|
"properties": {
|
|
71
71
|
"disabled": {
|
|
72
|
+
"description": "Souls not run on this machine (a spawn is E_SOUL_DISABLED): a bare soul name disables every soul of that name; a qualified name disables one — <package>/<soul> for a package soul, <member name>/<soul> for a member's.",
|
|
72
73
|
"type": "array",
|
|
73
74
|
"uniqueItems": true,
|
|
74
|
-
"items": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
|
|
75
|
+
"items": { "type": "string", "pattern": "^(?:[a-z0-9][a-z0-9._-]*/)?[a-z0-9]+(?:-[a-z0-9]+)*$" }
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -53,6 +53,45 @@
|
|
|
53
53
|
},
|
|
54
54
|
"uniqueItems": true
|
|
55
55
|
},
|
|
56
|
+
"souls": {
|
|
57
|
+
"description": "Package souls (OATS 0.28.0): package-relative soul directories, each an ordinary soul (soul.yaml + AGENTS.md, optional skills/). A soul's name is its directory's. They are versioned and locked with the package (oats-lock.json records each soul's name, path and digest), listed with their package origin, and spawned by the qualified name <package>/<soul> (or the bare name when unique). `from: here` in a package soul is its own package at the locked commit.",
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"minLength": 1,
|
|
62
|
+
"pattern": "^[^/].*$",
|
|
63
|
+
"not": {
|
|
64
|
+
"pattern": "(^|/)\\.\\.(/|$)"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"uniqueItems": true
|
|
68
|
+
},
|
|
69
|
+
"triggers": {
|
|
70
|
+
"description": "Trigger templates (OATS 0.28.0): `oats trigger add --from <package>:<id> [--set <name>=<value>]…` instantiates one from the locked package. Each file is { parameters: { <name>: { path, required?, default?, description? } }, definition: { …a trigger definition… } }; see docs/schedules.md#triggers.",
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"required": [
|
|
75
|
+
"id",
|
|
76
|
+
"file"
|
|
77
|
+
],
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"properties": {
|
|
80
|
+
"id": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
|
83
|
+
},
|
|
84
|
+
"file": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1,
|
|
87
|
+
"pattern": "^[^/].*$",
|
|
88
|
+
"not": {
|
|
89
|
+
"pattern": "(^|/)\\.\\.(/|$)"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
56
95
|
"configTemplates": {
|
|
57
96
|
"description": "Named config TEMPLATES: complete reference oats-config.yaml files of the classic line, whose adoption verb is removed under the workspace model. A template is a recommended starting point that becomes ordinary local policy on adoption — adopters may change every copied setting, and package updates never rewrite an adopted config. Templates must stay portable: no secret, credential, account, machine path or provider-local ID. Installation applies none of them. This is the canonical spelling; a manifest may not carry both this and the legacy `configs`.",
|
|
58
97
|
"type": "object",
|
package/docs/packages.md
CHANGED
|
@@ -27,7 +27,9 @@ A Git repository **contains** a package at `oats-package/`:
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
`oats-package.json` must declare `package` and `capabilities` (a list of
|
|
30
|
-
directories relative to the package root, each holding an `oats.json`).
|
|
30
|
+
directories relative to the package root, each holding an `oats.json`). It may
|
|
31
|
+
also declare `souls` (0.28.0): soul directories the package ships, see
|
|
32
|
+
[Package souls](#package-souls). A
|
|
31
33
|
directory entry need not equal the capability's name
|
|
32
34
|
(`capabilities/oats-okf` → capability `oats.okf`). A package declaring one
|
|
33
35
|
capability name twice, a listed directory without a manifest, or a manifest
|
|
@@ -73,7 +75,7 @@ members:
|
|
|
73
75
|
- git:github.com/acme/platform
|
|
74
76
|
packages:
|
|
75
77
|
oats.framework: v1.1.3
|
|
76
|
-
oats.okf:
|
|
78
|
+
oats.okf: v3.0.0
|
|
77
79
|
oats.aweb: v1.14.2
|
|
78
80
|
teams:
|
|
79
81
|
global: { description: Org-wide }
|
|
@@ -172,7 +174,8 @@ same workspace commit hold identical locks.
|
|
|
172
174
|
"version": "0.4.0",
|
|
173
175
|
"commit": "47f4b81660e4cc9701d373088de52462762585a3",
|
|
174
176
|
"integrity": "sha256-4cd126a7…",
|
|
175
|
-
"capabilities": ["acme-deploy", "acme-lint"]
|
|
177
|
+
"capabilities": ["acme-deploy", "acme-lint"],
|
|
178
|
+
"souls": [{ "name": "release-reviewer", "path": "souls/release-reviewer", "digest": "sha256-9a0f…" }]
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
}
|
|
@@ -187,6 +190,7 @@ same workspace commit hold identical locks.
|
|
|
187
190
|
| `commit` | full 40-hex OID the version resolved to |
|
|
188
191
|
| `integrity` | `sha256-<hex>` content digest of the package tree at `path` |
|
|
189
192
|
| `capabilities` | the capability names the package provides (sorted) — what `from: package` looks up |
|
|
193
|
+
| `souls` | the package souls (0.28.0), sorted by name: `name`, `path` (inside the package) and `digest` (`sha256-<hex>` of the soul directory); absent when the package ships none |
|
|
190
194
|
|
|
191
195
|
A capability provided by **two** locked packages is ambiguous and fails
|
|
192
196
|
closed (`E_PACKAGE_MISSING { ambiguous: [ids] }`): keep one of them in
|
|
@@ -220,6 +224,66 @@ against what the fetch reported; skills are copied to
|
|
|
220
224
|
`packages:` and syncing affects **only new spawns**; `oats status` shows a
|
|
221
225
|
running instance's package module as `moved` once the lock points elsewhere.
|
|
222
226
|
|
|
227
|
+
## Package souls
|
|
228
|
+
|
|
229
|
+
A package may ship **souls** as well as capabilities (0.28.0). One pin in
|
|
230
|
+
`packages:` then versions both: nothing drifts, unlike an `external:` soul's
|
|
231
|
+
commit pin.
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
oats-package/
|
|
235
|
+
├── oats-package.json # { …, "capabilities": ["capabilities/acme-review"], "souls": ["souls/release-reviewer"] }
|
|
236
|
+
├── capabilities/acme-review/oats.json
|
|
237
|
+
└── souls/release-reviewer/
|
|
238
|
+
├── soul.yaml # an ordinary soul: name = the directory's name
|
|
239
|
+
├── AGENTS.md
|
|
240
|
+
└── skills/…
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- **Locked.** `oats sync` records each soul's `name`, `path` and `digest` in
|
|
244
|
+
the lock entry. A soul without `soul.yaml` or `AGENTS.md`, or whose
|
|
245
|
+
directory is not a soul name, is `E_PACKAGE_MANIFEST`. On a later sync at
|
|
246
|
+
the same version the souls must still match (`E_PACKAGE_INTEGRITY { why:
|
|
247
|
+
"souls" }`); a lock written before 0.28.0 has its `souls` filled in.
|
|
248
|
+
- **Listed.** `oats souls` lists a package soul with `kind: "package"`,
|
|
249
|
+
`package`, `version`, `qualifiedName` and `origin: "package <id>
|
|
250
|
+
v<version>"`; `oats sync` / `oats workspace status` list each package's
|
|
251
|
+
`souls`. Only packages the workspace still declares are listed.
|
|
252
|
+
- **Named.** The qualified name is `<package>/<soul>` (`oats.okf/knowledge-maintainer`).
|
|
253
|
+
A bare name works when it is unique across member, external and package
|
|
254
|
+
souls; otherwise `E_SOUL_AMBIGUOUS` names each qualified form
|
|
255
|
+
(`details.qualified`). A member soul's qualified form is `<member name>/<soul>`.
|
|
256
|
+
- **Resolved** like any soul: the workspace and team defaults apply, `off` and
|
|
257
|
+
`<slot>: none` work, every `team:` label must be declared (`E_TEAM_UNKNOWN`
|
|
258
|
+
in discovery), and `from: here` means **this package** at the locked commit
|
|
259
|
+
(a capability it does not provide is `E_CAPABILITY_MISSING`).
|
|
260
|
+
- **Spawned** at the locked commit: the soul is fetched into the per-commit
|
|
261
|
+
soul cache and its digest must equal the lock's (`E_PACKAGE_INTEGRITY
|
|
262
|
+
{ why: "soul-digest" }`). A package soul homes in its own agent directory,
|
|
263
|
+
`agents/<package>--<soul>/` (the package id with `.` as `-`:
|
|
264
|
+
`agents/oats-okf--knowledge-maintainer/`), which is also its agent name
|
|
265
|
+
(`OATS_AGENT`, the `oats status` row); its instances are named from it
|
|
266
|
+
(`oats-okf-knowledge-maintainer-<purpose>`). A soul name never holds `--`,
|
|
267
|
+
so a member soul of the same bare name keeps its own `agents/<soul>/`.
|
|
268
|
+
Two packages whose ids sanitise alike (`a.b`, `a-b`) and that ship a
|
|
269
|
+
same-named soul would share a directory: both are listed with an
|
|
270
|
+
`E_SOUL_AMBIGUOUS` problem, and spawning either is `E_SOUL_AMBIGUOUS
|
|
271
|
+
{ agentDir, qualified }` — keep one of the packages.
|
|
272
|
+
`instance.json.workspace.soul` records `name`, `qualifiedName`, `package:
|
|
273
|
+
{ id, version, commit, digest, path }` and the soul id `package:<id>#<soul>`;
|
|
274
|
+
in `oats status` the soul is `moved` once the package pin moves.
|
|
275
|
+
- **Disabled** by `oats-local.yaml` `souls.disabled` by its qualified or bare
|
|
276
|
+
name (`E_SOUL_DISABLED` at spawn).
|
|
277
|
+
- **Trusted** as the package's capabilities are: declaring the package is the
|
|
278
|
+
trust decision.
|
|
279
|
+
|
|
280
|
+
## Trigger templates
|
|
281
|
+
|
|
282
|
+
A package may also ship **trigger templates** (0.28.0): `triggers: [{ id,
|
|
283
|
+
file }]` in `oats-package.json`, each file `{ parameters, definition }`.
|
|
284
|
+
`oats trigger add --from <package>:<id> --set <name>=<value>` instantiates one
|
|
285
|
+
at the locked commit; see [schedules.md#triggers](schedules.md#triggers).
|
|
286
|
+
|
|
223
287
|
## Compatibility floors
|
|
224
288
|
|
|
225
289
|
A soul may state floors on package versions — constraints, not sources:
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# OATS 0.28.0
|
|
2
|
+
|
|
3
|
+
Package souls, triggers, okf 3.0.0 remote knowledge consult, and the Workspace v4 Desktop.
|
|
4
|
+
|
|
5
|
+
## Added
|
|
6
|
+
|
|
7
|
+
- **Package souls** (feature `package-souls`). A package may ship souls as
|
|
8
|
+
well as capabilities: `souls: ["souls/<name>", …]` in
|
|
9
|
+
`oats-package/oats-package.json`, each an ordinary soul directory
|
|
10
|
+
(`soul.yaml`, `AGENTS.md`, `skills/`). One pin in the workspace's
|
|
11
|
+
`packages:` versions both, so a package soul never drifts the way an
|
|
12
|
+
`external:` commit pin can. See [packages.md](../packages.md#package-souls).
|
|
13
|
+
- `oats sync` locks each soul's `name`, `path` and `digest` in the package's
|
|
14
|
+
lock entry and checks them again on the next sync
|
|
15
|
+
(`E_PACKAGE_INTEGRITY { why: "souls" }`). A soul without `soul.yaml` or
|
|
16
|
+
`AGENTS.md` is `E_PACKAGE_MANIFEST`. A lock written by an earlier kernel
|
|
17
|
+
has its `souls` filled in on the next sync.
|
|
18
|
+
- `oats souls` lists them with `kind: "package"`, `package`, `version` and
|
|
19
|
+
`qualifiedName`; `oats sync` and `oats workspace status` list each
|
|
20
|
+
package's `souls`.
|
|
21
|
+
- Spawn one by its qualified name `<package>/<soul>`
|
|
22
|
+
(`oats spawn oats.okf/knowledge-maintainer`), or by its bare name when that
|
|
23
|
+
is unique. A bare name shared by several souls is `E_SOUL_AMBIGUOUS`, which
|
|
24
|
+
now names each qualified form (`details.qualified`); a member soul's is
|
|
25
|
+
`<member name>/<soul>`.
|
|
26
|
+
- A package soul resolves like any soul: workspace and team defaults apply,
|
|
27
|
+
`off` and `<slot>: none` work, its `team:` labels must be declared, and
|
|
28
|
+
`from: here` means its own package at the locked commit.
|
|
29
|
+
- The spawn fetches the soul at the locked commit and verifies it against the
|
|
30
|
+
lock's digest (`E_PACKAGE_INTEGRITY { why: "soul-digest" }`). It homes in
|
|
31
|
+
its own agent directory `agents/<package>--<soul>/`
|
|
32
|
+
(`agents/oats-okf--knowledge-maintainer/`), its agent name in `oats status`
|
|
33
|
+
and `OATS_AGENT`, so a member soul of the same bare name never shares its
|
|
34
|
+
directory or roster row. `instance.json.workspace.soul` records `name`,
|
|
35
|
+
`qualifiedName` and `package: { id, version, commit, digest, path }`, and
|
|
36
|
+
the soul id is `package:<id>#<soul>`. `oats status` shows the soul as
|
|
37
|
+
`moved` once the package pin moves. Two packages whose ids differ only by
|
|
38
|
+
`.` and `-` (`a.b`, `a-b`) and that ship a same-named soul would share an
|
|
39
|
+
agent directory: that is an `E_SOUL_AMBIGUOUS` problem in `oats sync` and
|
|
40
|
+
`oats souls`, and neither spawns.
|
|
41
|
+
|
|
42
|
+
- **Triggers** (feature `triggers`): event-driven spawns. A trigger says
|
|
43
|
+
"when a GitHub pull request event matches, spawn a new instance of this soul
|
|
44
|
+
with this task, in these teams". It is stored beside the schedules
|
|
45
|
+
(`kind: "trigger"` in `oats-schedules.json`) and evaluated by the same host
|
|
46
|
+
tick; there is no daemon or webhook. See
|
|
47
|
+
[schedules.md#triggers](../schedules.md#triggers).
|
|
48
|
+
- The source `github.pull_request` is polled with the host's `gh` at the
|
|
49
|
+
trigger's `poll` interval (at least 1m). Events: `opened`, `reopened`,
|
|
50
|
+
`ready_for_review`, `labeled`, `synchronize`, filtered by `labels` and
|
|
51
|
+
`base`.
|
|
52
|
+
- Each event fires once: its key is recorded only after a successful spawn,
|
|
53
|
+
so a failed spawn is retried on the next poll. Delivery is at least once: a
|
|
54
|
+
tick that dies between the spawn and recording its key spawns the event
|
|
55
|
+
again (held while the first instance is live, under the default `perKey: 1`). `concurrency.max` and
|
|
56
|
+
`perKey` bound the live instances of the trigger and of one PR.
|
|
57
|
+
- The purpose and task are templated from `{repo} {number} {url} {event}
|
|
58
|
+
{headSha} {trigger}` only; a PR's title and body never reach the task.
|
|
59
|
+
`spawn.teams` becomes the messaging capability's `join=`. The instance gets
|
|
60
|
+
the event as `OATS_TRIGGER_EVENT_FILE` (`<home>/.oats/trigger-event.json`)
|
|
61
|
+
and records `instance.json.trigger`.
|
|
62
|
+
- `oats trigger add (--file | --from <package>:<template> --set …) | list |
|
|
63
|
+
show | enable | disable | remove | test | status`, all with `--json`.
|
|
64
|
+
`oats trigger test` checks gh auth and where its credential comes from
|
|
65
|
+
(warning when the host timer cannot reach it, such as a shell-only
|
|
66
|
+
`GH_TOKEN`), the repository and your push/maintain/admin permissions on it,
|
|
67
|
+
the soul, the teams, and what would fire now, and spawns nothing.
|
|
68
|
+
`oats schedule list` counts the triggers it does not list.
|
|
69
|
+
- A package may ship trigger templates (`triggers: [{ id, file }]` in
|
|
70
|
+
`oats-package.json`).
|
|
71
|
+
- `oats spawn --trigger-event <file>` is how a trigger hands the event to the
|
|
72
|
+
spawn.
|
|
73
|
+
|
|
74
|
+
## Fixed
|
|
75
|
+
|
|
76
|
+
- **`--flag=value` is read.** Every kernel command ignored the inline form
|
|
77
|
+
silently: `oats spawn dev --harness=claude` spawned the default harness,
|
|
78
|
+
and `--name=x`, `--runtime=x`, `--dir=x` and `--server=x` were dropped the
|
|
79
|
+
same way. A kernel command now reads `--flag=value` exactly as
|
|
80
|
+
`--flag value`, with the same validation. The value is everything after the
|
|
81
|
+
first `=`. Two inline forms are refused with `E_BAD_ARGS`: an empty
|
|
82
|
+
`--flag=`, and a value on a switch. `--yolo=false` is refused and never
|
|
83
|
+
turns yolo on. A capability command's own flags are its provider's: they
|
|
84
|
+
are forwarded exactly as typed, and the kernel reads only its dispatch flag
|
|
85
|
+
(`--soul`) in either form. `oats capture`, `recall` and `setup` parse their
|
|
86
|
+
own arguments, as before. See
|
|
87
|
+
[desktop-cli-api.md § Flags](../desktop-cli-api.md#flags).
|
|
88
|
+
**On an older kernel:** use the spaced form.
|
|
89
|
+
|
|
90
|
+
- **`oats-local.yaml` `souls.disabled` is enforced at spawn.** It was documented
|
|
91
|
+
as "not run on this machine", but only counted souls in the `oats sync`
|
|
92
|
+
report. A listed soul is now refused with `E_SOUL_DISABLED { name,
|
|
93
|
+
qualifiedName, entry }`. An entry is a bare name (every soul of that name) or
|
|
94
|
+
a qualified name (`oats.okf/knowledge-harvester`, `<member>/<soul>`).
|
|
95
|
+
|
|
96
|
+
## oats.okf 3.0.0 (catalog pin and bundled mirror)
|
|
97
|
+
|
|
98
|
+
The official catalog now pins `oats.okf` to `v3.0.0` (tag object `ad2349c7`,
|
|
99
|
+
commit `76f7ccdb`). The copy bundled in this package is its byte mirror. The
|
|
100
|
+
package declares `oats >=0.26.0`.
|
|
101
|
+
|
|
102
|
+
- **Knowledge is consulted remotely.** An instance reads its soul's knowledge
|
|
103
|
+
at the accepted state through new commands: `oats okf bases`, `index`,
|
|
104
|
+
`cat --base <alias> <path>`, `ls`, `links` and `search`. A Git base is served
|
|
105
|
+
from one host-wide partial clone per base, and a read refetches the accepted
|
|
106
|
+
branch once the cached commit is older than the `consult-max-age` setting
|
|
107
|
+
(seconds; default 300; `0` refetches on every read). `--fresh` always
|
|
108
|
+
refetches. If the fetch fails, the read is served from the cache, with
|
|
109
|
+
`stale: true` and the reason in its receipt.
|
|
110
|
+
A directory base is read in place. The new `okf-consultation` skill and the
|
|
111
|
+
okf injection teach instances to run `oats okf index` at the start of every
|
|
112
|
+
task.
|
|
113
|
+
- **BREAKING: no `./knowledge/` in homes.** A spawn records the accepted
|
|
114
|
+
resolution and materializes no `./knowledge/` directory or view. Anything
|
|
115
|
+
that read `<home>/knowledge/bases/<alias>/…` reads
|
|
116
|
+
`oats okf cat --base <alias> <path>` instead.
|
|
117
|
+
- **BREAKING: `oats okf refresh` is `E_REMOVED`.** There are no per-instance
|
|
118
|
+
views to refresh.
|
|
119
|
+
- **`oats okf read --path` still works** in 3.0.0, as an alias of `cat`.
|
|
120
|
+
- **Older homes:** a `./knowledge/` a 2.x spawn left in a home is not touched.
|
|
121
|
+
3.0.0 ignores it, and `oats okf inspect` reports it as
|
|
122
|
+
`legacy-local-view`.
|
|
123
|
+
- The memory-harvest worker soul no longer ships a `CLAUDE.md -> AGENTS.md`
|
|
124
|
+
symlink (npm drops symlinks, and the kernel composes each home's
|
|
125
|
+
`CLAUDE.md`). The mirror check (`scripts/check-okf-mirror.mjs`) no longer
|
|
126
|
+
requires one; any symlink a future release ships is mirrored and verified
|
|
127
|
+
as before.
|
|
128
|
+
|
|
129
|
+
## Desktop
|
|
130
|
+
|
|
131
|
+
- **Workspace v4 redesign** (#206): the Workspace area (Setup, Souls, Capabilities, the soul and capability pages) is rebuilt to the Workspace v4 design. Facts the kernel does not report yet are left out rather than guessed; the Setup screen no longer names kernel files.
|
|
132
|
+
- **Soul team labels** reach the Souls grid (#208).
|
|
133
|
+
|
|
134
|
+
## Tests and CI
|
|
135
|
+
|
|
136
|
+
- A home's resolved view (`resolvedFromHome`) carrying the slot rows its
|
|
137
|
+
spawn recorded (`layers`, since 0.26.0 layers-from) is now pinned. Before,
|
|
138
|
+
no test caught reverting it to `{}`.
|
|
139
|
+
- CI runs the suite in six parallel shards (`node --test --test-shard`), plus a gate job with the old check name. Main runs no longer cancel each other.
|
|
140
|
+
|
|
141
|
+
## Upgrading
|
|
142
|
+
|
|
143
|
+
- **okf 3.0.0 is breaking for anything that read `<home>/knowledge/`.** Use `oats okf cat`. Existing homes keep their old view, unused.
|
|
144
|
+
- **Package souls and triggers are additive.** Nothing changes until a package declares `souls:`/`triggers:`, or you add a trigger.
|
package/docs/schedules.md
CHANGED
|
@@ -10,7 +10,7 @@ is `E_LOCAL_MISSING`. Scheduled spawns materialize exactly like `oats spawn`.
|
|
|
10
10
|
Execution belongs to the host that holds the scope, so a schedule on a
|
|
11
11
|
registered server keeps running while your laptop sleeps.
|
|
12
12
|
|
|
13
|
-
There is no daemon. One host timer (a launchd user agent on macOS, a systemd
|
|
13
|
+
[Triggers](#triggers) are evaluated by the same tick. There is no daemon. One host timer (a launchd user agent on macOS, a systemd
|
|
14
14
|
user timer on Linux) runs `oats schedule tick --host` once a minute; the tick
|
|
15
15
|
is a short-lived process that evaluates only the current minute, launches
|
|
16
16
|
what is due through the same `spawn`, `session start` and `session input`
|
|
@@ -89,6 +89,104 @@ required IANA zone; both are evaluated by the croner library. `--wake-every
|
|
|
89
89
|
N` at spawn time means `*/N * * * *`: every 7 fires at :00, :07, ... :56 and
|
|
90
90
|
then :00 again, so 1, 5, 10, 15 and 30 give an even cadence.
|
|
91
91
|
|
|
92
|
+
## Triggers
|
|
93
|
+
|
|
94
|
+
A **trigger** (OATS 0.28.0, feature `triggers`) is an event-driven schedule:
|
|
95
|
+
"when EVENT matches, spawn a NEW instance of SOUL with TASK, in TEAMS". It is
|
|
96
|
+
stored in the same `oats-schedules.json` as a job of `kind: "trigger"`,
|
|
97
|
+
managed with `oats trigger …` (never `oats schedule …`, which neither lists nor
|
|
98
|
+
edits one), and evaluated by the same host tick (`oats schedule tick --host`,
|
|
99
|
+
and `oats schedule tick` for one scope). There is no daemon and no webhook: it
|
|
100
|
+
runs only on the host that holds the scope, with **that host's own
|
|
101
|
+
credentials**; a definition carries none.
|
|
102
|
+
|
|
103
|
+
**Credentials reach the tick through the host timer, not your shell.** The
|
|
104
|
+
timer (a user LaunchAgent on macOS, a `systemd --user` unit on Linux) runs the
|
|
105
|
+
tick with its own environment, which sets only `PATH` and `OATS_HOME_DIR`. `gh`
|
|
106
|
+
logged in with the keyring or its config file under your HOME works there. A
|
|
107
|
+
`GH_TOKEN` or `GITHUB_TOKEN` exported in your shell does not reach it.
|
|
108
|
+
`oats trigger test` reports where `gh`'s credential comes from (`gh.credentialSource`:
|
|
109
|
+
`keyring`, `config`, `env:<VAR>`) and warns when the timer cannot reach it.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "id": "okf-harvest-review", "enabled": true, "kind": "trigger",
|
|
113
|
+
"on": { "source": "github.pull_request", "repo": "github.com/acme/knowledge",
|
|
114
|
+
"events": ["opened", "reopened", "ready_for_review"],
|
|
115
|
+
"labels": ["okf-harvest"], "base": "main", "poll": "2m" },
|
|
116
|
+
"spawn": { "soul": "oats.okf/knowledge-maintainer", "purpose": "review-pr-{number}",
|
|
117
|
+
"task": "Review knowledge-base PR {repo}#{number}. Load knowledge-review first.",
|
|
118
|
+
"teams": ["okf"], "harness": "claude", "model": "opus" },
|
|
119
|
+
"concurrency": { "max": 2, "perKey": 1 } }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **Source** `github.pull_request` (the only one in v1): the tick polls the
|
|
123
|
+
repository's open pull requests with the host's `gh` (`gh api repos/<owner>/<repo>/pulls`,
|
|
124
|
+
`state=open`, most recently updated first) every `poll` (default `2m`, at
|
|
125
|
+
least `1m`). `labels` (all must be present) and `base` filter them. A repo is
|
|
126
|
+
`github.com/<owner>/<repo>`; another host is passed to `gh` as `--hostname`.
|
|
127
|
+
- **Events** are inferred poll over poll: `opened` (a PR first seen, not a
|
|
128
|
+
draft; the first poll sees every open PR), `reopened` (seen closed, open
|
|
129
|
+
again), `ready_for_review` (was a draft), `labeled` (now carries the filter
|
|
130
|
+
labels it lacked; without a filter, any new label), `synchronize` (a new
|
|
131
|
+
head commit).
|
|
132
|
+
- **Dedup and retry.** Each event has a key
|
|
133
|
+
`<trigger>:<repo>#<number>:<event>:<stamp>` (`created_at` for `opened`, the
|
|
134
|
+
head SHA for `synchronize`, `updated_at` otherwise). A key is recorded as
|
|
135
|
+
fired **only after a successful spawn**; until then the event stays pending
|
|
136
|
+
and is retried at every poll, and dropped when its PR closes.
|
|
137
|
+
- **At least once, not exactly once.** The fired key is written after the
|
|
138
|
+
spawn returns. If the tick dies in between (a crash, a kill, the host going
|
|
139
|
+
down), the spawned instance exists but the key does not, and the next poll
|
|
140
|
+
spawns the event again. Concurrency still applies to that retry: with the
|
|
141
|
+
default `perKey: 1` the first instance is live, so the event is `held` rather
|
|
142
|
+
than spawned twice, and it fires once that instance retires. A trigger's soul
|
|
143
|
+
should therefore tolerate a second run on the same PR event (a review that
|
|
144
|
+
finds its own earlier review, for example).
|
|
145
|
+
- **Concurrency.** `max` (default 1) bounds the live instances of the trigger,
|
|
146
|
+
`perKey` (default 1) those of one PR; both are counted from the homes'
|
|
147
|
+
`instance.json.trigger` records, so a retired instance frees its slot. An
|
|
148
|
+
event over the bound stays pending (`held`).
|
|
149
|
+
- **The spawn** is `oats spawn` (the same path as a scheduled spawn). `soul` is
|
|
150
|
+
bare or qualified (`<package>/<soul>`). `purpose` (default
|
|
151
|
+
`{trigger}-{number}`, must render to a slug) and `task` are templated from
|
|
152
|
+
**only** `{repo} {number} {url} {event} {headSha} {trigger}`: a pull
|
|
153
|
+
request's title and body are untrusted and never reach the task (a template
|
|
154
|
+
naming any other field is refused). `teams` becomes the messaging
|
|
155
|
+
capability's `join=` setting (as `--provider <messaging cap> join=<labels>`).
|
|
156
|
+
`harness`, `model`, `yolo`, `backend` are as for schedules.
|
|
157
|
+
- **The event reaches the instance** as `OATS_TRIGGER_EVENT_FILE`
|
|
158
|
+
(`<home>/.oats/trigger-event.json`: `{ trigger, source, repo, number, url,
|
|
159
|
+
event, headSha, labels, observedAt, key }`), given to the spawn hooks and the
|
|
160
|
+
harness; `instance.json.trigger` records `{ id, key, source, repo, number,
|
|
161
|
+
url, event, headSha, observedAt, eventFile }`. The task ends with a short
|
|
162
|
+
block naming the event file.
|
|
163
|
+
- **State** lives in `<scope>/.agents/schedules/triggers.json` (last poll, the
|
|
164
|
+
PRs seen, pending events, fired keys, the last error).
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
oats trigger add --file trigger.json # or:
|
|
168
|
+
oats trigger add --from oats.okf:harvest-review --set repo=github.com/acme/knowledge [--id <id>]
|
|
169
|
+
oats trigger list | show <id> | enable <id> | disable <id> | remove <id>
|
|
170
|
+
oats trigger test <id> # dry run: gh auth + credential source, repo + permissions (push/maintain/admin), the soul resolves,
|
|
171
|
+
# its messaging capability, the teams declared, what WOULD fire now; spawns nothing
|
|
172
|
+
oats trigger status [<id>] # last poll, pending, fired keys, live instances, last error
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
All take `--dir` and `--json` (`triggerApi: 1`). `remove` leaves the instances
|
|
176
|
+
it spawned running. `oats schedule list` does not list triggers, but it counts
|
|
177
|
+
them (`triggers: { count, command: "oats trigger list" }`, and a line in text
|
|
178
|
+
mode). Errors: `E_TRIGGER_INVALID { field }`, `E_TRIGGER_EXISTS`,
|
|
179
|
+
`E_TRIGGER_UNKNOWN`, `E_BAD_ARGS`.
|
|
180
|
+
|
|
181
|
+
**Package trigger templates.** A package may declare `triggers: [{ id, file }]`
|
|
182
|
+
in `oats-package.json`. Each file is `{ parameters: { <name>: { path,
|
|
183
|
+
required?, default?, description? } }, definition: { …a trigger… } }`.
|
|
184
|
+
`oats trigger add --from <package>:<id>` reads it at the locked commit;
|
|
185
|
+
`--set <name>=<value>` fills a parameter at its dotted `path` (a list value is
|
|
186
|
+
comma-separated); a required parameter without a value is `E_BAD_ARGS
|
|
187
|
+
{ missing }` naming it. The trigger records `template: { package, version,
|
|
188
|
+
commit, template }`.
|
|
189
|
+
|
|
92
190
|
## Captured definitions (removed in 0.26)
|
|
93
191
|
|
|
94
192
|
0.24–0.25 could save captured command definitions: `definitionVersion`,
|
|
@@ -196,9 +196,13 @@ refused (`E_INSTANCE_NAME_TAKEN`).
|
|
|
196
196
|
|
|
197
197
|
From a deployment (where `oats-local.yaml` is), a spawn: reads the local file →
|
|
198
198
|
discovers the workspace over its remotes and confirms membership → finds the
|
|
199
|
-
soul among the confirmed members
|
|
200
|
-
|
|
201
|
-
`<
|
|
199
|
+
soul among the confirmed members, `external:` souls and the locked packages'
|
|
200
|
+
souls (an ambiguous bare name is `E_SOUL_AMBIGUOUS`, naming each qualified
|
|
201
|
+
form: `<member>/<soul>` or `<package>/<soul>`; a soul listed in
|
|
202
|
+
`oats-local.yaml` `souls.disabled` is `E_SOUL_DISABLED`) → fetches the soul's
|
|
203
|
+
source into `<agents-root>/<soul>/souls/<commit12>/` at its commit (a package
|
|
204
|
+
soul at the locked commit, verified against the lock's digest — see
|
|
205
|
+
[package souls](packages.md#package-souls); the home links that
|
|
202
206
|
directory; `<agents-root>/<soul>/soul` points at the current one) → resolves
|
|
203
207
|
every capability by
|
|
204
208
|
`from:` (member = latest, package = locked) → creates the home →
|
package/docs/workspaces.md
CHANGED
|
@@ -60,7 +60,7 @@ members: # repo refs, NO @revision (E_WORKSPAC
|
|
|
60
60
|
|
|
61
61
|
packages: # the ONLY versioned things
|
|
62
62
|
oats.framework: v1.1.3 # bare version → resolves through the official catalog
|
|
63
|
-
oats.okf:
|
|
63
|
+
oats.okf: v3.0.0
|
|
64
64
|
acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
|
|
65
65
|
|
|
66
66
|
teams: # labels, declared once so they cannot drift
|
|
@@ -168,7 +168,7 @@ settings: # host-owned values the manifests ask
|
|
|
168
168
|
bindings-file: /Users/ana/.oats/okf-bindings.json
|
|
169
169
|
state-dir: /Users/ana/.oats/okf
|
|
170
170
|
souls:
|
|
171
|
-
disabled: [data-analyst] # not run on this machine
|
|
171
|
+
disabled: [data-analyst] # not run on this machine (E_SOUL_DISABLED); oats.okf/knowledge-harvester names a package soul
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
See [configuration.md](configuration.md). `oats-config.yaml` no longer exists.
|
|
@@ -230,6 +230,12 @@ read; the soul gets no member-tier capabilities of its own repo; it is
|
|
|
230
230
|
"source-complete" (its skills travel with it) and the workspace's defaults fill
|
|
231
231
|
its slots. An `external[].team` overrides the soul's own `team`.
|
|
232
232
|
|
|
233
|
+
**Package souls.** A package may ship souls (`souls:` in `oats-package.json`,
|
|
234
|
+
0.28.0): they are listed from the lock for each package the workspace declares,
|
|
235
|
+
named `<package>/<soul>` (a bare name when unique), resolved like any soul
|
|
236
|
+
(`from: here` = their own package at the locked commit) and trusted as the
|
|
237
|
+
package is. See [packages](packages.md#package-souls).
|
|
238
|
+
|
|
233
239
|
## Member tier vs package tier — the non-collapse rule
|
|
234
240
|
|
|
235
241
|
A repository may be a **member** (it completed the handshake; its `souls/*` and
|