Skip to content

feat: add per-folder notebook memory with mid-task note attach and interactive review - #43058

Closed
SaeedAqamiri wants to merge 11 commits into
anomalyco:devfrom
SaeedAqamiri:notebook-memory
Closed

SaeedAqamiri wants to merge 11 commits into
anomalyco:devfrom
SaeedAqamiri:notebook-memory

Conversation

@SaeedAqamiri

@SaeedAqamiri SaeedAqamiri commented Aug 17, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #43068 (per-folder notebook memory); also related to #16077 (persistent session memory). Implements per-project memory as .note.yaml files with notes_get/notes_commit, mid-task leaf-note attach, and a save-learnings nudge.

Type of change

  • New feature

What does this PR do?

Adds per-folder notebook memory for the agent. Each folder can carry a .note.yaml with a folder summary, per-file entries, and cross-file relations, grounded in based_on source fingerprints with freshness badges (✓/⚠/✗).

  • notes_get reads the ancestor-chain memory for a path, or keyword-searches all notebooks; bare-directory targets are handled as leaves (fixes the old "No memory on this path yet" for directories) and relations-only notebooks are no longer skipped.
  • notes_commit writes summaries/entries/relations with PermissionV2 (write-notes) approval and a real diff, never auto-approved even in --auto.
  • A concreteness gate rejects summaries that name no concrete symbols (classes/functions/modules), so saved notes are usable instead of vague.
  • NotebookAttach prepends the relevant per-file note to read/edit/write/apply_patch output on first touch in a session, so leaf memory reaches the model mid-task. This is the opinionated part, so it is opt-out via experimental.notebook.attach = false.
  • The injected digest states it only samples memory (up to 2 entries/folder) and points the model at notes_get before answering general project questions.
  • Save-learnings nudge after exploring without saving; clears on commit.

Why attach in the SessionRunner instead of the registry: ToolRegistry deliberately has no FSUtil/Location deps and is a standalone unit-test boundary; the Location-scoped runner already owns the step lifecycle and notebook evidence, so prepending a bounded text note to the published tool result fits there with no new cross-cutting seams.

How did you verify your code works?

  • bun typecheck green in core, app, session-ui, tui.
  • Core tests for the concreteness gate, prependNote, directory-target notes_get, and notebook tools/memory/evidence all pass (61 tests), plus the portable V1 plugin suite.

Screenshots / recordings

N/A here — UI adds a save-learnings button and a permission diff view; can attach once the CI build is visible.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Splits: interactive review helper in #43059, locale keys in #43066.

Add a first-class notebook memory to core in packages/core/src/notebook:
a per-folder .note.yaml store (pure model + FSUtil-backed file access), a
core/notebook SystemContext source that folds a conservative memory digest
into the system baseline, and built-in notes_get/notes_commit tools.

Wires the memory source into location-services and the tools into
BuiltInTools. Shared pure notebook logic underpins both this native (V2)
implementation and the external V1 notes-plugin.
Give the native notebook memory the same save-reminder the V1 plugin has. A
per-session evidence tracker records exploration (read/grep/glob or tools with
a filePath/path arg) in the runner and silences on notes_commit; the save
nudge is injected alongside the system baseline on subsequent turns, honoring
a cooldown. Adds focuses evidence tests.
- Add notes_get and notes_commit tools for per-session knowledge persistence
- Add notebook system context source with freshness badges
- Add evidence tracking to prevent empty or repetitive commits
- Add Save Learnings button to message UI (TUI and web)
- Propagate onSaveLearnings through component chain
- Add write-notes permission guard (never auto-approved)
- Add bookmark icon to v1 and v2 icon sets
- Add i18n keys for saveLearnings command in all locales
- Add permissions diff display in session permission dock
- Exclude write-notes from auto-respond in permission context
- Add /record-notes (TUI) and /save (web) slash commands
- Add deep quality guard: reject empty, non-English, or short commits
…otes to tool output

- Add hasConcreteAnchor to the notes_commit quality gate so entry/relation
  summaries that name no real symbols are rejected as vague instead of saved.
- Add NotebookAttach service (attach.ts): noteFor() surfaces a per-file notebook
  note once per session on first read/edit/write/apply_patch; prependNote()
  merges it into a single-text ToolOutput preserving the text result type.
