Skip to content

Add Phase 5A Memory Autopilot for auto-retrieve and auto-candidate queue - #105

Merged
besfeng23 merged 2 commits into
mainfrom
codex/add-phase-5a-memory-autopilot
Jun 28, 2026
Merged

Add Phase 5A Memory Autopilot for auto-retrieve and auto-candidate queue#105
besfeng23 merged 2 commits into
mainfrom
codex/add-phase-5a-memory-autopilot

Conversation

@besfeng23

@besfeng23 besfeng23 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Motivation

  • Make Pandora automatically assist ChatGPT by deterministically deciding when to retrieve context before answers and when to queue durable memory candidates after exchanges while keeping all dangerous persistence gated.
  • Preserve strict safety posture: no public memory read/write, model calls/embeddings/semantic retrieval off by default, secrets blocked, and real_life/au namespaces never mixed.
  • Provide a shared, testable API surface (REST + MCP) so ChatGPT and MCP clients can call a single adaptive_turn flow for both pre_answer retrieval and post_answer candidate queuing.

Description

  • Added runtime autopilot flags and mode parsing in lib/config/pandora-runtime-safety-config.ts, including PANDORA_ENABLE_MEMORY_AUTOPILOT, PANDORA_MEMORY_AUTOPILOT (off|suggest|queue|capture_low_risk), PANDORA_AUTO_RETRIEVE, PANDORA_AUTO_CANDIDATE_QUEUE, PANDORA_AUTO_CAPTURE_LOW_RISK, and PANDORA_SENSITIVE_MEMORY_REQUIRES_APPROVAL (defaults to true unless explicitly set to "false").
  • Implemented deterministic Autopilot policy service lib/services/memory-autopilot-policy-service.ts with inferMemoryNamespace, shouldAutoRetrieveMemory, and shouldAutoCreateMemoryCandidate that encode the retrieval/candidate trigger lists, namespace rules, sensitivity/review heuristics, and secret detection fallback.
  • Added shared adaptive turn orchestration lib/services/memory-adaptive-turn-service.ts that exposes safe pre_answer and post_answer behaviors (redaction, safety snapshot, mode resolution), queues candidates via createCandidatesFromSession (writes only to memory_capture_candidates), and returns auto_capture_not_implemented for capture_low_risk mode in Phase 5A.
  • Exposed REST endpoint POST /api/memory/adaptive/turn at app/api/memory/adaptive/turn/route.ts with Zod validation, namespace inference, withBridge auth/gates, pre-answer retrieval (uses buildAdaptiveChatGptContext), and post-answer candidate queueing (requires capture gate to write).
  • Added MCP tool support via adaptive_turn in lib/services/pandora-mcp-tools.ts and registered in lib/services/pandora-mcp-server.ts with the same shared logic and MCP capture gating (PANDORA_ENABLE_MCP_CAPTURE required to write candidates via MCP).
  • Updated OpenAPI (public/pandora-memory-openapi.json) and docs (docs/pandora-memory-autopilot.md, docs/pandora-chatgpt-adaptive-instructions.md) to document purpose, env flags, safety model, and recommended rollout posture.
  • Tests: added tests/phase5-memory-autopilot.test.ts covering policy, endpoint gate behavior, queue-only persistence, namespace separation, secrets handling, and MCP gating; preserved existing Phase 4C/other unit tests.

Testing

  • Ran npm run typecheck which completed successfully with no type errors.
  • Ran npm run lint which passed but surfaced unrelated pre-existing warnings; no new lint errors were introduced.
  • Ran npm run test and the full test suite passed including the new tests/phase5-memory-autopilot.test.ts (all tests green).
  • Ran npm run build and produced a successful Next.js build (build completed with informational warnings about Edge runtime and existing project telemetry, but no errors).

Codex Task

Summary by CodeRabbit

  • New Features

    • Added an adaptive memory turn flow that can handle pre-answer retrieval checks and post-answer candidate queueing.
    • Introduced a new API endpoint and MCP tool for adaptive memory turns.
    • Added support for configurable memory autopilot modes and related runtime safety controls.
  • Bug Fixes

    • Improved validation and error reporting for malformed requests.
    • Added clear access-blocked responses when required memory features are disabled.
  • Documentation

    • Updated memory autopilot guidance and safety instructions, including review requirements for sensitive content.

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
memory Ready Ready Preview, Comment Jun 28, 2026 6:11pm

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@besfeng23, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 23 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c027ed1-354e-4653-a66b-28e226d06964

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb58ae and 9b138e2.

