diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a700dc9..8e38a4b 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -37,7 +37,10 @@ 2. Agent detects similar previous sessions via hindsight similarity search on session start 3. Memory provider is set to `hindsight` (verifiable in config and agent state) 4. No memory provider conflicts — only one external provider registered (Hindsight, not MEMORY.md+user) -**Plans**: TBD +**Plans**: 1 plan + +Plans: +- [ ] 05-01-PLAN.md — Hindsight Memory Provider: Create config.json, set env var, activate provider, verify ### Phase 6: Default Repos & SSH Mount **Goal**: Default repos auto-cloned into every new session with secure credential mounting for git operations @@ -83,7 +86,7 @@ | 2. Memory, Git & Session Management | v1.0 | — | Complete | 2026-06-14 | | 3. Telegram Gateway | v1.0 | — | Complete | 2026-06-14 | | 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 | -| 5. Hindsight Memory Provider | v1.1 | 0/TBD | Not started | - | +| 5. Hindsight Memory Provider | v1.1 | 0/1 | Not started | - | | 6. Default Repos & SSH Mount | v1.1 | 0/TBD | Not started | - | | 7. Main Session Skill | v1.1 | 0/TBD | Not started | - | | 8. Cron Reporting | v1.1 | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 1b838b2..c217d98 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,11 +5,11 @@ milestone_name: Session Lifecycle, Memory & Reporting status: planning stopped_at: Phase 5 context gathered last_updated: "2026-06-14T10:18:04.833Z" -last_activity: 2026-06-14 — Milestone v1.1 roadmap created +last_activity: 2026-06-14 — Phase 5 plan created progress: total_phases: 4 completed_phases: 0 - total_plans: 0 + total_plans: 1 completed_plans: 0 percent: 0 --- @@ -26,9 +26,9 @@ See: .planning/PROJECT.md (updated 2026-06-14) ## Current Position Phase: 5 of 8 (Hindsight Memory Provider) -Plan: — (not yet planned) -Status: Not started — roadmap defined, ready to plan -Last activity: 2026-06-14 — Milestone v1.1 roadmap created +Plan: 1 plan (05-01-PLAN.md) +Status: Planned — ready to execute +Last activity: 2026-06-14 — Phase 5 plan created Progress: [░░░░░░░░░░] 0% diff --git a/.planning/phases/05-hindsight-memory-provider/05-01-PLAN.md b/.planning/phases/05-hindsight-memory-provider/05-01-PLAN.md new file mode 100644 index 0000000..a54df1e --- /dev/null +++ b/.planning/phases/05-hindsight-memory-provider/05-01-PLAN.md @@ -0,0 +1,374 @@ +--- +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 +