--- phase: 05-hindsight-memory-provider plan: 01 type: execute wave: 1 depends_on: [] files_modified: - ~/.hermes/hindsight/config.json - ~/.hermes/.env - ~/.hermes/config.yaml autonomous: true requirements: - MEM-01 user_setup: [] must_haves: truths: - "`hermes config get memory.provider` returns `hindsight`" - "`~/.hermes/hindsight/config.json` exists with all locked config keys from D-04 through D-10" - "`grep HINDSIGHT_LLM_API_KEY ~/.hermes/.env` returns the env var with the same value as OPENROUTER_API_KEY" - "Hindsight daemon starts on first session init (verifiable via `~/.hermes/logs/hindsight-embed.log`)" - "Agent has all 3 hindsight tools available (hindsight_retain, hindsight_recall, hindsight_reflect) in hybrid mode" artifacts: - path: "~/.hermes/hindsight/config.json" provides: "Hindsight memory provider configuration with latency-optimized settings" contains: "mode: local_embedded, recall_budget: low, retain_every_n_turns: 5, retain_async: true, memory_mode: hybrid" - path: "~/.hermes/.env" provides: "HINDSIGHT_LLM_API_KEY env var for daemon LLM extraction" contains: "HINDSIGHT_LLM_API_KEY=sk-or-v1-" - path: "~/.hermes/config.yaml" provides: "memory.provider set to hindsight" key_links: - from: "~/.hermes/hindsight/config.json" to: "~/.hermes/.env" via: "HINDSIGHT_LLM_API_KEY env var in daemon startup" pattern: "HINDSIGHT_LLM_API_KEY" - from: "~/.hermes/config.yaml" to: "plugins/memory/hindsight/__init__.py" via: "memory.provider: hindsight activates HindsightMemoryProvider" pattern: "memory.provider.*hindsight" --- Activate Hindsight as the cross-session memory provider for ngn-agent in local embedded mode with latency-optimized settings. **Purpose:** Replace the limited built-in MEMORY.md/USER.md (2.2k chars, frozen per-session) with Hindsight's entity-aware knowledge graph memory for persistent cross-session recall. Pure configuration change — no code, no infrastructure, no Docker image changes. **Output:** - `~/.hermes/hindsight/config.json` — Hindsight config with all latency-optimized settings per D-01 through D-10 - `~/.hermes/.env` — `HINDSIGHT_LLM_API_KEY` env var appended (reuses existing OpenRouter key) - `~/.hermes/config.yaml` — `memory.provider` changed to `hindsight` @/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md @/Users/bapung/.config/opencode/gsd-core/templates/summary.md @/Users/bapung/Razer/ngn-agent/.planning/phases/05-hindsight-memory-provider/05-CONTEXT.md @/Users/bapung/Razer/ngn-agent/.planning/phases/05-hindsight-memory-provider/05-RESEARCH.md # Existing state to read before modifying @/Users/bapung/.hermes/config.yaml @/Users/bapung/.hermes/.env @/Users/bapung/.hermes/hermes-agent/plugins/memory/hindsight/README.md # Critical constraint — single provider only @/Users/bapung/.hermes/hermes-agent/plugins/memory/__init__.py Task 1: Create Hindsight config.json and add HINDSIGHT_LLM_API_KEY to .env ~/.hermes/hindsight/config.json (create) ~/.hermes/.env (append) @/Users/bapung/.hermes/hermes-agent/plugins/memory/hindsight/README.md (source of truth for all config keys) @/Users/bapung/.hermes/.env (current state — must read before appending) @/Users/bapung/Razer/ngn-agent/.planning/phases/05-hindsight-memory-provider/05-CONTEXT.md (verify locked decisions D-01 through D-10) @/Users/bapung/Razer/ngn-agent/.planning/phases/05-hindsight-memory-provider/05-RESEARCH.md (verify exact config values) Create `~/.hermes/hindsight/config.json` with the following EXACT content (per decisions D-01 through D-10): ```json { "mode": "local_embedded", "llm_provider": "openrouter", "llm_base_url": "https://openrouter.ai/api/v1", "llm_model": "qwen/qwen3.5-9b", "bank_id": "hermes", "recall_budget": "low", "recall_prefetch_method": "recall", "auto_recall": true, "recall_types": "observation", "auto_retain": true, "retain_async": true, "retain_every_n_turns": 5, "memory_mode": "hybrid" } ``` Step-by-step: 1. Read `~/.hermes/.env` to get the current `OPENROUTER_API_KEY` value: `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` 2. Create directory: `mkdir -p ~/.hermes/hindsight` 3. Write the config.json above using Write tool (not heredoc) 4. Append `HINDSIGHT_LLM_API_KEY=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` to `~/.hermes/.env` ONLY if it doesn't already exist (grep first). Add a comment line above it: `# Hindsight — LLM API key for local embedded mode (reuses OpenRouter key)` Key configuration rationale: - `mode: local_embedded` (D-01): Local PostgreSQL daemon managed by Hermes. No external data send. - `llm_provider: openrouter` (D-01): Use existing OpenRouter key. The `llm_base_url` matches OpenRouter's OpenAI-compatible endpoint. - `llm_model: qwen/qwen3.5-9b` (D-01): Model per-provider default from plugin. - `bank_id: hermes` (discretion): Default bank name from plugin. - `recall_budget: low` (D-04): Fastest retrieval, minimal latency overhead per turn. - `recall_prefetch_method: recall` (D-05): Raw fact search — no LLM synthesis in the hot path. - `auto_recall: true` (D-06): Auto-inject context before each turn. - `recall_types: observation` (D-07): Observations only, denser per token. - `auto_retain: true` (D-10): Automatic retention active. - `retain_async: true` (D-08): Processing in background, never blocks agent loop. - `retain_every_n_turns: 5` (D-09): Extract memories every 5 turns (~80% overhead reduction). - `memory_mode: hybrid` (D-02): Auto-inject relevant memories + expose all 3 hindsight tools (hindsight_retain, hindsight_recall, hindsight_reflect). - No `bank_mission` or `bank_retain_mission` (discretion): Keep defaults (none). - Do NOT set `api_url` (not needed for local_embedded mode — plugin defaults to localhost:9177). - Do NOT set `HINDSIGHT_API_KEY` (not needed for local_embedded — that's for cloud mode). test -f ~/.hermes/hindsight/config.json && echo "config.json EXISTS" || echo "config.json MISSING" # Validate JSON syntax python3 -c "import json; json.load(open('$HOME/.hermes/hindsight/config.json')); print('JSON VALID')" # Verify all locked keys are present python3 -c " import json c = json.load(open('$HOME/.hermes/hindsight/config.json')) checks = { 'mode': c.get('mode') == 'local_embedded', 'llm_provider': c.get('llm_provider') == 'openrouter', 'llm_base_url': c.get('llm_base_url') == 'https://openrouter.ai/api/v1', 'llm_model': c.get('llm_model') == 'qwen/qwen3.5-9b', 'bank_id': c.get('bank_id') == 'hermes', 'recall_budget': c.get('recall_budget') == 'low', 'recall_prefetch_method': c.get('recall_prefetch_method') == 'recall', 'auto_recall': c.get('auto_recall') == True, 'recall_types': c.get('recall_types') == 'observation', 'auto_retain': c.get('auto_retain') == True, 'retain_async': c.get('retain_async') == True, 'retain_every_n_turns': c.get('retain_every_n_turns') == 5, 'memory_mode': c.get('memory_mode') == 'hybrid', } all_ok = all(checks.values()) for k, v in checks.items(): status = 'OK' if v else 'FAIL' print(f' [{status}] {k}: {c.get(k)}') assert all_ok, 'Config validation FAILED' print('ALL CHECKS PASSED') " # Verify HINDSIGHT_LLM_API_KEY exists in .env and matches OPENROUTER_API_KEY OR_KEY=$(grep '^OPENROUTER_API_KEY=' ~/.hermes/.env | cut -d= -f2-) HL_KEY=$(grep '^HINDSIGHT_LLM_API_KEY=' ~/.hermes/.env | cut -d= -f2-) if [ "$OR_KEY" = "$HL_KEY" ]; then echo "ENV KEY MATCH: $OR_KEY"; else echo "ENV KEY MISMATCH"; fi - `~/.hermes/hindsight/config.json` exists and is valid JSON - All 13 config keys from D-01 through D-10 are present with correct values as verified by the python validation script - `HINDSIGHT_LLM_API_KEY=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` is in `~/.hermes/.env` - `HINDSIGHT_LLM_API_KEY` value is identical to `OPENROUTER_API_KEY` value Hindsight config.json created with all 13 locked settings, env var added matching existing OpenRouter key Task 2: Set memory.provider to hindsight, restart gateway, and verify full memory provider activation ~/.hermes/config.yaml (modified via `hermes config set`) @/Users/bapung/.hermes/hermes-agent/plugins/memory/__init__.py (read the single-provider constraint to understand the activation path) @/Users/bapung/.hermes/config.yaml (read current memory.provider value — should be empty string before change) Perform the following STEPS IN ORDER: STEP 1 — Set memory provider: Run: `hermes config set memory.provider hindsight` This modifies `~/.hermes/config.yaml` setting `memory.provider: hindsight`. NOTE: The current value at line 355 is `provider: ''` (empty string), meaning no external provider is set. After this command, it becomes `provider: hindsight`. The existing `memory.memory_enabled: true` and `memory.user_profile_enabled: true` remain unchanged — built-in memory continues as fallback (per D-03). STEP 2 — Restart gateway: Run: `hermes gateway restart` This reloads config.yaml and activates the Hindsight provider. The plugin will: - Detect `hindsight-all` is already installed in the venv (verified: 0.8.2) - Read `~/.hermes/hindsight/config.json` - Check existing `~/.hindsight/profiles/hermes.env` (already exists from previous attempt) - Regenerate the profile env if it doesn't match config.json settings (per plugin __init__.py initialize() logic — Pitfall 5 prevention) - The daemon will NOT start until the first session access (lazy init, not gateway restart) STEP 3 — Verify provider is active: Run: `hermes config get memory.provider` Expected output: `hindsight` Run: `hermes memory status` Expected: Shows hindsight as the active memory provider. Output may say "not available" or similar for daemon status since daemon only starts on first session access (this is expected behavior — the provider IS registered, just the daemon hasn't been launched yet). STEP 4 — Verify no provider conflict: Run: `grep -i "Rejected memory provider" ~/.hermes/hermes-agent/logs/* 2>/dev/null || echo "No provider conflicts detected"` Expected: No output (no rejections) since only one provider is set. STEP 5 — Verify daemon readiness (check daemon binary exists in venv): Run: `~/.hermes/hermes-agent/venv/bin/hindsight-embed --version 2>&1 || echo "(version flag not supported — this is fine)"` Expected: Binary exists (no "not found" error). STEP 6 — Final verification that all components are in place: Run a comprehensive check combining config file, env var, and provider setting: ``` echo "=== Hindsight Phase 5 Verification ===" echo "1. Config file: $(test -f ~/.hermes/hindsight/config.json && echo 'EXISTS' || echo 'MISSING')" echo "2. Provider setting: $(hermes config get memory.provider)" echo "3. Env var: $(grep -c '^HINDSIGHT_LLM_API_KEY=' ~/.hermes/.env | xargs -I{} echo 'FOUND ({})' )" echo "4. Package: $(uv pip show hindsight-all --python ~/.hermes/hermes-agent/venv/bin/python 2>/dev/null | grep Version || echo 'NOT FOUND')" echo "5. Daemon binary: $(ls ~/.hermes/hermes-agent/venv/bin/hindsight-embed 2>/dev/null && echo 'EXISTS' || echo 'NOT FOUND')" echo "6. Daemon profile: $(ls ~/.hindsight/profiles/hermes.env 2>/dev/null && echo 'EXISTS' || echo 'NOT FOUND')" echo "================================" ``` # Check provider is set correctly [ "$(hermes config get memory.provider)" = "hindsight" ] && echo "Provider OK" || echo "Provider FAIL" # Check no provider conflict in logs LOG_DIR=~/.hermes/hermes-agent/logs if [ -d "$LOG_DIR" ]; then CONFLICT_COUNT=$(grep -c "Rejected memory provider" "$LOG_DIR"/*.log 2>/dev/null || echo 0) if [ "$CONFLICT_COUNT" = "0" ]; then echo "No provider conflict detected" else echo "WARNING: $CONFLICT_COUNT provider conflict(s) found" fi else echo "No logs yet (no sessions started) — expected at this stage" fi # Full config validation using hermes config get (reads live config) hermes config get memory.provider | grep -q hindsight || echo "CONFIG MISMATCH" - `hermes config get memory.provider` returns `hindsight` - No "Rejected memory provider" warnings in logs (run `grep` check) - `hindsight-embed` binary is present in the Hermes venv at `~/.hermes/hermes-agent/venv/bin/hindsight-embed` - `hermes memory status` runs without errors and shows hindsight as the registered provider Hindsight memory provider is set as the active external provider, gateway restarted, all verifications pass ## Artifacts This Phase Produces ### New files | Artifact | Path | Purpose | |----------|------|---------| | Hindsight config | `~/.hermes/hindsight/config.json` | All Hindsight configuration keys per D-01 through D-10 | ### Modified files | Artifact | Path | Change | |----------|------|--------| | Env file | `~/.hermes/.env` | Added `HINDSIGHT_LLM_API_KEY` env var (same value as `OPENROUTER_API_KEY`) | | Hermes config | `~/.hermes/config.yaml` | Changed `memory.provider` from `""` to `hindsight` | ### New env vars | Env Var | Value | Source | |---------|-------|--------| | `HINDSIGHT_LLM_API_KEY` | `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` | Same as existing `OPENROUTER_API_KEY` in `~/.hermes/.env` | ### New config keys in `~/.hermes/config.yaml` | Config Key | Value | Description | |------------|-------|-------------| | `memory.provider` | `hindsight` | Enables Hindsight as the active external memory provider | ### New config keys in `~/.hermes/hindsight/config.json` | Key | Value | Decision | |-----|-------|----------| | `mode` | `local_embedded` | D-01 | | `llm_provider` | `openrouter` | D-01 | | `llm_base_url` | `https://openrouter.ai/api/v1` | D-01 | | `llm_model` | `qwen/qwen3.5-9b` | D-01 | | `bank_id` | `hermes` | Discretion | | `recall_budget` | `low` | D-04 | | `recall_prefetch_method` | `recall` | D-05 | | `auto_recall` | `true` | D-06 | | `recall_types` | `observation` | D-07 | | `auto_retain` | `true` | D-10 | | `retain_async` | `true` | D-08 | | `retain_every_n_turns` | `5` | D-09 | | `memory_mode` | `hybrid` | D-02 | ## Trust Boundaries | Boundary | Description | |----------|-------------| | Agent process ↔ OpenRouter API | LLM extraction calls (memory entity extraction) cross the process-to-internet boundary with conversation turn data | | Agent process ↔ Local PostgreSQL (daemon) | Memory data persisted in local embedded PostgreSQL via localhost:9177 — no network boundary | | Agent loop ↔ Hindsight provider | Plugin hooks (queue_prefetch, sync_turn) operate in-process with background threads | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-05-01 | Information Disclosure | OpenRouter API key reused as HINDSIGHT_LLM_API_KEY | mitigate | Key already stored in `~/.hermes/.env` with 0600 permissions (existing protection). Same key reused — no new secret to manage. Key is not logged beyond daemon startup. | | T-05-02 | Denial of Service | Daemon startup failure / PostgreSQL embedded crash | mitigate | Daemon failure is non-fatal — agent continues with built-in memory fallback (D-03). Verify daemon log post-session-start. If daemon fails, memories are silently lost (async retain) but agent loop is unaffected. | | T-05-03 | Elevation of Privilege | Daemon port localhost:9177 | mitigate | Daemon binds to localhost only (default). Not exposed to Docker container or network. Confirmed via README — no `HINDSIGHT_API_HOST` set means `0.0.0.0` is default but only reachable from host. | | T-05-04 | Information Disclosure | Conversation data sent to OpenRouter for LLM extraction | accept | Local embedded mode means NO external data send to Hindsight Cloud. LLM extraction calls to OpenRouter carry only the conversation turn (~70-200 chars), not full history. This is the same OpenRouter endpoint already used for primary LLM calls. Equivalent risk to existing model calls. | | T-05-05 | Tampering | Async retain silent failure | accept | Async retain failures are logged as warnings. Mitigation is monitoring (`grep "Hindsight retain failed"`). Not blocking the agent loop is intentional per D-08. | | T-05-SC | Tampering | Python package installations (`hindsight-all`, `hindsight-client`, `hindsight-embed`, `hindsight-api-slim`) | mitigate | Package legitimacy verified in RESEARCH.md §Package Legitimacy Audit — all packages from Vectorize.io (same org as Hermes plugin). Already installed at version 0.8.2. No new install needed. | ### Residual Risk | Risk | Why Accepted | |------|--------------| | LLM extraction timeout or error causes memory loss for that turn | Async retain swallows failures; memories from that turn are lost. Mitigated by `retain_every_n_turns: 5` — next retain window will capture subsequent turns. | | OpenRouter rate limiting on extraction calls | Same key used for model calls and extraction; extraction happens every 5 turns (~70-200 chars), which is negligible compared to model call traffic. | Run after all tasks complete: ```bash echo "=== Phase 5 Full Verification ===" echo "--- Step 1: Provider Check ---" hermes config get memory.provider echo "--- Step 2: Config File ---" python3 -c "import json; c=json.load(open('$HOME/.hermes/hindsight/config.json')); print('mode:', c['mode']); print('budget:', c['recall_budget']); print('async retain:', c['retain_async']); print('every_n:', c['retain_every_n_turns']); print('memory_mode:', c['memory_mode'])" echo "--- Step 3: Env Var ---" grep '^HINDSIGHT_LLM_API_KEY=' ~/.hermes/.env | grep -o '^HINDSIGHT_LLM_API_KEY=sk-or-v1-' && echo "Format OK" echo "--- Step 4: Package ---" uv pip show hindsight-all --python ~/.hermes/hermes-agent/venv/bin/python 2>/dev/null | grep -E '(Name|Version)' echo "--- Step 5: Daemon Binary ---" ls -la ~/.hermes/hermes-agent/venv/bin/hindsight-embed 2>/dev/null echo "--- Step 6: Daemon Profile (pre-existing) ---" ls -la ~/.hindsight/profiles/hermes.env 2>/dev/null echo "--- Step 7: Provider Conflict Check ---" grep -i "Rejected memory provider" ~/.hermes/hermes-agent/logs/*.log 2>/dev/null || echo "No conflicts" echo "=== Verification Complete ===" ``` 1. `hermes config get memory.provider` returns `hindsight` — provider activated 2. `~/.hermes/hindsight/config.json` exists with valid JSON containing all 13 locked settings — config complete 3. `grep HINDSIGHT_LLM_API_KEY ~/.hermes/.env` returns the env var matching OPENROUTER_API_KEY — LLM extraction configured 4. `~/.hermes/hermes-agent/venv/bin/hindsight-embed` binary exists — daemon ready for first session start 5. No "Rejected memory provider" warnings in gateway logs — no provider conflict 6. Built-in MEMORY.md/USER.md continues working in parallel (no config changes to memory_enabled/user_profile_enabled) — fallback preserved per D-03 Create `.planning/phases/05-hindsight-memory-provider/05-01-SUMMARY.md` when done