@tangle-network/agent-knowledge 1.7.0 → 1.8.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/dist/index.js CHANGED
@@ -33,13 +33,14 @@ import {
33
33
  reciprocalRankFusion,
34
34
  searchKnowledge,
35
35
  sourceRegistryPath,
36
+ stringMetadata,
36
37
  textSourceAdapter,
37
38
  tokenizeQuery,
38
39
  validateKnowledgeIndex,
39
40
  writeJson,
40
41
  writeKnowledgeIndex,
41
42
  writeSourceRegistry
42
- } from "./chunk-HKYD765Q.js";
43
+ } from "./chunk-WWY5FTKQ.js";
43
44
  import {
44
45
  AgentMemoryHitSchema,
45
46
  AgentMemoryKindSchema,
@@ -72,6 +73,9 @@ import {
72
73
  slugify,
73
74
  stableId
74
75
  } from "./chunk-YMKHCTS2.js";
76
+ import {
77
+ researcherProfile
78
+ } from "./chunk-MU5CEOGE.js";
75
79
 
76
80
  // src/changes.ts
77
81
  function detectChanges(prev, next, options = {}) {
@@ -421,10 +425,6 @@ function sourceFreshness(sources, now) {
421
425
  expiredSourceIds
422
426
  };
423
427
  }
424
- function stringMetadata(metadata, key) {
425
- const value = metadata?.[key];
426
- return typeof value === "string" ? value : void 0;
427
- }
428
428
  function isIsoDate(value) {
429
429
  return Boolean(value && Number.isFinite(Date.parse(value)));
430
430
  }
@@ -837,6 +837,11 @@ function knowledgeReleaseReport(input) {
837
837
  gateDecision: input.gateDecision ?? null,
838
838
  thresholds: {
839
839
  requireCorpus: false,
840
+ // The report gates on RunRecord-level outcomes, not on a separate scenario
841
+ // corpus (KnowledgeReleaseInput carries no dataset/scenarios). The scenario
842
+ // floor must therefore be 0 — otherwise the corpus axis fails closed on a
843
+ // dimension the report has no way to satisfy, masking the run-based gate.
844
+ minScenarioCount: 0,
840
845
  requireHoldout: input.hasHoldout ?? false,
841
846
  minHoldoutRuns: input.hasHoldout ? 1 : 0,
842
847
  minSearchRuns: 1,
@@ -859,6 +864,19 @@ import {
859
864
  blockingKnowledgeEval,
860
865
  objectiveEval
861
866
  } from "@tangle-network/agent-eval";
867
+
868
+ // src/readiness-helpers.ts
869
+ function readinessFor(options, index) {
870
+ if (!options.readinessSpecs?.length) return void 0;
871
+ return buildEvalKnowledgeBundle({
872
+ ...options.readiness ?? {},
873
+ taskId: options.readinessTaskId ?? options.goal,
874
+ index,
875
+ specs: options.readinessSpecs
876
+ });
877
+ }
878
+
879
+ // src/research-loop.ts
862
880
  function createKnowledgeControlLoopAdapter(options) {
863
881
  let initialized = false;
864
882
  const appliedSteps = [];
@@ -1005,15 +1023,256 @@ async function applyKnowledgeResearchDecision(options, decision, iteration = 1)
1005
1023
  metadata: decision.metadata
1006
1024
  };
1007
1025
  }
1008
- function readinessFor(options, index) {
1009
- if (!options.readinessSpecs?.length) return void 0;
1026
+
1027
+ // src/research-supervisor.ts
1028
+ import {
1029
+ supervise
1030
+ } from "@tangle-network/agent-runtime/loops";
1031
+ var RESEARCH_SUPERVISOR_SYSTEM_PROMPT = [
1032
+ "You are a research SUPERVISOR. You do not answer the research question yourself \u2014",
1033
+ "you create and manage a team of researcher workers that grow ONE shared",
1034
+ "knowledge base until it is ready.",
1035
+ "",
1036
+ "Each round:",
1037
+ " 1. Read the goal and the gaps the readiness gate still reports.",
1038
+ " 2. DECOMPOSE the open work into independent sub-topics. Spawn ONE researcher",
1039
+ " per sub-topic (spawn_agent) \u2014 split by sub-topic, never duplicate work.",
1040
+ " Spawn more researchers for a broad goal, fewer for a narrow one.",
1041
+ " 3. Wait for the researchers to settle (await_event). Steer or re-spawn for",
1042
+ " the sub-topics that came back thin.",
1043
+ " 4. STOP as soon as the deliverable check reports the knowledge base is ready.",
1044
+ "",
1045
+ "Conserve your budget: every spawn spends from one shared pool. Prefer a small",
1046
+ "number of well-scoped researchers over a large fanout that re-covers the same",
1047
+ "ground."
1048
+ ].join("\n");
1049
+ function knowledgeReadinessDeliverable(options) {
1050
+ return {
1051
+ describe: `knowledge base at ${options.root} is ready for: ${options.goal}`,
1052
+ async check() {
1053
+ const index = await buildKnowledgeIndex(options.root);
1054
+ const bundle = readinessFor(options, index);
1055
+ return (bundle?.report.blockingMissingRequirements.length ?? 0) === 0;
1056
+ }
1057
+ };
1058
+ }
1059
+ async function runResearchSupervisor(options) {
1060
+ await initKnowledgeBase(options.root);
1061
+ const { profile: workerProfile } = researcherProfile({ harness: options.harness });
1062
+ const baseInstructions = options.supervisorSystemPrompt ?? RESEARCH_SUPERVISOR_SYSTEM_PROMPT;
1063
+ const workerContract = workerProfile.prompt?.systemPrompt;
1064
+ const systemPrompt = workerContract ? `${baseInstructions}
1065
+
1066
+ Each researcher worker you spawn follows this contract:
1067
+ ${workerContract}` : baseInstructions;
1068
+ const profile = {
1069
+ name: "research-supervisor",
1070
+ model: options.supervisorModel,
1071
+ systemPrompt
1072
+ };
1073
+ return supervise(profile, options.goal, {
1074
+ ...options.superviseOptions,
1075
+ budget: options.budget,
1076
+ backend: options.backend,
1077
+ deliverable: knowledgeReadinessDeliverable(options)
1078
+ });
1079
+ }
1080
+
1081
+ // src/two-agent-research-loop.ts
1082
+ async function runTwoAgentResearchLoop(options) {
1083
+ const maxRounds = Math.max(1, options.maxRounds ?? 3);
1084
+ await initKnowledgeBase(options.root);
1085
+ const steps = [];
1086
+ let index = await buildKnowledgeIndex(options.root);
1087
+ let readiness = readinessFor(options, index);
1088
+ let ready = isReady(readiness?.report);
1089
+ let steer;
1090
+ for (let round2 = 1; round2 <= maxRounds && !ready; round2++) {
1091
+ if (options.signal?.aborted) throw new Error("Two-agent research loop aborted");
1092
+ const gaps = gapsFromReadiness(readiness);
1093
+ const workerContribution = await options.worker({
1094
+ root: options.root,
1095
+ goal: options.goal,
1096
+ round: round2,
1097
+ index,
1098
+ gaps,
1099
+ steer,
1100
+ readiness: requireReadiness(readiness, options),
1101
+ signal: options.signal
1102
+ });
1103
+ const accepted = [];
1104
+ const rejectedWorkerSources = [];
1105
+ const existingUris = new Set(
1106
+ index.sources.flatMap(
1107
+ (source) => typeof source.metadata?.originalUri === "string" ? [source.metadata.originalUri] : []
1108
+ )
1109
+ );
1110
+ for (const source of workerContribution.sources ?? []) {
1111
+ if (isDuplicate(source, existingUris, accepted)) {
1112
+ rejectedWorkerSources.push({ source, reason: "duplicate: already in the knowledge base" });
1113
+ continue;
1114
+ }
1115
+ const verdict = await options.driver.verifySource(source, {
1116
+ root: options.root,
1117
+ goal: options.goal,
1118
+ round: round2,
1119
+ index,
1120
+ gaps,
1121
+ acceptedThisRound: accepted,
1122
+ signal: options.signal
1123
+ });
1124
+ if (verdict.accept) accepted.push(source);
1125
+ else rejectedWorkerSources.push({ source, reason: verdict.reason });
1126
+ }
1127
+ const acceptedWorkerSources = await registerSources(options, accepted);
1128
+ const writtenPages = [];
1129
+ writtenPages.push(
1130
+ ...await applyPages(options.root, workerContribution, acceptedWorkerSources)
1131
+ );
1132
+ index = await buildKnowledgeIndex(options.root);
1133
+ readiness = readinessFor(options, index);
1134
+ let driverSources = [];
1135
+ let driverNotes;
1136
+ if (options.driverResearches && options.driver.research) {
1137
+ const remainingGaps2 = gapsFromReadiness(readiness);
1138
+ const driverContribution = await options.driver.research({
1139
+ root: options.root,
1140
+ goal: options.goal,
1141
+ round: round2,
1142
+ index,
1143
+ remainingGaps: remainingGaps2,
1144
+ readiness: requireReadiness(readiness, options),
1145
+ signal: options.signal
1146
+ });
1147
+ driverNotes = driverContribution.notes;
1148
+ driverSources = await registerSources(options, driverContribution.sources ?? []);
1149
+ writtenPages.push(...await applyPages(options.root, driverContribution, driverSources));
1150
+ index = await buildKnowledgeIndex(options.root);
1151
+ readiness = readinessFor(options, index);
1152
+ }
1153
+ ready = isReady(readiness?.report);
1154
+ const remainingGaps = gapsFromReadiness(readiness);
1155
+ steer = ready ? void 0 : foldGaps(options.driver, remainingGaps);
1156
+ const step = {
1157
+ round: round2,
1158
+ gaps,
1159
+ acceptedWorkerSources,
1160
+ rejectedWorkerSources,
1161
+ driverSources,
1162
+ writtenPages,
1163
+ readiness,
1164
+ ready,
1165
+ event: createKnowledgeEvent({
1166
+ type: "research.iteration",
1167
+ actor: options.actor,
1168
+ target: options.root,
1169
+ metadata: {
1170
+ goal: options.goal,
1171
+ round: round2,
1172
+ ready,
1173
+ acceptedWorkerSourceCount: acceptedWorkerSources.length,
1174
+ rejectedWorkerSourceCount: rejectedWorkerSources.length,
1175
+ driverSourceCount: driverSources.length,
1176
+ writtenPageCount: writtenPages.length,
1177
+ remainingGapCount: remainingGaps.length
1178
+ }
1179
+ }),
1180
+ notes: { worker: workerContribution.notes, driver: driverNotes }
1181
+ };
1182
+ steps.push(step);
1183
+ await options.onRound?.(step);
1184
+ }
1185
+ return {
1186
+ root: options.root,
1187
+ goal: options.goal,
1188
+ rounds: steps.length,
1189
+ ready,
1190
+ index,
1191
+ readiness,
1192
+ steps
1193
+ };
1194
+ }
1195
+ function isReady(report) {
1196
+ if (!report) return false;
1197
+ return report.blockingMissingRequirements.length === 0;
1198
+ }
1199
+ function gapsFromReadiness(readiness) {
1200
+ if (!readiness) return [];
1201
+ const blocking = readiness.report.blockingMissingRequirements.map(
1202
+ (requirement) => gapFor(requirement, readiness, true)
1203
+ );
1204
+ const nonBlocking = readiness.report.nonBlockingGaps.map(
1205
+ (requirement) => gapFor(requirement, readiness, false)
1206
+ );
1207
+ return [...blocking, ...nonBlocking];
1208
+ }
1209
+ function gapFor(requirement, readiness, blocking) {
1210
+ const spec = readiness.requirements.find((entry) => entry.id === requirement.id);
1211
+ const query = typeof spec?.metadata?.query === "string" ? spec.metadata.query : requirement.description;
1212
+ return { id: requirement.id, description: requirement.description, query, blocking };
1213
+ }
1214
+ function foldGaps(driver, gaps) {
1215
+ if (gaps.length === 0) return void 0;
1216
+ if (driver.foldGaps) return driver.foldGaps(gaps);
1217
+ return [
1218
+ "The knowledge base is still missing the following. Prioritise these next round:",
1219
+ ...gaps.map(
1220
+ (gap) => `- (${gap.blocking ? "blocking" : "soft"}) ${gap.description} [${gap.id}]`
1221
+ )
1222
+ ].join("\n");
1223
+ }
1224
+ function isDuplicate(source, existingUris, accepted) {
1225
+ return existingUris.has(source.uri) || accepted.some((candidate) => candidate.uri === source.uri);
1226
+ }
1227
+ async function registerSources(options, sources) {
1228
+ const records = [];
1229
+ for (const source of sources) {
1230
+ records.push(await addSourceText(options.root, source, options.sourceOptions));
1231
+ }
1232
+ return records;
1233
+ }
1234
+ async function applyPages(root, contribution, acceptedSources) {
1235
+ if (acceptedSources.length === 0) return [];
1236
+ const parts = [];
1237
+ if (contribution.proposalText) parts.push(contribution.proposalText);
1238
+ const built = contribution.buildPages?.(acceptedSources);
1239
+ if (built) parts.push(built);
1240
+ if (parts.length === 0) return [];
1241
+ const applied = await applyKnowledgeWriteBlocks(root, parts.join("\n"));
1242
+ return applied.written;
1243
+ }
1244
+ function requireReadiness(readiness, options) {
1245
+ if (readiness) return readiness;
1010
1246
  return buildEvalKnowledgeBundle({
1011
1247
  ...options.readiness ?? {},
1012
1248
  taskId: options.readinessTaskId ?? options.goal,
1013
- index,
1014
- specs: options.readinessSpecs
1249
+ index: emptyIndex(options.root),
1250
+ specs: []
1015
1251
  });
1016
1252
  }
1253
+ function emptyIndex(root) {
1254
+ return {
1255
+ root,
1256
+ generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1257
+ sources: [],
1258
+ pages: [],
1259
+ graph: { nodes: [], edges: [] }
1260
+ };
1261
+ }
1262
+ function sourceMatchesGaps(source, index, gaps) {
1263
+ const haystack = `${source.title ?? ""}
1264
+ ${source.text}`.toLowerCase();
1265
+ const hits = [];
1266
+ for (const gap of gaps) {
1267
+ for (const token of gap.query.toLowerCase().split(/\s+/).filter(Boolean)) {
1268
+ if (haystack.includes(token)) {
1269
+ hits.push(...searchKnowledge(index, gap.query, 1));
1270
+ break;
1271
+ }
1272
+ }
1273
+ }
1274
+ return hits;
1275
+ }
1017
1276
  export {
1018
1277
  AgentMemoryHitSchema,
1019
1278
  AgentMemoryKindSchema,
@@ -1033,6 +1292,7 @@ export {
1033
1292
  MemoryKbStore,
1034
1293
  POLITE_USER_AGENT,
1035
1294
  READINESS_SPEC_DEFAULTS,
1295
+ RESEARCH_SUPERVISOR_SYSTEM_PROMPT,
1036
1296
  SCAFFOLD_PAGE_BASENAMES,
1037
1297
  SourceAnchorSchema,
1038
1298
  SourceRecordSchema,
@@ -1069,6 +1329,7 @@ export {
1069
1329
  inspectKnowledgeIndex,
1070
1330
  isSafeKnowledgePath,
1071
1331
  isScaffoldPath,
1332
+ knowledgeReadinessDeliverable,
1072
1333
  knowledgeReleaseReport,
1073
1334
  layoutFor,
1074
1335
  lintKnowledgeIndex,
@@ -1087,9 +1348,12 @@ export {
1087
1348
  reciprocalRankFusion,
1088
1349
  renderMemoryContext,
1089
1350
  runKnowledgeResearchLoop,
1351
+ runResearchSupervisor,
1352
+ runTwoAgentResearchLoop,
1090
1353
  searchKnowledge,
1091
1354
  sha256,
1092
1355
  slugify,
1356
+ sourceMatchesGaps,
1093
1357
  sourceRegistryPath,
1094
1358
  stableId,
1095
1359
  stripFrontmatter,