@tangle-network/agent-knowledge 1.3.0 → 1.4.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/LICENSE ADDED
@@ -0,0 +1,43 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tangle Network
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ *** Add File: /home/drew/worktrees/publish-open-source/modal-inference-blueprint/LICENSE-MIT
23
+ MIT License
24
+
25
+ Copyright (c) 2026 Tangle Network
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
package/README.md CHANGED
@@ -199,6 +199,66 @@ await runAgentControlLoop({
199
199
  })
200
200
  ```
201
201
 
202
+ ## Researcher profile
203
+
204
+ `@tangle-network/agent-knowledge/profiles` ships a sandbox-SDK
205
+ `AgentProfile` preset for source-grounded research agents. Pairs with
206
+ `runLoop` from `@tangle-network/agent-runtime/loops` — the profile owns
207
+ the prompt + output adapter + validator; the kernel owns iteration,
208
+ concurrency, cost, and trace emission.
209
+
210
+ ```ts
211
+ import { runLoop } from '@tangle-network/agent-runtime/loops'
212
+ import { multiHarnessResearcherFanout } from '@tangle-network/agent-knowledge/profiles'
213
+
214
+ const research = multiHarnessResearcherFanout({
215
+ harnesses: ['opencode/zai-coding-plan/glm-5.1', 'claude-code', 'codex'],
216
+ })
217
+
218
+ const result = await runLoop({
219
+ driver: research.driver,
220
+ agentRuns: research.agentRuns,
221
+ output: research.output,
222
+ validator: research.validator,
223
+ task: {
224
+ question: 'What content does cpg-founder ICP engage with on Twitter?',
225
+ knowledgeNamespace: 'cust_42',
226
+ sources: ['twitter', 'web'],
227
+ maxItems: 20,
228
+ minConfidence: 0.6,
229
+ },
230
+ ctx: { sandboxClient },
231
+ })
232
+
233
+ if (result.winner?.verdict?.valid) {
234
+ // result.winner.output.proposedWrites: KnowledgeUpdate[]
235
+ // The profile does NOT materialize. Decide whether to apply.
236
+ for (const write of result.winner.output.proposedWrites) {
237
+ // route through applyKnowledgeWriteBlocks / a KbStore put when ready
238
+ }
239
+ }
240
+ ```
241
+
242
+ Three invariants are enforced by the validator:
243
+
244
+ - **Namespace isolation** — every `KnowledgeItem` + `KnowledgeUpdate`
245
+ must carry `task.knowledgeNamespace`. Cross-tenant writes hard-fail.
246
+ - **Provenance** — every item carries at least one evidence entry.
247
+ - **Citation density** — quotes-with-source / items >= 0.7 by default.
248
+
249
+ Validator scoring (default; overridable):
250
+
251
+ ```
252
+ score = 0.4 · citation_density
253
+ + 0.2 · source_diversity
254
+ + 0.2 · recency_match
255
+ + 0.2 · gap_coverage
256
+ ```
257
+
258
+ The output preserves agent intelligence — `items`, `citations`,
259
+ `proposedWrites` are typed; `gaps`, `notes`, and any extras the agent
260
+ emitted land in `raw` rather than getting dropped.
261
+
202
262
  ## Pluggable Knowledge Sources
203
263
 
204
264
  Static knowledge rots. Authorities like Cornell LII, the IRS, and state
@@ -0,0 +1,194 @@
1
+ import { Validator, AgentRunSpec, OutputAdapter, Driver, AgentProfile } from '@tangle-network/agent-runtime/loops';
2
+
3
+ /**
4
+ * @experimental
5
+ *
6
+ * `researcherProfile` — opinionated preset for source-grounded research
7
+ * tasks. The agent is told to:
8
+ * - bound its work to a single `knowledgeNamespace`
9
+ * - emit `items[]` carrying provenance + confidence
10
+ * - emit `citations[]` linking quotes back to source urls
11
+ * - emit `proposedWrites[]` — never call materialize itself
12
+ * - describe `gaps` it could not answer
13
+ *
14
+ * The profile is stateless and agent-agnostic. `harness` selects the
15
+ * sandbox-SDK backend. For heterogeneous fanout, use
16
+ * `multiHarnessResearcherFanout`.
17
+ *
18
+ * Propose-don't-apply: the profile NEVER writes to the knowledge base.
19
+ * It produces `proposedWrites: KnowledgeUpdate[]` in the output. The
20
+ * caller (gtm-agent, journey-eval, user) decides whether to feed those
21
+ * updates through `applyKnowledgeWriteBlocks` / a KbStore put.
22
+ *
23
+ * Namespace isolation: every `KnowledgeItem` + `KnowledgeUpdate` in the
24
+ * output carries `namespace`. The validator hard-fails when any item
25
+ * touches a namespace other than `task.knowledgeNamespace`.
26
+ */
27
+
28
+ /** @experimental */
29
+ type ResearchSource = 'web' | 'corpus' | 'twitter' | 'github' | 'docs';
30
+ /** @experimental */
31
+ interface ResearchTask {
32
+ /** The research question to answer. */
33
+ question: string;
34
+ /** Bound: e.g. "audience for cpg-founder ICP". */
35
+ scope?: string;
36
+ /** Multi-tenant scope (customer-id, workspace-id). Validator enforces. */
37
+ knowledgeNamespace: string;
38
+ sources?: ResearchSource[];
39
+ recencyWindow?: {
40
+ since?: Date;
41
+ until?: Date;
42
+ };
43
+ maxItems?: number;
44
+ /** Per-item minimum confidence in [0, 1]. Validator scores recall vs this. */
45
+ minConfidence?: number;
46
+ }
47
+ /**
48
+ * Knowledge item emitted by the researcher.
49
+ *
50
+ * Profile-local type. When agent-knowledge promotes `KnowledgeClaim` →
51
+ * top-level `KnowledgeItem` substrate-wide, these fields collapse 1:1.
52
+ *
53
+ * @experimental
54
+ */
55
+ interface KnowledgeItem {
56
+ id: string;
57
+ /** Multi-tenant scope. MUST equal `task.knowledgeNamespace`. */
58
+ namespace: string;
59
+ /** The factual claim, in the researcher's words. */
60
+ claim: string;
61
+ /** Provenance — at least one entry required. */
62
+ evidence: Array<{
63
+ source: string;
64
+ quote?: string;
65
+ url?: string;
66
+ capturedAt: number;
67
+ }>;
68
+ /** Researcher's self-reported confidence in [0, 1]. */
69
+ confidence: number;
70
+ /** Prior item ids this supersedes (chain). */
71
+ supersedes?: string[];
72
+ /** Set if the agent is retracting an earlier item. Unix ms. */
73
+ retractedAt?: number;
74
+ authoredBy: {
75
+ kind: 'human' | 'agent';
76
+ id: string;
77
+ };
78
+ }
79
+ /**
80
+ * A proposed write to the knowledge base. The profile does NOT apply
81
+ * these — the caller decides.
82
+ *
83
+ * @experimental
84
+ */
85
+ type KnowledgeUpdate = {
86
+ kind: 'insert';
87
+ namespace: string;
88
+ item: KnowledgeItem;
89
+ } | {
90
+ kind: 'supersede';
91
+ namespace: string;
92
+ previousId: string;
93
+ item: KnowledgeItem;
94
+ } | {
95
+ kind: 'retract';
96
+ namespace: string;
97
+ itemId: string;
98
+ reason: string;
99
+ };
100
+ /**
101
+ * Researcher output. Required fields are typed; optional fields preserve
102
+ * the agent's free-form intelligence (`notes`, `raw`). The validator
103
+ * enforces the typed minimum.
104
+ *
105
+ * @experimental
106
+ */
107
+ interface ResearchOutput {
108
+ items: KnowledgeItem[];
109
+ citations: Array<{
110
+ url: string;
111
+ quote: string;
112
+ confidence: number;
113
+ }>;
114
+ proposedWrites: KnowledgeUpdate[];
115
+ gaps?: string[];
116
+ notes?: string;
117
+ /** Anything the agent emitted beyond the typed fields. */
118
+ raw?: unknown;
119
+ }
120
+ /** @experimental */
121
+ interface ResearcherProfileOptions {
122
+ /** Sandbox-SDK backend.type. Default `'opencode/zai-coding-plan/glm-5.1'`. */
123
+ harness?: string;
124
+ /** Default model id passed in `AgentProfile.model.default`. */
125
+ model?: string;
126
+ /** Custom system prompt replacement. Default = built-in researcher preset. */
127
+ systemPrompt?: string;
128
+ /** Stable name for `AgentRunSpec.name`. Default = `researcher-${harness}`. */
129
+ name?: string;
130
+ /**
131
+ * Default 0.7. Minimum (citations with quote) / items ratio for `valid=true`.
132
+ * Below this floor, citation_density scores < 1 and the item set is gated.
133
+ */
134
+ citationDensityMin?: number;
135
+ }
136
+ /** @experimental */
137
+ declare function researcherProfile(options?: ResearcherProfileOptions & {
138
+ task?: ResearchTask;
139
+ }): {
140
+ profile: AgentProfile;
141
+ taskToPrompt: (task: ResearchTask) => string;
142
+ output: OutputAdapter<ResearchOutput>;
143
+ validator: Validator<ResearchOutput>;
144
+ agentRunSpec: AgentRunSpec<ResearchTask>;
145
+ };
146
+ /** @experimental */
147
+ interface MultiHarnessResearcherFanoutOptions {
148
+ /** Backend.type identifiers, one per parallel agent. */
149
+ harnesses?: string[];
150
+ /** Optional per-harness model override. Indexed parallel to `harnesses`. */
151
+ models?: (string | undefined)[];
152
+ /** Default citation density floor for the shared validator. */
153
+ citationDensityMin?: number;
154
+ /** Optional task — narrows the validator's namespace check. */
155
+ task?: ResearchTask;
156
+ }
157
+ /**
158
+ * Build a fanout topology over multiple harnesses. The kernel round-robins
159
+ * `agentRuns` across the N parallel iterations and the `FanoutVote` driver
160
+ * picks the highest-scoring valid output.
161
+ *
162
+ * @experimental
163
+ */
164
+ declare function multiHarnessResearcherFanout(options?: MultiHarnessResearcherFanoutOptions): {
165
+ agentRuns: AgentRunSpec<ResearchTask>[];
166
+ output: OutputAdapter<ResearchOutput>;
167
+ validator: Validator<ResearchOutput>;
168
+ driver: Driver<ResearchTask, ResearchOutput, 'pick-winner' | 'fail'>;
169
+ };
170
+ /**
171
+ * Build a validator that closes over a specific `ResearchTask`'s constraints.
172
+ *
173
+ * Checks in order:
174
+ * 1. Items must be non-empty.
175
+ * 2. Every item carries `evidence.length >= 1`.
176
+ * 3. Every item + proposedWrite is scoped to `task.knowledgeNamespace`
177
+ * (hard-fail on any namespace mismatch — defence in depth for the
178
+ * multi-tenant invariant).
179
+ * 4. Citation density (citations with quote / items) >= floor.
180
+ *
181
+ * Aggregate score:
182
+ * 0.4 · citation_density
183
+ * + 0.2 · source_diversity (distinct sources / max(items, 1))
184
+ * + 0.2 · recency_match (mean fraction within `recencyWindow`)
185
+ * + 0.2 · (1 − gaps/maxGaps), maxGaps = max(items, 1)
186
+ *
187
+ * @experimental
188
+ */
189
+ declare function createResearcherValidator(task: ResearchTask, config?: {
190
+ citationDensityMin?: number;
191
+ namespaceCheck?: boolean;
192
+ }): Validator<ResearchOutput>;
193
+
194
+ export { type KnowledgeItem, type KnowledgeUpdate, type MultiHarnessResearcherFanoutOptions, type ResearchOutput, type ResearchSource, type ResearchTask, type ResearcherProfileOptions, createResearcherValidator, multiHarnessResearcherFanout, researcherProfile };
@@ -0,0 +1,368 @@
1
+ // src/profiles/researcher.ts
2
+ import {
3
+ createFanoutVoteDriver
4
+ } from "@tangle-network/agent-runtime/loops";
5
+ var DEFAULT_HARNESS = "opencode/zai-coding-plan/glm-5.1";
6
+ var DEFAULT_CITATION_DENSITY_MIN = 0.7;
7
+ function researcherProfile(options = {}) {
8
+ const harness = options.harness ?? DEFAULT_HARNESS;
9
+ const name = options.name ?? `researcher-${harness}`;
10
+ const systemPrompt = options.systemPrompt ?? DEFAULT_RESEARCHER_SYSTEM_PROMPT;
11
+ const citationDensityMin = options.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN;
12
+ const profile = {
13
+ name,
14
+ description: "Source-grounded research agent. Propose-don't-apply.",
15
+ prompt: { systemPrompt },
16
+ model: options.model ? { default: options.model } : void 0,
17
+ tools: { web_search: true, fs: true, shell: true },
18
+ metadata: { backendType: harness, role: "researcher" }
19
+ };
20
+ const output = { parse: parseResearcherEvents };
21
+ const validator = options.task ? createResearcherValidator(options.task, { citationDensityMin }) : createResearcherValidator(
22
+ { question: "", knowledgeNamespace: "" },
23
+ { citationDensityMin, namespaceCheck: false }
24
+ );
25
+ const agentRunSpec = {
26
+ name,
27
+ profile,
28
+ taskToPrompt: formatResearcherPrompt
29
+ };
30
+ return { profile, taskToPrompt: formatResearcherPrompt, output, validator, agentRunSpec };
31
+ }
32
+ function multiHarnessResearcherFanout(options = {}) {
33
+ const harnesses = options.harnesses && options.harnesses.length > 0 ? options.harnesses : ["opencode/zai-coding-plan/glm-5.1", "claude-code", "codex"];
34
+ const models = options.models ?? [];
35
+ const agentRuns = harnesses.map((harness, i) => {
36
+ const { agentRunSpec } = researcherProfile({ harness, model: models[i] });
37
+ return agentRunSpec;
38
+ });
39
+ const { output, validator } = researcherProfile({
40
+ citationDensityMin: options.citationDensityMin,
41
+ task: options.task
42
+ });
43
+ const driver = createFanoutVoteDriver({ n: harnesses.length });
44
+ return { agentRuns, output, validator, driver };
45
+ }
46
+ function createResearcherValidator(task, config = {}) {
47
+ const citationDensityMin = config.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN;
48
+ const namespaceCheck = config.namespaceCheck ?? true;
49
+ return {
50
+ async validate(output) {
51
+ const notes = [];
52
+ const scores = {};
53
+ let pass = true;
54
+ if (!Array.isArray(output.items) || output.items.length === 0) {
55
+ pass = false;
56
+ notes.push("no items");
57
+ scores.items = 0;
58
+ } else {
59
+ scores.items = 1;
60
+ }
61
+ const missingEvidence = (output.items ?? []).filter(
62
+ (item) => !Array.isArray(item.evidence) || item.evidence.length === 0
63
+ );
64
+ if (missingEvidence.length > 0) {
65
+ pass = false;
66
+ notes.push(`${missingEvidence.length} item(s) without evidence`);
67
+ scores.provenance = 0;
68
+ } else {
69
+ scores.provenance = 1;
70
+ }
71
+ if (namespaceCheck) {
72
+ const foreignItems = (output.items ?? []).filter(
73
+ (item) => item.namespace !== task.knowledgeNamespace
74
+ );
75
+ const foreignWrites = (output.proposedWrites ?? []).filter(
76
+ (write) => write.namespace !== task.knowledgeNamespace
77
+ );
78
+ if (foreignItems.length > 0 || foreignWrites.length > 0) {
79
+ pass = false;
80
+ notes.push(
81
+ `namespace violation: ${foreignItems.length} item(s) + ${foreignWrites.length} write(s) outside ${task.knowledgeNamespace}`
82
+ );
83
+ scores.namespace = 0;
84
+ } else {
85
+ scores.namespace = 1;
86
+ }
87
+ }
88
+ const itemCount = Math.max(output.items?.length ?? 0, 1);
89
+ const citationsWithQuote = (output.citations ?? []).filter(
90
+ (citation) => typeof citation.quote === "string" && citation.quote.length > 0
91
+ ).length;
92
+ const citationDensity = Math.min(1, citationsWithQuote / itemCount);
93
+ if (citationDensity < citationDensityMin) {
94
+ pass = false;
95
+ notes.push(
96
+ `citation density ${citationDensity.toFixed(2)} below floor ${citationDensityMin.toFixed(2)}`
97
+ );
98
+ }
99
+ scores.citation_density = citationDensity;
100
+ const sourceSet = /* @__PURE__ */ new Set();
101
+ for (const item of output.items ?? []) {
102
+ for (const evidence of item.evidence ?? []) {
103
+ if (evidence.source) sourceSet.add(evidence.source);
104
+ }
105
+ }
106
+ scores.source_diversity = Math.min(1, sourceSet.size / itemCount);
107
+ scores.recency_match = recencyMatchScore(output.items ?? [], task.recencyWindow);
108
+ const maxGaps = itemCount;
109
+ const gapCount = output.gaps?.length ?? 0;
110
+ scores.gap_coverage = Math.max(0, 1 - gapCount / maxGaps);
111
+ const score = 0.4 * scores.citation_density + 0.2 * scores.source_diversity + 0.2 * scores.recency_match + 0.2 * scores.gap_coverage;
112
+ const verdict = {
113
+ valid: pass,
114
+ score: Number.isFinite(score) ? score : 0,
115
+ scores
116
+ };
117
+ if (notes.length > 0) verdict.notes = notes.join("; ");
118
+ return verdict;
119
+ }
120
+ };
121
+ }
122
+ function recencyMatchScore(items, window) {
123
+ if (!window || window.since === void 0 && window.until === void 0) return 1;
124
+ if (items.length === 0) return 0;
125
+ const sinceMs = window.since?.getTime() ?? Number.NEGATIVE_INFINITY;
126
+ const untilMs = window.until?.getTime() ?? Number.POSITIVE_INFINITY;
127
+ let hits = 0;
128
+ let total = 0;
129
+ for (const item of items) {
130
+ for (const evidence of item.evidence ?? []) {
131
+ if (typeof evidence.capturedAt !== "number") continue;
132
+ total += 1;
133
+ if (evidence.capturedAt >= sinceMs && evidence.capturedAt <= untilMs) hits += 1;
134
+ }
135
+ }
136
+ return total === 0 ? 0 : hits / total;
137
+ }
138
+ var DEFAULT_RESEARCHER_SYSTEM_PROMPT = [
139
+ "You are a research agent. Your job is to answer a research question with",
140
+ "source-grounded knowledge items that the caller will choose whether to",
141
+ "persist to a multi-tenant knowledge base.",
142
+ "",
143
+ "Hard rules:",
144
+ " 1. Every item you emit MUST carry the task's knowledgeNamespace exactly.",
145
+ " Never write to a different namespace.",
146
+ " 2. Every item MUST carry at least one evidence entry with a source.",
147
+ " A quote + url is strongly preferred; capturedAt is unix ms.",
148
+ " 3. You propose writes \u2014 you do NOT apply them. The caller decides.",
149
+ " 4. Self-report confidence honestly in [0, 1]. Do not inflate.",
150
+ " 5. List what you could not answer in `gaps`. Better to admit a gap",
151
+ " than fabricate.",
152
+ "",
153
+ "When you finish, emit a single final structured message of the shape:",
154
+ " ```json",
155
+ ' { "items": [{ "id": "...", "namespace": "...", "claim": "...",',
156
+ ' "evidence": [{ "source": "...", "quote": "...",',
157
+ ' "url": "...", "capturedAt": 0 }],',
158
+ ' "confidence": 0.0,',
159
+ ' "authoredBy": { "kind": "agent", "id": "..." } }],',
160
+ ' "citations": [{ "url": "...", "quote": "...", "confidence": 0.0 }],',
161
+ ' "proposedWrites": [{ "kind": "insert", "namespace": "...",',
162
+ ' "item": { /* same shape as items[] */ } }],',
163
+ ' "gaps": ["..."],',
164
+ ' "notes": "free-form commentary" }',
165
+ " ```"
166
+ ].join("\n");
167
+ function formatResearcherPrompt(task) {
168
+ const sources = task.sources?.length ? task.sources.join(", ") : "(no preference)";
169
+ const window = formatRecencyWindow(task.recencyWindow);
170
+ return [
171
+ `Question: ${task.question}`,
172
+ `Knowledge namespace (DO NOT cross): ${task.knowledgeNamespace}`,
173
+ `Scope: ${task.scope ?? "(unspecified)"}`,
174
+ `Preferred sources: ${sources}`,
175
+ `Recency window: ${window}`,
176
+ `Max items: ${task.maxItems ?? "(no cap)"}`,
177
+ `Per-item minimum confidence: ${task.minConfidence ?? "(no floor)"}`,
178
+ "",
179
+ "Produce knowledge items with provenance + citations + proposed writes.",
180
+ "List gaps for anything you could not answer. Emit the final JSON",
181
+ "result block exactly as instructed."
182
+ ].join("\n");
183
+ }
184
+ function formatRecencyWindow(window) {
185
+ if (!window) return "(none)";
186
+ const since = window.since ? window.since.toISOString() : "-\u221E";
187
+ const until = window.until ? window.until.toISOString() : "now";
188
+ return `${since} .. ${until}`;
189
+ }
190
+ function parseResearcherEvents(events) {
191
+ for (let i = events.length - 1; i >= 0; i -= 1) {
192
+ const event = events[i];
193
+ if (!event) continue;
194
+ const type = String(event.type ?? "");
195
+ const data = isRecord(event.data) ? event.data : {};
196
+ if (type === "result" || type === "final" || type === "research.result") {
197
+ const direct = coerceResearchOutput(data.result ?? data.output ?? data);
198
+ if (direct) return direct;
199
+ }
200
+ }
201
+ for (let i = events.length - 1; i >= 0; i -= 1) {
202
+ const event = events[i];
203
+ if (!event) continue;
204
+ const data = isRecord(event.data) ? event.data : {};
205
+ const text = pickString(data.text) ?? pickString(data.delta);
206
+ if (!text) continue;
207
+ const fenced = extractFencedJson(text);
208
+ if (!fenced) continue;
209
+ const coerced = coerceResearchOutput(fenced);
210
+ if (coerced) return coerced;
211
+ }
212
+ return { items: [], citations: [], proposedWrites: [] };
213
+ }
214
+ function isRecord(value) {
215
+ return value !== null && typeof value === "object" && !Array.isArray(value);
216
+ }
217
+ function pickString(value) {
218
+ return typeof value === "string" && value.length > 0 ? value : void 0;
219
+ }
220
+ function extractFencedJson(text) {
221
+ const match = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
222
+ if (!match) return void 0;
223
+ const body = (match[1] ?? "").trim();
224
+ if (!body) return void 0;
225
+ try {
226
+ return JSON.parse(body);
227
+ } catch {
228
+ return void 0;
229
+ }
230
+ }
231
+ function coerceResearchOutput(value) {
232
+ if (!isRecord(value)) return void 0;
233
+ const items = coerceItems(value.items);
234
+ const citations = coerceCitations(value.citations);
235
+ const proposedWrites = coerceProposedWrites(value.proposedWrites);
236
+ if (items === void 0 && citations === void 0 && proposedWrites === void 0) {
237
+ return void 0;
238
+ }
239
+ const output = {
240
+ items: items ?? [],
241
+ citations: citations ?? [],
242
+ proposedWrites: proposedWrites ?? []
243
+ };
244
+ if (Array.isArray(value.gaps)) {
245
+ output.gaps = value.gaps.filter((entry) => typeof entry === "string");
246
+ }
247
+ const notes = pickString(value.notes);
248
+ if (notes) output.notes = notes;
249
+ const known = /* @__PURE__ */ new Set(["items", "citations", "proposedWrites", "gaps", "notes"]);
250
+ const extras = {};
251
+ let extrasCount = 0;
252
+ for (const [key, val] of Object.entries(value)) {
253
+ if (known.has(key)) continue;
254
+ extras[key] = val;
255
+ extrasCount += 1;
256
+ }
257
+ if (extrasCount > 0) output.raw = extras;
258
+ return output;
259
+ }
260
+ function coerceItems(value) {
261
+ if (!Array.isArray(value)) return void 0;
262
+ const out = [];
263
+ for (const entry of value) {
264
+ if (!isRecord(entry)) continue;
265
+ const id = pickString(entry.id);
266
+ const namespace = pickString(entry.namespace);
267
+ const claim = pickString(entry.claim);
268
+ const evidence = coerceEvidence(entry.evidence);
269
+ const confidence = toFiniteNumber(entry.confidence);
270
+ const authoredBy = coerceAuthoredBy(entry.authoredBy);
271
+ if (!id || !namespace || !claim || !authoredBy) continue;
272
+ const item = {
273
+ id,
274
+ namespace,
275
+ claim,
276
+ evidence,
277
+ confidence: clamp01(confidence),
278
+ authoredBy
279
+ };
280
+ if (Array.isArray(entry.supersedes)) {
281
+ item.supersedes = entry.supersedes.filter((s) => typeof s === "string");
282
+ }
283
+ const retractedAt = toFiniteNumber(entry.retractedAt);
284
+ if (retractedAt > 0) item.retractedAt = retractedAt;
285
+ out.push(item);
286
+ }
287
+ return out;
288
+ }
289
+ function coerceEvidence(value) {
290
+ if (!Array.isArray(value)) return [];
291
+ const out = [];
292
+ for (const entry of value) {
293
+ if (!isRecord(entry)) continue;
294
+ const source = pickString(entry.source);
295
+ if (!source) continue;
296
+ const item = {
297
+ source,
298
+ capturedAt: toFiniteNumber(entry.capturedAt)
299
+ };
300
+ const quote = pickString(entry.quote);
301
+ if (quote) item.quote = quote;
302
+ const url = pickString(entry.url);
303
+ if (url) item.url = url;
304
+ out.push(item);
305
+ }
306
+ return out;
307
+ }
308
+ function coerceAuthoredBy(value) {
309
+ if (!isRecord(value)) return void 0;
310
+ const kind = value.kind === "human" || value.kind === "agent" ? value.kind : void 0;
311
+ const id = pickString(value.id);
312
+ if (!kind || !id) return void 0;
313
+ return { kind, id };
314
+ }
315
+ function coerceCitations(value) {
316
+ if (!Array.isArray(value)) return void 0;
317
+ const out = [];
318
+ for (const entry of value) {
319
+ if (!isRecord(entry)) continue;
320
+ const url = pickString(entry.url);
321
+ const quote = pickString(entry.quote);
322
+ if (!url || !quote) continue;
323
+ out.push({ url, quote, confidence: clamp01(toFiniteNumber(entry.confidence)) });
324
+ }
325
+ return out;
326
+ }
327
+ function coerceProposedWrites(value) {
328
+ if (!Array.isArray(value)) return void 0;
329
+ const out = [];
330
+ for (const entry of value) {
331
+ if (!isRecord(entry)) continue;
332
+ const namespace = pickString(entry.namespace);
333
+ if (!namespace) continue;
334
+ if (entry.kind === "insert") {
335
+ const items = coerceItems([entry.item]);
336
+ const item = items?.[0];
337
+ if (!item) continue;
338
+ out.push({ kind: "insert", namespace, item });
339
+ } else if (entry.kind === "supersede") {
340
+ const previousId = pickString(entry.previousId);
341
+ const items = coerceItems([entry.item]);
342
+ const item = items?.[0];
343
+ if (!previousId || !item) continue;
344
+ out.push({ kind: "supersede", namespace, previousId, item });
345
+ } else if (entry.kind === "retract") {
346
+ const itemId = pickString(entry.itemId);
347
+ const reason = pickString(entry.reason);
348
+ if (!itemId || !reason) continue;
349
+ out.push({ kind: "retract", namespace, itemId, reason });
350
+ }
351
+ }
352
+ return out;
353
+ }
354
+ function toFiniteNumber(value) {
355
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
356
+ }
357
+ function clamp01(value) {
358
+ if (!Number.isFinite(value)) return 0;
359
+ if (value < 0) return 0;
360
+ if (value > 1) return 1;
361
+ return value;
362
+ }
363
+ export {
364
+ createResearcherValidator,
365
+ multiHarnessResearcherFanout,
366
+ researcherProfile
367
+ };
368
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/profiles/researcher.ts"],"sourcesContent":["/**\n * @experimental\n *\n * `researcherProfile` — opinionated preset for source-grounded research\n * tasks. The agent is told to:\n * - bound its work to a single `knowledgeNamespace`\n * - emit `items[]` carrying provenance + confidence\n * - emit `citations[]` linking quotes back to source urls\n * - emit `proposedWrites[]` — never call materialize itself\n * - describe `gaps` it could not answer\n *\n * The profile is stateless and agent-agnostic. `harness` selects the\n * sandbox-SDK backend. For heterogeneous fanout, use\n * `multiHarnessResearcherFanout`.\n *\n * Propose-don't-apply: the profile NEVER writes to the knowledge base.\n * It produces `proposedWrites: KnowledgeUpdate[]` in the output. The\n * caller (gtm-agent, journey-eval, user) decides whether to feed those\n * updates through `applyKnowledgeWriteBlocks` / a KbStore put.\n *\n * Namespace isolation: every `KnowledgeItem` + `KnowledgeUpdate` in the\n * output carries `namespace`. The validator hard-fails when any item\n * touches a namespace other than `task.knowledgeNamespace`.\n */\n\nimport {\n type AgentProfile,\n type AgentRunSpec,\n createFanoutVoteDriver,\n type DefaultVerdict,\n type Driver,\n type OutputAdapter,\n type SandboxEvent,\n type Validator,\n} from '@tangle-network/agent-runtime/loops'\n\n/** @experimental */\nexport type ResearchSource = 'web' | 'corpus' | 'twitter' | 'github' | 'docs'\n\n/** @experimental */\nexport interface ResearchTask {\n /** The research question to answer. */\n question: string\n /** Bound: e.g. \"audience for cpg-founder ICP\". */\n scope?: string\n /** Multi-tenant scope (customer-id, workspace-id). Validator enforces. */\n knowledgeNamespace: string\n sources?: ResearchSource[]\n recencyWindow?: { since?: Date; until?: Date }\n maxItems?: number\n /** Per-item minimum confidence in [0, 1]. Validator scores recall vs this. */\n minConfidence?: number\n}\n\n/**\n * Knowledge item emitted by the researcher.\n *\n * Profile-local type. When agent-knowledge promotes `KnowledgeClaim` →\n * top-level `KnowledgeItem` substrate-wide, these fields collapse 1:1.\n *\n * @experimental\n */\nexport interface KnowledgeItem {\n id: string\n /** Multi-tenant scope. MUST equal `task.knowledgeNamespace`. */\n namespace: string\n /** The factual claim, in the researcher's words. */\n claim: string\n /** Provenance — at least one entry required. */\n evidence: Array<{ source: string; quote?: string; url?: string; capturedAt: number }>\n /** Researcher's self-reported confidence in [0, 1]. */\n confidence: number\n /** Prior item ids this supersedes (chain). */\n supersedes?: string[]\n /** Set if the agent is retracting an earlier item. Unix ms. */\n retractedAt?: number\n authoredBy: { kind: 'human' | 'agent'; id: string }\n}\n\n/**\n * A proposed write to the knowledge base. The profile does NOT apply\n * these — the caller decides.\n *\n * @experimental\n */\nexport type KnowledgeUpdate =\n | { kind: 'insert'; namespace: string; item: KnowledgeItem }\n | { kind: 'supersede'; namespace: string; previousId: string; item: KnowledgeItem }\n | { kind: 'retract'; namespace: string; itemId: string; reason: string }\n\n/**\n * Researcher output. Required fields are typed; optional fields preserve\n * the agent's free-form intelligence (`notes`, `raw`). The validator\n * enforces the typed minimum.\n *\n * @experimental\n */\nexport interface ResearchOutput {\n items: KnowledgeItem[]\n citations: Array<{ url: string; quote: string; confidence: number }>\n proposedWrites: KnowledgeUpdate[]\n gaps?: string[]\n notes?: string\n /** Anything the agent emitted beyond the typed fields. */\n raw?: unknown\n}\n\n/** @experimental */\nexport interface ResearcherProfileOptions {\n /** Sandbox-SDK backend.type. Default `'opencode/zai-coding-plan/glm-5.1'`. */\n harness?: string\n /** Default model id passed in `AgentProfile.model.default`. */\n model?: string\n /** Custom system prompt replacement. Default = built-in researcher preset. */\n systemPrompt?: string\n /** Stable name for `AgentRunSpec.name`. Default = `researcher-${harness}`. */\n name?: string\n /**\n * Default 0.7. Minimum (citations with quote) / items ratio for `valid=true`.\n * Below this floor, citation_density scores < 1 and the item set is gated.\n */\n citationDensityMin?: number\n}\n\nconst DEFAULT_HARNESS = 'opencode/zai-coding-plan/glm-5.1'\nconst DEFAULT_CITATION_DENSITY_MIN = 0.7\n\n/** @experimental */\nexport function researcherProfile(\n options: ResearcherProfileOptions & { task?: ResearchTask } = {},\n): {\n profile: AgentProfile\n taskToPrompt: (task: ResearchTask) => string\n output: OutputAdapter<ResearchOutput>\n validator: Validator<ResearchOutput>\n agentRunSpec: AgentRunSpec<ResearchTask>\n} {\n const harness = options.harness ?? DEFAULT_HARNESS\n const name = options.name ?? `researcher-${harness}`\n const systemPrompt = options.systemPrompt ?? DEFAULT_RESEARCHER_SYSTEM_PROMPT\n const citationDensityMin = options.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN\n const profile: AgentProfile = {\n name,\n description: \"Source-grounded research agent. Propose-don't-apply.\",\n prompt: { systemPrompt },\n model: options.model ? { default: options.model } : undefined,\n tools: { web_search: true, fs: true, shell: true },\n metadata: { backendType: harness, role: 'researcher' },\n }\n const output: OutputAdapter<ResearchOutput> = { parse: parseResearcherEvents }\n const validator: Validator<ResearchOutput> = options.task\n ? createResearcherValidator(options.task, { citationDensityMin })\n : createResearcherValidator(\n { question: '', knowledgeNamespace: '' },\n { citationDensityMin, namespaceCheck: false },\n )\n const agentRunSpec: AgentRunSpec<ResearchTask> = {\n name,\n profile,\n taskToPrompt: formatResearcherPrompt,\n }\n return { profile, taskToPrompt: formatResearcherPrompt, output, validator, agentRunSpec }\n}\n\n/** @experimental */\nexport interface MultiHarnessResearcherFanoutOptions {\n /** Backend.type identifiers, one per parallel agent. */\n harnesses?: string[]\n /** Optional per-harness model override. Indexed parallel to `harnesses`. */\n models?: (string | undefined)[]\n /** Default citation density floor for the shared validator. */\n citationDensityMin?: number\n /** Optional task — narrows the validator's namespace check. */\n task?: ResearchTask\n}\n\n/**\n * Build a fanout topology over multiple harnesses. The kernel round-robins\n * `agentRuns` across the N parallel iterations and the `FanoutVote` driver\n * picks the highest-scoring valid output.\n *\n * @experimental\n */\nexport function multiHarnessResearcherFanout(options: MultiHarnessResearcherFanoutOptions = {}): {\n agentRuns: AgentRunSpec<ResearchTask>[]\n output: OutputAdapter<ResearchOutput>\n validator: Validator<ResearchOutput>\n driver: Driver<ResearchTask, ResearchOutput, 'pick-winner' | 'fail'>\n} {\n const harnesses =\n options.harnesses && options.harnesses.length > 0\n ? options.harnesses\n : ['opencode/zai-coding-plan/glm-5.1', 'claude-code', 'codex']\n const models = options.models ?? []\n const agentRuns = harnesses.map((harness, i) => {\n const { agentRunSpec } = researcherProfile({ harness, model: models[i] })\n return agentRunSpec\n })\n const { output, validator } = researcherProfile({\n citationDensityMin: options.citationDensityMin,\n task: options.task,\n })\n const driver = createFanoutVoteDriver<ResearchTask, ResearchOutput>({ n: harnesses.length })\n return { agentRuns, output, validator, driver }\n}\n\n/**\n * Build a validator that closes over a specific `ResearchTask`'s constraints.\n *\n * Checks in order:\n * 1. Items must be non-empty.\n * 2. Every item carries `evidence.length >= 1`.\n * 3. Every item + proposedWrite is scoped to `task.knowledgeNamespace`\n * (hard-fail on any namespace mismatch — defence in depth for the\n * multi-tenant invariant).\n * 4. Citation density (citations with quote / items) >= floor.\n *\n * Aggregate score:\n * 0.4 · citation_density\n * + 0.2 · source_diversity (distinct sources / max(items, 1))\n * + 0.2 · recency_match (mean fraction within `recencyWindow`)\n * + 0.2 · (1 − gaps/maxGaps), maxGaps = max(items, 1)\n *\n * @experimental\n */\nexport function createResearcherValidator(\n task: ResearchTask,\n config: { citationDensityMin?: number; namespaceCheck?: boolean } = {},\n): Validator<ResearchOutput> {\n const citationDensityMin = config.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN\n const namespaceCheck = config.namespaceCheck ?? true\n return {\n async validate(output) {\n const notes: string[] = []\n const scores: Record<string, number> = {}\n let pass = true\n\n if (!Array.isArray(output.items) || output.items.length === 0) {\n pass = false\n notes.push('no items')\n scores.items = 0\n } else {\n scores.items = 1\n }\n\n const missingEvidence = (output.items ?? []).filter(\n (item) => !Array.isArray(item.evidence) || item.evidence.length === 0,\n )\n if (missingEvidence.length > 0) {\n pass = false\n notes.push(`${missingEvidence.length} item(s) without evidence`)\n scores.provenance = 0\n } else {\n scores.provenance = 1\n }\n\n if (namespaceCheck) {\n const foreignItems = (output.items ?? []).filter(\n (item) => item.namespace !== task.knowledgeNamespace,\n )\n const foreignWrites = (output.proposedWrites ?? []).filter(\n (write) => write.namespace !== task.knowledgeNamespace,\n )\n if (foreignItems.length > 0 || foreignWrites.length > 0) {\n pass = false\n notes.push(\n `namespace violation: ${foreignItems.length} item(s) + ${foreignWrites.length} write(s) ` +\n `outside ${task.knowledgeNamespace}`,\n )\n scores.namespace = 0\n } else {\n scores.namespace = 1\n }\n }\n\n const itemCount = Math.max(output.items?.length ?? 0, 1)\n const citationsWithQuote = (output.citations ?? []).filter(\n (citation) => typeof citation.quote === 'string' && citation.quote.length > 0,\n ).length\n const citationDensity = Math.min(1, citationsWithQuote / itemCount)\n if (citationDensity < citationDensityMin) {\n pass = false\n notes.push(\n `citation density ${citationDensity.toFixed(2)} below floor ${citationDensityMin.toFixed(2)}`,\n )\n }\n scores.citation_density = citationDensity\n\n const sourceSet = new Set<string>()\n for (const item of output.items ?? []) {\n for (const evidence of item.evidence ?? []) {\n if (evidence.source) sourceSet.add(evidence.source)\n }\n }\n scores.source_diversity = Math.min(1, sourceSet.size / itemCount)\n\n scores.recency_match = recencyMatchScore(output.items ?? [], task.recencyWindow)\n\n const maxGaps = itemCount\n const gapCount = output.gaps?.length ?? 0\n scores.gap_coverage = Math.max(0, 1 - gapCount / maxGaps)\n\n const score =\n 0.4 * scores.citation_density +\n 0.2 * scores.source_diversity +\n 0.2 * scores.recency_match +\n 0.2 * scores.gap_coverage\n\n const verdict: DefaultVerdict = {\n valid: pass,\n score: Number.isFinite(score) ? score : 0,\n scores,\n }\n if (notes.length > 0) verdict.notes = notes.join('; ')\n return verdict\n },\n }\n}\n\nfunction recencyMatchScore(items: KnowledgeItem[], window: ResearchTask['recencyWindow']): number {\n if (!window || (window.since === undefined && window.until === undefined)) return 1\n if (items.length === 0) return 0\n const sinceMs = window.since?.getTime() ?? Number.NEGATIVE_INFINITY\n const untilMs = window.until?.getTime() ?? Number.POSITIVE_INFINITY\n let hits = 0\n let total = 0\n for (const item of items) {\n for (const evidence of item.evidence ?? []) {\n if (typeof evidence.capturedAt !== 'number') continue\n total += 1\n if (evidence.capturedAt >= sinceMs && evidence.capturedAt <= untilMs) hits += 1\n }\n }\n return total === 0 ? 0 : hits / total\n}\n\nconst DEFAULT_RESEARCHER_SYSTEM_PROMPT = [\n 'You are a research agent. Your job is to answer a research question with',\n 'source-grounded knowledge items that the caller will choose whether to',\n 'persist to a multi-tenant knowledge base.',\n '',\n 'Hard rules:',\n \" 1. Every item you emit MUST carry the task's knowledgeNamespace exactly.\",\n ' Never write to a different namespace.',\n ' 2. Every item MUST carry at least one evidence entry with a source.',\n ' A quote + url is strongly preferred; capturedAt is unix ms.',\n ' 3. You propose writes — you do NOT apply them. The caller decides.',\n ' 4. Self-report confidence honestly in [0, 1]. Do not inflate.',\n ' 5. List what you could not answer in `gaps`. Better to admit a gap',\n ' than fabricate.',\n '',\n 'When you finish, emit a single final structured message of the shape:',\n ' ```json',\n ' { \"items\": [{ \"id\": \"...\", \"namespace\": \"...\", \"claim\": \"...\",',\n ' \"evidence\": [{ \"source\": \"...\", \"quote\": \"...\",',\n ' \"url\": \"...\", \"capturedAt\": 0 }],',\n ' \"confidence\": 0.0,',\n ' \"authoredBy\": { \"kind\": \"agent\", \"id\": \"...\" } }],',\n ' \"citations\": [{ \"url\": \"...\", \"quote\": \"...\", \"confidence\": 0.0 }],',\n ' \"proposedWrites\": [{ \"kind\": \"insert\", \"namespace\": \"...\",',\n ' \"item\": { /* same shape as items[] */ } }],',\n ' \"gaps\": [\"...\"],',\n ' \"notes\": \"free-form commentary\" }',\n ' ```',\n].join('\\n')\n\nfunction formatResearcherPrompt(task: ResearchTask): string {\n const sources = task.sources?.length ? task.sources.join(', ') : '(no preference)'\n const window = formatRecencyWindow(task.recencyWindow)\n return [\n `Question: ${task.question}`,\n `Knowledge namespace (DO NOT cross): ${task.knowledgeNamespace}`,\n `Scope: ${task.scope ?? '(unspecified)'}`,\n `Preferred sources: ${sources}`,\n `Recency window: ${window}`,\n `Max items: ${task.maxItems ?? '(no cap)'}`,\n `Per-item minimum confidence: ${task.minConfidence ?? '(no floor)'}`,\n '',\n 'Produce knowledge items with provenance + citations + proposed writes.',\n 'List gaps for anything you could not answer. Emit the final JSON',\n 'result block exactly as instructed.',\n ].join('\\n')\n}\n\nfunction formatRecencyWindow(window: ResearchTask['recencyWindow']): string {\n if (!window) return '(none)'\n const since = window.since ? window.since.toISOString() : '-∞'\n const until = window.until ? window.until.toISOString() : 'now'\n return `${since} .. ${until}`\n}\n\n/**\n * Walk the event stream and return the last structured `research.result`\n * payload. Falls back to scanning text deltas for a fenced JSON block.\n */\nfunction parseResearcherEvents(events: SandboxEvent[]): ResearchOutput {\n for (let i = events.length - 1; i >= 0; i -= 1) {\n const event = events[i]\n if (!event) continue\n const type = String(event.type ?? '')\n const data = isRecord(event.data) ? event.data : {}\n if (type === 'result' || type === 'final' || type === 'research.result') {\n const direct = coerceResearchOutput(data.result ?? data.output ?? data)\n if (direct) return direct\n }\n }\n for (let i = events.length - 1; i >= 0; i -= 1) {\n const event = events[i]\n if (!event) continue\n const data = isRecord(event.data) ? event.data : {}\n const text = pickString(data.text) ?? pickString(data.delta)\n if (!text) continue\n const fenced = extractFencedJson(text)\n if (!fenced) continue\n const coerced = coerceResearchOutput(fenced)\n if (coerced) return coerced\n }\n return { items: [], citations: [], proposedWrites: [] }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction pickString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction extractFencedJson(text: string): unknown | undefined {\n const match = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/i)\n if (!match) return undefined\n const body = (match[1] ?? '').trim()\n if (!body) return undefined\n try {\n return JSON.parse(body)\n } catch {\n return undefined\n }\n}\n\nfunction coerceResearchOutput(value: unknown): ResearchOutput | undefined {\n if (!isRecord(value)) return undefined\n const items = coerceItems(value.items)\n const citations = coerceCitations(value.citations)\n const proposedWrites = coerceProposedWrites(value.proposedWrites)\n // Reject completely empty payloads — those signal \"no result block\",\n // not \"a valid empty result\".\n if (items === undefined && citations === undefined && proposedWrites === undefined) {\n return undefined\n }\n const output: ResearchOutput = {\n items: items ?? [],\n citations: citations ?? [],\n proposedWrites: proposedWrites ?? [],\n }\n if (Array.isArray(value.gaps)) {\n output.gaps = value.gaps.filter((entry): entry is string => typeof entry === 'string')\n }\n const notes = pickString(value.notes)\n if (notes) output.notes = notes\n // Preserve any extra fields the agent emitted that don't fit the typed\n // surface — callers can inspect `raw` without forcing them through the\n // typed coercion. We only include `raw` when the agent emitted fields\n // beyond the known set.\n const known = new Set(['items', 'citations', 'proposedWrites', 'gaps', 'notes'])\n const extras: Record<string, unknown> = {}\n let extrasCount = 0\n for (const [key, val] of Object.entries(value)) {\n if (known.has(key)) continue\n extras[key] = val\n extrasCount += 1\n }\n if (extrasCount > 0) output.raw = extras\n return output\n}\n\nfunction coerceItems(value: unknown): KnowledgeItem[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: KnowledgeItem[] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const id = pickString(entry.id)\n const namespace = pickString(entry.namespace)\n const claim = pickString(entry.claim)\n const evidence = coerceEvidence(entry.evidence)\n const confidence = toFiniteNumber(entry.confidence)\n const authoredBy = coerceAuthoredBy(entry.authoredBy)\n if (!id || !namespace || !claim || !authoredBy) continue\n const item: KnowledgeItem = {\n id,\n namespace,\n claim,\n evidence,\n confidence: clamp01(confidence),\n authoredBy,\n }\n if (Array.isArray(entry.supersedes)) {\n item.supersedes = entry.supersedes.filter((s): s is string => typeof s === 'string')\n }\n const retractedAt = toFiniteNumber(entry.retractedAt)\n if (retractedAt > 0) item.retractedAt = retractedAt\n out.push(item)\n }\n return out\n}\n\nfunction coerceEvidence(value: unknown): KnowledgeItem['evidence'] {\n if (!Array.isArray(value)) return []\n const out: KnowledgeItem['evidence'] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const source = pickString(entry.source)\n if (!source) continue\n const item: KnowledgeItem['evidence'][number] = {\n source,\n capturedAt: toFiniteNumber(entry.capturedAt),\n }\n const quote = pickString(entry.quote)\n if (quote) item.quote = quote\n const url = pickString(entry.url)\n if (url) item.url = url\n out.push(item)\n }\n return out\n}\n\nfunction coerceAuthoredBy(value: unknown): KnowledgeItem['authoredBy'] | undefined {\n if (!isRecord(value)) return undefined\n const kind = value.kind === 'human' || value.kind === 'agent' ? value.kind : undefined\n const id = pickString(value.id)\n if (!kind || !id) return undefined\n return { kind, id }\n}\n\nfunction coerceCitations(value: unknown): ResearchOutput['citations'] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: ResearchOutput['citations'] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const url = pickString(entry.url)\n const quote = pickString(entry.quote)\n if (!url || !quote) continue\n out.push({ url, quote, confidence: clamp01(toFiniteNumber(entry.confidence)) })\n }\n return out\n}\n\nfunction coerceProposedWrites(value: unknown): KnowledgeUpdate[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: KnowledgeUpdate[] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const namespace = pickString(entry.namespace)\n if (!namespace) continue\n if (entry.kind === 'insert') {\n const items = coerceItems([entry.item])\n const item = items?.[0]\n if (!item) continue\n out.push({ kind: 'insert', namespace, item })\n } else if (entry.kind === 'supersede') {\n const previousId = pickString(entry.previousId)\n const items = coerceItems([entry.item])\n const item = items?.[0]\n if (!previousId || !item) continue\n out.push({ kind: 'supersede', namespace, previousId, item })\n } else if (entry.kind === 'retract') {\n const itemId = pickString(entry.itemId)\n const reason = pickString(entry.reason)\n if (!itemId || !reason) continue\n out.push({ kind: 'retract', namespace, itemId, reason })\n }\n }\n return out\n}\n\nfunction toFiniteNumber(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0\n if (value < 0) return 0\n if (value > 1) return 1\n return value\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-knowledge",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Source-grounded, eval-gated knowledge growth primitives for agents.",
5
5
  "homepage": "https://github.com/tangle-network/agent-knowledge#readme",
6
6
  "repository": {
@@ -33,6 +33,11 @@
33
33
  "types": "./dist/sources/index.d.ts",
34
34
  "import": "./dist/sources/index.js",
35
35
  "default": "./dist/sources/index.js"
36
+ },
37
+ "./profiles": {
38
+ "types": "./dist/profiles/index.d.ts",
39
+ "import": "./dist/profiles/index.js",
40
+ "default": "./dist/profiles/index.js"
36
41
  }
37
42
  },
38
43
  "bin": {
@@ -47,8 +52,20 @@
47
52
  "publishConfig": {
48
53
  "access": "public"
49
54
  },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "dev": "tsup --watch",
58
+ "prepare": "tsup",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest",
61
+ "typecheck": "tsc --noEmit",
62
+ "lint": "biome check src tests",
63
+ "format": "biome format --write src tests"
64
+ },
50
65
  "dependencies": {
51
66
  "@tangle-network/agent-eval": "^0.29.1",
67
+ "@tangle-network/agent-runtime": "^0.19.0",
68
+ "@tangle-network/sandbox": "^0.2.1",
52
69
  "zod": "^4.3.6"
53
70
  },
54
71
  "devDependencies": {
@@ -58,17 +75,13 @@
58
75
  "typescript": "^5.7.0",
59
76
  "vitest": "^3.0.0"
60
77
  },
78
+ "pnpm": {
79
+ "minimumReleaseAge": 4320,
80
+ "minimumReleaseAgeExclude": []
81
+ },
61
82
  "engines": {
62
83
  "node": ">=20"
63
84
  },
64
85
  "license": "MIT",
65
- "scripts": {
66
- "build": "tsup",
67
- "dev": "tsup --watch",
68
- "test": "vitest run",
69
- "test:watch": "vitest",
70
- "typecheck": "tsc --noEmit",
71
- "lint": "biome check src tests",
72
- "format": "biome format --write src tests"
73
- }
74
- }
86
+ "packageManager": "pnpm@10.28.0"
87
+ }