Files

20 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup must_haves
05-hindsight-memory-provider 01 execute 1
~/.hermes/hindsight/config.json
~/.hermes/.env
~/.hermes/config.yaml
true
MEM-01
truths artifacts key_links
`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
path provides contains
~/.hermes/hindsight/config.json Hindsight memory provider configuration with latency-optimized settings mode: local_embedded, recall_budget: low, retain_every_n_turns: 5, retain_async: true, memory_mode: hybrid
path provides contains
~/.hermes/.env HINDSIGHT_LLM_API_KEY env var for daemon LLM extraction HINDSIGHT_LLM_API_KEY=sk-or-v1-
path provides
~/.hermes/config.yaml memory.provider set to hindsight
from to via pattern
~/.hermes/hindsight/config.json ~/.hermes/.env HINDSIGHT_LLM_API_KEY env var in daemon startup HINDSIGHT_LLM_API_KEY
from to via pattern
~/.hermes/config.yaml plugins/memory/hindsight/__init__.py memory.provider: hindsight activates HindsightMemoryProvider 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/.envHINDSIGHT_LLM_API_KEY env var appended (reuses existing OpenRouter key)
  • ~/.hermes/config.yamlmemory.provider changed to hindsight

<execution_context> @/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md @/Users/bapung/.config/opencode/gsd-core/templates/summary.md </execution_context>

@/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
</automated>
- `~/.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"
</automated>
- `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

<threat_model>

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.
</threat_model>
Run after all tasks complete:
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 ==="

<success_criteria>

  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 </success_criteria>
Create `.planning/phases/05-hindsight-memory-provider/05-01-SUMMARY.md` when done