Skip to content

memory: ship rem CLI; sync installs memory tools - #142

Merged
yourconscience merged 1 commit into
mainfrom
memory/rem-tool
Aug 22, 2026
Merged

memory: ship rem CLI; sync installs memory tools#142
yourconscience merged 1 commit into
mainfrom
memory/rem-tool

Conversation

@yourconscience

@yourconscience yourconscience commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • add memory/tools/rem — capture (rem add), consolidation (rem dream [--apply]), search and vault sync wrapper; the default memory workflow CLI described in the knowledge-vault plan
  • update bundled knowledge-sync with a branch guard: refuses to run when the vault worktree is not on the target branch (prevents wrong-branch commits + duplicate resurrection seen in production)
  • dotagents sync now builds every Go tool under memory/tools/ and installs it to $GOBIN / ~/.local/bin, rebuilding only on source changes and skipping gracefully without a Go toolchain

Test plan

  • new TestInstallMemoryTools* unit tests (build, freshness skip, rebuild-on-touch, missing-dir noop)
  • full go test ./cmd/dotagents/ green

Summary by Sourcery

Ship the rem memory workflow and provision bundled memory tools through dotagents sync while guarding vault synchronization against wrong-branch operations.

New Features:

  • Add the rem CLI for capturing, searching, reviewing, consolidating, and syncing knowledge-vault memory.
  • Automatically build and install all Go-based memory tools during dotagents sync, with incremental rebuilds and graceful handling on hosts without Go.

Bug Fixes:

  • Prevent knowledge-sync from operating when the vault worktree is checked out on the wrong branch, avoiding unintended commits and duplicate resurrection.

Enhancements:

  • Make rem dream --apply safely consolidate duplicate knowledge sections with worktree and branch safeguards, backups, and a commit.

Documentation:

  • Document the bundled memory tools and their automatic installation behavior.

Tests:

  • Add coverage for memory-tool installation freshness, rebuilds, missing directories, candidate deduplication, clustering, and guarded consolidation.

@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.

@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new rem CLI as a memory workflow front-end, introduces an automated build-and-install step for all Go-based memory tools during dotagents sync, and hardens knowledge-sync with a branch guard and simplified conflict handling, with tests around tool installation and rem’s consolidation logic.

Sequence diagram for guarded vault synchronization

sequenceDiagram
    participant Rem
    participant Sync as knowledge-sync
    participant Vault as VaultWorktree
    participant Remote as GitRemote
    Rem->>Sync: cmdSync()
    Sync->>Vault: git status --short
    Sync->>Vault: git rev-parse --abbrev-ref HEAD
    alt wrong branch
        Sync-->>Rem: refuse sync
    else expected branch
        Sync->>Remote: git fetch remote branch
        Sync->>Vault: git merge --no-edit remote/branch
        alt merge conflict
            Sync->>Vault: git branch conflict-branch
            Sync->>Vault: git merge --abort
            Sync-->>Rem: report conflict
        else merge succeeds
            Sync->>Remote: git push remote branch
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce automatic build-and-install of Go memory tools during dotagents sync, with freshness checks and no-op behavior when prerequisites are missing.
  • Implement installMemoryTools to discover Go modules under memory/tools, determine destination bin directory from GOBIN or ~/.local/bin, and build tools only when sources are newer than installed binaries.
  • Implement buildMemoryTool to compute newest source modification time, skip builds when binaries are fresh, and perform atomic go build to a temp file followed by rename.
  • Wire installMemoryTools into runSync, printing a summary of installed tools, and add unit tests for initial build, freshness skip, rebuild-on-touch, and missing tools directory behavior.
  • Update README to explain memory/tools helper binaries and the sync-time build-and-install behavior.
cmd/dotagents/memory_tools.go
cmd/dotagents/memory_tools_test.go
cmd/dotagents/sync.go
README.md
Strengthen knowledge-sync safety with a branch guard and simpler conflict-branch handling.
  • Ensure the lock file is closed and unlocked via straightforward defers, reducing error handling noise.
  • Add a branch guard that checks the current HEAD branch against the configured target branch and aborts sync when they differ.
  • Simplify merge conflict handling to create a conflict branch and abort the merge without failing the whole process, then report via fatal.
  • Keep existing single-instance protection via flock while adjusting error reporting paths.