📒 Files selected for processing (1)
  • lib/services/pandora-mcp-server.ts
📝 Walkthrough

Walkthrough

Introduces Phase 5A memory autopilot: a heuristic policy service decides whether to auto-retrieve memory or queue memory candidates per turn; a new adaptive turn service orchestrates those decisions; a REST endpoint (POST /api/memory/adaptive/turn) and MCP tool (adaptive_turn) expose the two-phase flow with runtime gate enforcement; runtime safety config gains new autopilot/autoretrieve gates; and documentation, OpenAPI spec, and tests are added.

Changes

Phase 5A Memory Autopilot

Layer / File(s) Summary
Runtime safety gates and autopilot mode config
lib/config/pandora-runtime-safety-config.ts, lib/services/admin-memory-verification-loader.ts
PandoraRuntimeGate gains memoryAutopilotEnabled, autoRetrieveEnabled, autoCandidateQueueEnabled, autoCaptureLowRiskEnabled, sensitiveMemoryRequiresApproval (default-true). New PandoraMemoryAutopilotMode type and resolvePandoraMemoryAutopilotMode added. RiskGate exclusion list updated.
Autopilot policy service
lib/services/memory-autopilot-policy-service.ts
New module exports inferMemoryNamespace (regex AU vs real_life), shouldAutoRetrieveMemory (trigger/skip/confidence), and shouldAutoCreateMemoryCandidate (trigger/skip/secret detection forcing private sensitivity and review).
Adaptive turn service
lib/services/memory-adaptive-turn-service.ts
New module defines AdaptiveTurnInput, autopilotSafety, disabledPreAnswer/disabledPostAnswer decision helpers, and runPreAnswerTurn/runPostAnswerTurn async functions that conditionally build retrieval context or queue candidates.
REST endpoint and OpenAPI spec
app/api/memory/adaptive/turn/route.ts, public/pandora-memory-openapi.json
New POST handler validates body with Zod, branches on mode, enforces memoryContextApiEnabled/memoryCaptureApiEnabled gates (403 on disabled), bridges to runPreAnswerTurn or runPostAnswerTurn. OpenAPI spec documents the endpoint with request schema and 200/400/401/403 responses.
MCP adaptive_turn tool
lib/services/pandora-mcp-tools.ts, lib/services/pandora-mcp-server.ts
Adds adaptiveTurnInputSchema and adaptiveTurnTool (pre-answer delegates to runPreAnswerTurn; post-answer checks capture gate, returns blocker if disabled, otherwise calls runPostAnswerTurn). Registered as adaptive_turn in createPandoraMcpServer.
Tests and docs
tests/phase5-memory-autopilot.test.ts, tests/unit/pandora-auth-runtime-gates.test.ts, docs/pandora-memory-autopilot.md, docs/pandora-chatgpt-adaptive-instructions.md
Tests cover policy defaults, mode parsing, trigger detection, endpoint gating, candidate queueing, namespace separation, and MCP behavior. Runtime gates test updated for sensitiveMemoryRequiresApproval default. Docs describe Phase 5A concept, safe loop, runtime flags, safety boundaries, and limitations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • besfeng23/Memory#101: Established createPandoraMcpServer and the base MCP toolset in pandora-mcp-server.ts; this PR extends that by registering the new adaptive_turn tool.
  • besfeng23/Memory#103: Introduced Phase 4C building blocks (withBridge, adaptive chat context builder, candidate creation pipeline) that the new adaptive turn service and REST endpoint directly reuse.

Poem

🐇 Hippity-hop, no more "save my thought"!
The rabbit remembers what the user just brought.
Pre-answer retrieves, post-answer queues,
secrets get blocked and reviewed on cue.
Candidates wait in a tidy little row—
Phase 5A autopilot, watch the memory flow! 🌿

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: adding Phase 5A Memory Autopilot with auto-retrieve and candidate queueing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-phase-5a-memory-autopilot

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cb58ae221

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!preview.should_create_candidate) return preview;
const gate = requireMcpCaptureEnabled(env);
if (!gate.ok) return { ...preview, should_create_candidate: false, warnings: [...preview.warnings, "mcp_capture_disabled_no_candidate_written"], blocker: gate };
return runPostAnswerTurn(client, principal.userId, input, env);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Audit MCP adaptive-turn candidate writes

