nubos-pilot 1.2.1 → 1.2.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/lib/learnings.cjs CHANGED
@@ -6,6 +6,10 @@ const crypto = require('node:crypto');
6
6
 
7
7
  const { projectStateDir, atomicWriteFileSync, withFileLock, NubosPilotError, safeAssign } = require('./core.cjs');
8
8
  const { TASK_ID_RE, MILESTONE_ID_RE } = require('./ids.cjs');
9
+ const { assertValid } = require('./validate.cjs');
10
+ const { runMigrators } = require('./migrate.cjs');
11
+
12
+ const STORE_SCHEMA = 'learnings.v1';
9
13
 
10
14
  const STOPWORDS = new Set([
11
15
  'the','a','an','of','to','in','on','for','and','or','is','are','was','were',
@@ -96,14 +100,7 @@ function _readStore(cwd) {
96
100
  );
97
101
  }
98
102
  if (obj.version === STORE_VERSION) {
99
- if (!Array.isArray(obj.learnings)) {
100
- throw new NubosPilotError(
101
- 'learnings-store-corrupt',
102
- 'learnings.json missing learnings[] array',
103
- { path: p, version: obj.version },
104
- );
105
- }
106
- _assertLearningRecords(obj.learnings, p);
103
+ assertValid(obj, STORE_SCHEMA, 'learnings-store-corrupt', { path: p });
107
104
  return obj;
108
105
  }
109
106
  const migrated = _migrate(obj, p);
@@ -120,99 +117,26 @@ function _readStore(cwd) {
120
117
  );
121
118
  }
122
119
 
