Skip to content

fix: guard autonomous memory extractor writes - #396

Merged
pedronauck merged 2 commits into
compozy:mainfrom
davioliveeira:codex/fix-memory-extractor-safety
Aug 14, 2026
Merged

pedronauck merged 2 commits into
compozy:mainfrom
davioliveeira:codex/fix-memory-extractor-safety

Conversation

@davioliveeira

@davioliveeira davioliveeira commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • reject Memory v2 operational identifiers such as memory_propose, native compozy__memory_* tool names, controller event names, and scanner rule IDs
  • prevent autonomous extractor/provider/dreaming candidates from updating an existing memory solely because their generated slug collides
  • preserve explicit filename collision updates for direct CLI/user writes

Root cause

The extractor prompt described the correct WHAT_NOT_TO_SAVE policy, but the deterministic scanner did not recognize operational Memory v2 identifiers. Model-generated operational chatter could therefore reach curated memory.

Separately, exact filename collisions were treated as update identity before semantic matching. For autonomous candidates, the filename is generated from model-supplied type/entity data, so unrelated facts could overwrite an existing entry with the same slug.

Autonomous generated collisions now produce a deterministic noop and do not invoke the LLM tiebreaker. This also prevents a tiebreaker add result from writing over the colliding filename. Explicitly targeted writes keep the existing update behavior.

Fixes #395.

Validation

  • go test ./internal/memory/scan ./internal/memory/controller
  • go test ./internal/memory/...
  • go test -race ./internal/memory/scan ./internal/memory/controller
  • independent diff review: no remaining actionable findings

make gate-full was also attempted twice. Its Go/module verification completed, but CodegenCheck is blocked in this checkout by the local Bun runtime (1.1.22-canary) differing from the repository's declared [email protected]; OpenAPI generation fails while resolving TypeScript/Redocly dependencies. bun install --frozen-lockfile was run and the gate retried once, but the environment-level failure remained. No generated or dependency files are included in this PR.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented autonomous memory updates from overwriting existing entries when generated filenames collide.
    • Preserved existing entries without creating duplicate updates in collision scenarios.
    • Added safeguards to reject internal memory-operation identifiers from being saved as regular memory content.
    • Improved collision handling, including numeric-prefix filenames and cases where alternative actions might otherwise be suggested.

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

@davioliveeira is attempting to deploy a commit to the Compozy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 10671a75-5941-40b6-a8bc-3d44315364b5

📥 Commits

Reviewing files that changed from the base of the PR and between 9071385 and bc82bf3.

