Skip to content

feat(capture): add captureExclude and captureSkipMarker#139

Open
vvvvroot wants to merge 1 commit intonowledge-co:mainfrom
vvvvroot:feat/capture-exclude
Open

feat(capture): add captureExclude and captureSkipMarker#139
vvvvroot wants to merge 1 commit intonowledge-co:mainfrom
vvvvroot:feat/capture-exclude

Conversation

@vvvvroot
Copy link

@vvvvroot vvvvroot commented Mar 22, 2026

Summary

Adds two config options to filter unwanted sessions from auto-capture:

  • captureExclude (array of glob patterns): matched against ctx.sessionKey to skip sessions deterministically. Glob * matches within a colon-delimited segment. Example: ["agent:*:cron:*", "agent:*:subagent:*"] excludes all cron jobs and executor subagent sessions.

  • captureSkipMarker (string, default #nmem-skip): when any message in the session contains this marker, the entire session is skipped. Gives users ad-hoc control directly from conversations.

Both layers apply to buildAgentEndCaptureHandler and buildBeforeResetCaptureHandler. When excluded, neither thread creation nor triage/distillation occurs.

Motivation

OpenClaw users running scheduled cron jobs (daily news digests, token usage reports) and executor subagent sessions accumulate hundreds of low-value threads that degrade Working Memory quality. In one production instance: 422 cron threads + 46 subagent threads out of 541 total (87% noise).

The existing sessionDigest toggle is all-or-nothing. Users need granular control to exclude specific session types while keeping valuable conversations (Telegram, Discord, direct interactions).

Config example

{
  "sessionDigest": true,
  "captureExclude": [
    "agent:*:cron:*",
    "agent:*:subagent:*"
  ],
  "captureSkipMarker": "#nmem-skip"
}

Changes

  • config.js: Add captureExclude and captureSkipMarker to ALLOWED_KEYS and parseConfig (file > pluginConfig > defaults)
  • hooks/capture.js: Add matchesExcludePattern() and hasSkipMarker() helpers; guard both handler builders
  • openclaw.plugin.json: Add schema properties and UI hints

Backward compatibility

  • captureExclude defaults to [] (no exclusions) — existing configs unchanged
  • captureSkipMarker defaults to "#nmem-skip" — no impact unless marker is used
  • No breaking changes to existing behavior

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Sessions can be excluded from auto-capture using configurable glob patterns
    • Added marker-based control to skip capturing entire sessions when detected in messages

Add two-layer filtering to skip unwanted sessions from auto-capture:

1. Pattern-based exclusion (captureExclude): array of glob patterns
   matched against ctx.sessionKey. Glob `*` matches within a
   colon-delimited segment. Example: "agent:*:cron:*" excludes all
   cron job sessions without affecting other conversations.

2. Marker-based exclusion (captureSkipMarker): when any message in
   the session contains the marker text (default: "#nmem-skip"),
   the entire session is skipped. Gives users ad-hoc control over
   which conversations are captured.

Both layers apply to buildAgentEndCaptureHandler (thread append +
triage/distill) and buildBeforeResetCaptureHandler (thread-only
checkpoints). When a session is excluded, neither thread creation
nor distillation occurs.

Use case: OpenClaw users with scheduled cron jobs (news digests,
token reports) and executor subagent sessions that produce hundreds
of low-value threads, degrading Working Memory quality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

The PR introduces two new session auto-capture controls: captureExclude (glob patterns to exclude matching session keys) and captureSkipMarker (marker string to prevent capturing when present in messages). Configuration schema, parsing logic, and capture handlers are updated to support these filtering mechanisms.

Changes

Cohort / File(s) Summary
Configuration Schema & Parsing
openclaw.plugin.json, src/config.js
Added two new user-configurable fields: captureExclude (array of glob patterns, default []) and captureSkipMarker (string, default #nmem-skip). Config parsing implements precedence rules: file-level overrides plugin-level, which overrides defaults; includes validation/filtering for string entries.
Capture Handler Filtering
src/hooks/capture.js
Added two helper functions (matchesExcludePattern, hasSkipMarker) for pattern and marker matching. Updated buildAgentEndCaptureHandler and buildBeforeResetCaptureHandler to apply early-exit filtering before capture: checks session key against exclude patterns and scans messages for skip marker; also changed handler signature parameter from _cfg to cfg.

Sequence Diagram(s)

sequenceDiagram
    actor Trigger as Capture Event<br/>(End/Reset)
    participant Handler as Capture Handler
    participant Config as Config<br/>captureExclude<br/>captureSkipMarker
    participant Filter as Filter Logic
    participant Session as Session<br/>Capture

    Trigger->>Handler: Trigger event
    Handler->>Handler: Derive sessionKey
    Handler->>Filter: Check matchesExcludePattern(sessionKey, patterns)
    Filter-->>Handler: Pattern matched?
    alt Pattern Match
        Handler->>Session: Return early<br/>(skip capture)
    else No Match
        Handler->>Filter: Check hasSkipMarker(messages, marker)
        Filter-->>Handler: Marker found?
        alt Marker Present
            Handler->>Session: Return early<br/>(skip capture)
        else No Marker
            Handler->>Session: Proceed with capture
            Session->>Session: Append/Distill thread
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with delight
Skip markers dance, exclude patterns gleam,
Sessions now filter and fly just right,
No more unwanted captures in the stream—
Config rules reign supreme! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature addition: two new capture filter configuration options (captureExclude and captureSkipMarker).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nowledge-mem-openclaw-plugin/src/hooks/capture.js (1)

454-454: ⚠️ Potential issue | 🔴 Critical

Bug: _cfg is undefined — should be cfg.

Line 454 references _cfg?.maxThreadMessageChars, but the parameter was renamed from _cfg to cfg on line 429. This causes maxThreadMessageChars to always fall back to the default (800), ignoring user configuration.

🐛 Proposed fix
 		await appendOrCreateThread({
 			client,
 			logger,
 			event,
 			ctx,
 			reason,
-			maxMessageChars: _cfg?.maxThreadMessageChars,
+			maxMessageChars: cfg.maxThreadMessageChars,
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-openclaw-plugin/src/hooks/capture.js` at line 454, The
maxMessageChars property is using the old variable name `_cfg` which is
undefined after the parameter rename to `cfg`, so update the reference to use
`cfg?.maxThreadMessageChars` (replace `_cfg?.maxThreadMessageChars` with
`cfg?.maxThreadMessageChars`) where maxMessageChars is set to ensure
user-provided maxThreadMessageChars is respected; verify this change in the
function using the cfg parameter that constructs the options object (look for
maxMessageChars and cfg in capture.js).
🧹 Nitpick comments (1)
nowledge-mem-openclaw-plugin/src/config.js (1)

348-371: Consider adding _sources tracking for consistency.

Other config fields track their origin in _sources for diagnostic reporting (e.g., _sources.sessionContext = sc.source). The new captureExclude and captureSkipMarker fields don't populate _sources, which creates an inconsistency in the config diagnostics.

♻️ Proposed fix to add source tracking
 // --- captureExclude: file > pluginConfig > default ---
 const captureExclude = (() => {
 	const fromFile = Array.isArray(resolvedFile.captureExclude)
 		? resolvedFile.captureExclude
 		: null;
 	const fromPlugin = Array.isArray(resolvedPlugin.captureExclude)
 		? resolvedPlugin.captureExclude
 		: null;
 	const raw = fromFile ?? fromPlugin ?? [];
+	_sources.captureExclude = fromFile ? "file" : fromPlugin ? "pluginConfig" : "default";
 	return raw.filter((v) => typeof v === "string" && v.trim());
 })();

 // --- captureSkipMarker: file > pluginConfig > default ---
 const captureSkipMarker = (() => {
 	const fromFile =
 		typeof resolvedFile.captureSkipMarker === "string"
 			? resolvedFile.captureSkipMarker.trim()
 			: undefined;
 	const fromPlugin =
 		typeof resolvedPlugin.captureSkipMarker === "string"
 			? resolvedPlugin.captureSkipMarker.trim()
 			: undefined;
+	_sources.captureSkipMarker = fromFile ? "file" : fromPlugin ? "pluginConfig" : "default";
 	return fromFile || fromPlugin || "#nmem-skip";
 })();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nowledge-mem-openclaw-plugin/src/config.js` around lines 348 - 371, The new
captureExclude and captureSkipMarker config values don't record their origin in
_sources; update the blocks that compute captureExclude and captureSkipMarker to
also set _sources.captureExclude and _sources.captureSkipMarker to indicate
where the value came from (use "file" when coming from resolvedFile, "plugin"
when from resolvedPlugin, and "default" otherwise), referencing the existing
computed values from resolvedFile and resolvedPlugin so diagnostics remain
consistent with other fields like _sources.sessionContext.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@nowledge-mem-openclaw-plugin/src/hooks/capture.js`:
- Line 454: The maxMessageChars property is using the old variable name `_cfg`
which is undefined after the parameter rename to `cfg`, so update the reference
to use `cfg?.maxThreadMessageChars` (replace `_cfg?.maxThreadMessageChars` with
`cfg?.maxThreadMessageChars`) where maxMessageChars is set to ensure
user-provided maxThreadMessageChars is respected; verify this change in the
function using the cfg parameter that constructs the options object (look for
maxMessageChars and cfg in capture.js).

---

Nitpick comments:
In `@nowledge-mem-openclaw-plugin/src/config.js`:
- Around line 348-371: The new captureExclude and captureSkipMarker config
values don't record their origin in _sources; update the blocks that compute
captureExclude and captureSkipMarker to also set _sources.captureExclude and
_sources.captureSkipMarker to indicate where the value came from (use "file"
when coming from resolvedFile, "plugin" when from resolvedPlugin, and "default"
otherwise), referencing the existing computed values from resolvedFile and
resolvedPlugin so diagnostics remain consistent with other fields like
_sources.sessionContext.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7ba281b-b4a2-4fff-a050-36eb00d46e8e

📥 Commits

Reviewing files that changed from the base of the PR and between 8363254 and 90bea07.

📒 Files selected for processing (3)
  • nowledge-mem-openclaw-plugin/openclaw.plugin.json
  • nowledge-mem-openclaw-plugin/src/config.js
  • nowledge-mem-openclaw-plugin/src/hooks/capture.js

Copy link
Member

@wey-gu wey-gu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #139captureExclude and captureSkipMarker

Good motivation, clean implementation. Two things to address before merge:

1. Context Engine path not covered (gap)

matchesExcludePattern() and hasSkipMarker() only guard the hooks path (buildAgentEndCaptureHandler, buildBeforeResetCaptureHandler). The Context Engine's afterTurn() in context-engine.js calls appendOrCreateThread + triageAndDistill directly — it does not check captureExclude or captureSkipMarker.

When a user has CE active (plugins.slots.contextEngine: "nowledge-mem"), excluded sessions will still be captured on every turn. This is the path that most active OpenClaw power users are on.

Suggested fix: export matchesExcludePattern and hasSkipMarker from capture.js, then add an early-return guard in afterTurn() in context-engine.js:

// context-engine.js, inside afterTurn()
if (matchesExcludePattern(sessionKey, cfg.captureExclude)) {
    logger.debug?.(`ce: skipped excluded session ${sessionKey}`);
    return;
}
// For hasSkipMarker, you'd need access to recent messages — 
// check if event.messages or the session transcript is available

2. Minor: regex compiled on every call

matchesExcludePattern creates new RegExp() per pattern per invocation. For the hooks path (session-end only) this is fine. But if extended to the CE afterTurn() (per-turn), consider caching compiled patterns — cfg.captureExclude is immutable after parse, so patterns can be compiled once in parseConfig() or lazily on first call.

Overall

The two-layer design (deterministic glob + ad-hoc marker) is elegant. Config precedence follows the established pattern. Schema additions are clean. Backward compatibility is solid.

Merge-ready once the CE path gap is addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants