---
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