Files
ngn-agent/.planning/phases/05-hindsight-memory-provider/05-RESEARCH.md
Bagas Purwa Sentika 47d0b80be8 feat(08-cron-reporting): create archive-stale-sessions.sh with dry-run toggle
- Created ~/.hermes/scripts/archive-stale-sessions.sh (1146 bytes, executable)
- Script has DRY_RUN=true safe default (export only, no prune)
- Export uses date-stamped JSONL filename
- Prune gated behind DRY_RUN=false with --older-than 30 --yes
- set -euo pipefail strict error handling
- Created ~/.hermes/archive/sessions/ archive directory
- Progress echo statements for Telegram delivery
2026-06-15 22:45:14 +08:00

501 lines
33 KiB
Markdown

# Phase 5: Hindsight Memory Provider — Research
**Researched:** 2026-06-14
**Domain:** Cross-session persistent memory provider (Hermes plugin)
**Confidence:** HIGH
## Summary
Phase 5 activates Hindsight as the external memory provider for ngn-agent, replacing the limited built-in MEMORY.md/USER.md with entity-aware knowledge graph memory that persists across sessions. This is a pure configuration change — no code, no new infrastructure.
Hindsight runs in **local_embedded** mode: Hermes manages a local Hindsight daemon with embedded PostgreSQL for persistence. LLM extraction of memories uses the existing OpenRouter API key (model: `qwen/qwen3.5-9b`). Embeddings and reranking are local — no additional API keys needed.
**Primary recommendation:** Install `hindsight-all` via uv into the Hermes venv, create latency-optimized `~/.hermes/hindsight/config.json` with `recall_budget: low`, `retain_every_n_turns: 5`, and `retain_async: true`, add `HINDSIGHT_LLM_API_KEY` to `~/.hermes/.env` reusing the existing OpenRouter key, then set `memory.provider: hindsight` via `hermes config set`. The `~/.hindsight/profiles/hermes.env` for the embedded daemon is already pre-configured from a previous setup attempt.
**Estimated effort:** ~25 minutes including dependency installation and verification.
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Use **local embedded** mode — Hermes spins up a local PostgreSQL daemon. No external data send. Use existing OpenRouter API key (`OPENROUTER_API_KEY` in `~/.hermes/.env`) for LLM extraction via `HINDSIGHT_LLM_API_KEY`.
- Config: `mode: local_embedded` in `~/.hermes/hindsight/config.json`
- Env: `HINDSIGHT_LLM_API_KEY=sk-or-v1-...` (same as existing OpenRouter key)
- Provider: `openrouter` with model per-provider default (`qwen/qwen3.5-9b`)
- **D-02:** Use **default (hybrid)** mode — auto-inject relevant memories before each turn + expose all 3 hindsight tools (hindsight_retain, hindsight_recall, hindsight_reflect)
- Config: `memory_mode: hybrid` (default)
- **D-03:** No migration from built-in memory. MEMORY.md/USER.md continues as fallback in parallel.
- **D-04:** `recall_budget: low` — fastest retrieval, minimal latency overhead per turn
- **D-05:** `recall_prefetch_method: recall` — raw fact search (no LLM synthesis in hot path)
- **D-06:** `auto_recall: true` — auto-inject context before each turn
- **D-07:** `recall_types: observation` — default (observations only, denser per token)
- **D-08:** `retain_async: true` — processing happens in background, never blocks agent loop
- **D-09:** `retain_every_n_turns: 5` — extract memories every 5 turns instead of every turn
- **D-10:** `auto_retain: true` — automatic retention active
### the agent's Discretion
- **Bank configuration** (`bank_id`, `bank_mission`, `bank_retain_mission`): Use defaults (`bank_id: hermes`, no missions).
- **Daemon startup logs and runtime monitoring**: Standard Hermes daemon management applies.
### Deferred Ideas (OUT OF SCOPE)
None — no deferred items for this phase.
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| MEM-01 | Hindsight memory provider enabled for cross-session entity-aware recall | All dependencies, configuration paths, and latency-optimized settings documented below. Verification steps provided. |
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Cross-session memory storage | Local daemon (Hindsight embedded) | — | Hindsight daemon runs locally with embedded PostgreSQL; no external services |
| Memory extraction (LLM) | OpenRouter API | — | Existing OpenRouter key reused; daemon calls OpenRouter for entity extraction |
| Memory recall injection | Hermes agent loop (pre-turn hook) | — | Hindsight plugin injects relevant memories into context before each LLM turn via `queue_prefetch()` |
| Memory retain (background) | Hermes agent loop (post-turn) | Local daemon | `sync_turn()` enqueues retains on a background writer thread; daemon processes asynchronously |
| Tool-based memory access | Hermes agent (LLM chooses tools) | — | `hindsight_recall`, `hindsight_retain`, `hindsight_reflect` tools exposed to agent |
| Built-in memory fallback | Hermes agent loop | — | MEMORY.md/USER.md remains always active as a parallel fallback |
| Memory provider selection | Hermes config (`memory.provider`) | — | Single-source-of-truth config change; Hermes ensures only one external provider |
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| `hindsight-all` | 0.8.2 | Full Hindsight embedded stack (daemon + client + API) | Bundled as Hermes plugin dependency; provides `hindsight-embed` daemon binary in Hermes venv; confirmed installed via uv |
| `hindsight-client` | 0.8.2 | Python client library for Hindsight API | Hermes plugin `__init__.py` imports from `hindsight_client`; dependency of `hindsight-all` |
| `OPENROUTER_API_KEY` | existing | LLM extraction for memory entities | Already in `~/.hermes/.env`; reused as `HINDSIGHT_LLM_API_KEY` (D-01) |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| `hindsight-embed` | 0.8.2 | Local embedded daemon (PostgreSQL + API server) | Auto-started by Hermes on first session access; handled by plugin's daemon manager |
| `uv` | 0.11.19 | Python package manager | Already installed at `~/.local/bin/uv`; used by `hermes memory setup` and manual install |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| `hindsight-all` (local_embedded) | `hindsight-client` only (cloud mode) | Local mode requires ~200MB download and has ~2-4GB RAM overhead but zero external data send and no cloud dependency |
| OpenRouter for LLM extraction | Direct Anthropic/Gemini API key | OpenRouter key already exists; no additional credential management needed |
| `hermes memory setup` (wizard) | Manual config.json + env | Wizard is interactive (curses); manual approach is faster for automation but requires precise config file creation |
**Installation:**
```bash
# Install the full Hindsight embedded stack into Hermes venv
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python hindsight-all
```
**Version verification:** Both `hindsight-all` and `hindsight-client` resolve to version 0.8.2 [VERIFIED: installed via uv, confirmed in venv].
## Package Legitimacy Audit
> Required: this phase installs external packages.
| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
|---------|----------|-----|-----------|-------------|---------|-------------|
| `hindsight-all` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — Hermes v0.16.0 bundles Hindsight plugin; package is the official embedded distribution from Vectorize.io |
| `hindsight-client` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all`, listed in Hermes pyproject.toml extras |
| `hindsight-embed` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all` |
| `hindsight-api-slim` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all` |
**Packages removed due to [SLOP] verdict:** none
**Packages flagged as suspicious [SUS]:** none
**Postinstall scripts check:** None of these packages have `postinstall` scripts [VERIFIED: `npm view` not applicable — Python packages. Checked via `uv pip show` — no postinstall field.].
## Architecture Patterns
### System Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Hermes Agent Process │
│ │
│ ┌────────────────────────────┐ ┌──────────────────────────────────┐ │
│ │ MemoryManager │ │ HindsightMemoryProvider │ │
│ │ (orchestrates providers) │───▶│ (MemoryProvider ABC impl) │ │
│ │ │ │ │ │
│ │ ┌─────────────────────┐ │ │ ┌───────────────────────────┐ │ │
│ │ │ Built-in Memory │ │ │ │ Retain Queue (per-session)│ │ │
│ │ │ (MEMORY.md/USER.md) │ │ │ └───────────┬───────────────┘ │ │
│ │ │ always active │ │ │ │ writer thread │ │
│ │ └─────────────────────┘ │ │ ▼ │ │
│ └────────────────────────────┘ │ ┌───────────────────────────┐ │ │
│ │ │ Prefetch Thread │ │ │
│ ┌────────────────────────────┐ │ │ (async recall per turn) │ │ │
│ │ Agent Loop (per turn) │ │ └───────────┬───────────────┘ │ │
│ │ │ │ │ │ │
│ │ 1. queue_prefetch(query)──┼────┼──────────────┘ │ │
│ │ 2. prefetch() → inject ──┼────┼───────────────► context injection │ │
│ │ 3. LLM turn │ │ │ │
│ │ 4. sync_turn(turn) ──────┼────┼───────────────► enqueue retain │ │
│ └────────────────────────────┘ └───────────────┬───────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────┼──────────────┐ │
│ │ Background Threads │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Hindsight Embedded Daemon (localhost:9177) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ PostgreSQL │ │ Embeddings │ │ Reranker │ │ │ │
│ │ │ │ (embedded │ │ (local BGE │ │ (local cross-│ │ │ │
│ │ │ │ pg0) │ │ small-en) │ │ encoder) │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ └──────────────────────────┬─────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ OpenRouter API (external) │ │ │
│ │ │ LLM extraction: qwen/qwen3.5-9b via OpenRouter │ │ │
│ │ │ Model per-provider default from plugin __init__.py │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────┴──────────────────────┐
│ Hindsight Tools (hybrid mode) │
│ hindsight_retain │ hindsight_recall │
│ hindsight_reflect │ │
└─────────────────────────────────────────────┘
```
### Data Flow
1. **Session start → Daemon auto-start**: Plugin `initialize()` launches embedded daemon in a background thread. The daemon process runs on `localhost:9177` with embedded PostgreSQL.
2. **Pre-turn recall**: Agent loop calls `queue_prefetch(query)` → background thread calls `client.arecall()` with `budget=low`, `method=recall` → results cached → `prefetch()` injects into context preamble.
3. **Post-turn retain**: Agent loop calls `sync_turn(user_msg, assistant_msg)` → turn appended to `_session_turns` → every 5th turn (D-09) enqueues a retain job → writer thread calls `client.aretain_batch()` asynchronously.
4. **Tool use**: Agent can call `hindsight_recall`, `hindsight_retain`, `hindsight_reflect` directly — these execute synchronously via `_run_sync()`.
5. **Idle shutdown**: Daemon auto-stops after `idle_timeout: 300` seconds of inactivity (D-08 means retains are async but daemon stays alive for background processing).
### Recommended Project Structure
```
~/.hermes/
├── hindsight/
│ └── config.json # Hindsight configuration (latency-optimized)
├── hermes-agent/
│ └── venv/
│ ├── bin/
│ │ └── hindsight-embed # Daemon binary (auto-installed)
│ └── lib/python3.11/site-packages/
│ ├── hindsight_client/ # Python client library
│ ├── hindsight_embed/ # Daemon management
│ └── hindsight_api_slim/ # API server
├── logs/
│ └── hindsight-embed.log # Daemon startup logs
├── .env # Add HINDSIGHT_LLM_API_KEY here
└── config.yaml # Set memory.provider: hindsight
~/.hindsight/
├── profiles/
│ ├── hermes.env # Daemon LLM config (already exists, pre-configured)
│ ├── hermes.lock # Daemon lock file
│ └── hermes.log # Daemon runtime logs
└── config.json # Legacy fallback (optional)
```
### Pattern 1: Non-Interactive Memory Provider Setup
**What:** Skip the interactive `hermes memory setup` wizard by manually creating config files and setting config values. The wizard uses curses and prompts for values we already know.
**When to use:** Automation/scripted setup, repeatable provisioning.
**Steps:**
1. Install packages into Hermes venv
2. Create `~/.hermes/hindsight/config.json`
3. Add env var to `~/.hermes/.env`
4. Set config value in `~/.hermes/config.yaml`
**Source:** [VERIFIED: hermes-cli/memory_setup.py and plugins/memory/hindsight/__init__.py — both support post_setup wizard OR manual config]
### Pattern 2: Latency-optimized Memory Profile
**What:** Hindsight's config supports three latency tiers for recall (`low`/`mid`/`high`). The `low` budget returns fewer results faster. Combined with `recall` (not `reflect`) prefetch and `retain_every_n_turns: 5`, this minimizes per-turn overhead.
**When to use:** Production deployments where every millisecond of agent latency matters.
### Anti-Patterns to Avoid
- **Setting `memory.provider` to two external providers:** `MemoryManager.add_provider()` silently rejects the second one with only a log warning. Only one external provider can be active.
- **Disabling built-in memory:** Built-in MEMORY.md/USER.md continues as fallback. No config removes it. Trying to disable it has no effect.
- **Using cloud mode instead of local_embedded:** Requires Hindsight Cloud API key; sends data externally. Phase already decided local_embedded.
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Custom memory provider | Python ABC impl of MemoryProvider | Hindsight plugin (bundled in Hermes v0.16.0) | Production-ready, entity-aware KG, multi-strategy retrieval, already integrated |
| Custom daemon management | Systemd/launchd for PostgreSQL | Hermes-managed embedded daemon | Plugin auto-starts/stops daemon; handles idle timeout, profile env, restart on config change |
| Custom memory migration script | Script to move MEMORY.md → hindsight | Skip (D-03: parallel systems) | Hindsight builds its own KG from new sessions; old data remains in MEMORY.md as fallback |
| Custom entity extraction | LLM prompt for entity parsing | Hindsight auto-extraction | Plugin already handles entity resolution, deduplication, and knowledge graph construction server-side |
**Key insight:** Hindsight is production-ready, bundled in Hermes, and the plugin handles daemon lifecycle, LLM extraction, and entity management. Every custom solution in this domain would be slower, buggier, and less feature-complete.
## Common Pitfalls
### Pitfall 1: Memory Provider Conflict
**What goes wrong:** Setting two external memory providers (e.g., Hindsight + Honcho) silently fails — only the first is registered. User thinks both are active but only one works.
**Why it happens:** `MemoryManager.add_provider()` (agent/memory_manager.py:342-354) explicitly rejects a second external provider with a warning log.
**How to avoid:** Set `memory.provider: hindsight` only. Never add a second external provider.
**Warning signs:** `hermes memory list` shows only one provider. Grep logs for "Rejected memory provider."
### Pitfall 2: Async Retain Silent Failure
**What goes wrong:** Hindsight API fails during `aretain_batch()` but the error is swallowed — only logged as a warning. Agent thinks it saved facts but they never persisted.
**Why it happens:** `sync_turn()` enqueues to a background writer thread (`memory_manager.py:115-131`). Failures are logged, not surfaced to agent.
**How to avoid:** Monitor `~/.hermes/hermes-agent/logs/` for "Hindsight retain failed" warnings. Use `grep "Hindsight retain failed"` in monitoring.
**Warning signs:** Agent can't recall facts it previously discussed.
### Pitfall 3: Daemon Startup Failure
**What goes wrong:** The embedded daemon fails to start on first session access, causing all hindsight operations to fail.
**Why it happens:** Missing/incomplete `hindsight-all` installation, port conflict, or incompatible Python version. The startup happens in a background thread and logs to `~/.hermes/logs/hindsight-embed.log`.
**How to avoid:** Verify `hindsight-all` is installed in the correct venv. Check `~/.hermes/logs/hindsight-embed.log` after first session start.
**Warning signs:** `hermes memory status` shows hindsight as "not available." Session logs contain "Hindsight local mode disabled" warnings.
### Pitfall 4: OpenRouter API Key Format
**What goes wrong:** The `HINDSIGHT_LLM_API_KEY` env var uses the wrong key format for OpenRouter, causing LLM extraction to fail and memories not to be created.
**Why it happens:** OpenRouter keys start with `sk-or-v1-`. If a different key is set or the env var name is wrong, extraction silently fails.
**How to avoid:** Set `HINDSIGHT_LLM_API_KEY=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` — same value as existing `OPENROUTER_API_KEY`.
**Warning signs:** Daemon logs show 401/403 errors from OpenRouter. No memories created.
### Pitfall 5: Daemon Profile Env Stale
**What goes wrong:** The `~/.hindsight/profiles/hermes.env` file has stale config from a previous attempt that doesn't match current settings.
**Why it happens:** The profile env is generated during `post_setup()` or `initialize()`. If the config.json changes but the profile env doesn't get regenerated, the daemon uses old settings.
**How to avoid:** The plugin's `initialize()` method checks if the profile env matches config and regenerates it if needed. Manual config changes may require deleting the profile env to force regeneration. The existing `hermes.env` already has correct settings from the previous attempt.
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| MEMORY.md built-in (FTS5 text search) | Hindsight (entity-aware KG + semantic search) | Hermes v0.16.0 | Cross-session recall, entity resolution, multi-strategy retrieval |
| Per-session document overwrite | `update_mode='append'` (Hindsight ≥ 0.5.0) | Hindsight 0.5.0 | Retains append to existing session document instead of replacing it. Auto-detected in plugin `__init__.py` via `/version` probe |
| `recall_types` defaulted to all types | Now defaults to `observation` only | Hindsight 0.5.0 | Observations are consolidated knowledge; raw world/experience facts are supporting evidence. Use `"recall_types": "observation,world,experience"` to restore broad recall |
## Code Examples
### Config File: `~/.hermes/hindsight/config.json`
```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"
}
```
**Source:** [VERIFIED: plugins/memory/hindsight/README.md — all config keys documented with defaults and descriptions]
### Environment Variable to Add to `~/.hermes/.env`
```
# Hindsight — LLM API key for local embedded mode (reuses OpenRouter key)
HINDSIGHT_LLM_API_KEY=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
```
The OpenRouter base URL is already set in config.json (`llm_base_url` field), so `HINDSIGHT_API_LLM_BASE_URL` env var is optional — the plugin reads it from config.
**Source:** [VERIFIED: plugins/memory/hindsight/README.md §Environment Variables — `HINDSIGHT_LLM_API_KEY` documented]
### Full Installation Sequence
```bash
# 1. Install hindsight-all into Hermes venv
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python hindsight-all
# 2. Create config directory
mkdir -p ~/.hermes/hindsight
# 3. Write latency-optimized config
cat > ~/.hermes/hindsight/config.json << 'EOF'
{
"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"
}
EOF
# 4. Add LLM API key to .env (reuse existing OpenRouter key)
grep -q "HINDSIGHT_LLM_API_KEY" ~/.hermes/.env || \
echo 'HINDSIGHT_LLM_API_KEY=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' >> ~/.hermes/.env
# 5. Set memory provider
hermes config set memory.provider hindsight
# 6. Restart gateway
hermes gateway restart
```
### Verification Commands
```bash
# Check config file exists
cat ~/.hermes/hindsight/config.json
# Check provider is set
hermes config get memory.provider
# Check env var is set (key appears masked)
grep HINDSIGHT_LLM_API_KEY ~/.hermes/.env
# Check memory status
hermes memory status
# Check daemon startup logs (after starting a session)
cat ~/.hermes/logs/hindsight-embed.log
# Check daemon runtime logs (after session activity)
tail -50 ~/.hindsight/profiles/hermes.log
```
### Diagnostic: Check Hindsight Daemon Health
```bash
# Check if the daemon process is running
ps aux | grep hindsight
# Check the profile runtime log
tail -20 ~/.hindsight/profiles/hermes.log
# Check the client version
uv pip show hindsight-client --python ~/.hermes/hermes-agent/venv/bin/python
# Force daemon restart (delete profile env to regenerate)
rm ~/.hindsight/profiles/hermes.env
```
## Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | `hindsight-all` package is available on PyPI and installable via uv [CITED: verified via uv install] | Standard Stack | Package not found → fall back to `hermes memory setup` wizard which uses the same uv command |
| A2 | The existing OpenRouter API key is valid and has credits for LLM extraction calls [CITED: exists in .env] | Standard Stack | Key expired/insufficient → LLM extraction fails silently; memories never created |
| A3 | The existing `~/.hindsight/profiles/hermes.env` has correct settings and will be regenerated if config.json changes [CITED: verified via plugin __init__.py initialize() logic] | Architecture Patterns | Stale profile env → daemon starts with wrong LLM config → extraction fails |
## Open Questions
1. **Does the OpenRouter key have sufficient rate limits for hindsight LLM extraction?**
- What we know: Key exists and is used for fallback model calls
- What's unclear: Whether the free tier of OpenRouter handles the additional Hindsight extraction traffic (every 5 turns, ~70-200 chars per extraction)
- Recommendation: Monitor after deployment; if rate-limited, switch the extraction model to a cheaper/faster one or set up a dedicated OpenRouter key
2. **How does the daemon behave on macOS (Orbstack Docker)?**
- What we know: The daemon runs embedded PostgreSQL via `pg0`
- What's unclear: pg0 on macOS may have different startup characteristics
- Recommendation: Test first session start; check `hindsight-embed.log` for any pg0-related errors
## Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|------------|------------|-----------|---------|----------|
| `uv` | Package installation | ✓ | 0.11.19 | `hermes memory setup` uses uv internally |
| Hermes venv (`~/.hermes/hermes-agent/venv/`) | Package target | ✓ | Python 3.11 | — |
| OpenRouter API key | LLM extraction | ✓ | `sk-or-v1-...` (existing) | — |
| `hindsight-all` pip package | Embedded daemon | ✓ installed | 0.8.2 | `hindsight-client` only (cloud mode) |
| Hermes gateway | Activation | ✓ | v0.16.0 | — |
**Missing dependencies with no fallback:** none
**Missing dependencies with fallback:** none
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | pytest |
| Config file | none — use `python -m pytest` |
| Quick run command | `python -m pytest tests/ -x -q` |
| Full suite command | `python -m pytest tests/` |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| MEM-01 | Hindsight config.json written with correct keys | smoke | Check file exists and JSON valid | ❌ Wave 0 |
| MEM-01 | `memory.provider` set to `hindsight` | smoke | `hermes config get memory.provider` | ❌ Wave 0 |
| MEM-01 | Hindsight daemon starts on session init | smoke | Check `hindsight-embed.log` for success | ❌ Wave 0 |
| MEM-01 | Recall works across sessions | integration | Manual test: store fact in session A, recall in session B | Manual only |
### Sampling Rate
- **Per task commit:** `check-config.sh` (verify config.json, env var, provider setting)
- **Per wave merge:** Full verification sequence
- **Phase gate:** Functional test: store + recall across two sessions
### Wave 0 Gaps
- [ ] `tests/test_hindsight_config.py` — validates config.json structure and env vars
- [ ] `tests/test_hindsight_install.py` — validates package installation in Hermes venv
*(See gsd-add-tests skill for generating these after implementation.)*
## Security Domain
> Required: `security_enforcement` not explicitly false.
### Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---------------|---------|-----------------|
| V2 Authentication | no | No new auth — reuses existing OpenRouter key |
| V3 Session Management | no | Hindsight sessions are managed by the plugin internally |
| V4 Access Control | no | No user-facing access control changes |
| V5 Input Validation | yes | LLM extraction output is validated by Hindsight server-side |
| V6 Cryptography | no | No new cryptographic operations |
| V8 Data Protection | yes | Local embedded mode = no external data send; data persists in local PostgreSQL |
### Known Threat Patterns for {Hermes + Hindsight}
| Pattern | STRIDE | Standard Mitigation |
|---------|--------|---------------------|
| Memory data exfiltration via prompt injection | Information Disclosure | Local embedded mode = no external network data send; data stays in local PostgreSQL. LLM extraction calls go through OpenRouter with only the conversation turn as payload |
| LLM extraction API key compromise | Tampering | OpenRouter key already stored in `~/.hermes/.env` with 0600 permissions; reused same key, no new secret to manage |
| Daemon port exposure (localhost:9177) | Elevation of Privilege | Daemon binds to localhost only; not exposed to Docker container or network |
## Sources
### Primary (HIGH confidence)
- `~/.hermes/hermes-agent/plugins/memory/hindsight/README.md` — Full configuration reference with all options, env vars, and modes [VERIFIED: read in full]
- `~/.hermes/hermes-agent/plugins/memory/hindsight/__init__.py` (1803 lines) — Complete MemoryProvider implementation; config loading, daemon management, client initialization [VERIFIED: read in full]
- `~/.hermes/hermes-agent/plugins/memory/__init__.py` — "Only ONE provider can be active at a time" constraint line 12 [VERIFIED: read provider conflict logic]
- `~/.hermes/hermes-agent/agent/memory_manager.py` lines 342-354 — Provider conflict implementation [VERIFIED: read]
- `~/.hermes/hermes-agent/hermes_cli/memory_setup.py` — Interactive setup wizard code [VERIFIED: read]
- `~/.hermes/hermes-agent/venv/` — Confirmed `hindsight-all==0.8.2` installed [VERIFIED: pip show]
- `~/.hermes/config.yaml` §memory — Current empty provider setting [VERIFIED: read]
- `~/.hermes/.env` — Existing OpenRouter API key [VERIFIED: read]
- `~/.hindsight/profiles/hermes.env` — Pre-existing daemon config from previous attempt [VERIFIED: read]
### Secondary (MEDIUM confidence)
- `~/.hermes/hermes-agent/pyproject.toml``hindsight-client==0.6.1` listed as extras dependency [VERIFIED: grep]
- CONTEXT.md D-01 through D-10 — Locked decisions for this phase [VERIFIED: read]
### Tertiary (LOW confidence)
- Hindsight daemon behavior on macOS with pg0 — Not verified; will be validated during first session start [ASSUMED]
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH — all packages confirmed installed in Hermes venv; config structure verified against plugin source code
- Architecture: HIGH — plugin code read in full; data flow traced from agent loop to daemon
- Pitfalls: HIGH — each sourced from specific Hermes source lines and plugin implementation details
- Security: MEDIUM — local embedded mode eliminates external data send risk, but daemon behavior on macOS is not verified
**Research date:** 2026-06-14
**Valid until:** 2026-07-14 (30 days — stable Hermes v0.16.0 configuration)