chainlesschain 0.42.3 → 0.43.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/README.md +9 -7
- package/package.json +1 -1
- package/src/commands/init.js +1 -1
- package/src/lib/agent-coordinator.js +111 -0
- package/src/lib/agent-core.js +167 -2
- package/src/lib/cli-context-engineering.js +48 -15
- package/src/lib/cowork/debate-review-cli.js +12 -2
- package/src/lib/hierarchical-memory.js +186 -68
- package/src/lib/sub-agent-context.js +296 -0
- package/src/lib/sub-agent-registry.js +186 -0
- package/src/lib/ws-session-manager.js +8 -0
- package/src/repl/agent-repl.js +45 -0
package/README.md
CHANGED
|
@@ -200,7 +200,9 @@ chainlesschain agent --provider openai --api-key sk-...
|
|
|
200
200
|
|
|
201
201
|
Built-in tools: `read_file`, `write_file`, `edit_file`, `run_shell`, `search_files`, `list_dir`, `run_skill`, `list_skills`, `run_code`
|
|
202
202
|
|
|
203
|
-
Agent slash commands: `/plan` (plan mode), `/plan interactive <request>` (LLM-driven planning with skill recommendations), `/model`, `/provider`, `/clear`, `/compact`, `/task`, `/session`, `/stats`, `/auto` (autonomous agent), `/cowork` (multi-agent collaboration)
|
|
203
|
+
Agent slash commands: `/plan` (plan mode), `/plan interactive <request>` (LLM-driven planning with skill recommendations), `/model`, `/provider`, `/clear`, `/compact`, `/task`, `/session`, `/stats`, `/auto` (autonomous agent), `/cowork` (multi-agent collaboration), `/sub-agents` (show active/completed sub-agents)
|
|
204
|
+
|
|
205
|
+
**Sub-Agent Isolation v2** (v0.43.0): Complex tasks are automatically decomposed into isolated sub-agents, each with its own namespaced memory, scoped context, and lifecycle tracking. Use `/sub-agents` inside an agent session to inspect active and completed sub-agents, token usage, and average durations.
|
|
204
206
|
|
|
205
207
|
### `chainlesschain skill <action>`
|
|
206
208
|
|
|
@@ -845,7 +847,7 @@ chainlesschain serve --allow-remote --token <secret> # Allow remote + auth
|
|
|
845
847
|
chainlesschain serve --project /path/to/project # Default project root for sessions
|
|
846
848
|
```
|
|
847
849
|
|
|
848
|
-
**Session Protocol** (v0.
|
|
850
|
+
**Session Protocol** (v0.43.0): WebSocket clients can create stateful agent/chat sessions via `session-create`, send messages via `session-message`, resume previous sessions via `session-resume`, and manage sessions via `session-list`/`session-close`. Supports `slash-command` for in-session commands and `session-answer` for interactive Q&A (SlotFiller/Planner).
|
|
849
851
|
|
|
850
852
|
---
|
|
851
853
|
|
|
@@ -922,7 +924,7 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
|
|
|
922
924
|
```bash
|
|
923
925
|
cd packages/cli
|
|
924
926
|
npm install
|
|
925
|
-
npm test # Run all tests (
|
|
927
|
+
npm test # Run all tests (2748 tests across 124 files)
|
|
926
928
|
npm run test:unit # Unit tests only
|
|
927
929
|
npm run test:integration # Integration tests
|
|
928
930
|
npm run test:e2e # End-to-end tests
|
|
@@ -932,15 +934,15 @@ npm run test:e2e # End-to-end tests
|
|
|
932
934
|
|
|
933
935
|
| Category | Files | Tests | Status |
|
|
934
936
|
| ------------------------ | ------- | -------- | --------------- |
|
|
935
|
-
| Unit — lib modules |
|
|
937
|
+
| Unit — lib modules | 63 | 1380+ | All passing |
|
|
936
938
|
| Unit — commands | 15 | 350+ | All passing |
|
|
937
939
|
| Unit — runtime | 1 | 6 | All passing |
|
|
938
|
-
| Integration |
|
|
939
|
-
| E2E |
|
|
940
|
+
| Integration | 6 | 40+ | All passing |
|
|
941
|
+
| E2E | 15 | 160+ | All passing |
|
|
940
942
|
| Core packages (external) | — | 118 | All passing |
|
|
941
943
|
| Unit — WS sessions | 9 | 156 | All passing |
|
|
942
944
|
| Integration — WS session | 1 | 12 | All passing |
|
|
943
|
-
| **CLI Total** | **
|
|
945
|
+
| **CLI Total** | **124** | **2748** | **All passing** |
|
|
944
946
|
|
|
945
947
|
## License
|
|
946
948
|
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import crypto from "crypto";
|
|
9
|
+
import { SubAgentContext } from "./sub-agent-context.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Keyword map for agent type detection.
|
|
@@ -271,3 +272,113 @@ export function estimateComplexity(task) {
|
|
|
271
272
|
estimatedSubtasks: Math.max(1, matchedTypes),
|
|
272
273
|
};
|
|
273
274
|
}
|
|
275
|
+
|
|
276
|
+
// ─── Role-based tool whitelist ──────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
export const ROLE_TOOL_WHITELIST = {
|
|
279
|
+
"code-review": ["read_file", "search_files", "list_dir"],
|
|
280
|
+
"code-generation": [
|
|
281
|
+
"read_file",
|
|
282
|
+
"write_file",
|
|
283
|
+
"edit_file",
|
|
284
|
+
"run_shell",
|
|
285
|
+
"search_files",
|
|
286
|
+
"list_dir",
|
|
287
|
+
],
|
|
288
|
+
"data-analysis": [
|
|
289
|
+
"read_file",
|
|
290
|
+
"search_files",
|
|
291
|
+
"list_dir",
|
|
292
|
+
"run_code",
|
|
293
|
+
"run_shell",
|
|
294
|
+
],
|
|
295
|
+
document: ["read_file", "write_file", "search_files", "list_dir"],
|
|
296
|
+
testing: [
|
|
297
|
+
"read_file",
|
|
298
|
+
"write_file",
|
|
299
|
+
"edit_file",
|
|
300
|
+
"run_shell",
|
|
301
|
+
"search_files",
|
|
302
|
+
"list_dir",
|
|
303
|
+
"run_code",
|
|
304
|
+
],
|
|
305
|
+
general: null, // all tools
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Execute a decomposed task using isolated sub-agent contexts.
|
|
310
|
+
* Each subtask gets its own SubAgentContext with role-appropriate tool whitelist.
|
|
311
|
+
*
|
|
312
|
+
* @param {{ taskId: string, subtasks: Array }} decomposition - From decomposeTask()
|
|
313
|
+
* @param {object} [options]
|
|
314
|
+
* @param {string} [options.cwd] - Working directory
|
|
315
|
+
* @param {object} [options.db] - Database instance
|
|
316
|
+
* @param {object} [options.llmOptions] - LLM provider options
|
|
317
|
+
* @param {string} [options.parentContext] - Condensed context from parent
|
|
318
|
+
* @returns {Promise<{ taskId: string, status: string, results: Array, summary: string }>}
|
|
319
|
+
*/
|
|
320
|
+
export async function executeDecomposedTask(decomposition, options = {}) {
|
|
321
|
+
const { subtasks } = decomposition;
|
|
322
|
+
if (!subtasks || subtasks.length === 0) {
|
|
323
|
+
return {
|
|
324
|
+
taskId: decomposition.taskId,
|
|
325
|
+
status: "empty",
|
|
326
|
+
results: [],
|
|
327
|
+
summary: "No subtasks to execute",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const maxConcurrency = options.maxConcurrency || 3;
|
|
332
|
+
|
|
333
|
+
// Run subtasks in parallel batches with concurrency limit
|
|
334
|
+
const results = [];
|
|
335
|
+
for (let i = 0; i < subtasks.length; i += maxConcurrency) {
|
|
336
|
+
const batch = subtasks.slice(i, i + maxConcurrency);
|
|
337
|
+
const batchPromises = batch.map(async (subtask) => {
|
|
338
|
+
const allowedTools = ROLE_TOOL_WHITELIST[subtask.agentType] || null;
|
|
339
|
+
|
|
340
|
+
const subCtx = SubAgentContext.create({
|
|
341
|
+
role: subtask.agentType,
|
|
342
|
+
task: subtask.description,
|
|
343
|
+
inheritedContext: options.parentContext || null,
|
|
344
|
+
allowedTools,
|
|
345
|
+
cwd: options.cwd || process.cwd(),
|
|
346
|
+
db: options.db || null,
|
|
347
|
+
llmOptions: options.llmOptions || {},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const result = await subCtx.run(subtask.description);
|
|
352
|
+
subtask.status = "completed";
|
|
353
|
+
subtask.result = result.summary;
|
|
354
|
+
return {
|
|
355
|
+
id: subtask.id,
|
|
356
|
+
agentType: subtask.agentType,
|
|
357
|
+
status: "completed",
|
|
358
|
+
summary: result.summary,
|
|
359
|
+
toolsUsed: result.toolsUsed,
|
|
360
|
+
};
|
|
361
|
+
} catch (err) {
|
|
362
|
+
subtask.status = "failed";
|
|
363
|
+
subtask.result = err.message;
|
|
364
|
+
return {
|
|
365
|
+
id: subtask.id,
|
|
366
|
+
agentType: subtask.agentType,
|
|
367
|
+
status: "failed",
|
|
368
|
+
error: err.message,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const batchResults = await Promise.all(batchPromises);
|
|
374
|
+
results.push(...batchResults);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const aggregated = aggregateResults(subtasks);
|
|
378
|
+
return {
|
|
379
|
+
taskId: decomposition.taskId,
|
|
380
|
+
status: aggregated.status,
|
|
381
|
+
results,
|
|
382
|
+
summary: aggregated.summary,
|
|
383
|
+
};
|
|
384
|
+
}
|
package/src/lib/agent-core.js
CHANGED
|
@@ -23,6 +23,7 @@ import { CLISkillLoader } from "./skill-loader.js";
|
|
|
23
23
|
import { executeHooks, HookEvents } from "./hook-manager.js";
|
|
24
24
|
import { detectPython } from "./cli-anything-bridge.js";
|
|
25
25
|
import { findProjectRoot, loadProjectConfig } from "./project-detector.js";
|
|
26
|
+
import { SubAgentContext } from "./sub-agent-context.js";
|
|
26
27
|
|
|
27
28
|
// ─── Tool definitions ────────────────────────────────────────────────────
|
|
28
29
|
|
|
@@ -212,6 +213,40 @@ export const AGENT_TOOLS = [
|
|
|
212
213
|
},
|
|
213
214
|
},
|
|
214
215
|
},
|
|
216
|
+
{
|
|
217
|
+
type: "function",
|
|
218
|
+
function: {
|
|
219
|
+
name: "spawn_sub_agent",
|
|
220
|
+
description:
|
|
221
|
+
"Spawn an isolated sub-agent to handle a subtask. The sub-agent has its own context and message history, and only returns a summary result. Use this for tasks that benefit from focused, independent execution (e.g. code review, summarization, translation).",
|
|
222
|
+
parameters: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: {
|
|
225
|
+
role: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description:
|
|
228
|
+
"Sub-agent role (e.g. code-review, summarizer, translator, debugger)",
|
|
229
|
+
},
|
|
230
|
+
task: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Task description for the sub-agent",
|
|
233
|
+
},
|
|
234
|
+
context: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description:
|
|
237
|
+
"Optional condensed context from the parent agent to pass to the sub-agent",
|
|
238
|
+
},
|
|
239
|
+
tools: {
|
|
240
|
+
type: "array",
|
|
241
|
+
items: { type: "string" },
|
|
242
|
+
description:
|
|
243
|
+
'Optional tool whitelist for the sub-agent (e.g. ["read_file", "search_files"]). If omitted, all tools are available.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
required: ["role", "task"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
215
250
|
];
|
|
216
251
|
|
|
217
252
|
// ─── Shared skill loader ──────────────────────────────────────────────────
|
|
@@ -326,6 +361,16 @@ When the user's problem involves data processing, calculations, file operations,
|
|
|
326
361
|
|
|
327
362
|
You are not just a chatbot — you are a capable coding agent. Think step by step, write code when needed, and deliver real results.
|
|
328
363
|
|
|
364
|
+
## Sub-Agent Isolation
|
|
365
|
+
When a task involves multiple distinct roles (e.g. code review + code generation), or when you need
|
|
366
|
+
focused analysis without polluting your current context, use the spawn_sub_agent tool. Examples:
|
|
367
|
+
- Code review as a separate perspective while you're implementing
|
|
368
|
+
- Summarizing a large file before incorporating it into your response
|
|
369
|
+
- Running a focused analysis (security, performance) on specific code
|
|
370
|
+
- Translating or reformatting content independently
|
|
371
|
+
The sub-agent has its own message history and only returns a summary — your context stays clean.
|
|
372
|
+
Do NOT spawn sub-agents for trivial tasks that you can handle directly.
|
|
373
|
+
|
|
329
374
|
## Environment
|
|
330
375
|
${envLines.join("\n")}
|
|
331
376
|
|
|
@@ -512,7 +557,11 @@ export async function executeTool(name, args, context = {}) {
|
|
|
512
557
|
|
|
513
558
|
let toolResult;
|
|
514
559
|
try {
|
|
515
|
-
toolResult = await executeToolInner(name, args, {
|
|
560
|
+
toolResult = await executeToolInner(name, args, {
|
|
561
|
+
skillLoader,
|
|
562
|
+
cwd,
|
|
563
|
+
parentMessages: context.parentMessages,
|
|
564
|
+
});
|
|
516
565
|
} catch (err) {
|
|
517
566
|
if (hookDb) {
|
|
518
567
|
try {
|
|
@@ -550,7 +599,11 @@ export async function executeTool(name, args, context = {}) {
|
|
|
550
599
|
/**
|
|
551
600
|
* Inner tool execution — no hooks, no plan-mode checks.
|
|
552
601
|
*/
|
|
553
|
-
async function executeToolInner(
|
|
602
|
+
async function executeToolInner(
|
|
603
|
+
name,
|
|
604
|
+
args,
|
|
605
|
+
{ skillLoader, cwd, parentMessages },
|
|
606
|
+
) {
|
|
554
607
|
switch (name) {
|
|
555
608
|
case "read_file": {
|
|
556
609
|
const filePath = path.resolve(cwd, args.path);
|
|
@@ -613,6 +666,10 @@ async function executeToolInner(name, args, { skillLoader, cwd }) {
|
|
|
613
666
|
return _executeRunCode(args, cwd);
|
|
614
667
|
}
|
|
615
668
|
|
|
669
|
+
case "spawn_sub_agent": {
|
|
670
|
+
return _executeSpawnSubAgent(args, { skillLoader, cwd, parentMessages });
|
|
671
|
+
}
|
|
672
|
+
|
|
616
673
|
case "search_files": {
|
|
617
674
|
const dir = args.directory ? path.resolve(cwd, args.directory) : cwd;
|
|
618
675
|
try {
|
|
@@ -676,6 +733,31 @@ async function executeToolInner(name, args, { skillLoader, cwd }) {
|
|
|
676
733
|
error: `Skill "${args.skill_name}" not found or has no handler. Use list_skills to see available skills.`,
|
|
677
734
|
};
|
|
678
735
|
}
|
|
736
|
+
|
|
737
|
+
// Check if skill requests isolation (via SKILL.md frontmatter)
|
|
738
|
+
const skillIsolation = match.isolation === true;
|
|
739
|
+
if (skillIsolation) {
|
|
740
|
+
// Run skill through isolated sub-agent context
|
|
741
|
+
const subCtx = SubAgentContext.create({
|
|
742
|
+
role: `skill-${args.skill_name}`,
|
|
743
|
+
task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
|
|
744
|
+
allowedTools: ["read_file", "search_files", "list_dir"],
|
|
745
|
+
cwd,
|
|
746
|
+
});
|
|
747
|
+
try {
|
|
748
|
+
const result = await subCtx.run(args.input);
|
|
749
|
+
return {
|
|
750
|
+
success: true,
|
|
751
|
+
isolated: true,
|
|
752
|
+
skill: args.skill_name,
|
|
753
|
+
summary: result.summary,
|
|
754
|
+
toolsUsed: result.toolsUsed,
|
|
755
|
+
};
|
|
756
|
+
} catch (err) {
|
|
757
|
+
return { error: `Isolated skill execution failed: ${err.message}` };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
679
761
|
try {
|
|
680
762
|
const handlerPath = path.join(match.skillDir, "handler.js");
|
|
681
763
|
const imported = await import(
|
|
@@ -953,6 +1035,86 @@ async function _executeRunCode(args, cwd) {
|
|
|
953
1035
|
}
|
|
954
1036
|
}
|
|
955
1037
|
|
|
1038
|
+
// ─── spawn_sub_agent implementation ──────────────────────────────────────
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Execute a spawn_sub_agent tool call.
|
|
1042
|
+
* Creates an isolated SubAgentContext, runs it, and returns only the summary.
|
|
1043
|
+
*
|
|
1044
|
+
* @param {object} args - { role, task, context?, tools? }
|
|
1045
|
+
* @param {object} ctx - { skillLoader, cwd }
|
|
1046
|
+
* @returns {Promise<object>}
|
|
1047
|
+
*/
|
|
1048
|
+
async function _executeSpawnSubAgent(args, ctx) {
|
|
1049
|
+
const { role, task, context: inheritedContext, tools: allowedTools } = args;
|
|
1050
|
+
|
|
1051
|
+
if (!role || !task) {
|
|
1052
|
+
return { error: "Both 'role' and 'task' are required for spawn_sub_agent" };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Auto-condense parent context if caller didn't provide explicit context
|
|
1056
|
+
let resolvedContext = inheritedContext || null;
|
|
1057
|
+
if (!resolvedContext && Array.isArray(ctx.parentMessages)) {
|
|
1058
|
+
const recentMsgs = ctx.parentMessages
|
|
1059
|
+
.filter((m) => m.role === "assistant" && typeof m.content === "string")
|
|
1060
|
+
.slice(-3)
|
|
1061
|
+
.map((m) => m.content.substring(0, 200));
|
|
1062
|
+
if (recentMsgs.length > 0) {
|
|
1063
|
+
resolvedContext = recentMsgs.join("\n---\n");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const subCtx = SubAgentContext.create({
|
|
1068
|
+
role,
|
|
1069
|
+
task,
|
|
1070
|
+
inheritedContext: resolvedContext,
|
|
1071
|
+
allowedTools: allowedTools || null,
|
|
1072
|
+
cwd: ctx.cwd,
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
try {
|
|
1076
|
+
// Notify registry if available
|
|
1077
|
+
const { SubAgentRegistry } = await import("./sub-agent-registry.js").catch(
|
|
1078
|
+
() => ({ SubAgentRegistry: null }),
|
|
1079
|
+
);
|
|
1080
|
+
if (SubAgentRegistry) {
|
|
1081
|
+
try {
|
|
1082
|
+
SubAgentRegistry.getInstance().register(subCtx);
|
|
1083
|
+
} catch (_err) {
|
|
1084
|
+
// Registry not available — non-critical
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const result = await subCtx.run(task);
|
|
1089
|
+
|
|
1090
|
+
// Complete in registry
|
|
1091
|
+
if (SubAgentRegistry) {
|
|
1092
|
+
try {
|
|
1093
|
+
SubAgentRegistry.getInstance().complete(subCtx.id, result);
|
|
1094
|
+
} catch (_err) {
|
|
1095
|
+
// Non-critical
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return {
|
|
1100
|
+
success: true,
|
|
1101
|
+
subAgentId: subCtx.id,
|
|
1102
|
+
role: subCtx.role,
|
|
1103
|
+
summary: result.summary,
|
|
1104
|
+
toolsUsed: result.toolsUsed,
|
|
1105
|
+
iterationCount: result.iterationCount,
|
|
1106
|
+
artifactCount: result.artifacts.length,
|
|
1107
|
+
};
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
subCtx.forceComplete(err.message);
|
|
1110
|
+
return {
|
|
1111
|
+
error: `Sub-agent failed: ${err.message}`,
|
|
1112
|
+
subAgentId: subCtx.id,
|
|
1113
|
+
role: subCtx.role,
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
956
1118
|
// ─── LLM chat with tools ─────────────────────────────────────────────────
|
|
957
1119
|
|
|
958
1120
|
/**
|
|
@@ -1157,6 +1319,7 @@ export async function* agentLoop(messages, options) {
|
|
|
1157
1319
|
hookDb: options.hookDb || null,
|
|
1158
1320
|
skillLoader: options.skillLoader || _defaultSkillLoader,
|
|
1159
1321
|
cwd: options.cwd || process.cwd(),
|
|
1322
|
+
parentMessages: messages, // pass parent messages for sub-agent auto-condensation
|
|
1160
1323
|
};
|
|
1161
1324
|
|
|
1162
1325
|
// ── Slot-filling phase ──────────────────────────────────────────────
|
|
@@ -1292,6 +1455,8 @@ export function formatToolArgs(name, args) {
|
|
|
1292
1455
|
return args.category || args.query || "all";
|
|
1293
1456
|
case "run_code":
|
|
1294
1457
|
return `${args.language} (${(args.code || "").length} chars)`;
|
|
1458
|
+
case "spawn_sub_agent":
|
|
1459
|
+
return `[${args.role}] ${(args.task || "").substring(0, 60)}`;
|
|
1295
1460
|
default:
|
|
1296
1461
|
return JSON.stringify(args).substring(0, 60);
|
|
1297
1462
|
}
|
|
@@ -40,10 +40,15 @@ export class CLIContextEngineering {
|
|
|
40
40
|
* @param {object} options
|
|
41
41
|
* @param {object|null} options.db - Database instance (null for graceful degradation)
|
|
42
42
|
* @param {object|null} options.permanentMemory - CLIPermanentMemory instance (optional)
|
|
43
|
+
* @param {object|null} options.scope - Scoping options for sub-agent isolation
|
|
44
|
+
* @param {string} [options.scope.taskId] - Task/sub-agent ID
|
|
45
|
+
* @param {string} [options.scope.role] - Sub-agent role
|
|
46
|
+
* @param {string} [options.scope.parentObjective] - Parent task objective
|
|
43
47
|
*/
|
|
44
|
-
constructor({ db, permanentMemory } = {}) {
|
|
48
|
+
constructor({ db, permanentMemory, scope } = {}) {
|
|
45
49
|
this.db = db || null;
|
|
46
50
|
this.permanentMemory = permanentMemory || null;
|
|
51
|
+
this.scope = scope || null;
|
|
47
52
|
this.errorHistory = [];
|
|
48
53
|
this.taskContext = null;
|
|
49
54
|
this._bm25 = null;
|
|
@@ -52,6 +57,15 @@ export class CLIContextEngineering {
|
|
|
52
57
|
this._compactionSummaries = [];
|
|
53
58
|
// Stable prefix cache: { hash, cleanedPrefix }
|
|
54
59
|
this._prefixCache = null;
|
|
60
|
+
|
|
61
|
+
// When scoped, auto-set task context from scope
|
|
62
|
+
if (this.scope && this.scope.parentObjective) {
|
|
63
|
+
this.taskContext = {
|
|
64
|
+
objective: this.scope.parentObjective,
|
|
65
|
+
steps: [],
|
|
66
|
+
currentStep: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
/**
|
|
@@ -91,31 +105,46 @@ export class CLIContextEngineering {
|
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
// 3. Memory injection
|
|
108
|
+
// 3. Memory injection (scoped: higher threshold, namespace-aware)
|
|
95
109
|
if (this.db && userQuery) {
|
|
96
110
|
try {
|
|
97
|
-
const
|
|
111
|
+
const memoryQuery = this.scope
|
|
112
|
+
? `[${this.scope.role}] ${userQuery}`
|
|
113
|
+
: userQuery;
|
|
114
|
+
const memoryOpts = { limit: 5 };
|
|
115
|
+
if (this.scope) {
|
|
116
|
+
memoryOpts.namespace = this.scope.taskId;
|
|
117
|
+
}
|
|
118
|
+
const memories = _deps.recallMemory(this.db, memoryQuery, memoryOpts);
|
|
98
119
|
if (memories && memories.length > 0) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
// When scoped, apply higher relevance threshold to reduce noise
|
|
121
|
+
const threshold = this.scope ? 0.6 : 0.3;
|
|
122
|
+
const filtered = memories.filter((m) => m.retention >= threshold);
|
|
123
|
+
if (filtered.length > 0) {
|
|
124
|
+
const lines = filtered.map(
|
|
125
|
+
(m) =>
|
|
126
|
+
`- [${m.layer}] ${m.content} (retention: ${(m.retention * 100).toFixed(0)}%)`,
|
|
127
|
+
);
|
|
128
|
+
result.push({
|
|
129
|
+
role: "system",
|
|
130
|
+
content: `## Relevant Memories\n${lines.join("\n")}`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
107
133
|
}
|
|
108
134
|
} catch (_err) {
|
|
109
135
|
// Memory injection failed — skip silently
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
// 4. Notes injection (BM25 search)
|
|
139
|
+
// 4. Notes injection (BM25 search — scoped: role-prefixed query)
|
|
114
140
|
if (this.db && userQuery) {
|
|
115
141
|
try {
|
|
116
142
|
this._ensureNotesIndex();
|
|
117
143
|
if (this._bm25 && this._bm25.totalDocs > 0) {
|
|
118
|
-
const
|
|
144
|
+
const notesQuery = this.scope
|
|
145
|
+
? `[${this.scope.role}] ${userQuery}`
|
|
146
|
+
: userQuery;
|
|
147
|
+
const hits = this._bm25.search(notesQuery, {
|
|
119
148
|
topK: 3,
|
|
120
149
|
threshold: 0.5,
|
|
121
150
|
});
|
|
@@ -135,10 +164,14 @@ export class CLIContextEngineering {
|
|
|
135
164
|
}
|
|
136
165
|
}
|
|
137
166
|
|
|
138
|
-
// 5. Permanent memory injection
|
|
167
|
+
// 5. Permanent memory injection (scoped: reduced results)
|
|
139
168
|
if (this.permanentMemory && userQuery) {
|
|
140
169
|
try {
|
|
141
|
-
const
|
|
170
|
+
const pmLimit = this.scope ? 2 : 3;
|
|
171
|
+
const pmResults = this.permanentMemory.getRelevantContext(
|
|
172
|
+
userQuery,
|
|
173
|
+
pmLimit,
|
|
174
|
+
);
|
|
142
175
|
if (pmResults && pmResults.length > 0) {
|
|
143
176
|
const lines = pmResults.map(
|
|
144
177
|
(r) => `- [${r.source || "memory"}] ${r.content}`,
|
|
@@ -91,6 +91,16 @@ export async function startDebate({
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// Phase 2: Moderator synthesizes final verdict
|
|
94
|
+
// Summarize each reviewer's output to reduce context pollution for the moderator
|
|
95
|
+
const REVIEW_SUMMARY_MAX = 300;
|
|
96
|
+
const reviewSummaries = reviews.map((r) => {
|
|
97
|
+
const summarized =
|
|
98
|
+
r.review.length <= REVIEW_SUMMARY_MAX
|
|
99
|
+
? r.review
|
|
100
|
+
: r.review.substring(0, REVIEW_SUMMARY_MAX) + "... [truncated]";
|
|
101
|
+
return { ...r, reviewSummary: summarized };
|
|
102
|
+
});
|
|
103
|
+
|
|
94
104
|
const moderatorMessages = [
|
|
95
105
|
{
|
|
96
106
|
role: "system",
|
|
@@ -99,8 +109,8 @@ export async function startDebate({
|
|
|
99
109
|
},
|
|
100
110
|
{
|
|
101
111
|
role: "user",
|
|
102
|
-
content: `Multiple reviewers analyzed this code. Synthesize their findings into a final verdict.\n\nTarget: ${target}\n\n${
|
|
103
|
-
.map((r) => `### ${r.role} (${r.verdict})\n${r.
|
|
112
|
+
content: `Multiple reviewers analyzed this code. Synthesize their findings into a final verdict.\n\nTarget: ${target}\n\n${reviewSummaries
|
|
113
|
+
.map((r) => `### ${r.role} (${r.verdict})\n${r.reviewSummary}`)
|
|
104
114
|
.join(
|
|
105
115
|
"\n\n---\n\n",
|
|
106
116
|
)}\n\nProvide:\n1. Final Verdict: APPROVE / NEEDS_WORK / REJECT\n2. Consensus Score: 0-100 (how much the reviewers agree)\n3. Summary of key findings across all perspectives\n4. Priority action items (if any)`,
|