Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ docker exec -it hermes-memory hermes
| `pipeline.l2MinIntervalSeconds` | `900` | Minimum interval between two L2 passes within the same session |
| `recall.timeoutMs` | `5000` | Recall timeout; on timeout, skip injection without blocking the conversation |
| `extraction.enableDedup` | `true` | L1 vector dedup / conflict detection |
| `extraction.model` | *(default model)* | LLM model for L1 extraction (format: `provider/model`) |
| `extraction.lane` | *(main lane)* | Global lane for extraction runs (e.g. `"cron"` to use `"cron-nested"` lane) |
| `persona.model` | *(default model)* | LLM model for L2 scene extraction & L3 persona generation |
| `persona.lane` | *(main lane)* | Global lane for persona/scene runs |
| `capture.excludeAgents` | `[]` | Glob patterns to exclude specific agents (e.g. `bench-judge-*`) |
| `capture.l0l1RetentionDays` | `0` | Local retention days for L0 / L1 files; `0` = never clean up |
| `offload.mildOffloadRatio` | `0.5` | Mild compression trigger ratio (of context window) |
Expand Down
3 changes: 3 additions & 0 deletions src/core/persona/persona-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export class PersonaGenerator {
config: unknown;
model?: string;
backupCount?: number;
/** Global lane name for the embedded agent run. */
lane?: string;
logger?: Logger;
/** Plugin instance ID for metric reporting (optional) */
instanceId?: string;
Expand All @@ -54,6 +56,7 @@ export class PersonaGenerator {
this.runner = opts.llmRunner ?? new CleanContextRunner({
config: opts.config,
modelRef: opts.model,
lane: opts.lane,
enableTools: true,
logger: opts.logger,
});
Expand Down
8 changes: 6 additions & 2 deletions src/core/record/l1-dedup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ export async function batchDedup(params: {
conflictRecallTopK?: number;
/** Override embedding timeout for capture-path calls (milliseconds) */
embeddingTimeoutMs?: number;
/** Global lane name for the embedded agent run. */
lane?: string;
/** Host-neutral LLM runner — when provided, used instead of CleanContextRunner. */
llmRunner?: LLMRunner;
}): Promise<DedupDecision[]> {
const { memories, config, logger, model, vectorStore, embeddingService, llmRunner } = params;
const { memories, config, logger, model, vectorStore, embeddingService, llmRunner, lane } = params;
const topK = params.conflictRecallTopK ?? 5;

if (memories.length === 0) {
Expand Down Expand Up @@ -139,7 +141,7 @@ export async function batchDedup(params: {
}

// Phase 2: Batch LLM judgment
return runLlmJudgment(matches, memories, config, logger, model, llmRunner);
return runLlmJudgment(matches, memories, config, logger, model, lane, llmRunner);
}

/**
Expand All @@ -151,6 +153,7 @@ async function runLlmJudgment(
config: unknown,
logger: Logger | undefined,
model: string | undefined,
lane: string | undefined,
llmRunner?: LLMRunner,
): Promise<DedupDecision[]> {
logger?.debug?.(`${TAG} Running batch conflict detection for ${memories.length} memories`);
Expand All @@ -172,6 +175,7 @@ async function runLlmJudgment(
const runner = new CleanContextRunner({
config,
modelRef: model,
lane,
enableTools: false,
logger,
});
Expand Down
6 changes: 6 additions & 0 deletions src/core/record/l1-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export async function extractL1Memories(params: {
model?: string;
/** Previous scene name for continuity */
previousSceneName?: string;
/** Global lane name for the embedded agent run. */
lane?: string;
/** Vector store for cosine similarity candidate recall */
vectorStore?: IMemoryStore;
/** Embedding service for computing query vectors */
Expand Down Expand Up @@ -165,6 +167,7 @@ export async function extractL1Memories(params: {
config,
logger,
model: options.model,
lane: options.lane,
llmRunner: options.llmRunner,
});
logger?.debug?.(`${TAG} LLM detected ${scenes.length} scene(s)`);
Expand Down Expand Up @@ -307,6 +310,8 @@ async function callLlmExtraction(params: {
config: unknown;
logger?: Logger;
model?: string;
/** Global lane name for the embedded agent run. */
lane?: string;
/** Host-neutral LLM runner — when provided, used instead of CleanContextRunner. */
llmRunner?: LLMRunner;
}): Promise<SceneSegment[]> {
Expand Down Expand Up @@ -338,6 +343,7 @@ async function callLlmExtraction(params: {
const runner = new CleanContextRunner({
config,
modelRef: model,
lane: params.lane,
enableTools: false,
logger,
});
Expand Down
6 changes: 6 additions & 0 deletions src/core/scene/scene-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface SceneExtractorOptions {
logger?: ExtractorLogger;
/** Plugin instance ID for metric reporting (optional) */
instanceId?: string;
/**
* Global lane name for the embedded agent run.
* @default undefined → OpenClaw defaults to "main"
*/
lane?: string;
/**
* Host-neutral LLM runner. When provided, used instead of creating
* a CleanContextRunner (decouples from OpenClaw runtime).
Expand Down Expand Up @@ -106,6 +111,7 @@ export class SceneExtractor {
this.runner = opts.llmRunner ?? new CleanContextRunner({
config: opts.config,
modelRef: opts.model,
lane: opts.lane,
enableTools: true,
logger: opts.logger,
});
Expand Down
15 changes: 15 additions & 0 deletions src/utils/clean-context-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ export interface CleanContextRunnerOptions {
agentRuntime?: EmbeddedAgentRuntimeLike;
/** Allow the LLM to use tools (read_file, write_to_file, etc). Default: false */
enableTools?: boolean;
/**
* Global lane name for the embedded agent run.
*
* OpenClaw routes embedded runs through a session lane (one-at-a-time per session)
* and a global lane (shared parallelism pool). By default the global lane is `"main"`,
* which is also used by the primary chat agent. Setting this to `"cron"` (→ `"cron-nested"`)
* or another named lane prevents long-running extraction calls from competing with
* interactive chat turns.
*
* @default undefined → OpenClaw defaults to `"main"`
*/
lane?: string;
/** Logger instance for detailed tracing */
logger?: RunnerLogger;
}
Expand Down Expand Up @@ -459,6 +471,9 @@ export class CleanContextRunner {
// providers (qwencode) reject with "[] is too short - 'tools'".
// Instead rely on cleanConfig.tools.allow to restrict the tool set
// to a minimal read-only tool (when enableTools=false).
// Pass lane to use a dedicated global lane (e.g. "cron" → "cron-nested")
// instead of the default "main" lane shared with interactive sessions.
lane: this.options.lane,
disableTools: false,
extraSystemPrompt: effectiveSystemPrompt,
streamParams: {
Expand Down