Skip to content

feat(memory): add PawWork memory v1 - #520

Merged
Astro-Han merged 10 commits into
devfrom
slock/i519-memory-v1
May 10, 2026
Merged

feat(memory): add PawWork memory v1#520
Astro-Han merged 10 commits into
devfrom
slock/i519-memory-v1

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

Implements PawWork Memory v1 from #519:

  • adds a single local ~/.pawwork/memory/MEMORY.md contract with ## Profile and ## Archive
  • loads only Profile into PawWork runtime context with explicit precedence guardrails
  • keeps Archive as plain Markdown history searched through Bash grep, matching the Codex CLI pattern instead of adding a dedicated memory search tool
  • adds user-reviewed Archive writes, redaction, disable mode, raw editor, and reset controls
  • keeps a minimal delete-by-id API for future management UI, without exposing a v1 delete-by-id form
  • adds Playwright smoke coverage for the Memory settings UI and idle-session review UI

Why

PawWork sessions currently forget useful cross-session context. The v1 goal is the smallest transparent memory system: user-visible Markdown, no embeddings, no cloud sync, no silent writes, no dedicated memory search tool, and reversible controls.

Related Issue

Closes #519

Human Review Status

Pending. GLM first-pass and Opus second-pass reviewed the earlier versions; after AstroHan/Opus/GPT/Kimi/GLM arbitration, the dedicated memory_search tool and v1-only metadata/UI surface were removed for a smaller Bash-grep design.

Review Focus

  • Parser contract and safe-mode behavior for ## Profile / ## Archive
  • Memory precedence text in prompt assembly: memory must remain user context, not instruction authority
  • Archive lookup guidance: PawWork agents should grep ~/.pawwork/memory/MEMORY.md directly and keep injected Archive output capped to 2000 chars
  • User controls: disable, raw editor, and reset-to-template
  • Settings Memory and idle-session review UI smoke coverage
  • Whether the minimal session-end review textbox is acceptable for v1, with auto-suggest deferred

Risk Notes

  • Local files: creates and updates ~/.pawwork/memory/MEMORY.md, plus .bak, .broken.bak, and .disabled files under the same directory.
  • Privacy: v1 includes basic token/password redaction at write time, but it is intentionally conservative and not a full secret scanner.
  • Archive lookup: scope filtering is now prompt/Bash-grep guidance rather than a server-side search API. This was chosen deliberately to keep v1 aligned with Codex CLI and avoid an extra LLM-visible tool.
  • UI: Settings and session review surfaces are minimal v1 controls, not a polished management UI.
  • Platform: file permissions use private modes where supported; Windows behavior should be verified by CI/advisory flows.
  • Generated content: SDK client files were regenerated for the new memory routes.

How To Verify

Diff check: git diff --check passed
OpenCode memory/smoke tests: 10 passed, 0 failed
OpenCode typecheck: passed
App typecheck: passed
App focused/source tests: 1003 passed, 0 failed
App Memory Playwright E2E: 2 passed, 0 failed
SDK typecheck: passed
Core typecheck: passed
Dependency setup in new worktree: bun install --frozen-lockfile completed without lockfile changes

Screenshots or Recordings

Not captured in this Slock run. Visible UI changes are covered by packages/app/e2e/settings/settings-memory.spec.ts:

  • Settings > Memory raw editor / disable / reset controls
  • Idle session footer memory review textbox
  • Accepted memory entry redacts sk-* style secrets before landing in MEMORY.md

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, primary area, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Requested labels: type: feature, area: app, area: opencode, priority: P2.

@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 13 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b78eb4c5-8026-4d5e-bfaa-8c2f8b0e4db9

📥 Commits

Reviewing files that changed from the base of the PR and between a9383fd and b02eb50.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/v2/gen/sdk.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (16)
  • packages/app/e2e/settings/settings-memory.spec.ts
  • packages/app/src/components/memory/memory-state-sync.ts
  • packages/app/src/components/memory/session-memory-review.tsx
  • packages/app/src/components/settings-memory.test.ts
  • packages/app/src/components/settings-memory.tsx
  • packages/app/src/components/settings-page.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout.tsx
  • packages/opencode/src/memory/memory.ts
  • packages/opencode/src/memory/proposal.ts
  • packages/opencode/src/memory/service.ts
  • packages/opencode/src/server/instance/memory.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/opencode/test/memory/pawwork-memory.test.ts
📝 Walkthrough

