@awebai/oats 0.22.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/bin/oats.mjs +3294 -0
  4. package/capabilities/oats-authoring/oats.json +12 -0
  5. package/capabilities/oats-aweb/bin/oats-aweb.mjs +297 -0
  6. package/capabilities/oats-aweb/injects/aweb.md +55 -0
  7. package/capabilities/oats-aweb/oats.json +47 -0
  8. package/capabilities/oats-aweb/package.json +20 -0
  9. package/capabilities/oats-jira/bin/oats-jira.mjs +40 -0
  10. package/capabilities/oats-jira/injects/jira.md +10 -0
  11. package/capabilities/oats-jira/oats.json +22 -0
  12. package/capabilities/oats-jira/package.json +25 -0
  13. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +176 -0
  14. package/capabilities/oats-linear/README.md +234 -0
  15. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +32 -0
  16. package/capabilities/oats-linear/bin/oats-linear.mjs +344 -0
  17. package/capabilities/oats-linear/injects/linear.md +8 -0
  18. package/capabilities/oats-linear/oats.json +24 -0
  19. package/capabilities/oats-linear/package.json +29 -0
  20. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +217 -0
  21. package/capabilities/oats-linear/test/oats-linear.test.mjs +168 -0
  22. package/capabilities/oats-okf/agents/memory-harvest.md +25 -0
  23. package/capabilities/oats-okf/bin/oats-okf.mjs +283 -0
  24. package/capabilities/oats-okf/injects/okf.md +62 -0
  25. package/capabilities/oats-okf/oats.json +20 -0
  26. package/capabilities/oats-okf/package.json +22 -0
  27. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +100 -0
  28. package/capabilities/oats-okf/skills/okf/SKILL.md +107 -0
  29. package/capabilities/oats-okf/skills/okf/scripts/okf-validate.mjs +123 -0
  30. package/capabilities/oats-review/agents/reviewer/AGENTS.md +53 -0
  31. package/capabilities/oats-review/agents/reviewer/soul.yaml +6 -0
  32. package/capabilities/oats-review/injects/review.md +68 -0
  33. package/capabilities/oats-review/oats.json +10 -0
  34. package/capabilities/oats-review/skills/code-review/SKILL.md +44 -0
  35. package/capabilities/oats-review/skills/security-review/SKILL.md +59 -0
  36. package/docs/capabilities.md +505 -0
  37. package/docs/capability-manifest.schema.json +223 -0
  38. package/docs/configuration.md +482 -0
  39. package/docs/conventions.md +73 -0
  40. package/docs/design/desktop-ux-plan.md +362 -0
  41. package/docs/design/package-engine-contract.md +813 -0
  42. package/docs/design/package-runtime-api.md +414 -0
  43. package/docs/desktop-cli-api.md +89 -0
  44. package/docs/desktop-succession.md +51 -0
  45. package/docs/desktop.md +187 -0
  46. package/docs/implementation.md +282 -0
  47. package/docs/integrations.md +123 -0
  48. package/docs/knowledge-theory.md +97 -0
  49. package/docs/knowledge.md +139 -0
  50. package/docs/layers.md +110 -0
  51. package/docs/migration-from-oas.md +122 -0
  52. package/docs/oats-config.schema.json +140 -0
  53. package/docs/oats-lock.schema.json +175 -0
  54. package/docs/oats-package.schema.json +129 -0
  55. package/docs/packages.md +442 -0
  56. package/docs/release-lane.md +132 -0
  57. package/docs/release-notes/v0.18.2.md +79 -0
  58. package/docs/release-notes/v0.18.3.md +63 -0
  59. package/docs/release-notes/v0.18.4.md +65 -0
  60. package/docs/release-notes/v0.18.5.md +66 -0
  61. package/docs/release-notes/v0.18.6.md +87 -0
  62. package/docs/release-notes/v0.19.0.md +186 -0
  63. package/docs/release-notes/v0.19.1.md +24 -0
  64. package/docs/release-notes/v0.19.2.md +32 -0
  65. package/docs/release-notes/v0.19.3.md +16 -0
  66. package/docs/release-notes/v0.19.4.md +12 -0
  67. package/docs/release-notes/v0.20.0.md +89 -0
  68. package/docs/release-notes/v0.22.0.md +180 -0
  69. package/docs/souls-and-instances.md +344 -0
  70. package/injects/framework-workspace.md +7 -0
  71. package/injects/instance-boundary.md +43 -0
  72. package/injects/local-soul.md +19 -0
  73. package/injects/oats.md +11 -0
  74. package/injects/work-attached.md +16 -0
  75. package/injects/work-checkout.md +12 -0
  76. package/injects/work-workspace.md +22 -0
  77. package/injects/work-worktree.md +13 -0
  78. package/lib/core.mjs +6792 -0
  79. package/lib/packages.mjs +1375 -0
  80. package/lib/tmux-config.mjs +35 -0
  81. package/package-catalog.json +44 -0
  82. package/package.json +55 -0
  83. package/packages/record/README.md +151 -0
  84. package/packages/record/bin/capture.mjs +256 -0
  85. package/packages/record/bin/recall.mjs +102 -0
  86. package/packages/record/bin/setup.mjs +281 -0
  87. package/packages/record/bin/turn-record.mjs +47 -0
  88. package/packages/record/docs/turn-record-sot.md +350 -0
  89. package/packages/record/lib/canonical.mjs +151 -0
  90. package/packages/record/lib/capture-aw.mjs +179 -0
  91. package/packages/record/lib/capture-cc.mjs +322 -0
  92. package/packages/record/lib/formats.mjs +296 -0
  93. package/packages/record/lib/ignore.mjs +155 -0
  94. package/packages/record/lib/index-db.mjs +524 -0
  95. package/packages/record/lib/project-aweb.mjs +251 -0
  96. package/packages/record/lib/segments.mjs +186 -0
  97. package/packages/record/lib/store.mjs +447 -0
  98. package/packages/record/lib/tags.mjs +114 -0
  99. package/packages/record/package.json +45 -0
  100. package/packages/record/test/vectors/README.md +34 -0
  101. package/packages/record/test/vectors/aweb-projection-v1.json +231 -0
  102. package/packages/record/test/vectors/journal-merge-v1.json +261 -0
  103. package/packages/record/test/vectors/turn-id-v1.json +152 -0
  104. package/packages/record/test/vectors/validate.mjs +391 -0
  105. package/skills/integration-authoring/SKILL.md +76 -0
  106. package/skills/oats/SKILL.md +151 -0
  107. package/skills/oats-config/SKILL.md +162 -0
  108. package/skills/oats-getting-started/SKILL.md +159 -0
  109. package/skills/oats-packages/SKILL.md +180 -0
  110. package/skills/oats-support/SKILL.md +79 -0
  111. package/skills/skill-craft/SKILL.md +109 -0
  112. package/skills/soul-craft/SKILL.md +109 -0
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ // Turn-record conformance validator (docs/turn-record-sot.md).
3
+ //
4
+ // Dependency-free, Node >= 18. Independent re-implementation of canonical
5
+ // JSON, turn ids, did:key Ed25519 verification, the aweb projection, and the
6
+ // merge rules; the vectors are generated by the Python awid stack, so a green
7
+ // run is a two-language agreement on every byte.
8
+
9
+ import { createHash, createPublicKey, verify as edVerify } from "node:crypto";
10
+ import { readFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const HERE = dirname(fileURLToPath(import.meta.url));
15
+
16
+ // ---------------------------------------------------------- canonical JSON
17
+
18
+ class CoreNumberError extends Error {}
19
+
20
+ function canon(v) {
21
+ if (v === null) return "null";
22
+ const t = typeof v;
23
+ if (t === "string") return JSON.stringify(v);
24
+ if (t === "boolean") return v ? "true" : "false";
25
+ if (t === "number") {
26
+ if (!Number.isInteger(v) || Math.abs(v) > Number.MAX_SAFE_INTEGER) {
27
+ throw new CoreNumberError(`non-integer number in canonical core: ${v}`);
28
+ }
29
+ return String(v);
30
+ }
31
+ if (Array.isArray(v)) return "[" + v.map(canon).join(",") + "]";
32
+ if (t === "object") {
33
+ const keys = Object.keys(v).sort();
34
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}";
35
+ }
36
+ throw new Error(`unsupported value in canonical core: ${t}`);
37
+ }
38
+
39
+ function coreString(turn) {
40
+ const core = {};
41
+ for (const k of Object.keys(turn)) if (k !== "id" && k !== "sig") core[k] = turn[k];
42
+ return canon(core);
43
+ }
44
+
45
+ function turnId(turn) {
46
+ return "t1:" + createHash("sha256").update(coreString(turn), "utf8").digest("hex");
47
+ }
48
+
49
+ // ------------------------------------------------------- did:key + ed25519
50
+
51
+ const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
52
+
53
+ function b58decode(s) {
54
+ let n = 0n;
55
+ for (const c of s) {
56
+ const i = B58.indexOf(c);
57
+ if (i < 0) throw new Error(`invalid base58 char ${c}`);
58
+ n = n * 58n + BigInt(i);
59
+ }
60
+ const bytes = [];
61
+ while (n > 0n) {
62
+ bytes.push(Number(n & 0xffn));
63
+ n >>= 8n;
64
+ }
65
+ bytes.reverse();
66
+ for (const c of s) {
67
+ if (c === "1") bytes.unshift(0);
68
+ else break;
69
+ }
70
+ return Uint8Array.from(bytes);
71
+ }
72
+
73
+ function publicKeyFromDid(did) {
74
+ if (!did.startsWith("did:key:z")) throw new Error(`unsupported did ${did}`);
75
+ const decoded = b58decode(did.slice("did:key:z".length));
76
+ if (decoded[0] !== 0xed || decoded[1] !== 0x01 || decoded.length !== 34) {
77
+ throw new Error(`not an ed25519 did:key: ${did}`);
78
+ }
79
+ const SPKI = Buffer.from("302a300506032b6570032100", "hex");
80
+ return createPublicKey({
81
+ key: Buffer.concat([SPKI, Buffer.from(decoded.subarray(2))]),
82
+ format: "der",
83
+ type: "spki",
84
+ });
85
+ }
86
+
87
+ function b64decode(s) {
88
+ const normalized = s.replaceAll("-", "+").replaceAll("_", "/");
89
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
90
+ return Buffer.from(padded, "base64");
91
+ }
92
+
93
+ function verifySig(did, payloadUtf8, sigB64) {
94
+ return edVerify(null, Buffer.from(payloadUtf8, "utf8"), publicKeyFromDid(did), b64decode(sigB64));
95
+ }
96
+
97
+ // ------------------------------------------------------------- aweb project
98
+
99
+ function projectSigned(row, kind) {
100
+ const p = JSON.parse(row.signed_payload);
101
+ return {
102
+ v: 1,
103
+ ts: p.timestamp,
104
+ from: p.from,
105
+ to: p.to,
106
+ thread: "aweb:conv:" + row.conversation_id,
107
+ kind,
108
+ body: { subject: p.subject, text: p.body },
109
+ signature: row.signature,
110
+ signed_payload: row.signed_payload,
111
+ provenance: {
112
+ source: kind === "mail" ? "aweb-mail" : "aweb-chat",
113
+ fidelity: "projected",
114
+ origin: { message_id: row.message_id, conversation_id: row.conversation_id },
115
+ },
116
+ };
117
+ }
118
+
119
+ function projectLegacy(row) {
120
+ const origin = {
121
+ message_id: row.message_id,
122
+ conversation_id: row.conversation_id,
123
+ };
124
+ for (const k of ["from_did", "from_stable_id", "to_did", "to_stable_id", "priority"]) {
125
+ if (row[k] !== undefined) origin[k] = row[k];
126
+ }
127
+ origin.timestamp_source = "created_at";
128
+ return {
129
+ v: 1,
130
+ ts: row.created_at,
131
+ from: row.from,
132
+ to: row.to,
133
+ thread: "aweb:conv:" + row.conversation_id,
134
+ kind: "mail",
135
+ body: { subject: row.subject, text: row.body },
136
+ provenance: { source: "aweb-mail", fidelity: "projected", origin },
137
+ };
138
+ }
139
+
140
+ function checkSignedConsistency(turn) {
141
+ const p = JSON.parse(turn.signed_payload);
142
+ const pairs = [
143
+ [turn.ts, p.timestamp, "ts/timestamp"],
144
+ [turn.from, p.from, "from"],
145
+ [turn.to, p.to, "to"],
146
+ [turn.body.subject, p.subject, "body.subject/subject"],
147
+ [turn.body.text, p.body, "body.text/body"],
148
+ ];
149
+ for (const [a, b, what] of pairs) {
150
+ if (a !== b) throw new Error(`signed consistency violation on ${what}`);
151
+ }
152
+ }
153
+
154
+ function unproject(turn) {
155
+ const out = {};
156
+ if (turn.signed_payload !== undefined) {
157
+ const p = JSON.parse(turn.signed_payload);
158
+ Object.assign(out, p);
159
+ out.message_id = turn.provenance.origin.message_id;
160
+ out.conversation_id = turn.provenance.origin.conversation_id;
161
+ out.signature = turn.signature;
162
+ out.signed_payload = turn.signed_payload;
163
+ if (p.message_id !== undefined && p.message_id !== out.message_id) {
164
+ throw new Error("signed_payload message_id disagrees with origin");
165
+ }
166
+ if (p.conversation_id !== undefined && p.conversation_id !== out.conversation_id) {
167
+ throw new Error("signed_payload conversation_id disagrees with origin");
168
+ }
169
+ return out;
170
+ }
171
+ const origin = turn.provenance.origin;
172
+ out.message_id = origin.message_id;
173
+ out.conversation_id = origin.conversation_id;
174
+ for (const k of ["from_did", "from_stable_id", "to_did", "to_stable_id", "priority"]) {
175
+ if (origin[k] !== undefined) out[k] = origin[k];
176
+ }
177
+ out.from = turn.from;
178
+ out.to = turn.to;
179
+ out.subject = turn.body.subject;
180
+ out.body = turn.body.text;
181
+ if (origin.timestamp_source === "created_at") out.created_at = turn.ts;
182
+ return out;
183
+ }
184
+
185
+ // -------------------------------------------------------------- merge rules
186
+
187
+ function isPrefix(shorter, longer) {
188
+ if (shorter.length > longer.length) return false;
189
+ for (let i = 0; i < shorter.length; i++) {
190
+ if (shorter[i].id !== longer[i].id) return false;
191
+ }
192
+ return true;
193
+ }
194
+
195
+ function mergeStreams(replicas) {
196
+ const merged = {};
197
+ for (const replica of Object.values(replicas)) {
198
+ for (const [stream, copy] of Object.entries(replica.streams)) {
199
+ const prev = merged[stream];
200
+ if (!prev) {
201
+ merged[stream] = copy;
202
+ continue;
203
+ }
204
+ const [a, b] = prev.length <= copy.length ? [prev, copy] : [copy, prev];
205
+ if (!isPrefix(a, b)) throw new Error(`non-prefix copies of stream ${stream}`);
206
+ merged[stream] = b;
207
+ }
208
+ }
209
+ return merged;
210
+ }
211
+
212
+ function effectiveRecord(merged, recordOwner) {
213
+ const byId = new Map();
214
+ for (const copy of Object.values(merged)) {
215
+ for (const turn of copy) if (!byId.has(turn.id)) byId.set(turn.id, turn);
216
+ }
217
+ const hidden = new Set();
218
+ for (const turn of byId.values()) {
219
+ if (turn.kind !== "tombstone") continue;
220
+ for (const link of turn.links ?? []) {
221
+ if (link.rel !== "tombstones") continue;
222
+ const target = byId.get(link.ref);
223
+ if (!target) continue;
224
+ if (turn.from === target.from || turn.from === recordOwner) hidden.add(target.id);
225
+ }
226
+ }
227
+ return { byId, hidden };
228
+ }
229
+
230
+ // ------------------------------------------------------------------ harness
231
+
232
+ let failures = 0;
233
+ let checks = 0;
234
+
235
+ function assert(cond, label) {
236
+ checks++;
237
+ if (!cond) {
238
+ failures++;
239
+ console.error(`FAIL ${label}`);
240
+ }
241
+ }
242
+
243
+ function assertThrows(fn, label) {
244
+ checks++;
245
+ try {
246
+ fn();
247
+ failures++;
248
+ console.error(`FAIL ${label} (expected rejection, got acceptance)`);
249
+ } catch {
250
+ /* expected */
251
+ }
252
+ }
253
+
254
+ function load(name) {
255
+ return JSON.parse(readFileSync(join(HERE, name), "utf8"));
256
+ }
257
+
258
+ // turn-id-v1
259
+ {
260
+ const doc = load("turn-id-v1.json");
261
+ for (const c of doc.cases) {
262
+ assert(coreString(c.turn) === c.expected_canonical_core, `${c.name}: canonical core bytes`);
263
+ assert(turnId(c.turn) === c.expected_id, `${c.name}: id`);
264
+ assert(c.turn.id === c.expected_id, `${c.name}: stored id`);
265
+ if (c.turn.sig) {
266
+ assert(
267
+ verifySig(c.turn.sig.by, coreString(c.turn), c.turn.sig.sig),
268
+ `${c.name}: envelope sig`,
269
+ );
270
+ }
271
+ }
272
+ for (const n of doc.negatives) {
273
+ if (n.reject === "id") {
274
+ assert(turnId(n.turn) !== n.turn.id, `${n.name}: id mismatch detected`);
275
+ } else if (n.reject === "sig") {
276
+ checks++;
277
+ let ok = false;
278
+ try {
279
+ ok = verifySig(n.turn.sig.by, coreString(n.turn), n.turn.sig.sig);
280
+ } catch {
281
+ ok = false;
282
+ }
283
+ if (ok) {
284
+ failures++;
285
+ console.error(`FAIL ${n.name}: invalid signature accepted`);
286
+ }
287
+ } else if (n.reject === "number") {
288
+ assertThrows(() => coreString(n.turn), `${n.name}: float rejected from core`);
289
+ } else {
290
+ assert(false, `${n.name}: unknown negative kind ${n.reject}`);
291
+ }
292
+ }
293
+ }
294
+
295
+ // aweb-projection-v1
296
+ {
297
+ const doc = load("aweb-projection-v1.json");
298
+ for (const c of doc.cases) {
299
+ const expected = c.expected_turn;
300
+ const projected = expected.signed_payload
301
+ ? projectSigned(c.row, expected.kind)
302
+ : projectLegacy(c.row);
303
+ assert(
304
+ coreString(projected) === coreString(expected),
305
+ `${c.name}: independent projection matches expected core bytes`,
306
+ );
307
+ assert(turnId(projected) === expected.id, `${c.name}: projected id`);
308
+
309
+ if (expected.signed_payload) {
310
+ assert(
311
+ expected.signed_payload === c.row.signed_payload,
312
+ `${c.name}: signed_payload byte-verbatim`,
313
+ );
314
+ const p = JSON.parse(expected.signed_payload);
315
+ assert(
316
+ verifySig(p.from_did, expected.signed_payload, expected.signature),
317
+ `${c.name}: source signature verifies against from_did`,
318
+ );
319
+ checks++;
320
+ try {
321
+ checkSignedConsistency(expected);
322
+ } catch (e) {
323
+ failures++;
324
+ console.error(`FAIL ${c.name}: ${e.message}`);
325
+ }
326
+ }
327
+
328
+ const row = unproject(expected);
329
+ for (const f of c.round_trip_fields) {
330
+ const want = c.row[f] !== undefined ? c.row[f] : JSON.parse(c.row.signed_payload ?? "{}")[f];
331
+ assert(
332
+ JSON.stringify(row[f]) === JSON.stringify(want),
333
+ `${c.name}: round-trip field ${f}`,
334
+ );
335
+ }
336
+ }
337
+ for (const n of doc.negatives) {
338
+ if (n.reject === "consistency") {
339
+ assert(turnId(n.turn) === n.turn.id, `${n.name}: id itself is valid`);
340
+ assertThrows(() => checkSignedConsistency(n.turn), `${n.name}: consistency rejected`);
341
+ } else {
342
+ assert(false, `${n.name}: unknown negative kind ${n.reject}`);
343
+ }
344
+ }
345
+ }
346
+
347
+ // journal-merge-v1
348
+ {
349
+ const doc = load("journal-merge-v1.json");
350
+ const merged = mergeStreams(doc.replicas);
351
+ for (const [stream, len] of Object.entries(doc.expected.merged_stream_lengths)) {
352
+ assert(merged[stream]?.length === len, `merge: stream ${stream} length ${len}`);
353
+ }
354
+ const { byId, hidden } = effectiveRecord(merged, doc.record_owner);
355
+ assert(
356
+ JSON.stringify([...byId.keys()].sort()) === JSON.stringify(doc.expected.union_ids),
357
+ "merge: union ids",
358
+ );
359
+ assert(
360
+ JSON.stringify([...hidden].sort()) === JSON.stringify([...doc.expected.hidden_ids].sort()),
361
+ "merge: hidden ids (tombstone dominance + authority)",
362
+ );
363
+ const visible = [...byId.keys()].filter((id) => !hidden.has(id)).sort();
364
+ assert(
365
+ JSON.stringify(visible) === JSON.stringify(doc.expected.visible_ids),
366
+ "merge: visible ids",
367
+ );
368
+ for (const t of byId.values()) {
369
+ assert(turnId(t) === t.id, `merge: turn ${t.id.slice(0, 12)}… id recomputes`);
370
+ }
371
+ for (const n of doc.negatives) {
372
+ if (n.reject === "prefix") {
373
+ assertThrows(
374
+ () =>
375
+ mergeStreams({
376
+ a: { streams: { [n.stream]: n.copy_a } },
377
+ b: { streams: { [n.stream]: n.copy_b } },
378
+ }),
379
+ `${n.name}: non-prefix copies quarantined`,
380
+ );
381
+ } else {
382
+ assert(false, `${n.name}: unknown negative kind ${n.reject}`);
383
+ }
384
+ }
385
+ }
386
+
387
+ if (failures) {
388
+ console.error(`\n${failures}/${checks} checks failed`);
389
+ process.exit(1);
390
+ }
391
+ console.log(`ok — ${checks} checks passed`);
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: integration-authoring
3
+ description: >-
4
+ Route custom OATS capability-package and integration work to the framework's
5
+ integrations expert. Use when building, adapting, or debugging a reusable
6
+ capability, new task/messaging/knowledge integration, oats.json manifest,
7
+ lifecycle hook, or operational command—not merely activating an existing
8
+ package. Triggers: "custom integration", "capability package", "integrate
9
+ our tracker", "new messaging integration", "write an oats.json".
10
+ ---
11
+
12
+ # Capability and integration authoring — delegate
13
+
14
+ A capability package may ship skills, instance instructions, requirements,
15
+ namespaced commands, and approved hooks. An integration is the constrained
16
+ subtype implementing exactly one fundamental layer. Building either requires
17
+ manifest, security, targeting-boundary, collision, and probe discipline; use
18
+ the framework's **integrations-expert** soul rather than improvising.
19
+
20
+ If the user only wants an existing package, use:
21
+
22
+ ```bash
23
+ oats install <source> # external acquisition + exact lock; inactive
24
+ oats trust <id> # only if commands/hooks exist
25
+ oats use <id> --global|--type <t>|--soul <s>
26
+ ```
27
+
28
+ ## 1. Locate the OATS framework repository
29
+
30
+ Check a local pi package path, then likely locations such as
31
+ `~/oats`; verify with `git -C <dir> remote get-url origin`. Avoid
32
+ pi-managed git clones because updates reset them. If absent, ask where to
33
+ clone `https://github.com/awebai/oats`.
34
+
35
+ ## 2. Spawn the expert against the user's repository
36
+
37
+ ```bash
38
+ node -e "
39
+ import('<framework-repo>/lib/core.mjs').then(m => {
40
+ const root = '<framework-repo>/agents';
41
+ const a = m.findAgent(root, 'integrations-expert');
42
+ const r = m.spawnInstance(root, a, {
43
+ purpose: '<package-slug>',
44
+ repo: '<users-workspace-or-repo>',
45
+ work: 'checkout',
46
+ task: '<capability intent; layer if any; skills/instructions/commands/hooks; external tools; desired global/group/soul targets; distribution path>',
47
+ });
48
+ console.log('window:', r.tmux.window, '| attach:', r.attach);
49
+ })"
50
+ ```
51
+
52
+ The work tree is the user's repository, where a local package belongs under
53
+ `.agents/capabilities/<name>/`. A framework contribution belongs under
54
+ `capabilities/<name>/` in the framework worktree; an independently published
55
+ package uses its own repository.
56
+
57
+ ## 3. Brief the design boundary
58
+
59
+ Tell the expert:
60
+
61
+ - whether it is additive or implements exactly one of knowledge/messaging/tasks;
62
+ - external requirements and executable surfaces;
63
+ - intended distribution and version/compatibility;
64
+ - desired config-owned targets and settings; and
65
+ - expected skill/instruction/scaffold collisions.
66
+
67
+ Targets never belong in the manifest. The expert must test exact pi/Claude
68
+ instance materialization, generated instructions, lock/trust behavior,
69
+ command gating, deterministic hooks, and scaffold ownership as applicable.
70
+
71
+ ## 4. Hand off
72
+
73
+ Report the tmux window (`tmux attach -t pi-agents`). The expert follows its
74
+ package/integration craft, runs a scaffold-only probe, and leaves acquisition
75
+ and activation commands for the user. Its durable lessons harvest back into
76
+ its soul.
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: oats
3
+ description: >-
4
+ How to operate inside OATS (Open Agent Team Specification): instance layout and
5
+ lifecycle, status, spawn, retire, doctor, operational capability commands,
6
+ canonical-vs-generated instructions, or explaining OATS. For configuring
7
+ deployments (capabilities, layers, agent types, injections) load the
8
+ oats-config skill. Triggers: "spawn an agent", "what agents are running",
9
+ "retire this instance", "oats doctor", "oats status", "how does OATS work".
10
+ ---
11
+
12
+ # Operating in OATS
13
+
14
+ A **soul** is a durable specialized agent. An **instance** is one disposable,
15
+ resumable incarnation. A **capability package** distributes reusable skills,
16
+ instructions, commands, and approved lifecycle hooks. An **integration** is a
17
+ capability selected for one exclusive knowledge, messaging, or tasks layer.
18
+
19
+ ## Instance home
20
+
21
+ | Path | Meaning |
22
+ |---|---|
23
+ | `TASK.md` | briefing and task |
24
+ | `soul/` | linked canonical soul |
25
+ | `AGENTS.md` | generated canonical soul + active capability instructions |
26
+ | `CLAUDE.md -> AGENTS.md` | compatibility view |
27
+ | `.agents/skills/` | exact runtime skill set |
28
+ | `work/` | all repository work happens here |
29
+ | `instance.json` | repo, branch, capabilities, skills, instruction sources, trust, hooks |
30
+
31
+ Memory files exist only when the selected knowledge integration creates them.
32
+ Follow their injected protocol.
33
+
34
+ ## Lifecycle and roster
35
+
36
+ ```bash
37
+ oats status [--json]
38
+ oats status --team [--json] # whole-team roster when config declares team: (all repos in the team scope)
39
+ # with the aweb messaging integration active, `oats aweb roster` adds the
40
+ # cross-machine view: aweb team members, where OATS aliases are instance names
41
+ oats create <name> [--description ...] [--type <agent-type>] [--repo ...] [--work worktree|checkout|attached|workspace]
42
+ # workspace mode = cross-repo coordinator: ./work is the whole team scope; read
43
+ # all member repos, edit none; if a knowledge layer is active, IT defines how
44
+ # soul updates are delivered (see that capability's own instructions)
45
+ oats spawn <agent> [--task ...] [--purpose ...] [--relation child|sibling|parent|unrelated --relative-to <instance>] [--parent <instance>] [--no-launch] [--json]
46
+ # lineage is explicit: agents spawning sub-agents declare their RELATION to the
47
+ # new instance with --relation + --relative-to (--parent X is sugar for
48
+ # --relative-to X --relation child). Without a relation the spawn is
49
+ # operator-origin and appears top-level. Attached-mode spawns are ALWAYS
50
+ # children of the work-tree owner (relation flags are rejected there).
51
+ # when config declares team:, spawn/retire also resolve souls and instances
52
+ # defined in sibling repos of the team scope (unique match wins; the instance
53
+ # homes with its owning repo, works in that repo, resolves that repo's config)
54
+ oats retire <instance> [--delete-branch]
55
+ ```
56
+
57
+ ### Spawn relations — choosing how the new instance relates to you
58
+
59
+ When you spawn, declare what the new instance IS to you — the workspace is
60
+ viewed as clusters of related agents, and the relation is how clusters form:
61
+
62
+ - **child** (`--relation child --relative-to <you>`, or `--parent <you>`) —
63
+ the new instance works FOR you and nests under you. Example: a coordinator
64
+ spawning the developers of its feature.
65
+ - **parent** (`--relation parent --relative-to <you>`) — the new instance
66
+ oversees YOU: your recorded lineage is re-pointed so it becomes your parent.
67
+ Example: spawning a maintainer of your own PR — the maintainer sits
68
+ above you. When it later retires, lineage is spliced automatically: you
69
+ return to your previous parent (or top-level).
70
+ - **sibling** (`--relation sibling --relative-to <you>`) — a peer at your
71
+ level, in your cluster. Example: enlisting a peer coordinator in another
72
+ repo, or an architecture coordinator helping you.
73
+ - **unrelated** (default, no flags) — no link. Example: work with no
74
+ connection to yours.
75
+
76
+ Exception: **attached** work mode implies child-of-owner — an attached agent
77
+ shares its owner's work tree and is always that owner's child; relation flags
78
+ are rejected there.
79
+
80
+ This is judgment, not mandate: every workspace differs, and a soul's own
81
+ explicit relation instructions (in its AGENTS.md or task briefing) take
82
+ precedence over these defaults. When unsure which relation fits, check with
83
+ the human.
84
+
85
+ Do not spawn on your own judgment. Spawn when the human asks or a documented
86
+ workflow requires it.
87
+
88
+ ### Instance naming
89
+
90
+ Name an instance for both **who it is** and **what this incarnation does** by
91
+ spawning with `oats spawn <soul> --purpose <descriptive-role>`. OATS constructs
92
+ `<full-soul-name>-<descriptive-role>`; use a short, lowercase kebab-case role
93
+ suffix (for example, `desktop-ux` or `terminal-safety`), not an opaque number
94
+ or generic word. The current spawn command always retains the full soul name,
95
+ so shorten the purpose—not the soul prefix—when the result would be unwieldy.
96
+ Do **not** use `oats create` to name an incarnation: it creates a new persistent
97
+ soul. Never put secrets, user data, or volatile task details in an instance
98
+ name.
99
+
100
+ To self-retire, first finish memory/commit/reporting requirements, report final
101
+ status, then run `oats retire <own-instance> --self`. Never retire merely to
102
+ clean up; retirement deletes the instance home.
103
+
104
+ ## Canonical versus generated
105
+
106
+ Edit `soul/AGENTS.md` for durable role instructions. Instance `AGENTS.md` is a
107
+ generated view; marked blocks name their source. Config changes do not mutate
108
+ the committed soul. Preview a fresh composition with:
109
+
110
+ ```bash
111
+ oats doctor /path/to/context --soul <name>
112
+ ```
113
+
114
+ The instance's `.agents/skills/` holds the exact OATS-composed set (kernel +
115
+ soul + active capabilities); `.claude/skills` mirrors it. Harness-ambient
116
+ skills (user-level, packages, work tree) coexist with this set. Duplicate
117
+ names *within* the OATS set fail spawn unless `skill-overrides` explicitly
118
+ chooses a source.
119
+
120
+ ## Configuration
121
+
122
+ Deployments are configured in scoped `oats-config.yaml` files (laptop /
123
+ workspace / repository) declaring capability packages, exclusive
124
+ knowledge/messaging/tasks layers, agent types, targeting, and injection
125
+ overrides. The CLI is the config author (`oats init`, `oats use`, `oats type`,
126
+ `oats inject eject`). **Load the `oats-config` skill for all configuration
127
+ work** — this skill covers operating, not configuring.
128
+
129
+ ## Commands and doctor
130
+
131
+ Operational namespaces run only when their package is active in the current
132
+ context/instance:
133
+
134
+ ```bash
135
+ oats okf harvest
136
+ oats linear issue list ...
137
+ ```
138
+
139
+ Package-management commands remain global. Use doctor first when something is
140
+ missing:
141
+
142
+ ```bash
143
+ oats doctor [context] [--soul <name>] [--json]
144
+ ```
145
+
146
+ It shows config chain, acquired/active packages, layer selection, target and
147
+ settings provenance, requirements, trust, skill sources, instruction blocks,
148
+ and—with `--soul`—final composed text.
149
+
150
+ Infrastructure faults should be reported to the spawner/human, not repaired by
151
+ an instance ad hoc.