Skip to content

memory: memsearch parity + reindex-after-sync, pinned sync identity - #166

Merged
yourconscience merged 4 commits into
mainfrom
fm/memsearch-sync-parity
Sep 9, 2026
Merged

memory: memsearch parity + reindex-after-sync, pinned sync identity#166
yourconscience merged 4 commits into
mainfrom
fm/memsearch-sync-parity

Conversation

@yourconscience

@yourconscience yourconscience commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Makes memsearch a first-class, equal-citizen search backend on every machine, with derived per-machine indexes and automatic refresh, plus the audit's sync/consolidation fixes (report sections 2, 5, 7, 8). No per-machine index is ever synced or canonical; the vault markdown is the only source of truth.

Changes (all within the memory tool surface)

R2 - reindex-after-sync (knowledge-sync)
After a successful pull/merge/push, knowledge-sync triggers a bounded, best-effort refresh of the derived index (memsearch index <vault> --collection ai). It never blocks the sync (git work is already done), skips when a concurrent refresh holds the shared lock, and is a no-op when memsearch is absent. This plus reindex-after-capture replace a reindex cron. Timeout via MEMSEARCH_REINDEX_TIMEOUT_SECONDS (default 300s).

Shared reindex contract (memory/tools/memsearch/README.md)
Both freshness triggers are idempotent, target collection ai, index the whole vault, and coordinate through one non-blocking OS advisory lock at ~/.memsearch/reindex.lock so they never overlap. sync.py's post-sync reindex now uses the same canonical whole-vault scope and lock.

Multi-machine parity (memory/tools/memsearch/install-parity.sh + README)
One idempotent script brings any machine to full parity: uv tool install 'memsearch[onnx]', config provider=onnx + collection=ai, index derived from the canonical markdown, and --rebuild for a documented full rebuild from zero. Documents that the index is disposable and never synced.

R4 - two-dreams split: documented one owner per input
Chose the smaller correct change (documentation, not code): Go rem dream owns ai/ candidate promotion; Python basic_memory dream owns sessions/ digest review. They consume different inputs and neither feeds the other. Pointing Go rem dream at sessions/ would have added a third consumer of that input and increased the duplication R4 asks to remove; documenting the split respects the ownership boundary (basic_memory.py is owned by another change).

R6 - pinned knowledge-sync identity
Root cause: there is no persistent git identity on this host (no ~/.gitconfig, no repo-local config); the vault relies on GIT_AUTHOR_* env from interactive agent sessions. LaunchAgent runs lack that env, so git synthesized a host-detected conscience@<hostname> identity - the "configured automatically based on your username and hostname" warning and EX_CONFIG status. Fix: commits now pass explicit -c user.name/-c user.email, resolved from KNOWLEDGE_GIT_AUTHOR_NAME/EMAIL -> GIT_AUTHOR_* -> git config, and refuse to commit rather than allow a host-detected fallback. The warning is prevented by construction.

rem search now targets collection ai unless overridden (REM_COLLECTION).

Verification

  • go test ./... (workspace, rem, knowledge-sync modules), go vet, gofmt, Python unittest, and shellcheck all clean. New focused tests: identity resolution precedence + host-detected refusal; rem search collection injection; sync.py reindex scope + shared lock.
  • m1 (gamrevinu@m1, macOS 26.4.1): installed memsearch[onnx] (0.4.19), wrote config (provider=onnx, collection=ai), full rebuild from zero -> 4418 chunks. Real searches verified: "preferred relocation locations for job search" -> profile/USER.md Job search (score 1.0000).