Walkthrough

This PR implements PawWork's transparent memory system: a MEMORY.md-backed store with user-visible Profile/Archive sections, session-end review prompts, memory search tooling, settings UI, and cross-session context injection into the system prompt.

Changes

Core Memory System

Layer / File(s) Summary
Memory Data Format
packages/opencode/src/memory/memory.ts
Defines MemoryFile namespace with Scope/SafeModeReason/Entry/InvalidEntry types and ParseResult discriminated union for ok vs safe_mode states.
Parsing & Formatting
packages/opencode/src/memory/memory.ts
Implements defaultTemplate generation, ID creation via UUID, parse logic with Profile/Archive validation and duplicate-ID detection, formatEntry for heading/body assembly, and metadata extraction helpers.
Proposal Redaction
packages/opencode/src/memory/proposal.ts
Detects sensitive patterns (tokens, keys, passwords) via regex, redacts matched content, and builds Proposal objects with defaultSelected/warning flags.
Filesystem Service
packages/opencode/src/memory/service.ts
Implements MemoryService with MEMORY.md creation/ensure, atomic writes via .bak/temp, read/save/reset/delete/toggle/search/append operations, and test helpers.
Path Helpers
packages/core/src/pawwork-home.ts
Adds memoryDir() and memoryFile() to derive memory directory and MEMORY.md paths.

Session & API Integration

Layer / File(s) Summary
System Prompt Injection
packages/opencode/src/session/prompt.ts
Conditionally loads memory Profile from MemoryService when isPawWork() is true, truncates to 4k chars, wraps in <pawwork-memory>, and injects into system prompt with error handling.
Memory Search Tool
packages/opencode/src/tool/memory-search.ts, packages/opencode/src/tool/registry.ts
Implements memory_search tool for Archive queries; registers conditionally in ToolRegistry only when isPawWork() is true.
HTTP API Routes
packages/opencode/src/server/instance/index.ts, packages/opencode/src/server/instance/memory.ts
Registers /memory route handler with endpoints for GET state, PATCH/raw update, POST reset, PATCH disabled toggle, DELETE entry/:id, POST search, and POST proposal preview/accept.

User Interface

Layer / File(s) Summary
Session Review Prompt
packages/app/src/components/memory/session-memory-review.tsx
Shows memory proposal review at session end with editable draft textarea, dismiss/accept buttons, and API integration with safe_mode bypass logic.
Settings Memory UI
packages/app/src/components/settings-memory.tsx
Settings component for memory management: displays state, safe-mode/profile-size warnings, raw textarea editor, save/reset/delete entry controls, and refresh integration.
Settings Page Integration
packages/app/src/components/settings-page.tsx
Extends SettingsPageTab type to include "memory", updates tab allowlist, and wires SettingsMemory component with trigger/content in settings tabs.
Session Page Wiring
packages/app/src/pages/session.tsx, packages/app/src/pages/session/session-main-view.tsx
Adds memoryReviewVisible prop to SessionMainView, computed from timeline session state; renders SessionMemoryReview alongside composer in active-session conditional.
Localization
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Adds English and Chinese UI strings for memory tab, state labels, warnings, actions, and review/confirm flow.
Tests & Contracts
packages/opencode/test/memory/pawwork-memory.test.ts, packages/app/src/components/settings-memory.test.ts, packages/app/src/shell-frame-contract.test.ts
Adds comprehensive memory parser/service tests; contract test for settings tab/component presence; updates session-main-view test expectations for SessionMemoryReview.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Astro-Han/pawwork#438: Both extend PawWorkHome path utilities with new memory-specific helpers.
  • Astro-Han/pawwork#264: Both modify packages/opencode/src/session/prompt.ts for prompt assembly logic.
  • Astro-Han/pawwork#515: Both modify session page wiring (packages/app/src/pages/session.tsx) for component prop passing.

Suggested labels

enhancement, P1, app, ui, harness

Poem

