Skip to content

Add project isolation for agentmemory#829

Open
Durocks wants to merge 5 commits into
rohitg00:mainfrom
Durocks:feat/mcp-project-isolation
Open

Add project isolation for agentmemory#829
Durocks wants to merge 5 commits into
rohitg00:mainfrom
Durocks:feat/mcp-project-isolation

Conversation

@Durocks
Copy link
Copy Markdown

@Durocks Durocks commented Jun 4, 2026

What changed

  • Added AGENTMEMORY_PROJECT_ISOLATION to require project scoping for memory reads and writes when enabled.
  • Updated MCP, REST, and core memory paths to preserve and enforce project scope.
  • Fixed hybrid smart-search so scoped queries no longer leak cross-project results.
  • Added regression tests for scoped save/recall/search behavior.

Why

  • The MCP client was dropping or not enforcing project scope in some paths.
  • We want optional strict project isolation without breaking the default global-memory workflow.

Behavior

  • When AGENTMEMORY_PROJECT_ISOLATION=true, memory operations require a project scope.
  • When the flag is off, existing global/unscoped behavior stays the same.

Verification

  • npm run build
  • npx vitest run test/project-isolation.test.ts test/smart-search.test.ts test/mcp-standalone-proxy.test.ts
  • Manual Zoo Code smoke tests for memory_save, memory_recall, and memory_smart_search
  • Targeted automated coverage across scoped memory, smart-search, and MCP proxy behavior

Summary by CodeRabbit

  • New Features

    • Project scoping for memory: AGENTMEMORY_PROJECT_NAME (default project) and AGENTMEMORY_PROJECT_ISOLATION (enforced isolation). Memory tools, CLI/MCP adapters, and API endpoints accept/forward optional project and include project info in search/smart-search results.
  • Bug Fixes / Validation

    • API and MCP boundaries now validate project and input, returning 400 on malformed/missing project when isolation is enabled.
  • Documentation

    • README updated with project-isolation defaults and configuration guidance.
  • Tests

    • Expanded coverage for project-isolation, proxy/fallback, and scoped search/recall scenarios.

- Added project isolation support across various MCP endpoints and functions.
- Introduced `resolveProjectScope` and `requireProjectScope` functions to handle project resolution and validation.
- Updated existing endpoints in `server.ts`, `standalone.ts`, and `api.ts` to require a project argument when isolation is enabled.
- Enhanced tools registry to include project as an optional parameter for memory operations.
- Implemented filtering of memories and sessions based on the project in relevant functions.
- Added tests to validate project isolation behavior, including handling of missing project arguments and scoped memory access.
- Updated types to include optional project fields in memory interfaces.
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 4, 2026

@Durocks is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds project-scoped resolution and isolation: env/docs and config helper; enforces/resolves project at HTTP and MCP boundaries; threads scope-aware matching through memory functions, smart-search, consolidation; stamps project on persisted memories; updates CLI/MCP templates, plugin hooks, and tests.

Changes

Project Isolation Implementation