⛔ Files ignored due to path filters (5)
  • docs/qa/charters/CH-memory-operational-state-boundary.md is excluded by !**/*.md
  • docs/qa/journeys/J-store-durable-memory-safely.md is excluded by !**/*.md
  • docs/qa/reports/2026-08-14-pr-396-memory-safety.md is excluded by !**/*.md
  • docs/qa/scenarios/MS-reject-operational-memory-state.md is excluded by !**/*.md
  • skills/compozy/references/memory.md is excluded by !**/*.md
📒 Files selected for processing (5)
  • internal/memory/controller/controller.go
  • internal/memory/controller/controller_collision.go
  • internal/memory/controller/controller_test.go
  • internal/memory/scan/scan.go
  • internal/memory/scan/scan_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/memory/scan/scan.go
  • internal/memory/scan/scan_test.go
  • internal/memory/controller/controller.go

Walkthrough

The memory scanner rejects operational-state identifiers. Autonomous writes no longer update existing targets when generated filenames collide without an explicit filename. Controller tests verify no-op results, target preservation, absent replay content, and tiebreaker bypass.

Changes

Memory safety corrections

Layer / File(s) Summary
Operational-state scan rule
internal/memory/scan/scan.go, internal/memory/scan/scan_test.go
The scanner rejects memory-controller operational messages and native memory tool identifiers with policy_memory_operational_state.
Autonomous collision guard
internal/memory/controller/controller.go, internal/memory/controller/controller_collision.go, internal/memory/controller/controller_test.go
Autonomous writes with generated filename collisions return noop. Explicit filename collisions continue to return update with the existing target content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to bc82b

The PR filters operational memory content and prevents autonomous slug collisions from overwriting existing memories while preserving explicit user updates. No actionable merge-blocking risk remains beyond normal checks.

Sequence Diagram(s)

sequenceDiagram
  participant Candidate as Memory candidate
  participant Controller as Memory controller
  participant Target as Existing target
  participant Tiebreaker as Tiebreaker
  Candidate->>Controller: Submit generated filename
  Controller->>Target: Check exact filename collision
  alt Autonomous write without explicit target
    Controller->>Controller: Create noop decision
    Controller-->>Candidate: Return existing target ID without replay
  else Explicit target or non-autonomous write
    Controller->>Tiebreaker: Continue collision resolution
    Controller-->>Candidate: Return update decision with prior content
  end
Loading

Suggested reviewers: pedronauck

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main autonomous memory write safety fix.
Linked Issues check ✅ Passed The changes implement scanner rejection, autonomous collision no-ops, explicit update preservation, and targeted regression tests required by issue #395.
Out of Scope Changes check ✅ Passed All reviewed changes directly support the linked issue objectives for Memory v2 safety and regression coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@davioliveeira
davioliveeira marked this pull request as ready for review August 14, 2026 02:53
@greptile-apps

greptile-apps Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR strengthens durable-memory safety by rejecting operational Memory v2 identifiers and preventing generated autonomous filename collisions from overwriting existing entries.

  • Adds deterministic scanner coverage for native tool names, operation and event identifiers, and policy rule IDs.
  • Converts autonomous generated-slug collisions into no-op decisions without invoking the LLM tiebreaker.
  • Preserves explicitly targeted collision updates and adds controller, scanner, and QA coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
internal/memory/controller/controller.go Delegates exact-filename collision handling to the new origin-aware collision decision path.
internal/memory/controller/controller_collision.go Prevents autonomous generated-slug collisions from establishing update identity while retaining explicit-target updates.
internal/memory/controller/controller_test.go Covers generated and explicit collisions for extractor, provider, and dreaming origins; the previously reported parallel-subtest requirement is satisfied.
internal/memory/scan/scan.go Adds deterministic rejection of Memory v2 operational identifiers and policy names.
internal/memory/scan/scan_test.go Exercises qualified tools, unqualified tools, operations, events, and policy identifiers against the new scanner rule.
skills/compozy/references/memory.md Documents that Memory v2 operational identifiers are rejected from durable memory.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Memory candidate] --> B{Operational identifier?}
    B -->|Yes| C[Reject without write]
    B -->|No| D{Filename collision?}
    D -->|No| E[Continue semantic matching]
    D -->|Yes| F{Autonomous without explicit target?}
    F -->|Yes| G[No-op and preserve existing memory]
    F -->|No| H[Update explicitly targeted memory]
Loading

Reviews (2): Last reviewed commit: "fix: adjustments" | Re-trigger Greptile

Comment thread internal/memory/controller/controller_test.go Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 14, 2026

@pedronauck pedronauck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested

  1. Cover the full canonical Memory v2 operational surface in the scanner. The new closed matcher in internal/memory/scan/scan.go:238 does not match canonical identifiers such as compozy__memory_note, compozy__memory_search, compozy__memory_reindex, memory.write.reverted, or policy_memory_operational_state itself. Those candidates reach Controller.Decide and can be persisted, which contradicts this PR goal. Derive the matcher from the canonical inventory or cover the controlled family, and add isolated table-driven regression cases for every identifier family.

  2. Complete the collision-boundary regression coverage. The new controller tests exercise only OriginExtractor with a generated filename. Cover Provider and Dreaming as autonomous origins, and prove that an explicitly supplied target filename still permits the intended direct update. This protects both sides of the new guard.

The reported lack of t.Parallel is not valid; the new subtests already run in parallel.

@pedronauck
pedronauck merged commit fed0271 into compozy:main Aug 14, 2026
29 of 30 checks passed
This was referenced Aug 24, 2026
This was referenced Sep 1, 2026
This was referenced Sep 27, 2026
This was referenced Oct 6, 2026
This was referenced Oct 9, 2026

This branch was successfully deployed

1 active deployment
Preview — bc82bf3c Deployed Aug 14, 2026 by vercel[bot]
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.

Memory extractor persists forbidden operational chatter and overwrites unrelated entries on generated slug collisions

2 participants