🐰 A memory so bright and clear,
Stores your context year after year,
Profile ready, Archive complete,
Cross-session work becomes so neat!
No more forgetting what came before,
PawWork remembers more and more. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The PR title 'feat(memory): add PawWork memory v1' clearly and concisely summarizes the main feature addition, follows Conventional Commits format, and accurately reflects the changeset's primary objective.
Linked Issues check ✅ Passed The changeset fully implements the core requirements from issue #519: memory file at ~/.pawwork/memory/MEMORY.md with Profile/Archive sections [#519], session startup Profile injection with precedence guardrails [#519], Archive scoped search via memory_search tool [#519], user-reviewed batch writes with redaction [#519], Settings UI for search/edit/delete/disable [#519], and keyword-only search without embeddings [#519].
Out of Scope Changes check ✅ Passed All code changes are directly aligned with #519 objectives: memory file handling, runtime Profile injection, Archive search, session-end review UI, Settings controls, and redaction logic. No unrelated refactors, unnecessary dependencies, or out-of-scope modifications are present in the changeset.
Description check ✅ Passed PR description is comprehensive, well-structured, and follows the template with all major sections filled: Summary clearly outlines the memory v1 implementation; Why explains the problem and design rationale; Related Issue (#519) is linked; Human Review Status is marked Pending with context; Review Focus highlights critical areas for review; Risk Notes detail file operations, privacy considerations, and platform concerns; How To Verify lists specific test results; Checklist is nearly complete.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slock/i519-memory-v1

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 8

🧹 Nitpick comments (6)
packages/opencode/src/session/prompt.ts (1)

1828-1848: ⚡ Quick win

Avoid reading MEMORY.md on every loop step.

This block runs inside the main loop, so Line 1830 can re-read disk multiple times in one turn. Consider loading once per runLoop cycle and reusing the value unless a write happens in-turn.

🤖 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 `@packages/opencode/src/session/prompt.ts` around lines 1828 - 1848, The memory
profile is being read from disk on every loop iteration because
MemoryService.create(...).read() is called inside the loop where memoryProfile
is computed; change this to load the MEMORY.md once per runLoop cycle and reuse
it for each iteration unless an in-turn write occurs. Implement a small cache in
the runLoop scope (e.g., a local variable like cachedMemoryProfile and
cachedMemoryVersion/timestamp) and replace the direct
MemoryService.create(...).read() usage in the memoryProfile computation with a
lookup from that cache; when any code path performs a write to memory during the
runLoop, invalidate/update the cache so subsequent loop iterations read the new
value. Ensure you keep the existing error handling (Effect.catch) and the same
string formatting around the profile when using the cached value.
packages/opencode/src/server/instance/memory.ts (2)

20-20: ⚡ Quick win

Consider a more specific schema for MemoryState.

Using z.any() bypasses validation and type safety. If the actual MemoryState shape is defined elsewhere (e.g., in @/memory/service), reference or reuse that schema here for consistency.

🛡️ Improve type safety

If MemoryService.read() returns a typed object, define its schema:

const MemoryState = z.object({
  disabled: z.boolean().optional(),
  status: z.enum(["ok", "safe_mode"]).optional(),
  // ... other fields from MemoryService.read()
}).meta({ ref: "MemoryState" })
🤖 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 `@packages/opencode/src/server/instance/memory.ts` at line 20, The MemoryState
schema currently uses z.any() which bypasses validation; replace it with a
concrete Zod schema that matches the actual shape returned by
MemoryService.read() (e.g., include fields like disabled:boolean, status: "ok" |
"safe_mode", and any other properties from MemoryService.read()) or import and
reuse the existing schema from the memory service module; update the MemoryState
declaration (symbol: MemoryState) to use that object/enum schema or the imported
schema so type safety and validation are enforced across usages of MemoryState
and MemoryService.read().

22-24: ⚖️ Poor tradeoff

Service instance is recreated on every request.

The service() helper creates a new MemoryService instance for each route handler call. If MemoryService.create() is lightweight and stateless, this is fine; otherwise, consider caching the instance per workspace.

Currently this matches the stateless pattern used elsewhere in the instance routes (e.g., creating clients/services inline per request). Unless MemoryService initialization is expensive, the existing approach is acceptable.

🤖 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 `@packages/opencode/src/server/instance/memory.ts` around lines 22 - 24,
service() currently calls MemoryService.create({ workspacePath:
Instance.directory }) on every invocation which recreates the service per
request; if MemoryService initialization is expensive you should cache it per
workspace instead. Modify the helper (service) to use a module-level cache
(e.g., a Map keyed by Instance.directory or a single variable if only one
workspace) so that service() returns the existing MemoryService instance when
present, otherwise calls MemoryService.create(...) and stores it; reference
MemoryService.create, service() and Instance.directory when implementing the
cache.
packages/app/src/components/memory/session-memory-review.tsx (1)

7-10: 💤 Low value

Type definition duplicated across files.

The local MemoryState type mirrors the shape returned by the backend. If this type is shared between frontend and backend (or generated from OpenAPI), consider importing from a shared location to ensure consistency.

If the SDK client already generates types from the OpenAPI schema, use those instead of a local definition.

🤖 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 `@packages/app/src/components/memory/session-memory-review.tsx` around lines 7
- 10, The local MemoryState type in session-memory-review.tsx duplicates the
backend/OpenAPI shape; remove this local definition and import the canonical
type instead (e.g., the generated SDK/OpenAPI type or the shared backend types)
to keep types consistent; update any references to MemoryState in this file (and
export/usage in components) to use the imported symbol so the frontend relies on
the single source of truth rather than the duplicated local declaration.
packages/opencode/src/tool/memory-search.ts (1)

20-22: ⚡ Quick win

Consider adding error context for searchArchive failures.

The Effect.promise wrapper will propagate any rejection from searchArchive as an Effect failure, but without additional context about what query failed. When debugging tool execution failures, knowing the query parameter can help.

🔍 Optional: Add error context
-        const result = yield* Effect.promise(() =>
-          MemoryService.create({ workspacePath: ins.directory }).searchArchive(params.query),
-        )
+        const result = yield* Effect.promise(() =>
+          MemoryService.create({ workspacePath: ins.directory }).searchArchive(params.query),
+        ).pipe(
+          Effect.mapError((error) => new Error(`Memory search failed for query "${params.query}": ${error}`))
+        )
🤖 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 `@packages/opencode/src/tool/memory-search.ts` around lines 20 - 22, The
searchArchive call inside Effect.promise can fail without context; update the
call that creates result (the Effect.promise wrapping
MemoryService.create(...).searchArchive(...)) to catch any rejection and rethrow
or reject with an error that includes params.query and ins.directory (or other
identifying info) so the Effect failure message contains the query and
workspace; use MemoryService.create, searchArchive, params.query and
ins.directory as the referenced symbols when adding the catch/augmenting the
error before returning from Effect.promise.
packages/app/src/components/settings-memory.tsx (1)

33-58: ⚡ Quick win

Consider adding operation-specific loading states.

The buttons are disabled only during the initial resource fetch (state.loading), not during save/reset/toggle/delete operations. This allows rapid repeated clicks and provides no visual feedback during operations.

♻️ Add operation loading signal
 const language = useLanguage()
 const sdk = useSDK()
 const [draft, setDraft] = createSignal("")
 const [deleteID, setDeleteID] = createSignal("")
+const [operationLoading, setOperationLoading] = createSignal(false)
 const [state, actions] = createResource(async () => {
   const result = await sdk.client.memory.get()
   const data = (result.data ?? {}) as MemoryState
   setDraft(data.content ?? "")
   return data
 })

 const refresh = () => void actions.refetch()

 const save = async () => {
+  setOperationLoading(true)
+  try {
     await sdk.client.memory.update({ memoryRawInput: { content: draft() } })
     showToast({ variant: "success", title: language.t("settings.memory.saved") })
     refresh()
+  } finally {
+    setOperationLoading(false)
+  }
 }

 // Apply same pattern to reset, toggle, deleteEntry
 
 // Then update button disabled states:
-<Button variant="primary" onClick={save} disabled={state.loading}>
+<Button variant="primary" onClick={save} disabled={state.loading || operationLoading()}>

Also applies to: 104-110

🤖 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 `@packages/app/src/components/settings-memory.tsx` around lines 33 - 58, Add
per-operation loading flags and use them to disable buttons and show feedback
during SDK calls: introduce boolean state variables like isSaving, isResetting,
isToggling, isDeleting and set them true before and false after each async call
in the save, reset, toggle, and deleteEntry functions (also handle
errors/finally to ensure flags are cleared). Update the UI bindings that
currently rely only on state.loading to also disable/reflect loading for the
corresponding operations (e.g., disable the Save button when isSaving, Reset
when isResetting, toggle control when isToggling, Delete when isDeleting) and
keep using draft(), deleteID(), showToast(), refresh() as before. Ensure toggle
uses the provided enabled param and that each flag is cleared in a finally block
so repeated clicks are prevented and users see operation-specific feedback.
🤖 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 `@packages/app/src/components/memory/session-memory-review.tsx`:
- Around line 32-34: The acceptProposal flow exits silently when the API returns
disabled or status === "safe_mode"; update the post-response branch (the code
that handles result from sdk.client.memory.acceptProposal and casts to
MemoryState) so that when state.disabled || state.status === "safe_mode" it (1)
clears the user's draft input (call the component's draft-clearing handler e.g.,
setProposalText or clearDraft), (2) dismisses/closes the review UI (invoke the
existing dismiss/close handler such as onDismiss or closeReview), and (3)
surfaces feedback to the user (use the app's notification mechanism, e.g.,
showToast/enqueueSnackbar/showNotification) explaining acceptance is blocked;
place these calls immediately inside the early-return branch that currently just
returns.