Layer / File(s) Summary
All changes (project isolation + wiring + tests)
src/functions/*, src/mcp/*, src/triggers/*, src/cli/*, src/types.ts, src/config.ts, plugin/scripts/*.mjs, plugin/.mcp*.json, test/*, .env.example, README.md
Introduces project-scope helpers (resolveProjectScope, requireProjectScope, projectMatchesScope), isProjectIsolationEnabled(), adds project?: string to memory/result types, wires AGENTMEMORY_PROJECT_* env vars into CLI/MCP templates and plugin hooks, enforces/validates project at API/MCP boundaries, threads scope-aware filtering/stamping through core memory flows (remember, search, enrich, lessons, patterns, reflect, consolidate, consolidation-pipeline, smart-search, flow-compress), updates local MCP standalone proxy/local fallback to persist/filter by project, normalizes small plugin footer exports, and adds/updates Vitest suites for project-isolation behavior and API boundary validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • rohitg00

Poem

🐰 I dug a burrow through the code,
Found project names in every node,
Memories now wear tidy tags,
No stray hops between the flags,
Hooray — scoped hops and tidy load!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding project isolation for agentmemory as a new feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@Durocks Durocks changed the title feat: implement project isolation feature Add project isolation for agentmemory Jun 4, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
test/mcp-standalone-proxy.test.ts (1)

21-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clean up AGENTMEMORY_PROJECT_NAME in beforeEach to prevent test pollution.

The beforeEach hook deletes AGENTMEMORY_SECRET but not AGENTMEMORY_PROJECT_NAME. If a test (e.g., line 121) sets AGENTMEMORY_PROJECT_NAME and fails before afterEach cleanup runs, subsequent tests may inherit that value.

🧹 Add cleanup for consistency
 beforeEach(() => {
   resetHandleForTests();
   process.env["AGENTMEMORY_URL"] = BASE;
   delete process.env["AGENTMEMORY_SECRET"];
+  delete process.env["AGENTMEMORY_PROJECT_NAME"];
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/mcp-standalone-proxy.test.ts` around lines 21 - 25, The beforeEach hook
in test/mcp-standalone-proxy.test.ts currently calls resetHandleForTests() and
cleans AGENTMEMORY_SECRET but does not clear AGENTMEMORY_PROJECT_NAME, which can
cause cross-test pollution; update the beforeEach to also remove
process.env["AGENTMEMORY_PROJECT_NAME"] (similar to the existing delete
process.env["AGENTMEMORY_SECRET"]) so tests that set AGENTMEMORY_PROJECT_NAME
(e.g., around line 121) cannot leak its value into later tests.
src/functions/enrich.ts (1)

28-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass project through to mem::file-context.

src/functions/file-index.ts now calls requireProjectScope(data?.project, "mem::file-context"), but this trigger payload still omits project. When isolation is enabled, the call throws and the local catch turns that into an empty file context, so scoped enrich requests quietly lose one of their main context sources.

Suggested fix
         .trigger<{ sessionId: string; files: string[] }, { context: string }>({
           function_id: "mem::file-context",
           payload: {
             sessionId: data.sessionId,
             files: data.files,
+            ...(project !== undefined && { project }),
           },
         })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/enrich.ts` around lines 28 - 40, The mem::file-context trigger
is missing the project field in its payload, so when isolation is enabled the
call fails; update the sdk.trigger call that creates fileContextPromise to
include the project (use the project returned by requireProjectScope) in the
payload object passed to function_id "mem::file-context" so the invoked function
receives the same project scope; specifically modify the fileContextPromise
payload in enrich.ts to add project: project (or data.project if that pattern is
used) and keep the existing catch behavior.
src/functions/flow-compress.ts (1)

30-57: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Validate runId and actionIds against the resolved project.

Only the kv.list(KV.actions) branch is project-filtered. If a caller passes a foreign runId or actionIds, this will still compress those actions and then persist the result under the caller's project, which defeats the isolation this PR is introducing.

Suggested fix
       if (data.runId) {
         const run = await kv.get<RoutineRun>(KV.routineRuns, data.runId);
         if (!run) {
           return { success: false, error: "run not found" };
         }
         for (const id of run.actionIds) {
           const action = await kv.get<Action>(KV.actions, id);
-          if (action) actionsToCompress.push(action);
+          if (action && (!project || action.project === project)) {
+            actionsToCompress.push(action);
+          }
         }
       } else if (data.actionIds && data.actionIds.length > 0) {
         for (const id of data.actionIds) {
           const action = await kv.get<Action>(KV.actions, id);
-          if (action) actionsToCompress.push(action);
+          if (action && (!project || action.project === project)) {
+            actionsToCompress.push(action);
+          }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/flow-compress.ts` around lines 30 - 57, The code currently
accepts a resolved project via requireProjectScope but still loads actions from
a foreign runId or actionIds; ensure we only compress actions belonging to that
resolved project: when handling a runId (RoutineRun), verify the run's project
equals the resolved project and return an error if not, or alternatively filter
its actionIds by fetching each Action (KV.actions) and only include actions
whose action.project === project; when handling explicit actionIds, fetch each
Action and only push those where action.project === project (or return an error
for any mismatched id based on desired strictness). Update the run branch (where
run.actionIds are iterated) and the actionIds branch (where data.actionIds are
iterated) to perform this project check before adding to actionsToCompress,
referencing requireProjectScope, KV.routineRuns, KV.actions, RoutineRun, and
Action.
src/functions/reflect.ts (1)

281-287: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Scope the insight fingerprint to the project.

fp is still derived from content alone, so two projects producing the same insight text hit the same KV key. A scoped mem::reflect run will then reinforce or overwrite the other project's insight instead of creating its own record.

Suggested fix
-            const fp = fingerprintId("ins", content.trim().toLowerCase());
+            const fp = fingerprintId(
+              "ins",
+              `${project ?? "__global__"}:${content.trim().toLowerCase()}`,
+            );
             const existing = await kv.get<Insight>(KV.insights, fp);

Also applies to: 300-306

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/reflect.ts` around lines 281 - 287, The fingerprinting is
currently based on content only, causing cross-project collisions; update the
fingerprint key generation to include the project scope (e.g., incorporate
projectId or project slug) when calling fingerprintId so each project gets its
own insight record. Concretely, change the fp computation used with
kv.get/kv.set for KV.insights (the lines around fingerprintId("ins",
content.trim().toLowerCase())) to include the project identifier (for example
fingerprintId("ins", `${projectId}:${content.trim().toLowerCase()}`) or pass
both projectId and content if fingerprintId supports multiple args), and make
the same change for the other occurrence referenced (near reinforceInsight and
the block at 300-306) so lookups and writes are consistently namespaced per
project.
🧹 Nitpick comments (1)
test/mcp-standalone-proxy.test.ts (1)

79-119: 💤 Low value

Clarify why the mocked /agentmemory/search response contains project even though the request omits it

In the real mem::search implementation, the response can include project when AGENTMEMORY_PROJECT_NAME is set, even if the client doesn’t send project (because projectFilter is derived via requireProjectScope(...)resolveProjectScope(...) → env fallback). Since this test stubs the fetch, that project field is only meaningful if you’re intentionally simulating that env-driven server behavior.

Either document that intent in the mock (and/or set process.env["AGENTMEMORY_PROJECT_NAME"] in this test for consistency), or remove project from the mocked /agentmemory/search response and drop the corresponding assertion so the test stays focused on forwarding format/token_budget.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/mcp-standalone-proxy.test.ts` around lines 79 - 119, The mocked
/agentmemory/search response in the test "proxies memory_recall to POST
/agentmemory/search and forwards format/token_budget (`#507`)" includes a project
field that is only produced server-side when AGENTMEMORY_PROJECT_NAME is set,
which makes the test inconsistent; either set
process.env["AGENTMEMORY_PROJECT_NAME"] at the top of this test to mirror server
behavior (so the mock + assertion on body.project stays valid) or remove the
project property from the stubbed response and delete the corresponding
assertion (the test should only assert forwarding of format/token_budget and
other provided fields via handleToolCall and the calls array). Ensure changes
are applied in this test block (function/test name: it(... "proxies
memory_recall ...")) and update or remove the expect(body.project) and related
mock project value accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Line 110: Add documentation for the AGENTMEMORY_PROJECT_NAME env var to
.env.example: mention that it provides a default project name used by the
project-scoping logic (fallback used in src/functions/project-scope.ts when no
explicit project is provided) and note it only applies when
AGENTMEMORY_PROJECT_ISOLATION is enabled; include an example value and a short
description of its purpose. Ensure the variable name is spelled exactly as
AGENTMEMORY_PROJECT_NAME and place the new line near the
AGENTMEMORY_PROJECT_ISOLATION entry so users discover the related setting.

In `@src/functions/consolidate.ts`:
- Around line 166-170: The current existingMatch selection uses
projectMatchesScope which returns true for undefined m.project when
scopedProject is falsy, allowing a scoped consolidation to pick an unscoped
memory; change the predicate to require strict project equality when
scopedProject is set: in the existingMemories.find call (look around the
existingMatch variable and the projectMatchesScope usage), keep the title
comparison but replace the project check with a conditional that, if
scopedProject is truthy, only matches when m.project === scopedProject,
otherwise allow any project (or undefined); this ensures we never mutate a
shared/unscoped row when consolidating into a specific project.

In `@src/functions/consolidation-pipeline.ts`:
- Around line 64-69: The current scoping uses projectMatchesScope() for mutable
collections (scopedSummaries, scopedSemantic and other places that later
update/write back existingSemantic/procedural sets), which can include
unscoped/global rows; change those filters to require strict project equality
(e.g., compare s.project === project or explicit null/undefined handling)
instead of projectMatchesScope(), so only rows actually owned by the project are
returned before any mutations are applied; update the same pattern where
procedural/semantic sets are filtered elsewhere in this file to avoid modifying
legacy global entries.

In `@src/functions/lessons.ts`:
- Around line 33-36: The lesson fingerprint currently only uses normalized
content (fingerprintId("lsn", data.content.trim().toLowerCase())), causing
cross-project key collisions; update all places that compute the lesson
fingerprint (the fingerprintId call that assigns fp and any subsequent uses
around the kv.get/kv.put logic) to include the project scope (e.g., include
project or project.id in the fingerprint input alongside the normalized content)
so each project's lessons generate distinct KV keys (ensure the same change is
applied wherever fingerprintId("lsn", ...) is used in this file, including the
blocks around lines that perform kv.get<Lesson>(KV.lessons, fp) and
kv.put(KV.lessons, fp, ...)).

In `@src/functions/project-scope.ts`:
- Around line 3-13: The function resolveProjectScope reads
process.env["AGENTMEMORY_PROJECT_NAME"] directly which bypasses the .env loader;
replace that direct access with the project's environment helper by calling
getEnvVar("AGENTMEMORY_PROJECT_NAME") (or the exported name of the env helper)
and use its returned value instead of envProject, preserving the existing
trim/empty-string checks and the function signature; update references in
resolveProjectScope so it prefers the explicit argument, then getEnvVar result,
then undefined.

In `@src/functions/remember.ts`:
- Around line 55-56: The mem::remember flow creates/supersedes project-scoped
memories but never records audits; update the remember handler (the code around
requireProjectScope and the mem::remember write paths, including the logic
covered by the later block at lines ~118-159) to call recordAudit() for every
state-changing event: add a recordAudit call after creating the new memory id
(include action like "memory.create" with the new memory id, project id, and
relevant metadata) and another recordAudit when a previous memory is
superseded/deleted (action like "memory.supersede" with the old memory id and
the new memory id), ensuring payloads include project and user context; follow
existing recordAudit signature and error handling conventions when adding these
calls.

In `@src/functions/search.ts`:
- Around line 348-349: The session-backed branch uses a strict equality check to
decide scope, causing inconsistent filtering between legacy unscoped hits and
KV.sessions-backed hits; update the session-backed hit filtering to use the same
projectMatchesScope logic used by the fallback memory branch. Specifically,
replace the strict equality check that inspects hit.project (in the branch that
calls loadSession() and queries KV.sessions) with a call to
projectMatchesScope(projectFilter, hit.project) (or the equivalent predicate
used for the fallback branch), ensuring both the loadSession()/KV.sessions path
and the memory fallback path use projectMatchesScope to decide whether to keep
or drop a hit.

In `@src/functions/smart-search.ts`:
- Around line 397-407: The current logic trusts a missing session row and
proceeds to read KV.observations(sessionIdHint), allowing observations to bypass
project scoping; update the check in the block that reads KV.sessions for
sessionIdHint so that when project is provided you treat a missing hintedSession
as a mismatch and return null (i.e., after awaiting kv.get for KV.sessions, if
hintedSession is falsy OR hintedSession.project !== project then return null)
before attempting to read KV.observations(sessionIdHint) — this ensures
expandIds cannot return an unscoped observation when the session metadata is
missing.
- Around line 187-193: Import the Session type from ../types.js and use it in
the new sessionCache/loadSession code path so TypeScript can type-check Session;
then tighten findObservation so it never reads KV.observations(sessionIdHint)
when KV.sessions returns no session: modify findObservation to call
loadSession(sessionIdHint) and only proceed to query
KV.observations(sessionIdHint) if the loaded Session is non-null and its project
matches the provided project (otherwise return null or continue other lookup
paths), referencing the symbols Session, sessionCache, loadSession,
findObservation, KV.sessions, KV.observations, and sessionIdHint.

In `@src/mcp/tools-registry.ts`:
- Around line 27-30: Add the missing "project" input schema to the MCP tool
entries for memory_file_history and memory_sessions in tools-registry.ts so they
match the project-scoped handlers enforced in server.ts; specifically, update
the inputSchema objects for the memory_file_history and memory_sessions tool
registrations to include project: { type: "string", description: "Optional
project identifier used to scope recall" } using the same symbol names
(memory_file_history, memory_sessions) so schema-driven MCP clients can supply
project and avoid 400 errors.

In `@src/triggers/api.ts`:
- Around line 879-884: The handler currently forwards the entire req.body into
sdk.trigger (e.g., the block using requireProjectParam(...) and calling
sdk.trigger({ function_id: "mem::file-context", payload: { ...req.body, project
} })), which violates the allowlist rule; replace the spread of req.body with an
explicit, validated payload object that picks only allowed fields (e.g.,
destructure and validate expected keys from req.body, or build payload = {
project, fileId, contextOptions } etc.), use requireProjectParam result for
project, validate/normalize each field, and pass that explicit payload to
sdk.trigger; apply the same change to the other endpoints that call sdk.trigger
with raw req.body (the blocks around lines referencing function_ids like
"mem::file-context" and the other three similar handlers).

---

Outside diff comments:
In `@src/functions/enrich.ts`:
- Around line 28-40: The mem::file-context trigger is missing the project field
in its payload, so when isolation is enabled the call fails; update the
sdk.trigger call that creates fileContextPromise to include the project (use the
project returned by requireProjectScope) in the payload object passed to
function_id "mem::file-context" so the invoked function receives the same
project scope; specifically modify the fileContextPromise payload in enrich.ts
to add project: project (or data.project if that pattern is used) and keep the
existing catch behavior.

In `@src/functions/flow-compress.ts`:
- Around line 30-57: The code currently accepts a resolved project via
requireProjectScope but still loads actions from a foreign runId or actionIds;
ensure we only compress actions belonging to that resolved project: when
handling a runId (RoutineRun), verify the run's project equals the resolved
project and return an error if not, or alternatively filter its actionIds by
fetching each Action (KV.actions) and only include actions whose action.project
=== project; when handling explicit actionIds, fetch each Action and only push
those where action.project === project (or return an error for any mismatched id
based on desired strictness). Update the run branch (where run.actionIds are
iterated) and the actionIds branch (where data.actionIds are iterated) to
perform this project check before adding to actionsToCompress, referencing
requireProjectScope, KV.routineRuns, KV.actions, RoutineRun, and Action.

In `@src/functions/reflect.ts`:
- Around line 281-287: The fingerprinting is currently based on content only,
causing cross-project collisions; update the fingerprint key generation to
include the project scope (e.g., incorporate projectId or project slug) when
calling fingerprintId so each project gets its own insight record. Concretely,
change the fp computation used with kv.get/kv.set for KV.insights (the lines
around fingerprintId("ins", content.trim().toLowerCase())) to include the
project identifier (for example fingerprintId("ins",
`${projectId}:${content.trim().toLowerCase()}`) or pass both projectId and
content if fingerprintId supports multiple args), and make the same change for
the other occurrence referenced (near reinforceInsight and the block at 300-306)
so lookups and writes are consistently namespaced per project.

In `@test/mcp-standalone-proxy.test.ts`:
- Around line 21-25: The beforeEach hook in test/mcp-standalone-proxy.test.ts
currently calls resetHandleForTests() and cleans AGENTMEMORY_SECRET but does not
clear AGENTMEMORY_PROJECT_NAME, which can cause cross-test pollution; update the
beforeEach to also remove process.env["AGENTMEMORY_PROJECT_NAME"] (similar to
the existing delete process.env["AGENTMEMORY_SECRET"]) so tests that set
AGENTMEMORY_PROJECT_NAME (e.g., around line 121) cannot leak its value into
later tests.

---

Nitpick comments:
In `@test/mcp-standalone-proxy.test.ts`:
- Around line 79-119: The mocked /agentmemory/search response in the test
"proxies memory_recall to POST /agentmemory/search and forwards
format/token_budget (`#507`)" includes a project field that is only produced
server-side when AGENTMEMORY_PROJECT_NAME is set, which makes the test
inconsistent; either set process.env["AGENTMEMORY_PROJECT_NAME"] at the top of
this test to mirror server behavior (so the mock + assertion on body.project
stays valid) or remove the project property from the stubbed response and delete
the corresponding assertion (the test should only assert forwarding of
format/token_budget and other provided fields via handleToolCall and the calls
array). Ensure changes are applied in this test block (function/test name:
it(... "proxies memory_recall ...")) and update or remove the
expect(body.project) and related mock project value accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10d90049-6c2b-4b82-9fb9-29bc9d6e1fde

📥 Commits

Reviewing files that changed from the base of the PR and between 2a58140 and 819f5ea.

📒 Files selected for processing (41)
  • .env.example
  • plugin/.mcp.copilot.json
  • plugin/.mcp.json
  • plugin/scripts/notification.mjs
  • plugin/scripts/post-commit.mjs
  • plugin/scripts/post-tool-failure.mjs
  • plugin/scripts/post-tool-use.mjs
  • plugin/scripts/pre-compact.mjs
  • plugin/scripts/pre-tool-use.mjs
  • plugin/scripts/prompt-submit.mjs
  • plugin/scripts/session-end.mjs
  • plugin/scripts/session-start.mjs
  • plugin/scripts/stop.mjs
  • plugin/scripts/subagent-start.mjs
  • plugin/scripts/subagent-stop.mjs
  • plugin/scripts/task-completed.mjs
  • src/cli/connect/util.ts
  • src/config.ts
  • src/functions/consolidate.ts
  • src/functions/consolidation-pipeline.ts
  • src/functions/enrich.ts
  • src/functions/file-index.ts
  • src/functions/flow-compress.ts
  • src/functions/lessons.ts
  • src/functions/patterns.ts
  • src/functions/project-scope.ts
  • src/functions/reflect.ts
  • src/functions/remember.ts
  • src/functions/search.ts
  • src/functions/smart-search.ts
  • src/mcp/server.ts
  • src/mcp/standalone.ts
  • src/mcp/tools-registry.ts
  • src/triggers/api.ts
  • src/types.ts
  • test/consolidation-pipeline.test.ts
  • test/cross-project-isolation.test.ts
  • test/mcp-standalone-proxy.test.ts
  • test/mcp-standalone.test.ts
  • test/project-isolation.test.ts
  • test/smart-search.test.ts

Comment thread .env.example
Comment thread src/functions/consolidate.ts
Comment thread src/functions/consolidation-pipeline.ts
Comment thread src/functions/lessons.ts
Comment thread src/functions/project-scope.ts
Comment thread src/functions/search.ts
Comment thread src/functions/smart-search.ts Outdated
Comment thread src/functions/smart-search.ts
Comment thread src/mcp/tools-registry.ts
Comment thread src/triggers/api.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/triggers/api.ts (1)

888-899: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject malformed api::file-context bodies instead of downgrading them.

{}, { files: [42] }, or a blank sessionId now normalize into payload = { files: [] } and still call mem::file-context. That leaves the boundary-validation gap open and turns invalid client input into ambiguous downstream behavior.

Suggested fix
       const body = req.body ?? {};
       const sessionId = asNonEmptyString(body.sessionId);
       const files = Array.isArray(body.files)
         ? body.files
             .map((file) => (typeof file === "string" ? file.trim() : ""))
             .filter(Boolean)
         : [];
+      if (!sessionId || files.length === 0) {
+        return {
+          status_code: 400,
+          body: {
+            error: "sessionId (string) and files (string[]) are required",
+          },
+        };
+      }
       const payload: { sessionId?: string; files: string[]; project?: string } = {
         files,
       };
-      if (sessionId) payload.sessionId = sessionId;
+      payload.sessionId = sessionId;

As per coding guidelines, "REST endpoint registration must: validate and whitelist request body fields (never pass raw body to sdk.trigger)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/triggers/api.ts` around lines 888 - 899, The handler currently normalizes
invalid bodies (e.g., {} or { files: [42] } or blank sessionId) into payload = {
files: [] } and still calls the mem::file-context trigger; change this to
strictly validate and whitelist incoming fields: ensure body.files exists as an
array of non-empty strings (map/filter as now but reject if any element is not a
string) and ensure sessionId is a non-empty string (use asNonEmptyString and
treat blank as invalid), and if validation fails return a 400 error instead of
constructing payload; perform these checks before building payload and before
calling sdk.trigger or mem::file-context to avoid passing downgraded/malformed
input downstream.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/triggers/api.ts`:
- Around line 1646-1658: Validate that body.tier (after trimming into the local
variable tier) is one of the allowed values
["all","semantic","reflect","procedural","decay"] and return a 400 with a clear
error if it is not; keep the existing non-empty-string check but replace it with
a whitelist check before assigning payload.tier so that invalid values (e.g.,
"foo") are rejected; update the error message to say "tier must be one of: all,
semantic, reflect, procedural, decay" and leave the handling of body.force and
project unchanged.
- Around line 886-899: The handler currently passes req.body?.project directly
into requireProjectParam which lets non-string values (e.g. numbers) be treated
as missing by resolveProjectScope; validate the project input at the HTTP
boundary by checking if req.body.project is present and is a string before
calling requireProjectParam, returning a 400 error for any present non-string
value; update the same validation in all other handlers that call
requireProjectParam (the other instances noted) so requireProjectParam only
receives string|undefined and resolveProjectScope cannot silently fallback.

In `@test/lessons.test.ts`:
- Around line 234-236: The test "includes legacy unscoped lessons in project
filters when isolation is off" relies on AGENTMEMORY_PROJECT_ISOLATION being
disabled; pin that environment variable at the start of the test (e.g., set
process.env.AGENTMEMORY_PROJECT_ISOLATION = "false" or delete it) before calling
sdk.trigger("mem::lesson-list", ...) and restore the previous value in an
after/finally block so other tests aren't affected; update the test to capture
the original process.env.AGENTMEMORY_PROJECT_ISOLATION, set it explicitly for
this case, run the assertion on result.lessons, then restore the original value.

---

Duplicate comments:
In `@src/triggers/api.ts`:
- Around line 888-899: The handler currently normalizes invalid bodies (e.g., {}
or { files: [42] } or blank sessionId) into payload = { files: [] } and still
calls the mem::file-context trigger; change this to strictly validate and
whitelist incoming fields: ensure body.files exists as an array of non-empty
strings (map/filter as now but reject if any element is not a string) and ensure
sessionId is a non-empty string (use asNonEmptyString and treat blank as
invalid), and if validation fails return a 400 error instead of constructing
payload; perform these checks before building payload and before calling
sdk.trigger or mem::file-context to avoid passing downgraded/malformed input
downstream.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a21e0dd-fff2-45c9-9bd9-fa0a4b936c0c

📥 Commits

Reviewing files that changed from the base of the PR and between 819f5ea and aa69fc2.

📒 Files selected for processing (15)
  • .env.example
  • src/functions/consolidate.ts
  • src/functions/consolidation-pipeline.ts
  • src/functions/lessons.ts
  • src/functions/project-scope.ts
  • src/functions/search.ts
  • src/functions/smart-search.ts
  • src/mcp/tools-registry.ts
  • src/triggers/api.ts
  • test/cli-connect.test.ts
  • test/consolidation-pipeline.test.ts
  • test/cross-project-isolation.test.ts
  • test/lessons.test.ts
  • test/mcp-standalone.test.ts
  • test/smart-search.test.ts
✅ Files skipped from review due to trivial changes (2)
  • .env.example
  • test/mcp-standalone.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • test/cross-project-isolation.test.ts
  • test/consolidation-pipeline.test.ts
  • src/mcp/tools-registry.ts
  • src/functions/project-scope.ts
  • src/functions/lessons.ts
  • src/functions/search.ts

Comment thread src/triggers/api.ts
Comment thread src/triggers/api.ts
Comment thread test/lessons.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/triggers/api.ts (1)

140-145: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don’t silently floor fractional timeline offsets.

parseOptionalNonNegativeInt(0.9) becomes 0 and 1.9 becomes 1, so /agentmemory/timeline rewrites malformed input instead of rejecting it. That changes the requested window and weakens the boundary validation this PR is tightening.

Suggested fix
 function parseOptionalNonNegativeInt(value: unknown): number | undefined | null {
   const parsed = parseOptionalFiniteNumber(value);
   if (parsed === undefined || parsed === null) return parsed;
-  const floored = Math.floor(parsed);
-  return floored >= 0 ? floored : null;
+  if (!Number.isInteger(parsed) || parsed < 0) return null;
+  return parsed;
 }

As per coding guidelines, "Perform input validation at system boundaries (MCP handlers and REST endpoints)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/triggers/api.ts` around lines 140 - 145, The parseOptionalNonNegativeInt
function currently floors fractional inputs, which silently rewrites malformed
offsets; change it to reject non-integer values instead: after calling
parseOptionalFiniteNumber(value) (in parseOptionalNonNegativeInt), if the result
is undefined/null return it, then check Number.isInteger(parsed) and that parsed
>= 0—return parsed when both true, otherwise return null; this preserves
boundary validation and avoids silently altering fractional inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/triggers/api.ts`:
- Around line 151-156: The current validation only rejects non-strings but
allows whitespace-only strings to reach resolveProjectScope; update the input
check in the handler so that after confirming value is a string you also reject
strings that are empty after trimming (e.g., if (typeof value === "string" &&
value.trim() === "") return { error: `project must be a string for ${routeName}`
};), returning the same 400-style error before calling
resolveProjectScope(value) so whitespace-only input does not fall through and
pick up AGENTMEMORY_PROJECT_NAME.

---

Outside diff comments:
In `@src/triggers/api.ts`:
- Around line 140-145: The parseOptionalNonNegativeInt function currently floors
fractional inputs, which silently rewrites malformed offsets; change it to
reject non-integer values instead: after calling
parseOptionalFiniteNumber(value) (in parseOptionalNonNegativeInt), if the result
is undefined/null return it, then check Number.isInteger(parsed) and that parsed
>= 0—return parsed when both true, otherwise return null; this preserves
boundary validation and avoids silently altering fractional inputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86deae31-9a77-484a-8007-b2461838953d

📥 Commits

Reviewing files that changed from the base of the PR and between aa69fc2 and 19b83a6.

📒 Files selected for processing (3)
  • src/triggers/api.ts
  • test/api-project-validation.test.ts
  • test/lessons.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/lessons.test.ts

Comment thread src/triggers/api.ts
Comment on lines +151 to +156
if (value !== undefined && typeof value !== "string") {
return {
error: `project must be a string for ${routeName}`,
};
}
const project = resolveProjectScope(value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject whitespace-only project values before resolving scope.

This only blocks non-strings. A body/query value like " " still falls through to resolveProjectScope() and can pick up AGENTMEMORY_PROJECT_NAME, so routes using this helper may read or write the default project instead of returning 400 for invalid client input.

Suggested fix
 function requireProjectParam(
   value: unknown,
   routeName: string,
 ): { project?: string; error?: string } {
   if (value !== undefined && typeof value !== "string") {
     return {
       error: `project must be a string for ${routeName}`,
     };
   }
+  if (typeof value === "string" && value.trim().length === 0) {
+    return {
+      error: `project must be a non-empty string for ${routeName}`,
+    };
+  }
   const project = resolveProjectScope(value);
   if (isProjectIsolationEnabled() && !project) {
     return {
       error: `project is required for ${routeName} when AGENTMEMORY_PROJECT_ISOLATION=true`,
     };

As per coding guidelines, "Perform input validation at system boundaries (MCP handlers and REST endpoints)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/triggers/api.ts` around lines 151 - 156, The current validation only
rejects non-strings but allows whitespace-only strings to reach
resolveProjectScope; update the input check in the handler so that after
confirming value is a string you also reject strings that are empty after
trimming (e.g., if (typeof value === "string" && value.trim() === "") return {
error: `project must be a string for ${routeName}` };), returning the same
400-style error before calling resolveProjectScope(value) so whitespace-only
input does not fall through and pick up AGENTMEMORY_PROJECT_NAME.

Durocks added 2 commits June 5, 2026 12:07
- Updated the default behavior for AGENTMEMORY_PROJECT_ISOLATION to be enabled, allowing for stricter project scoping.
- Modified the TOML configuration to include additional environment variables related to project isolation.
- Enhanced the handling of project isolation in various functions, ensuring proper error messages and behavior when isolation is enabled or disabled.
- Updated tests to reflect changes in project isolation handling, ensuring backward compatibility and proper functionality.
- Improved documentation and descriptions for project-related fields in the tools registry.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mcp/tools-registry.ts (1)

205-221: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Schema inconsistency: memory_profile marks project as required but description suggests conditional requirement.

The memory_profile tool includes project in the required array (line 220), making it mandatory at the schema level. However, the description (lines 212-213) states "Required when AGENTMEMORY_PROJECT_ISOLATION is enabled (default)", which matches the conditional-requirement pattern used by all other tools in this file (memory_recall, memory_save, memory_smart_search, etc.).

For consistency with the rest of the codebase, either:

  1. Remove project from the required array and rely on runtime validation via requireProjectArg() (like other tools), OR
  2. Update the description to state "Project path (required)" if memory_profile genuinely cannot function without a project regardless of the isolation flag.

Based on the PR's isolation-scoping architecture and the uniform handling across other tools, option 1 is likely correct.

Suggested alignment with other tools
     inputSchema: {
       type: "object",
       properties: {
         project: {
           type: "string",
           description:
             "Project path. Required when AGENTMEMORY_PROJECT_ISOLATION is enabled (default); use the repo root folder name.",
         },
         refresh: {
           type: "string",
           description: "Set to 'true' to force rebuild",
         },
       },
-      required: ["project"],
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp/tools-registry.ts` around lines 205 - 221, The memory_profile tool's
JSON schema marks "project" as required but its description indicates it's
conditionally required; make it consistent with other tools by removing
"project" from the inputSchema.required array of the memory_profile tool (the
memory_profile object), and rely on runtime validation via requireProjectArg()
where the tool is invoked; ensure the description remains the conditional one
("Required when AGENTMEMORY_PROJECT_ISOLATION is enabled") so behavior and
messaging match other tools like memory_recall/memory_save.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/state/hybrid-search.ts`:
- Around line 291-314: The code references the Session type (used in loadSession
and resolveProject) but it isn't imported, causing a TS error; update the module
imports at the top of the file to include the Session type (the same module that
exports Memory, KV, etc.) so Session is available for the sessionCache,
loadSession, and resolveProject signatures; ensure the import uses the correct
named export (Session) consistent with how Memory and other types are imported.

---

Outside diff comments:
In `@src/mcp/tools-registry.ts`:
- Around line 205-221: The memory_profile tool's JSON schema marks "project" as
required but its description indicates it's conditionally required; make it
consistent with other tools by removing "project" from the inputSchema.required
array of the memory_profile tool (the memory_profile object), and rely on
runtime validation via requireProjectArg() where the tool is invoked; ensure the
description remains the conditional one ("Required when
AGENTMEMORY_PROJECT_ISOLATION is enabled") so behavior and messaging match other
tools like memory_recall/memory_save.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d774cb20-d0ef-4f7e-8cd8-56c0975772bf

📥 Commits

Reviewing files that changed from the base of the PR and between 19b83a6 and c30cc0c.

📒 Files selected for processing (29)
  • README.md
  • plugin/.mcp.copilot.json
  • plugin/.mcp.json
  • src/cli/connect/codex.ts
  • src/cli/connect/util.ts
  • src/config.ts
  • src/functions/project-scope.ts
  • src/functions/search.ts
  • src/functions/smart-search.ts
  • src/mcp/rest-proxy.ts
  • src/mcp/server.ts
  • src/mcp/standalone.ts
  • src/mcp/tools-registry.ts
  • src/state/hybrid-search.ts
  • src/triggers/api.ts
  • src/types.ts
  • test/agent-id-scope.test.ts
  • test/cli-connect.test.ts
  • test/connect-new-agents.test.ts
  • test/enrich-project-isolation.test.ts
  • test/enrich.test.ts
  • test/lessons.test.ts
  • test/mcp-standalone-proxy.test.ts
  • test/mcp-standalone.test.ts
  • test/project-isolation.test.ts
  • test/remember-project-scope.test.ts
  • test/search.test.ts
  • test/smart-search.test.ts
  • test/vector-index-populate.test.ts
✅ Files skipped from review due to trivial changes (3)
  • src/cli/connect/codex.ts
  • README.md
  • plugin/.mcp.copilot.json
🚧 Files skipped from review as they are similar to previous changes (11)
  • plugin/.mcp.json
  • src/config.ts
  • src/functions/project-scope.ts
  • src/cli/connect/util.ts
  • test/smart-search.test.ts
  • src/mcp/standalone.ts
  • test/project-isolation.test.ts
  • src/functions/search.ts
  • src/mcp/server.ts
  • src/functions/smart-search.ts
  • src/triggers/api.ts

Comment on lines +291 to +314
const sessionCache = new Map<string, Session | null>();
const memoryProjectCache = new Map<string, string | null>();
const loadSession = async (sessionId: string): Promise<Session | null> => {
if (sessionCache.has(sessionId)) return sessionCache.get(sessionId)!;
const session = await this.kv.get<Session>(KV.sessions, sessionId).catch(() => null);
sessionCache.set(sessionId, session);
return session;
};
const loadMemoryProject = async (obsId: string): Promise<string | null> => {
if (memoryProjectCache.has(obsId)) return memoryProjectCache.get(obsId)!;
const mem = await this.kv.get<Memory>(KV.memories, obsId).catch(() => null);
const project =
typeof mem?.project === "string" && mem.project.trim().length > 0
? mem.project.trim()
: null;
memoryProjectCache.set(obsId, project);
return project;
};
const resolveProject = async (obsId: string, sessionId: string): Promise<string | undefined> => {
const session = await loadSession(sessionId);
if (session) return session.project;
const memProject = await loadMemoryProject(obsId);
return memProject ?? undefined;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing import for Session type.

Lines 291 and 295 reference the Session type, but it is not included in the import statement (lines 3-9). This will cause a TypeScript compilation error.

📦 Proposed fix to add the missing import
 import type {
   EmbeddingProvider,
   HybridSearchResult,
   CompressedObservation,
   Memory,
   QueryExpansion,
+  Session,
 } from "../types.js";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/state/hybrid-search.ts` around lines 291 - 314, The code references the
Session type (used in loadSession and resolveProject) but it isn't imported,
causing a TS error; update the module imports at the top of the file to include
the Session type (the same module that exports Memory, KV, etc.) so Session is
available for the sessionCache, loadSession, and resolveProject signatures;
ensure the import uses the correct named export (Session) consistent with how
Memory and other types are imported.

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