@unson/brainbase-mcp 0.4.1 → 0.5.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/README.md +5 -5
- package/contracts/judgment-dag/digest.json +4 -4
- package/contracts/judgment-dag/source-lock.json +65 -1
- package/contracts/judgment-value-proof/fixture.json +54 -0
- package/contracts/judgment-value-proof/schema.json +163 -0
- package/dist/cli.js +17 -0
- package/dist/embedding-provider.d.ts +72 -0
- package/dist/embedding-provider.js +391 -0
- package/dist/graph-retrieval.d.ts +94 -0
- package/dist/graph-retrieval.js +443 -0
- package/dist/judgment-dag-artifact-store.d.ts +29 -0
- package/dist/judgment-dag-artifact-store.js +416 -0
- package/dist/judgment-dag-replay-evaluation.d.ts +108 -0
- package/dist/judgment-dag-replay-evaluation.js +424 -0
- package/dist/judgment-dag.d.ts +4 -0
- package/dist/judgment-dag.js +2 -0
- package/dist/judgment-value-proof.d.ts +92 -0
- package/dist/judgment-value-proof.js +229 -0
- package/dist/organization-graph.d.ts +76 -0
- package/dist/organization-graph.js +331 -0
- package/dist/portable-graph.d.ts +39 -0
- package/dist/portable-graph.js +240 -0
- package/dist/server.d.ts +42 -3
- package/dist/server.js +52 -7
- package/docs/management/judgment-dag-milestones.md +31 -1
- package/package.json +16 -6
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { validateCanonicalGraph } from './canonical-graph.js';
|
|
3
|
+
import { retrieveGraph } from './graph-retrieval.js';
|
|
4
|
+
/** The public, read-only interchange format for a canonical Graph v2 snapshot. */
|
|
5
|
+
export const PORTABLE_GRAPH_SCHEMA_VERSION = 1;
|
|
6
|
+
/** Bounds keep an imported snapshot finite before it reaches a runtime or model. */
|
|
7
|
+
export const PORTABLE_GRAPH_MAX_ENTITIES = 10_000;
|
|
8
|
+
export const PORTABLE_GRAPH_MAX_EDGES = 25_000;
|
|
9
|
+
export const PORTABLE_GRAPH_MAX_DECISIONS = 10_000;
|
|
10
|
+
export const PORTABLE_GRAPH_MAX_JSON_BYTES = 8_000_000;
|
|
11
|
+
export const PORTABLE_GRAPH_MAX_JSON_DEPTH = 64;
|
|
12
|
+
/**
|
|
13
|
+
* Create a detached Graph v2 bundle for transfer between the OSS and
|
|
14
|
+
* organization runtimes. Personal KG, legacy relationships, source files, and
|
|
15
|
+
* the local data directory are deliberately outside this contract.
|
|
16
|
+
*/
|
|
17
|
+
export function createPortableGraph(os) {
|
|
18
|
+
if (!os || typeof os !== 'object' || !('graph' in os) || !('decisions' in os)) {
|
|
19
|
+
throw new Error('PORTABLE-GRAPH-SOURCE: a PersonalOs with graph and decisions is required');
|
|
20
|
+
}
|
|
21
|
+
validateCanonicalGraph(os.graph);
|
|
22
|
+
if (os.graph.version !== 2) {
|
|
23
|
+
throw new Error('PORTABLE-GRAPH-V1-REJECTED: only Graph v2 can be exported');
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(os.decisions)) {
|
|
26
|
+
throw new Error('PORTABLE-GRAPH-DECISIONS: decisions must be an array');
|
|
27
|
+
}
|
|
28
|
+
const decisionIds = new Set(os.graph.entities.filter((entity) => entity.type === 'decision').map((entity) => entity.id));
|
|
29
|
+
const decisions = os.decisions
|
|
30
|
+
.filter((decision) => decisionIds.has(decision.id))
|
|
31
|
+
.map((decision) => structuredClone(decision))
|
|
32
|
+
.sort((left, right) => left.id.localeCompare(right.id, 'en'));
|
|
33
|
+
const bundle = {
|
|
34
|
+
schemaVersion: PORTABLE_GRAPH_SCHEMA_VERSION,
|
|
35
|
+
graph: structuredClone(os.graph),
|
|
36
|
+
decisions
|
|
37
|
+
};
|
|
38
|
+
validatePortableGraph(bundle);
|
|
39
|
+
return bundle;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate the exact portable contract. Unknown top-level fields are rejected
|
|
43
|
+
* so an organization runtime cannot silently ignore a new capability. Unknown
|
|
44
|
+
* fields inside Graph records are retained after the canonical Graph validator
|
|
45
|
+
* accepts them, which keeps forward-compatible metadata lossless.
|
|
46
|
+
*/
|
|
47
|
+
export function validatePortableGraph(value) {
|
|
48
|
+
assertJsonValue(value, '$', new WeakSet(), 0);
|
|
49
|
+
if (!isPlainRecord(value))
|
|
50
|
+
throw new Error('PORTABLE-GRAPH-SHAPE: bundle must be a plain JSON object');
|
|
51
|
+
const topLevelKeys = Object.getOwnPropertyNames(value).sort();
|
|
52
|
+
const expected = ['decisions', 'graph', 'schemaVersion'];
|
|
53
|
+
const unknownKeys = topLevelKeys.filter((key) => !expected.includes(key));
|
|
54
|
+
if (unknownKeys.length > 0) {
|
|
55
|
+
throw new Error(`PORTABLE-GRAPH-SHAPE: unknown top-level field ${unknownKeys[0]}`);
|
|
56
|
+
}
|
|
57
|
+
if (value.schemaVersion !== PORTABLE_GRAPH_SCHEMA_VERSION) {
|
|
58
|
+
throw new Error(`PORTABLE-GRAPH-VERSION: unsupported portable schema version ${String(value.schemaVersion)}`);
|
|
59
|
+
}
|
|
60
|
+
if (!isPlainRecord(value.graph)) {
|
|
61
|
+
throw new Error('PORTABLE-GRAPH-SHAPE: graph must be a plain JSON object');
|
|
62
|
+
}
|
|
63
|
+
if (value.graph.version === 1) {
|
|
64
|
+
throw new Error('PORTABLE-GRAPH-V1-REJECTED: portable bundles require Graph v2');
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(value.decisions)) {
|
|
67
|
+
throw new Error('PORTABLE-GRAPH-DECISIONS: decisions must be an array');
|
|
68
|
+
}
|
|
69
|
+
const graph = value.graph;
|
|
70
|
+
if (graph.version !== 2) {
|
|
71
|
+
// Keep the canonical validator's explicit version error for other future
|
|
72
|
+
// versions while still making the portable boundary clear to callers.
|
|
73
|
+
validateCanonicalGraph(graph);
|
|
74
|
+
}
|
|
75
|
+
validateCanonicalGraph(graph);
|
|
76
|
+
if (graph.entities.length > PORTABLE_GRAPH_MAX_ENTITIES) {
|
|
77
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: graph.entities exceeds ${PORTABLE_GRAPH_MAX_ENTITIES}`);
|
|
78
|
+
}
|
|
79
|
+
if (graph.edges.length > PORTABLE_GRAPH_MAX_EDGES) {
|
|
80
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: graph.edges exceeds ${PORTABLE_GRAPH_MAX_EDGES}`);
|
|
81
|
+
}
|
|
82
|
+
if (value.decisions.length > PORTABLE_GRAPH_MAX_DECISIONS) {
|
|
83
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: decisions exceeds ${PORTABLE_GRAPH_MAX_DECISIONS}`);
|
|
84
|
+
}
|
|
85
|
+
validateDecisionRecords(graph, value.decisions);
|
|
86
|
+
const bytes = Buffer.byteLength(canonicalPortableJson(value), 'utf8');
|
|
87
|
+
if (bytes > PORTABLE_GRAPH_MAX_JSON_BYTES) {
|
|
88
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: JSON payload exceeds ${PORTABLE_GRAPH_MAX_JSON_BYTES} bytes`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Rehydrate a validated bundle into the shared retrieval input shape. */
|
|
92
|
+
export function hydratePortableGraph(bundle) {
|
|
93
|
+
validatePortableGraph(bundle);
|
|
94
|
+
return {
|
|
95
|
+
dataDir: '',
|
|
96
|
+
graph: structuredClone(bundle.graph),
|
|
97
|
+
personalKg: [],
|
|
98
|
+
relationships: { version: 1, relationships: [] },
|
|
99
|
+
decisions: structuredClone(bundle.decisions),
|
|
100
|
+
// sourceCount counts imported source files in the local SSOT. A portable
|
|
101
|
+
// Graph bundle contains no source files, so zero is the truthful value.
|
|
102
|
+
sourceCount: 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Use exactly the OSS retrieval implementation against a portable bundle. */
|
|
106
|
+
export function retrievePortableGraph(bundle, input, provider) {
|
|
107
|
+
return retrieveGraph(hydratePortableGraph(bundle), input, provider);
|
|
108
|
+
}
|
|
109
|
+
/** Canonical JSON with recursively sorted object keys and preserved array order. */
|
|
110
|
+
export function canonicalPortableJson(value) {
|
|
111
|
+
assertJsonValue(value, '$', new WeakSet(), 0);
|
|
112
|
+
return stableJson(value, 0);
|
|
113
|
+
}
|
|
114
|
+
/** Stable content identity for a portable Graph bundle. */
|
|
115
|
+
export function portableGraphDigest(value) {
|
|
116
|
+
validatePortableGraph(value);
|
|
117
|
+
return `sha256:${createHash('sha256').update(canonicalPortableJson(value), 'utf8').digest('hex')}`;
|
|
118
|
+
}
|
|
119
|
+
/** Descriptive alias for callers that prefer the content-identity wording. */
|
|
120
|
+
export const digestPortableGraph = portableGraphDigest;
|
|
121
|
+
function validateDecisionRecords(graph, decisions) {
|
|
122
|
+
const decisionEntities = new Set(graph.entities.filter((entity) => entity.type === 'decision').map((entity) => entity.id));
|
|
123
|
+
const records = new Map();
|
|
124
|
+
decisions.forEach((value, index) => {
|
|
125
|
+
if (!isPlainRecord(value))
|
|
126
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-SHAPE at decisions[${index}]`);
|
|
127
|
+
requireNonEmptyString(value.id, `decisions[${index}].id`);
|
|
128
|
+
requireNonEmptyString(value.title, `decisions[${index}].title`);
|
|
129
|
+
requireNonEmptyString(value.decision, `decisions[${index}].decision`);
|
|
130
|
+
if (records.has(value.id))
|
|
131
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-ID-UNIQUE: duplicate decision ID ${value.id}`);
|
|
132
|
+
records.set(value.id, value);
|
|
133
|
+
optionalString(value.topic, `decisions[${index}].topic`);
|
|
134
|
+
optionalString(value.rationale, `decisions[${index}].rationale`);
|
|
135
|
+
optionalString(value.updatedAt, `decisions[${index}].updatedAt`);
|
|
136
|
+
if (value.effectiveAt !== undefined) {
|
|
137
|
+
if (typeof value.effectiveAt !== 'string' || !isRfc3339(value.effectiveAt)) {
|
|
138
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-DATE at decisions[${index}].effectiveAt`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (value.tags !== undefined && (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string'))) {
|
|
142
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-TAGS at decisions[${index}].tags`);
|
|
143
|
+
}
|
|
144
|
+
if (value.supersedes !== undefined) {
|
|
145
|
+
if (!Array.isArray(value.supersedes) || value.supersedes.some((id) => typeof id !== 'string' || id.trim() === '')) {
|
|
146
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-SUPERSEDES at decisions[${index}].supersedes`);
|
|
147
|
+
}
|
|
148
|
+
const refs = new Set();
|
|
149
|
+
for (const target of value.supersedes) {
|
|
150
|
+
if (refs.has(target))
|
|
151
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: duplicate supersedes reference ${target}`);
|
|
152
|
+
refs.add(target);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
for (const id of decisionEntities) {
|
|
157
|
+
if (!records.has(id))
|
|
158
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: missing decision record for Graph entity ${id}`);
|
|
159
|
+
}
|
|
160
|
+
for (const [id, decision] of records) {
|
|
161
|
+
const entity = graph.entities.find((candidate) => candidate.id === id);
|
|
162
|
+
if (!entity || entity.type !== 'decision') {
|
|
163
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: decision record ${id} has no Graph decision entity`);
|
|
164
|
+
}
|
|
165
|
+
for (const target of decision.supersedes ?? []) {
|
|
166
|
+
if (!records.has(target)) {
|
|
167
|
+
throw new Error(`PORTABLE-GRAPH-DECISION-REFERENCE: decision ${id} supersedes missing decision ${target}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function requireNonEmptyString(value, path) {
|
|
173
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
174
|
+
throw new Error(`PORTABLE-GRAPH-STRING at ${path}`);
|
|
175
|
+
}
|
|
176
|
+
function optionalString(value, path) {
|
|
177
|
+
if (value !== undefined && typeof value !== 'string')
|
|
178
|
+
throw new Error(`PORTABLE-GRAPH-STRING at ${path}`);
|
|
179
|
+
}
|
|
180
|
+
function isRfc3339(value) {
|
|
181
|
+
return /^(?:\d{4})-(?:\d{2})-(?:\d{2})T(?:\d{2}):(?:\d{2}):(?:\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(value) && Number.isFinite(Date.parse(value));
|
|
182
|
+
}
|
|
183
|
+
function isPlainRecord(value) {
|
|
184
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
185
|
+
return false;
|
|
186
|
+
const prototype = Object.getPrototypeOf(value);
|
|
187
|
+
return prototype === Object.prototype || prototype === null;
|
|
188
|
+
}
|
|
189
|
+
function assertJsonValue(value, path, active, depth) {
|
|
190
|
+
if (depth > PORTABLE_GRAPH_MAX_JSON_DEPTH)
|
|
191
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: JSON depth exceeds ${PORTABLE_GRAPH_MAX_JSON_DEPTH} at ${path}`);
|
|
192
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
193
|
+
return;
|
|
194
|
+
if (typeof value === 'number') {
|
|
195
|
+
if (!Number.isFinite(value))
|
|
196
|
+
throw new Error(`PORTABLE-GRAPH-JSON: non-finite number at ${path}`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (typeof value !== 'object')
|
|
200
|
+
throw new Error(`PORTABLE-GRAPH-JSON: non-JSON value at ${path}`);
|
|
201
|
+
if (active.has(value))
|
|
202
|
+
throw new Error(`PORTABLE-GRAPH-JSON: cyclic value at ${path}`);
|
|
203
|
+
active.add(value);
|
|
204
|
+
try {
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
207
|
+
if (!(index in value))
|
|
208
|
+
throw new Error(`PORTABLE-GRAPH-JSON: sparse array at ${path}[${index}]`);
|
|
209
|
+
assertJsonValue(value[index], `${path}[${index}]`, active, depth + 1);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!isPlainRecord(value))
|
|
214
|
+
throw new Error(`PORTABLE-GRAPH-JSON: value at ${path} must be a plain JSON object`);
|
|
215
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
216
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
217
|
+
if (!descriptor || !('value' in descriptor))
|
|
218
|
+
throw new Error(`PORTABLE-GRAPH-JSON: accessor at ${path}.${key}`);
|
|
219
|
+
assertJsonValue(descriptor.value, `${path}.${key}`, active, depth + 1);
|
|
220
|
+
}
|
|
221
|
+
if (Object.getOwnPropertySymbols(value).length > 0)
|
|
222
|
+
throw new Error(`PORTABLE-GRAPH-JSON: symbol key at ${path}`);
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
active.delete(value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function stableJson(value, depth) {
|
|
229
|
+
if (depth > PORTABLE_GRAPH_MAX_JSON_DEPTH)
|
|
230
|
+
throw new Error(`PORTABLE-GRAPH-BOUND: JSON depth exceeds ${PORTABLE_GRAPH_MAX_JSON_DEPTH}`);
|
|
231
|
+
if (value === null)
|
|
232
|
+
return 'null';
|
|
233
|
+
if (typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
|
|
234
|
+
return JSON.stringify(value);
|
|
235
|
+
if (Array.isArray(value))
|
|
236
|
+
return `[${value.map((item) => stableJson(item, depth + 1)).join(',')}]`;
|
|
237
|
+
const record = value;
|
|
238
|
+
const pairs = Object.getOwnPropertyNames(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key], depth + 1)}`);
|
|
239
|
+
return `{${pairs.join(',')}}`;
|
|
240
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
export declare const toolDefinitions: readonly [{
|
|
3
3
|
readonly name: "get_context";
|
|
4
|
-
readonly description: "Return initial
|
|
4
|
+
readonly description: "Return an initial owner context snapshot. For a question needing related judgments or evidence, use search with an explicit relation plan; this snapshot is not a retrieval answer.";
|
|
5
5
|
readonly inputSchema: {
|
|
6
6
|
readonly type: "object";
|
|
7
7
|
readonly properties: {
|
|
@@ -35,19 +35,58 @@ export declare const toolDefinitions: readonly [{
|
|
|
35
35
|
};
|
|
36
36
|
}, {
|
|
37
37
|
readonly name: "search";
|
|
38
|
-
readonly description: "
|
|
38
|
+
readonly description: "Find canonical Graph candidates, follow explicit typed relations, and return evidence and missing information. Use seedIds from resolve_entity/list_entities or previous results and steps from the observed relation catalog. Configured embeddings enable semantic discovery; without them discovery is lexical and reported as limited. Never treat similarity as proof or empty results as absence. Personal KG has its own search tool.";
|
|
39
39
|
readonly inputSchema: {
|
|
40
40
|
readonly type: "object";
|
|
41
41
|
readonly required: readonly ["query"];
|
|
42
|
+
readonly additionalProperties: false;
|
|
42
43
|
readonly properties: {
|
|
43
44
|
readonly dataDir: {
|
|
44
45
|
readonly type: "string";
|
|
45
46
|
};
|
|
47
|
+
readonly asOf: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
readonly format: "date-time";
|
|
50
|
+
readonly description: "Compatibility alias for as_of.";
|
|
51
|
+
};
|
|
46
52
|
readonly query: {
|
|
47
53
|
readonly type: "string";
|
|
54
|
+
readonly minLength: 1;
|
|
55
|
+
};
|
|
56
|
+
readonly seedIds: {
|
|
57
|
+
readonly type: "array";
|
|
58
|
+
readonly minItems: 1;
|
|
59
|
+
readonly maxItems: 10;
|
|
60
|
+
readonly items: {
|
|
61
|
+
readonly type: "string";
|
|
62
|
+
};
|
|
63
|
+
readonly description: "Canonical IDs chosen by the calling model; no embeddings needed.";
|
|
64
|
+
};
|
|
65
|
+
readonly steps: {
|
|
66
|
+
readonly type: "array";
|
|
67
|
+
readonly minItems: 1;
|
|
68
|
+
readonly maxItems: 3;
|
|
69
|
+
readonly items: {
|
|
70
|
+
readonly type: "object";
|
|
71
|
+
readonly additionalProperties: false;
|
|
72
|
+
readonly required: readonly ["relation", "direction"];
|
|
73
|
+
readonly properties: {
|
|
74
|
+
readonly relation: {
|
|
75
|
+
readonly enum: readonly ["member_of", "participates_in", "accountable_for", "owned_by", "governs", "supersedes"];
|
|
76
|
+
};
|
|
77
|
+
readonly direction: {
|
|
78
|
+
readonly enum: readonly ["incoming", "outgoing"];
|
|
79
|
+
};
|
|
80
|
+
readonly targetType: {
|
|
81
|
+
readonly enum: readonly ["person", "org", "project", "decision"];
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
};
|
|
48
85
|
};
|
|
49
86
|
readonly limit: {
|
|
50
|
-
readonly type: "
|
|
87
|
+
readonly type: "integer";
|
|
88
|
+
readonly minimum: 1;
|
|
89
|
+
readonly maximum: 50;
|
|
51
90
|
};
|
|
52
91
|
readonly project: {
|
|
53
92
|
readonly type: "string";
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { createOrganizationGraphConfig, createOrganizationGraphClient } from './organization-graph.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { retrieveGraph } from './graph-retrieval.js';
|
|
4
|
+
import { createEmbeddingProviderFromEnv } from './embedding-provider.js';
|
|
2
5
|
import { readFile } from 'node:fs/promises';
|
|
3
6
|
import { join } from 'node:path';
|
|
4
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -22,8 +25,16 @@ const argsSchema = z.object({
|
|
|
22
25
|
ontologyVersion: z.string().optional(),
|
|
23
26
|
asOf: z.string().datetime({ offset: true }).optional(),
|
|
24
27
|
project: z.string().min(1).optional(),
|
|
25
|
-
as_of: z.string().datetime({ offset: true }).optional()
|
|
28
|
+
as_of: z.string().datetime({ offset: true }).optional(),
|
|
29
|
+
seedIds: z.array(z.string().min(1)).min(1).max(10).optional(),
|
|
30
|
+
steps: z.array(z.object({
|
|
31
|
+
relation: z.enum(['member_of', 'participates_in', 'accountable_for', 'owned_by', 'governs', 'supersedes']),
|
|
32
|
+
direction: z.enum(['incoming', 'outgoing']),
|
|
33
|
+
targetType: z.enum(['person', 'org', 'project', 'decision']).optional()
|
|
34
|
+
}).strict()).min(1).max(3).optional(),
|
|
35
|
+
mode: z.never().optional()
|
|
26
36
|
});
|
|
37
|
+
const searchArgsSchema = argsSchema.pick({ dataDir: true, query: true, limit: true, project: true, as_of: true, asOf: true, seedIds: true, steps: true, mode: true }).strict();
|
|
27
38
|
const mentionSpanSchema = z.object({
|
|
28
39
|
start: z.number().int().nonnegative(),
|
|
29
40
|
end: z.number().int().positive()
|
|
@@ -117,7 +128,7 @@ const connectedSchemas = {
|
|
|
117
128
|
export const toolDefinitions = [
|
|
118
129
|
{
|
|
119
130
|
name: 'get_context',
|
|
120
|
-
description: 'Return initial
|
|
131
|
+
description: 'Return an initial owner context snapshot. For a question needing related judgments or evidence, use search with an explicit relation plan; this snapshot is not a retrieval answer.',
|
|
121
132
|
inputSchema: {
|
|
122
133
|
type: 'object',
|
|
123
134
|
properties: {
|
|
@@ -140,14 +151,24 @@ export const toolDefinitions = [
|
|
|
140
151
|
},
|
|
141
152
|
{
|
|
142
153
|
name: 'search',
|
|
143
|
-
description: '
|
|
154
|
+
description: 'Find canonical Graph candidates, follow explicit typed relations, and return evidence and missing information. Use seedIds from resolve_entity/list_entities or previous results and steps from the observed relation catalog. Configured embeddings enable semantic discovery; without them discovery is lexical and reported as limited. Never treat similarity as proof or empty results as absence. Personal KG has its own search tool.',
|
|
144
155
|
inputSchema: {
|
|
145
156
|
type: 'object',
|
|
146
157
|
required: ['query'],
|
|
158
|
+
additionalProperties: false,
|
|
147
159
|
properties: {
|
|
148
160
|
dataDir: { type: 'string' },
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
asOf: { type: 'string', format: 'date-time', description: 'Compatibility alias for as_of.' },
|
|
162
|
+
query: { type: 'string', minLength: 1 },
|
|
163
|
+
seedIds: { type: 'array', minItems: 1, maxItems: 10, items: { type: 'string' }, description: 'Canonical IDs chosen by the calling model; no embeddings needed.' },
|
|
164
|
+
steps: { type: 'array', minItems: 1, maxItems: 3, items: {
|
|
165
|
+
type: 'object', additionalProperties: false, required: ['relation', 'direction'], properties: {
|
|
166
|
+
relation: { enum: ['member_of', 'participates_in', 'accountable_for', 'owned_by', 'governs', 'supersedes'] },
|
|
167
|
+
direction: { enum: ['incoming', 'outgoing'] },
|
|
168
|
+
targetType: { enum: ['person', 'org', 'project', 'decision'] }
|
|
169
|
+
}
|
|
170
|
+
} },
|
|
171
|
+
limit: { type: 'integer', minimum: 1, maximum: 50 },
|
|
151
172
|
project: { type: 'string', description: 'Optional canonical project ID, name, or alias.' },
|
|
152
173
|
as_of: { type: 'string', format: 'date-time', description: 'RFC 3339 validity instant. Defaults to now.' }
|
|
153
174
|
}
|
|
@@ -330,6 +351,15 @@ export const toolDefinitions = [
|
|
|
330
351
|
}
|
|
331
352
|
}
|
|
332
353
|
];
|
|
354
|
+
let embeddingProvider;
|
|
355
|
+
let embeddingProviderInitialized = false;
|
|
356
|
+
function configuredEmbeddingProvider() {
|
|
357
|
+
if (!embeddingProviderInitialized) {
|
|
358
|
+
embeddingProvider = createEmbeddingProviderFromEnv();
|
|
359
|
+
embeddingProviderInitialized = true;
|
|
360
|
+
}
|
|
361
|
+
return embeddingProvider;
|
|
362
|
+
}
|
|
333
363
|
export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
334
364
|
if (name in connectedSchemas) {
|
|
335
365
|
return callConnectedOnboardingTool(name, rawArgs);
|
|
@@ -337,7 +367,7 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
|
337
367
|
if (name === 'resolve_entity') {
|
|
338
368
|
return callResolveEntityTool(rawArgs);
|
|
339
369
|
}
|
|
340
|
-
const args = argsSchema.parse(rawArgs ?? {});
|
|
370
|
+
const args = (name === 'search' ? searchArgsSchema : argsSchema).parse(rawArgs ?? {});
|
|
341
371
|
const dataDir = resolveDataDir(args.dataDir);
|
|
342
372
|
switch (name) {
|
|
343
373
|
case 'get_ontology':
|
|
@@ -400,6 +430,13 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
|
400
430
|
case 'ontology_impact':
|
|
401
431
|
return getOntologyImpact(args.fromVersion);
|
|
402
432
|
}
|
|
433
|
+
if (name === 'search') {
|
|
434
|
+
const organization = createOrganizationGraphConfig();
|
|
435
|
+
if (organization) {
|
|
436
|
+
return createOrganizationGraphClient(organization).search({ query: args.query, limit: args.limit,
|
|
437
|
+
project: args.project, asOf: args.as_of ?? args.asOf, seedIds: args.seedIds, steps: args.steps });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
403
440
|
const os = await loadPersonalOs(dataDir);
|
|
404
441
|
switch (name) {
|
|
405
442
|
case 'get_context':
|
|
@@ -410,7 +447,15 @@ export async function callBrainbaseTool(name, rawArgs = {}) {
|
|
|
410
447
|
if (!args.query) {
|
|
411
448
|
throw new Error('search requires query');
|
|
412
449
|
}
|
|
413
|
-
|
|
450
|
+
if (os.graph.version !== 2) {
|
|
451
|
+
if (args.seedIds || args.steps)
|
|
452
|
+
throw new Error('graph_retrieval_migration_required');
|
|
453
|
+
return { results: searchAll(os, args.query, args.limit, { project: args.project, asOf: args.as_of ?? args.asOf }),
|
|
454
|
+
status: 'migration_required', discovery: 'legacy_lexical', absenceConfirmed: false,
|
|
455
|
+
sufficiency: 'insufficient', partialReasons: ['canonical_graph_v2_required'] };
|
|
456
|
+
}
|
|
457
|
+
return retrieveGraph(os, { query: args.query, limit: args.limit, project: args.project,
|
|
458
|
+
asOf: args.as_of ?? args.asOf, seedIds: args.seedIds, steps: args.steps }, args.seedIds ? undefined : configuredEmbeddingProvider());
|
|
414
459
|
case 'search_personal_kg':
|
|
415
460
|
if (!args.query) {
|
|
416
461
|
throw new Error('search_personal_kg requires query');
|
|
@@ -3,12 +3,42 @@ title: Brainbase Judgment DAG Milestones
|
|
|
3
3
|
status: active
|
|
4
4
|
date: 2026-08-20
|
|
5
5
|
scope: OSS Brainbase
|
|
6
|
+
governed_by_repository: Unson-LLC/brainbase-unson
|
|
7
|
+
governed_by_path: docs/management/milestones/brainbase-program-master-roadmap.md
|
|
8
|
+
governed_by_machine_path: docs/management/milestones/brainbase-program-master-roadmap.json
|
|
9
|
+
governed_by_commit: 18544f58a2a0298d97eab45de2f05544bed48a43
|
|
10
|
+
governed_by_markdown_sha256: 167afb6d3fc57198c9f5ffca06fbafea968a5435af04f44b55017199b6d859fe
|
|
11
|
+
governed_by_json_sha256: f3e6e023060ef3976f367ed7efa62ac57f7091f517ea1ca1d13824d9e6ca429f
|
|
12
|
+
program_packages:
|
|
13
|
+
- R0
|
|
14
|
+
- J0
|
|
15
|
+
- G0
|
|
16
|
+
- R1
|
|
17
|
+
- D0
|
|
18
|
+
- P0
|
|
19
|
+
- C0
|
|
6
20
|
---
|
|
7
21
|
|
|
8
22
|
# Brainbase Judgment DAG Milestones
|
|
9
23
|
|
|
10
24
|
This roadmap replaces any implicit assumption that OSS Brainbase stops at memory retrieval. The next milestones make the shared Judgment DAG core real without prematurely building a complete enterprise ontology.
|
|
11
25
|
|
|
26
|
+
## Program governance
|
|
27
|
+
|
|
28
|
+
この文書はOSS Judgment DAGのcomponent roadmapであり、cross-repositoryの依存順、開始条件、完了条件は`Unson-LLC/brainbase-unson`の`docs/management/milestones/brainbase-program-master-roadmap.md`に従う。受理した正本snapshotはcommit `18544f58a2a0298d97eab45de2f05544bed48a43`へ固定し、Markdownとmachine-readable JSONのpath・SHA-256は`contracts/judgment-dag/source-lock.json`を正本とする。対応するProgram work packageは R0 / J0 / G0 / R1 / D0 / P0 / C0 である。競合時は固定したProgram Master Roadmapを優先し、この文書のM0〜M6をProgram全体の順序や完了判定へ読み替えない。新しいProgram revisionへ追随する場合は、commitと両content hashを明示的に更新し、契約生成・検証・独立レビューを再実行する。
|
|
29
|
+
|
|
30
|
+
| Component milestone | Program work package |
|
|
31
|
+
|---|---|
|
|
32
|
+
| M0 Architecture lock | R0 + J0 |
|
|
33
|
+
| M1 Local DAG kernel | J0 |
|
|
34
|
+
| M2 Human + Agent judgment runners | G0 |
|
|
35
|
+
| M3 Replay and evaluation | R1 |
|
|
36
|
+
| M4 Brainbase Deployment dogfood | D0 |
|
|
37
|
+
| M5 Scope promotion | P0 |
|
|
38
|
+
| M6 Organization-ready primitives | G0 + C0 |
|
|
39
|
+
|
|
40
|
+
Program statusには `planned` / `contract_ready` / `implementing` / `verified` / `production_proven` / `done` の6語彙だけを使う。hard dependencyを満たさないmilestoneを`done`にしない。文書のmergeだけを実装完了または`done`と扱わず、未実施・未収集・staleな証跡をpassや0件へ丸めない。
|
|
41
|
+
|
|
12
42
|
## M0 — Architecture lock
|
|
13
43
|
|
|
14
44
|
Goal: freeze semantic boundaries before implementation.
|
|
@@ -140,4 +170,4 @@ The roadmap is not complete merely because graph/node counts increase. Measure:
|
|
|
140
170
|
4. **Outcome calibration** — do judgment versions improve against declared goals?
|
|
141
171
|
5. **Promotion quality** — are reusable judgments correctly distinguished from case-specific decisions?
|
|
142
172
|
|
|
143
|
-
The primary dogfood KPI is **expert escalation count per deployment**, not total stored knowledge.
|
|
173
|
+
The primary dogfood KPI is **expert escalation count per deployment**, not total stored knowledge.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Local-first Brainbase
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -19,11 +19,21 @@
|
|
|
19
19
|
"types": "./dist/judgment-dag.d.ts",
|
|
20
20
|
"import": "./dist/judgment-dag.js"
|
|
21
21
|
},
|
|
22
|
+
"./judgment-value-proof": {
|
|
23
|
+
"types": "./dist/judgment-value-proof.d.ts",
|
|
24
|
+
"import": "./dist/judgment-value-proof.js"
|
|
25
|
+
},
|
|
26
|
+
"./portable-graph": {
|
|
27
|
+
"types": "./dist/portable-graph.d.ts",
|
|
28
|
+
"import": "./dist/portable-graph.js"
|
|
29
|
+
},
|
|
22
30
|
"./dist/*": "./dist/*",
|
|
23
31
|
"./contracts/judgment-dag/schema.json": "./contracts/judgment-dag/schema.json",
|
|
24
32
|
"./contracts/judgment-dag/fixture.json": "./contracts/judgment-dag/fixture.json",
|
|
25
33
|
"./contracts/judgment-dag/source-lock.json": "./contracts/judgment-dag/source-lock.json",
|
|
26
34
|
"./contracts/judgment-dag/digest.json": "./contracts/judgment-dag/digest.json",
|
|
35
|
+
"./contracts/judgment-value-proof/schema.json": "./contracts/judgment-value-proof/schema.json",
|
|
36
|
+
"./contracts/judgment-value-proof/fixture.json": "./contracts/judgment-value-proof/fixture.json",
|
|
27
37
|
"./package.json": "./package.json"
|
|
28
38
|
},
|
|
29
39
|
"bin": {
|
|
@@ -51,10 +61,10 @@
|
|
|
51
61
|
"test:e2e": "vitest run tests/e2e/brainbase-mcp-only-acceptance.spec.ts tests/e2e/story-brainbase-portable-ontology-kernel-acceptance.spec.ts",
|
|
52
62
|
"docs:dev": "vitepress dev docs",
|
|
53
63
|
"docs:sync": "node scripts/sync-public-message.mjs --write",
|
|
54
|
-
"docs:check": "node scripts/check-public-docs.mjs",
|
|
64
|
+
"docs:check": "node scripts/check-public-docs.mjs && node scripts/check-organization-teaser.mjs",
|
|
55
65
|
"docs:build": "vitepress build docs",
|
|
56
|
-
"docs:smoke": "node scripts/check-built-docs.mjs",
|
|
57
|
-
"docs:verify-public": "node scripts/verify-public-site.mjs",
|
|
66
|
+
"docs:smoke": "node scripts/check-built-docs.mjs && node scripts/check-built-organization-teaser.mjs",
|
|
67
|
+
"docs:verify-public": "node scripts/verify-public-site.mjs && node scripts/verify-organization-teaser.mjs",
|
|
58
68
|
"docs:promotion:plan": "node scripts/promote-public-message.mjs --plan",
|
|
59
69
|
"docs:promotion:apply": "node scripts/promote-public-message.mjs --apply",
|
|
60
70
|
"docs:preview": "vitepress preview docs",
|
|
@@ -88,5 +98,5 @@
|
|
|
88
98
|
"engines": {
|
|
89
99
|
"node": ">=20"
|
|
90
100
|
},
|
|
91
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "00ba08ebc953a3f8db66e9b624eceebd331c1e24"
|
|
92
102
|
}
|