Repository navigation
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR adds gateway-managed memory with independently administered saving and recall, permission-aware memory operations, private tool orchestration across Chat Completions, Responses, and Anthropic Messages, bounded Responses continuations, and dashboard controls. The latest revision fixes preparation exhaustion by discarding truncated private calls, restoring the caller’s final-answer constraints, and persisting only resumable Responses history
Confidence Score: 5/5The current revision appears safe to merge, with the previous preparation-exhaustion failure fixed and no new actionable issue established Truncated preparation now bypasses partial tool execution, restores the client’s exact output constraints for a final model call, and persists a completed Responses result without unmatched private calls. The earlier duplicate-directive finding was manually resolved without explanation; all other previous findings were fixed, withdrawn, accepted as intentional behavior, or resolved with supporting context Important Files Changed
Reviews (41): Last reviewed commit: "fix(memory): finish replies after prepar..." | Re-trigger Greptile |
PR overviewThis PR adds gateway-backed memory with separate controls for administrators to save memories and for callers to recall them. Recalled results can be incorporated into subsequent model requests through server-tool continuation. Seven issues have been addressed, with one conditional data-exposure issue remaining. When prompt logging is enabled, recalled memory content may appear in spend logs, allowing team members with spend-log access but without memory-read permission to view it. The issue depends on a specific logging configuration and permission combination. Open issues (1)
Fixed/addressed: 7 · PR risk: 4/10 |
|
@greptileai @veria-ai @cursor review Please review 610a5a4, especially per-round RPM enforcement, JSON updates, error preservation, and disabled-path isolation |
| if self.route == "aresponses" | ||
| else () | ||
| ) | ||
| self.data = continue_server_tools( |
There was a problem hiding this comment.
Low: Recalled memories bypass memory-read permissions via spend logs
continue_server_tools embeds full memory results in the next model request, which follows the normal spend-logging path and is attributed to the caller's team. When prompt logging is enabled, a team member with /spend/logs but without /memory/v2/entries can read memory content they are not authorized to access; keep these private tool results out of logging snapshots, or replace them with a redacted logging-only representation while retaining billing metadata.
There was a problem hiding this comment.
This is intended. Access to a user's full request logs includes recalled memories within those requests. Separate memory permissions govern direct browsing and search
|
bugbot run |
|
@veria-ai "Low: Recalled memories bypass memory-read permissions via spend logs" - this is intended - all recorded memories are already available as part of logs - memory read permissions are for agentic read/write |
|
@greptileai @veria-ai @cursor review Please review the current changes, including admin capture guidance, permission enforcement, default behavior, and prompt cache stability |
|
@cursor review Please check the final provider-field fix and capture guidance. @greptileai @veria-ai please refresh your reviews for this revision |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 13d0a57. Configure here.
|
@greptileai @veria-ai @cursor review Please re-review the latest commit covering enrollment caching, coordinated continuation cleanup, and service-key identifier redaction |
3c295d2 to
396b18b
Compare
|
@greptileai @veria-ai @cursor review Please review 396b18b, including the cleanup lock expiry margin and regression covering the next scheduled run |
|
@greptileai @veria-ai @cursor review Please review c530e50: saved Responses now replay complete authenticated transcripts independently of spend logs |
|
@greptileai @veria-ai @cursor review Please review 06860d0 for isolated memory tool budgets, exact final-answer caps, and rejection of incomplete continuations |
|
@greptileai @veria-ai @cursor review Please review f0995fc, including preparation exhaustion, partial-call removal, restored final-answer limits, and safe Responses replay |
TLDR
Problem this solves:
How it solves it:
User Flow
Before: a user asks an assistant to remember a project port, but a fresh conversation cannot recall it
POST https://gateway.example/v1/chat/completions, asking to remember the project's staging portAfter: the administrator enables saving and recall, and the same client can remember across conversations
POST https://gateway.example/v1/chat/completions, asking to remember the project's staging porthttps://gateway.example/ui/memory/, they see recent memories, contributors, and separate saving/recall statesAt Memory > Administration, the proxy admin controls Save memories and Use saved memories independently. Each has an on/off switch and an Everyone/Selected users selector. The selection follows the user across their keys. No end-user opt-in, per-key preference, client hook, or local installation is needed. With no V2 configuration, saving and recall are off. Existing V2 settings in the database persist across deployments; V1 settings do not enable V2
These controls govern automatic agent memory. They do not delete records or change human dashboard/API visibility. Users see their own records; team admins can also see their team's records. Ordinary members can receive team memory access through the existing Member Permissions editor. Proxy admins can see all V2 records. When enabled for a proxy-admin inference identity, recall can therefore send records from any user or organization to that model. Read permission does not grant editing or deletion, and agents have no deletion tool. V1 behavior is unchanged
What should memory remember?
Memory > Administration now includes an editable What should memory remember? field. A proxy admin can set gateway-wide capture guidance, such as “Remember architectural decisions and deployment requirements; skip routine updates and personal preferences.” Click Save changes to apply it to future saves without a restart. Reset to default restores the draft to the original guidance; save again to apply it. Discard changes cancels unsaved edits
The default is: “Save durable new facts, decisions or corrections when useful, without waiting for an explicit request to remember.” Existing settings inherit that text. Editing it does not enable memory, change visibility, or rewrite existing records. The technical tool workflow, evidence requirements, and server-enforced permissions stay separate. This is model guidance, not a guaranteed content filter. It uses the existing configuration record and adds no further migration
Implementation
Existing components reused: memory/configuration tables, authenticated identities and team permissions, repositories, the request processor and router, spend/budget/limiter hooks, DualCache and cross-worker invalidation, APScheduler, and PodLockManager. New code covers memory selection/capture, private tool execution across three protocols, and bounded saved Responses transcripts. LiteLLM already has a shared
AgenticLoopPlanframework across Chat, Responses, and Anthropic, including private-tool hold-back streaming. Its current follow-up executors call the SDK (litellm.acompletion/litellm.aresponses) directly. They do not re-enter the gateway admission and accounting path used here. Reusing that framework requires a gateway-aware round executor that preserves per-round RPM, budgets, guardrails, cost completion, and permission checks. This PR still has separate gateway orchestration; that is a consolidation opportunity, not a claim that the framework is missingEnrollment settings are cached for 30 seconds and invalidated on admin updates. Unenrolled inference requests return before user/team database lookups. Cached enrollment never authorizes a memory operation: eligible requests and memory reads/writes still resolve current permissions. Service-key contributors return
actor: nullandactor_name: "Service key"; internal ownership identifiers remain privateThe model chooses when to call capture, search, or read; only enabled tools are injected. Capture requires an exact evidence quote from an incoming user message or application tool result. There is no forced checkpoint, catalog, nightly work, or mandatory reflection call. Capture is best-effort and can miss facts if the model does not save them
Hidden rounds reuse the proxy request processor, hooks, router, budgets, token limits, concurrency limits, and spend tracking. Each physical model call is billed and logged under the same request trace. Each upstream round consumes RPM, including hidden continuations; requests stop with 429 when a limit is reached. The memory loop permits up to eight rounds; structured-output requests and requests capped below 4,096 output tokens use a separate final-answer call, which also counts against limits. Clients cannot force a private memory tool through
tool_choice. Streaming rounds are buffered until private activity is distinguished from the final answer, with keepalives while waitingWith automatic tool choice, short-budget requests give memory preparation a 4,096-token output allowance and restore the caller's exact
max_tokens,max_completion_tokens, ormax_output_tokensfor the final answer. Application tools and output formatting are also restored for that final call. Larger unconstrained requests keep their existing flow. This prevents a 40-token answer limit from truncating private tool arguments. If preparation exhausts its budget, its unfinished response is discarded and the final pass still runs with the caller's cap, using only completed tool results and explicit failure guidance. Outside preparation, incomplete tool responses remain incomplete withstore:false. Unmatched private calls and incomplete application calls cannot become resumable history. Previously poisoned histories are rejected with HTTP 409 before a provider callStorage reuses the existing memory and configuration tables. The only new table holds complete Responses transcripts and public response objects, readable for up to 24 hours with eviction at capacity. Follow-ups replay that authenticated transcript instead of reconstructing it from spend logs, so prompt logging is optional. Tool calls and results remain paired, and branching starts from the selected response. The existing one-megabyte per-response, 256-response per-key, and 32-megabyte per-namespace limits still apply.
store:falsedisables this retention. Public retrieval returns only the public response; private input history remains unavailableResponses saved before this fix require a fresh conversation (HTTP 409). An existing non-memory provider parent remains subject to its normal provider/session replay behavior
Expired rows are removed by bounded hourly cleanup using the existing scheduler and Redis pod lock; without Redis, each worker performs its own bounded sweep. Expiry checks do not depend on cleanup timing. Turning recall off blocks resuming a saved Responses conversation with previously retrieved memory. Already-returned content in client history cannot be removed; start a fresh conversation after changing recall
Instructions and tool definitions stay stable between admin edits, preserving provider prompt-cache eligibility. Editing capture guidance changes the prompt prefix and can cause an expected cache miss until the new prefix warms. Saved records are fetched on demand, not injected into every system prompt. Whole-response caching is bypassed for memory rounds to avoid replaying side effects. No E2E scripts are committed
With prompt logging enabled, recalled memory is part of the full request log. Someone authorized to view that request can see it there. Direct memory browsing/search still uses memory permissions; this is the accepted logging contract
Relevant issues
Affected release
Linear ticket
Pre-Submission checklist
make checkpass locallyScreenshots / Proof of Fix
Real provider calls against isolated PostgreSQL databases: merge base on
127.0.0.1:4019, PR tip on127.0.0.1:4018.memory-testroutes tovertex_ai/claude-sonnet-5; Messages uses that provider directly. No QA scripts or screenshot branches are part of the PRThe save-only configuration is
PUT /memory/v2/settingswith{"enabled":true,"everyone":true,"read":{"enabled":false}}. Recall-only uses{"enabled":false,"read":{"enabled":true,"everyone":true}}. Custom guidance uses the same endpoint and addscapture_instructionsBefore (1ca4579)
Chat Completions
Lark-80ff6b2duses port8359Anthropic Messages
Lark-c37c8ac9uses port8359Responses
Lark-7823dee2uses port8359Memory dashboard and administration
/v1/memorytable is availableAfter (f0995fc)
Chat Completions
With saving on and recall off, send
POST /v1/chat/completionswith{"model":"memory-test","messages":[{"role":"user","content":"Remember project AdminRecall-2fb672 uses staging port 8764."}],"max_tokens":1200}: HTTP 200; the record appears in/memory/v2/entriesAsk a fresh conversation using a sibling key for the port: HTTP 200,
UNKNOWNSwitch to saving off and recall on, then repeat: HTTP 200,
8764, with streaming both enabled and disabledSet capture guidance to save deployment ports, prefix titles with
Deployment note:, and skip food preferences. Turn saving on, then send “Project Guidance-ca3c01be uses staging port 9284. My favorite snack is blueberries.”: HTTP 200; the saved entry has the requested title prefix and port, without the snack preferenceSet
max_tokens:40and ask to remember projectBudget-198f0d34uses port8567, then reply exactlyok: HTTP 200, exactok, memory saved. Both streaming modes passAnthropic Messages
With saving on and recall off, send
POST /v1/messageswith{"model":"vertex_ai/claude-sonnet-5","messages":[{"role":"user","content":"Remember project AdminRecall-e67975 uses staging port 8764."}],"max_tokens":1200}: HTTP 200; the record appears in/memory/v2/entriesAsk a fresh conversation using a sibling key for the port: HTTP 200,
UNKNOWNSwitch to saving off and recall on, then repeat: HTTP 200,
8764, with streaming both enabled and disabledSet capture guidance to save deployment ports, prefix titles with
Deployment note:, and skip food preferences. Turn saving on, then send “Project Guidance-d3570bf8 uses staging port 9284. My favorite snack is blueberries.”: HTTP 200; the saved entry has the requested title prefix and port, without the snack preferenceSet
max_tokens:40and ask to remember projectBudget-7ef8d177uses port8567, then reply exactlyok: HTTP 200, exactok, memory saved. Both streaming modes passResponses
With saving on and recall off, send
POST /v1/responseswith{"model":"memory-test","input":"Remember project AdminRecall-34fbd0 uses staging port 8764.","max_output_tokens":1200,"store":false}: HTTP 200; the record appears in/memory/v2/entriesAsk a fresh conversation using a sibling key for the port: HTTP 200,
UNKNOWNSwitch to saving off and recall on, then repeat: HTTP 200,
8764, with streaming both enabled and disabledSet capture guidance to save deployment ports, prefix titles with
Deployment note:, and skip food preferences. Turn saving on, then send “Project Guidance-f61eaad0 uses staging port 9284. My favorite snack is blueberries.”: HTTP 200; the saved entry has the requested title prefix and port, without the snack preferenceWith saving and recall enabled, run
curl -sS http://127.0.0.1:4018/v1/responses -H "Authorization: Bearer $ALICE_KEY" -H 'Content-Type: application/json' -d '{"model":"vertex_ai/claude-sonnet-5","input":"Find the staging port for AdminRecall-2fb672 in memory. Return only the port.","store":true,"max_output_tokens":1600}': HTTP 200, output text exactly8764, ID starting withresp_litellm_memory_Send another
POST http://127.0.0.1:4018/v1/responseswith that ID asprevious_response_id,input: "Repeat your previous answer exactly. Only the port.", andstream:true: HTTP 200, streamed text exactly8764, completed response text exactly8764Repeat with prompt logging disabled and enabled, and with native
gpt-5.5: all four combinations pass. Continue with a temporary random code and tools disabled, then ask for the preceding answer: exact code recovered. Branch from the first response:8764, without later branch contextCall an application function, submit its
function_call_outputwithprevious_response_id, and stream the answer: exact temporary code returned for both models and logging modes. These are 28 real requests with exact-output assertions, not substring matchesStart a native OpenAI conversation with memory off, enable memory, and resume twice: the original temporary code survives both transitions. Other keys cannot retrieve its memory-owned response (404), private
input_itemsstays unavailable (501),store:falseleaves no retained response (404), and turning recall off blocks resumption (403)Set
max_output_tokens:40and ask to remember projectBudget-d76a7728uses port8567, then reply exactlyok: HTTP 200, exactok, memory saved. Both streaming modes pass; the savedprevious_response_idalso resumes successfully. A 20-worker burst saved all 20 facts, returned 20 exactokreplies, and completed all 20 follow-ups. A poisoned response captured before this fix now returns 409 instead of OpenAI's permanent missing-tool-output 400Ask to capture eight long, exact public label sequences with
max_output_tokens:512, exhausting the 4,096-token preparation allowance. The final pass still returns HTTP 200 with the requested failure markerFAILED, and a follow-up also returnsFAILED. Inspection confirmed two model calls and no retained partial function callsMemory dashboard and administration
/ui/memory/: the table shows recent memories, contributor, team, search, and separate saving/recall statusArchitecture note:title prefix: a new real model request saves the PostgreSQL decision with that prefix without restarting the gatewayAdditional current-tip checks: recall-only requests did not create memories; turning recall off blocked a stored Responses continuation with HTTP 403. Both controls off returned an ordinary greeting with 8 input tokens. Local validation passed: 375 memory/Responses-authorization tests (2 skipped) and
make check. Dashboard source is unchanged in this token-budget fix; existing screenshots show the same layoutA stable provider prefix still produced 12,619 cache-read input tokens across gateway rounds after adding a saved memory. This enrollment cache is separate from provider prompt caching
Search currently scans every visible record in 128-row pages, retaining only the best results in memory. That bounds RAM, not total database and CPU work. A SQL candidate search with indexed fuzzy matching would improve large stores. Enrollment caching protects unenrolled traffic; enabled operations still read fresh permissions before and after access to avoid stale grants. Replacing those reads with cached user/team helpers requires reliable revocation invalidation
The primary pin deliberately fails closed instead of falling back to a potentially stale replica for permissions.
prisma_transactionis a typed adapter overdb.tx(), not another transaction implementation. V1 and V2 share the existing table, with V2 namespaces separating their rows and visibility rules. The two spend-writer P2028 errors in the reported load run remain unattributed; this patch does not change pool settings or claim to fix themType
New Feature
Caveats
Severe
Medium
Low
Final Attestation