12 KiB
Memory Gateway User Provisioning Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Move Beaver's Gateway code into beaver.memory.gateway, load one shared non-secret Gateway configuration, provision Gateway users during Beaver registration, and resolve per-user credentials for each authenticated chat run.
Architecture: EngineLoader loads curated memory, a shared Gateway config, and an instance-local credential store. Registration calls Gateway /users and atomically stores credentials by Beaver username. REST/WebSocket chat derive a trusted username from the access token and AgentLoop creates a run-local Gateway service for that user, leaving unauthenticated or unprovisioned runs on curated memory only.
Tech Stack: Python 3.14, dataclasses, FastAPI, httpx, pytest, shell-based Docker instance creation.
Task 1: Move Gateway source and load shared configuration
Files:
-
Create:
app-instance/backend/beaver/memory/gateway/__init__.py -
Create:
app-instance/backend/beaver/memory/gateway/config.py -
Create:
app-instance/backend/beaver/memory/gateway/client.py -
Create:
app-instance/backend/beaver/memory/gateway/service.py -
Create:
app-instance/backend/memory/config.json -
Modify:
app-instance/backend/beaver/foundation/config/schema.py -
Modify:
app-instance/backend/beaver/foundation/config/loader.py -
Modify:
app-instance/backend/beaver/foundation/config/__init__.py -
Delete:
app-instance/backend/beaver/integrations/memory_gateway/ -
Delete:
app-instance/backend/beaver/services/memory_gateway_service.py -
Modify:
app-instance/backend/beaver/services/__init__.py -
Test:
app-instance/backend/tests/unit/test_config_loader.py -
Test:
app-instance/backend/tests/unit/test_memory_gateway_service.py -
Step 1: Write failing shared-config and import tests
Set BEAVER_MEMORY_CONFIG_PATH to a temporary JSON file and assert load_config() obtains memory.mode, URL, and all three scopes from that file. Change all Gateway tests to import from beaver.memory.gateway.
- Step 2: Run tests and verify RED
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_config_loader.py tests/unit/test_memory_gateway_service.py
Expected: failures because the new package and shared config loading do not exist.
- Step 3: Implement package migration and shared config parsing
Move existing client/service behavior without changing payloads. Define MemoryConfig and MemoryGatewayConfig in beaver.memory.gateway.config, without userId/userKey. Add default_memory_config_path() using BEAVER_MEMORY_CONFIG_PATH then <backend-root>/memory/config.json. Instance config parsing remains responsible for non-memory settings; shared config supplies BeaverConfig.memory.
Create tracked memory/config.json with http://172.19.207.37:8010, scopes current_chat, resources, all_user_memory, top K 8, and timeout 10.
- Step 4: Run targeted tests and verify GREEN
Run the command from Step 2. Expected: selected tests pass.
- Step 5: Commit
git add app-instance/backend/beaver/memory/gateway app-instance/backend/memory/config.json app-instance/backend/beaver/foundation/config app-instance/backend/beaver/services app-instance/backend/tests/unit/test_config_loader.py app-instance/backend/tests/unit/test_memory_gateway_service.py
git commit -m "refactor(memory): move gateway into memory domain"
Task 2: Add per-instance Gateway credential storage
Files:
-
Create:
app-instance/backend/beaver/memory/gateway/credentials.py -
Modify:
app-instance/backend/beaver/memory/gateway/__init__.py -
Create:
app-instance/backend/tests/unit/test_memory_gateway_credentials.py -
Modify:
app-instance/create-instance.sh -
Modify:
app-instance/entrypoint.sh -
Modify:
app-instance/README.md -
Step 1: Write failing credential-store tests
Cover missing files, multi-user round trips, updates preserving other users, secret-free repr, atomic replace, and mode 0600.
- Step 2: Run test and verify RED
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_credentials.py
Expected: import failure because the credential store does not exist.
- Step 3: Implement atomic credential persistence
Implement MemoryGatewayUserCredential(user_id, user_key) and MemoryGatewayCredentialStore.get/save. Use JSON shape {"users": {username: {"userId": ..., "userKey": ...}}}, sibling temporary file, os.replace, and chmod(0o600).
Update create-instance.sh to create $BEAVER_HOME/memory_gateway_users.json as {"users": {}}, chmod it 0600, and pass BEAVER_MEMORY_GATEWAY_USERS_PATH=/root/.beaver/memory_gateway_users.json. entrypoint.sh exports the same default.
- Step 4: Run credential and shell syntax tests
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_credentials.py
cd ../..
bash -n app-instance/create-instance.sh app-instance/entrypoint.sh
Expected: tests pass and shell syntax exits zero.
- Step 5: Commit
git add app-instance/backend/beaver/memory/gateway app-instance/backend/tests/unit/test_memory_gateway_credentials.py app-instance/create-instance.sh app-instance/entrypoint.sh app-instance/README.md
git commit -m "feat(memory): persist gateway user credentials"
Task 3: Provision Gateway identities during frontend registration
Files:
-
Modify:
app-instance/backend/beaver/memory/gateway/client.py -
Modify:
app-instance/backend/beaver/interfaces/web/app.py -
Create:
app-instance/backend/tests/unit/test_memory_gateway_registration.py -
Step 1: Write failing registration tests
Use a temporary auth file and fake Gateway client. Assert registration sends {"user_id": "tom"}, stores the returned key, never returns the key to the browser, and still registers the Beaver user without a partial credential when Gateway provisioning fails.
- Step 2: Run tests and verify RED
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_registration.py
Expected: failures because /users provisioning is not connected.
- Step 3: Implement provisioning
Add MemoryGatewayClient.create_user(user_id), validating non-empty response user_id/user_key. During /api/auth/register, after local/AuthZ registration succeeds, call it with the Beaver username and save through the credential store. Catch sanitized Gateway failures without retrying or rolling back Beaver registration. Never include the Gateway credential in the response.
- Step 4: Run registration tests and verify GREEN
Run the command from Step 2. Expected: all registration tests pass.
- Step 5: Commit
git add app-instance/backend/beaver/memory/gateway/client.py app-instance/backend/beaver/interfaces/web/app.py app-instance/backend/tests/unit/test_memory_gateway_registration.py
git commit -m "feat(memory): provision gateway users on registration"
Task 4: Pass trusted authenticated identity into chat runs
Files:
-
Modify:
app-instance/backend/beaver/interfaces/web/app.py -
Modify:
app-instance/backend/beaver/engine/loop.py -
Modify:
app-instance/backend/beaver/services/agent_service.py -
Modify:
app-instance/backend/tests/unit/test_websocket_chat.py -
Step 1: Write failing REST/WebSocket identity tests
Issue a web token for tom. Assert REST and WebSocket calls pass gateway_user_id="tom". Send a conflicting client user_id="other" and assert the trusted identity remains tom. Unauthenticated calls pass gateway_user_id=None.
- Step 2: Run tests and verify RED
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_websocket_chat.py
Expected: identity assertions fail because chat does not pass a trusted Gateway principal.
- Step 3: Implement optional trusted identity resolution
Add gateway_user_id: str | None to AgentLoop direct-run kwargs. REST reads the optional bearer token from Authorization; WebSocket reads the existing ?token= parameter. Both resolve only through app.state.auth_tokens. Request user_id remains session metadata and never selects Gateway credentials.
- Step 4: Run identity tests and verify GREEN
Run the command from Step 2. Expected: tests pass.
- Step 5: Commit
git add app-instance/backend/beaver/interfaces/web/app.py app-instance/backend/beaver/engine/loop.py app-instance/backend/beaver/services/agent_service.py app-instance/backend/tests/unit/test_websocket_chat.py
git commit -m "feat(memory): bind gateway runs to authenticated users"
Task 5: Resolve a run-local Gateway service per user
Files:
-
Modify:
app-instance/backend/beaver/engine/loader.py -
Modify:
app-instance/backend/beaver/engine/loop.py -
Modify:
app-instance/backend/beaver/memory/gateway/service.py -
Modify:
app-instance/backend/tests/unit/test_memory_gateway_loader.py -
Modify:
app-instance/backend/tests/unit/test_memory_gateway_agent_loop.py -
Step 1: Write failing loader and AgentLoop tests
Assert loader exposes shared config, credential store, and service factory instead of a fixed-user service. Add two users with different keys and verify each run constructs a service from only the selected credential. Missing identity or credential performs no Gateway calls while curated memory remains present.
- Step 2: Run tests and verify RED
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_loader.py tests/unit/test_memory_gateway_agent_loop.py
Expected: failures because loader still creates one fixed-user service.
- Step 3: Implement per-run service resolution
Expose memory_gateway_config, memory_gateway_credentials, and a service factory on EngineLoadResult. At run start, resolve the credential by gateway_user_id; construct a fresh service only in hybrid mode when a credential exists. Pass shared config and credential separately to the service and preserve current recall/add/flush/audit behavior.
- Step 4: Run Gateway runtime tests and verify GREEN
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_loader.py tests/unit/test_memory_gateway_agent_loop.py tests/unit/test_memory_gateway_service.py tests/unit/test_context_builder.py
Expected: all selected tests pass.
- Step 5: Commit
git add app-instance/backend/beaver/engine app-instance/backend/beaver/memory/gateway app-instance/backend/tests/unit/test_memory_gateway_loader.py app-instance/backend/tests/unit/test_memory_gateway_agent_loop.py
git commit -m "feat(memory): resolve gateway service per user"
Task 6: Update documentation and perform final verification
Files:
-
Modify:
app-instance/backend/README.md -
Modify:
app-instance/README.md -
Modify:
docs/superpowers/plans/2026-06-15-hybrid-memory-gateway.md -
Step 1: Update operational documentation
Document the shared config path, instance credential path, registration provisioning, token-based identity, secret handling, and rebuild/restart requirements. Remove examples that place userId/userKey in instance config.json.
- Step 2: Verify removed source imports
rg -n "beaver\.integrations\.memory_gateway|beaver\.services\.memory_gateway_service" app-instance/backend/beaver app-instance/backend/tests
Expected: no matches.
- Step 3: Run full verification
cd app-instance/backend
.venv/bin/python -m compileall -q beaver
.venv/bin/pytest -q
cd ../..
bash -n app-instance/create-instance.sh app-instance/entrypoint.sh
git diff --check
Expected: compile and shell checks exit zero, all tests pass, and diff check is clean.
- Step 4: Scan tracked content for credentials
git grep -nE 'uk_[A-Za-z0-9]{8,}' -- ':!docs/superpowers/specs/*' ':!docs/superpowers/plans/*'
Expected: no real Gateway key in tracked source or runtime files; obvious test placeholders are reviewed manually.
- Step 5: Commit
git add app-instance/backend/README.md app-instance/README.md docs/superpowers/plans/2026-06-15-hybrid-memory-gateway.md
git commit -m "docs(memory): document gateway user provisioning"