In `@packages/app/src/components/settings-memory.tsx`:
- Around line 33-37: Wrap each async action handler (save, reset, toggle,
deleteEntry) in a try/catch: perform the SDK call in try, call refresh() and
success showToast on success, and in catch log the error (console.error or
process logger) and show an error showToast with a descriptive message and the
error message; ensure any local state (e.g., loading flags) is cleared in
finally if used. Locate the functions save, reset, toggle, and deleteEntry in
settings-memory.tsx and apply the same pattern to each async SDK invocation so
users receive feedback on failures.
- Around line 22-29: The createResource wrapper around sdk.client.memory.get
lacks error handling; update the async function passed to createResource (the
one that sets state/actions) to wrap the call in try/catch, handle failures from
sdk.client.memory.get, and surface the error via the resource state or a new
signal so the UI can show feedback. Specifically, catch errors, log them, set a
safe fallback MemoryState (e.g., empty content) and setDraft("") (or appropriate
fallback), and return that fallback or rethrow to let the resource populate its
error state so consumers of state/actions can render an error message.

In `@packages/opencode/src/memory/memory.ts`:
- Around line 102-104: The current serialization of project scope metadata puts
input.appliesTo directly into the scope token (in the expression that assigns
scope in memory.ts), which is later parsed by splitting metadata on whitespace
and thus loses spaces in paths; update the serializer to encode or quote
appliesTo (e.g., wrap it in quotes or use JSON.stringify/encodeURIComponent)
when building the scope string (`const scope = ...` that uses input.appliesTo)
and update the corresponding parser that splits metadata (the metadata
split/parse logic that consumes the scope/applies_to token around lines
~145-165) to decode/unquote the value so paths like "C:\Users\Jane Doe\repo"
round-trip intact.
- Around line 112-116: The current parseEntries implementation splits the
archive using /\n(?=### )/, which breaks entries if their body contains markdown
"### " lines; update parseEntries to split or extract whole entries only at
headings that start at the beginning of a line. Replace the simple split with a
multiline-aware approach (e.g. use a regex like /(^### [\s\S]*?)(?=^### |\z)/gm
to match full entry blocks, or use archive.split(/\n(?=^### )/m)) so that only
headings at line-start (not inline/body subheadings) delimit entries; adjust
subsequent code that uses chunks/entries accordingly.

In `@packages/opencode/src/memory/service.ts`:
- Around line 66-73: The read-modify-write sequences (e.g., writeAtomic and
callers like appendAcceptedProposal and the other affected blocks) are
vulnerable to race conditions; serialize mutation operations by introducing an
in-process write queue or mutex: add a single Promise-based queue (or a simple
lock) that all functions performing read-modify-write await before calling
ensure(), reading the file, and invoking writeAtomic, then release the lock when
done; update writeAtomic and every caller listed (the appendAcceptedProposal
code path and the other ranges noted) to acquire the queue/lock before
performing the read-modify-write and to chain the promise so concurrent callers
are executed serially. Ensure the queue/lock is shared at module scope so all
mutations go through it.

In `@packages/opencode/src/tool/memory-search.ts`:
- Around line 1-33: The module is missing the canonical self-reexport so
consumers can import it as a namespace; add the module-as-namespace reexport at
the end of the file by appending the line export * as MemorySearch from
"./memory-search" (so code that expects to import the MemorySearch namespace
alongside the exported MemorySearchTool will work).

In `@packages/opencode/test/memory/pawwork-memory.test.ts`:
- Around line 76-77: Replace manual temporary directory creation using
fs.mkdtemp with the repo tmpdir test fixture: import tmpdir from the fixture
(fixture/fixture.ts) and use "await using (const dir = await tmpdir()) { ... }"
so the directory is auto-cleaned; inside the using block pass dir to
MemoryService.createForTest({ home: dir, workspacePath: "/repo/pawwork" })
(update the three occurrences around the current usage at
MemoryService.createForTest calls). Ensure the test file uses the tmpdir fixture
import and the await using pattern for automatic cleanup and consistent setup.

---

Nitpick comments:
In `@packages/app/src/components/memory/session-memory-review.tsx`:
- Around line 7-10: The local MemoryState type in session-memory-review.tsx
duplicates the backend/OpenAPI shape; remove this local definition and import
the canonical type instead (e.g., the generated SDK/OpenAPI type or the shared
backend types) to keep types consistent; update any references to MemoryState in
this file (and export/usage in components) to use the imported symbol so the
frontend relies on the single source of truth rather than the duplicated local
declaration.

In `@packages/app/src/components/settings-memory.tsx`:
- Around line 33-58: Add per-operation loading flags and use them to disable
buttons and show feedback during SDK calls: introduce boolean state variables
like isSaving, isResetting, isToggling, isDeleting and set them true before and
false after each async call in the save, reset, toggle, and deleteEntry
functions (also handle errors/finally to ensure flags are cleared). Update the
UI bindings that currently rely only on state.loading to also disable/reflect
loading for the corresponding operations (e.g., disable the Save button when
isSaving, Reset when isResetting, toggle control when isToggling, Delete when
isDeleting) and keep using draft(), deleteID(), showToast(), refresh() as
before. Ensure toggle uses the provided enabled param and that each flag is
cleared in a finally block so repeated clicks are prevented and users see
operation-specific feedback.

In `@packages/opencode/src/server/instance/memory.ts`:
- Line 20: The MemoryState schema currently uses z.any() which bypasses
validation; replace it with a concrete Zod schema that matches the actual shape
returned by MemoryService.read() (e.g., include fields like disabled:boolean,
status: "ok" | "safe_mode", and any other properties from MemoryService.read())
or import and reuse the existing schema from the memory service module; update
the MemoryState declaration (symbol: MemoryState) to use that object/enum schema
or the imported schema so type safety and validation are enforced across usages
of MemoryState and MemoryService.read().
- Around line 22-24: service() currently calls MemoryService.create({
workspacePath: Instance.directory }) on every invocation which recreates the
service per request; if MemoryService initialization is expensive you should
cache it per workspace instead. Modify the helper (service) to use a
module-level cache (e.g., a Map keyed by Instance.directory or a single variable
if only one workspace) so that service() returns the existing MemoryService
instance when present, otherwise calls MemoryService.create(...) and stores it;
reference MemoryService.create, service() and Instance.directory when
implementing the cache.

In `@packages/opencode/src/session/prompt.ts`:
- Around line 1828-1848: The memory profile is being read from disk on every
loop iteration because MemoryService.create(...).read() is called inside the
loop where memoryProfile is computed; change this to load the MEMORY.md once per
runLoop cycle and reuse it for each iteration unless an in-turn write occurs.
Implement a small cache in the runLoop scope (e.g., a local variable like
cachedMemoryProfile and cachedMemoryVersion/timestamp) and replace the direct
MemoryService.create(...).read() usage in the memoryProfile computation with a
lookup from that cache; when any code path performs a write to memory during the
runLoop, invalidate/update the cache so subsequent loop iterations read the new
value. Ensure you keep the existing error handling (Effect.catch) and the same
string formatting around the profile when using the cached value.

In `@packages/opencode/src/tool/memory-search.ts`:
- Around line 20-22: The searchArchive call inside Effect.promise can fail
without context; update the call that creates result (the Effect.promise
wrapping MemoryService.create(...).searchArchive(...)) to catch any rejection
and rethrow or reject with an error that includes params.query and ins.directory
(or other identifying info) so the Effect failure message contains the query and
workspace; use MemoryService.create, searchArchive, params.query and
ins.directory as the referenced symbols when adding the catch/augmenting the
error before returning from Effect.promise.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 033cad88-f480-4f6a-8444-b963cd9f34ac

📥 Commits

Reviewing files that changed from the base of the PR and between 0782d4b and a9383fd.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/v2/gen/sdk.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (19)
  • packages/app/src/components/memory/session-memory-review.tsx
  • packages/app/src/components/settings-memory.test.ts
  • packages/app/src/components/settings-memory.tsx
  • packages/app/src/components/settings-page.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/session-main-view.tsx
  • packages/app/src/shell-frame-contract.test.ts
  • packages/core/src/pawwork-home.ts
  • packages/opencode/src/memory/memory.ts
  • packages/opencode/src/memory/proposal.ts
  • packages/opencode/src/memory/service.ts
  • packages/opencode/src/server/instance/index.ts
  • packages/opencode/src/server/instance/memory.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/tool/memory-search.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/test/memory/pawwork-memory.test.ts

Comment thread packages/app/src/components/memory/session-memory-review.tsx Outdated
Comment thread packages/app/src/components/settings-memory.tsx
Comment thread packages/app/src/components/settings-memory.tsx
Comment thread packages/opencode/src/memory/memory.ts Outdated
Comment thread packages/opencode/src/memory/memory.ts
Comment thread packages/opencode/src/memory/service.ts
Comment thread packages/opencode/src/tool/memory-search.ts Outdated
Comment thread packages/opencode/test/memory/pawwork-memory.test.ts Outdated

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a comprehensive 'Memory' feature to PawWork, enabling persistent user context across sessions via a structured MEMORY.md file. The implementation includes a MemoryService for file operations, a new settings tab for configuration and raw editing, and a SessionMemoryReview component for saving session-specific insights. Additionally, a memory_search tool allows the AI to retrieve historical context, and a redaction mechanism is included to protect sensitive information. Review feedback identifies potential issues with the regular expressions used for parsing and deleting memory entries, specifically regarding how they handle markdown headings within entry bodies, and provides suggestions for more robust matching.

Comment thread packages/opencode/src/memory/memory.ts Outdated
Comment thread packages/opencode/src/memory/service.ts Outdated
@Astro-Han
Astro-Han merged commit 44339e3 into dev May 10, 2026
20 checks passed
@Astro-Han
Astro-Han deleted the slock/i519-memory-v1 branch May 10, 2026 14:42
Astro-Han added a commit that referenced this pull request May 10, 2026
Summary:
- Remove the session-end SessionMemoryReview confirmation UI and its mount path so PawWork memory no longer asks users whether to save a session.
- Remove the old memory proposal accept/review API surface, proposal redaction helper, and generated SDK methods that only supported the confirmation flow.
- Simplify MEMORY.md handling for v1: Profile remains the only section loaded automatically, Archive becomes freeform markdown that is searched/read only on demand, and Settings keeps view/edit/disable/reset control.
- Update the memory prompt so the assistant maintains MEMORY.md silently at reply/task closeout, records only explicit stable facts, avoids inference from user tasks, updates conflicts instead of appending contradictions, and never writes sensitive data.

Root cause:
- PR #520 implemented memory as a user-confirmed session review panel, but the product direction is silent background memory. That UI surfaced an unnecessary confirmation step and made memory feel like an interruptive modal workflow instead of an assistant-maintained local notebook.

Scope boundary:
- This is not a new memory search system, structured memory schema, hidden patch protocol, or dedicated memory tool.
- Archive is intentionally not injected into every session to avoid context growth; the prompt only tells the assistant to grep/read it when historical context is actually needed.
- Profile remains short and startup-loaded. Archive remains local history.

Review follow-up handled:
- GLM first-pass verified the issue #526 spec coverage and found no P0/P1 blockers.
- Opus second-pass identified prompt gaps for first-write onboarding and no extrapolated preferences; both were patched.
- GPT-X final review confirmed no over-engineering remnants, no unresolved P1/P2 risks, and complete spec coverage.
- CodeRabbit and Gemini feedback was addressed: archive grep output is capped, the explicit remember acknowledgement example is English, deleteEntry no longer stops on arbitrary markdown headings, and prompt examples are fully English.

Verification:
- bun --cwd packages/opencode test test/memory/pawwork-memory.test.ts
- bun --cwd packages/app test:unit -- src/shell-frame-contract.test.ts src/components/settings-memory.test.ts
- bun --cwd packages/app test:e2e -- e2e/settings/settings-memory.spec.ts
- bun --cwd packages/opencode typecheck
- bun --cwd packages/app typecheck
- bun --cwd packages/sdk/js typecheck
- bun --cwd packages/sdk/js build
- git diff --check
- GitHub checks passed: CI, CodeQL, desktop smoke, e2e artifacts, commit lint, PR title lint, dependency review, CodeRabbit.
- GitHub reviewThreads unresolved count verified as 0 before merge.

Closes #526
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.

[Feature] Add transparent cross-session memory for PawWork

1 participant