# OpenViking Skills — User Guide

> Compiled from the active Hermes skill library (2026-08-15).
> Purpose: help a user best utilize OpenViking — the durable, semantic-vector memory store that Hermes uses for recall.
> Endpoint: `http://<openviking-host>:1933` (v0.4.13, api_key auth) — replace `<openviking-host>` with your OpenViking server's address.

## How to use this document

- **Section 1 (openviking-integration)** is the operational core: how to read, write, search, and maintain OpenViking data, including the mandatory taxonomy and the traps that break it.
- **Section 2 (mistakes-feedback-loop)** is the capture discipline: how to turn errors and corrections into stored lessons instead of losing them.
- **Related skills** are listed at the end with one-line pointers — load those skill files for the full procedure when you need them.

The two stores, in one sentence: `viking_remember` writes to the Hermes **peer store**; the HTTP API `content/write` writes to the **default store**. Canonical entities live in the default store; session-extracted facts live in the peer store.

---

# Section 1 — openviking-integration

Patterns for storing, retrieving, and batch-importing data into OpenViking (context database for AI agents).

## When to use

- Adding memory writes to cron jobs (viking_remember tool)
- Batch-importing historical data into OpenViking
- Debugging OpenViking connectivity or storage issues
- Storing daily cron outputs (research, market reports, congress trades)
- Removing stale viking_remember steps after scripts gain deterministic writes (strip prompt step AND enabled_toolsets in the same update; sweep cron prompt script paths for missing files; sync both script paths)
- Organizing and sanitizing data in OpenViking
- Proactively searching OpenViking before answering questions (see Proactive Search Protocol below)
- Syncing an Obsidian vault to OpenViking (see Vault Sync Integration below)

## Single Instance Setup

OpenViking runs as an external service (default port `1933`, `api_key` auth). There is no local instance by default.

All config files (`config.yaml`, `.hermes/config.yaml`, `.env`) and the gateway s6 environment must point to the same OpenViking endpoint. Verify which host your gateway is configured to use before writing.

**When writing data:** always use your configured endpoint (`http://<openviking-host>:1933`).

## Proactive Search Protocol — MANDATORY

Search OpenViking BEFORE composing responses on relevant topics. This is not optional.

**When to search:**
- Market data, news, research findings
- Past decisions, mistakes, lessons learned
- Anything that might be stored in the knowledge base
- Before presenting facts that could be outdated

**How to search:**
```
viking_search(query="topic keywords", limit=5)
```

