Skip to content

memory: restore Claude basic_memory capture/injection; add Codex/OMP capture + bounded reindex - #165

Merged
yourconscience merged 2 commits into
mainfrom
fm/memory-capture-restore
Sep 9, 2026
Merged

memory: restore Claude basic_memory capture/injection; add Codex/OMP capture + bounded reindex#165
yourconscience merged 2 commits into
mainfrom
fm/memory-capture-restore

Conversation

@yourconscience

@yourconscience yourconscience commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What

Restores automatic session-memory capture and injection for Claude Code, and adds equivalent local capture for Codex and OMP/Pi. Root cause and evidence: knowledge-audit report sections 3, 6, 8.

The registered Claude hooks delegate to a memsearch plugins/claude-code directory that no longer ships in memsearch 0.2.x, and there was no fallback — so no Claude digests have been written since ~2026-08-25 and no memory context is injected at session start. The basic_memory implementation was healthy but unwired.

Changes (scope: memory/hooks/*, memory/lib/basic_memory.py, memory/tests/*)

  • R1 — Claude fallback. When resolve_claude_memory_plugin is empty, session-end.sh now falls back to the local basic_memory digest and session-start.sh falls back to basic-session-start.py (restoring additionalContext injection). The plugin path stays preferred when it exists.
  • R7 — Codex/OMP capture. The classifier/dispatch (now shared in common.sh) recognizes Codex and OMP payloads (via an explicit DOTAGENTS_MEMORY_SOURCE hint or the payload's own agent/platform marker) and routes them to the same local basic_memory digest — never the Claude plugin. stop.sh also honors this so Codex/OMP wired to their Stop event capture; plain Claude Stop stays a clean continuation so the Claude SessionEnd hook owns the full-session digest. build_digest honors DOTAGENTS_MEMORY_SOURCE for the source: label.
  • R2 — Reindex. After a digest is actually appended, refresh_index_async fires a backgrounded memsearch index of the vault. It never blocks the hook, is bounded by a watchdog (MEMSEARCH_REINDEX_TIMEOUT, default 120s; no dependency on timeout(1)), and refuses to overlap a running refresh via an atomic mkdir lock.
  • R5 — one-line rem add -src … reminders added where the repo now documents Codex/OMP capture.

Manual wiring (not installed automatically)

dotagents does not install Codex hooks or OMP extensions, so their capture is opt-in. See memory/hooks/README-codex-omp.md.

Codex — add to ~/.codex/hooks.json under SessionEnd (preferred) or Stop:

{ "type": "command", "command": "DOTAGENTS_MEMORY_SOURCE=codex ~/.agents/memory/hooks/session-end.sh", "timeout": 30 }

The DOTAGENTS_MEMORY_SOURCE=codex env labels the digest and makes the Stop-wired path capture rather than pass through.

OMP/Pi — copy the shipped extension:

cp ~/.agents/memory/hooks/omp-memory.ts ~/.omp/agent/extensions/

It fires on agent_end and pipes an agent: "omp" payload into session-end.sh.

Follow-up (owner of setup_scaffold.go): the memsearch-tier setup already targets Codex for these dispatchers; adding DOTAGENTS_MEMORY_SOURCE=codex to that generated command would give Codex-labelled digests out of the box.

Testing

  • python3 -m unittest over memory/tests/ — 27 tests pass, covering: plugin-present path unchanged (delegates, no basic digest), plugin-missing fallback (writes digest + injects context), Codex/OMP classification and source labelling, Claude Stop no-capture, and the reindex being gated on append, non-overlapping, non-blocking, and watchdog-bounded.
  • go test ./... — passes (includes the existing session-end.sh e2e).
  • shellcheck on the touched scripts — only pre-existing CDPATH= cd (SC1007) / source (SC1091) notes; no new findings.
  • Verified end-to-end against a scratch KNOWLEDGE_DIR (real vault/hooks/settings untouched): Claude SessionEnd writes a digest, SessionStart injects it, Codex/OMP payloads produce labelled digests, and the reindex fires once.

Design notes

  • Claude capture is centralized on SessionEnd (full-session digest); Stop intentionally does not double-capture, so marker de-dup can't pin the digest to a first-turn snapshot.
  • Reindex avoids a timeout(1) dependency (absent on stock macOS) by using a background watchdog.

Summary by Sourcery

Restore reliable Claude memory capture and injection while extending local digest capture to Codex and OMP/Pi with asynchronous, bounded indexing.

New Features:

  • Add local memory capture for Codex and OMP/Pi sessions through the shared hook dispatcher.
  • Inject recent local memory into Claude sessions when the Claude memory plugin is unavailable.

Bug Fixes:

  • Restore Claude session-end digest capture and session-start context injection when the external Claude plugin is missing.

Enhancements:

  • Add bounded, asynchronous vault reindexing after newly appended digests with overlap prevention and stale-lock recovery.
  • Preserve the external Claude plugin path when available while routing Codex and OMP capture to local basic memory with source labels.

Documentation:

  • Document manual Codex hook and OMP/Pi extension setup and deliberate cross-harness memory capture.

Tests:

  • Add coverage for Claude fallback behavior, Codex/OMP dispatch, source labeling, Stop handling, and bounded non-overlapping reindexing.

@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR restores Claude basic-memory capture and SessionStart injection when the Claude plugin is missing, adds source-aware Codex and OMP/Pi capture through shared dispatch logic, and triggers a backgrounded, watchdog-bounded, lock-protected reindex only after a digest is appended.

Sequence diagram for cross-harness memory capture and reindex

sequenceDiagram
    participant Harness as Claude/Codex/OMP
    participant Hook as session-end.sh
    participant Dispatch as dispatch_basic_digest
    participant Basic as basic-session-end.py
    participant Vault as Memory vault
    participant Index as memsearch index

    Harness->>Hook: Send session payload
    Hook->>Hook: classify_payload
    alt Claude plugin exists
        Hook->>Hook: Claude plugin session-end.sh
    else Claude plugin missing, or source is Codex/OMP
        Hook->>Dispatch: dispatch_basic_digest
        Dispatch->>Basic: Write local digest
        Basic->>Vault: Append digest
        alt Digest appended
            Dispatch->>Index: refresh_index_async
            Index->>Index: Acquire reindex.lock
            Index->>Vault: Index vault in background
        end
    end
Loading

Flow diagram for Claude memory fallback and context injection

flowchart TD
    Start[Claude session] --> EndHook[session-end.sh]
    EndHook --> Plugin{Claude plugin available?}
    Plugin -->|Yes| PluginCapture[Claude plugin capture]
    Plugin -->|No| BasicCapture[basic-session-end.py]
    BasicCapture --> Digest[Append local session digest]
    Start --> StartHook[session-start.sh]
    StartHook --> PluginStart{Claude plugin available?}
    PluginStart -->|Yes| PluginContext[Claude plugin context lookup]
    PluginStart -->|No| BasicStart[basic-session-start.py]
    BasicStart --> Context[Return additionalContext]
Loading

File-Level Changes

Change Details Files
Restore Claude local memory behavior when the external Claude plugin is unavailable.
  • Keep the resolved plugin as the preferred Claude SessionEnd/SessionStart path.
  • Fallback to the local basic-memory digest writer on SessionEnd.
  • Fallback to local session-start context injection on SessionStart.
  • Preserve plugin-path behavior without creating duplicate local digests.
memory/hooks/session-end.sh
memory/hooks/session-start.sh
memory/tests/test_basic_memory_hooks.py
Centralize hook payload classification and route Codex/OMP capture through basic memory.
  • Move payload classification into shared shell logic with explicit source hints and agent/platform detection.
  • Capture Codex and OMP payloads through the local digest path and label sources appropriately.
  • Handle Codex/OMP Stop wiring while leaving Claude Stop as a non-capturing continuation.
  • Add an OMP/Pi agent-end extension and manual wiring documentation.
memory/hooks/common.sh
memory/hooks/session-end.sh
memory/hooks/stop.sh
memory/hooks/omp-memory.ts
memory/hooks/README-codex-omp.md
memory/lib/basic_memory.py
memory/tests/test_basic_memory_hooks.py
Add asynchronous, bounded, non-overlapping vault reindexing after successful digest creation.
  • Start reindexing only when a new digest is appended.
  • Run indexing in the background so hooks return promptly.
  • Use an atomic lock to prevent concurrent refreshes.
  • Terminate hung indexing through an internal watchdog without relying on timeout(1).
memory/hooks/common.sh
memory/tests/test_basic_memory_hooks.py
Document deliberate Codex/OMP memory promotion alongside automatic capture.
  • Add source-specific rem add examples for Codex and OMP facts.
  • Document opt-in Codex SessionEnd/Stop and OMP extension installation.
memory/hooks/README-codex-omp.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d89e6d44-7b2b-49b9-87d8-54cb406b0de4


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.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="memory/lib/basic_memory.py" line_range="306-312" />
<code_context>
     assistant = last_assistant_text(messages)
     paths = extract_paths(messages, payload)
-    platform = payload.get("platform") or payload.get("agent") or payload.get("hook_event_name") or "basic"
+    platform = (
+        payload.get("platform")
+        or payload.get("agent")
+        or os.environ.get("DOTAGENTS_MEMORY_SOURCE")
+        or payload.get("hook_event_name")
+        or "basic"
+    )
     model = payload.get("model")
</code_context>
<issue_to_address>
**issue (bug_risk):** `DOTAGENTS_MEMORY_SOURCE` does not reliably control the digest source label because `payload.platform` and `payload.agent` take precedence over the environment hint. A payload dispatched as Codex/OMP can therefore be written with a different source label when it also contains another platform or agent marker.

**Triggers:** When `DOTAGENTS_MEMORY_SOURCE` is set and the payload also contains a non-matching `platform` or `agent` field.

**Suggested fix:** Give `DOTAGENTS_MEMORY_SOURCE` precedence over payload platform and agent fields, or reject conflicting markers.

```suggestion
    platform = (
        os.environ.get("DOTAGENTS_MEMORY_SOURCE")
        or payload.get("platform")
        or payload.get("agent")
        or payload.get("hook_event_name")
        or "basic"
    )
```
</issue_to_address>

### Comment 2
<location path="memory/hooks/common.sh" line_range="64-69" />
<code_context>
+  command -v memsearch >/dev/null 2>&1 || return 0
+  prepare_memory_index_env
+
+  reindex_lock="${MEMSEARCH_STATE_DIR%/}/reindex.lock"
+  # mkdir is atomic: it fails when a refresh already holds the lock, so we never
+  # spawn overlapping reindexers.
+  if ! mkdir "$reindex_lock" 2>/dev/null; then
+    return 0
+  fi
+
+  (
</code_context>
<issue_to_address>
**issue (bug_risk):** A forced termination of the background refresh leaves `reindex.lock` behind because cleanup depends on the subshell's EXIT trap. That stale directory permanently suppresses all future reindexes until it is manually removed.

**Triggers:** When the hook process or background refresh is killed with SIGKILL, or the machine terminates during an active refresh.

**Suggested fix:** Store the lock owner's PID and creation metadata and recover stale locks safely, or use a lock mechanism that is automatically removed by the operating system.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and a faulty capture or digest could persist incorrect or sensitive session content in the local memory store and inject it into later sessions; reverting the hooks would not remove digests already written. The data is local and bounded, however, so the impact can be repaired by deleting or rebuilding the affected memory and reindexing.

Blocking findings: memory/lib/basic_memory.py:312, memory/hooks/common.sh:69


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread memory/lib/basic_memory.py
Comment thread memory/hooks/common.sh
@yourconscience
yourconscience merged commit 1ba17a3 into main Sep 9, 2026
6 checks passed
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.

1 participant