memory/tools/knowledge-sync/main.go
Ship the new rem CLI to orchestrate memory workflows including capture, search, consolidation (dream), and guarded vault sync.
  • Add a top-level rem dispatcher that routes subcommands (add, search, dream, sync) and provides usage/help output.
  • Implement candidate parsing and normalization for ai/*.md files, including deduped capture across days via cmdAdd and loadCandidates.
  • Add cmdSearch to delegate queries to memsearch and cmdSync to wrap the guarded knowledge-sync binary with configurable location via REM_SYNC_BIN.
  • Implement dreamReport to cluster captured candidates by lexical similarity, detect repeats and polarity conflicts, generate a consolidation report into reviews/rem-dream-.md, and summarize duplicate sync sections in sessions/knowledge.md.
  • Implement dreamApply to collapse exact duplicate sync sections in sessions/knowledge.md with backup creation, git add/commit, and guards for a clean tree and main branch checkout, while keeping backups excluded from git status.
  • Provide rem README describing commands, environment variables, and tests, plus a dedicated go.mod for the rem module.
  • Add tests for normalization, add-time deduping across days, duplicate sync section collapsing, clustering of repeats/conflicts, and guarded dreamApply behavior including backup and commit checks.
memory/tools/rem/README.md
memory/tools/rem/dispatch.go
memory/tools/rem/main.go
memory/tools/rem/dream.go
memory/tools/rem/dream_test.go
memory/tools/rem/go.mod

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

@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 1 security issue, and 6 other issues

Security issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="memory/tools/rem/go.mod" line_range="3" />
<code_context>
+module rem
+
+go 1.26.1
</code_context>
<issue_to_address>
**issue (bug_risk):** `dotagents sync` fails to build `rem` on machines running the repository's supported Go 1.24 toolchain because this module requires Go 1.26.1. The new installer propagates that build error, so sync no longer completes for ordinary hosts with the available toolchain.

**Triggers:** When Go 1.24.x is installed and automatic toolchain download is unavailable or disabled.

**Suggested fix:** Use a Go version compatible with the repository's declared toolchain, or lower the module directive to the minimum version required by the code.

```suggestion
go 1.24.2
```
</issue_to_address>

### Comment 2
<location path="memory/tools/knowledge-sync/main.go" line_range="66-69" />
<code_context>
 		}
 	}
+	if out, err := git(repo, "rev-parse", "--abbrev-ref", "HEAD"); err != nil {
+		fatal("current branch", err, out)
+	} else if current := strings.TrimSpace(out); current != branch {
+		fatal("branch guard", fmt.Errorf("checkout is on %q, not %q", current, branch),
+			"knowledge-sync: refusing to sync: worktree checked out on "+current+", expected "+branch+"\n")
+	}

</code_context>
<issue_to_address>
**issue (broader_impact):** The branch guard runs after the existing dirty-tree handling has staged and committed all changes. A dirty worktree on the wrong branch is therefore committed to that wrong branch before the command refuses to fetch, merge, or push, directly violating the guard's purpose.

**Triggers:** When the vault worktree is dirty and checked out on a branch other than `KNOWLEDGE_BRANCH`.

**Suggested fix:** Validate the current branch before the status/add/commit block.
</issue_to_address>

### Comment 3
<location path="memory/tools/knowledge-sync/main.go" line_range="78-79" />
<code_context>
-		if abortOut, abortErr := git(repo, "merge", "--abort"); abortErr != nil {
-			fatal("git merge abort", abortErr, abortOut)
-		}
+		git(repo, "branch", conflict)
+		git(repo, "merge", "--abort")
 		fatal("git merge", err, "created conflict branch "+conflict+"\n")
 	}
</code_context>
<issue_to_address>
**issue (bug_risk):** Errors from creating the conflict branch and aborting the merge are discarded, so a failed `git branch` or `git merge --abort` still reports only the original merge failure and can leave the repository in a conflicted merge state without the promised recovery branch.

**Triggers:** When conflict recovery itself fails, such as when branch creation is rejected or the repository cannot abort the merge.

**Suggested fix:** Restore explicit error checks for both commands and call `fatal` with the recovery step that failed.

```suggestion
		if out, err := git(repo, "branch", conflict); err != nil {
			fatal("git branch", err, out)
		}
		if out, err := git(repo, "merge", "--abort"); err != nil {
			fatal("git merge --abort", err, out)
		}
```
</issue_to_address>

### Comment 4
<location path="cmd/dotagents/memory_tools.go" line_range="72-87" />
<code_context>
+		return false, err
+	}
+	for _, entry := range entries {
+		if entry.IsDir() || (!strings.HasSuffix(entry.Name(), ".go") && entry.Name() != "go.mod") {
+			continue
+		}
+		info, err := entry.Info()
</code_context>
<issue_to_address>
**issue (bug_risk):** The freshness scan ignores `.go` files in subdirectories, so changing an imported local package does not advance `newest`; an existing binary is then treated as fresh and the installer skips rebuilding it.

**Triggers:** When a memory tool has Go source in a nested package directory and that source changes.

**Suggested fix:** Walk the module recursively, or use Go's package/build metadata to determine whether any source dependency is newer than the installed binary.

```suggestion
	err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() || (!strings.HasSuffix(info.Name(), ".go") && info.Name() != "go.mod") {
			return nil
		}
		if info.ModTime().After(newest) {
			newest = info.ModTime()
		}
		return nil
	})
	if err != nil {
		return false, err
	}
```
</issue_to_address>

### Comment 5
<location path="memory/tools/rem/main.go" line_range="62-83" />
<code_context>
+		return fmt.Errorf("usage: rem add [-src harness] \"fact\"")
+	}
+
+	dir := filepath.Join(knowledgeDir(), "ai")
+	existing, err := loadCandidates(dir)
+	if err != nil {
+		return err
+	}
+	fp := normalize(fact)
+	for _, c := range existing {
+		if normalize(c.Text) == fp {
+			fmt.Printf("rem add: already captured on %s: %s\n", c.Day, c.Text)
+			return nil
+		}
+	}
+
+	if err := os.MkdirAll(dir, 0o755); err != nil {
+		return err
+	}
+	path := filepath.Join(dir, today()+".md")
+	fresh := false
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		fresh = true
+	}
+	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
+	if err != nil {
+		return err
</code_context>
<issue_to_address>
**issue (bug_risk):** `cmdAdd` loads existing candidates and later opens today's file for append without any lock or atomic check, so concurrent `rem add` processes can both observe a missing fact and append it, defeating the documented cross-file deduplication.

**Triggers:** When two capture hooks invoke `rem add` concurrently for the same fact.

**Suggested fix:** Serialize candidate read-and-append with a lock in the vault or use an atomic update mechanism.
</issue_to_address>

### Comment 6
<location path="memory/tools/rem/main.go" line_range="167-166" />
<code_context>
+		}
+		bin = filepath.Join(home, ".local", "bin", "knowledge-sync")
+	}
+	if _, err := os.Stat(bin); err != nil {
+		return fmt.Errorf("knowledge-sync not found at %s: %w", bin, err)
+	}
+	cmd := exec.Command(bin)
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
</code_context>
<issue_to_address>
**issue (bug_risk):** `rem sync` ignores `GOBIN` and falls back only to `~/.local/bin/knowledge-sync`, even though `dotagents sync` installs knowledge-sync into the configured `GOBIN`; with a custom GOBIN, the wrapper cannot find the binary it just provisioned.

**Triggers:** When `GOBIN` is set to a directory other than `~/.local/bin` and `REM_SYNC_BIN` is unset.

**Suggested fix:** Resolve the default sync binary from `GOBIN` before falling back to `~/.local/bin`, or have the installer/configuration provide the path consistently.
</issue_to_address>

### Comment 7
<location path="memory/tools/rem/main.go" line_range="170" />
<code_context>
	cmd := exec.Command(bin)
</code_context>
<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 7 findings to address first, and the new rem dream --apply can rewrite and commit sessions/knowledge.md, dropping sections based on normalized duplicate bodies; a misclassification would alter persisted knowledge beyond the immediate command. The change creates a timestamped backup and a local commit, so the affected file is bounded and recoverable by restoring the backup or reverting the commit, while the rest of the CLI has ordinary runtime-bug consequences.

Blocking findings: memory/tools/rem/go.mod:3, memory/tools/knowledge-sync/main.go:69, memory/tools/knowledge-sync/main.go:79, cmd/dotagents/memory_tools.go:87, memory/tools/rem/main.go:83, and 2 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread memory/tools/rem/go.mod
@@ -0,0 +1,3 @@
module rem

go 1.26.1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): dotagents sync fails to build rem on machines running the repository's supported Go 1.24 toolchain because this module requires Go 1.26.1. The new installer propagates that build error, so sync no longer completes for ordinary hosts with the available toolchain.

Triggers: When Go 1.24.x is installed and automatic toolchain download is unavailable or disabled.

Suggested fix: Use a Go version compatible with the repository's declared toolchain, or lower the module directive to the minimum version required by the code.

Suggested change
go 1.26.1
go 1.24.2

Comment on lines +66 to +69
fatal("current branch", err, out)
} else if current := strings.TrimSpace(out); current != branch {
fatal("branch guard", fmt.Errorf("checkout is on %q, not %q", current, branch),
"knowledge-sync: refusing to sync: worktree checked out on "+current+", expected "+branch+"\n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (broader_impact): The branch guard runs after the existing dirty-tree handling has staged and committed all changes. A dirty worktree on the wrong branch is therefore committed to that wrong branch before the command refuses to fetch, merge, or push, directly violating the guard's purpose.

Triggers: When the vault worktree is dirty and checked out on a branch other than KNOWLEDGE_BRANCH.

Suggested fix: Validate the current branch before the status/add/commit block.

Comment thread memory/tools/knowledge-sync/main.go Outdated
Comment on lines +78 to +79
git(repo, "branch", conflict)
git(repo, "merge", "--abort")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Errors from creating the conflict branch and aborting the merge are discarded, so a failed git branch or git merge --abort still reports only the original merge failure and can leave the repository in a conflicted merge state without the promised recovery branch.

Triggers: When conflict recovery itself fails, such as when branch creation is rejected or the repository cannot abort the merge.

Suggested fix: Restore explicit error checks for both commands and call fatal with the recovery step that failed.

Suggested change
git(repo, "branch", conflict)
git(repo, "merge", "--abort")
if out, err := git(repo, "branch", conflict); err != nil {
fatal("git branch", err, out)
}
if out, err := git(repo, "merge", "--abort"); err != nil {
fatal("git merge --abort", err, out)
}

Comment on lines +72 to +87
entries, err := os.ReadDir(srcDir)
if err != nil {
return false, err
}
for _, entry := range entries {
if entry.IsDir() || (!strings.HasSuffix(entry.Name(), ".go") && entry.Name() != "go.mod") {
continue
}
info, err := entry.Info()
if err != nil {
return false, err
}
if info.ModTime().After(newest) {
newest = info.ModTime()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The freshness scan ignores .go files in subdirectories, so changing an imported local package does not advance newest; an existing binary is then treated as fresh and the installer skips rebuilding it.

Triggers: When a memory tool has Go source in a nested package directory and that source changes.

Suggested fix: Walk the module recursively, or use Go's package/build metadata to determine whether any source dependency is newer than the installed binary.

Suggested change
entries, err := os.ReadDir(srcDir)
if err != nil {
return false, err
}
for _, entry := range entries {
if entry.IsDir() || (!strings.HasSuffix(entry.Name(), ".go") && entry.Name() != "go.mod") {
continue
}
info, err := entry.Info()
if err != nil {
return false, err
}
if info.ModTime().After(newest) {
newest = info.ModTime()
}
}
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || (!strings.HasSuffix(info.Name(), ".go") && info.Name() != "go.mod") {
return nil
}
if info.ModTime().After(newest) {
newest = info.ModTime()
}
return nil
})
if err != nil {
return false, err
}

Comment thread memory/tools/rem/main.go
Comment on lines +62 to +83
dir := filepath.Join(knowledgeDir(), "ai")
existing, err := loadCandidates(dir)
if err != nil {
return err
}
fp := normalize(fact)
for _, c := range existing {
if normalize(c.Text) == fp {
fmt.Printf("rem add: already captured on %s: %s\n", c.Day, c.Text)
return nil
}
}

if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
path := filepath.Join(dir, today()+".md")
fresh := false
if _, err := os.Stat(path); os.IsNotExist(err) {
fresh = true
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): cmdAdd loads existing candidates and later opens today's file for append without any lock or atomic check, so concurrent rem add processes can both observe a missing fact and append it, defeating the documented cross-file deduplication.

Triggers: When two capture hooks invoke rem add concurrently for the same fact.

Suggested fix: Serialize candidate read-and-append with a lock in the vault or use an atomic update mechanism.

Comment thread memory/tools/rem/main.go
return err
}
bin = filepath.Join(home, ".local", "bin", "knowledge-sync")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): rem sync ignores GOBIN and falls back only to ~/.local/bin/knowledge-sync, even though dotagents sync installs knowledge-sync into the configured GOBIN; with a custom GOBIN, the wrapper cannot find the binary it just provisioned.

Triggers: When GOBIN is set to a directory other than ~/.local/bin and REM_SYNC_BIN is unset.

Suggested fix: Resolve the default sync binary from GOBIN before falling back to ~/.local/bin, or have the installer/configuration provide the path consistently.

Comment thread memory/tools/rem/main.go
if _, err := os.Stat(bin); err != nil {
return fmt.Errorf("knowledge-sync not found at %s: %w", bin, err)
}
cmd := exec.Command(bin)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (go.lang.security.audit.dangerous-exec-command): Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

Source: opengrep

@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.

New security issues found

Comment thread memory/tools/rem/main.go
if _, err := os.Stat(bin); err != nil {
return fmt.Errorf("knowledge-sync not found at %s: %w", bin, err)
}
cmd := exec.Command(bin)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (go.lang.security.audit.dangerous-exec-command): Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

Source: opengrep

@yourconscience
yourconscience merged commit 85b7835 into main Aug 22, 2026
4 of 5 checks passed
@yourconscience
yourconscience deleted the memory/rem-tool branch August 22, 2026 20:45
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