**What to do with results:**
1. If relevant data found → synthesize it into the response (don't re-derive from scratch)
2. If data seems stale → flag it: "This was stored on [date] — verify before acting"
3. If no results → proceed with fresh research, but note the gap

**The rule:** "Search before answering" — not after, not optionally, not "if I remember to." Before.

## Vault Sync Integration

An Obsidian vault (e.g. `~/vault/`) containing research, reference materials, and project notes can be synced to OpenViking daily via a cron job.

**Script:** `~/scripts/vault-to-openviking.py` (or your own equivalent)

**What a typical sync includes:**
- Reference files, plans, guides
- Weekly synthesis notes
- Daily digests from cron
- Project notes
- Recent daily session notes (last 7 days)

**What to skip:**
- Individual news articles (redundant with daily digests)
- URL-only bookmark lists (not searchable content)

**API flow for local files:** OpenViking's HTTP API doesn't accept local filesystem paths directly. Two-step flow:
1. `POST /api/v1/resources/temp_upload` — upload file as multipart form data, get `temp_file_id`
2. `POST /api/v1/resources` — add resource with `{"temp_file_id": "...", "reason": "vault sync"}`

**Incremental sync:** Use mtime + size hash to detect changes. Only modified files are re-uploaded.

## HTTP API (Preferred)

The Python SDK has binary compatibility issues across Python versions. **Always use the HTTP API directly** — it's reliable and requires no SDK installation.

**Base URL:** `http://<openviking-host>:1933`

### Authentication

OpenViking uses API key auth (`auth_mode: api_key`). Every request requires the `Authorization: Bearer <key>` header.

```bash
curl -s -H "Authorization: Bearer $OV_KEY" "http://<openviking-host>:1933/health"
```

**API Key Sources:** The gateway process typically reads the key from its environment (e.g. s6 container environment), NOT from `.env` or `config.yaml`. If the gateway's key doesn't match the server's expected key, all requests return `UNAUTHENTICATED: Invalid API Key`.

### Write Content

```bash
curl -s -X POST -H "Authorization: Bearer $OV_KEY" \
  -H "Content-Type: application/json" \
  "http://<openviking-host>:1933/api/v1/content/write" \
  -d '{"uri": "viking://user/peers/hermes/memories/<category>/<file>.md", "content": "<content>", "mode": "create"}'
```

**`mode: "create"` is required for new files.** Without it, the API returns `NOT_FOUND` even though you're trying to create the file. For existing files, omit `mode` or use `mode: "replace"`.

**Mode summary (verified 2026-08-09):**
- `"create"` — creates; returns `409 ALREADY_EXISTS` if the URI exists
- `"replace"` — overwrites an existing URI in place
- `"overwrite"` — **INVALID**, returns `HTTP 400 Bad Request`. Do not use.

**Pattern for daily files written twice/day:** try `create`, on 409 retry with `replace`. A shared writer module (`write_resource()`) implements exactly this.

**Response:** `{"status":"ok","result":{"uri":"...","written_bytes":N,...}}`

### Read Content

```bash
curl -s -H "Authorization: Bearer $OV_KEY" \
  "http://<openviking-host>:1933/api/v1/content/read?uri=viking://user/peers/hermes/memories/<category>/<file>.md"
```

**Response:** `{"status":"ok","result":"<content>"}`

### Create Directory (for new category paths)

If writing to a new subdirectory that doesn't exist yet, create it first:

```bash
curl -s -X POST -H "Authorization: Bearer $OV_KEY" \
  -H "Content-Type: application/json" \
  "http://<openviking-host>:1933/api/v1/fs/mkdir" \
  -d '{"uri": "viking://user/default/memories/<category>"}'
```

**Note:** `content/write` with `mode: "create"` does NOT auto-create parent directories. Create parent directories first with `fs/mkdir` before writing to new category paths.

### URI Convention

```
viking://user/peers/hermes/memories/<category>/<file>.md
```

Categories: `cron`, `general`, `project`, `user_pref`, `theses` (adjust to your deployment)

## Semantic Indexing Behavior

When writing via `content/write`, the response includes `semantic_status`:
- `"queued"` — file will be indexed for semantic search
- `"skipped"` — file written to disk but NOT indexed (not searchable via `viking_search`)

**What gets indexed:** Files written via the `viking_remember` tool get indexed.

**What doesn't get indexed:** Direct `content/write` API calls to certain paths may return `semantic_status: "skipped"`. The data exists at the API level (readable via `content/read`) but won't appear in `viking_search` results.

**Workaround for batch imports:** After importing via HTTP API, use the `content/reindex` endpoint to force re-indexing.

**Rule of thumb:** For data that needs to be searchable, use `viking_remember` (cron jobs) or call `content/reindex` after bulk imports. For data that only needs to be readable via API, `content/write` is sufficient.

## Server Restart

OpenViking can crash under load (e.g., during bulk imports). To restart:

```bash
# Find and kill old process
ps aux | grep openviking | grep -v grep
kill <pid>

# Start in background (paths depend on your install)
openviking-server --config ~/.openviking/ov.conf &
```

**Verify:** `curl -s http://<openviking-host>:1933/health` should return `{"status":"ok"}`.

## Taxonomy Compliance — MANDATORY

**Every write must follow the taxonomy.** The two most common violations:

1. **Fragmentation** — writing 10 individual `mem_*.md` files instead of one consolidated digest. Rule: *"One daily news digest > 47 individual headlines."*
2. **Wrong scope** — writing operational data (market reports, news, anomalies) to `memories/` instead of `resources/`. Memories burn tokens on every relevant conversation; resources don't.

**Decision tree for every write:**

| Data type | Scope | Function | Naming |
|-----------|-------|----------|--------|
| Daily news digest | `resources/` | `write_resource()` | `news-YYYY-MM-DD.md` |
| Market report (anomalies, macro, Fed) | `resources/` | `write_resource()` | `market-YYYY-MM-DD-<type>.md` |
| Prediction markets | `resources/` | `write_resource()` | `market-YYYY-MM-DD-polymarket.md` |
| Congress/SEC disclosures | `resources/` | `write_resource()` | `market-YYYY-MM-DD-congress.md` |
| Mistakes, root causes | `memories/cases/` | `write_memory()` | auto-hash |
| Investment theses | `memories/theses/` | `write_memory()` | `theses/<ticker>/` |

**Superseded entities must be retired, not just corrected.** Writing a corrected entity while the stale one stays alive creates ontology drift: semantic search returns BOTH, and nothing flags the contradiction. When an entity is corrected, either (a) DELETE the superseded file, or (b) mark it `Status: superseded by <uri>` and move it out of the active entities path. Never leave two entities asserting different values for the same attribute.

**Write-path store trap:** `viking_remember` writes to the **peers store** (`viking://user/default/peers/hermes/memories/...`) with a hash filename. To create/update a **canonical entity** in the default store (`viking://user/default/memories/entities/<name>.md`), use HTTP `POST /api/v1/content/write` with `mode: "replace"` (exists) or `"create"` (new) — NEVER viking_remember for canonical entities (it creates a duplicate hashed file in the wrong store). Canonical entity conventions: human-readable filename, `supersedes:` links on rewrites, `source_of_truth:` pointers for live values (ports/versions/counts/models — never copy queryable numbers into entities), `Last verified:` date.

**Template:** a shared writer module with both `write_resource()` and `write_memory()` functions.

## Cron Job Enrichment Pattern

**Do NOT rely on LLM tool calls for cron data storage.** Cron models hallucinate tool calls. Use deterministic writes in Python scripts instead.

For new cron jobs, use a shared writer module:

```python
from openviking_writer import write_resource  # for daily digests
from openviking_writer import write_memory    # for high-signal facts only

# Daily digest → resources/ (no token cost)
write_resource(f"market-{date_str}-macro.md", consolidated_markdown)

# High-signal fact → memories/ (auto-injected)
write_memory("Example: a principal officer disclosed a large purchase", category="cases")
```

**The `tags` field is NOT supported** by the content/write API. Including it returns HTTP 400. Do not pass tags.

**Old pattern (UNRELIABLE — don't use for new jobs):** adding `viking_remember` to a cron job's enabled_toolsets and appending a "Store findings" step to the prompt.

**What to store:** Thesis updates, regime changes, new signals, discrete events (trades, disclosures).
**What to skip:** Routine data updates, daily noise, [SILENT] responses, content < 200 chars.

### CRITICAL: Plugin must be enabled for tools to work

`viking_remember` is registered by the OpenViking **plugin**, not the memory provider. Setting `memory.provider: openviking` in config.yaml enables the memory backend but does NOT register the `viking_remember` tool. For cron jobs to use `viking_remember`, the plugin must ALSO be in `plugins.enabled`:

```yaml
# config.yaml — BOTH required for cron jobs
memory:
  provider: openviking    # enables memory backend
plugins:
  enabled:
    - openviking          # registers viking_remember tool
```

**Without `openviking` in `plugins.enabled`:** cron jobs with `viking_remember` in `enabled_toolsets` fail with `RuntimeError: [Errno 32] Broken pipe` because the tool isn't registered.

### CRITICAL: LLM tool calls are unreliable in cron — use deterministic writes

LLM agents in cron jobs frequently **hallucinate tool calls** — they report success ("28 headlines stored") without actually invoking `viking_remember`. Root causes:
- The cron model sometimes can't access `viking_remember` despite it being in `enabled_toolsets`
- Even when accessible, the model may skip the tool call and just claim it did the work

**Pattern: Deterministic writes in Python scripts** — write directly to OpenViking from the Python script via HTTP API, then simplify the cron prompt to just run the script and report results — no LLM tool calls needed.

**Chaining lesson (verified 2026-08-10): never make a cron agent run a multi-step script chain.** A cron agent may do the LLM work and then silently skip a 3+ step terminal chain. Fix: expose ONE chained command in the script (e.g. `--stage all`) and have the prompt call only that.

**Audit pattern — verify writes landed, don't trust the prompt:** after a cron run, search OpenViking for the job's expected content. Check `created_at` dates — are they recent (matching last cron run)? If no results or stale dates → the LLM is hallucinating tool calls → convert to deterministic writes.

**Two storage locations — don't confuse them:**

| Store | Queried by | What's there |
|-------|-----------|--------------|
| Hermes peer memory | `viking_search` tool | Memories written by `viking_remember` tool calls (from scripts or LLM) |
| Standalone OpenViking server | Direct API `/api/v1/resources` | Resources written via `content/write` HTTP API |

### Anti-bot fallback bug (DataDome-style)

Some reader fallbacks don't throw exceptions on anti-bot blocked pages. They return the challenge HTML as a "successful" 200 response with ~125 bytes, which prevents the fallback chain from activating. Fix: add content-length validation to the reader tier:

```python
if len(markdown.strip()) < 500:
    raise RuntimeError(f"Reader returned too little content ({len(markdown)} bytes)")
```

**Lesson:** Anti-bot systems (DataDome, Cloudflare, Imperva) return "successful" HTTP responses with challenge HTML. Always validate content quality, not just HTTP status.

## Auto-Extraction vs Manual Storage

OpenViking has TWO memory storage mechanisms:

**Automatic (per-session):** The plugin extracts 6 categories on session commit: `profile`, `preferences`, `entities`, `events`, `cases`, `patterns`. Sessions commit when idle 24h or at the daily boundary.

**Manual (bulk):** Direct HTTP API writes for critical system knowledge that predates OpenViking or must survive wipes.

**Rule of thumb:** Automatic handles "what we talked about today." Manual handles "how the system works, what the cron jobs do, what the vault reference files say."

## Search Behavior — No Time Decay

OpenViking search is **purely semantic similarity** — cosine distance against query embeddings. Newer ≠ higher score. Scripts that need the NEWEST dated resource must not trust semantic ranking:

1. Scoped search with `target_uri="viking://resources/"`
2. Regex-extract `(\d{4}-\d{2}-\d{2})` from each result URI, pick the LARGEST date matching the resource type keyword
3. `content/read` the winner, parse defensively, truncate to budget, label with the source filename

Key failure mode: the first search result by score is often OLD. Always date-sort by filename, never take `results[0]`.

**Tool mode `deep` is unreliable (verified 2026-08-09):** `viking_search(mode="deep")` timed out 3 of 4 attempts; `mode="auto"` succeeded 6/6. Use `auto` (or `fast`) for routine recall. Reserve `deep` for single queries when auto returns weak results.

### Search scope — `target_uri` required for memories

Unscoped search returns resources but **memories: [] even for memories that exist and are readable.** Memory hits are EXCLUDED unless the query is scoped to the memories tree:

```bash
# WRONG — resources only, memories always empty:
curl -s -X POST "$OV/api/v1/search/search" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"query":"environment infrastructure","limit":3}'

# RIGHT — scope to the memories store:
curl -s -X POST "$OV/api/v1/search/search" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"environment infrastructure","limit":3,"target_uri":"viking://user/default/memories/"}'
```

**Diagnosis order when search looks empty:**
1. `fs/ls` on `viking://user/default/memories/` — files present? (If yes, it's a scope problem, NOT data loss.)
2. `content/read` a known file — readable? (Confirms data integrity.)
3. Re-run search with `target_uri` — if hits appear, it was scoping.

**The Hermes `viking_search` tool handles scoping internally.** The empty-memories trap only bites direct HTTP API calls. Never conclude "memories were wiped" from an unscoped search — verify with `fs/ls` first.

## Measuring recall quality (optional)

Since OpenViking search has no time decay and no feedback, retrieval quality can go unmeasured. A self-study pipeline adds measurement: synthetic grounded Q&A over the corpus → held-out eval that scores top-1 retrieval accuracy weekly.

**Typical stages:** `corpus | validate | eval | store | report | all` — with `all` as a one-command chain for the cron agent's deterministic tail. QA pairs stay local (never written to OpenViking — would pollute semantic search). Distilled overviews go to `resources/self-study/`.

## What the Community Stores (and What They Don't)

**What users actually store:**
- Preferences, environment facts, patterns, corrections
- Investment thesis, market signals, portfolio data
- Household/personal context (tax, pets, maintenance)
- System architecture, debugging lessons

**What users DON'T store (and shouldn't):**
- Raw session transcripts — use SQLite FTS5 instead
- Easily re-discovered facts (e.g., "what's the S&P 500 at?")
- Daily noise without signal — routine data updates
- Code files — use skills for procedures, not OpenViking

**Community consensus:**
- Bounded curation > unbounded dumps
- Transparency matters most — users want to see and correct what the agent knows
- Correctness > recency — fixing wrong memories matters more than time-decay weighting
- Skills and OpenViking serve different roles — don't duplicate skills into OpenViking

## Skills vs OpenViking — Different Memory Layers

| Layer | Type | Storage | Use Case |
|-------|------|---------|----------|
| **Skills** | Procedural memory | Local skills directory (`~/.hermes/skills/`) | How to do things (workflows, patterns, pitfalls) |
| **OpenViking** | Declarative memory | External server (`http://<openviking-host>:1933`) | What things are (facts, events, entities, preferences) |

**Don't duplicate skills into OpenViking.** Skills have linked files (references, templates, scripts) that OpenViking can't serve. Skills change frequently — dual-sync creates stale risk.

**Do store skill-like knowledge in OpenViking** when it's a fact about how YOUR system works (e.g., "the ledger uses SQLite with these vendors") rather than a reusable procedure (e.g., "how to write to SQLite").

## API Key Hierarchy — CRITICAL

OpenViking has three key levels. **Getting this wrong is the #1 cause of "it's broken" reports.**

| Key Type | Format | Can Do | CANNOT Do |
|----------|--------|--------|-----------|
| **Root** | `<account>.<user>.<sig>` with root role | Manage accounts, create users, list all accounts | **Read/write/search ANY user data** — returns `PERMISSION_DENIED` |
| **Admin** | `<account>.<user>.<sig>` with admin role | Read/write/search for scoped account | Manage other accounts |
| **User** | `<account>.<user>.<sig>` with user role | Read/write/search for own data only | Anything admin/root can do |

**API key format:** `base64(account_id).base64(user_id).signature` — e.g. `<base64-account>.<base64-user>.<signature>`

**THE PITFALL:** The root key CANNOT access tenant-scoped data APIs in `api_key` mode. If your gateway has a root key configured, ALL memory operations fail with `PERMISSION_DENIED: ROOT API keys cannot access tenant-scoped data APIs in api_key mode.` This means: if OpenViking is "working" (health check passes) but memories appear empty or writes fail, check whether the gateway has a ROOT key instead of a USER/ADMIN key.

**Correct setup:** The gateway MUST use a user or admin key. The root key should only be used for admin operations like creating accounts.

## Account & User Management

After a container wipe or fresh install, accounts and users must be recreated.

### Create account + admin user (single call)

```bash
ROOT_KEY="<root-key>"
curl -s -X POST -H "Authorization: Bearer $ROOT_KEY" \
  -H "Content-Type: application/json" \
  "http://<openviking-host>:1933/api/v1/admin/accounts" \
  -d '{"account_id": "hermes", "admin_user_id": "default"}'
# Returns: {"result":{"account_id":"hermes","admin_user_id":"default","user_key":"<USER_KEY>"}}
```

The `user_key` in the response is the key you MUST use in the gateway. Save it.

### Update gateway with user key

```bash
echo "<USER_KEY>" > /run/s6/container_environment/OPENVIKING_API_KEY   # adjust to your environment
```

**Pitfall:** Do NOT use the root key in the gateway. It creates a confusing situation where health checks pass but all data operations fail silently.

## Python SDK (Avoid)

The openviking Python SDK has binary compatibility issues (pydantic_core module not found at runtime). Just use curl / urllib.

## Troubleshooting

### `PERMISSION_DENIED: ROOT API keys cannot access tenant-scoped data APIs`

**Root cause:** The gateway has a ROOT key instead of a USER/ADMIN key. ROOT keys are for account management only.

**Fix:** Get the user key and update the gateway.

### `UNAUTHENTICATED: Invalid API Key`

**Root cause:** The API key in the gateway process environment doesn't match the key the OpenViking server expects.

**Diagnostic steps:**
1. Check what key the gateway has (e.g. `cat /run/s6/container_environment/OPENVIKING_API_KEY`)
2. Test the key manually: `curl -s -H "Authorization: Bearer <key>" http://<openviking-host>:1933/health`
3. If key doesn't match, get the current key from the OpenViking admin

**Common pitfall:** The `.env` file is NOT loaded into the gateway process. Setting `OPENVIKING_API_KEY` in `.env` has no effect on the gateway — only on local scripts.

### Recovery after container wipe

When the container restarts without a volume mount (or with wrong mount), ALL data is lost. Recovery steps:

1. **Verify wipe:** accounts endpoint returns `NOT_FOUND`
2. **Fix volume mount** — set `storage.workspace` in `ov.conf` and mount the data directory
3. **Recreate account** via admin endpoint, **save the user_key**
4. **Update gateway** with the new user key
5. **Re-migrate memories** — start with Tier 1 (operational essentials), then Tier 2 (investment/research)

### `content/read` parse quirk (hit twice 2026-08-09)

On peers-store URIs, `GET /api/v1/content/read` returns `result` as a STRING: `{"status":"ok","result":"<content>"}`. On default-store URIs it returns an object: `{"status":"ok","result":{"content":"<content>",...}}`. A parser doing `result.get("content")` reports peers files as "0 bytes" or empty — NOT data loss. Parse defensively:

```python
txt = res.get("result")
txt = txt.get("content", "") if isinstance(txt, dict) else (txt or "")
```

### `fs/tree` 401s on the peers store

While `fs/ls` works there — use `ls` recursion for peers paths, `tree` is fine on the default store.

### Fastest diagnostic — readiness probe

```bash
curl -s http://<openviking-host>:1933/ready
# "ollama": "not_configured" → embedding provider not set in ov.conf (need config + restart)
# "ollama": "ok" → embedding provider configured, check other causes
```

### `Broken pipe` in cron jobs

**Root cause:** `viking_remember` tool isn't registered because `openviking` is missing from `plugins.enabled`. Fix: add `- openviking` to `plugins.enabled` in `config.yaml` and restart gateway.

### viking tools report "OpenViking server not connected" while HTTP API works

**Root cause:** `config.yaml` has `memory.openviking.use_ovcli_config: true` with an `ovcli_config_path` the gateway process cannot read. Fix: remove the `openviking:` sub-block entirely (the top-level `endpoint` + `api_key` are sufficient), or repoint `ovcli_config_path` to a readable location, then restart the gateway.

**Config edits require gateway restart.** The gateway does not hot-reload config.yaml memory settings.

### Gateway process missing env vars

**Root cause:** Only container/process-level env flags inject vars into the gateway environment. `.env` file values are NOT loaded. Diagnostic: `env | grep OPENVIKING`.

## Do Not Use When

- Temporary task state — use the memory tool, not OpenViking
- Session-specific context — stays in session, not persisted
- Modifying OpenViking server config — that's server admin work
- The write would create fragmented data — consolidate first

## Completion Checklist

- [ ] Content consolidated (not fragmented into tiny files)
- [ ] Correct scope: resources/ for operational data, memories/ for facts
- [ ] Descriptive filename (not mem_{uuid}.md)
- [ ] No duplicate writes (check if content already exists)
- [ ] Searchable via viking_search after write

---

# Section 2 — mistakes-feedback-loop

Capture judgment errors and corrections into OpenViking mistakes log.

## When to Capture

- User corrects your approach ("that's wrong", "actually it's X not Y")
- A task fails due to a wrong assumption
- You repeat a mistake from the operational-lessons or mistakes-log
- A cron job fails due to a configuration oversight
- You learn something non-obvious about the system
- You cited a single source as authoritative without cross-referencing
- You presented a number that seemed implausible without flagging it
- You conflated "on platform X" with "in the total market"
- You made a judgment error in reasoning (not just a factual error)
- You picked one data source and treated it as the whole picture

## User Expectation: Holistic Pattern Thinking

The user does NOT want one-off incident logging. They want root cause analysis that identifies behavioral patterns across incidents. Every mistake must be traced to a repeatable reasoning bias, not just described as a one-time error.

**The user's standard:** "You shouldn't take everything exactly. You should be able to look at a holistic pattern and understand why the mistake is occurring."

This means:
- Don't just log "I picked one source" — trace it to "I optimize for speed over accuracy"
- Don't just log "config was wrong" — trace it to "I assume systems work intuitively"
- If the same root cause appears 3+ times, store a root cause pattern entry in OpenViking
- The 5 Whys is mandatory, not optional, for every mistake entry

## Root Cause Pattern

All these mistakes share one underlying cause: **anchoring to the first plausible answer without questioning it.**

The pattern:
1. User asks a question
2. I find a source that seems to confirm a direction
3. I present it as fact without asking "is this actually representative?"
4. User has to push back

The fix isn't "cross-reference more sources" — that's a tactic. The fix is a mindset shift:

**Before presenting any claim, ask: "Would I bet money on this being true?"**
- If yes → state it with confidence
- If no → say "I found X, but I'm not confident it's representative"
- If the number seems implausibly low or high → flag it explicitly
- If it's from one platform → say "on platform X" not "in the market"

**The key question is never "did I find an answer?" — it's "is this answer reliable enough to act on?"**

## How to Capture

### 1. Apply the 5 Whys

For every mistake, ask "why" 5 times to get past the surface symptom:

```
Why did X happen? → Because Y
Why did Y happen? → Because Z
Why did Z happen? → Because W
Why did W happen? → Because V
Why did V happen? → [True root cause]
```

Example:
1. Why did I cite "3 providers"? → A directory site showed that
2. Why did I trust it as representative? → First result, seemed authoritative
3. Why didn't I cross-reference? → Number answered the question directly
4. Why didn't I question plausibility? → Looking for confirmation, not validation
5. Why confirmation over validation? → Default to answering quickly over correctly

### 2. Look for the holistic pattern

Don't just log the individual incident. Ask:
- What TYPE of mistake is this? (data sourcing, config assumption, architectural, reasoning)
- Have I made similar mistakes before in a different domain?
- What's the BEHAVIORAL pattern, not just the tactical error?

### 3. Distinguish symptom from cause

- **Symptom:** "I picked one source" (tactical)
- **Root cause:** "I optimize for speed over accuracy" (behavioral)
- Log BOTH — symptom for quick reference, root cause for pattern recognition

### 4. Store in OpenViking (REQUIRED — not optional)

Every mistake MUST be stored via `viking_remember` with the 5 Whys chain. This is how future sessions avoid repeating the same error.

```
viking_remember(
  content="## [DATE]: [TITLE]\n- **Mistake:** [what went wrong]\n- **5 Whys:**\n  1. Why... → ...\n  2. Why... → ...\n  3. Why... → ...\n  4. Why... → ...\n  5. Why... → [root cause]\n- **Root cause:** [one-sentence behavioral pattern]\n- **Prevention:** [rule for next time]",
  category="case"
)
```

Also store the root cause pattern separately:

```
viking_remember(
  content="## Root Cause Pattern: [pattern name]\n[Brief description of the recurring reasoning bias]\n- Example 1 → how it manifested\n- Example 2 → how it manifested\n**The key question:** [one-liner to prevent this class of error]",
  category="case"
)
```

### 5. Update local file

Also append to a local mistakes log (e.g. `~/mistakes-log.md`) for backup and weekly cron access.

### 6. Check for patterns

Before storing, search OpenViking for similar mistakes:

```
viking_search(query="similar mistake pattern", limit=3)
```

If a similar mistake exists, note it as a recurring pattern.

## Feedback Loop Cron

A weekly cron job reviews recent sessions for uncaught mistakes:
- Searches session transcripts for correction signals ("wrong", "actually", "fix this")
- Cross-references with existing mistakes-log
- Reports new patterns not yet captured

## What NOT to Store

- Trivial typos or syntax errors
- Things that are easily re-discovered
- Session-specific temporary state

---

# Section 3 — Improving your system for coding applications

Evidence-based recommendations from a production Hermes + OpenViking box (verified August 2026). These are the patterns that survived real failures — not theory.

## 3.1 Reliability: deterministic writes, not LLM tool calls

- Cron agents **hallucinate tool calls** — they report "28 headlines stored" with zero writes actually landing. The cron model is the usual offender.
- Fix: Python scripts write to OpenViking via the HTTP API directly. The cron prompt just runs the script and reports its output. No LLM tool calls for storage.
- **Never let a cron agent run a multi-step script chain.** A 3+ step terminal chain gets silently skipped after the LLM work is done. Expose ONE chained command (`--stage all`) and have the prompt call only that.
- Audit every cron job: after a run, search for the job's expected keywords and check `created_at` dates. Stale or missing results = hallucinated writes = convert to deterministic.

## 3.2 Storage discipline

- **Taxonomy is law:** `resources/` for operational data (market reports, digests — no token cost), `memories/` for facts that should inject into conversations. Writing operational data to `memories/` burns tokens on every relevant conversation.
- **One canonical entity per concept.** When a fact is corrected, retire the superseded file (delete it or mark `Status: superseded by <uri>`). Two entities asserting different values for the same attribute = ontology drift that semantic search returns silently.
- **`source_of_truth:` pointers for live values** (ports, versions, counts, models). Never copy a queryable number into an entity — it will diverge. Store the pointer and the rule, not the data.
- Consolidate: one daily digest beats 47 fragmented `mem_*.md` files.

## 3.3 Search discipline

- **No time decay.** Semantic similarity ≠ recency. Never take `results[0]` when you need the newest resource — extract the date from the filename and pick the max.
- Use `mode="auto"` (or `fast`). `mode="deep"` times out ~3 of 4 attempts.
- **Scope memory searches with `target_uri`** on the direct API — unscoped searches return `memories: []` even for memories that exist. The Hermes `viking_search` tool handles this internally; only direct HTTP calls are affected.
- **Search before answering** — mandatory protocol, not optional. Synthesize stored knowledge instead of re-deriving from scratch; flag stale data with its stored date.
- Verify "wiped" diagnoses with `fs/ls` before concluding data loss — the empty-memories trap is usually a scoping problem.

## 3.4 Models in the stack

OpenViking uses two model roles. Knowing them matters because each is a different failure surface, and the `/ready` probe reports on exactly one of them.

| Role | Model | Serves | Failure surface |
|------|-------|--------|-----------------|
| **Embeddings** | Nomic (`nomic-embed-text`) | Converts stored content into vectors; powers semantic similarity ranking | If `/ready` shows `"ollama": "not_configured"`, embeddings never run — writes land but are not searchable |
| **Search / extraction** | DeepSeek V4 (optionally served via vLLM) | Semantic query understanding and the optional LLM extraction pass (`semantic_status`) | Slow responses under load; model provider outages stall the extraction queue |

**What this means operationally:** embeddings are the load-bearing piece — no embeddings, no search. The nightly monitor checks the queue drains; the readiness probe checks Ollama is configured. The search/extraction model is swappable (any OpenAI-compatible model can serve that role; vLLM is a common local host), but the embedding model choice is baked into the vector index — change embeddings and you must reindex everything.

## 3.5 Coding-specific improvements

For a Hermes box doing coding work with an external coding agent (pi.dev or similar):

1. **Lesson inheritance (global AGENTS.md).** The coding agent natively loads `~/.pi/agent/AGENTS.md` every session — verified in shipped source (`resource-loader.js` scans `AGENTS.override.md > AGENTS.md > AGENTS.MD > CLAUDE.md > CLAUDE.MD`, then walks up from cwd for per-project files). Put environment-specific traps there (git-sweep dangers, repo history, model drift insurance), keep it under ~600 tokens, and let per-repo `AGENTS.md` files carry project facts. This is the only channel that reaches every session regardless of spec quality.
2. **Deterministic lesson capture.** A tiny append script (dedup on first 60 chars, token-budget warning, exit codes 0/1/2) plus a **mandatory capture step per work wave**: did the review surface a new lesson? Then append it. A wave that corrected you MUST produce at least one capture line. Judgment errors (not coding facts) go through the mistakes-feedback-loop instead (5 Whys → OpenViking `cases`).
3. **Mechanical rails for multi-agent concurrency.** If several agents build at once: checkpoint auto-commit before mutations, a destructive-op gate (block `git reset --hard`, `rm -rf` on repo paths, writes to protected files), ownership locks with stale TTL, and revert-never-reset restore. Rules you can enforce in code are worth more than rules you ask the model to remember.
4. **Anti-over-engineering as a default.** Install a YAGNI-ladder extension (needs to exist? → in codebase? → stdlib? → one line? → minimum) in FULL mode by default; a review command returns a delete-list of over-engineered code. Complements the safety rails: one guards style, the other guards destruction.
5. **Spec-driven waves.** Every build gets a spec template carrying the constraints (verify-before-acting, do-not-touch paths, `pi_lock` shared paths, "never cut validation/security/accessibility"). The spec is the primary rules channel; the global AGENTS.md is the failsafe for sloppy specs.
6. **Skills vs OpenViking — do not duplicate.** Skills are procedural memory (how to do things — workflows, pitfalls); OpenViking is declarative (what things are — facts, events, entities). Storing a procedure in OpenViking creates a stale copy that can't serve linked files. Store system-specific facts, not reusable procedures.

### The two-channel loop (how this powers pi.dev in a real workflow)

pi.dev is a minimal, stateless coding agent: it executes the spec it is given and remembers nothing between sessions. That is a feature — but it means all institutional knowledge must arrive in the prompt or the rails. OpenViking is the declarative layer that makes the loop self-improving:

1. **Recall** — the orchestrator (Hermes) searches OpenViking before writing the wave spec: past decisions, environment facts, mistake patterns.
2. **Spec** — the spec template carries the constraints (verify-before-acting, do-not-touch paths, pi_lock shared paths, never cut validation/security/accessibility).
3. **Execute** — pi.dev loads `~/.pi/agent/AGENTS.md` every session (verified in shipped source: scans `AGENTS.override.md` → `AGENTS.md` → `CLAUDE.md`, then walks up from cwd for per-project files). Rails enforce mechanically: checkpoint auto-commit, destructive-op gate, ownership locks, YAGNI ladder.
4. **Review** — the orchestrator reviews the wave's output.
5. **Capture (mandatory, never skipped)** — did the review surface a NEW lesson? Append it deterministically (dedup + token budget). A wave that corrected you MUST produce at least one capture line.
6. **Route the lesson** — coding facts → `AGENTS.md` (procedural, loaded every pi.dev session); pattern-level judgment errors → `viking_remember` → OpenViking `cases` (declarative, recalled by the orchestrator).

Why two channels: pi.dev is stateless by design — it cannot query OpenViking mid-task, so it needs its lessons injected as AGENTS.md. The orchestrator CAN query OpenViking, so it stores the declarative layer there. The mandatory capture step is what connects them — each wave starts smarter than the last.

## 3.6 Other skills worth loading

| Skill | Why | Load when |
|-------|-----|-----------|
| `karpathy-guidelines` | Verify-first discipline, surgical patches, honest failure reporting — the behavioral baseline | Any coding task |
| `autonomous-ai-agents/pi` | Coding agent usage: auth, extensions, lesson inheritance | Delegating code work to pi |
| `autonomous-ai-agents/delegate-wave` | Wave orchestration: spec template, rails, mandatory capture step | Multi-agent coding waves |
| `software-development/test-driven-development` | RED-GREEN-REFACTOR enforced before code | Building features that must work |
| `software-development/spec-driven-agent-builds` | Delegate whole-app builds with verifiable success criteria | App builds via background agents |
| `software-development/delegate-task-resilience` | Write-to-disk proof, caps, recovery for subagent batches | Parallel subagent research/builds |
| `software-development/systematic-debugging` | 4-phase root cause debugging: understand before fixing | Any bug that survives one fix attempt |
| `software-development/requesting-code-review` | Pre-commit review: security scan, quality gates, auto-fix | Before committing non-trivial changes |
| `productivity/agent-self-maintenance` | Memory/USER.md hygiene, storage-layer policy, data-source principle | Pruning persistent memory |
| `hermes-configuration-patterns` | OpenViking external setup, memory provider options, config pitfalls | Diagnosing memory provider config |
| `autonomous-ai-agents/session-conduct` | Session management discipline for agent-user interactions | Long-running sessions |
| `open-source-tool-vetting` | Vet OSS tools pre-install: local-only, cloud-dep check | Installing any new tool |
| `devops/system-backup` | Full backup/restore — Hermes config, user-created data | Before any risky migration |
| `memory/mistakes-feedback-loop` | Judgment-error capture with 5 Whys → OpenViking | After any user correction or task failure |

---

*Compiled 2026-08-15 from the active Hermes skill library. Sections 1-2 are the OpenViking skills (openviking-integration, mistakes-feedback-loop); Section 3 is recommendations for a coding-enabled Hermes + OpenViking system. Reference files (full API docs, taxonomy, diagnostics) live in each skill directory — load them via skill_view when needed. Replace `<openviking-host>` with your server's address before sharing.*