- SessionRunner prepends the note to the published toolResult so leaf memory
  reaches the model mid-task (registry stays free of FSUtil/Location deps).
- Fix notes_get naked-directory path mode: descend into the target as a leaf
  and stop skipping relations-only notebooks.
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Aug 17, 2026
… questions

The injected notebook digest only samples up to 2 entries per folder and
never instructed the model to pull the full per-file memory, so overview
questions (what a project is, how to run it, its progress) were answered by
re-exploring code instead of consulting notes_get. Make the digest say it is
only a sample and direct the model to call notes_get (task or path) first —
cheap and local — at task start and before any general project question.
- SaveLearningsButton: label is a plain string, not JSX; drop String() on aria-label.
- session-permission-dock: remove a redundant type assertion on metadata.diff.
- notebook tools: drop an unnecessary template expression in save labels.
- notebook test: use a template literal instead of concat.
Locale translations move to a separate PR (anomalyco#43066) to shrink the memory
PR diff; English keys remain because the app/ui i18n types derive from en
and the runtime merges { ...en, ...locale } with English as the fallback.
…ok.attach

The mid-task note prepending is the most opinionated part of the feature, so
give it an explicit knob: experimental.notebook.attach = false disables only
the auto-attach; notes_get/notes_commit and the memory digest are unaffected.
Default stays enabled.
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Aug 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.
Diff too large for automated review — recommend human review.

@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

  • packages/app/src/context/permission.tsx:322 — good security posture: write-notes is excluded from all auto-approve paths so notebook mutations always get human review, matching the PR's interactive-review intent.
  • packages/app/src/pages/session.tsx:2119 (onSaveLearnings) — the instruction prompt is hardcoded inline and names the notes_commit tool literally; if the tool is ever renamed or gated behind a plugin, this silently degrades to a confusing no-op prompt. Consider centralizing the prompt string (and ideally asserting the tool exists) somewhere testable.
  • Design note: .note.yaml per folder plus a root-level gitignore entry here only covers this repo; users' own projects need their own ignore rule for committed notebooks — worth documenting since memory files may contain session-derived content some teams won't want committed.
  • The diff was too large to fully review (34 files); I focused on the app/permission/i18n surfaces. Recommend dedicated human attention on the server-side notes_commit tool validation and YAML parsing paths (js-yaml load of untrusted-ish files can construct unexpected objects unless using safe load / schema validation).
  • i18n keys added for en; assuming other locales follow via the translation pipeline (see feat(i18n): add notebook memory keys across locales #43066 pattern).

… save prompt

- parseNotebook now loads .note.yaml with CORE_SCHEMA so hostile JS tags
  (!!js/function etc.) cannot be instantiated; add a regression test.
- Extract the shared RECORD_NOTES_PROMPT used by the /save command and the
  save-learnings button so a rename/gating of notes_commit cannot silently
  degrade one path while the other keeps a stale tool name.
@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

@SaeedAqamiri

Copy link
Copy Markdown
Author

Still actively maintained, and the automated review feedback has been addressed since this was opened:

  • notebook parsing now uses a safe YAML schema (CORE_SCHEMA) so hostile JS tags can't be instantiated, with a regression test;
  • the shared save-learnings prompt is centralized instead of hardcoded inline;
  • the mid-task note attach is opt-out via experimental.notebook.attach.
    Typecheck and tests pass. This is the core of the per-folder notebook memory proposal in [FEATURE] Per-folder notebook memory (notes_get/notes_commit) for the agent #43068. Could a maintainer please reopen it for human review?

@SaeedAqamiri

Copy link
Copy Markdown
Author

Reopen request: this PR was auto-closed by the stale-PR cleanup bot (age + reactions), not by a maintainer. The implementation is still current: safe YAML parsing (CORE_SCHEMA) with a regression test, a centralized save-learnings prompt, and mid-task attach opt-out via experimental.notebook.attach. Typecheck and bun test pass from packages/core.

Could a maintainer reopen it for human review? Happy to split it into smaller PRs if that is easier to land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Per-folder notebook memory (notes_get/notes_commit) for the agent

2 participants