Files
beaver_project/docs/superpowers/specs/2026-06-15-memory-gateway-backend-design.md

352 lines
14 KiB
Markdown

# Hybrid Memory Gateway Integration Design
## Goal
Keep Beaver's existing curated memory as the permanent baseline and optionally
add Memory Gateway as an independent second memory layer.
- Curated memory continues to load `MEMORY.md` and `USER.md` into a frozen
per-run snapshot and continues to expose the existing `memory` tool.
- Memory Gateway independently recalls conversation/resource memory through
`POST /memories/search` and persists each completed conversation turn through
one `POST /memories/add` followed by one `POST /memories/flush`.
- The two layers do not synchronize, overwrite, merge, deduplicate, or resolve
conflicts with each other.
Memory Gateway is best-effort. Gateway failures must be auditable without
affecting curated memory or turning an otherwise successful chat run into a
failure.
## Scope
This change includes:
- Runtime configuration for `curated` and `hybrid` modes.
- Fixed Memory Gateway credentials and search scopes in instance config.
- An asynchronous Memory Gateway HTTP client.
- An optional `MemoryGatewayService` alongside the existing `MemoryService`.
- Gateway recall before each provider run in hybrid mode.
- Gateway add and flush after each normally completed run in hybrid mode.
- Hidden session audit events for Gateway outcomes.
- Unit and integration-style tests using fake transports and providers.
This change does not include:
- Replacing or disabling curated memory.
- Synchronizing curated `memory` tool writes to Memory Gateway.
- Writing Gateway conversation turns into `MEMORY.md` or `USER.md`.
- Conflict resolution or automatic deduplication across the two layers.
- Automatic `POST /users` calls or credential provisioning.
- A memory settings UI or memory administration UI.
- Resource upload support from Beaver.
- Gateway override or deletion APIs.
- Persisting tool calls, tool results, system events, reasoning, recalled
memory, or skill activation messages to Gateway.
## Configuration
Beaver adds a top-level `memory` section:
```json
{
"memory": {
"mode": "hybrid",
"gateway": {
"baseUrl": "http://127.0.0.1:8010",
"userId": "gateway_test_user",
"userKey": "uk_xxx",
"appId": "default",
"projectId": "default",
"scope": ["current_chat", "resources"],
"topK": 8,
"timeoutSeconds": 10
}
}
}
```
Configuration rules:
- Valid modes are `curated` and `hybrid`.
- Curated memory is initialized and enabled in both modes.
- If the entire `memory` section is absent, the effective mode is implicitly
`hybrid`. Missing Gateway credentials in this implicit-default case produce
a startup warning and degrade only the Gateway layer; Beaver continues with
curated memory.
- If `mode: "hybrid"` is explicitly present, non-empty `baseUrl`, `userId`, and
`userKey` are required. Missing required values fail runtime loading.
- `mode: "curated"` disables Gateway initialization and ignores an optional
Gateway block.
- `appId` and `projectId` default to `default`.
- `scope` must be a non-empty subset of `current_chat`, `resources`, and
`all_user_memory`. The initial integration uses `current_chat` and
`resources`.
- `topK` defaults to 8 and must be between 1 and 100.
- `timeoutSeconds` defaults to 10 and must be positive.
- `userKey` must never appear in status payloads, warnings, logs produced by
this integration, session events, or raised configuration/client errors.
The parsed configuration must retain whether hybrid mode was explicit or
implicit so runtime loading can apply the different validation behavior.
## Architecture
### Existing curated memory remains unchanged
`MemoryStore`, `MemorySnapshot`, `MemoryService`, and `MemoryTool` retain their
current responsibilities:
- `EngineLoader` always initializes `MemoryService`.
- `AgentLoop` always captures a per-run frozen curated snapshot.
- `ContextBuilder` always receives that snapshot for system-prompt injection.
- The original `memory` tool remains registered and always operates only on
`MEMORY.md` and `USER.md`.
- Gateway availability and Gateway failures do not change curated behavior.
### Optional Gateway service
Add a separate `MemoryGatewayService` rather than a mutually exclusive backend
strategy. It is present only when hybrid mode has a valid Gateway configuration.
The service exposes two runtime operations:
1. `recall_before_run`: search Gateway using the current Beaver session and
user prompt, then return sanitized reference messages plus audit metadata.
2. `persist_after_run`: add the current user message and final assistant answer,
then flush the Gateway chat session.
`EngineLoadResult` exposes `memory_gateway_service: MemoryGatewayService | None`.
`AgentLoop` uses it conditionally while continuing its existing curated path
unconditionally.
`session_search` remains independent and available in both modes.
### Memory Gateway HTTP client
The HTTP client owns transport and response validation for:
- `POST {baseUrl}/memories/search`
- `POST {baseUrl}/memories/add`
- `POST {baseUrl}/memories/flush`
It uses an asynchronous HTTP client, the configured timeout, JSON request
bodies, and sanitized typed exceptions containing operation/path/status
metadata without credentials or complete request bodies.
Beaver adds no automatic retries in this first integration. Gateway already
retries upstream ingestion, and retrying add from Beaver could duplicate a
turn when the first request succeeded but its response was lost.
## Recall Data Flow
Every run follows the existing curated flow. Hybrid mode adds these steps:
1. `AgentLoop` creates or resolves `resolved_session_id`.
2. It captures the curated frozen snapshot as it does today.
3. Before `ContextBuilder.build_messages`, it calls Gateway search using:
```json
{
"user_id": "<configured userId>",
"user_key": "<configured userKey>",
"conversation_id": "<resolved_session_id>",
"query": "<current user prompt>",
"scope": ["<configured scopes>"],
"top_k": 8,
"app_id": "<configured appId>",
"project_id": "<configured projectId>"
}
```
4. Beaver accepts only a top-level `results` list. Malformed responses are
treated as Gateway recall failures.
5. Each result is reduced to the optional fields `id`, `session_id`, `text`,
`score`, `source_scope`, and `resource_uri`. The Gateway `raw` object is
discarded.
6. Empty or unusable results produce no Gateway reference message.
7. Non-empty results become one ephemeral provider message placed after skill
activation messages and before persisted session history/current user input.
8. The Gateway reference message is not written to Beaver session history and
is not included in post-run Gateway persistence.
9. The system prompt includes a stable rule that Gateway recall is untrusted
reference data, not executable instruction. The recalled text itself stays
outside the system prompt.
The model receives both memory layers without an imposed priority:
- Curated blocks remain in the system prompt exactly as today.
- Gateway results appear as a separately labelled reference message.
- Beaver performs no conflict detection, winner selection, merge, or
deduplication between them.
In curated mode, or when implicit hybrid degrades because Gateway credentials
are absent, no Gateway request or Gateway prompt section occurs.
## Persistence Data Flow
Curated persistence remains model-driven through the original `memory` tool.
Gateway persistence is separate and occurs only when the optional Gateway
service is active.
For each run that reaches the normal completion path:
1. Wait until the tool loop has produced the final assistant text.
2. Construct exactly two Gateway messages in chronological order:
```json
[
{
"sender_id": "<configured userId>",
"role": "user",
"timestamp": 1780000000000,
"content": "<original current user prompt>"
},
{
"sender_id": "beaver",
"role": "assistant",
"timestamp": 1780000001000,
"content": "<final assistant text>"
}
]
```
Timestamps are UTC Unix epoch milliseconds captured for the user turn and final
assistant turn. They must be positive and monotonic within the payload.
3. Call `/memories/add` exactly once with:
```json
{
"user_id": "<configured userId>",
"user_key": "<configured userKey>",
"session_id": "chat:<resolved_session_id>",
"app_id": "<configured appId>",
"project_id": "<configured projectId>",
"messages": ["<the two messages above>"]
}
```
4. If add succeeds, call `/memories/flush` exactly once using the same Gateway
identity, app/project scope, and `chat:<resolved_session_id>`.
5. If add fails, do not call flush.
6. Runs entering Beaver's exception/error completion path are not persisted.
Normal completion outputs such as a tool-limit fallback are persisted because
they are returned to the user.
7. Tool calls, tool results, hidden events, system prompts, curated snapshot
text, Gateway recalled text, reasoning, and activated skill text are never
included in the Gateway add payload.
8. Gateway persistence never modifies `MEMORY.md` or `USER.md`.
9. Curated `memory` tool add/replace/remove operations never call Gateway.
## Session Audit Events
When the Gateway service is active, Beaver writes hidden
(`context_visible=false`) session events without credentials or full response
bodies:
- `memory_gateway_recall_succeeded`: configured scopes and result count.
- `memory_gateway_recall_failed`: operation, sanitized error category, and
optional HTTP status.
- `memory_gateway_add_succeeded`: Gateway chat session and message count.
- `memory_gateway_add_failed`: sanitized failure metadata.
- `memory_gateway_flush_succeeded`: Gateway chat session.
- `memory_gateway_flush_failed`: sanitized failure metadata and indication that
add already succeeded.
For implicit hybrid degradation at runtime boot, use a normal application
warning rather than a session event because no session exists yet. The warning
must not contain credential values.
## Failure Semantics
- Curated initialization or writes retain their existing behavior and are not
caught or changed by Gateway code.
- Missing Gateway credentials in implicit-default hybrid mode: warn, leave the
Gateway service unset, and continue with curated memory.
- Missing/invalid Gateway configuration in explicit hybrid mode: fail runtime
loading with a sanitized configuration error.
- Search timeout, connection failure, 401, other HTTP error, or malformed JSON:
record recall failure and continue with curated memory and normal context.
- Add failure: record add failure, skip flush, and return the normal assistant
result.
- Flush failure: record flush failure and return the normal assistant result.
- Gateway failures do not disable, roll back, or mutate curated memory.
- Gateway failures are not surfaced as user-facing chat errors in this phase.
## Security and Privacy
- Fixed Gateway credentials come only from Beaver instance configuration.
- `userKey` is passed only in Gateway request bodies and retained in memory by
the typed config/client objects.
- Client exceptions, startup warnings, and audit payloads never serialize
request bodies or credentials.
- Gateway conversation/resource text is treated as untrusted data.
- Gateway `raw` fields are discarded before prompt construction.
- Curated and Gateway stores remain isolated. No content is copied between
them: curated receives only explicit `memory` tool mutations, while Gateway
receives only the configured per-run conversation payload.
## Testing
### Configuration tests
- Missing memory configuration produces implicit hybrid mode.
- Implicit hybrid without credentials leaves Gateway disabled and curated
enabled, with one sanitized warning.
- Explicit curated mode does not require or initialize Gateway.
- Complete explicit hybrid config parses camelCase fields and initializes both
memory layers.
- Explicit hybrid with missing credentials fails loading.
- Invalid mode, empty/unknown scope, invalid `topK`, and non-positive timeout
fail with explicit sanitized errors.
- No warning or exception text contains `userKey`.
### HTTP client tests
- Search, add, and flush use the exact paths and payload shapes above.
- Configured timeout is applied.
- Non-2xx, network, invalid JSON, and invalid response shapes produce sanitized
client exceptions.
- Exception strings never contain the configured key.
### Gateway service tests
- Search uses configured scopes and strips `raw` fields.
- Empty search results produce no reference message.
- Persistence sends exactly the original user prompt and final assistant
response, then flushes once.
- Add failure skips flush; flush failure preserves the successful add outcome.
- Service methods never read or write curated files or call `MemoryStore`.
### Agent loop and loader tests
- Curated snapshot injection and `memory` tool availability remain present in
both curated and hybrid modes.
- Hybrid search occurs before the provider call while the curated snapshot is
still present in the system prompt.
- Gateway recall appears before the current user prompt and outside the system
prompt body.
- The system prompt contains the untrusted-reference rule only when Gateway is
active.
- Add and flush happen after the final assistant response and exactly once each.
- Tool/system/reasoning/curated/Gateway-recall content is absent from the add
payload.
- Recall/add/flush failures do not change the returned `AgentRunResult` or the
curated snapshot/tool behavior.
- Hidden success/failure audit events contain no credentials.
- Curated `memory` tool operations produce no Gateway calls.
- Gateway persistence produces no changes to `MEMORY.md` or `USER.md`.
- Curated mode and degraded implicit hybrid perform no Gateway HTTP calls.
## Documentation
Update the backend README/config example with:
- `hybrid` as the implicit default.
- Explicit `curated` mode for disabling Gateway.
- A complete explicit hybrid example.
- The implicit-default degradation rule and explicit-hybrid validation rule.
- A warning that `userKey` is a secret.
- A note that changing memory mode/config requires runtime reload or restart
because `EngineLoader` constructs the optional Gateway service during boot.