Skip to content

feat(plugin): OpenClaw conversation turn hooks for memory capture#27

Open
ehfazrezwan wants to merge 14 commits intodevfrom
feature/openclaw-conversation-hook
Open

feat(plugin): OpenClaw conversation turn hooks for memory capture#27
ehfazrezwan wants to merge 14 commits intodevfrom
feature/openclaw-conversation-hook

Conversation

@ehfazrezwan
Copy link
Copy Markdown
Owner

Summary

  • Add conversation-turn hook that captures completed OpenClaw conversation turns and sends them to NeuralScape's conversation-compiler/flush endpoint for automatic memory extraction
  • Add session-summary hook that triggers end-of-session compilation via conversation-compiler/compile
  • Add isHeartbeat(), isNoReply(), and isSystemMessage() filtering helpers to skip trivial exchanges (heartbeat polls, NO_REPLY responses, system messages, short responses)
  • Add openclaw-hooks.json manifest for message:sent and session:end events
  • Document OpenClaw integration setup, managed hook installation, stdin payload formats, and verification steps in README

Test plan

  • Verify build succeeds: cd neuralscape-plugin && npm run build
  • Test conversation-turn filtering: pipe heartbeat/NO_REPLY/short payloads via stdin and confirm they are skipped
  • Test conversation-turn flush: pipe a valid turn payload and confirm it reaches the conversation-compiler endpoint
  • Test session-summary compilation: pipe a session-end payload and confirm compile is triggered
  • Verify managed hook installation works with OpenClaw

…fest

Define the interface all NeuralScape extensions must implement:
- ExtensionManifest (Pydantic model with name, version, description, hooks)
- NeuralscapeExtension (runtime-checkable Protocol with startup, shutdown,
  on_event, and get_routes methods)
Define canonical event types (conversation_turn, session_start,
session_end, memory_stored, compile_requested) as an enum with
corresponding Pydantic payload models for type-safe event dispatch.
ExtensionRegistry handles:
- Auto-discovery from extensions/ subdirectories
- Explicit registration via NEURALSCAPE_EXTENSIONS env var
- Startup/shutdown lifecycle with graceful failure handling
- Route mounting at /v1/extensions/<name>/
- Event broadcasting to extensions matching manifest.hooks
Integrate ExtensionRegistry into main.py:
- Discover and start extensions during lifespan startup
- Mount extension routes onto the app
- Shutdown extensions during lifespan teardown
- Add GET /v1/extensions endpoint to list registered extensions
- Add POST /v1/extensions/events endpoint for external event posting
Cover the extension protocol, step-by-step creation guide, lifecycle
(discovery → startup → events → shutdown), route mounting, event
listening, configuration via env vars, and a skeleton example.
30 tests covering registration, lifecycle (startup/shutdown), event
dispatch, route mounting, discovery (local + env var), graceful failure
handling, /v1/extensions listing endpoint, event schemas, and protocol
compliance.
Add isHeartbeat(), isNoReply(), and isSystemMessage() helpers for
filtering trivial conversation turns in OpenClaw integration.
Captures completed conversation turns and sends them to NeuralScape's
conversation-compiler flush endpoint. Accepts both direct invocation
and OpenClaw InternalHookEvent formats via stdin. Filters heartbeats,
NO_REPLY responses, system messages, and short trivial exchanges.
Triggers NeuralScape's conversation-compiler compile endpoint on
session end to consolidate captured turns into durable memories.
Skips sessions with fewer than 2 messages.
Add OpenClaw hook manifest for message:sent and session:end events.
Update esbuild entry points to include conversation-turn and
session-summary scripts.
Add README covering Claude Code and OpenClaw hook setup, environment
variables, managed hook installation, stdin payload formats, filtering
behavior, and verification steps.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds OpenClaw hook support to the neuralscape-plugin so completed conversation turns can be flushed to NeuralScape’s conversation-compiler, and an end-of-session hook can trigger compilation into durable memories.

