feat(memory): Add passive turn extraction#628
Conversation
Add a post-delivery observeTurn plugin hook and wire it through local and Slack turn delivery so trusted plugins can inspect successful turns without affecting user-visible delivery. Teach the memory plugin to extract public, durable facts from user-authored turn text, skip memory-management tool turns to avoid duplicate writes, and store accepted memories through the existing context-bound memory store. Update the memory specs and evals to cover organic passive extraction, canonical stored content, and follow-up recall. Co-Authored-By: GPT-5 Codex <codex@openai.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Keep passive memory observation scoped to supported local and public Slack contexts, wire resumed Slack success through observation, and type observation contexts with platform-coupled runtime context. Remove the telemetry mock from resume behavior tests and align memory specs with the explicit-review and passive-extraction split. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Expose normalized plugin source helpers with compact pub/priv source typing so trusted plugins can rely on shared source visibility and stable source keys instead of rebuilding platform-specific checks. Update memory passive extraction, runtime callers, tests, specs, and source-mode package exports around that contract. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Keep memory extraction prompt examples neutral and make the model-facing output field explicitly canonical. Pass assistant responses as rejection context for passive extraction so follow-up and advice turns are less likely to create duplicate memories. Fix eval assertions to read memories for the active test thread and remove the package development export condition that made CI load TypeScript from node_modules. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Pass successful tool results into post-turn observation so failed explicit memory attempts do not block passive extraction. Derive Slack memory source privacy from explicit runtime context instead of channel id prefixes, and observe the same queued text the agent answered. Also keep requester memories in canonical stored-fact form and leave deterministic listMemories behavior in storage tests rather than evals. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Persist the normalized run source through auth, timeout, and continuation state so resumed Slack turns reuse the original source instead of reconstructing one. Require source at Slack resume boundaries and tighten tests to prove stored sources are forwarded. Remove deterministic memory tool mechanics from eval assertions so evals stay focused on model-facing behavior. Co-Authored-By: GPT-5 Codex <codex@openai.com>
| const pendingMessage = optionalString(value.pendingMessage); | ||
| if (pendingMessage && !source?.success) { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
OAuth state rejects missing source
Medium Severity
OAuth state parsing now treats payloads with a pendingMessage but no normalized source as invalid, and pending-message resume also requires source. OAuth flows started before this change (or any path that stored pending text without source) can no longer be parsed or resumed after authorization.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 24882f3. Configure here.
There was a problem hiding this comment.
Intentional hard cutover for this branch. OAuth pending-message state now requires a normalized source because source/destination context is the runtime boundary contract; we are not adding compatibility fallback for old in-flight state that was created before this schema change.
— Claude Code
Normalize old dispatch records that only stored destination by deriving a source through the plugin API helper. This keeps recovery and heartbeat paths able to read pre-source records without accepting malformed destinations. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Pending-message OAuth state now requires a source so resumed Slack runs can reuse the original context. Update the unit fixture to match the runtime contract. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Keep passive extraction on a single canonical memory shape while preserving DB-backed eval coverage. Tighten source context handling, memory model selection, and eval harness behavior so the memory slice follows the trusted plugin and agentic-semantics policies. Co-Authored-By: GPT-5 Codex <gpt-5-codex@openai.com>
| errorMessage: "Stored Slack source missing for continuation", | ||
| }); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Resume fails without stored source
Medium Severity
Slack continuation and OAuth resume paths now require activeSessionRecord.source and fail the session when it is missing. Turn records created before this change only persisted destination metadata, so in-flight auth pauses, timeout continuations, or OAuth resumes can terminate with “Stored Slack source missing” instead of completing the user’s turn.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit b069491. Configure here.
There was a problem hiding this comment.
Intentional hard cutover for this branch. Resumed runs now require the stored source rather than reconstructing it from destination metadata; old in-flight records without source are not supported by this migration slice.
— Claude Code
Only explicit memory mutation tools should suppress passive extraction. Recall tools can run during an ordinary turn and still leave durable facts for the memory agent to extract afterward. Co-Authored-By: GPT-5 Codex <gpt-5-codex@openai.com>
| value: unknown, | ||
| ): DispatchRecord | undefined { | ||
| const candidate = | ||
| value && |
There was a problem hiding this comment.
Legacy dispatch records unparsable
Medium Severity
Removing the parse-time backfill that copied destination into missing source means older persisted dispatch records without a source field no longer parse, so recovery and callbacks can lose in-flight plugin dispatches after deploy.
Reviewed by Cursor Bugbot for commit 6f70be8. Configure here.
There was a problem hiding this comment.
Intentional hard cutover for this branch. Plugin dispatch records now require a normalized source as part of the runtime boundary contract; we are not preserving parse-time fallback for older persisted dispatch records that predate source storage.
— Claude Code
Slack sources expose runtime-owned Slack address fields while the opaque Junior conversation id is passed separately on the plugin context. Update the hook assertion to match the strict source schema. Co-Authored-By: GPT-5 Codex <gpt-5-codex@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e3a5882. Configure here.
| assistantText: reply.text, | ||
| toolCalls: reply.piMessages | ||
| ? getSuccessfulToolCalls(reply.piMessages) | ||
| : reply.diagnostics.toolCalls, |
There was a problem hiding this comment.
Turn tool history skips passive memory
High Severity
After a successful turn, passive memory observation builds the mutation-tool skip list from getSuccessfulToolCalls over the full reply.piMessages session, not just the current turn. An earlier successful createMemory or removeMemory in the same conversation makes later organic turns skip passive extraction even when this turn did not use those tools.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit e3a5882. Configure here.


Junior memory now learns from completed turns through the plugin observation hook, in addition to explicit memory tools. Core observes successful local replies, normal Slack replies, and resumed Slack replies; the memory plugin runs one structured internal agent over bounded user-authored context, ignores turns that already used memory-management tools, and stores accepted public/shareable facts using runtime-derived requester and source authority.
The plugin source context is now normalized before it reaches hooks and tools.
Sourcecarries platform, privacy type, conversation id, and platform coordinates, with helpers for construction, private-source checks, and stable source keys. Memory uses that context to allow local development and stable public sources while skipping private or unstable non-local sources before extraction.Resumable Slack runs now persist the normalized source through auth, timeout, and continuation state. OAuth/MCP callbacks and agent continuation reuse the stored run source instead of reconstructing source privacy from channel ids, which keeps passive memory behavior consistent after resumed public-channel turns.
The memory specs and eval policy were tightened around public-only memory, agentic extraction, and eval boundaries. Memory evals now cover organic passive learning, automatic recall injection, and natural-language forget behavior while avoiding deterministic list/search/tool-mechanics checks.