Notes for deploy

  • Deploy order for R6: before deploying the new knowledge-sync (via dotagents sync), pin the identity - repo-local git -C ~/Workspace/knowledge config user.name/user.email or KNOWLEDGE_GIT_AUTHOR_* in the LaunchAgent plist - otherwise the tool will refuse to commit rather than mis-author. No launchd/~/.memsearch edits were made on the local Mac as part of this change.
  • Version skew: m1 now runs memsearch 0.4.19 (unpinned latest, per the vault's documented install); the local Mac still runs 0.2.4. Indexes are per-machine and never shared, so this is functionally independent, but pinning both machines to one version is a reasonable follow-up.

Summary by Sourcery

Establish parity and freshness for per-machine memsearch indexes while hardening knowledge-sync identity handling and clarifying memory workflow ownership.

New Features:

  • Add an idempotent installer for configuring and rebuilding per-machine memsearch indexes from the canonical vault.
  • Refresh the memsearch index automatically after knowledge synchronization while coordinating with capture-time refreshes.

Bug Fixes:

  • Prevent knowledge-sync from creating commits with host-detected git identities by requiring and pinning an explicit identity.
  • Keep memsearch refreshes scoped to the canonical vault and collection so configuration drift cannot leave the primary index stale.

Enhancements:

  • Make memsearch a documented first-class backend with disposable per-machine indexes and a shared reindex contract.
  • Document separate ownership for Go and Python dream/consolidation workflows.
  • Default rem search to the canonical ai collection while preserving explicit collection overrides.

Documentation:

  • Document memsearch parity installation, rebuilds, freshness behavior, and canonical markdown ownership.
  • Document knowledge-sync identity configuration and post-sync indexing behavior.

Tests:

  • Add coverage for reindex scope, locking, best-effort behavior, identity resolution, pinned commits, and rem search collection handling.

…ontract, pinned knowledge-sync identity, rem search collection ai
@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR makes memsearch a machine-local derived backend over canonical vault markdown, with parity installation and coordinated refreshes after capture and sync; it also hardens unattended Git identity resolution and documents the separate dream-pass ownership model while defaulting rem search to collection ai.

Sequence diagram for coordinated memsearch refreshes

sequenceDiagram
    participant Capture as basic_memory capture
    participant Sync as knowledge-sync
    participant Lock as reindex.lock
    participant Search as memsearch
    participant Vault as Canonical vault markdown

    alt session digest written
        Capture->>Lock: flock(LOCK_EX | LOCK_NB)
        Lock-->>Capture: lock acquired or skip
        Capture->>Search: index(Vault, --collection ai)
        Search-->>Capture: refresh result
    else sync completes
        Sync->>Lock: syscall.Flock(LOCK_EX | LOCK_NB)
        Lock-->>Sync: lock acquired or skip
        Sync->>Search: index(Vault, --collection ai)
        Search-->>Sync: refresh result or timeout
    end
Loading

Sequence diagram for pinned knowledge-sync commit identity

sequenceDiagram
    participant Sync as knowledge-sync
    participant Env as Environment
    participant GitConfig as Git config
    participant Git as git

    Sync->>Env: resolveIdentity(repo)
    Env-->>Sync: KNOWLEDGE_GIT_AUTHOR_* or GIT_AUTHOR_*
    alt identity incomplete
        Sync->>GitConfig: git config --get user.name/user.email
        GitConfig-->>Sync: configured values or empty
    end
    alt name and email resolved
        Sync->>Git: commit -c user.name -c user.email -m message
        Git-->>Sync: commit result
    else identity unresolved
        Sync-->>Sync: refuse commit
    end
Loading

File-Level Changes

Change Details Files
Adds a shared, best-effort memsearch refresh contract after vault mutations.
  • Reindexes the entire vault into the configured collection after capture and after successful knowledge sync.
  • Uses a non-blocking advisory lock under MEMSEARCH_HOME to prevent overlapping refreshes.
  • Skips cleanly when memsearch is unavailable and bounds sync-triggered refreshes with a configurable timeout.
memory/lib/sync.py
memory/tests/test_sync.py
memory/tools/knowledge-sync/main.go
memory/tools/knowledge-sync/README.md
memory/tools/memsearch/README.md
Introduces an idempotent workflow for installing and rebuilding equivalent memsearch indexes on each machine.
  • Installs memsearch with ONNX embeddings and asserts the ai collection configuration.
  • Indexes only the canonical vault markdown; documents indexes as disposable and machine-local.
  • Supports full rebuilds, verification, environment overrides, and no-verify operation.
memory/tools/memsearch/install-parity.sh
memory/tools/memsearch/README.md
memory/README.md
Pins knowledge-sync Git commit identity to prevent unattended host-derived authorship.
  • Resolves name and email from dedicated KNOWLEDGE_GIT_AUTHOR_* variables, standard Git author variables, then Git config.
  • Passes resolved values via per-commit git -c options and refuses to commit when either value is unavailable.
  • Adds focused tests for precedence, config fallback, and host-detected identity refusal.
memory/tools/knowledge-sync/main.go
memory/tools/knowledge-sync/main_test.go
memory/tools/knowledge-sync/README.md
Clarifies consolidation ownership boundaries and standardizes rem search collection selection.
  • Documents Go rem dream ownership of ai candidates and Python basic_memory dream ownership of session digests.
  • Defaults rem search to collection ai while preserving explicit -c/--collection and REM_COLLECTION overrides.
  • Adds argument-construction tests for default injection and explicit collection preservation.
memory/tools/rem/main.go
memory/tools/rem/search_test.go
memory/tools/rem/README.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: 8387a03a-a677-4dd1-8d82-8fd53caaee4e


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 3 issues

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

## Individual Comments

### Comment 1
<location path="memory/tools/knowledge-sync/main.go" line_range="66" />
<code_context>
 		msg := "sync knowledge " + time.Now().UTC().Format("2006-01-02T15:04:05Z")
-		if out, err := git(repo, "commit", "-m", msg); err != nil {
+		commitArgs := []string{"-c", "user.name=" + name, "-c", "user.email=" + email, "commit", "-m", msg}
+		if out, err := git(repo, commitArgs...); err != nil {
 			// Nothing to commit after add is harmless; anything else is not.
 			if !strings.Contains(out, "nothing to commit") && !strings.Contains(out, "no changes added") {
</code_context>
<issue_to_address>
**issue (bug_risk):** The explicit `-c user.name` and `-c user.email` values do not override Git's `GIT_AUTHOR_NAME`/`GIT_AUTHOR_EMAIL` or committer environment variables, so a dedicated `KNOWLEDGE_GIT_AUTHOR_*` pin can still produce a commit with a different author or committer identity.

**Triggers:** When LaunchAgent or interactive environments still contain `GIT_AUTHOR_*` or `GIT_COMMITTER_*` values alongside `KNOWLEDGE_GIT_AUTHOR_*`.

**Suggested fix:** Clear the relevant Git identity environment variables for the commit or pass the resolved identity explicitly with `--author` and controlled committer environment variables.

```suggestion
		for _, key := range []string{"GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL"} {
			_ = os.Unsetenv(key)
		}
		if out, err := git(repo, commitArgs...); err != nil {
```
</issue_to_address>

### Comment 2
<location path="memory/tools/knowledge-sync/main.go" line_range="185-191" />
<code_context>
+	}
+	defer func() { _ = syscall.Flock(int(rlock.Fd()), syscall.LOCK_UN) }()
+
+	knowledge := getenv("KNOWLEDGE_DIR", repo)
+	collection := getenv("MEMSEARCH_COLLECTION", "ai")
+	ctx, cancel := context.WithTimeout(context.Background(), reindexTimeout())
+	defer cancel()
+
+	// Incremental by default (only files changed by the merge are re-embedded).
+	cmd := exec.CommandContext(ctx, bin, "index", knowledge, "--collection", collection)
+	out, err := cmd.CombinedOutput()
+	if ctx.Err() == context.DeadlineExceeded {
</code_context>
<issue_to_address>
**issue (broader_impact):** The refresh triggers honor `MEMSEARCH_COLLECTION`, so setting it to a non-`ai` value makes them update a different collection while the documented shared contract and default `rem search` continue using `ai`; the canonical collection then becomes stale.

**Triggers:** When `MEMSEARCH_COLLECTION` is set to a custom collection in the sync or capture environment.

**Suggested fix:** Use the fixed `ai` collection for both freshness triggers, or document and apply the override consistently to indexing and search.
</issue_to_address>

### Comment 3
<location path="memory/lib/sync.py" line_range="260-266" />
<code_context>
+        print("memsearch reindex: memsearch not installed, skipped")
+        return
+
+    state_dir = paths["memsearch_home"]
+    try:
+        state_dir.mkdir(parents=True, exist_ok=True)
+    except OSError:
+        pass
+    lock_path = state_dir / "reindex.lock"
+    lock = open(lock_path, "w")
+    try:
+        fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
</code_context>
<issue_to_address>
**issue (bug_risk):** If creating the memsearch state directory fails, the exception is swallowed and the function then calls `open(lock_path, "w")`, which raises `OSError` and aborts the caller instead of remaining best-effort.

**Triggers:** When `MEMSEARCH_HOME` is missing, inaccessible, read-only, or otherwise cannot be created.

**Suggested fix:** Return after the failed `mkdir`, and handle lock-file open failures as a skipped refresh.

```suggestion
    state_dir = paths["memsearch_home"]
    try:
        state_dir.mkdir(parents=True, exist_ok=True)
    except OSError:
        print("memsearch reindex: unable to create state directory, skipped")
        return
    lock_path = state_dir / "reindex.lock"
    try:
        lock = open(lock_path, "w")
    except OSError:
        print("memsearch reindex: unable to open lock file, skipped")
        return
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and if the pinned identity or reset behavior is wrong, unattended sync can create commits attributed to the wrong person, and those pushed history records are not repaired by reverting this change. The memsearch index itself is disposable and rebuildable, but correcting already-published commit attribution would require history repair.

Blocking findings: memory/tools/knowledge-sync/main.go:66, memory/tools/knowledge-sync/main.go:191, memory/lib/sync.py:266


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

Comment thread memory/tools/knowledge-sync/main.go Outdated
Comment thread memory/tools/knowledge-sync/main.go Outdated
Comment thread memory/lib/sync.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a07355373

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread memory/tools/knowledge-sync/main.go Outdated
Comment on lines +65 to +66
commitArgs := []string{"-c", "user.name=" + name, "-c", "user.email=" + email, "commit", "-m", msg}
if out, err := git(repo, commitArgs...); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply the resolved identity through Git's environment

When KNOWLEDGE_GIT_AUTHOR_* and GIT_AUTHOR_* are both present, resolveIdentity selects the dedicated values, but the inherited GIT_AUTHOR_* variables take precedence over these user.* configuration overrides during git commit. For example, Git records the inherited author while using the resolved value only for the committer, defeating the documented preference order and pinned-author guarantee. Set explicit author and committer environment variables on the commit command, or remove conflicting inherited variables.

Useful? React with 👍 / 👎.

Comment thread memory/tools/knowledge-sync/main.go Outdated
Comment thread memory/lib/sync.py Outdated
Comment on lines +275 to +276
cmd = ["memsearch", "index", str(paths["vault_dir"]), "--collection", paths["collection"]]
result = subprocess.run(cmd, capture_output=True, text=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor configured vault subdirectories during reindex

When SESSIONS_DIR, NOTES_DIR, or PROFILE_DIR points outside KNOWLEDGE_DIR, the sync still reads and writes those configured locations, but the new command indexes only vault_dir. This regresses the previous implementation, which explicitly indexed each resolved directory, so a successful sync can leave the changed external files absent from search results. Either retain the resolved paths or require and validate that they reside under the vault root.

Useful? React with 👍 / 👎.

Comment thread memory/lib/sync.py Outdated
Comment on lines +260 to +266
state_dir = paths["memsearch_home"]
try:
state_dir.mkdir(parents=True, exist_ok=True)
except OSError:
pass
lock_path = state_dir / "reindex.lock"
lock = open(lock_path, "w")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat lock-file creation as a best-effort failure

When MEMSEARCH_HOME is unwritable, is an existing regular file, or otherwise cannot host reindex.lock, mkdir errors are ignored and this open raises an uncaught OSError. Because main calls this after modifying the canonical memory files, a supposedly best-effort reindex makes the overall sync exit with failure after partial success. Catch lock creation errors and report/skip them just like a missing binary or held lock.

Useful? React with 👍 / 👎.

…i, keep reindex best-effort on state-dir failure
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…e/sessions scope so sync and capture triggers mutually exclude
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@yourconscience
yourconscience merged commit 1abc3bd 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