@tangleai/context 0.21.1
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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +488 -0
- package/package.json +98 -0
- package/src/archive.d.ts +47 -0
- package/src/archive.js +53 -0
- package/src/environment.d.ts +66 -0
- package/src/environment.js +715 -0
- package/src/evidence.d.ts +122 -0
- package/src/evidence.js +71 -0
- package/src/index.d.ts +9 -0
- package/src/index.js +11 -0
- package/src/ledger.d.ts +354 -0
- package/src/ledger.js +1397 -0
- package/src/recall.d.ts +74 -0
- package/src/recall.js +160 -0
- package/src/retention.d.ts +24 -0
- package/src/retention.js +91 -0
- package/src/schemas/evidence.d.ts +54 -0
- package/src/schemas/evidence.js +32 -0
- package/src/schemas/ledger.d.ts +515 -0
- package/src/schemas/ledger.js +357 -0
- package/src/schemas/patch.d.ts +129 -0
- package/src/schemas/patch.js +135 -0
- package/src/storage/memory.d.ts +86 -0
- package/src/storage/memory.js +118 -0
- package/src/storage/slot.d.ts +30 -0
- package/src/storage/slot.js +107 -0
- package/src/storage/transaction.d.ts +33 -0
- package/src/storage/transaction.js +49 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* The ledger's four record schemas, as plain JSON Schema documents
|
|
4
|
+
* compiled by `JarenValidator` when a ledger is constructed. A malformed
|
|
5
|
+
* write is rejected at the boundary exactly the way a malformed tool call
|
|
6
|
+
* is — the suite validating its own durable state with its own validator.
|
|
7
|
+
*
|
|
8
|
+
* They live in one module rather than four files because they are small,
|
|
9
|
+
* they share a vocabulary (`at` is an RFC 3339 timestamp everywhere, ids
|
|
10
|
+
* are non-empty strings everywhere), and one file is where a reader looks
|
|
11
|
+
* for "what may a ledger hold".
|
|
12
|
+
*
|
|
13
|
+
* Why four kinds and not one bag: they differ in every dimension that
|
|
14
|
+
* matters. Lifetime — a goal is superseded, a memory accumulates, a slot
|
|
15
|
+
* is garbage. Retrieval — a goal is always in context, memories are
|
|
16
|
+
* recalled by relevance, slots are addressed by name. Write rule — a
|
|
17
|
+
* memory or skill may be proposed by a model (and must therefore carry
|
|
18
|
+
* evidence), a slot is written by the harness and never proposed.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { CLAIM_EVIDENCE_SCHEMA } from './evidence.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A timestamp property: RFC 3339, the suite's only date representation.
|
|
25
|
+
*
|
|
26
|
+
* Both keywords earn their place. `format` is the right metadata and a
|
|
27
|
+
* host that injects a validator with `@jarenjs/formats` registered gets
|
|
28
|
+
* real format checking from it — but `format` is annotation-only by
|
|
29
|
+
* default, so on its own it would enforce NOTHING here, and this package
|
|
30
|
+
* may not depend on `@jarenjs/formats` to find that out. The `pattern`
|
|
31
|
+
* is the zero-dependency enforcement, and it is not pedantry: recency
|
|
32
|
+
* ordering compares these strings lexicographically, so a junk `at`
|
|
33
|
+
* would not be rejected, it would quietly sort wrong.
|
|
34
|
+
*
|
|
35
|
+
* The lexicographic comparison is exact only for a consistent offset.
|
|
36
|
+
* The ledger's own clock emits `Z`; a host mixing offsets gets ordering
|
|
37
|
+
* that is off by the difference, which is why the pattern documents the
|
|
38
|
+
* offset rather than banning it.
|
|
39
|
+
*/
|
|
40
|
+
const AT = {
|
|
41
|
+
type: 'string',
|
|
42
|
+
format: 'date-time',
|
|
43
|
+
pattern: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** A non-empty identifier. */
|
|
47
|
+
const ID = { type: 'string', minLength: 1 };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A stored embedding: a plain array of numbers, never a typed array. A
|
|
51
|
+
* `Float32Array` does not survive the storage boundary — JSON serializes
|
|
52
|
+
* it as a dense object — so the ledger's form is what JSON keeps. What
|
|
53
|
+
* the schema cannot say (every component finite, the length equal to the
|
|
54
|
+
* identity's `dims`) the ledger checks with `isVector` from
|
|
55
|
+
* `@jarenjs/core/vector` in the same gate.
|
|
56
|
+
*/
|
|
57
|
+
const EMBEDDING = { type: 'array', items: { type: 'number' }, minItems: 1 };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A vector's identity: which model produced it, at what width. Vectors
|
|
61
|
+
* from two models are pairwise meaningless and compare into plausible
|
|
62
|
+
* garbage, so an embedding never travels without this and ranked recall
|
|
63
|
+
* refuses to mix two.
|
|
64
|
+
*/
|
|
65
|
+
const EMBEDDED_BY = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
model: { type: 'string', minLength: 1 },
|
|
69
|
+
dims: { type: 'integer', minimum: 1 },
|
|
70
|
+
},
|
|
71
|
+
required: ['model', 'dims'],
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `embedding` and `embeddedBy` are both-or-neither, and nothing requires
|
|
77
|
+
* them: an un-embedded record is exactly as valid as it ever was. The
|
|
78
|
+
* schemas declare no `$schema`, so they compile under the validator's
|
|
79
|
+
* draft-07 default, where "if this member is present, that one is
|
|
80
|
+
* required" is spelled `dependencies` (the `dependentRequired` of
|
|
81
|
+
* 2019-09 and later says the same thing).
|
|
82
|
+
*/
|
|
83
|
+
const EMBEDDING_PAIR = { embedding: ['embeddedBy'], embeddedBy: ['embedding'] };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One active objective. Singular by construction: a second `setGoal`
|
|
87
|
+
* supersedes this one and archives it, so "what am I doing" has exactly
|
|
88
|
+
* one answer at any moment.
|
|
89
|
+
*
|
|
90
|
+
* `progress` is storage only in this order — an append-only record of
|
|
91
|
+
* what happened, with the evidence for it. Nothing here interprets a
|
|
92
|
+
* progress entry or decides when a goal is done.
|
|
93
|
+
*/
|
|
94
|
+
export const GOAL_SCHEMA = {
|
|
95
|
+
$id: 'https://jarenjs.github.io/schemas/ai/ledger-goal.json',
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
objective: { type: 'string', minLength: 1 },
|
|
99
|
+
createdAt: AT,
|
|
100
|
+
status: { enum: ['active', 'done', 'abandoned', 'superseded'] },
|
|
101
|
+
progress: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
at: AT,
|
|
107
|
+
id: ID,
|
|
108
|
+
note: { type: 'string', minLength: 1 },
|
|
109
|
+
// an unevidenced progress note is a claim, not a record
|
|
110
|
+
evidence: { type: 'string', minLength: 1 },
|
|
111
|
+
},
|
|
112
|
+
required: ['at', 'note', 'evidence'],
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
checkpoint: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
version: { const: 1 },
|
|
120
|
+
records: { type: 'array', items: { type: 'object', properties: {
|
|
121
|
+
note: ID, evidence: ID,
|
|
122
|
+
}, required: ['note', 'evidence'], additionalProperties: false } },
|
|
123
|
+
sources: { type: 'array', items: { type: 'object', properties: {
|
|
124
|
+
id: ID, at: AT, record: { type: 'integer', minimum: 0 },
|
|
125
|
+
}, required: ['id', 'at', 'record'], additionalProperties: false } },
|
|
126
|
+
},
|
|
127
|
+
required: ['version', 'records', 'sources'], additionalProperties: false,
|
|
128
|
+
},
|
|
129
|
+
retention: { type: 'object', properties: {
|
|
130
|
+
version: { const: 1 }, reason: { const: 'goal-budget' },
|
|
131
|
+
retired: { type: 'array', items: ID, uniqueItems: true },
|
|
132
|
+
}, required: ['version', 'reason', 'retired'], additionalProperties: false },
|
|
133
|
+
},
|
|
134
|
+
required: ['objective', 'createdAt', 'status', 'progress'],
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A fact worth carrying past this context window.
|
|
140
|
+
*
|
|
141
|
+
* `evidence` is REQUIRED, and that is the load-bearing part: a memory
|
|
142
|
+
* without evidence is not a memory, it is a guess, and a ledger full of
|
|
143
|
+
* guesses is worse than an empty one. It is also what makes a
|
|
144
|
+
* model-proposed refinement auditable — the reviewer of a patch can ask
|
|
145
|
+
* "on what basis" and get an answer from the record itself.
|
|
146
|
+
*
|
|
147
|
+
* `embedding` + `embeddedBy` are optional and travel together: a memory
|
|
148
|
+
* that carries them can be recalled by meaning through the embedder
|
|
149
|
+
* seam; one that does not is recalled by tag and recency exactly as
|
|
150
|
+
* before, and ranked recall reports it as skipped rather than scoring it.
|
|
151
|
+
*/
|
|
152
|
+
export const MEMORY_SCHEMA = {
|
|
153
|
+
$id: 'https://jarenjs.github.io/schemas/ai/ledger-memory.json',
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
id: ID,
|
|
157
|
+
text: { type: 'string', minLength: 1 },
|
|
158
|
+
evidence: { anyOf: [{ type: 'string', minLength: 1 }, CLAIM_EVIDENCE_SCHEMA] },
|
|
159
|
+
tags: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
160
|
+
at: AT,
|
|
161
|
+
// OPTIONAL, as a pair: the vector `recall({ near })` ranks by, and
|
|
162
|
+
// the identity that makes it comparable
|
|
163
|
+
embedding: EMBEDDING,
|
|
164
|
+
embeddedBy: EMBEDDED_BY,
|
|
165
|
+
},
|
|
166
|
+
required: ['id', 'text', 'evidence', 'tags', 'at'],
|
|
167
|
+
dependencies: EMBEDDING_PAIR,
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A reusable recipe: when it applies, what to do, and which tools it
|
|
173
|
+
* needs. Retrieved like a memory and composed into a system prompt by
|
|
174
|
+
* whatever drives the agent — the ledger stores it and nothing more.
|
|
175
|
+
*/
|
|
176
|
+
export const PROGRAM_SKILL_SCHEMA = {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
version: { const: 1 },
|
|
180
|
+
question: { type: 'string', minLength: 1 },
|
|
181
|
+
fingerprint: { type: 'string', pattern: '^[a-f0-9]{64}$' },
|
|
182
|
+
environmentId: { type: 'string', minLength: 1 },
|
|
183
|
+
schemaVersion: { type: 'string', minLength: 1 },
|
|
184
|
+
document: { type: 'object' },
|
|
185
|
+
evidence: { type: 'string', minLength: 1 },
|
|
186
|
+
checked: { const: true },
|
|
187
|
+
},
|
|
188
|
+
required: ['version', 'question', 'fingerprint', 'environmentId', 'schemaVersion', 'document', 'evidence', 'checked'],
|
|
189
|
+
additionalProperties: false,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const SKILL_SCHEMA = {
|
|
193
|
+
$id: 'https://jarenjs.github.io/schemas/ai/ledger-skill.json',
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
id: ID,
|
|
197
|
+
name: { type: 'string', minLength: 1 },
|
|
198
|
+
when: { type: 'string', minLength: 1 },
|
|
199
|
+
instructions: { type: 'string', minLength: 1 },
|
|
200
|
+
program: PROGRAM_SKILL_SCHEMA,
|
|
201
|
+
tools: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
202
|
+
at: AT,
|
|
203
|
+
// the same optional pair as a memory; the text a skill is embedded
|
|
204
|
+
// from is its name, when and instructions together
|
|
205
|
+
embedding: EMBEDDING,
|
|
206
|
+
embeddedBy: EMBEDDED_BY,
|
|
207
|
+
},
|
|
208
|
+
required: ['id', 'name', 'when', 'instructions', 'tools', 'at'],
|
|
209
|
+
dependencies: EMBEDDING_PAIR,
|
|
210
|
+
additionalProperties: false,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* An addressable blob's METADATA. The content lives under a separate
|
|
215
|
+
* storage key and never travels with the metadata — that separation is
|
|
216
|
+
* the whole point: a root request may carry `{ name, kind, size, excerpt }`
|
|
217
|
+
* for a hundred slots without carrying one slot's content.
|
|
218
|
+
*/
|
|
219
|
+
export const SLOT_SCHEMA = {
|
|
220
|
+
$id: 'https://jarenjs.github.io/schemas/ai/ledger-slot.json',
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
name: ID,
|
|
224
|
+
kind: { type: 'string', minLength: 1 },
|
|
225
|
+
size: { type: 'integer', minimum: 0 },
|
|
226
|
+
excerpt: { type: 'string' },
|
|
227
|
+
at: AT,
|
|
228
|
+
// OPTIONAL, and optional on purpose: `size` is what a slot costs and
|
|
229
|
+
// is always known, while `count` is what it CONTAINS — lines,
|
|
230
|
+
// records, pieces — which only the writer knows and only sometimes.
|
|
231
|
+
// A root view listing a hundred slots is far more useful with "1 240
|
|
232
|
+
// lines" beside a size, and a slot whose writer could not say is
|
|
233
|
+
// better off saying nothing than guessing.
|
|
234
|
+
count: { type: 'integer', minimum: 0 },
|
|
235
|
+
pinned: { type: 'boolean' },
|
|
236
|
+
},
|
|
237
|
+
required: ['name', 'kind', 'size', 'excerpt', 'at'],
|
|
238
|
+
additionalProperties: false,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** Every ledger schema by kind — what `createLedger` compiles at construction. */
|
|
242
|
+
export const LEDGER_SCHEMAS = {
|
|
243
|
+
goal: GOAL_SCHEMA,
|
|
244
|
+
memory: MEMORY_SCHEMA,
|
|
245
|
+
skill: SKILL_SCHEMA,
|
|
246
|
+
slot: SLOT_SCHEMA,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* The record shapes, as named types beside the schemas that enforce them.
|
|
251
|
+
*
|
|
252
|
+
* These exist because a strictly-typed consumer (the first one was the
|
|
253
|
+
* tangleai rebuild, 2026-08-24) otherwise hand-writes its own copy of
|
|
254
|
+
* "what addMemory returns" and the copy drifts. The schema stays the
|
|
255
|
+
* runtime contract; the typedef is the same statement made to the
|
|
256
|
+
* compiler, and the two live in one file so a change to one is a diff
|
|
257
|
+
* touching the other's neighbourhood.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* One evidenced progress entry on the active goal.
|
|
262
|
+
* @typedef {object} LedgerProgressEntry
|
|
263
|
+
* @property {string} [id]
|
|
264
|
+
* @property {string} at RFC 3339
|
|
265
|
+
* @property {string} note
|
|
266
|
+
* @property {string} evidence
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The active (or archived) objective — see {@link GOAL_SCHEMA}.
|
|
271
|
+
* @typedef {object} LedgerGoal
|
|
272
|
+
* @property {string} objective
|
|
273
|
+
* @property {string} createdAt RFC 3339
|
|
274
|
+
* @property {'active'|'done'|'abandoned'|'superseded'} status
|
|
275
|
+
* @property {LedgerProgressEntry[]} progress
|
|
276
|
+
* @property {{ version: 1, records: { note: string, evidence: string }[], sources: { id: string, at: string, record: number }[] }} [checkpoint]
|
|
277
|
+
* @property {{ version: 1, reason: 'goal-budget', retired: string[] }} [retention]
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* A vector's identity — see {@link EMBEDDED_BY}. What `recall({ near })`
|
|
282
|
+
* compares against the query embedder's `{ model, dims }` before any
|
|
283
|
+
* arithmetic happens.
|
|
284
|
+
* @typedef {object} LedgerEmbeddedBy
|
|
285
|
+
* @property {string} model
|
|
286
|
+
* @property {number} dims
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The vector pair, both-or-neither — the schemas' `dependencies` rule
|
|
291
|
+
* (see {@link EMBEDDING_PAIR}) stated in the type: a record either
|
|
292
|
+
* carries `embedding` AND `embeddedBy`, or neither. `embedding` is the
|
|
293
|
+
* vector `recall({ near })` ranks by — plain numbers, never a typed
|
|
294
|
+
* array — and `embeddedBy` its identity. Narrow on either member and
|
|
295
|
+
* the other follows: `if (record.embedding) record.embeddedBy.dims`.
|
|
296
|
+
* An orphan vector does not type, just as the ledger refuses it.
|
|
297
|
+
* @typedef {{ embedding?: undefined, embeddedBy?: undefined }
|
|
298
|
+
* | { embedding: number[], embeddedBy: LedgerEmbeddedBy }} LedgerEmbeddingPair
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A stored memory's own members — see {@link MEMORY_SCHEMA}; the
|
|
303
|
+
* record is these plus {@link LedgerEmbeddingPair}.
|
|
304
|
+
* @typedef {object} LedgerMemoryFields
|
|
305
|
+
* @property {string} id
|
|
306
|
+
* @property {string} text
|
|
307
|
+
* @property {string | import('./evidence.js').ClaimEvidenceEnvelope} evidence
|
|
308
|
+
* @property {string[]} tags
|
|
309
|
+
* @property {string} at RFC 3339
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* A stored memory — see {@link MEMORY_SCHEMA}.
|
|
314
|
+
* @typedef {LedgerMemoryFields & LedgerEmbeddingPair} LedgerMemory
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* A stored skill's own members — see {@link SKILL_SCHEMA}; the record
|
|
319
|
+
* is these plus {@link LedgerEmbeddingPair}.
|
|
320
|
+
* @typedef {object} LedgerSkillFields
|
|
321
|
+
* @property {string} id
|
|
322
|
+
* @property {string} name
|
|
323
|
+
* @property {string} when
|
|
324
|
+
* @property {string} instructions
|
|
325
|
+
* @property {string[]} tools
|
|
326
|
+
* @property {string} at RFC 3339
|
|
327
|
+
* @property {{ version: 1, question: string, fingerprint: string, environmentId: string,
|
|
328
|
+
* schemaVersion: string, document: any, evidence: string, checked: true }} [program]
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* A stored skill — see {@link SKILL_SCHEMA}.
|
|
333
|
+
* @typedef {LedgerSkillFields & LedgerEmbeddingPair} LedgerSkill
|
|
334
|
+
*/
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* A slot's metadata — see {@link SLOT_SCHEMA}. Never the content.
|
|
338
|
+
* @typedef {object} LedgerSlot
|
|
339
|
+
* @property {string} name
|
|
340
|
+
* @property {string} kind
|
|
341
|
+
* @property {number} size
|
|
342
|
+
* @property {string} excerpt
|
|
343
|
+
* @property {string} at RFC 3339
|
|
344
|
+
* @property {number} [count]
|
|
345
|
+
* @property {boolean} [pinned]
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The one rejection shape every schema-guarded write answers with —
|
|
350
|
+
* produced by `invalidInput` in `check.js`, named here because this is
|
|
351
|
+
* where a consumer of the ledger's API goes looking for "what comes
|
|
352
|
+
* back when a write is refused".
|
|
353
|
+
* @typedef {object} LedgerRejection
|
|
354
|
+
* @property {string} error
|
|
355
|
+
* @property {any[]} errors
|
|
356
|
+
* @property {any} inputSchema
|
|
357
|
+
*/
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-discriminated RFC 6902 operations. Provider decoding uses this same
|
|
3
|
+
* schema in non-strict mode; local validation remains authoritative.
|
|
4
|
+
* @param {{ maxOps?: number }} [options]
|
|
5
|
+
* @returns {any}
|
|
6
|
+
*/
|
|
7
|
+
export function refinementPatchSchema(options?: {
|
|
8
|
+
maxOps?: number;
|
|
9
|
+
}): any;
|
|
10
|
+
/**
|
|
11
|
+
* Where a refinement may write. Anchored, and read as: append or address
|
|
12
|
+
* one memory, append or address one skill, append one progress entry.
|
|
13
|
+
*
|
|
14
|
+
* `-` is RFC 6902's "end of array" and is how an append is written.
|
|
15
|
+
* There is deliberately no way to address INSIDE a record (`/memories/0/text`):
|
|
16
|
+
* a memory is revised by replacing it whole, with fresh evidence, so
|
|
17
|
+
* that every stored record was validated as a whole exactly once.
|
|
18
|
+
*/
|
|
19
|
+
export const REFINEMENT_PATH_PATTERN: "^(/memories/(-|[0-9]+)|/skills/(-|[0-9]+)|/goal/progress/-)$";
|
|
20
|
+
/** The default cap on operations per refinement. */
|
|
21
|
+
export const DEFAULT_MAX_OPS: 6;
|
|
22
|
+
export namespace MEMORY_PROPOSAL_SCHEMA {
|
|
23
|
+
let type: string;
|
|
24
|
+
namespace properties {
|
|
25
|
+
namespace text {
|
|
26
|
+
let type_1: string;
|
|
27
|
+
export { type_1 as type };
|
|
28
|
+
export let minLength: number;
|
|
29
|
+
export let description: string;
|
|
30
|
+
}
|
|
31
|
+
namespace evidence {
|
|
32
|
+
let anyOf: ({
|
|
33
|
+
type: "object";
|
|
34
|
+
properties: Record<string, object>;
|
|
35
|
+
required: string[];
|
|
36
|
+
additionalProperties: false;
|
|
37
|
+
} | {
|
|
38
|
+
type: string;
|
|
39
|
+
minLength: number;
|
|
40
|
+
})[];
|
|
41
|
+
}
|
|
42
|
+
namespace tags {
|
|
43
|
+
let type_2: string;
|
|
44
|
+
export { type_2 as type };
|
|
45
|
+
export namespace items {
|
|
46
|
+
let type_3: string;
|
|
47
|
+
export { type_3 as type };
|
|
48
|
+
let minLength_1: number;
|
|
49
|
+
export { minLength_1 as minLength };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let required: string[];
|
|
54
|
+
let additionalProperties: boolean;
|
|
55
|
+
}
|
|
56
|
+
export namespace SKILL_PROPOSAL_SCHEMA {
|
|
57
|
+
let type_4: string;
|
|
58
|
+
export { type_4 as type };
|
|
59
|
+
export namespace properties_1 {
|
|
60
|
+
namespace name {
|
|
61
|
+
let type_5: string;
|
|
62
|
+
export { type_5 as type };
|
|
63
|
+
let minLength_2: number;
|
|
64
|
+
export { minLength_2 as minLength };
|
|
65
|
+
}
|
|
66
|
+
namespace when {
|
|
67
|
+
let type_6: string;
|
|
68
|
+
export { type_6 as type };
|
|
69
|
+
let minLength_3: number;
|
|
70
|
+
export { minLength_3 as minLength };
|
|
71
|
+
let description_1: string;
|
|
72
|
+
export { description_1 as description };
|
|
73
|
+
}
|
|
74
|
+
namespace instructions {
|
|
75
|
+
let type_7: string;
|
|
76
|
+
export { type_7 as type };
|
|
77
|
+
let minLength_4: number;
|
|
78
|
+
export { minLength_4 as minLength };
|
|
79
|
+
let description_2: string;
|
|
80
|
+
export { description_2 as description };
|
|
81
|
+
}
|
|
82
|
+
namespace tools {
|
|
83
|
+
let type_8: string;
|
|
84
|
+
export { type_8 as type };
|
|
85
|
+
export namespace items_1 {
|
|
86
|
+
let type_9: string;
|
|
87
|
+
export { type_9 as type };
|
|
88
|
+
let minLength_5: number;
|
|
89
|
+
export { minLength_5 as minLength };
|
|
90
|
+
}
|
|
91
|
+
export { items_1 as items };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export { properties_1 as properties };
|
|
95
|
+
let required_1: string[];
|
|
96
|
+
export { required_1 as required };
|
|
97
|
+
let additionalProperties_1: boolean;
|
|
98
|
+
export { additionalProperties_1 as additionalProperties };
|
|
99
|
+
}
|
|
100
|
+
export namespace PROGRESS_PROPOSAL_SCHEMA {
|
|
101
|
+
let type_10: string;
|
|
102
|
+
export { type_10 as type };
|
|
103
|
+
export namespace properties_2 {
|
|
104
|
+
export namespace note {
|
|
105
|
+
let type_11: string;
|
|
106
|
+
export { type_11 as type };
|
|
107
|
+
let minLength_6: number;
|
|
108
|
+
export { minLength_6 as minLength };
|
|
109
|
+
let description_3: string;
|
|
110
|
+
export { description_3 as description };
|
|
111
|
+
}
|
|
112
|
+
export namespace evidence_1 {
|
|
113
|
+
let type_12: string;
|
|
114
|
+
export { type_12 as type };
|
|
115
|
+
let minLength_7: number;
|
|
116
|
+
export { minLength_7 as minLength };
|
|
117
|
+
let description_4: string;
|
|
118
|
+
export { description_4 as description };
|
|
119
|
+
}
|
|
120
|
+
export { evidence_1 as evidence };
|
|
121
|
+
}
|
|
122
|
+
export { properties_2 as properties };
|
|
123
|
+
let required_2: string[];
|
|
124
|
+
export { required_2 as required };
|
|
125
|
+
let additionalProperties_2: boolean;
|
|
126
|
+
export { additionalProperties_2 as additionalProperties };
|
|
127
|
+
}
|
|
128
|
+
/** The full schema used for handwritten and generated proposals alike. */
|
|
129
|
+
export const REFINEMENT_PATCH_SCHEMA: any;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* The refinement patch: a deliberately small subset of RFC 6902.
|
|
4
|
+
*
|
|
5
|
+
* A model that has finished a run is asked what it learned. The unsafe
|
|
6
|
+
* answer is a rewritten prompt or a restated set of memories — a
|
|
7
|
+
* rewrite is unreviewable, unbounded, and a model asked to restate what
|
|
8
|
+
* it remembers will drift it. The safe answer is a PATCH: small enough
|
|
9
|
+
* to read, addressed enough to audit, and reversible with a snapshot.
|
|
10
|
+
*
|
|
11
|
+
* Three restrictions, and each one exists because the alternative fails
|
|
12
|
+
* on the tier this package targets:
|
|
13
|
+
*
|
|
14
|
+
* - **`path` is a pattern, not a free string.** It matches exactly the
|
|
15
|
+
* supplemental subtree — append or replace a memory, append or
|
|
16
|
+
* replace a skill, append one progress entry — and nothing else. The
|
|
17
|
+
* base system prompt is not merely undocumented as a target, it is
|
|
18
|
+
* unaddressable: it is not in the document a refinement is applied
|
|
19
|
+
* to, and no path that could reach it matches this pattern. That is
|
|
20
|
+
* D5 asserted rather than described.
|
|
21
|
+
* - **`op` is three verbs.** `move`, `copy` and `test` are legal RFC
|
|
22
|
+
* 6902 and useless here; every one of them is another shape a small
|
|
23
|
+
* model can get subtly wrong, and none of them expresses anything
|
|
24
|
+
* `add`/`replace`/`remove` cannot.
|
|
25
|
+
* - **The number of operations is capped.** A refinement is meant to be
|
|
26
|
+
* a few evidence-backed updates. A patch of forty operations is a
|
|
27
|
+
* rewrite wearing a patch's clothes, and it is also the shape that
|
|
28
|
+
* makes a repair round useless — the model cannot tell which of forty
|
|
29
|
+
* operations the error came from.
|
|
30
|
+
*
|
|
31
|
+
* `value` carries no `id` and no `at` on purpose. Identity and time are
|
|
32
|
+
* the ledger's to mint: a model that could choose an id could overwrite
|
|
33
|
+
* a record it never read, and a model that could choose a timestamp
|
|
34
|
+
* could put a memory in front of one that came later — the recency
|
|
35
|
+
* ordering everything downstream depends on is not the model's to set.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { CLAIM_EVIDENCE_SCHEMA } from './evidence.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where a refinement may write. Anchored, and read as: append or address
|
|
42
|
+
* one memory, append or address one skill, append one progress entry.
|
|
43
|
+
*
|
|
44
|
+
* `-` is RFC 6902's "end of array" and is how an append is written.
|
|
45
|
+
* There is deliberately no way to address INSIDE a record (`/memories/0/text`):
|
|
46
|
+
* a memory is revised by replacing it whole, with fresh evidence, so
|
|
47
|
+
* that every stored record was validated as a whole exactly once.
|
|
48
|
+
*/
|
|
49
|
+
export const REFINEMENT_PATH_PATTERN =
|
|
50
|
+
'^(/memories/(-|[0-9]+)|/skills/(-|[0-9]+)|/goal/progress/-)$';
|
|
51
|
+
|
|
52
|
+
/** The default cap on operations per refinement. */
|
|
53
|
+
export const DEFAULT_MAX_OPS = 6;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A proposed memory. `evidence` is required here AND by the ledger's own
|
|
57
|
+
* schema — the same requirement twice, on purpose: this copy is what
|
|
58
|
+
* constrains decoding (a model generating against it tends to write the
|
|
59
|
+
* evidence rather than be corrected into it), and the ledger's copy is
|
|
60
|
+
* what makes the rule true even for a patch that never went near a
|
|
61
|
+
* model. It is the mechanism that stops a refinement laundering a
|
|
62
|
+
* hallucination into durable state.
|
|
63
|
+
*/
|
|
64
|
+
export const MEMORY_PROPOSAL_SCHEMA = {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
text: { type: 'string', minLength: 1, description: 'The fact worth carrying, in one sentence.' },
|
|
68
|
+
evidence: { anyOf: [{ type: 'string', minLength: 1 }, CLAIM_EVIDENCE_SCHEMA] },
|
|
69
|
+
tags: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
70
|
+
},
|
|
71
|
+
required: ['text', 'evidence'],
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** A proposed skill: when it applies, what to do, which tools it needs. */
|
|
76
|
+
export const SKILL_PROPOSAL_SCHEMA = {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
name: { type: 'string', minLength: 1 },
|
|
80
|
+
when: { type: 'string', minLength: 1, description: 'The situation this recipe applies to.' },
|
|
81
|
+
instructions: { type: 'string', minLength: 1, description: 'The steps, concretely.' },
|
|
82
|
+
tools: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
83
|
+
},
|
|
84
|
+
required: ['name', 'when', 'instructions'],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** A proposed progress entry against the active goal. */
|
|
89
|
+
export const PROGRESS_PROPOSAL_SCHEMA = {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
note: { type: 'string', minLength: 1, description: 'What was done or established.' },
|
|
93
|
+
evidence: { type: 'string', minLength: 1, description: 'What in the run establishes it.' },
|
|
94
|
+
},
|
|
95
|
+
required: ['note', 'evidence'],
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Path-discriminated RFC 6902 operations. Provider decoding uses this same
|
|
101
|
+
* schema in non-strict mode; local validation remains authoritative.
|
|
102
|
+
* @param {{ maxOps?: number }} [options]
|
|
103
|
+
* @returns {any}
|
|
104
|
+
*/
|
|
105
|
+
export function refinementPatchSchema(options = {}) {
|
|
106
|
+
const maxOps = options.maxOps ?? DEFAULT_MAX_OPS;
|
|
107
|
+
if (!Number.isSafeInteger(maxOps) || maxOps < 0)
|
|
108
|
+
throw new TypeError('maxOps must be a non-negative safe integer');
|
|
109
|
+
const branch = (op, pattern, value) => ({
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: { op: { enum: op }, path: { type: 'string', pattern },
|
|
112
|
+
...(value ? { value } : {}) },
|
|
113
|
+
required: value ? ['op', 'path', 'value'] : ['op', 'path'],
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
});
|
|
116
|
+
const index = '(0|[1-9][0-9]*)';
|
|
117
|
+
return {
|
|
118
|
+
$id: 'https://jarenjs.github.io/schemas/ai/refinement-patch.json',
|
|
119
|
+
title: 'Refinement patch',
|
|
120
|
+
type: 'array', maxItems: maxOps,
|
|
121
|
+
$defs: { memory: MEMORY_PROPOSAL_SCHEMA, skill: SKILL_PROPOSAL_SCHEMA,
|
|
122
|
+
progress: PROGRESS_PROPOSAL_SCHEMA },
|
|
123
|
+
items: { oneOf: [
|
|
124
|
+
...[['memories', 'memory'], ['skills', 'skill']].flatMap(([path, kind]) => [
|
|
125
|
+
branch(['add'], `^/${path}/(-|${index})$`, { $ref: `#/$defs/${kind}` }),
|
|
126
|
+
branch(['replace'], `^/${path}/${index}$`, { $ref: `#/$defs/${kind}` }),
|
|
127
|
+
branch(['remove'], `^/${path}/${index}$`, null),
|
|
128
|
+
]),
|
|
129
|
+
branch(['add'], '^/goal/progress/-$', { $ref: '#/$defs/progress' }),
|
|
130
|
+
] },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The full schema used for handwritten and generated proposals alike. */
|
|
135
|
+
export const REFINEMENT_PATCH_SCHEMA = refinementPatchSchema();
|