When PANDORA_ENABLE_MCP_CAPTURE=true and a post-answer durable-memory trigger is detected, this path calls runPostAnswerTurn, which inserts into memory_capture_candidates, but it never calls auditPandoraMcpToolCall unlike the other MCP tools in this file. Those auto-queued candidates therefore have no audit_logs proof, breaking the audit-backed requirement for MCP memory writes.

Useful? React with 👍 / 👎.


export type AdaptiveTurnInput = { namespace: MemoryBridgeNamespace; user_message: string; assistant_draft?: string; current_task?: string; conversation_type?: string; mode: "pre_answer" | "post_answer"; source?: string };
export function autopilotSafety(env: Partial<NodeJS.ProcessEnv> = process.env) { const r=resolvePandoraRuntimeSafetyConfig(env).config; return { public_read:false, public_persistence:false, model_calls:r.modelCallsEnabled, embeddings:r.embeddingsEnabled, semantic_retrieval:r.semanticRetrievalEnabled, auto_capture_low_risk:r.autoCaptureLowRiskEnabled }; }
export function disabledPreAnswer(input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const mode=resolvePandoraMemoryAutopilotMode(env); const decision=shouldAutoRetrieveMemory({userMessage:input.user_message,namespace:input.namespace,currentTask:input.current_task,conversationType:input.conversation_type}); const r=resolvePandoraRuntimeSafetyConfig(env).config; const enabled=r.memoryAutopilotEnabled&&r.autoRetrieveEnabled&&mode!=="off"; return {ok:true,mode:"pre_answer" as const,autopilot_mode:mode,namespace:input.namespace,should_retrieve:enabled&&decision.shouldRetrieve,retrieve_decision:decision,context:null,warnings:enabled?[]:["autopilot_or_auto_retrieve_disabled"],safety:autopilotSafety(env)}; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor suggest mode before retrieving context

When PANDORA_MEMORY_AUTOPILOT=suggest, the documented behavior is preview-only, but this condition treats every mode except off as enabled. In a suggest-only environment with PANDORA_AUTO_RETRIEVE=true, pre_answer still requires the context gate and returns actual private memory context instead of just the retrieval decision.

Useful? React with 👍 / 👎.

},
"/api/memory/adaptive/turn": {
"post": {
"summary": "Phase 5A adaptive turn autopilot",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose adaptive_turn with an operationId

The new ChatGPT/OpenAPI operation is the only path in this file without an operationId, while the added instructions tell clients to call adaptive_turn. Importers that name tools from operationId will not expose that tool name for this endpoint, so the advertised autopilot action is not callable from the schema as written.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/config/pandora-runtime-safety-config.ts (1)

54-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the exported metadata for sensitiveMemoryRequiresApproval.

This gate now defaults to enabled unless the env var is "false", but runtime.gates[...] still reports safeDefault: false and dangerous: true. That makes the exported status contradict the actual config for a default-on safety control.

🤖 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 `@lib/config/pandora-runtime-safety-config.ts` around lines 54 - 64, The
exported gate metadata for sensitiveMemoryRequiresApproval is inconsistent with
its actual default-on behavior in resolvePandoraRuntimeSafetyConfig. Update the
gates entry construction so that the metadata for that specific key reflects the
real semantics, including the correct safeDefault and dangerous values, while
leaving the other gates unchanged. Use the existing
resolvePandoraRuntimeSafetyConfig, vars, and runtime.gates mapping to locate and
adjust the special-case handling for sensitiveMemoryRequiresApproval.
🤖 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 `@app/api/memory/adaptive/turn/route.ts`:
- Around line 9-11: The POST handler in the adaptive turn route returns preview
responses before calling withBridge(), which allows unauthenticated callers to
get autopilot decision data. Move the bridge authentication check to the start
of POST, before any disabledPreAnswer/disabledPostAnswer or preview responses
are returned, and reuse the authenticated client/principal for the later
runPreAnswerTurn/runPostAnswerTurn paths.

In `@docs/pandora-chatgpt-adaptive-instructions.md`:
- Around line 11-20: Remove the legacy memory-retrieval guidance that still
tells the agent to call get_adaptive_context before important answers, and leave
only the adaptive_turn workflow. Update the instructions in the markdown section
that defines the Phase 5A behavior so there is a single retrieval path, using
adaptive_turn with mode=pre_answer and mode=post_answer, with no conflicting
references to get_adaptive_context.

In `@lib/services/memory-adaptive-turn-service.ts`:
- Around line 10-11: Update the shared pre-answer path in disabledPreAnswer and
runPreAnswerTurn so retrieval is gated by memoryContextApiEnabled as well as the
existing autopilot flags. Right now should_retrieve can still become true when
only the autopilot settings are on, which lets runPreAnswerTurn proceed without
the read gate. Add the gate into the shared enabled calculation used by
disabledPreAnswer, and keep runPreAnswerTurn relying on that shared decision so
both REST and MCP paths behave consistently.
- Line 13: The runPostAnswerTurn flow is sending raw user and assistant content
into candidate classification before any redaction, so secret-like text can
still leave the process. Update runPostAnswerTurn to redact or mask sensitive
content in candidateText before calling createCandidatesFromSession(), and
ensure any downstream classifier/provider input uses the redacted text rather
than input.user_message or input.assistant_draft. Keep the candidate creation
behavior in createCandidatesFromSession compatible with the sanitized payload.
- Around line 12-13: The `capture_low_risk` mode is currently bypassing its
dedicated runtime gate in `disabledPostAnswer` and `runPostAnswerTurn`, so add a
separate check for `PANDORA_AUTO_CAPTURE_LOW_RISK` (via the runtime safety
config) before allowing candidate creation in that mode. Update the eligibility
logic in `disabledPostAnswer` to require the specific capture-low-risk flag when
`autopilot_mode` is `"capture_low_risk"`, and keep `runPostAnswerTurn`’s warning
behavior aligned with the new gate.

In `@lib/services/pandora-mcp-tools.ts`:
- Around line 44-54: The adaptiveTurnTool path is missing MCP audit logging, so
private-context access and candidate creation aren’t recorded like the other
memory-facing tools. Update adaptiveTurnTool to call auditPandoraMcpToolCall
with the adaptive_turn tool name and relevant principal/input details before it
branches into runPreAnswerTurn, disabledPostAnswer, or runPostAnswerTurn. Keep
the audit call aligned with the existing audit_logs behavior used by the other
MCP tools so the autopilot path is visible in the audit trail.

In `@tests/phase5-memory-autopilot.test.ts`:
- Around line 9-10: The test setup in `phase5-memory-autopilot.test.ts` is only
restoring mocks, so `vi.stubEnv(...)` overrides can leak between cases. Update
the existing `afterEach` рядом with the `envOn` fixture to also clear stubbed
environment variables by calling `vi.unstubAllEnvs()`, or switch the suite to
`unstubEnvs`, so later tests don’t inherit these env settings.

---

Outside diff comments:
In `@lib/config/pandora-runtime-safety-config.ts`:
- Around line 54-64: The exported gate metadata for
sensitiveMemoryRequiresApproval is inconsistent with its actual default-on
behavior in resolvePandoraRuntimeSafetyConfig. Update the gates entry
construction so that the metadata for that specific key reflects the real
semantics, including the correct safeDefault and dangerous values, while leaving
the other gates unchanged. Use the existing resolvePandoraRuntimeSafetyConfig,
vars, and runtime.gates mapping to locate and adjust the special-case handling
for sensitiveMemoryRequiresApproval.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: fecce092-bed0-4ac1-be8a-c1077eb4b836

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc812d and 7cb58ae.

📒 Files selected for processing (12)
  • app/api/memory/adaptive/turn/route.ts
  • docs/pandora-chatgpt-adaptive-instructions.md
  • docs/pandora-memory-autopilot.md
  • lib/config/pandora-runtime-safety-config.ts
  • lib/services/admin-memory-verification-loader.ts
  • lib/services/memory-adaptive-turn-service.ts
  • lib/services/memory-autopilot-policy-service.ts
  • lib/services/pandora-mcp-server.ts
  • lib/services/pandora-mcp-tools.ts
  • public/pandora-memory-openapi.json
  • tests/phase5-memory-autopilot.test.ts
  • tests/unit/pandora-auth-runtime-gates.test.ts

Comment on lines +9 to +11
export async function POST(request:NextRequest){ const parsed=schema.safeParse(await request.json().catch(()=>({}))); if(!parsed.success)return NextResponse.json({ok:false,blockers:["invalid_request"],issues:parsed.error.flatten()},{status:400}); const body=parsed.data; const namespace=inferMemoryNamespace({userMessage:body.user_message,namespace:body.namespace,currentTask:body.current_task}); const input={...body,namespace}; const env=process.env; const runtime=resolvePandoraRuntimeSafetyConfig(env).config;
if(body.mode==="pre_answer"){ const preview=disabledPreAnswer(input,env); if(!preview.should_retrieve)return NextResponse.json(preview); if(!runtime.memoryContextApiEnabled)return NextResponse.json({...preview,ok:false,blockers:["memoryContextApiEnabled_disabled"],next_step:"Set PANDORA_ENABLE_MEMORY_CONTEXT_API=true in a reviewed environment."},{status:403}); const bridge=await withBridge(request,"memoryContextApiEnabled"); if("error" in bridge)return bridge.error; return NextResponse.json(await runPreAnswerTurn(bridge.client,bridge.principal.userId,input,env)); }
const preview=disabledPostAnswer(input,env); if(!preview.should_create_candidate)return NextResponse.json(preview); if(!runtime.memoryCaptureApiEnabled)return NextResponse.json({...preview,ok:false,blockers:["memoryCaptureApiEnabled_disabled"],next_step:"Set PANDORA_ENABLE_MEMORY_CAPTURE_API=true in a reviewed environment."},{status:403}); const bridge=await withBridge(request,"memoryCaptureApiEnabled"); if("error" in bridge)return bridge.error; return NextResponse.json(await runPostAnswerTurn(bridge.client,bridge.principal.userId,input,env)); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require bridge auth before returning autopilot previews.

This handler only calls withBridge() after it decides a read/write is needed. Anonymous callers can therefore get 200 responses with namespace, candidate_decision/retrieve_decision, and safety whenever the policy says “do nothing,” even though the endpoint is documented as bearer-auth protected.

🤖 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 `@app/api/memory/adaptive/turn/route.ts` around lines 9 - 11, The POST handler
in the adaptive turn route returns preview responses before calling
withBridge(), which allows unauthenticated callers to get autopilot decision
data. Move the bridge authentication check to the start of POST, before any
disabledPreAnswer/disabledPostAnswer or preview responses are returned, and
reuse the authenticated client/principal for the later
runPreAnswerTurn/runPostAnswerTurn paths.

Comment on lines +11 to +20
## Phase 5A autopilot instructions

- Before important answers, call `adaptive_turn` with `mode=pre_answer` so Pandora can retrieve relevant context automatically.
- After important exchanges, call `adaptive_turn` with `mode=post_answer` so Pandora can queue durable memory candidates automatically.
- Do not require the user to manually say “retrieve memory,” “save this,” or “use Pandora.”
- Do not save secrets, credentials, tokens, API keys, passwords, OAuth codes, or database keys.
- Sensitive/private details should go to the review queue, not permanent memory.
- Keep `real_life` and `au` namespaces separate.
- Do not call things done without verification.
- Catch gambling risk, money/reputation risk, and fantasy-vs-execution drift as review-gated risk candidates.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the older get_adaptive_context guidance or this prompt will issue conflicting retrieval instructions.

These new adaptive_turn rules conflict with the still-present directive at Line 3 to call get_adaptive_context before important answers. Keeping both paths in the same prompt makes the agent follow two memory workflows and weakens the Phase 5A contract.

🤖 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 `@docs/pandora-chatgpt-adaptive-instructions.md` around lines 11 - 20, Remove
the legacy memory-retrieval guidance that still tells the agent to call
get_adaptive_context before important answers, and leave only the adaptive_turn
workflow. Update the instructions in the markdown section that defines the Phase
5A behavior so there is a single retrieval path, using adaptive_turn with
mode=pre_answer and mode=post_answer, with no conflicting references to
get_adaptive_context.

Comment on lines +10 to +11
export function disabledPreAnswer(input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const mode=resolvePandoraMemoryAutopilotMode(env); const decision=shouldAutoRetrieveMemory({userMessage:input.user_message,namespace:input.namespace,currentTask:input.current_task,conversationType:input.conversation_type}); const r=resolvePandoraRuntimeSafetyConfig(env).config; const enabled=r.memoryAutopilotEnabled&&r.autoRetrieveEnabled&&mode!=="off"; return {ok:true,mode:"pre_answer" as const,autopilot_mode:mode,namespace:input.namespace,should_retrieve:enabled&&decision.shouldRetrieve,retrieve_decision:decision,context:null,warnings:enabled?[]:["autopilot_or_auto_retrieve_disabled"],safety:autopilotSafety(env)}; }
export async function runPreAnswerTurn(client: MemoryBridgeDbClient, userId: string, input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const base=disabledPreAnswer(input,env); if(!base.should_retrieve)return base; const context=await buildAdaptiveChatGptContext(client,{user_id:userId,namespace:input.namespace,query:redactSecrets(input.user_message),current_task:redactSecrets(input.current_task??""),max_items:8}); return {...base,context,warnings:[...base.warnings,...(context.warnings??[])]}; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fold memoryContextApiEnabled into the shared pre-answer flow.

disabledPreAnswer() can still return should_retrieve: true with only the autopilot flags enabled. The REST route compensates with a separate memoryContextApiEnabled check, but the MCP path calls runPreAnswerTurn() directly, so private context retrieval still runs when the read gate is off.

🤖 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 `@lib/services/memory-adaptive-turn-service.ts` around lines 10 - 11, Update
the shared pre-answer path in disabledPreAnswer and runPreAnswerTurn so
retrieval is gated by memoryContextApiEnabled as well as the existing autopilot
flags. Right now should_retrieve can still become true when only the autopilot
settings are on, which lets runPreAnswerTurn proceed without the read gate. Add
the gate into the shared enabled calculation used by disabledPreAnswer, and keep
runPreAnswerTurn relying on that shared decision so both REST and MCP paths
behave consistently.

Comment on lines +12 to +13
export function disabledPostAnswer(input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const mode=resolvePandoraMemoryAutopilotMode(env); const decision=shouldAutoCreateMemoryCandidate({userMessage:input.user_message,assistantResponse:input.assistant_draft,namespace:input.namespace,currentTask:input.current_task,source:input.source}); const r=resolvePandoraRuntimeSafetyConfig(env).config; const enabled=r.memoryAutopilotEnabled&&r.autoCandidateQueueEnabled&&(mode==="queue"||mode==="capture_low_risk"); return {ok:true,mode:"post_answer" as const,autopilot_mode:mode,namespace:decision.namespace,should_create_candidate:enabled&&decision.shouldCreateCandidate,candidate_decision:decision,candidates:[],warnings:enabled?[]:["autopilot_or_auto_candidate_queue_disabled"],safety:autopilotSafety(env)}; }
export async function runPostAnswerTurn(client: MemoryBridgeDbClient, userId: string, input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const base=disabledPostAnswer(input,env); if(!base.should_create_candidate)return base; const candidateText=[`User: ${input.user_message}`,input.assistant_draft?`Assistant: ${input.assistant_draft}`:null].filter(Boolean).join("\n\n"); const data=await createCandidatesFromSession(client,{user_id:userId,namespace:base.namespace,source:input.source??"adaptive_turn",text:candidateText,mode:"candidate_only"},env); const warnings=[...base.warnings,...(data.warnings??[])]; if(base.autopilot_mode==="capture_low_risk") warnings.push("auto_capture_not_implemented"); return {...base,candidates:data.candidates,warnings}; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

capture_low_risk currently ignores its own runtime gate.

disabledPostAnswer() enables both "queue" and "capture_low_risk" from autoCandidateQueueEnabled, and runPostAnswerTurn() only adds auto_capture_not_implemented. PANDORA_AUTO_CAPTURE_LOW_RISK never changes eligibility, so this mode behaves as enabled even when its dedicated gate is false.

🤖 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 `@lib/services/memory-adaptive-turn-service.ts` around lines 12 - 13, The
`capture_low_risk` mode is currently bypassing its dedicated runtime gate in
`disabledPostAnswer` and `runPostAnswerTurn`, so add a separate check for
`PANDORA_AUTO_CAPTURE_LOW_RISK` (via the runtime safety config) before allowing
candidate creation in that mode. Update the eligibility logic in
`disabledPostAnswer` to require the specific capture-low-risk flag when
`autopilot_mode` is `"capture_low_risk"`, and keep `runPostAnswerTurn`’s warning
behavior aligned with the new gate.

export function disabledPreAnswer(input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const mode=resolvePandoraMemoryAutopilotMode(env); const decision=shouldAutoRetrieveMemory({userMessage:input.user_message,namespace:input.namespace,currentTask:input.current_task,conversationType:input.conversation_type}); const r=resolvePandoraRuntimeSafetyConfig(env).config; const enabled=r.memoryAutopilotEnabled&&r.autoRetrieveEnabled&&mode!=="off"; return {ok:true,mode:"pre_answer" as const,autopilot_mode:mode,namespace:input.namespace,should_retrieve:enabled&&decision.shouldRetrieve,retrieve_decision:decision,context:null,warnings:enabled?[]:["autopilot_or_auto_retrieve_disabled"],safety:autopilotSafety(env)}; }
export async function runPreAnswerTurn(client: MemoryBridgeDbClient, userId: string, input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const base=disabledPreAnswer(input,env); if(!base.should_retrieve)return base; const context=await buildAdaptiveChatGptContext(client,{user_id:userId,namespace:input.namespace,query:redactSecrets(input.user_message),current_task:redactSecrets(input.current_task??""),max_items:8}); return {...base,context,warnings:[...base.warnings,...(context.warnings??[])]}; }
export function disabledPostAnswer(input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const mode=resolvePandoraMemoryAutopilotMode(env); const decision=shouldAutoCreateMemoryCandidate({userMessage:input.user_message,assistantResponse:input.assistant_draft,namespace:input.namespace,currentTask:input.current_task,source:input.source}); const r=resolvePandoraRuntimeSafetyConfig(env).config; const enabled=r.memoryAutopilotEnabled&&r.autoCandidateQueueEnabled&&(mode==="queue"||mode==="capture_low_risk"); return {ok:true,mode:"post_answer" as const,autopilot_mode:mode,namespace:decision.namespace,should_create_candidate:enabled&&decision.shouldCreateCandidate,candidate_decision:decision,candidates:[],warnings:enabled?[]:["autopilot_or_auto_candidate_queue_disabled"],safety:autopilotSafety(env)}; }
export async function runPostAnswerTurn(client: MemoryBridgeDbClient, userId: string, input: AdaptiveTurnInput, env: Partial<NodeJS.ProcessEnv> = process.env) { const base=disabledPostAnswer(input,env); if(!base.should_create_candidate)return base; const candidateText=[`User: ${input.user_message}`,input.assistant_draft?`Assistant: ${input.assistant_draft}`:null].filter(Boolean).join("\n\n"); const data=await createCandidatesFromSession(client,{user_id:userId,namespace:base.namespace,source:input.source??"adaptive_turn",text:candidateText,mode:"candidate_only"},env); const warnings=[...base.warnings,...(data.warnings??[])]; if(base.autopilot_mode==="capture_low_risk") warnings.push("auto_capture_not_implemented"); return {...base,candidates:data.candidates,warnings}; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact post-answer text before candidate classification.

This path builds candidateText from raw user/assistant content and hands it to createCandidatesFromSession(). That service detects secrets, but it still forwards input.text to the classifier/provider before it redacts the stored rows, so secret-like content can still leave the process here.

🤖 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 `@lib/services/memory-adaptive-turn-service.ts` at line 13, The
runPostAnswerTurn flow is sending raw user and assistant content into candidate
classification before any redaction, so secret-like text can still leave the
process. Update runPostAnswerTurn to redact or mask sensitive content in
candidateText before calling createCandidatesFromSession(), and ensure any
downstream classifier/provider input uses the redacted text rather than
input.user_message or input.assistant_draft. Keep the candidate creation
behavior in createCandidatesFromSession compatible with the sanitized payload.

Comment on lines +44 to +54
export async function adaptiveTurnTool(client: MemoryBridgeDbClient, principal: Extract<PandoraMcpPrincipal, { ok: true }>, rawInput: unknown, env: Partial<NodeJS.ProcessEnv> = process.env) {
const parsed = adaptiveTurnInputSchema.parse(rawInput);
const namespace = inferMemoryNamespace({ userMessage: parsed.user_message, namespace: parsed.namespace, currentTask: parsed.current_task });
const input = { ...parsed, namespace, source: "mcp_adaptive_turn" };
if (input.mode === "pre_answer") return runPreAnswerTurn(client, principal.userId, input, env);
const preview = disabledPostAnswer(input, env);
if (!preview.should_create_candidate) return preview;
const gate = requireMcpCaptureEnabled(env);
if (!gate.ok) return { ...preview, should_create_candidate: false, warnings: [...preview.warnings, "mcp_capture_disabled_no_candidate_written"], blocker: gate };
return runPostAnswerTurn(client, principal.userId, input, env);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add MCP audit logging to adaptive_turn.

The other memory-facing MCP tools record audit_logs, but this tool can retrieve private context and queue candidates without calling auditPandoraMcpToolCall. That leaves the new autopilot path invisible in the audit trail.

🤖 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 `@lib/services/pandora-mcp-tools.ts` around lines 44 - 54, The adaptiveTurnTool
path is missing MCP audit logging, so private-context access and candidate
creation aren’t recorded like the other memory-facing tools. Update
adaptiveTurnTool to call auditPandoraMcpToolCall with the adaptive_turn tool
name and relevant principal/input details before it branches into
runPreAnswerTurn, disabledPostAnswer, or runPostAnswerTurn. Keep the audit call
aligned with the existing audit_logs behavior used by the other MCP tools so the
autopilot path is visible in the audit trail.

Comment on lines +9 to +10
afterEach(()=>vi.restoreAllMocks());
const envOn={PANDORA_ENABLE_MEMORY_AUTOPILOT:"true",PANDORA_MEMORY_AUTOPILOT:"queue",PANDORA_AUTO_RETRIEVE:"true",PANDORA_AUTO_CANDIDATE_QUEUE:"true",PANDORA_ENABLE_MEMORY_CONTEXT_API:"true",PANDORA_ENABLE_MEMORY_CAPTURE_API:"true"};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files tests/phase5-memory-autopilot.test.ts vitest.config.* vite.config.* package.json tsconfig*.json

printf '\n== test file excerpt ==\n'
cat -n tests/phase5-memory-autopilot.test.ts | sed -n '1,220p'

printf '\n== vitest config search ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'unstubEnvs|restoreAllMocks|stubEnv|vi\.stubEnv|vi\.unstubAllEnvs' .

printf '\n== package vitest version ==\n'
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const deps = {...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies};
for (const k of Object.keys(deps || {}).sort()) {
  if (k.toLowerCase().includes('vitest') || k.toLowerCase().includes('vite')) {
    console.log(k + ': ' + deps[k]);
  }
}
JS

Repository: besfeng23/Memory

Length of output: 9362


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== vitest config ==\n'
cat -n vitest.config.ts | sed -n '1,220p'

printf '\n== package.json ==\n'
cat -n package.json | sed -n '1,220p'

Repository: besfeng23/Memory

Length of output: 2608


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== setup file ==\n'
cat -n tests/setup.ts | sed -n '1,220p'

printf '\n== search for env cleanup ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'unstubAllEnvs|unstubEnvs|restoreAllMocks|stubEnv|afterEach|beforeEach' tests .

Repository: besfeng23/Memory

Length of output: 4941


Reset stubbed env vars in afterEach. vi.restoreAllMocks() does not clear vi.stubEnv(...), so these overrides can leak into later cases and make the suite order-dependent. Add vi.unstubAllEnvs() here, or enable unstubEnvs.

🤖 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 `@tests/phase5-memory-autopilot.test.ts` around lines 9 - 10, The test setup in
`phase5-memory-autopilot.test.ts` is only restoring mocks, so `vi.stubEnv(...)`
overrides can leak between cases. Update the existing `afterEach` рядом with the
`envOn` fixture to also clear stubbed environment variables by calling
`vi.unstubAllEnvs()`, or switch the suite to `unstubEnvs`, so later tests don’t
inherit these env settings.

@besfeng23
besfeng23 merged commit a4412c3 into main Jun 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant