knolo-core 0.3.0 → 3.1.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/DOCS.md +16 -2
- package/README.md +376 -46
- package/bin/knolo.mjs +256 -45
- package/dist/agent.d.ts +53 -0
- package/dist/agent.js +175 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +28 -6
- package/dist/index.d.ts +14 -2
- package/dist/index.js +9 -2
- package/dist/pack.d.ts +2 -0
- package/dist/pack.js +22 -3
- package/dist/query.d.ts +1 -0
- package/dist/query.js +43 -1
- package/dist/router.d.ts +28 -0
- package/dist/router.js +74 -0
- package/dist/routing_profile.d.ts +19 -0
- package/dist/routing_profile.js +102 -0
- package/dist/tool_gate.d.ts +3 -0
- package/dist/tool_gate.js +8 -0
- package/dist/tool_parse.d.ts +2 -0
- package/dist/tool_parse.js +102 -0
- package/dist/tools.d.ts +27 -0
- package/dist/tools.js +34 -0
- package/dist/trace.d.ts +45 -0
- package/dist/trace.js +12 -0
- package/package.json +4 -2
package/DOCS.md
CHANGED
|
@@ -71,8 +71,16 @@ npx knolo docs.json knowledge.knolo
|
|
|
71
71
|
|
|
72
72
|
# semantic-enabled build (embeddings JSON + model id)
|
|
73
73
|
npx knolo docs.json knowledge.knolo --embeddings embeddings.json --model-id text-embedding-3-small
|
|
74
|
+
|
|
75
|
+
# embed agents from a local directory (.json/.yml/.yaml)
|
|
76
|
+
npx knolo docs.json knowledge.knolo --agents ./examples/agents
|
|
74
77
|
```
|
|
75
78
|
|
|
79
|
+
|
|
80
|
+
### Agents and namespace binding
|
|
81
|
+
|
|
82
|
+
When agent definitions are embedded into `meta.agents`, `resolveAgent(pack, { agentId, query, patch })` enforces **strict namespace binding**: `retrievalDefaults.namespace` always wins over caller `query.namespace`. This keeps retrieval deterministic and on-policy for each agent.
|
|
83
|
+
|
|
76
84
|
---
|
|
77
85
|
|
|
78
86
|
## Concepts
|
|
@@ -176,9 +184,15 @@ const hits: Hit[] = query(pack, '“react native bridge” throttling', {
|
|
|
176
184
|
### Semantic helper ergonomics
|
|
177
185
|
|
|
178
186
|
```ts
|
|
179
|
-
import { hasSemantic, validateSemanticQueryOptions } from "knolo-core";
|
|
187
|
+
import { hasSemantic, validateQueryOptions, validateSemanticQueryOptions } from "knolo-core";
|
|
180
188
|
|
|
181
189
|
if (hasSemantic(pack)) {
|
|
190
|
+
validateQueryOptions({
|
|
191
|
+
topK: 10,
|
|
192
|
+
namespace: "mobile",
|
|
193
|
+
queryExpansion: { enabled: true, docs: 3, terms: 4 },
|
|
194
|
+
});
|
|
195
|
+
|
|
182
196
|
validateSemanticQueryOptions({
|
|
183
197
|
enabled: true,
|
|
184
198
|
topN: 40,
|
|
@@ -188,7 +202,7 @@ if (hasSemantic(pack)) {
|
|
|
188
202
|
}
|
|
189
203
|
```
|
|
190
204
|
|
|
191
|
-
`validateSemanticQueryOptions(...)`
|
|
205
|
+
`validateQueryOptions(...)` and `validateSemanticQueryOptions(...)` throw useful errors for invalid option types/ranges (for example `topK`, `queryExpansion.docs`, `topN`, `minLexConfidence`, blend weights, and missing `Float32Array` embedding types).
|
|
192
206
|
|
|
193
207
|
**What the ranker does**
|
|
194
208
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Build a portable `.knolo` pack and run deterministic lexical retrieval with opti
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## ✨ What’s in v0.3.
|
|
19
|
+
## ✨ What’s in v0.3.1
|
|
20
20
|
|
|
21
21
|
- **Deterministic lexical quality upgrades**
|
|
22
22
|
- required phrase enforcement (quoted + `requirePhrases`)
|
|
@@ -31,6 +31,10 @@ Build a portable `.knolo` pack and run deterministic lexical retrieval with opti
|
|
|
31
31
|
- **Stability & diversity**
|
|
32
32
|
- near-duplicate suppression + MMR diversity
|
|
33
33
|
- KNS tie-break signal for stable close-score ordering
|
|
34
|
+
- **Agent/runtime utilities**
|
|
35
|
+
- embedded agent registries with strict namespace binding
|
|
36
|
+
- tool call parsing + policy gating helpers
|
|
37
|
+
- provider-agnostic routing profile + route decision validators
|
|
34
38
|
- **Portable packs**
|
|
35
39
|
- single `.knolo` artifact
|
|
36
40
|
- semantic payload embedded directly in pack when enabled
|
|
@@ -54,26 +58,34 @@ npm run build
|
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
61
|
+
## 🧪 Playground
|
|
62
|
+
|
|
63
|
+
Try KnoLo Core directly in your browser with the hosted playground:
|
|
64
|
+
|
|
65
|
+
- https://playgrounds.knolo.dev
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
57
69
|
## 🚀 Quickstart
|
|
58
70
|
|
|
59
71
|
### 1) Build + mount + query
|
|
60
72
|
|
|
61
73
|
```ts
|
|
62
|
-
import { buildPack, mountPack, query, makeContextPatch } from
|
|
74
|
+
import { buildPack, mountPack, query, makeContextPatch } from 'knolo-core';
|
|
63
75
|
|
|
64
76
|
const docs = [
|
|
65
77
|
{
|
|
66
|
-
id:
|
|
67
|
-
namespace:
|
|
68
|
-
heading:
|
|
69
|
-
text:
|
|
78
|
+
id: 'bridge-guide',
|
|
79
|
+
namespace: 'mobile',
|
|
80
|
+
heading: 'React Native Bridge',
|
|
81
|
+
text: 'The bridge sends messages between JS and native modules. Throttling limits event frequency.',
|
|
70
82
|
},
|
|
71
83
|
{
|
|
72
|
-
id:
|
|
73
|
-
namespace:
|
|
74
|
-
heading:
|
|
75
|
-
text:
|
|
76
|
-
}
|
|
84
|
+
id: 'perf-notes',
|
|
85
|
+
namespace: 'mobile',
|
|
86
|
+
heading: 'Debounce vs Throttle',
|
|
87
|
+
text: 'Debounce waits for silence; throttle enforces a maximum trigger rate.',
|
|
88
|
+
},
|
|
77
89
|
];
|
|
78
90
|
|
|
79
91
|
const bytes = await buildPack(docs);
|
|
@@ -81,11 +93,11 @@ const kb = await mountPack({ src: bytes });
|
|
|
81
93
|
|
|
82
94
|
const hits = query(kb, '"react native" throttle', {
|
|
83
95
|
topK: 5,
|
|
84
|
-
requirePhrases: [
|
|
85
|
-
namespace:
|
|
96
|
+
requirePhrases: ['maximum trigger rate'],
|
|
97
|
+
namespace: 'mobile',
|
|
86
98
|
});
|
|
87
99
|
|
|
88
|
-
const patch = makeContextPatch(hits, { budget:
|
|
100
|
+
const patch = makeContextPatch(hits, { budget: 'small' });
|
|
89
101
|
console.log(hits, patch);
|
|
90
102
|
```
|
|
91
103
|
|
|
@@ -95,34 +107,45 @@ console.log(hits, patch);
|
|
|
95
107
|
|
|
96
108
|
```json
|
|
97
109
|
[
|
|
98
|
-
{
|
|
99
|
-
|
|
110
|
+
{
|
|
111
|
+
"id": "guide",
|
|
112
|
+
"heading": "Guide",
|
|
113
|
+
"text": "Install deps.\n\n## Throttle\nLimit event frequency."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "faq",
|
|
117
|
+
"heading": "FAQ",
|
|
118
|
+
"text": "What is throttling? It reduces event frequency."
|
|
119
|
+
}
|
|
100
120
|
]
|
|
101
121
|
```
|
|
102
122
|
|
|
103
123
|
```bash
|
|
104
124
|
npx knolo docs.json knowledge.knolo
|
|
125
|
+
|
|
126
|
+
# embed agents from a local directory (.json/.yml/.yaml)
|
|
127
|
+
npx knolo docs.json knowledge.knolo --agents ./examples/agents
|
|
105
128
|
```
|
|
106
129
|
|
|
107
130
|
Then query in app:
|
|
108
131
|
|
|
109
132
|
```ts
|
|
110
|
-
import { mountPack, query } from
|
|
133
|
+
import { mountPack, query } from 'knolo-core';
|
|
111
134
|
|
|
112
|
-
const kb = await mountPack({ src:
|
|
113
|
-
const hits = query(kb,
|
|
135
|
+
const kb = await mountPack({ src: './knowledge.knolo' });
|
|
136
|
+
const hits = query(kb, 'throttle events', { topK: 3 });
|
|
114
137
|
```
|
|
115
138
|
|
|
116
139
|
---
|
|
117
140
|
|
|
118
|
-
## 🔀 Hybrid retrieval with embeddings (
|
|
141
|
+
## 🔀 Hybrid retrieval with embeddings (optional)
|
|
119
142
|
|
|
120
143
|
KnoLo’s core retrieval remains lexical-first and deterministic. Semantic signals are added as an **optional rerank stage** when lexical confidence is low (or forced).
|
|
121
144
|
|
|
122
145
|
### Build a semantic-enabled pack
|
|
123
146
|
|
|
124
147
|
```ts
|
|
125
|
-
import { buildPack } from
|
|
148
|
+
import { buildPack } from 'knolo-core';
|
|
126
149
|
|
|
127
150
|
// embeddings must align 1:1 with docs/block order
|
|
128
151
|
const embeddings: Float32Array[] = await embedDocumentsInOrder(docs);
|
|
@@ -130,32 +153,32 @@ const embeddings: Float32Array[] = await embedDocumentsInOrder(docs);
|
|
|
130
153
|
const bytes = await buildPack(docs, {
|
|
131
154
|
semantic: {
|
|
132
155
|
enabled: true,
|
|
133
|
-
modelId:
|
|
156
|
+
modelId: 'text-embedding-3-small',
|
|
134
157
|
embeddings,
|
|
135
|
-
quantization: { type:
|
|
136
|
-
}
|
|
158
|
+
quantization: { type: 'int8_l2norm', perVectorScale: true },
|
|
159
|
+
},
|
|
137
160
|
});
|
|
138
161
|
```
|
|
139
162
|
|
|
140
163
|
### Query with semantic rerank
|
|
141
164
|
|
|
142
165
|
```ts
|
|
143
|
-
import { mountPack, query, hasSemantic } from
|
|
166
|
+
import { mountPack, query, hasSemantic } from 'knolo-core';
|
|
144
167
|
|
|
145
168
|
const kb = await mountPack({ src: bytes });
|
|
146
|
-
const queryEmbedding = await embedQuery(
|
|
169
|
+
const queryEmbedding = await embedQuery('react native bridge throttling');
|
|
147
170
|
|
|
148
|
-
const hits = query(kb,
|
|
171
|
+
const hits = query(kb, 'react native bridge throttling', {
|
|
149
172
|
topK: 8,
|
|
150
173
|
semantic: {
|
|
151
174
|
enabled: hasSemantic(kb),
|
|
152
|
-
mode:
|
|
175
|
+
mode: 'rerank',
|
|
153
176
|
topN: 50,
|
|
154
177
|
minLexConfidence: 0.35,
|
|
155
178
|
blend: { enabled: true, wLex: 0.75, wSem: 0.25 },
|
|
156
179
|
queryEmbedding,
|
|
157
|
-
force: false
|
|
158
|
-
}
|
|
180
|
+
force: false,
|
|
181
|
+
},
|
|
159
182
|
});
|
|
160
183
|
```
|
|
161
184
|
|
|
@@ -165,8 +188,8 @@ const hits = query(kb, "react native bridge throttling", {
|
|
|
165
188
|
import {
|
|
166
189
|
quantizeEmbeddingInt8L2Norm,
|
|
167
190
|
encodeScaleF16,
|
|
168
|
-
decodeScaleF16
|
|
169
|
-
} from
|
|
191
|
+
decodeScaleF16,
|
|
192
|
+
} from 'knolo-core';
|
|
170
193
|
|
|
171
194
|
const { q, scale } = quantizeEmbeddingInt8L2Norm(queryEmbedding);
|
|
172
195
|
const packed = encodeScaleF16(scale);
|
|
@@ -188,25 +211,74 @@ type BuildInputDoc = {
|
|
|
188
211
|
};
|
|
189
212
|
|
|
190
213
|
type BuildPackOptions = {
|
|
214
|
+
agents?: AgentRegistry | AgentDefinitionV1[];
|
|
191
215
|
semantic?: {
|
|
192
216
|
enabled: boolean;
|
|
193
217
|
modelId: string;
|
|
194
218
|
embeddings: Float32Array[];
|
|
195
219
|
quantization?: {
|
|
196
|
-
type:
|
|
220
|
+
type: 'int8_l2norm';
|
|
197
221
|
perVectorScale?: true;
|
|
198
222
|
};
|
|
199
223
|
};
|
|
200
224
|
};
|
|
201
225
|
```
|
|
202
226
|
|
|
227
|
+
### Agents in pack metadata
|
|
228
|
+
|
|
229
|
+
Agents are optional and embedded in `meta.agents` so a single `.knolo` artifact can ship retrieval behavior + prompt defaults on-prem. Agent registries are validated once at `mountPack()` time, so invalid embedded registries fail fast during mount.
|
|
230
|
+
|
|
231
|
+
Agent namespace binding is **strict**: when `resolveAgent()` composes retrieval options, `retrievalDefaults.namespace` is always enforced and caller-provided `query.namespace` is ignored.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
type AgentPromptTemplate = string[] | { format: 'markdown'; template: string };
|
|
235
|
+
|
|
236
|
+
type AgentRegistry = {
|
|
237
|
+
version: 1;
|
|
238
|
+
agents: AgentDefinitionV1[];
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type PackMeta = {
|
|
242
|
+
version: number;
|
|
243
|
+
stats: { docs: number; blocks: number; terms: number; avgBlockLen?: number };
|
|
244
|
+
agents?: AgentRegistry;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
type AgentDefinitionV1 = {
|
|
248
|
+
id: string;
|
|
249
|
+
version: 1;
|
|
250
|
+
name?: string;
|
|
251
|
+
description?: string;
|
|
252
|
+
systemPrompt: AgentPromptTemplate;
|
|
253
|
+
retrievalDefaults: {
|
|
254
|
+
namespace: string[]; // required
|
|
255
|
+
topK?: number;
|
|
256
|
+
queryExpansion?: QueryOptions['queryExpansion'];
|
|
257
|
+
semantic?: Omit<
|
|
258
|
+
NonNullable<QueryOptions['semantic']>,
|
|
259
|
+
'queryEmbedding' | 'enabled' | 'force'
|
|
260
|
+
> & { enabled?: boolean };
|
|
261
|
+
minScore?: number;
|
|
262
|
+
requirePhrases?: string[];
|
|
263
|
+
source?: string[];
|
|
264
|
+
};
|
|
265
|
+
toolPolicy?: { mode: 'allow' | 'deny'; tools: string[] };
|
|
266
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
267
|
+
};
|
|
268
|
+
```
|
|
269
|
+
|
|
203
270
|
### `mountPack({ src }) => Promise<Pack>`
|
|
204
271
|
|
|
205
272
|
```ts
|
|
206
273
|
type Pack = {
|
|
207
274
|
meta: {
|
|
208
275
|
version: number;
|
|
209
|
-
stats: {
|
|
276
|
+
stats: {
|
|
277
|
+
docs: number;
|
|
278
|
+
blocks: number;
|
|
279
|
+
terms: number;
|
|
280
|
+
avgBlockLen?: number;
|
|
281
|
+
};
|
|
210
282
|
};
|
|
211
283
|
lexicon: Map<string, number>;
|
|
212
284
|
postings: Uint32Array;
|
|
@@ -219,7 +291,7 @@ type Pack = {
|
|
|
219
291
|
version: 1;
|
|
220
292
|
modelId: string;
|
|
221
293
|
dims: number;
|
|
222
|
-
encoding:
|
|
294
|
+
encoding: 'int8_l2norm';
|
|
223
295
|
perVectorScale: boolean;
|
|
224
296
|
vecs: Int8Array;
|
|
225
297
|
scales?: Uint16Array;
|
|
@@ -245,7 +317,7 @@ type QueryOptions = {
|
|
|
245
317
|
};
|
|
246
318
|
semantic?: {
|
|
247
319
|
enabled?: boolean;
|
|
248
|
-
mode?:
|
|
320
|
+
mode?: 'rerank';
|
|
249
321
|
topN?: number;
|
|
250
322
|
minLexConfidence?: number;
|
|
251
323
|
blend?: {
|
|
@@ -267,6 +339,264 @@ type Hit = {
|
|
|
267
339
|
};
|
|
268
340
|
```
|
|
269
341
|
|
|
342
|
+
### Agent runtime helpers
|
|
343
|
+
|
|
344
|
+
- `listAgents(pack) => string[]`
|
|
345
|
+
- `getAgent(pack, agentId) => AgentDefinitionV1 | undefined`
|
|
346
|
+
- `resolveAgent(pack, { agentId, query?, patch? }) => { agent, systemPrompt, retrievalOptions }`
|
|
347
|
+
- `buildSystemPrompt(agent, patch?) => string`
|
|
348
|
+
- `isToolAllowed(agent, toolId) => boolean` (defaults to allow-all when no `toolPolicy`)
|
|
349
|
+
- `assertToolAllowed(agent, toolId) => void` (throws deterministic error when blocked)
|
|
350
|
+
- `parseToolCallV1FromText(text) => ToolCallV1 | null` (safe parser for model outputs)
|
|
351
|
+
- `assertToolCallAllowed(agent, call) => void` (policy gate for parsed calls)
|
|
352
|
+
- `isToolCallV1(value) / isToolResultV1(value)` (runtime-safe type guards)
|
|
353
|
+
- `getAgentRoutingProfileV1(agent) => AgentRoutingProfileV1`
|
|
354
|
+
- `getPackRoutingProfilesV1(pack) => AgentRoutingProfileV1[]`
|
|
355
|
+
- `isRouteDecisionV1(value) => boolean` (strict contract guard for router output)
|
|
356
|
+
- `validateRouteDecisionV1(decision, registryById) => { ok: true } | { ok: false; error: string }`
|
|
357
|
+
- `selectAgentIdFromRouteDecisionV1(decision, registryById, { fallbackAgentId? }) => { agentId, reason }`
|
|
358
|
+
|
|
359
|
+
### Routing discoverability conventions
|
|
360
|
+
|
|
361
|
+
To make an agent easier to route, use these optional `metadata` keys on `AgentDefinitionV1`:
|
|
362
|
+
|
|
363
|
+
- `tags`: comma-separated (`"shopping,checkout"`) or JSON array string (`"[\"shopping\",\"checkout\"]"`)
|
|
364
|
+
- `examples`: comma-separated, newline-separated, or JSON array string
|
|
365
|
+
- `capabilities`: comma-separated, newline-separated, or JSON array string
|
|
366
|
+
- `heading`: short UI heading shown in routing cards
|
|
367
|
+
|
|
368
|
+
`knolo-core` parses these into a compact routing profile with trimming + dedupe + caps and never throws on bad metadata formats.
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
type AgentRoutingProfileV1 = {
|
|
372
|
+
agentId: string;
|
|
373
|
+
namespace?: string;
|
|
374
|
+
heading?: string;
|
|
375
|
+
description?: string;
|
|
376
|
+
tags: string[];
|
|
377
|
+
examples: string[];
|
|
378
|
+
capabilities: string[];
|
|
379
|
+
toolPolicy?: unknown;
|
|
380
|
+
toolPolicySummary?: {
|
|
381
|
+
mode: 'allow_all' | 'deny_all' | 'mixed' | 'unknown';
|
|
382
|
+
allowed?: string[];
|
|
383
|
+
denied?: string[];
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Example profile payload:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"agentId": "shopping.agent",
|
|
393
|
+
"namespace": "shopping",
|
|
394
|
+
"heading": "Shopping Assistant",
|
|
395
|
+
"description": "Handles product lookup, checkout help, and order tracking.",
|
|
396
|
+
"tags": ["shopping", "checkout", "order-status"],
|
|
397
|
+
"examples": ["track my order", "find running shoes under $120"],
|
|
398
|
+
"capabilities": ["catalog_search", "order_lookup"],
|
|
399
|
+
"toolPolicySummary": {
|
|
400
|
+
"mode": "mixed",
|
|
401
|
+
"allowed": ["search_docs", "order_lookup"]
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Route decision contract
|
|
407
|
+
|
|
408
|
+
`knolo-core` does not call Ollama (or any model provider). A runtime can call any router model, then validate the output with this contract:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
type RouteCandidateV1 = {
|
|
412
|
+
agentId: string;
|
|
413
|
+
score: number; // 0..1
|
|
414
|
+
why?: string;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
type RouteDecisionV1 = {
|
|
418
|
+
type: 'route_decision';
|
|
419
|
+
intent?: string;
|
|
420
|
+
entities?: Record<string, unknown>;
|
|
421
|
+
candidates: RouteCandidateV1[];
|
|
422
|
+
selected: string;
|
|
423
|
+
needsTools?: string[];
|
|
424
|
+
risk?: 'low' | 'med' | 'high';
|
|
425
|
+
};
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
JSON example:
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{
|
|
432
|
+
"type": "route_decision",
|
|
433
|
+
"intent": "order_tracking",
|
|
434
|
+
"entities": { "orderId": "A-1023" },
|
|
435
|
+
"candidates": [
|
|
436
|
+
{ "agentId": "shopping.agent", "score": 0.91, "why": "Order-related intent" },
|
|
437
|
+
{ "agentId": "returns.agent", "score": 0.37 }
|
|
438
|
+
],
|
|
439
|
+
"selected": "shopping.agent",
|
|
440
|
+
"needsTools": ["order_lookup"],
|
|
441
|
+
"risk": "low"
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Validation and selection notes:
|
|
446
|
+
|
|
447
|
+
- `isRouteDecisionV1(...)` is strict and rejects malformed payloads.
|
|
448
|
+
- `validateRouteDecisionV1(...)` requires `selected` and every candidate `agentId` to exist in the mounted registry and rejects duplicate candidate ids.
|
|
449
|
+
- `selectAgentIdFromRouteDecisionV1(...)` is deterministic and never throws:
|
|
450
|
+
1. use `selected` if registered,
|
|
451
|
+
2. else highest-score registered candidate,
|
|
452
|
+
3. else caller `fallbackAgentId` if valid,
|
|
453
|
+
4. else lexicographically first registered agent id.
|
|
454
|
+
|
|
455
|
+
### Router runtime flow (provider-agnostic)
|
|
456
|
+
|
|
457
|
+
1. Receive user input text.
|
|
458
|
+
2. Build routing profiles from mounted pack agents via `getPackRoutingProfilesV1(pack)`.
|
|
459
|
+
3. Send input + profiles to your router model (Ollama or any provider) outside `knolo-core`.
|
|
460
|
+
4. Parse model output JSON and gate with `isRouteDecisionV1`.
|
|
461
|
+
5. Validate against mounted registry with `validateRouteDecisionV1`.
|
|
462
|
+
6. Pick final agent using `selectAgentIdFromRouteDecisionV1`.
|
|
463
|
+
7. Call `resolveAgent(pack, { agentId, ... })` and run your existing loop.
|
|
464
|
+
|
|
465
|
+
### Tool call + result contracts
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
type ToolCallV1 = {
|
|
469
|
+
type: 'tool_call';
|
|
470
|
+
callId: string;
|
|
471
|
+
tool: string;
|
|
472
|
+
args: Record<string, unknown>;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
type ToolResultV1 = {
|
|
476
|
+
type: 'tool_result';
|
|
477
|
+
callId: string;
|
|
478
|
+
tool: string;
|
|
479
|
+
ok: boolean;
|
|
480
|
+
output?: unknown; // when ok=true
|
|
481
|
+
error?: { message: string; code?: string; details?: unknown }; // when ok=false
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
JSON examples:
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"type": "tool_call",
|
|
490
|
+
"callId": "call-42",
|
|
491
|
+
"tool": "search_docs",
|
|
492
|
+
"args": { "query": "bridge throttle" }
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
{
|
|
498
|
+
"type": "tool_result",
|
|
499
|
+
"callId": "call-42",
|
|
500
|
+
"tool": "search_docs",
|
|
501
|
+
"ok": true,
|
|
502
|
+
"output": { "hits": [{ "id": "mobile-doc" }] }
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Runtime loop shape (model-agnostic)
|
|
507
|
+
|
|
508
|
+
1. Run model with current conversation state.
|
|
509
|
+
2. Parse text output with `parseToolCallV1FromText(...)`.
|
|
510
|
+
3. If parsed: gate with `assertToolCallAllowed(resolved.agent, call)`.
|
|
511
|
+
4. Runtime executes the tool and creates `ToolResultV1`.
|
|
512
|
+
5. Feed the tool result back into the conversation and continue until completion.
|
|
513
|
+
|
|
514
|
+
### Trace events for timeline UIs
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
type TraceEventV1 =
|
|
518
|
+
| {
|
|
519
|
+
type: 'route.requested';
|
|
520
|
+
ts: string;
|
|
521
|
+
text: string;
|
|
522
|
+
agentCount: number;
|
|
523
|
+
}
|
|
524
|
+
| {
|
|
525
|
+
type: 'route.decided';
|
|
526
|
+
ts: string;
|
|
527
|
+
decision: RouteDecisionV1;
|
|
528
|
+
selectedAgentId: string;
|
|
529
|
+
}
|
|
530
|
+
| { type: 'agent.selected'; ts: string; agentId: string; namespace?: string }
|
|
531
|
+
| {
|
|
532
|
+
type: 'prompt.resolved';
|
|
533
|
+
ts: string;
|
|
534
|
+
agentId: string;
|
|
535
|
+
promptHash?: string;
|
|
536
|
+
patchKeys?: string[];
|
|
537
|
+
}
|
|
538
|
+
| { type: 'tool.requested'; ts: string; agentId: string; call: ToolCallV1 }
|
|
539
|
+
| {
|
|
540
|
+
type: 'tool.executed';
|
|
541
|
+
ts: string;
|
|
542
|
+
agentId: string;
|
|
543
|
+
result: ToolResultV1;
|
|
544
|
+
durationMs?: number;
|
|
545
|
+
}
|
|
546
|
+
| {
|
|
547
|
+
type: 'run.completed';
|
|
548
|
+
ts: string;
|
|
549
|
+
agentId: string;
|
|
550
|
+
status: 'ok' | 'error';
|
|
551
|
+
};
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Helpers: `nowIso()` for timestamps and `createTrace()` for lightweight trace collection.
|
|
555
|
+
|
|
556
|
+
### Build a pack with agents and resolve at runtime
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import {
|
|
560
|
+
buildPack,
|
|
561
|
+
mountPack,
|
|
562
|
+
resolveAgent,
|
|
563
|
+
query,
|
|
564
|
+
isToolAllowed,
|
|
565
|
+
assertToolAllowed,
|
|
566
|
+
} from 'knolo-core';
|
|
567
|
+
|
|
568
|
+
const bytes = await buildPack(docs, {
|
|
569
|
+
agents: [
|
|
570
|
+
{
|
|
571
|
+
id: 'mobile.agent',
|
|
572
|
+
version: 1,
|
|
573
|
+
systemPrompt: {
|
|
574
|
+
format: 'markdown',
|
|
575
|
+
template: 'You are {{team}} support.',
|
|
576
|
+
},
|
|
577
|
+
retrievalDefaults: { namespace: ['mobile'], topK: 5 },
|
|
578
|
+
toolPolicy: { mode: 'allow', tools: ['search_docs'] },
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const pack = await mountPack({ src: bytes });
|
|
584
|
+
const resolved = resolveAgent(pack, {
|
|
585
|
+
agentId: 'mobile.agent',
|
|
586
|
+
patch: { team: 'mobile' },
|
|
587
|
+
query: { namespace: ['backend'], topK: 8 },
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
console.log(resolved.retrievalOptions.namespace); // ['mobile'] (strict binding)
|
|
591
|
+
|
|
592
|
+
if (isToolAllowed(resolved.agent, 'search_docs')) {
|
|
593
|
+
// invoke search_docs
|
|
594
|
+
}
|
|
595
|
+
assertToolAllowed(resolved.agent, 'search_docs');
|
|
596
|
+
|
|
597
|
+
const hits = query(pack, 'bridge throttle', resolved.retrievalOptions);
|
|
598
|
+
```
|
|
599
|
+
|
|
270
600
|
### `makeContextPatch(hits, { budget }) => ContextPatch`
|
|
271
601
|
|
|
272
602
|
Budgets: `"mini" | "small" | "full"`
|
|
@@ -278,10 +608,10 @@ Budgets: `"mini" | "small" | "full"`
|
|
|
278
608
|
### Namespace + source filtering
|
|
279
609
|
|
|
280
610
|
```ts
|
|
281
|
-
const hits = query(kb,
|
|
282
|
-
namespace: [
|
|
283
|
-
source: [
|
|
284
|
-
topK: 6
|
|
611
|
+
const hits = query(kb, 'retry backoff', {
|
|
612
|
+
namespace: ['sdk', 'api'],
|
|
613
|
+
source: ['errors-guide', 'http-reference'],
|
|
614
|
+
topK: 6,
|
|
285
615
|
});
|
|
286
616
|
```
|
|
287
617
|
|
|
@@ -290,29 +620,29 @@ const hits = query(kb, "retry backoff", {
|
|
|
290
620
|
If your query has no free tokens but includes required phrases, KnoLo still forms candidates from phrase tokens and enforces phrase presence.
|
|
291
621
|
|
|
292
622
|
```ts
|
|
293
|
-
const hits = query(kb, '"event loop"', { requirePhrases: [
|
|
623
|
+
const hits = query(kb, '"event loop"', { requirePhrases: ['single thread'] });
|
|
294
624
|
```
|
|
295
625
|
|
|
296
626
|
### Precision mode with minimum score
|
|
297
627
|
|
|
298
628
|
```ts
|
|
299
|
-
const strictHits = query(kb,
|
|
629
|
+
const strictHits = query(kb, 'jwt refresh token rotation', {
|
|
300
630
|
topK: 5,
|
|
301
|
-
minScore: 2.5
|
|
631
|
+
minScore: 2.5,
|
|
302
632
|
});
|
|
303
633
|
```
|
|
304
634
|
|
|
305
635
|
### Validate semantic query options early
|
|
306
636
|
|
|
307
637
|
```ts
|
|
308
|
-
import { validateSemanticQueryOptions } from
|
|
638
|
+
import { validateSemanticQueryOptions } from 'knolo-core';
|
|
309
639
|
|
|
310
640
|
validateSemanticQueryOptions({
|
|
311
641
|
enabled: true,
|
|
312
642
|
topN: 40,
|
|
313
643
|
minLexConfidence: 0.3,
|
|
314
644
|
blend: { enabled: true, wLex: 0.8, wSem: 0.2 },
|
|
315
|
-
queryEmbedding
|
|
645
|
+
queryEmbedding,
|
|
316
646
|
});
|
|
317
647
|
```
|
|
318
648
|
|
|
@@ -356,7 +686,7 @@ Yes. Runtime text encoder/decoder compatibility is included.
|
|
|
356
686
|
|
|
357
687
|
---
|
|
358
688
|
|
|
359
|
-
## 🗺️
|
|
689
|
+
## 🗺️ Roadmap
|
|
360
690
|
|
|
361
691
|
- stronger hybrid retrieval evaluation tooling
|
|
362
692
|
- richer pack introspection and diagnostics
|