Changes:

  • Add conversation-turn and session-summary hook handlers that POST to conversation-compiler/flush and conversation-compiler/compile.
  • Add message filtering helpers (isHeartbeat, isNoReply, isSystemMessage) to reduce noisy captures.
  • Add OpenClaw hooks manifest and README documentation for setup + stdin payload formats.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
neuralscape-plugin/src/utils.ts Adds filtering helper functions for heartbeat / no-reply / system messages.
neuralscape-plugin/src/conversation-turn.ts New hook handler to normalize/filter turns and flush them to NeuralScape.
neuralscape-plugin/src/session-summary.ts New hook handler to trigger end-of-session compilation by date/user.
neuralscape-plugin/hooks/openclaw-hooks.json Declares OpenClaw message:sent and session:end command hooks.
neuralscape-plugin/esbuild.config.js Bundles new hook entrypoints into scripts/.
neuralscape-plugin/README.md Documents OpenClaw + Claude Code integration, payloads, and verification steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +103
if (isHeartbeat(userMessage)) return true;
if (isNoReply(assistantResponse)) return true;
if (isSystemMessage(userMessage)) return true;
if (assistantResponse.length < MIN_RESPONSE_LENGTH) return true;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

assistantResponse.length is used for the short-response filter, so a response with lots of whitespace can bypass the minimum-length check and be flushed even if the meaningful content is very short. Consider using assistantResponse.trim().length (and/or normalizing earlier) so the filter matches the intent of “responses under N characters”.

Suggested change
if (isHeartbeat(userMessage)) return true;
if (isNoReply(assistantResponse)) return true;
if (isSystemMessage(userMessage)) return true;
if (assistantResponse.length < MIN_RESPONSE_LENGTH) return true;
const trimmedAssistantResponse = assistantResponse.trim();
if (isHeartbeat(userMessage)) return true;
if (isNoReply(assistantResponse)) return true;
if (isSystemMessage(userMessage)) return true;
if (trimmedAssistantResponse.length < MIN_RESPONSE_LENGTH) return true;

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +59
const date =
raw.date ||
(raw.timestamp
? new Date(raw.timestamp).toISOString().split("T")[0]
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

new Date(raw.timestamp).toISOString() will throw a RangeError if raw.timestamp is present but not a valid date string. Guard against invalid timestamps (e.g., check isNaN(date.getTime()) / Number.isNaN(Date.parse(...))) and fall back to new Date() or the provided date instead.

Suggested change
const date =
raw.date ||
(raw.timestamp
? new Date(raw.timestamp).toISOString().split("T")[0]
const timestampDate = raw.timestamp ? new Date(raw.timestamp) : null;
const date =
raw.date ||
(timestampDate && !Number.isNaN(timestampDate.getTime())
? timestampDate.toISOString().split("T")[0]

Copilot uses AI. Check for mistakes.
function shouldSkipTurn(userMessage: string, assistantResponse: string): boolean {
if (isHeartbeat(userMessage)) return true;
if (isNoReply(assistantResponse)) return true;
if (isSystemMessage(userMessage)) return true;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

isSystemMessage() is only applied to userMessage, but for a message:sent hook the system-like markers (e.g. [system], [internal]) are likely to appear in the assistant payload (assistantResponse / context.content). Consider checking assistantResponse as well (or instead) so system-prefixed assistant messages are actually filtered out.

Suggested change
if (isSystemMessage(userMessage)) return true;
if (isSystemMessage(userMessage)) return true;
if (isSystemMessage(assistantResponse)) return true;

Copilot uses AI. Check for mistakes.
…istant for system messages

Trim whitespace before checking MIN_RESPONSE_LENGTH so padded
responses are correctly filtered. Also skip turns where the
assistant response is itself a system message.
Parse the timestamp into a Date first and check for NaN before
calling toISOString(), falling back to current date on invalid input.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ed309dd9-db5a-4a87-ad4e-436f60a09a79

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/openclaw-conversation-hook

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

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