Skip to content

feat(memory): add local memory provider#41

Merged
Abdulmuiz44 merged 2 commits into
mainfrom
feat/local-memory-provider
Jun 11, 2026
Merged

feat(memory): add local memory provider#41
Abdulmuiz44 merged 2 commits into
mainfrom
feat/local-memory-provider

Conversation

@Abdulmuiz44

Copy link
Copy Markdown
Collaborator

Summary

Supermemory-inspired but Codra-native local-first memory foundation.

  • Adds MemoryProvider trait and LocalMarkdownMemoryProvider
  • Reads workspace .codra/ markdown + global ~/.codra/ (with /root/USER.md / /root/MEMORY.md fallback)
  • Budgeted context() bundle — never injects all memory blindly
  • Simple local recall (term matching, no embeddings)
  • Secret-line filtering on read
  • CLI: codra memory status, codra memory context

Intentionally out of scope

  • No Supermemory API integration
  • No hosted memory
  • No MCP memory tools
  • No agent-loop wiring yet

Validation

cargo check -p codra-memory   ✅
cargo test -p codra-memory    ✅ (8 tests)
cargo check -p codra-cli      ✅
cargo test -p codra-cli --test memory_command_tests ✅ (3 tests)
pnpm check:task-loop-protocol-sync ✅

Full workspace cargo test not run (disk constraints during build); targeted crate checks passed.

Architecture rules documented

  • JSON task state leads; markdown memory follows
  • Scoped by user/project/task
  • Local-first, no API keys required
  • No secrets in memory files

@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: 8fdfec4e8b

ℹ️ 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 crates/codra-memory/src/budget.rs
Comment on lines +168 to +169
let profile = if query.include_profile {
self.profile(&query.scope)?

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 Budget profile facts before returning context

With the default include_profile: true, self.profile() reads global/project memory and returns those facts in the context bundle, but total_chars and the section budget only account for sections. A single very long non-heading line in USER.md or .codra/MEMORY.md is therefore returned and printed by codra memory context --budget ... outside the requested budget, even when all sections are capped.

Useful? React with 👍 / 👎.

Comment thread crates/codra-memory/src/local.rs
Comment on lines +53 to +57
pub fn task_dir(project_path: &Path, task_id: &str) -> PathBuf {
workspace_codra_dir(project_path)
.join("tasks")
.join(task_id)
}

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 Reject path separators in task IDs

When --task or another caller supplies a task id containing path components, join(task_id) is honored and the provider can read progress.md / plan.md / decisions.md outside .codra/tasks. That breaks task scoping for memory retrieval; task IDs need to be normalized or rejected before building filesystem paths.

Useful? React with 👍 / 👎.

Comment thread crates/codra-memory/src/local.rs
Comment on lines +151 to +158
fn forget(&self, id: &str) -> MemoryResult<()> {
let forgotten_dir = crate::paths::global_codra_dir().join("memory/.forgotten");
fs::create_dir_all(&forgotten_dir)?;
let tombstone = forgotten_dir.join(format!("{}.txt", sanitize_id(id)));
fs::write(
&tombstone,
format!("forgotten_at={}\n", Utc::now().to_rfc3339()),
)?;

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 Make forget affect future retrievals

Calling forget() only writes a tombstone file, but profile(), recall(), and context() never consult that directory, so the same memory can still be returned immediately afterward. For any consumer that exposes the MemoryProvider delete/forget action, this gives a false success while leaving the forgotten content active.

Useful? React with 👍 / 👎.

Comment thread crates/codra-memory/src/secrets.rs
Comment on lines +72 to +73
fn add(&self, input: MemoryAddInput) -> MemoryResult<MemoryRecord> {
let notes_path = workspace_notes_file(&input.scope.project_path);

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 Preserve static memories as durable profile facts

When a caller adds memory with is_static: true, this path still appends it to notes.md, so profile() only treats it as one of the last few dynamic notes instead of a durable project/user fact. Consumers using the new MemoryAddInput API to save long-term preferences or architecture facts will see those memories age out of the profile even though they explicitly marked them static.

Useful? React with 👍 / 👎.

Comment thread crates/codra-memory/tests/local_provider_tests.rs
Comment thread crates/codra-cli/tests/memory_command_tests.rs
@Abdulmuiz44 Abdulmuiz44 merged commit f038598 into main Jun 11, 2026
1 check 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