123
- const _REQUIRED_LEARNING_FIELDS = ['fingerprint', 'pattern', 'outcome', 'occurrence', 'first_seen', 'last_seen'];
124
-
125
120
  function _assertLearningRecords(records, p) {
126
- for (let i = 0; i < records.length; i += 1) {
127
- const r = records[i];
128
- if (!r || typeof r !== 'object' || Array.isArray(r)) {
129
- throw new NubosPilotError(
130
- 'learnings-store-corrupt',
131
- 'learnings[' + i + '] is not a JSON object',
132
- { path: p, index: i },
133
- );
134
- }
135
- for (const field of _REQUIRED_LEARNING_FIELDS) {
136
- if (!(field in r)) {
137
- throw new NubosPilotError(
138
- 'learnings-store-corrupt',
139
- 'learnings[' + i + '] missing required field "' + field + '"',
140
- { path: p, index: i, field, required: _REQUIRED_LEARNING_FIELDS.slice() },
141
- );
142
- }
143
- }
144
- if (typeof r.fingerprint !== 'string' || !/^[a-f0-9]{16}$/.test(r.fingerprint)) {
145
- throw new NubosPilotError(
146
- 'learnings-store-corrupt',
147
- 'learnings[' + i + '].fingerprint must be a 16-char hex string',
148
- { path: p, index: i, got: r.fingerprint },
149
- );
150
- }
151
- if (typeof r.occurrence !== 'number' || r.occurrence < 1) {
152
- throw new NubosPilotError(
153
- 'learnings-store-corrupt',
154
- 'learnings[' + i + '].occurrence must be a positive integer',
155
- { path: p, index: i, got: r.occurrence },
156
- );
157
- }
158
- if (typeof r.pattern !== 'string') {
159
- throw new NubosPilotError(
160
- 'learnings-store-corrupt',
161
- 'learnings[' + i + '].pattern must be a string',
162
- { path: p, index: i, got: typeof r.pattern },
163
- );
164
- }
165
- if (Buffer.byteLength(r.pattern, 'utf-8') > MAX_PATTERN_BYTES) {
166
- throw new NubosPilotError(
167
- 'learnings-store-corrupt',
168
- 'learnings[' + i + '].pattern exceeds MAX_PATTERN_BYTES',
169
- { path: p, index: i, max: MAX_PATTERN_BYTES },
170
- );
171
- }
172
- if (typeof r.outcome !== 'string') {
173
- throw new NubosPilotError(
174
- 'learnings-store-corrupt',
175
- 'learnings[' + i + '].outcome must be a string',
176
- { path: p, index: i, got: typeof r.outcome },
177
- );
178
- }
179
- if ('tokens' in r && !Array.isArray(r.tokens)) {
180
- throw new NubosPilotError(
181
- 'learnings-store-corrupt',
182
- 'learnings[' + i + '].tokens, when present, must be an array',
183
- { path: p, index: i, got: typeof r.tokens },
184
- );
185
- }
186
- for (const arrField of ['task_ids', 'milestone_ids']) {
187
- if (arrField in r && !Array.isArray(r[arrField])) {
188
- throw new NubosPilotError(
189
- 'learnings-store-corrupt',
190
- 'learnings[' + i + '].' + arrField + ', when present, must be an array',
191
- { path: p, index: i, field: arrField, got: typeof r[arrField] },
192
- );
193
- }
194
- }
195
- }
196
- }
197
-
198
- function _migrate(obj, p, migrators) {
199
- const reg = migrators || MIGRATORS;
200
- let cur = obj;
201
- while (cur && cur.version !== STORE_VERSION) {
202
- const next = reg[cur.version];
203
- if (typeof next !== 'function') return null;
204
- cur = next(cur);
205
- if (!cur || typeof cur !== 'object') return null;
206
- }
207
- if (!Array.isArray(cur.learnings)) {
121
+ if (!Array.isArray(records)) {
208
122
  throw new NubosPilotError(
209
123
  'learnings-store-corrupt',
210
- 'migrator produced invalid shape (missing learnings[])',
124
+ 'learnings[] must be an array',
211
125
  { path: p },
212
126
  );
213
127
  }
214
- _assertLearningRecords(cur.learnings, p);
215
- return cur;
128
+ assertValid({ version: STORE_VERSION, learnings: records }, STORE_SCHEMA, 'learnings-store-corrupt', { path: p });
129
+ }
130
+
131
+ function _migrate(obj, p, migrators) {
132
+ return runMigrators(obj, {
133
+ versionField: 'version',
134
+ targetVersion: STORE_VERSION,
135
+ migrators: migrators || MIGRATORS,
136
+ schema: STORE_SCHEMA,
137
+ code: 'learnings-store-corrupt',
138
+ details: { path: p },
139
+ });
216
140
  }
217
141
 
218
142
  function _evictIfOverCap(store, opts) {
package/lib/memory.cjs CHANGED
@@ -4,6 +4,7 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const crypto = require('node:crypto');
6
6
  const { atomicWriteFileSync, appendJsonl, withFileLockAsync, NubosPilotError, projectStateDir } = require('./core.cjs');
7
+ const { validate } = require('./validate.cjs');
7
8
 
8
9
  let _memLog;
9
10
  function _log() {
@@ -15,6 +16,19 @@ const TYPE_ENUM = new Set(['learning', 'handoff', 'critic', 'research']);
15
16
  const PROVENANCE_ENUM = new Set(['VERIFIED', 'CITED', 'ASSUMED', 'CACHED']);
16
17
  const SCHEMA_VERSION = 1;
17
18
 
19
+ const RECORD_SCHEMA = 'memory-record.v1';
20
+ const MANIFEST_SCHEMA = 'memory-manifest.v1';
21
+
22
+ const _RECORD_CODE_BY_FIELD = Object.freeze({
23
+ type: 'memory-invalid-type',
24
+ title: 'memory-missing-title',
25
+ body: 'memory-missing-body',
26
+ tags: 'memory-invalid-tags',
27
+ provenance: 'memory-invalid-provenance',
28
+ id: 'memory-invalid-id',
29
+ phase: 'memory-invalid-phase',
30
+ });
31
+
18
32
  function _memoryRoot(cwd) {
19
33
  return path.join(projectStateDir(cwd || process.cwd()), 'memory');
20
34
  }
@@ -24,38 +38,14 @@ function _indexPath(cwd) { return path.join(_memoryRoot(cwd), 'index.usearch');
24
38
  function _manifestPath(cwd) { return path.join(_memoryRoot(cwd), 'manifest.json'); }
25
39
 
26
40
  function _validateRecord(record) {
27
- if (!record || typeof record !== 'object') {
41
+ if (!record || typeof record !== 'object' || Array.isArray(record)) {
28
42
  throw new NubosPilotError('memory-invalid-record', 'record must be object', {});
29
43
  }
30
- if (typeof record.type !== 'string' || !TYPE_ENUM.has(record.type)) {
31
- throw new NubosPilotError(
32
- 'memory-invalid-type',
33
- `type must be one of [${[...TYPE_ENUM].join(', ')}]`,
34
- { type: record.type },
35
- );
36
- }
37
- if (typeof record.title !== 'string' || record.title.length === 0) {
38
- throw new NubosPilotError('memory-missing-title', 'title required as non-empty string', {});
39
- }
40
- if (typeof record.body !== 'string') {
41
- throw new NubosPilotError('memory-missing-body', 'body required as string', { type: typeof record.body });
42
- }
43
- if (record.tags !== undefined && record.tags !== null && !Array.isArray(record.tags)) {
44
- throw new NubosPilotError('memory-invalid-tags', 'tags must be array of strings or null', {});
45
- }
46
- if (record.provenance !== undefined && record.provenance !== null && !PROVENANCE_ENUM.has(record.provenance)) {
47
- throw new NubosPilotError(
48
- 'memory-invalid-provenance',
49
- `provenance must be one of [${[...PROVENANCE_ENUM].join(', ')}]`,
50
- { provenance: record.provenance },
51
- );
52
- }
53
- if (record.id !== undefined && record.id !== null && typeof record.id !== 'string') {
54
- throw new NubosPilotError('memory-invalid-id', 'id must be string or null', { id: record.id });
55
- }
56
- if (record.phase !== undefined && record.phase !== null && typeof record.phase !== 'string') {
57
- throw new NubosPilotError('memory-invalid-phase', 'phase must be string or null', { phase: record.phase });
58
- }
44
+ const errors = validate(record, RECORD_SCHEMA);
45
+ if (errors.length === 0) return;
46
+ const first = errors[0];
47
+ const code = _RECORD_CODE_BY_FIELD[first.field] || 'memory-invalid-record';
48
+ throw new NubosPilotError(code, first.message, { field: first.field, value: record[first.field] });
59
49
  }
60
50
 
61
51
  function _readRecordsJsonlWithStats(cwd) {
@@ -66,8 +56,11 @@ function _readRecordsJsonlWithStats(cwd) {
66
56
  let skipped = 0;
67
57
  for (const line of raw.split('\n')) {
68
58
  if (!line.trim()) continue;
69
- try { records.push(JSON.parse(line)); }
70
- catch { skipped += 1; }
59
+ let rec;
60
+ try { rec = JSON.parse(line); }
61
+ catch { skipped += 1; continue; }
62
+ if (validate(rec, RECORD_SCHEMA).length > 0) { skipped += 1; continue; }
63
+ records.push(rec);
71
64
  }
72
65
  if (skipped > 0) {
73
66
  _log().warn('skipped corrupt records.jsonl lines', {
@@ -92,7 +85,19 @@ function _writeManifest(cwd, manifest) {
92
85
  function _readManifest(cwd) {
93
86
  const p = _manifestPath(cwd);
94
87
  if (!fs.existsSync(p)) return null;
95
- try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
88
+ let obj;
89
+ try { obj = JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
90
+ const errors = validate(obj, MANIFEST_SCHEMA);
91
+ if (errors.length > 0) {
92
+ _log().warn('ignoring corrupt memory manifest', {
93
+ event: 'memory-manifest-corrupt',
94
+ file: 'manifest.json',
95
+ violation: errors[0].message,
96
+ hint: 'manifest will be rewritten on next index/rebuild; run `memory-rebuild` if model/dim changed',
97
+ });
98
+ return null;
99
+ }
100
+ return obj;
96
101
  }
97
102
 
98
103
  function _embeddingText(record) {
package/lib/messaging.cjs CHANGED
@@ -4,6 +4,7 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const crypto = require('node:crypto');
6
6
  const { atomicWriteFileSync, atomicCreateExclusiveSync, fsyncDir, appendJsonl, withFileLock, NubosPilotError, projectStateDir } = require('./core.cjs');
7
+ const { validate, assertValid } = require('./validate.cjs');
7
8
 
8
9
  function _runId() {
9
10
  try { return require('./run-context.cjs').getRunId(); }
@@ -13,6 +14,8 @@ function _runId() {
13
14
  const KIND_ENUM = new Set(['request', 'response', 'notify']);
14
15
  const AGENT_RE = /^[a-zA-Z0-9_-]+$/;
15
16
  const ID_RE = /^\d+-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
17
+ const MAX_BODY_BYTES = 256 * 1024;
18
+ const MESSAGE_SCHEMA = 'message.v1';
16
19
 
17
20
  function _messagesRoot(cwd) {
18
21
  return path.join(projectStateDir(cwd || process.cwd()), 'messages');
@@ -81,11 +84,11 @@ function _appendManifest(cwd, event) {
81
84
  function _readMessageFile(filePath) {
82
85
  let raw;
83
86
  try { raw = fs.readFileSync(filePath, 'utf-8'); } catch { return null; }
84
- try {
85
- const parsed = JSON.parse(raw);
86
- Object.defineProperty(parsed, '__path', { value: filePath, enumerable: false });
87
- return parsed;
88
- } catch { return null; }
87
+ let parsed;
88
+ try { parsed = JSON.parse(raw); } catch { return null; }
89
+ if (validate(parsed, MESSAGE_SCHEMA).length > 0) return null;
90
+ Object.defineProperty(parsed, '__path', { value: filePath, enumerable: false });
91
+ return parsed;
89
92
  }
90
93
 
91
94
  function _scanDir(dir) {
@@ -118,7 +121,6 @@ function send(opts, cwd) {
118
121
  if (typeof o.body !== 'string') {
119
122
  throw new NubosPilotError('messages-missing-body', 'body required (string, may be empty)', {});
120
123
  }
121
- const MAX_BODY_BYTES = 256 * 1024;
122
124
  if (Buffer.byteLength(o.body, 'utf-8') > MAX_BODY_BYTES) {
123
125
  throw new NubosPilotError(
124
126
  'messages-body-too-large',
@@ -157,6 +159,7 @@ function send(opts, cwd) {
157
159
  in_reply_to: o.in_reply_to || null,
158
160
  created_at: createdAt,
159
161
  };
162
+ assertValid(message, MESSAGE_SCHEMA, 'messages-invalid-record', { id });
160
163
 
161
164
  const workingDir = cwd || process.cwd();
162
165
  const dir = _inboxDir(workingDir, o.to);
@@ -472,4 +475,7 @@ module.exports = {
472
475
  pendingReplies,
473
476
  sweepTaskOnCommit,
474
477
  KIND_ENUM,
478
+ AGENT_RE,
479
+ ID_RE,
480
+ MAX_BODY_BYTES,
475
481
  };
@@ -5,6 +5,9 @@ const path = require('node:path');
5
5
  const readline = require('node:readline');
6
6
  const { NubosPilotError, findProjectRoot } = require('./core.cjs');
7
7
  const { SAFE_PHASE_RE } = require('./metrics.cjs');
8
+ const { validate } = require('./validate.cjs');
9
+
10
+ const RECORD_SCHEMA = 'metrics-record.v1';
8
11
 
9
12
  let _maLog;
10
13
  function _log() {
@@ -41,15 +44,24 @@ function _readJsonlLines(filePath, onRecord) {
41
44
  rl.on('line', (raw) => {
42
45
  const line = String(raw).trim();
43
46
  if (!line) return;
47
+ let rec;
44
48
  try {
45
- const rec = JSON.parse(line);
46
- onRecord(rec);
49
+ rec = JSON.parse(line);
47
50
  } catch (_err) {
48
51
  _log().warn('skipping malformed JSONL line', {
49
52
  event: 'metrics-aggregate-malformed-line',
50
53
  file: require('node:path').basename(filePath),
51
54
  });
55
+ return;
56
+ }
57
+ if (validate(rec, RECORD_SCHEMA).length > 0) {
58
+ _log().warn('skipping schema-invalid JSONL line', {
59
+ event: 'metrics-aggregate-invalid-line',
60
+ file: require('node:path').basename(filePath),
61
+ });
62
+ return;
52
63
  }
64
+ onRecord(rec);
53
65
  });
54
66
  rl.on('close', () => resolve());
55
67
  rl.on('error', (err) => reject(err));
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const { assertValid } = require('./validate.cjs');
4
+
5
+ const MAX_HOPS = 100;
6
+
7
+ function runMigrators(obj, opts) {
8
+ const o = opts || {};
9
+ const versionField = o.versionField || 'version';
10
+ const target = o.targetVersion;
11
+ const migrators = o.migrators || {};
12
+ let cur = obj;
13
+ let hops = 0;
14
+ while (cur && typeof cur === 'object' && !Array.isArray(cur) && cur[versionField] !== target) {
15
+ if (hops >= MAX_HOPS) return null;
16
+ hops += 1;
17
+ const key = cur[versionField];
18
+ if (!Object.prototype.hasOwnProperty.call(migrators, key)) return null;
19
+ const fn = migrators[key];
20
+ if (typeof fn !== 'function') return null;
21
+ cur = fn(cur);
22
+ if (!cur || typeof cur !== 'object' || Array.isArray(cur)) return null;
23
+ }
24
+ if (!cur || typeof cur !== 'object' || Array.isArray(cur)) return null;
25
+ if (o.schema) assertValid(cur, o.schema, o.code || 'data-migration-invalid', o.details || {});
26
+ return cur;
27
+ }
28
+
29
+ module.exports = { runMigrators, MAX_HOPS };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+
6
+ const { runMigrators, MAX_HOPS } = require('./migrate.cjs');
7
+ const { NubosPilotError } = require('./core.cjs');
8
+
9
+ test('MIG-1: object already at target version is returned unchanged', () => {
10
+ const obj = { version: 2, payload: 'x' };
11
+ const out = runMigrators(obj, { targetVersion: 2, migrators: {} });
12
+ assert.equal(out, obj);
13
+ });
14
+
15
+ test('MIG-2: single hop runs the matching migrator', () => {
16
+ const out = runMigrators({ version: 1, n: 1 }, {
17
+ targetVersion: 2,
18
+ migrators: { 1: (v) => ({ version: 2, n: v.n + 1 }) },
19
+ });
20
+ assert.deepEqual(out, { version: 2, n: 2 });
21
+ });
22
+
23
+ test('MIG-3: multi-hop chain runs each hop until target', () => {
24
+ const out = runMigrators({ version: 0, steps: [] }, {
25
+ targetVersion: 3,
26
+ migrators: {
27
+ 0: (v) => ({ version: 1, steps: [...v.steps, 'a'] }),
28
+ 1: (v) => ({ version: 2, steps: [...v.steps, 'b'] }),
29
+ 2: (v) => ({ version: 3, steps: [...v.steps, 'c'] }),
30
+ },
31
+ });
32
+ assert.deepEqual(out.steps, ['a', 'b', 'c']);
33
+ });
34
+
35
+ test('MIG-4: missing migrator for a version returns null', () => {
36
+ const out = runMigrators({ version: 99 }, { targetVersion: 1, migrators: {} });
37
+ assert.equal(out, null);
38
+ });
39
+
40
+ test('MIG-5: migrator returning a non-object returns null', () => {
41
+ const out = runMigrators({ version: 0 }, { targetVersion: 1, migrators: { 0: () => null } });
42
+ assert.equal(out, null);
43
+ });
44
+
45
+ test('MIG-6: hop cap prevents an infinite migrator cycle', () => {
46
+ const out = runMigrators({ version: 0 }, {
47
+ targetVersion: 999,
48
+ migrators: { 0: () => ({ version: 0 }) },
49
+ });
50
+ assert.equal(out, null);
51
+ });
52
+
53
+ test('MIG-7: pure function — input object is not mutated', () => {
54
+ const input = { version: 0, n: 1 };
55
+ runMigrators(input, { targetVersion: 1, migrators: { 0: (v) => ({ version: 1, n: v.n + 1 }) } });
56
+ assert.deepEqual(input, { version: 0, n: 1 });
57
+ });
58
+
59
+ test('MIG-8: migrated shape is validated against the given schema', () => {
60
+ assert.throws(
61
+ () => runMigrators({ version: 0, learnings: [] }, {
62
+ targetVersion: 1,
63
+ migrators: { 0: () => ({ version: 1, learnings: [{ fingerprint: 'NOTHEX', occurrence: 1 }] }) },
64
+ schema: 'learnings.v1',
65
+ code: 'learnings-store-corrupt',
66
+ details: { path: '<test>' },
67
+ }),
68
+ (err) => err instanceof NubosPilotError && err.code === 'learnings-store-corrupt',
69
+ );
70
+ });
71
+
72
+ test('MIG-9: custom versionField is honoured', () => {
73
+ const out = runMigrators({ schema_version: 1, ok: true }, {
74
+ versionField: 'schema_version',
75
+ targetVersion: 1,
76
+ migrators: {},
77
+ });
78
+ assert.deepEqual(out, { schema_version: 1, ok: true });
79
+ });
80
+
81
+ test('MIG-10: MAX_HOPS is a finite positive cap', () => {
82
+ assert.ok(Number.isInteger(MAX_HOPS) && MAX_HOPS > 0);
83
+ });
84
+
85
+ test('MIG-11: a version matching an Object.prototype member returns null, not a raw throw', () => {
86
+ for (const poison of ['valueOf', 'hasOwnProperty', 'toString', 'constructor', '__proto__']) {
87
+ let out;
88
+ assert.doesNotThrow(() => { out = runMigrators({ version: poison }, { targetVersion: 1, migrators: {} }); });
89
+ assert.equal(out, null);
90
+ }
91
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "$id": "checkpoint.v1",
3
+ "title": "Task checkpoint (checkpoints/<task_id>.json)",
4
+ "type": "object",
5
+ "required": ["schema_version"],
6
+ "properties": {
7
+ "schema_version": { "type": "integer", "minimum": 1 },
8
+ "task_id": { "type": "string" },
9
+ "status": { "type": "string" },
10
+ "files_touched": { "type": "array" },
11
+ "nubosloop": { "type": "object" }
12
+ }
13
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$id": "codebase-manifest.v1",
3
+ "title": "Codebase hash manifest (codebase/.hashes.json)",
4
+ "type": "object",
5
+ "required": ["schema_version"],
6
+ "properties": {
7
+ "schema_version": { "type": "integer", "minimum": 1 },
8
+ "generated_at": { "type": ["string", "null"] },
9
+ "files": {
10
+ "type": "object",
11
+ "additionalProperties": {
12
+ "type": "object",
13
+ "required": ["sha256"],
14
+ "properties": {
15
+ "sha256": { "type": "string" },
16
+ "size": { "type": "integer", "minimum": 0 },
17
+ "ext": { "type": "string" }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$id": "learnings.v1",
3
+ "title": "Learnings store (knowledge/learnings.json)",
4
+ "type": "object",
5
+ "required": ["version", "learnings"],
6
+ "properties": {
7
+ "version": { "type": "integer", "minimum": 1 },
8
+ "learnings": {
9
+ "type": "array",
10
+ "items": {
11
+ "type": "object",
12
+ "required": ["fingerprint", "pattern", "outcome", "occurrence", "first_seen", "last_seen"],
13
+ "properties": {
14
+ "fingerprint": { "type": "string", "pattern": "^[a-f0-9]{16}$" },
15
+ "pattern": { "type": "string", "maxBytes": 4096 },
16
+ "outcome": { "type": "string", "maxBytes": 4096 },
17
+ "occurrence": { "type": "integer", "minimum": 1 },
18
+ "first_seen": { "type": "string" },
19
+ "last_seen": { "type": "string" },
20
+ "tokens": { "type": "array", "items": { "type": "string" } },
21
+ "task_ids": { "type": "array", "items": { "type": "string" } },
22
+ "milestone_ids": { "type": "array", "items": { "type": "string" } },
23
+ "outcome_history": { "type": "array" }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$id": "memory-manifest.v1",
3
+ "title": "Memory manifest (memory/manifest.json)",
4
+ "type": "object",
5
+ "required": ["schema_version", "model", "dim"],
6
+ "properties": {
7
+ "schema_version": { "type": "integer", "minimum": 1 },
8
+ "model": { "type": "string", "minLength": 1 },
9
+ "dim": { "type": "integer", "minimum": 1 },
10
+ "alpha": { "type": "number" },
11
+ "created_at": { "type": "string" },
12
+ "rebuilt_at": { "type": "string" }
13
+ }
14
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$id": "memory-record.v1",
3
+ "title": "Memory record (memory/records.jsonl line)",
4
+ "type": "object",
5
+ "required": ["type", "title", "body"],
6
+ "properties": {
7
+ "type": { "enum": ["learning", "handoff", "critic", "research"] },
8
+ "title": { "type": "string", "minLength": 1 },
9
+ "body": { "type": "string" },
10
+ "tags": { "type": ["array", "null"] },
11
+ "provenance": { "enum": ["VERIFIED", "CITED", "ASSUMED", "CACHED", null] },
12
+ "id": { "type": ["string", "null"] },
13
+ "phase": { "type": ["string", "null"] },
14
+ "created_at": { "type": ["string", "null"] }
15
+ }
16
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$id": "message.v1",
3
+ "title": "Inter-agent message (messages/inbox/<agent>/<id>.json)",
4
+ "type": "object",
5
+ "required": ["id", "from", "to", "phase", "kind", "subject", "body", "created_at"],
6
+ "properties": {
7
+ "id": { "type": "string", "pattern": "^\\d+-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" },
8
+ "from": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
9
+ "to": { "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" },
10
+ "phase": { "type": "string", "minLength": 1 },
11
+ "round": { "type": ["integer", "null"], "minimum": 0 },
12
+ "kind": { "enum": ["request", "response", "notify"] },
13
+ "subject": { "type": "string", "minLength": 1 },
14
+ "body": { "type": "string", "maxBytes": 262144 },
15
+ "expects_reply": { "type": "boolean" },
16
+ "in_reply_to": { "type": ["string", "null"] },
17
+ "created_at": { "type": "string" }
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$id": "metrics-record.v1",
3
+ "title": "Metrics record (metrics/phase-<phase>.jsonl, meta.jsonl line)",
4
+ "type": "object",
5
+ "properties": {
6
+ "duration_ms": { "type": ["number", "null"], "minimum": 0 },
7
+ "tokens_in": { "type": ["number", "null"] },
8
+ "tokens_out": { "type": ["number", "null"] },
9
+ "retry_count": { "type": ["number", "null"], "minimum": 0 }
10
+ }
11
+ }