Skip to content

feat(memory)!: Move Memory into Junior core - #1889

Open
dcramer wants to merge 6 commits into
mainfrom
memory-core
Open

dcramer wants to merge 6 commits into
mainfrom
memory-core

Conversation

@dcramer

@dcramer dcramer commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Memory now ships inside @sentry/junior as a core feature, and the @sentry/junior-memory package is gone. An app that installs only @sentry/junior gets the Memory tools, automatic recall, passive extraction, memory/* events, the Memories page and conversation tab, REST routes, CLI, and operational report. It keeps all of its existing Memory data.

Core features use the plugin registration contract, but core registers them directly. They stay out of plugin and package discovery, every hook consumer reads one list of core features and plugins, and a plugin cannot claim a core feature name. Briefs moved onto the same path.

Breaking changes, with migration in the same release

Operators move off the plugin when they install this version:

  • Remove memoryPlugin() from defineJuniorPlugins(...) and @sentry/junior-memory from JUNIOR_PLUGIN_PACKAGES, then uninstall the package. While the plugin set still names it, startup and junior upgrade stop with a message that explains these steps.
  • Move memoryPlugin({ ... }) options to createApp({ memory: { modelId, disableRecall, disableExtraction } }). enabled: false is an emergency switch. AI_MEMORY_MODEL still works.
  • Import Memory stores, schemas, and types from @sentry/junior/memory.
  • REST clients move from /api/plugins/memory/* to /api/memory/*. The paths below the prefix are unchanged. The dashboard already uses the new routes.
  • vector and btree_gin become baseline database requirements. junior upgrade enables them under the migration lock and stops with a prerequisite error when it cannot.

Rollback means deploying the previous release with the plugin. It reads the same tables.

Review focus: 0044_memory_core.sql

Data safety depends on this migration. It adopts the Memory tables in one core journal entry and does not copy the 12 plugin journal entries. On a fresh database it creates the final schema. On an existing database it checks the current schema and data, then applies only the missing legacy transitions (0001–0011), in their original order. Data rewrites run only while legacy rows or event payloads remain. The conversation_id backfill runs only when the column is missing, so current rows without a Conversation stay unchanged. The legacy plugin journal stays in the database as audit state.

packages/junior/scripts/rehearse-memory-upgrade.ts runs the upgrade against real Postgres. It covers a fresh database and every meaningful legacy prefix, and it checks that each path ends with the same schema and data and that a rerun changes nothing. The plugin's 12 migrations stay in scripts/fixtures/legacy-memory-migrations/ so the rehearsal can build those states. All cases pass locally.

Memory adds recall and extraction model calls to every Turn, so tests run it only where they measure it. Core tests that run Turns use createTestApp(), which turns Memory off, and evals turn it on only through memoryEvals.

The eval harness also stops a timed-out scenario before the next test starts, instead of abandoning its agent run. Event evals work again: ingestEvent now passes its clock into the event's receivedAtMs, so eval fixtures can arrive after the event burst window closed.

Refs #1861

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Sep 23, 2026 7:57pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

4 screenshot changes — 4 changed · 0 added · 0 removed

Review screenshots in Frameshift

Automations · Desktop
Automations · Desktop
Changed
Automations · Mobile
Automations · Mobile
Changed
Automations List · Desktop
Automations List · Desktop
Changed
Automations List · Mobile
Automations List · Mobile
Changed

@sentry-junior sentry-junior 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.

The core-name reservation in src/chat/plugins/agent-hooks.ts only checks runtime registrations. pluginRuntimeRegistrationsFromPluginSet() filters out manifest-only and CLI-only registrations before that check, and registerPluginManifest() does not check reserved names. A declarative plugin can therefore still claim memory or briefs and enter provider/package discovery alongside the core feature.

I reproduced this with defineJuniorPlugins([{ manifest: { name: "memory", displayName: "Other Memory", description: "Not core memory" } }]): runtime validation passed, and the catalog returned memory as an installed provider. Put the reservation check at the shared manifest-registration boundary as well, so inline and YAML manifests both reject these names. Extend the existing registration coverage to include a manifest-only plugin.

The database adoption rehearsal passed all 13 cases on real Postgres. The focused API, host-wiring, core-registration, and CLI tests also passed (19 tests). I reviewed the latest eval change at a530f8014; the broader local test run did not finish within 200 seconds, so I am not claiming a full local suite pass.

@dcramer

dcramer commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Fixed in 6483374. registerPluginManifest() now rejects reserved core feature names, so inline and YAML manifests can no longer claim memory or briefs. I extended the existing reservation test in tests/unit/plugins/core-features.test.ts with your manifest-only repro sent through the real plugin catalog. It fails without the fix and passes with it.

dcramer and others added 5 commits September 23, 2026 11:02
Memory now ships inside @sentry/junior as a core feature. createApp()
registers it for every app, so an app with only @sentry/junior gets the
tools, recall, extraction, events, Memories page, REST routes, CLI, and
operational report without a plugin.

- Core features use the plugin registration contract but stay out of
  plugin and package discovery. Every hook consumer reads one list of
  core features and plugins. Plugins cannot claim a core feature name.
- createApp({ memory }) takes modelId, disableRecall, disableExtraction,
  and an emergency enabled: false.
- Core serves /api/memory/* and keeps /api/plugins/memory/* as an alias
  for one compatibility window. The dashboard uses the core routes.
- Migration 0044_memory_core adopts the Memory tables. It creates the
  final schema on a fresh database and applies only the legacy plugin
  transitions that are missing. It does not rewrite data on a database
  that already ran every plugin migration. The legacy plugin journal stays
  as audit state.
- junior upgrade enables vector and btree_gin under the migration lock
  and stops with a prerequisite error when it cannot.
- @sentry/junior-memory becomes a compatibility package that re-exports
  @sentry/junior/memory. Core ignores memoryPlugin() and the package in
  JUNIOR_PLUGIN_PACKAGES with one warning.
- junior init, the example app, and evals no longer install the plugin.
  Memory docs move to concepts/memory.

scripts/rehearse-memory-upgrade.ts rehearses the upgrade against real
Postgres for a fresh database and every legacy plugin prefix.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Memory ships inside @sentry/junior, so apps move off the plugin in the
same release instead of through a compatibility window. The upgrade keeps
all Memory data: the core migration adopts the tables in place.

- Delete packages/junior-memory and its CI, Craft, release, and package
  check wiring.
- A plugin set or JUNIOR_PLUGIN_PACKAGES that still names
  @sentry/junior-memory stops startup and junior upgrade with a
  migration message.
- Remove the /api/plugins/memory alias. REST clients use /api/memory.
- Keep the 12 legacy plugin migrations as rehearsal fixtures so the
  upgrade from every legacy state stays testable.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Rebuild pnpm-lock.yaml from main so the only change is removing
  @sentry/junior-memory. Earlier regeneration had bumped unrelated
  transitive versions.
- Describe Memory as core in moved comments and test names.
- Rename generic test plugins that still used Memory names to notes,
  since memory is a reserved core feature name.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Memory is a core feature, so evals install it for every scenario. Passive
extraction adds model calls and a background task after each completed
turn. On the OAuth resume path that task publishes to a real Vercel Queue,
which the eval HTTP mock rejects.

The harness now builds core features with extraction off unless a
scenario sets the memory_extraction override. Recall stays on everywhere.
The Memory eval suites opt in through memoryEvals.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Runtime validation only saw registrations with runtime hooks, so an inline
or YAML manifest could still claim memory or briefs and enter the plugin
catalog next to the core feature. Check reserved names at the shared
manifest registration boundary as well.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ests

Timed-out eval scenarios kept running after Vitest moved on. The harness
raced each agent run against its abort signal and abandoned the run, so
its later teardown reset the plugin catalog and core features under the
next test. Agent runs already stop on their signal, so the harness now
awaits them, and the eval setup waits for an aborted scenario to finish
teardown before the next test starts. Remove the unused taskTimeout race.

Memory adds recall and extraction model calls to every Turn. Evals now
run it only through memoryEvals, and core tests that run Turns use
createTestApp(), which turns it off. Drop the per-test AI Gateway
embedding fake that only those tests needed.

Event evals could not pass since the event burst window landed: the
worker deferred each event Turn and the eval queue retried until it gave
up. ingestEvent now passes its clock into the event's receivedAtMs, and
eval event fixtures arrive after the window closed. Integration tests
keep owning the window.

Refs #1861

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – junior-docs — b5739069 Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant