Agent-readable docs

Hermes OKF Documentation

Reference for the OKF-based filesystem memory system for Hermes agents: standalone CLI, Hermes plugin integration, Python SDK APIs, bundle schema, configuration, and operational workflows.

Pages

  1. OverviewExposed surfaces (standalone CLI, Hermes plugin, Python SDK), runtime assumptions (filesystem OKF bundles, pyyaml-only core), and the shortest path from install to first memory write.
  2. InstallationPyPI install targets, optional extras (`[rag]`, `[dev]`), entry-point scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`), Python version constraints, and PATH recovery when install scripts are not found.
  3. QuickstartFirst successful invocation: `pip install hermes-okf`, `hermes-okf init`, write a concept, search it, and verify with `hermes-okf validate` — expected stdout signals and recovery when the bundle path is non-empty.
  4. OKF bundle modelFilesystem bundle layout (`index.md`, `log.md`, concept `.md` files), `Concept` dataclass fields, required `type` frontmatter, auto-timestamp behavior, reserved directories, and Hermes-native layout (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`).
  5. Knowledge graphImplicit directed edges from markdown links, tag clustering, directory hierarchy, BFS traversal, neighbor/backlink queries, and optional NetworkX export via `GraphExtractor`.
  6. Two-memory modelHot in-process buffer (`HotMemoryBuffer`) vs cold OKF archive: push kinds (`observation`, `decision`, `tool_call`), flush triggers (session end, buffer max, explicit flush), and how Hermes flat memory writes map to typed OKF concepts.
  7. Install Hermes pluginRegister `hermes-okf` via `hermes-okf-install`: wrapper files in `~/.hermes/plugins/hermes-okf/`, auto-updates to `~/.hermes/config.yaml` (`plugins.enabled`, `memory.provider`, `bundle_path`), `hermes memory setup`, and uninstall semantics.
  8. Standalone CLI workflowsOperate an OKF bundle without Hermes: init, validate, list/show concepts, full-text search, log append, graph inspection, snapshot, context build, and session/plan/tool listing with `--path` placement rules.
  9. Python agent integrationWire OKF memory into custom agent classes with `HermesMemoryMixin`, `wrap_decision`/`wrap_tool`/`wrap_observation`, `HermesAgent` session/plan/tool lifecycle, snapshots, and `with_context` recall.
  10. Hermes provider integrationIntegrate via `HermesOKFProvider` and `HermesOKFMemoryProvider`: session hooks, memory/tool/decision/plan callbacks, `prefetch`/`sync_turn`, exposed tool schemas (`search_memory`, `snapshot_memory`), and model sync from Hermes `config.yaml`.
  11. Enable RAGOptional vector retrieval over OKF bundles: `pip install hermes-okf[rag]`, LangChain `DirectoryLoader` + `MarkdownHeaderTextSplitter`, Chroma persistence, `HERMES_OKF_ENABLE_RAG` config flag, and provider-neutral embedding model selection.
  12. Standalone CLI referenceAll `hermes-okf` subcommands, global flags (`--version`, `--path`), per-command arguments (`--force`, `--json`, `--top-k`, `--note`, `--agent-id`, `--category`), exit codes, and stdout/error message shapes.

Complete Markdown

# Hermes OKF Documentation

> Reference for the OKF-based filesystem memory system for Hermes agents: standalone CLI, Hermes plugin integration, Python SDK APIs, bundle schema, configuration, and operational workflows.

## Context Links

- [Agent index](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/llms.txt)
- [Human interactive docs](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02)
- [GitHub repository](https://github.com/EliaszDev/hermes-okf)

## Repository Metadata

- Repository: EliaszDev/hermes-okf

- Generated: 2026-06-15T19:29:41.862Z
- Updated: 2026-06-15T20:29:43.803Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 21

## Page Index

- 01. [Overview](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/01-overview.md) - Exposed surfaces (standalone CLI, Hermes plugin, Python SDK), runtime assumptions (filesystem OKF bundles, pyyaml-only core), and the shortest path from install to first memory write.
- 02. [Installation](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/02-installation.md) - PyPI install targets, optional extras (`[rag]`, `[dev]`), entry-point scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`), Python version constraints, and PATH recovery when install scripts are not found.
- 03. [Quickstart](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/03-quickstart.md) - First successful invocation: `pip install hermes-okf`, `hermes-okf init`, write a concept, search it, and verify with `hermes-okf validate` — expected stdout signals and recovery when the bundle path is non-empty.
- 04. [OKF bundle model](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/04-okf-bundle-model.md) - Filesystem bundle layout (`index.md`, `log.md`, concept `.md` files), `Concept` dataclass fields, required `type` frontmatter, auto-timestamp behavior, reserved directories, and Hermes-native layout (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`).
- 05. [Knowledge graph](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/05-knowledge-graph.md) - Implicit directed edges from markdown links, tag clustering, directory hierarchy, BFS traversal, neighbor/backlink queries, and optional NetworkX export via `GraphExtractor`.
- 06. [Two-memory model](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/06-two-memory-model.md) - Hot in-process buffer (`HotMemoryBuffer`) vs cold OKF archive: push kinds (`observation`, `decision`, `tool_call`), flush triggers (session end, buffer max, explicit flush), and how Hermes flat memory writes map to typed OKF concepts.
- 07. [Install Hermes plugin](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/07-install-hermes-plugin.md) - Register `hermes-okf` via `hermes-okf-install`: wrapper files in `~/.hermes/plugins/hermes-okf/`, auto-updates to `~/.hermes/config.yaml` (`plugins.enabled`, `memory.provider`, `bundle_path`), `hermes memory setup`, and uninstall semantics.
- 08. [Standalone CLI workflows](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/08-standalone-cli-workflows.md) - Operate an OKF bundle without Hermes: init, validate, list/show concepts, full-text search, log append, graph inspection, snapshot, context build, and session/plan/tool listing with `--path` placement rules.
- 09. [Python agent integration](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/09-python-agent-integration.md) - Wire OKF memory into custom agent classes with `HermesMemoryMixin`, `wrap_decision`/`wrap_tool`/`wrap_observation`, `HermesAgent` session/plan/tool lifecycle, snapshots, and `with_context` recall.
- 10. [Hermes provider integration](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/10-hermes-provider-integration.md) - Integrate via `HermesOKFProvider` and `HermesOKFMemoryProvider`: session hooks, memory/tool/decision/plan callbacks, `prefetch`/`sync_turn`, exposed tool schemas (`search_memory`, `snapshot_memory`), and model sync from Hermes `config.yaml`.
- 11. [Enable RAG](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/11-enable-rag.md) - Optional vector retrieval over OKF bundles: `pip install hermes-okf[rag]`, LangChain `DirectoryLoader` + `MarkdownHeaderTextSplitter`, Chroma persistence, `HERMES_OKF_ENABLE_RAG` config flag, and provider-neutral embedding model selection.
- 12. [Standalone CLI reference](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/12-standalone-cli-reference.md) - All `hermes-okf` subcommands, global flags (`--version`, `--path`), per-command arguments (`--force`, `--json`, `--top-k`, `--note`, `--agent-id`, `--category`), exit codes, and stdout/error message shapes.
- 13. [Hermes CLI reference](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/13-hermes-cli-reference.md) - `hermes okf` subcommands registered via `cli_extension.py` and `plugin.py`: `search`, `list --type`, `show --raw`, `snapshot --note`, `restore`, default `top_k`, and dispatch through `HermesOKFProvider`.
- 14. [Python SDK reference](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/14-python-sdk-reference.md) - Exported public API from `hermes_okf`: `OKFBundle` CRUD/log/graph methods, `Concept` fields, `SearchIndex` query methods, `GraphExtractor` traversal, `HermesMemory` recall APIs, `HermesAgent` plan/snapshot/tool methods, and `get_provider()`.
- 15. [Configuration reference](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/15-configuration-reference.md) - `HermesOKFConfig` fields and defaults, resolution order (env → `~/.hermes/hermes-okf.yaml` → `~/.hermes/config.yaml`), `HERMES_OKF_*` environment variables, `memory.*` keys written by install, and hot-memory/RAG toggles.
- 16. [OKF validation reference](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/16-okf-validation-reference.md) - `OKFValidator` rules: reserved files (`index.md`, `log.md`), frontmatter presence and YAML shape, required `type` field, `validate_file`/`quick_check` helpers, and `hermes-okf validate` error output format.
- 17. [OKF bundle basics](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/17-okf-bundle-basics.md) - Copy-paste recipe from `examples/basic_usage.py`: create bundle, write a `Project` concept, read by ID, search by tag, append a `Decision` log entry, and print graph edges with expected field values.
- 18. [Full Hermes agent](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/18-full-hermes-agent.md) - End-to-end agent state in an OKF bundle: register tools with JSON schemas, create/complete/archive plans, record decisions, save snapshots, resume sessions — from `examples/full_agent.py` and `examples/hermes_integration.py`.
- 19. [RAG pipeline](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/19-rag-pipeline.md) - Complete vector retrieval workflow from `examples/rag_integration.py`: load all bundle markdown, split on headers, embed into Chroma, query with `retriever.invoke`, and swap embedding providers without changing OKF storage.
- 20. [Troubleshooting](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/20-troubleshooting.md) - Known failure modes and recovery: `hermes-okf-install` not in PATH, plugin not listed in `hermes memory setup`, stale model in `config/agent`, missing bundle, Windows filename constraints, and `plugins.enabled` YAML list shape.
- 21. [Contributing](https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/21-contributing.md) - Dev setup (`pip install -e ".[dev]"`), test/lint/type-check commands, pre-commit hooks, branch naming, coverage expectations, CI workflow gates, and documentation update requirements for user-facing changes.

## Source File Index

- `.github/workflows/ci.yml`
- `.pre-commit-config.yaml`
- `CHANGELOG.md`
- `CONTRIBUTING.md`
- `docs/ARCHITECTURE.md`
- `docs/HERMES_INTEGRATION.md`
- `docs/HERMES_PLUGIN.md`
- `docs/HERMES_USERS.md`
- `examples/basic_usage.py`
- `examples/full_agent.py`
- `examples/hermes_integration.py`
- `examples/rag_integration.py`
- `pyproject.toml`
- `README.md`
- `src/hermes_okf/__init__.py`
- `src/hermes_okf/agent.py`
- `src/hermes_okf/bundle.py`
- `src/hermes_okf/cli_extension.py`
- `src/hermes_okf/cli.py`
- `src/hermes_okf/concept.py`
- `src/hermes_okf/graph.py`
- `src/hermes_okf/hermes_integration.py`
- `src/hermes_okf/hermes.py`
- `src/hermes_okf/install_plugin.py`
- `src/hermes_okf/memory_plugin.py`
- `src/hermes_okf/memory.py`
- `src/hermes_okf/plugin.py`
- `src/hermes_okf/search.py`
- `src/hermes_okf/validators.py`
- `tests/__init__.py`
- `tests/test_agent.py`
- `tests/test_bundle.py`
- `tests/test_graph.py`
- `tests/test_hermes_integration.py`
- `tests/test_hermes.py`
- `tests/test_memory.py`
- `tests/test_search.py`
- `tests/test_validators.py`
- `wiki/Troubleshooting.md`

---

## 01. Overview

> Exposed surfaces (standalone CLI, Hermes plugin, Python SDK), runtime assumptions (filesystem OKF bundles, pyyaml-only core), and the shortest path from install to first memory write.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/01-overview.md
- Generated: 2026-06-15T19:24:36.742Z

### Source Files

- `README.md`
- `pyproject.toml`
- `src/hermes_okf/__init__.py`
- `docs/ARCHITECTURE.md`
- `src/hermes_okf/bundle.py`

---
title: "Overview"
description: "Exposed surfaces (standalone CLI, Hermes plugin, Python SDK), runtime assumptions (filesystem OKF bundles, pyyaml-only core), and the shortest path from install to first memory write."
---

`hermes-okf` (v0.4.6) is a filesystem-backed memory layer for Hermes agents and standalone OKF bundles. Persistence is markdown files with YAML frontmatter on disk; the core runtime depends only on `pyyaml`. Three integration surfaces—`hermes-okf` CLI, Hermes plugin hooks, and the `hermes_okf` Python package—share `OKFBundle` as the write path into cold storage.

## Exposed surfaces

| Surface | Entry | Primary use |
|---------|-------|-------------|
| Standalone CLI | `hermes-okf` | Operate an OKF bundle without Hermes: init, validate, search, graph, snapshots |
| Hermes plugin | `hermes-okf-install`, `hermes okf`, `HermesOKFMemoryProvider` | Native Hermes memory provider with session hooks and `hermes okf` subcommands |
| Python SDK | `from hermes_okf import OKFBundle, HermesAgent, …` | Programmatic CRUD, agent lifecycle, provider wiring in custom code |

### Standalone CLI

The `hermes-okf` script maps to `hermes_okf.cli:main`. Global flags include `--version` and per-command `--path` (default `.`).

Representative subcommands:

| Command | Role |
|---------|------|
| `init` | Create a conformant bundle (`index.md`, `log.md`, seeded subdirectories) |
| `validate` | Run `OKFValidator` over the bundle |
| `list`, `show`, `search` | Inspect and query concepts |
| `log`, `log-append` | Read or append chronological entries to `log.md` |
| `graph-edges`, `graph-neighbors` | Traverse implicit link graph |
| `snapshot`, `context` | Checkpoint and build LLM context |
| `sessions`, `plans`, `tools` | List Hermes-native bundle directories |

`init` refuses non-empty directories unless `--force` is passed.

### Hermes plugin

Installation registers two pip entry points:

- `hermes.memory_providers` → `HermesOKFMemoryProvider` (implements Hermes `MemoryProvider` ABC)
- `hermes_agent.plugins` → `hermes_okf.plugin.register` (registers `hermes okf` CLI tree)

`hermes-okf-install` creates `~/.hermes/plugins/hermes-okf/` (`plugin.yaml`, wrapper files) and, when `~/.hermes/config.yaml` exists, sets `plugins.enabled`, `memory.provider: hermes-okf`, and `memory.bundle_path: ~/.hermes/okf_memory`. `hermes-okf-uninstall` removes the plugin wrapper only; the OKF bundle is preserved.

Hermes CLI commands registered via `cli_extension.py`:

```bash
hermes okf search <query> [--top-k N]
hermes okf list [--type TYPE]
hermes okf show <path> [--raw]
hermes okf snapshot [--note NOTE]
hermes okf restore
```

### Python SDK

Public exports from `hermes_okf`:

| Symbol | Responsibility |
|--------|----------------|
| `OKFBundle` | Concept CRUD, logging, graph edge extraction |
| `Concept` | Dataclass for a single `.md` concept |
| `SearchIndex` | Full-text search over bundle content |
| `GraphExtractor` | Link traversal, tag clustering, optional NetworkX export |
| `OKFValidator` | OKF conformance checks |
| `HermesMemory` | Session, decision, observation, tool-call semantics |
| `HermesAgent` | Full agent state stored in an OKF bundle |
| `HermesOKFProvider`, `get_provider()` | Universal Hermes memory provider |

Decorator-based integration (`HermesMemoryMixin` in `agent.py`) wraps decisions, tools, and observations for custom agent classes.

## Runtime assumptions

### Filesystem OKF bundles

An OKF bundle is a directory tree of `.md` files. `OKFBundle.__init__` creates the root if missing and seeds a minimal conformant layout when `index.md` is absent:

- Reserved files: `index.md` (directory index), `log.md` (chronological agent log)
- Default subdirectories: `projects/`, `decisions/`, `context/` (each with an `index.md` stub)
- Concept files: relative path as ID (e.g. `projects/my_project.md`), YAML frontmatter + markdown body
- Required OKF field: `type` in frontmatter (enforced by `OKFValidator`, not at write time)
- Auto-timestamp: `write_concept` adds `timestamp` in UTC ISO 8601 when omitted
- Implicit graph: markdown links `[label](path.md)` become directed edges

Hermes-extended bundles add `config/`, `sessions/`, `plans/`, `tools/`, and `snapshots/` as the agent runs.

<Info>
No database, vector store, or cloud service is required for core operation. Optional RAG (`pip install hermes-okf[rag]`) layers LangChain + ChromaDB on top of the same filesystem bundle without changing storage format.
</Info>

### pyyaml-only core

`pyproject.toml` declares a single runtime dependency: `pyyaml>=6.0`. Python `>=3.9` is supported. Optional extras:

| Extra | Packages | Purpose |
|-------|----------|---------|
| `[rag]` | langchain, langchain-community, langchain-chroma, langchain-openai | Vector retrieval over bundle markdown |
| `[dev]` | pytest, black, ruff, mypy, pre-commit | Development and CI |

Fuzzy search (`rapidfuzz`) and NetworkX graph export are optional runtime additions, not core dependencies.

### Two-memory model (Hermes path)

When `HermesOKFProvider` runs with `use_hot_memory: true` (default), writes land in `HotMemoryBuffer` first and flush to the OKF cold archive on session end, buffer reaching `hot_memory_max` (default 50), or explicit `flush()`. Push kinds: `observation`, `decision`, `tool_call`. Direct SDK/CLI writes bypass the hot buffer and hit `OKFBundle.write_concept` immediately.

## Architecture

```mermaid
flowchart TB
    subgraph human["Human interface"]
        HC["hermes okf search|list|show|snapshot|restore"]
        SC["hermes-okf init|validate|search|show|…"]
        PI["hermes-okf-install / hermes-okf-uninstall"]
    end

    subgraph plugin["Hermes plugin layer"]
        MP["HermesOKFMemoryProvider"]
        PL["plugin.py / cli_extension.py"]
        INS["install_plugin.py"]
    end

    subgraph provider["Universal provider"]
        HP["HermesOKFProvider"]
        HA["HermesAgent / HermesMemoryMixin"]
        HM["HotMemoryBuffer"]
    end

    subgraph core["Core OKF layer"]
        OB["OKFBundle"]
        CV["Concept"]
        GE["GraphExtractor"]
        SI["SearchIndex"]
        OV["OKFValidator"]
    end

    subgraph persist["Persistence"]
        FS["Filesystem: markdown + YAML frontmatter"]
    end

    HC --> PL
    SC --> OB
    PI --> INS
    MP --> HP
    PL --> HP
    HP --> HA
    HP --> HM
    HA --> OB
    HM -->|"flush"| OB
    OB --> CV
    OB --> GE
    OB --> SI
    OB --> OV
    OB --> FS
```

Configuration resolves in order: `HERMES_OKF_*` environment variables → `~/.hermes/hermes-okf.yaml` → `~/.hermes/config.yaml` → defaults (`bundle_path: ~/.hermes/okf_memory`, `agent_id: hermes`).

## Shortest path to first memory write

<Steps>
<Step title="Install the package">

```bash
pip install hermes-okf
```

Installs `hermes-okf`, `hermes-okf-install`, and `hermes-okf-uninstall` entry-point scripts.

</Step>
<Step title="Create a bundle">

```bash
hermes-okf init ./knowledge
```

<ResponseExample>

```text
Initialised OKF bundle at /path/to/knowledge
```

</ResponseExample>

`OKFBundle("./knowledge")` in Python performs the same seeding if the directory is new.

</Step>
<Step title="Write a concept">

<CodeGroup>

```python Python SDK
from hermes_okf import OKFBundle

bundle = OKFBundle("./knowledge")
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nFirst memory entry.",
    type="Project",
    title="My Project",
    tags=["first-write"],
)
```

```bash CLI log append
hermes-okf log-append --path ./knowledge \
  "First decision recorded" --category Decision
```

</CodeGroup>

`write_concept` persists a `.md` file with YAML frontmatter and auto-adds `timestamp`. `log-append` appends a timestamped line to `log.md`.

</Step>
<Step title="Verify the write">

```bash
hermes-okf show --path ./knowledge projects/my_project
hermes-okf validate --path ./knowledge
```

Expected validation output: `Bundle is valid.`

</Step>
</Steps>

### Hermes plugin path (automatic writes)

For Hermes users, the install-to-write path is two commands plus a session start:

```bash
pip install hermes-okf
hermes-okf-install
hermes
```

On first session, `HermesOKFMemoryProvider.initialize()` creates the bundle at `~/.hermes/okf_memory` (or `memory.bundle_path` from config). Memory writes from Hermes (`on_memory_write`, `on_tool_call`, `on_decision`) flow through `HermesOKFProvider` into typed OKF concepts. Inspect results with `hermes okf list` or `hermes okf show sessions/<session-id>`.

<Tip>
Run `hermes memory setup` after install to customize `bundle_path` and agent ID. The install script pre-populates `plugins.enabled` so the provider appears in the wizard.
</Tip>

## Surface selection

| Goal | Start here |
|------|------------|
| Manage a knowledge bundle without Hermes | Standalone CLI + `OKFBundle` |
| Drop-in Hermes agent memory | `hermes-okf-install` + `HermesOKFMemoryProvider` |
| Custom Python agent with decorators | `HermesMemoryMixin` or `HermesAgent` |
| Vector search over existing concepts | `[rag]` extra + `HERMES_OKF_ENABLE_RAG` |

<Warning>
If `hermes-okf-install` is not found, the script may be outside `PATH`. Run `python -m hermes_okf.install_plugin` from the same environment where `pip install` ran.
</Warning>

## Next

<CardGroup>
<Card title="Installation" href="/installation">
PyPI targets, optional extras, entry-point scripts, Python version constraints, and PATH recovery.
</Card>
<Card title="Quickstart" href="/quickstart">
End-to-end standalone flow: init, write, search, validate, and non-empty directory recovery.
</Card>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, `Concept` fields, frontmatter rules, and Hermes-native directories.
</Card>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
`hermes-okf-install` wrapper creation, config.yaml updates, and uninstall semantics.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot buffer vs cold archive, flush triggers, and Hermes flat-memory mapping.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
Full public API: `OKFBundle`, `SearchIndex`, `GraphExtractor`, `HermesAgent`, `get_provider()`.
</Card>
</CardGroup>

---

## 02. Installation

> PyPI install targets, optional extras (`[rag]`, `[dev]`), entry-point scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`), Python version constraints, and PATH recovery when install scripts are not found.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/02-installation.md
- Generated: 2026-06-15T19:24:30.001Z

### Source Files

- `pyproject.toml`
- `README.md`
- `src/hermes_okf/install_plugin.py`
- `docs/HERMES_PLUGIN.md`
- `docs/HERMES_USERS.md`

---
title: "Installation"
description: "PyPI install targets, optional extras (`[rag]`, `[dev]`), entry-point scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`), Python version constraints, and PATH recovery when install scripts are not found."
---

`hermes-okf` is published on PyPI as a Hatchling-built wheel with a single runtime dependency (`pyyaml>=6.0`). Installing the package registers three console scripts and two Hermes plugin entry points; the core OKF bundle layer works without Hermes, LangChain, or any model provider.

## Requirements

| Constraint | Value |
|------------|-------|
| Python | `>=3.9` (`requires-python` in `pyproject.toml`) |
| Supported classifiers | 3.9, 3.10, 3.11, 3.12, 3.13 |
| Core runtime dependency | `pyyaml>=6.0` |
| Hermes Agent | Optional — required only for the `hermes okf` plugin CLI and `HermesOKFMemoryProvider` |
| Model provider | Not required for install — RAG embeddings are provider-selectable when `[rag]` is installed |

<Note>
The README badge shows Python 3.10+, but the package metadata declares `requires-python = ">=3.9"`. Use 3.9+ for compatibility with the published wheel.
</Note>

## PyPI install targets

<Tabs>
<Tab title="Standard">

Install the core package — OKF bundle I/O, standalone CLI, Python SDK, and Hermes provider classes:

```bash
pip install hermes-okf
```

</Tab>
<Tab title="With RAG">

Add LangChain + Chroma vector retrieval dependencies:

```bash
pip install hermes-okf[rag]
```

Pulls: `langchain>=0.2.0`, `langchain-community>=0.2.0`, `langchain-chroma>=0.1.0`, `langchain-openai>=0.1.0`.

</Tab>
<Tab title="Development">

Editable install from a cloned repository with lint, test, and type-check tooling:

```bash
git clone https://github.com/EliaszDev/hermes-okf.git
cd hermes-okf
pip install -e ".[dev]"
```

</Tab>
<Tab title="All extras">

Install both `[rag]` and `[dev]` in one command:

```bash
pip install hermes-okf[all]
```

Resolves to `hermes-okf[rag,dev]` per `pyproject.toml`.

</Tab>
</Tabs>

### Optional extras summary

| Extra | Packages pulled | Use when |
|-------|-----------------|----------|
| *(none)* | `pyyaml` only | Standalone CLI, SDK, filesystem OKF bundles |
| `[rag]` | LangChain, Chroma, OpenAI embeddings adapter | Vector retrieval over bundle markdown |
| `[dev]` | pytest, black, ruff, mypy, pre-commit, types-PyYAML | Contributing, CI-parity local checks |
| `[all]` | `[rag]` + `[dev]` | Full feature + dev toolchain |

<Warning>
`[rag]` adds `langchain-openai` as a convenience embedding adapter. Embedding provider selection is runtime-configurable — OKF storage itself remains provider-neutral.
</Warning>

## Console entry points

`pyproject.toml` registers three `[project.scripts]` console commands:

| Script | Target | Purpose |
|--------|--------|---------|
| `hermes-okf` | `hermes_okf.cli:main` | Standalone OKF bundle CLI (`init`, `validate`, `search`, `show`, graph, snapshot, context, …) |
| `hermes-okf-install` | `hermes_okf.install_plugin:install_plugin` | Create `~/.hermes/plugins/hermes-okf/` and auto-update `~/.hermes/config.yaml` |
| `hermes-okf-uninstall` | `hermes_okf.install_plugin:uninstall_plugin` | Remove the plugin wrapper directory (does not delete the OKF bundle) |

### Equivalent invocations

The standalone CLI also exposes plugin management as subcommands — useful when only `hermes-okf` is on PATH:

<CodeGroup>
```bash title="Console scripts"
hermes-okf-install
hermes-okf-uninstall
```

```bash title="CLI subcommands"
hermes-okf install-plugin
hermes-okf uninstall-plugin
```

```bash title="Python module"
python -m hermes_okf.install_plugin
```
</CodeGroup>

<RequestExample>
```bash
hermes-okf --version
```
</RequestExample>

<ResponseExample>
```text
hermes-okf 0.4.6
```
</ResponseExample>

## Hermes plugin entry points

The wheel also declares setuptools entry points for Hermes discovery:

| Group | Name | Target |
|-------|------|--------|
| `hermes.memory_providers` | `hermes-okf` | `hermes_okf.memory_plugin:HermesOKFMemoryProvider` |
| `hermes_agent.plugins` | `hermes-okf` | `hermes_okf.plugin` |

<Info>
Hermes discovers plugins from the filesystem at `~/.hermes/plugins/`, not from `importlib.metadata` entry points. Running `hermes-okf-install` (or `hermes-okf install-plugin`) is required to create the wrapper directory even though entry points are declared in the wheel.
</Info>

## Register the Hermes plugin

After `pip install hermes-okf`, register the plugin wrapper so `hermes memory setup` and `hermes okf` subcommands can find it.

<Steps>
<Step title="Install the package">

```bash
pip install hermes-okf
```

</Step>
<Step title="Register the plugin wrapper">

```bash
hermes-okf-install
```

Creates `~/.hermes/plugins/hermes-okf/plugin.yaml` and `__init__.py`. If `~/.hermes/config.yaml` already exists, the installer appends `hermes-okf` to `plugins.enabled`, sets `memory.provider`, and adds `memory.bundle_path` (`~/.hermes/okf_memory`).

</Step>
<Step title="Verify registration">

```bash
ls ~/.hermes/plugins/hermes-okf/
hermes-okf --version
```

Expected plugin directory contents: `__init__.py`, `plugin.yaml`.

</Step>
<Step title="Activate in Hermes">

```bash
hermes memory setup   # optional — customize bundle path and agent ID
hermes                # plugin activates on first session start
```

</Step>
</Steps>

<RequestExample>
```bash
hermes-okf-install
```
</RequestExample>

<ResponseExample>
```text
Installed hermes-okf plugin to /home/username/.hermes/plugins/hermes-okf
  Updated ~/.hermes/config.yaml
  Run 'hermes memory setup' to finish activation
```
</ResponseExample>

If `~/.hermes/config.yaml` does not exist yet, the installer skips config mutation and prints `Run 'hermes memory setup' to activate` instead.

## Uninstall

```bash
hermes-okf-uninstall
```

Removes `~/.hermes/plugins/hermes-okf/` only. Your OKF bundle at `~/.hermes/okf_memory` (or a custom `memory.bundle_path`) is preserved.

## PATH recovery

Console scripts land in the active Python environment's `bin/` directory. If the shell reports `hermes-okf-install: command not found`, the package is likely installed but that directory is not on `PATH`.

<AccordionGroup>
<Accordion title="Run via Python module (most portable)">

```bash
python -m hermes_okf.install_plugin
```

Works from any environment where `hermes_okf` is importable, regardless of script PATH.

</Accordion>
<Accordion title="Activate the virtual environment">

```bash
source /path/to/venv/bin/activate
hermes-okf-install
```

</Accordion>
<Accordion title="Call the venv Python directly">

```bash
/path/to/venv/bin/python -m hermes_okf.install_plugin
```

Common when Hermes ships its own venv (e.g. `~/.hermes/hermes-agent/venv/bin/python`).

</Accordion>
<Accordion title="Use uv">

```bash
uv run --python /path/to/python hermes-okf-install
```

</Accordion>
<Accordion title="Use CLI subcommand instead">

If `hermes-okf` is on PATH but `hermes-okf-install` is not:

```bash
hermes-okf install-plugin
hermes-okf uninstall-plugin
```

</Accordion>
</AccordionGroup>

<Tip>
Run `which python` (or `python -c "import sys; print(sys.executable)"`) to locate the interpreter, then derive the `bin/` directory for script paths.
</Tip>

## Post-install paths

:::files
~/.hermes/
├── config.yaml              # updated by hermes-okf-install (if present)
├── plugins/
│   └── hermes-okf/
│       ├── __init__.py      # imports HermesOKFMemoryProvider
│       └── plugin.yaml      # name, version, hooks
└── okf_memory/              # default bundle_path (created on first session)
:::

## Verify installation

| Check | Command | Expected signal |
|-------|---------|-----------------|
| Package import | `python -c "import hermes_okf; print(hermes_okf.__version__)"` | `0.4.6` (or installed version) |
| Standalone CLI | `hermes-okf --version` | `hermes-okf <version>` |
| Plugin wrapper | `ls ~/.hermes/plugins/hermes-okf/` | `__init__.py`, `plugin.yaml` |
| Config wiring | `grep -A3 'plugins:' ~/.hermes/config.yaml` | `hermes-okf` in `plugins.enabled` list |
| Core-only smoke test | `hermes-okf init /tmp/okf-test && hermes-okf validate --path /tmp/okf-test` | `Initialised OKF bundle…` then `Bundle is valid.` |

## Upgrade

```bash
pip install --upgrade hermes-okf
```

Re-run `hermes-okf-install` after upgrading to refresh `plugin.yaml` version and ensure config keys remain current.

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First successful invocation: init a bundle, write a concept, search it, and validate conformance.
</Card>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Deep dive on `hermes-okf-install`: wrapper files, config.yaml mutations, and `hermes memory setup`.
</Card>
<Card title="Enable RAG" href="/enable-rag">
Optional vector retrieval after `pip install hermes-okf[rag]`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
PATH issues, plugin discovery failures, and config shape errors.
</Card>
<Card title="Contributing" href="/contributing">
Dev setup with `pip install -e ".[dev]"`, pytest, and pre-commit hooks.
</Card>
</CardGroup>

---

## 03. Quickstart

> First successful invocation: `pip install hermes-okf`, `hermes-okf init`, write a concept, search it, and verify with `hermes-okf validate` — expected stdout signals and recovery when the bundle path is non-empty.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/03-quickstart.md
- Generated: 2026-06-15T19:26:14.969Z

### Source Files

- `README.md`
- `examples/basic_usage.py`
- `src/hermes_okf/cli.py`
- `src/hermes_okf/bundle.py`
- `tests/test_bundle.py`

---
title: "Quickstart"
description: "First successful invocation: `pip install hermes-okf`, `hermes-okf init`, write a concept, search it, and verify with `hermes-okf validate` — expected stdout signals and recovery when the bundle path is non-empty."
---

The `hermes-okf` entry point (`hermes_okf.cli:main`) operates on filesystem OKF bundles through `OKFBundle`: `init` materializes `index.md`, `log.md`, and seeded `projects/`, `decisions/`, and `context/` stubs; concept writes persist as `.md` files with YAML frontmatter; `search` runs token matching over title, description, and body via `SearchIndex`; and `validate` enforces reserved files plus a required `type` field on every non-reserved markdown file through `OKFValidator`.

## Prerequisites

| Requirement | Value |
|-------------|-------|
| Python | `>=3.9` (`pyproject.toml`) |
| Core dependency | `pyyaml>=6.0` only |
| CLI entry point | `hermes-okf` |

```bash
pip install hermes-okf
```

<Note>
Hermes plugin registration (`hermes-okf-install`) is a separate path. This page covers the standalone CLI bundle workflow only.
</Note>

## Initialise a bundle

`hermes-okf init` resolves the target path from the positional argument or `--path` (default `.`), rejects non-empty directories unless `--force` is set, then constructs `OKFBundle(path)` which creates the directory tree when missing.

<Steps>
<Step title="Create the bundle directory">

```bash
hermes-okf init ./knowledge
```

<RequestExample>

```bash
hermes-okf init ./knowledge
```

</RequestExample>

<ResponseExample>

```text
Initialised OKF bundle at /absolute/path/to/knowledge
```

</ResponseExample>

Exit code: `0`.

</Step>

<Step title="Confirm layout">

:::files
knowledge/
├── index.md          # okf_version frontmatter + directory links
├── log.md            # chronological agent log
├── projects/
│   └── index.md      # type: Directory
├── decisions/
│   └── index.md
└── context/
    └── index.md
:::

Reserved files `index.md` and `log.md` are excluded from concept validation. Subdirectory `index.md` stubs include `type: Directory` and pass `hermes-okf validate` immediately after init.

</Step>
</Steps>

### Global `--path` flag

Subcommands that accept `--path` default to the current directory. Both placements are equivalent:

```bash
hermes-okf validate --path ./knowledge
hermes-okf init ./knowledge
```

## Write a concept

The standalone CLI has no `write-concept` subcommand. Persist concepts through `OKFBundle.write_concept` (Python SDK) or by creating a `.md` file under the bundle root.

### Python SDK

Mirrors `examples/basic_usage.py`:

```python
from hermes_okf.bundle import OKFBundle

bundle = OKFBundle("./knowledge")
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "data", "gpu"],
)
```

`write_concept` auto-inserts a UTC `timestamp` when omitted and writes `projects/my_project.md` with YAML frontmatter plus a markdown body.

### Manual markdown file

```bash
cat > ./knowledge/projects/my_project.md <<'EOF'
---
type: Project
title: My Project
tags:
  - ml
  - data
  - gpu
timestamp: 2026-06-15T19:25:46Z
---

# My Project

Describe your project here.
EOF
```

<Warning>
`OKFValidator` requires a `type` field in frontmatter on every `.md` file except `index.md` and `log.md`. `write_concept` does not enforce `type` at write time; missing `type` surfaces at `hermes-okf validate`.
</Warning>

## Search concepts

`hermes-okf search` builds an in-memory inverted index from `title`, `description`, and `body` tokens. Tags are not indexed.

```bash
hermes-okf search --path ./knowledge "project"
```

<RequestExample>

```bash
hermes-okf search --path ./knowledge "My Project"
```

</RequestExample>

<ResponseExample>

```text
1.00  projects/my_project
```

</ResponseExample>

| stdout | Meaning |
|--------|---------|
| `{score:.2f}  {concept_id}` | One result line; score is normalized token overlap |
| `No results.` | Query tokens matched no indexed text (exit `0`) |

<ParamField body="--top-k" type="integer" default="10">
Maximum result count.
</ParamField>

To find concepts by tag, use `OKFBundle.search_by_tag("gpu")` in Python rather than the CLI search command.

## Verify with validate

```bash
hermes-okf validate --path ./knowledge
```

### Success signal

<ResponseExample>

```text
Bundle is valid.
```

</ResponseExample>

Exit code: `0`.

### Failure signal

<ResponseExample>

```text
Found 1 validation error(s):
  - OKFValidationError(bad.md:1 -> Missing required 'type' field in frontmatter)
```

</ResponseExample>

Exit code: `1`. Each error line is the `repr` of an `OKFValidationError` with file path and message.

### Inspect a matched concept

```bash
hermes-okf show --path ./knowledge projects/my_project
```

Prints `ID`, `Type`, `Title`, `Tags`, `Timestamp`, optional `Resource`, then the markdown body.

## Non-empty bundle path recovery

`hermes-okf init` checks `path.exists() and any(path.iterdir())` before creating the bundle. A directory that already contains files is rejected without `--force`.

<RequestExample>

```bash
hermes-okf init ./existing-dir
```

</RequestExample>

<ResponseExample>

```text
Error: Directory './existing-dir' is not empty. Use --force to overwrite.
```

</ResponseExample>

Exit code: `1`.

### Recovery options

| Situation | Command | Behavior |
|-----------|---------|----------|
| Reuse directory, add OKF scaffold | `hermes-okf init ./existing-dir --force` | Calls `OKFBundle(path)`; creates missing `index.md`, `log.md`, and subdirectory stubs; does not delete existing files |
| Point CLI at an existing bundle | Skip `init`; pass `--path` on other subcommands | `OKFBundle` reopens without overwriting when `index.md` already exists |
| Fresh bundle at new path | `hermes-okf init ./new-knowledge` | Empty target directory only |

<ParamField body="--force" type="flag">
Bypass the non-empty directory guard on `init`. Does not truncate or remove pre-existing files in the target path.
</ParamField>

<Tip>
After `--force` init into a populated directory, run `hermes-okf validate --path <dir>` to confirm every non-reserved `.md` file (including pre-existing ones) has valid frontmatter with `type`.
</Tip>

## End-to-end command sequence

```bash
pip install hermes-okf
hermes-okf init ./knowledge
python -c "
from hermes_okf.bundle import OKFBundle
OKFBundle('./knowledge').write_concept(
    'projects/my_project',
    body='# My Project\n\nDescribe your project here.',
    type='Project', title='My Project', tags=['ml', 'data', 'gpu'],
)"
hermes-okf list --path ./knowledge
hermes-okf search --path ./knowledge "project"
hermes-okf validate --path ./knowledge
```

Expected stdout checkpoints:

| Step | Signal | Exit |
|------|--------|------|
| `init` | `Initialised OKF bundle at …` | `0` |
| `list` | `projects/my_project` | `0` |
| `search` | `1.00  projects/my_project` | `0` |
| `validate` | `Bundle is valid.` | `0` |

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
PyPI targets, optional `[rag]` and `[dev]` extras, entry-point scripts, and PATH recovery when `hermes-okf` is not found.
</Card>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, `Concept` fields, reserved directories, and Hermes-native paths.
</Card>
<Card title="Standalone CLI workflows" href="/standalone-cli-workflows">
Full command surface beyond this minimal path: log append, graph inspection, snapshots, and context build.
</Card>
<Card title="Example: OKF bundle basics" href="/example-okf-bundle-basics">
Copy-paste Python recipe from `examples/basic_usage.py` with tag search and graph edges.
</Card>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Register `hermes-okf` in `~/.hermes/plugins/` for `hermes okf` subcommands and automatic bundle creation.
</Card>
</CardGroup>

---

## 04. OKF bundle model

> Filesystem bundle layout (`index.md`, `log.md`, concept `.md` files), `Concept` dataclass fields, required `type` frontmatter, auto-timestamp behavior, reserved directories, and Hermes-native layout (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`).

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/04-okf-bundle-model.md
- Generated: 2026-06-15T19:24:26.669Z

### Source Files

- `src/hermes_okf/bundle.py`
- `src/hermes_okf/concept.py`
- `docs/ARCHITECTURE.md`
- `docs/HERMES_INTEGRATION.md`
- `src/hermes_okf/hermes.py`
- `tests/test_bundle.py`

---
title: "OKF bundle model"
description: "Filesystem bundle layout (`index.md`, `log.md`, concept `.md` files), `Concept` dataclass fields, required `type` frontmatter, auto-timestamp behavior, reserved directories, and Hermes-native layout (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`)."
---

An OKF bundle is a directory of Markdown files with YAML frontmatter, managed by `OKFBundle`. Each file (except reserved names) is a **concept** in an implicit knowledge graph. `OKFBundle.__init__` creates the bundle root if missing and calls `_init_bundle()` when `index.md` is absent. `HermesAgent` extends the same tree with agent-state directories under `config/`, `sessions/`, `plans/`, `tools/`, and `snapshots/`.

## Bundle layout

:::files
okf_bundle/
├── index.md                 # Bundle root index (reserved)
├── log.md                   # Chronological agent log (reserved)
├── projects/
│   ├── index.md             # Directory stub (excluded from concept listing)
│   └── my_project.md        # Concept: projects/my_project
├── decisions/
│   └── model_strategy.md
├── context/
│   └── openrouter_models.md
├── config/                  # Hermes-native (created by HermesAgent)
│   └── agent.md
├── tools/
│   └── search_web.md
├── sessions/
│   └── 2026-06-15T10-00-00Z.md
├── plans/
│   ├── research_ai_trends.md
│   └── archive/
│       └── completed_plan.md
└── snapshots/
    └── 2026-06-15T14-30-00Z.md
:::

Standalone `OKFBundle` initialization seeds `projects/`, `decisions/`, and `context/` with `index.md` stubs. `HermesAgent._ensure_structure()` adds `config/`, `tools/`, `sessions/`, `plans/`, and `plans/archive/` on first use. `snapshots/` is created when the first snapshot is written.

| Path | Role | Created by |
|------|------|------------|
| `index.md` | Bundle overview and navigation links | `OKFBundle._init_bundle()` |
| `log.md` | Append-only agent chronology | `OKFBundle._init_bundle()` |
| `projects/`, `decisions/`, `context/` | Default taxonomy directories | `OKFBundle._init_bundle()` |
| `config/agent.md` | Agent identity, model, system prompt | `HermesAgent._create_default_config()` |
| `tools/*.md` | Tool definitions with JSON schemas | `HermesAgent.register_tool()` |
| `sessions/*.md` | Per-session records | `HermesAgent.start_session()` |
| `plans/*.md` | Active task plans | `HermesAgent.create_plan()` |
| `plans/archive/*.md` | Completed or archived plans | `HermesAgent.archive_plan()` |
| `snapshots/*.md` | Point-in-time agent state | `HermesAgent.snapshot()` |

<Note>
Directory names are conventions, not enforced by the OKF spec. Types are user-defined strings in frontmatter; there is no fixed taxonomy.
</Note>

## Reserved files

`OKFValidator.REQUIRED_RESERVED_FILES` defines two root-level files that every conformant bundle must contain:

<ResponseField name="index.md" type="file">
Bundle directory listing. On init, contains `okf_version: "0.1"` in frontmatter and navigation links to default subdirectories.
</ResponseField>

<ResponseField name="log.md" type="file">
Chronological agent activity log. Initialized with `# Agent Log` and no frontmatter requirement.
</ResponseField>

Files named `index.md` or `log.md` at **any** depth are excluded from:

- `OKFBundle.list_concepts()` — not returned as concept IDs
- `OKFBundle.get_graph_edges()` — not treated as link sources
- `OKFValidator._check_all_concepts()` — not validated for `type` frontmatter

Root `index.md` and `log.md` are still checked for existence during full-bundle validation.

## Concept files

Each concept is one `.md` file. The **concept ID** is the relative path without the `.md` suffix, using forward slashes (for example `projects/my_project`). `read_concept` and `write_concept` normalize `/` to the OS path separator when resolving files.

### On-disk format

```markdown
---
type: Project
title: My Project
tags:
  - ml
  - data
timestamp: 2026-06-15T10:30:00Z
---

# My Project

Markdown body content.
```

`write_concept` serializes frontmatter with `yaml.dump` (unsorted keys, Unicode allowed), wraps it in `---` delimiters, and appends the body with a trailing newline.

### `Concept` dataclass

`Concept` is the in-memory representation parsed from one file:

| Field | Type | Default | Source |
|-------|------|---------|--------|
| `id` | `str` | — | Relative path without `.md` |
| `type` | `str` | `"Unknown"` | Frontmatter `type` |
| `title` | `str` | concept ID | Frontmatter `title` |
| `description` | `str` | `""` | Frontmatter `description` |
| `tags` | `list[str]` | `[]` | Frontmatter `tags` (coerced to `[]` if falsy) |
| `resource` | `str \| None` | `None` | Frontmatter `resource` |
| `timestamp` | `str \| None` | `None` | Frontmatter `timestamp` |
| `body` | `str` | `""` | Markdown after closing `---` |
| `metadata` | `dict[str, Any]` | `{}` | Full parsed frontmatter dict |

<ParamField body="id" type="string" required>
Relative path without `.md`, e.g. `projects/my_project` or `sessions/2026-06-15T10-00-00Z`.
</ParamField>

<ParamField body="type" type="string" required>
OKF concept type. Required by the v0.1 spec for validation; defaults to `"Unknown"` at parse time if absent.
</ParamField>

<ParamField body="metadata" type="object">
Raw frontmatter dictionary. Hermes agent concepts store extra fields here (`model`, `status`, `schema`, `steps`, `progress`, etc.).
</ParamField>

Common `type` values used by Hermes OKF:

| `type` | Typical path | Written by |
|--------|--------------|------------|
| `Project` | `projects/*` | `HermesMemory.register_project()` |
| `Decision` | `decisions/*` | `HermesMemory.record_decision()` |
| `Directory` | `*/index.md` | `OKFBundle._init_bundle()` stubs |
| `AgentConfig` | `config/agent` | `HermesAgent._create_default_config()` |
| `Session` | `sessions/*` | `HermesAgent.start_session()` |
| `Tool` | `tools/*` | `HermesAgent.register_tool()` |
| `Plan` | `plans/*`, `plans/archive/*` | `HermesAgent.create_plan()` |
| `Snapshot` | `snapshots/*` | `HermesAgent.snapshot()` |

## Required `type` frontmatter

OKF v0.1 conformance requires every concept `.md` file (excluding files named `index.md` or `log.md`) to have YAML frontmatter starting with `---` and a `type` key.

| Layer | Enforces `type`? |
|-------|------------------|
| `OKFBundle.write_concept()` | No — accepts arbitrary `**frontmatter` kwargs |
| `OKFBundle._parse_concept()` | No — falls back to `"Unknown"` |
| `OKFValidator` | Yes — reports `Missing required 'type' field in frontmatter` |

<Warning>
Writing concepts without `type` succeeds at runtime but fails `hermes-okf validate`. Always pass `type=` to `write_concept`, or run validation before committing bundle changes.
</Warning>

Validation also checks frontmatter presence, closing `---` delimiter, valid YAML, and mapping shape. See the validation reference for error output format.

## Auto-timestamp behavior

### Concept writes

`write_concept` injects a UTC timestamp when `timestamp` is not supplied in frontmatter:

```
%Y-%m-%dT%H:%M:%SZ
```

Example: `2026-06-15T10:30:00Z`. An explicit `timestamp` in `**frontmatter` is preserved unchanged.

### Log appends

`append_log(entry, category="Update")` appends to `log.md`:

1. Creates a `## YYYY-MM-DD` section header if today's date section is absent (UTC date).
2. Appends a bullet: `* **{category}**: {entry}`

`HermesMemory.record_observation()` and `record_tool_call()` route through `append_log` with categories such as `Observation`, `Tool-Call`, `Session`, and `Plan`. `record_decision()` writes a full concept under `decisions/` (with auto-timestamp via `write_concept`) **and** does not duplicate into `log.md` unless called separately.

<RequestExample>

```python
from hermes_okf.bundle import OKFBundle

bundle = OKFBundle("./my_knowledge")
concept = bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "data"],
)
# concept.timestamp is set automatically

bundle.append_log("Dropped PyTorch due to ROCm issues", category="Decision")
```

</RequestExample>

## `OKFBundle` operations

| Method | Behavior |
|--------|----------|
| `read_concept(id)` | Parse file to `Concept`, or `None` if missing |
| `write_concept(id, body, **frontmatter)` | Create parent dirs, write file, return `Concept` |
| `delete_concept(id)` | Remove file; returns `True` if existed |
| `list_concepts(subdir=None)` | All concept IDs, optionally filtered by subdirectory prefix |
| `append_log(entry, category)` | Append dated entry to `log.md` |
| `read_log()` | Full `log.md` contents |
| `search_by_tag(tag)` | Concepts whose `tags` contain `tag` |
| `get_graph_edges()` | Markdown `[label](target)` links as `{source, target, context}` |
| `get_neighbors(id)` | Outgoing edges from one concept |
| `to_dict(id)` | `dataclasses.asdict(Concept)` for JSON export |

External `http`/`https` link targets are excluded from graph edges. Relative `.md` link targets are normalized by stripping the `.md` suffix.

## Hermes-native layout

When `HermesAgent(bundle_path, agent_id)` starts, `_ensure_structure()` creates the agent directories and seeds `config/agent` if absent:

```yaml
---
type: AgentConfig
title: {agent_id} Configuration
model: gpt-4o
system_prompt: You are a helpful, autonomous Hermes agent.
version: 0.1.0
---
```

### Sessions

`start_session()` writes `sessions/{session_id}.md` with `type: Session`, `status: active`, and `started_at`. Session IDs default to filename-safe UTC timestamps (`%Y-%m-%dT%H-%M-%SZ`, colons replaced with hyphens). `end_session()` updates `status` to `completed` and sets `ended_at`.

### Tools

`register_tool(name, description, schema, example)` writes `tools/{name}.md` with `type: Tool`, `tags: [tool]`, and `schema` as a JSON string.

### Plans

`create_plan(task, steps)` writes `plans/{slug}_{date}.md` with `type: Plan`, `status: active`, `steps` list, and `progress: 0`. Step completion updates the markdown checklist (`[ ]` → `[x]`) and recalculates `progress`. `archive_plan()` copies to `plans/archive/` with `status: archived` and deletes the active file.

### Snapshots

`snapshot(note)` writes `snapshots/{filename_safe_timestamp}.md` with `type: Snapshot` and a JSON state block containing `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, and `note`. `restore()` reads metadata back into the agent instance.

```mermaid
classDiagram
    class OKFBundle {
        +Path root
        +read_concept(id) Concept
        +write_concept(id, body) Concept
        +list_concepts(subdir) list
        +append_log(entry, category)
    }
    class Concept {
        +str id
        +str type
        +str title
        +list tags
        +str timestamp
        +str body
        +dict metadata
    }
    class HermesAgent {
        +str model
        +str current_session_id
        +str current_plan_id
        +_ensure_structure()
        +start_session()
        +register_tool()
        +create_plan()
        +snapshot()
    }
    OKFBundle --> Concept : parses/writes
    HermesAgent --> OKFBundle : memory.bundle
```

## Initialize and validate

<Steps>
<Step title="Create a bundle">

```bash
hermes-okf init ./knowledge
```

`OKFBundle(path)` creates the directory, `index.md`, `log.md`, and seeded subdirectories. If the target directory is non-empty, the CLI exits with an error unless `--force` is passed.

</Step>
<Step title="Write concepts with required frontmatter">

Always include `type` (and typically `title`) when calling `write_concept` or agent memory APIs.

</Step>
<Step title="Validate conformance">

```bash
hermes-okf validate --path ./knowledge
```

Expected success output: `Bundle is valid.`

</Step>
</Steps>

## Implicit graph edges

Markdown links inside concept bodies define directed edges. `[label](target.md)` from concept `a` to `b` produces `{source: "a", target: "b", context: "label"}`. Directory hierarchy and tag clustering provide additional navigation via `GraphExtractor`, but link edges are extracted directly by `OKFBundle.get_graph_edges()`.

<Info>
Reserved `index.md` files can contain navigation links, but they are not included as edge sources. Subdirectory `index.md` stubs are written with `type: Directory` for human readability only.
</Info>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Create a bundle, write a concept, search it, and run `hermes-okf validate`.
</Card>
<Card title="OKF validation reference" href="/okf-validation-reference">
`OKFValidator` rules, `validate_file`/`quick_check` helpers, and CLI error output.
</Card>
<Card title="Knowledge graph" href="/knowledge-graph">
Link edges, tag clustering, BFS traversal, and `GraphExtractor` queries.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot buffer vs cold OKF archive and flush behavior into bundle files.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
Full `OKFBundle` and `Concept` API surface.
</Card>
<Card title="Example: OKF bundle basics" href="/example-okf-bundle-basics">
Copy-paste recipe for create, write, search, log, and graph inspection.
</Card>
</CardGroup>

---

## 05. Knowledge graph

> Implicit directed edges from markdown links, tag clustering, directory hierarchy, BFS traversal, neighbor/backlink queries, and optional NetworkX export via `GraphExtractor`.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/05-knowledge-graph.md
- Generated: 2026-06-15T19:25:36.184Z

### Source Files

- `src/hermes_okf/graph.py`
- `src/hermes_okf/bundle.py`
- `docs/ARCHITECTURE.md`
- `tests/test_graph.py`
- `src/hermes_okf/cli.py`

---
title: "Knowledge graph"
description: "Implicit directed edges from markdown links, tag clustering, directory hierarchy, BFS traversal, neighbor/backlink queries, and optional NetworkX export via `GraphExtractor`."
---

Hermes OKF builds an implicit knowledge graph over every concept file in an OKF bundle. Nodes are concept IDs (relative paths without `.md`); directed edges come from markdown links in concept bodies. `GraphExtractor` layers neighbor/backlink queries, tag clustering, directory co-location, BFS traversal, and optional NetworkX export on top of `OKFBundle.get_graph_edges()`. No RDF store, Cypher engine, or graph database is required — the graph is reconstructed on demand from plain markdown files.

## Graph model

The graph has three complementary views. Only markdown links form directed edges; directory layout and tags provide structural and semantic grouping without creating automatic edges.

| View | Mechanism | Edge type | API surface |
|------|-----------|-----------|-------------|
| Link graph | `[label](target.md)` in concept bodies | Directed (`source` → `target`) | `get_edges`, `get_neighbors`, `get_backlinks`, `traverse` |
| Directory hierarchy | Co-located `.md` files in the same folder | Sibling listing (not parent→child edges) | `get_children` |
| Tag clustering | Shared `tags` frontmatter values | Soft clusters (no edges) | `get_tag_clusters` |

```mermaid
flowchart LR
  subgraph persistence [Filesystem OKF bundle]
    MD["Concept .md files"]
  end
  subgraph extraction [Edge extraction]
    BE["OKFBundle.get_graph_edges()"]
  end
  subgraph navigation [GraphExtractor]
    GN["get_neighbors / get_backlinks"]
    GT["traverse (BFS)"]
    GC["get_children / get_tag_clusters"]
    NX["to_networkx()"]
  end
  subgraph surfaces [Callers]
    CLI["hermes-okf graph-*"]
    SDK["Python agents / scripts"]
  end
  MD --> BE
  BE --> GN
  BE --> GT
  BE --> NX
  MD --> GC
  GN --> CLI
  GN --> SDK
  GT --> SDK
  GC --> SDK
  NX --> SDK
```

<Info>
Reserved files `index.md` and `log.md` are excluded from edge scanning, concept listing, and tag clustering. They never appear as graph nodes.
</Info>

## Link-based directed edges

`OKFBundle.get_graph_edges()` scans every `*.md` file under the bundle root (except `index.md` and `log.md`), matches markdown links with the regex `\[([^\]]+)\]\(([^)]+)\)`, and emits one directed edge per match.

<ResponseField name="edge" type="object">
  <ResponseField name="source" type="string">Concept ID of the file containing the link (relative path without `.md`, POSIX separators).</ResponseField>
  <ResponseField name="target" type="string">Link destination after `.md` suffix removal. Relative paths are not resolved against the bundle root.</ResponseField>
  <ResponseField name="context" type="string">Link label text (the bracketed portion of the markdown link).</ResponseField>
</ResponseField>

**Inclusion rules:**

- Targets ending in `.md` have the suffix stripped (e.g. `b.md` → `b`, `projects/foo.md` → `projects/foo`).
- External URLs (`http://`, `https://`) are skipped entirely.
- Links in `log.md` and `index.md` are never scanned.

**Example:** A concept `a` with body `[see b](b.md)` and concept `b` with body `Body` produces one edge:

```text
a -> b  (see b)
```

Use concept IDs that match the filesystem layout. A link `[config](config/agent.md)` from `tools/search_web` yields target `config/agent`, not a resolved absolute concept path.

## GraphExtractor API

`GraphExtractor` is exported from `hermes_okf` and takes an `OKFBundle` instance. It delegates edge extraction to the bundle and adds navigation helpers.

```python
from hermes_okf import OKFBundle, GraphExtractor

bundle = OKFBundle("./my_knowledge")
extractor = GraphExtractor(bundle)
```

### Link navigation

| Method | Returns | Description |
|--------|---------|-------------|
| `get_edges()` | `list[dict[str, str]]` | All directed link edges in the bundle |
| `get_neighbors(concept_id)` | `list[str]` | Target concept IDs of outgoing edges from `concept_id` |
| `get_backlinks(concept_id)` | `list[str]` | Source concept IDs of incoming edges to `concept_id` |

<Note>
`OKFBundle.get_neighbors(concept_id)` also exists but returns full edge dicts (`source`, `target`, `context`), not bare target IDs. Prefer `GraphExtractor` for ID-only neighbor lists and backlink queries.
</Note>

### Directory siblings

`get_children(concept_id)` lists other concept IDs in the same directory as the given concept. It excludes `index.md` and the concept itself. This reflects filesystem co-location, not a parent→child link in the markdown graph.

For concepts `sub/a` and `sub/b` in the same folder, `get_children("sub/a")` includes `sub/b`.

### Tag clustering

`get_tag_clusters()` returns `dict[str, list[str]]` mapping each tag string to the concept IDs that carry it in frontmatter. A concept with multiple tags appears in multiple clusters. Tags do not create graph edges — they group concepts for filtering and recall alongside the link graph.

### BFS traversal

`traverse(start_id, max_depth=3)` performs a breadth-first walk of the link graph from `start_id` and returns a nested dict subtree.

<ResponseField name="traverse result" type="object">
  <ResponseField name="id" type="string">Concept ID at this node.</ResponseField>
  <ResponseField name="title" type="string">Concept title from frontmatter, or the ID if missing.</ResponseField>
  <ResponseField name="type" type="string">OKF `type` field, or `"Unknown"`.</ResponseField>
  <ResponseField name="depth" type="integer">Depth from `start_id` (0 at root).</ResponseField>
  <ResponseField name="children" type="array">Nested traverse nodes for outgoing link targets (optional).</ResponseField>
</ResponseField>

Traversal follows outgoing markdown links only. Cycles are bounded by `max_depth`; a node may appear in multiple branches of the returned tree. Nodes beyond `max_depth` are omitted.

```python
tree = extractor.traverse("decisions/api_provider", max_depth=2)
# tree["id"] == "decisions/api_provider"
# tree["children"][0]["id"] == outgoing link target
```

### NetworkX export

`to_networkx()` builds a `networkx.DiGraph` with one node per concept (attributes from `concept.metadata`) and directed edges from link extraction (edge attribute `context` holds the link label).

NetworkX is **not** a core or optional-extra dependency. Install it separately:

```bash
pip install networkx
```

If NetworkX is missing, `to_networkx()` raises `ImportError` with install instructions.

```python
graph = extractor.to_networkx()
# graph.nodes["projects/my_project"]["type"]  -> frontmatter fields
# graph.edges["a", "b"]["context"]            -> link label
```

## CLI graph inspection

The standalone CLI exposes two graph subcommands. Both accept `--path` (default `.`) to select the bundle root.

<Steps>
<Step title="List all link edges">

```bash
hermes-okf graph-edges --path ./my_knowledge
```

<ResponseExample>

```text
decisions/api_provider -> tools/search_web  (tool access)
tools/search_web -> context/firecrawl_config  (Firecrawl)
```

</ResponseExample>

Prints `No edges found.` when the bundle has no internal markdown links.

</Step>

<Step title="List outgoing neighbors for one concept">

```bash
hermes-okf graph-neighbors --path ./my_knowledge tools/search_web
```

<ResponseExample>

```text
context/firecrawl_config
config/agent
```

</ResponseExample>

Prints `No neighbors found.` when the concept has no outgoing links.

</Step>
</Steps>

Backlinks, tag clusters, BFS traversal, directory children, and NetworkX export are available only through the Python SDK (`GraphExtractor` or `OKFBundle.get_graph_edges()`).

## Authoring linked concepts

Link concepts in markdown bodies to grow the graph as agents write memory. A typical chain connects decisions, tools, and configuration:

```markdown
---
type: Decision
title: API Provider Choice
---

Selected OpenRouter for [tool access](tools/search_web.md).
```

```markdown
---
type: Tool
title: search_web
---

Search the web using [Firecrawl](context/firecrawl_config.md).
Requires [OpenRouter key](config/agent.md) for rate limits.
```

After both files exist, `get_edges()` reports directed edges from the decision to the tool and from the tool to its dependencies. Agents can then call `get_backlinks("config/agent")` to find what references a configuration, or `traverse("decisions/api_provider")` to walk the reasoning chain.

<Warning>
Broken links still produce edges: if `target.md` does not exist, the edge is recorded but `read_concept(target)` returns `None`. Validate bundle structure separately with `hermes-okf validate`.
</Warning>

## Design constraints

| Constraint | Behavior |
|------------|----------|
| No graph database | Graph is recomputed from filesystem on each call |
| No automatic hierarchy edges | Parent directories do not link to child concepts unless markdown links exist |
| No external link edges | `http://` and `https://` targets are ignored |
| Relative path targets | Only `.md` suffix stripping; `../` paths are not normalized to concept IDs |
| Core dependency footprint | Graph logic uses stdlib + `pyyaml` only; NetworkX is opt-in |

The OKF v0.1 conformance model treats markdown links as the canonical edge type. Types and tags are user-defined — the graph does not enforce a fixed ontology.

## Related pages

<CardGroup>
<Card title="OKF bundle model" href="/okf-bundle-model">
Concept files, frontmatter fields, reserved paths, and how concept IDs map to the filesystem.
</Card>
<Card title="Standalone CLI workflows" href="/standalone-cli-workflows">
Operate a bundle without Hermes: init, validate, search, and graph inspection with `--path`.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
Full `GraphExtractor` and `OKFBundle` graph method signatures and return shapes.
</Card>
<Card title="OKF bundle basics example" href="/example-okf-bundle-basics">
Copy-paste recipe: write concepts, search by tag, and print graph edges.
</Card>
<Card title="Enable RAG" href="/enable-rag">
Optional vector retrieval over the same bundle — complements link traversal with semantic search.
</Card>
</CardGroup>

---

## 06. Two-memory model

> Hot in-process buffer (`HotMemoryBuffer`) vs cold OKF archive: push kinds (`observation`, `decision`, `tool_call`), flush triggers (session end, buffer max, explicit flush), and how Hermes flat memory writes map to typed OKF concepts.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/06-two-memory-model.md
- Generated: 2026-06-15T19:25:34.110Z

### Source Files

- `src/hermes_okf/hermes_integration.py`
- `src/hermes_okf/memory.py`
- `docs/HERMES_PLUGIN.md`
- `docs/HERMES_USERS.md`
- `tests/test_memory.py`

---
title: "Two-memory model"
description: "Hot in-process buffer (`HotMemoryBuffer`) vs cold OKF archive: push kinds (`observation`, `decision`, `tool_call`), flush triggers (session end, buffer max, explicit flush), and how Hermes flat memory writes map to typed OKF concepts."
---

`HermesOKFProvider` batches high-frequency agent events into an in-process `HotMemoryBuffer` before persisting them to the filesystem OKF bundle. Hermes’ native `MEMORY.md` / `USER.md` files remain the bounded, in-prompt working set; the OKF bundle is the structured cold archive for search, graph traversal, snapshots, and cross-session recall.

## Memory layers

Three distinct layers cooperate when Hermes runs with the `hermes-okf` provider:

| Layer | Location | Role | Persistence |
|-------|----------|------|-------------|
| Hermes native hot | `MEMORY.md`, `USER.md` | Small curated facts injected into every system prompt | Hermes-managed flat text |
| OKF hot buffer | `HotMemoryBuffer.items` (in-process) | Batches observations, decisions, and tool calls before disk I/O | Lost on crash until flushed |
| OKF cold archive | Bundle root (`log.md`, `decisions/`, `tools/`, `sessions/`, …) | Typed concepts, chronology, graph edges | Filesystem markdown + YAML |

<Note>
The standalone Python SDK (`HermesMemory`, decorators, CLI) writes directly to the cold archive. The hot buffer is specific to `HermesOKFProvider` and `HermesOKFMemoryProvider`.
</Note>

```text
┌─────────────────────────────────────────────────────────────┐
│  Hermes Agent                                               │
│  ┌──────────────────┐         ┌──────────────────────────┐   │
│  │ MEMORY.md/USER.md│         │  HotMemoryBuffer         │   │
│  │ (in-prompt hot)  │  hooks  │  list[dict] in-process    │   │
│  └──────────────────┘ ──────► └───────────┬──────────────┘   │
│                                            │ flush()          │
│                                            ▼                  │
│                              ┌──────────────────────────┐   │
│                              │  OKF cold archive         │   │
│                              │  log.md, decisions/, …    │   │
│                              └──────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
```

## HotMemoryBuffer

`HotMemoryBuffer` is a dataclass owned by `HermesOKFProvider`. It holds a list of dict items and flushes them through `HermesAgent` → `HermesMemory` into the bundle.

<ParamField body="max_items" type="integer" default="50">
Maximum buffered items before an automatic flush. Set via `HermesOKFConfig.hot_memory_max` or `memory.hot_memory_max` in `~/.hermes/config.yaml`.
</ParamField>

### Push kinds

Each `push()` call stores the caller’s fields plus a UTC `timestamp`. A private `_kind` field selects the cold-archive writer during `flush()`:

| `_kind` | Set by | Cold destination | OKF shape |
|---------|--------|------------------|-----------|
| `observation` | `on_memory_write("memory", …)` | `log.md` via `record_observation` | Log line, category `HermesMemory` |
| `decision` | `on_decision(…)` when `log_decisions` is true | `decisions/{slug}_{date}.md` via `record_decision` | `Concept` with `type: Decision` |
| `tool_call` | `on_tool_call(…)` when `log_tool_calls` is true | `log.md` via `record_tool_call` | Log line, category `Tool-Call`; result truncated to 500 chars |

If `_kind` is omitted, `flush()` defaults to `observation`.

```python
# Observation push (Hermes memory write)
self.hot.push(_kind="observation", content=content, category="HermesMemory")

# Decision push
self.hot.push(_kind="decision", decision=decision, rationale=rationale, tags=tags)

# Tool-call push
self.hot.push(_kind="tool_call", tool_name=tool_name, args=args, result=result)
```

On flush, each item’s `_kind` is popped and dispatched:

- **observation** → `agent.memory.record_observation(observation=content, category=category)`
- **decision** → `agent.memory.record_decision(decision, rationale, tags)`
- **tool_call** → `agent.memory.record_tool_call(tool_name, result_summary)`

After all items are written, the buffer list is cleared and the flushed copy is returned.

## Flush triggers

Flushes move buffered items from process memory to the OKF bundle. Four trigger paths exist:

| Trigger | Entry point | When it runs |
|---------|-------------|--------------|
| Session end | `on_session_end`, `HermesOKFMemoryProvider.shutdown` | Before session close observation and optional snapshot |
| Buffer max | `_maybe_flush` | After `on_memory_write`, `on_tool_call`, or `on_decision` when `len(items) >= max_items` |
| Plan complete | `on_plan_complete` | After plan archive, before optional snapshot |
| Explicit | `snapshot()`, `restore()`, `_flush_hot()` | Before snapshot/restore or manual drain |

```mermaid
sequenceDiagram
    participant H as Hermes hooks
    participant HB as HotMemoryBuffer
    participant P as HermesOKFProvider
    participant M as HermesMemory
    participant B as OKF bundle

    H->>P: on_memory_write / on_tool_call / on_decision
    P->>HB: push(_kind=...)
    P->>P: _maybe_flush()
    alt len(items) >= hot_memory_max
        P->>HB: flush(agent)
        HB->>M: record_observation / record_decision / record_tool_call
        M->>B: append_log or write_concept
    end

    H->>P: on_session_end
    P->>HB: flush(agent)
    HB->>M: drain all kinds
    M->>B: persist
```

<Warning>
Items still in the hot buffer are not searchable until flushed. Search and `prefetch` operate on the cold archive only.
</Warning>

## Hermes flat memory → typed OKF

Hermes exposes flat `memory(action="add", target=…)` writes. `HermesOKFProvider.on_memory_write` maps targets to OKF concepts:

| Hermes `target` | Hot or cold | OKF output |
|-----------------|-------------|------------|
| `memory` | Hot buffer (`_kind="observation"`) | `log.md` entry, category `HermesMemory` |
| `user` | **Cold immediately** (bypasses hot buffer) | `hermes/observations/user_profile_{date}.md`, `type: UserProfile` |

User-profile writes call `bundle.write_concept` directly with tags `["user", "profile"]`. They are durable on write and do not wait for a flush.

Other lifecycle hooks write to cold storage without entering the hot buffer:

- **Session start/end** — `record_observation` with category `Session`, plus `Session` concepts and optional snapshots
- **Plans** — `Plan` concepts under `plans/`, archived to `plans/archive/` on completion (with hot flush)
- **Tool registry** — lazy `register_tool` on first `on_tool_call`, creating `tools/{name}` concepts

`HermesOKFMemoryProvider.sync_turn` forwards `target="memory"` and `target="user"` through `on_memory_write`. Calls with `target="assistant"` are not handled by the provider mapping.

## Configuration

Hot-buffer behavior is controlled through `HermesOKFConfig`:

<ParamField body="hot_memory_max" type="integer" default="50">
Items buffered before `_maybe_flush` drains to disk.
</ParamField>

<ParamField body="log_tool_calls" type="boolean" default="true">
When false, `on_tool_call` skips the hot buffer entirely.
</ParamField>

<ParamField body="log_decisions" type="boolean" default="true">
When false, `on_decision` skips the hot buffer entirely.
</ParamField>

<ParamField body="use_hot_memory" type="boolean" default="true">
Documented on `HermesOKFConfig`. The provider always instantiates `HotMemoryBuffer`; this flag is not consulted by current flush logic.
</ParamField>

Resolution order: `HERMES_OKF_*` environment variables → `~/.hermes/hermes-okf.yaml` → `~/.hermes/config.yaml` (`plugins.hermes_okf` or `memory.*` keys). See the configuration reference for the full field list.

### Tuning buffer size

If disk writes feel too frequent during long sessions, raise `hot_memory_max`:

```yaml
# ~/.hermes/config.yaml
memory:
  provider: hermes-okf
  hot_memory_max: 200
```

Trade-off: a larger buffer delays cold-archive visibility and increases data at risk if the process exits without a flush trigger.

## Cold-archive destinations by event kind

After flush (or on direct write), data lands in predictable bundle locations:

| Event | Primary file(s) | `type` / category |
|-------|-----------------|-------------------|
| Observation | `log.md` | Log category (`HermesMemory`, `Session`, `Plan`, …) |
| Decision | `decisions/*.md` | `Decision` |
| Tool call | `log.md` + `tools/{name}.md` | `Tool-Call` log; `Tool` concept |
| User profile | `hermes/observations/user_profile_*.md` | `UserProfile` |
| Session | `sessions/{id}.md` | `Session` |
| Snapshot | `snapshots/*.md` | Snapshot metadata concept |

Log entries use dated `## YYYY-MM-DD` sections with bullet lines: `* **{category}**: {entry}`.

## Verification

<Steps>
<Step title="Write and flush a memory observation">

```python
from hermes_okf.hermes_integration import HermesOKFConfig, HermesOKFProvider

config = HermesOKFConfig(bundle_path="/tmp/okf-test", auto_snapshot=False)
provider = HermesOKFProvider(config)
provider.on_memory_write("memory", "User prefers Python")
provider._flush_hot()
```

</Step>
<Step title="Confirm cold persistence">

```python
log = provider.agent.memory.bundle.read_log()
assert "User prefers Python" in log
```

</Step>
<Step title="Confirm decision concept">

```python
provider.on_decision("Use Claude", "Better reasoning", tags=["model"])
provider._flush_hot()
assert len(provider.agent.memory.bundle.list_concepts("decisions")) > 0
```

</Step>
</Steps>

Integration tests in `tests/test_hermes_integration.py` cover memory writes, tool-call registration, decision concepts, and plan lifecycle with explicit `_flush_hot()` calls.

## Related pages

<CardGroup>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, `Concept` fields, and Hermes-native directories (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`).
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
`HermesOKFProvider` and `HermesOKFMemoryProvider` session hooks, `prefetch`, `sync_turn`, and exposed memory tools.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, env vars, and `memory.*` keys including `hot_memory_max`.
</Card>
<Card title="Python agent integration" href="/python-agent-integration">
Direct cold writes via `HermesMemory` and decorators without the hot buffer.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery when buffer flushes are too frequent or memory is not visible in search.
</Card>
</CardGroup>

---

## 07. Install Hermes plugin

> Register `hermes-okf` via `hermes-okf-install`: wrapper files in `~/.hermes/plugins/hermes-okf/`, auto-updates to `~/.hermes/config.yaml` (`plugins.enabled`, `memory.provider`, `bundle_path`), `hermes memory setup`, and uninstall semantics.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/07-install-hermes-plugin.md
- Generated: 2026-06-15T19:25:26.755Z

### Source Files

- `src/hermes_okf/install_plugin.py`
- `docs/HERMES_PLUGIN.md`
- `docs/HERMES_USERS.md`
- `src/hermes_okf/memory_plugin.py`
- `src/hermes_okf/plugin.py`
- `pyproject.toml`

---
title: "Install Hermes plugin"
description: "Register `hermes-okf` via `hermes-okf-install`: wrapper files in `~/.hermes/plugins/hermes-okf/`, auto-updates to `~/.hermes/config.yaml` (`plugins.enabled`, `memory.provider`, `bundle_path`), `hermes memory setup`, and uninstall semantics."
---

`hermes-okf-install` registers the OKF memory provider with Hermes by writing a filesystem plugin wrapper under `~/.hermes/plugins/hermes-okf/` and, when `~/.hermes/config.yaml` already exists, patching `plugins.enabled`, `memory.provider`, and `memory.bundle_path`. Hermes discovers memory providers from that directory—not from the `hermes.memory_providers` setuptools entry point—so the install command is required even after `pip install hermes-okf`.

<Info>
Hermes also loads the `hermes okf` CLI tree through the separate `hermes_agent.plugins` entry point in `pyproject.toml`. That path does not replace the filesystem wrapper; both surfaces ship together after install.
</Info>

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Python | `>=3.9` (`requires-python` in package metadata) |
| Package | `pip install hermes-okf` (see [Installation](/installation)) |
| Hermes home | `~/.hermes/` directory (created by Hermes Agent setup) |
| Core dependency | `pyyaml>=6.0` (bundled with `hermes-okf`) |

Install `hermes-okf` into the same Python environment Hermes uses. If Hermes runs inside `~/.hermes/hermes-agent/venv/`, activate that venv before running the install command.

## Install procedure

<Steps>
<Step title="Install the package">

```bash
pip install hermes-okf
```

Verify the version:

```bash
python -c "import hermes_okf; print(hermes_okf.__version__)"
```

</Step>

<Step title="Register the plugin">

<CodeGroup>
```bash title="Entry-point script"
hermes-okf-install
```

```bash title="Standalone CLI subcommand"
hermes-okf install-plugin
```

```bash title="Module invocation (PATH fallback)"
python -m hermes_okf.install_plugin
```
</CodeGroup>

All three invoke the same `install_plugin()` implementation.

</Step>

<Step title="Finish activation">

If `~/.hermes/config.yaml` was updated, stdout includes:

```
Installed hermes-okf plugin to /home/you/.hermes/plugins/hermes-okf
  Updated ~/.hermes/config.yaml
  Run 'hermes memory setup' to finish activation
```

Run the setup wizard to customize memory settings, then start Hermes:

```bash
hermes memory setup
hermes
```

If config was not modified (missing `config.yaml`, or values already correct), stdout omits the update line and still prints `Run 'hermes memory setup' to activate`.

</Step>

<Step title="Verify">

On first session start, the provider initializes the OKF bundle at the configured `bundle_path` (default `~/.hermes/okf_memory`). Confirm the plugin CLI works:

```bash
hermes okf show config/agent
```

</Step>
</Steps>

## Plugin wrapper layout

`install_plugin()` creates `~/.hermes/plugins/hermes-okf/` with two files:

:::files
~/.hermes/plugins/hermes-okf/
├── plugin.yaml      # Hermes manifest (name, version, hooks)
└── __init__.py      # Imports HermesOKFMemoryProvider
:::

### `plugin.yaml`

Written with the installed package version, a description string, and one lifecycle hook:

```yaml
name: hermes-okf
version: 0.4.6
description: "OKF-based memory provider for Hermes agent — structured, persistent, agent-readable knowledge storage."
hooks:
  - on_session_end
```

The `version` field tracks `hermes_okf.__version__` at install time.

### `__init__.py`

```python
from hermes_okf.memory_plugin import HermesOKFMemoryProvider
__all__ = ["HermesOKFMemoryProvider"]
```

Hermes imports this module when loading plugins from `~/.hermes/plugins/`. The actual `MemoryProvider` implementation lives in the pip-installed `hermes_okf.memory_plugin` package; the wrapper only exposes the class for discovery.

## Config auto-updates

When `~/.hermes/config.yaml` exists and PyYAML is importable, `_configure_hermes_config()` loads the file, applies idempotent patches, and rewrites it only if something changed.

| Key | Action |
|-----|--------|
| `plugins.enabled` | Ensures a YAML list; appends `hermes-okf` if absent |
| `memory.provider` | Sets to `hermes-okf` when not already set |
| `memory.bundle_path` | Adds `~/.hermes/okf_memory` only when the key is missing |

<Warning>
`plugins.enabled` must remain a YAML list, not a JSON-encoded string. Hermes ignores string values and the plugin will not load.
</Warning>

<RequestExample>

```yaml title="Resulting ~/.hermes/config.yaml (excerpt)"
plugins:
  enabled:
    - hermes-okf

memory:
  provider: hermes-okf
  bundle_path: ~/.hermes/okf_memory
```

</RequestExample>

Config patching is skipped when:

- `~/.hermes/config.yaml` does not exist yet
- PyYAML cannot be imported (should not happen after `pip install hermes-okf`)

In those cases, create or edit `config.yaml` manually, or run `hermes memory setup` after Hermes creates the file.

### Config keys the installer does not set

`hermes-okf-install` does not write `memory.agent_id`, `auto_snapshot`, `hot_memory_max`, or other provider toggles. Those are applied later by `hermes memory setup` or manual edits. See [Configuration reference](/configuration-reference) for the full field list and resolution order.

## Why a filesystem wrapper is required

Hermes memory-provider discovery scans `~/.hermes/plugins/<name>/` for `plugin.yaml` plus a Python module exporting a `MemoryProvider` subclass. Although `pyproject.toml` registers:

```toml
[project.entry-points."hermes.memory_providers"]
hermes-okf = "hermes_okf.memory_plugin:HermesOKFMemoryProvider"
```

Hermes core does not read that entry point for provider loading. `hermes-okf-install` bridges pip installation to filesystem discovery.

```mermaid
flowchart LR
  subgraph pip["pip install"]
    PKG["hermes_okf package"]
    EP["entry points<br/>(not used for memory)"]
  end
  subgraph install["hermes-okf-install"]
    WRAP["~/.hermes/plugins/hermes-okf/"]
    CFG["~/.hermes/config.yaml"]
  end
  subgraph hermes["Hermes Agent"]
    DISC["Plugin scanner"]
    MP["MemoryProvider load"]
    CLI["hermes okf CLI"]
  end
  PKG --> WRAP
  install --> CFG
  WRAP --> DISC
  DISC --> MP
  PKG --> CLI
```

The `hermes_agent.plugins` entry point (`hermes_okf.plugin:register`) registers `hermes okf` CLI subcommands independently of the memory wrapper.

## `hermes memory setup`

After install, `hermes memory setup` lists `hermes-okf` when the wrapper directory exists and `plugins.enabled` includes the plugin name. The wizard reads `HermesOKFMemoryProvider.get_config_schema()` and prompts for:

<ParamField body="bundle_path" type="string">
Directory where the OKF memory bundle is stored. Default: `~/.hermes/okf_memory`.
</ParamField>

<ParamField body="agent_id" type="string">
Identifier for this agent's memory namespace. Default: `hermes-agent`.
</ParamField>

<ParamField body="auto_snapshot" type="boolean">
Whether to auto-save snapshots on session end. Default: `true`.
</ParamField>

Submitted values are written by `save_config()` into `memory.*` keys in `~/.hermes/config.yaml`, with `memory.provider` forced to `hermes-okf`. At session start, `initialize()` reads those keys (plus the top-level or `llm.model` field for model sync) and constructs `HermesOKFProvider`.

## Activation behavior

On first Hermes session after install:

1. Hermes loads `HermesOKFMemoryProvider` from the wrapper.
2. `initialize(session_id)` reads `memory.*` from `config.yaml`.
3. `on_session_start()` creates session and config concepts in the OKF bundle.
4. The default bundle path `~/.hermes/okf_memory/` receives `index.md`, `log.md`, and Hermes-native subdirectories (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`).

Hot Hermes memory (`MEMORY.md`, `USER.md`) and the cold OKF archive coexist; see [Two-memory model](/two-memory-model).

## Uninstall semantics

<CodeGroup>
```bash title="Entry-point script"
hermes-okf-uninstall
```

```bash title="Standalone CLI subcommand"
hermes-okf uninstall-plugin
```
</CodeGroup>

`uninstall_plugin()` removes `~/.hermes/plugins/hermes-okf/` with `shutil.rmtree` when the directory exists.

| Artifact | Uninstall behavior |
|----------|-------------------|
| `~/.hermes/plugins/hermes-okf/` | **Removed** |
| OKF bundle at `memory.bundle_path` | **Preserved** |
| `~/.hermes/config.yaml` entries | **Not reverted** — `plugins.enabled`, `memory.provider`, and `bundle_path` remain until edited manually |
| pip package `hermes-okf` | **Not removed** — run `pip uninstall hermes-okf` separately |

<ResponseExample>

```text title="Success stdout"
Removed hermes-okf plugin from /home/you/.hermes/plugins/hermes-okf
```

```text title="Already absent"
hermes-okf plugin not installed
```

</ResponseExample>

After uninstall, Hermes no longer discovers the provider on startup. To fully deactivate, set `memory.provider` to another value or remove `hermes-okf` from `plugins.enabled`.

## Failure modes

<AccordionGroup>
<Accordion title="hermes-okf-install: command not found">

The script lands in your environment's `bin/`. Use `python -m hermes_okf.install_plugin`, activate the venv, or call the venv Python directly (e.g. `~/.hermes/hermes-agent/venv/bin/python -m hermes_okf.install_plugin`).

</Accordion>

<Accordion title="hermes memory setup does not list hermes-okf">

Confirm the wrapper exists (`ls ~/.hermes/plugins/hermes-okf/` shows `__init__.py` and `plugin.yaml`), re-run `hermes-okf-install`, verify `plugins.enabled` is a YAML list containing `hermes-okf`, and restart Hermes.

</Accordion>

<Accordion title="Config was not auto-updated">

`install_plugin()` only patches an existing `~/.hermes/config.yaml`. Create the file via Hermes setup first, then re-run install, or add the `plugins` and `memory` blocks manually.

</Accordion>
</AccordionGroup>

See [Troubleshooting](/troubleshooting) for PATH recovery, stale model sync, and Windows filename constraints.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
PyPI install targets, optional extras, entry-point scripts, and Python version constraints.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, env overrides, and `memory.*` keys written by install and setup.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
Session hooks, `prefetch`/`sync_turn`, and exposed memory tool schemas after activation.
</Card>
<Card title="Hermes CLI reference" href="/hermes-cli-reference">
`hermes okf` subcommands registered through the general plugin entry point.
</Card>
</CardGroup>

---

## 08. Standalone CLI workflows

> Operate an OKF bundle without Hermes: init, validate, list/show concepts, full-text search, log append, graph inspection, snapshot, context build, and session/plan/tool listing with `--path` placement rules.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/08-standalone-cli-workflows.md
- Generated: 2026-06-15T19:26:21.414Z

### Source Files

- `src/hermes_okf/cli.py`
- `README.md`
- `src/hermes_okf/search.py`
- `src/hermes_okf/graph.py`
- `src/hermes_okf/hermes.py`
- `tests/test_search.py`

---
title: "Standalone CLI workflows"
description: "Operate an OKF bundle without Hermes: init, validate, list/show concepts, full-text search, log append, graph inspection, snapshot, context build, and session/plan/tool listing with `--path` placement rules."
---

The `hermes-okf` entry point (`hermes_okf.cli:main`) operates directly on filesystem OKF bundles through `OKFBundle`, `SearchIndex`, `GraphExtractor`, and `OKFValidator`. Agent-oriented subcommands (`snapshot`, `context`, `sessions`, `tools`) delegate to `HermesAgent`, which bootstraps the Hermes-native layout (`config/`, `sessions/`, `plans/`, `tools/`, `snapshots/`) inside the bundle. No Hermes runtime or model provider is required.

<Info>
Standalone CLI and the Hermes plugin share the same bundle format. Commands here read and write the same `.md` + YAML files that `hermes okf` uses when the plugin is installed.
</Info>

## Prerequisites

Install the package and confirm the CLI is on `PATH`:

```bash
pip install hermes-okf
hermes-okf --version
```

For a minimal first bundle, see [Quickstart](/quickstart). For bundle layout and reserved files, see [OKF bundle model](/okf-bundle-model).

## Bundle path (`--path`) placement

Most subcommands inherit a shared `--path` flag via a parent parser. The flag can appear **after the subcommand name** alongside subcommand-specific arguments.

<ParamField body="--path" type="string" default=".">
Root directory of the OKF bundle. Expanded with `expanduser()` and resolved to an absolute path before use.
</ParamField>

| Rule | Behavior |
|------|----------|
| Default | Current working directory (`.`) |
| Placement | After subcommand: `hermes-okf search "query" --path ./knowledge` |
| `init` precedence | Positional `init_path` wins over `--path`: `args.init_path or args.path` |
| No `--path` | `install-plugin`, `uninstall-plugin` (Hermes registration only) |

<CodeGroup>
```bash title="Explicit path after subcommand"
hermes-okf list --path ./knowledge
hermes-okf show projects/my_project --path ./knowledge
```

```bash title="Default path (cwd is the bundle)"
cd ./knowledge
hermes-okf list
hermes-okf validate
```

```bash title="init with positional path"
hermes-okf init ./knowledge
hermes-okf init --path ./knowledge   # equivalent when positional omitted
```
</CodeGroup>

<Warning>
`install-plugin` and `uninstall-plugin` do not accept `--path`. They modify `~/.hermes/plugins/` and config — see [Install Hermes plugin](/install-hermes-plugin).
</Warning>

## Command map

```text
hermes-okf [--version]
├── init [path] [--force] [--path]
├── validate [--path]
├── list [--subdir SUBDIR] [--path]
├── show CONCEPT_ID [--json] [--path]
├── search QUERY [--top-k N] [--path]
├── log [--path]
├── log-append ENTRY [--category CAT] [--path]
├── graph-edges [--path]
├── graph-neighbors CONCEPT_ID [--path]
├── snapshot [--note NOTE] [--agent-id ID] [--path]
├── context QUERY [--top-k N] [--agent-id ID] [--path]
├── sessions [--path]
├── plans [--path]
├── tools [--path]
├── install-plugin
└── uninstall-plugin
```

Agent commands (`snapshot`, `context`, `sessions`, `tools`) construct a `HermesAgent` at the bundle path. First invocation creates `config/agent`, starts a session, and ensures `config/`, `sessions/`, `plans/`, `tools/`, and `snapshots/` exist.

## Initialise a bundle

<Steps>
<Step title="Create the bundle directory">

```bash
hermes-okf init ./knowledge
```

`OKFBundle` creates `index.md`, `log.md`, and seed subdirectories `projects/`, `decisions/`, and `context/` each with an `index.md` stub.

<ResponseExample>
```text
Initialised OKF bundle at /absolute/path/to/knowledge
```
</ResponseExample>

</Step>

<Step title="Handle a non-empty target directory">

If the directory exists and is not empty, init exits with code `1` unless `--force` is passed:

```bash
hermes-okf init ./knowledge --force
```

<ResponseExample>
```text
Error: Directory './knowledge' is not empty. Use --force to overwrite.
```
</ResponseExample>

`--force` does not delete existing files; it allows init to proceed when the directory already has content.

</Step>

<Step title="Validate structure">

```bash
hermes-okf validate --path ./knowledge
```

<ResponseExample>
```text
Bundle is valid.
```
</ResponseExample>

See [OKF validation reference](/okf-validation-reference) for rule details.

</Step>
</Steps>

## Add and inspect concepts

The standalone CLI has no `write` subcommand. Create concepts with the Python SDK, a text editor, or another tool that writes `.md` files with YAML frontmatter.

<Steps>
<Step title="Write a concept (SDK)">

```python
from hermes_okf.bundle import OKFBundle

bundle = OKFBundle("./knowledge")
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "gpu"],
)
```

Every concept file needs a `type` field in frontmatter. `write_concept` auto-adds a UTC `timestamp` when omitted.

</Step>

<Step title="List concept IDs">

```bash
hermes-okf list --path ./knowledge
hermes-okf list --subdir projects --path ./knowledge
```

Prints one concept ID per line (relative path without `.md`). Skips reserved `index.md` and `log.md`.

<ResponseExample>
```text
projects/my_project
```
</ResponseExample>

Empty bundle:

<ResponseExample>
```text
No concepts found.
```
</ResponseExample>

</Step>

<Step title="Show a concept">

```bash
hermes-okf show projects/my_project --path ./knowledge
hermes-okf show projects/my_project --json --path ./knowledge
```

Human-readable output includes `ID`, `Type`, `Title`, `Tags`, `Timestamp`, optional `Resource`, then the markdown body. `--json` emits the full `Concept` dataclass as JSON.

Missing concept exits `1`:

<ResponseExample>
```text
Concept 'projects/missing' not found.
```
</ResponseExample>

</Step>
</Steps>

## Search concepts

`search` uses `SearchIndex`: an in-memory inverted index built on first query, tokenizing lowercase alphanumeric words from `title`, `description`, and `body`.

```bash
hermes-okf search "ffmpeg GPU" --path ./knowledge
hermes-okf search "machine learning" --top-k 5 --path ./knowledge
```

<ParamField body="--top-k" type="integer" default="10">
Maximum number of `(concept_id, score)` pairs to return.
</ParamField>

<ResponseExample>
```text
1.00  projects/my_project
0.50  decisions/gpu_choice
```
</ResponseExample>

Scores are token-match counts normalized by query length. No matches:

<ResponseExample>
```text
No results.
```
</ResponseExample>

<Tip>
For vector retrieval over the same bundle, install `hermes-okf[rag]` and follow [Enable RAG](/enable-rag). The standalone `search` command uses only the standard library.
</Tip>

## Log operations

The bundle's chronological history lives in `log.md`.

```bash
# Read full log
hermes-okf log --path ./knowledge

# Append an entry
hermes-okf log-append "Chose PyTorch over JAX" --category Decision --path ./knowledge
```

<ParamField body="--category" type="string" default="Update">
Prefix label for the log line. Common values: `Decision`, `Observation`, `Plan`.
</ParamField>

`append_log` groups entries under `## YYYY-MM-DD` date headings. Each line is `* **{category}**: {entry}`.

<ResponseExample>
```text
Log entry added.
```
</ResponseExample>

## Graph inspection

Edges come from markdown links `[text](target)` inside concept files. HTTP links are excluded.

```bash
# All directed edges
hermes-okf graph-edges --path ./knowledge

# Outgoing neighbors of one concept
hermes-okf graph-neighbors projects/my_project --path ./knowledge
```

<ResponseExample>
```text
projects/my_project -> decisions/gpu_choice  (see decision)
```
</ResponseExample>

For tag clustering, BFS traversal, and NetworkX export, use `GraphExtractor` in the Python SDK — see [Knowledge graph](/knowledge-graph).

## Snapshot and context build

These commands instantiate `HermesAgent(bundle_path, agent_id)` and operate on agent state stored in the bundle.

### Save a snapshot

```bash
hermes-okf snapshot --path ./knowledge --note "Before deployment"
hermes-okf snapshot --agent-id my-agent --note "Checkpoint" --path ./knowledge
```

<ParamField body="--note" type="string" default="">
Free-text note stored in the snapshot concept metadata.
</ParamField>

<ParamField body="--agent-id" type="string" default="hermes">
Agent identifier written into snapshot metadata and used for session structure.
</ParamField>

Writes a `snapshots/{timestamp}` concept (`type: Snapshot`) containing JSON state: `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, `note`.

<ResponseExample>
```text
Snapshot saved.
```
</ResponseExample>

<Note>
Standalone CLI has no `restore` subcommand. Restoration is available via `hermes okf restore` when the Hermes plugin is installed, or through `HermesAgent.restore()` in the SDK.
</Note>

### Build LLM context

```bash
hermes-okf context "What should I prioritize?" --path ./knowledge
hermes-okf context "GPU decisions" --top-k 3 --agent-id hermes --path ./knowledge
```

<ParamField body="--top-k" type="integer" default="5">
Number of relevant concepts from `memory.recall()` to include in the context block.
</ParamField>

`build_context` assembles markdown sections:

1. Agent ID header
2. System prompt from `config/agent`
3. Active plan body (if `current_plan_id` is set)
4. Relevant memory (search-ranked concepts)
5. Recent log (last 20 lines)
6. Available tools from `tools/` directory

Output is printed to stdout as a single markdown document suitable for pasting into an LLM prompt.

## List sessions, plans, and tools

| Command | Backend | Default agent ID | Output |
|---------|---------|------------------|--------|
| `sessions` | `HermesAgent.list_sessions()` | `hermes` (hardcoded) | Session IDs under `sessions/`, sorted |
| `plans` | `OKFBundle.list_concepts("plans")` | n/a | Active plan concept IDs |
| `tools` | `HermesAgent.list_tools()` | `hermes` (hardcoded) | Tool concept IDs under `tools/` |

```bash
hermes-okf sessions --path ./knowledge
hermes-okf plans --path ./knowledge
hermes-okf tools --path ./knowledge
```

Empty results:

<ResponseExample>
```text
No active plans.
```
</ResponseExample>

<Warning>
`sessions` and `tools` always use agent ID `hermes`, regardless of `--agent-id`. Only `snapshot` and `context` accept `--agent-id`. To inspect state for a custom agent ID, use the Python SDK or write concepts directly.
</Warning>

## End-to-end workflow

<Steps>
<Step title="Bootstrap">

```bash
pip install hermes-okf
hermes-okf init ./knowledge
```

</Step>

<Step title="Populate concepts">

Use the SDK or edit files manually. Ensure each `.md` concept has valid YAML frontmatter with `type`.

</Step>

<Step title="Verify and explore">

```bash
hermes-okf validate --path ./knowledge
hermes-okf list --path ./knowledge
hermes-okf search "project" --path ./knowledge
hermes-okf show projects/my_project --path ./knowledge
```

</Step>

<Step title="Record activity">

```bash
hermes-okf log-append "Initial setup complete" --category Observation --path ./knowledge
hermes-okf graph-edges --path ./knowledge
```

</Step>

<Step title="Agent state (optional)">

```bash
hermes-okf snapshot --note "Baseline" --path ./knowledge
hermes-okf context "summarize project status" --path ./knowledge
hermes-okf sessions --path ./knowledge
hermes-okf plans --path ./knowledge
hermes-okf tools --path ./knowledge
```

</Step>
</Steps>

## Exit codes and stdout conventions

| Condition | Exit code | Message shape |
|-----------|-----------|---------------|
| Success (including empty results) | `0` | Informational stdout |
| `init` on non-empty dir without `--force` | `1` | `Error: Directory '...' is not empty...` |
| `validate` with errors | `1` | `Found N validation error(s):` + per-error lines |
| `show` concept missing | `1` | `Concept '...' not found.` |
| No subcommand | `0` | Prints top-level help |

Validation errors print `OKFValidationError` representations including file path and message.

## Failure modes

<AccordionGroup>
<Accordion title="validate reports missing type field">

Every concept `.md` except reserved `index.md` and `log.md` must have YAML frontmatter with a `type` key. Fix the file or regenerate via `write_concept`.

</Accordion>

<Accordion title="search returns no results but concepts exist">

`SearchIndex` tokenizes lowercase alphanumeric substrings. Hyphenated or camelCase terms may not match as expected. Try shorter keywords or use SDK `fuzzy_search` (optional `rapidfuzz`).

</Accordion>

<Accordion title="graph-edges shows no edges">

Edges require markdown links between concepts: `[label](other_concept)` or `[label](other_concept.md)`. Plain text references do not create edges.

</Accordion>

<Accordion title="snapshot/context creates unexpected directories">

`HermesAgent` ensures `config/`, `sessions/`, `plans/`, `tools/`, and `snapshots/` on first use and starts a session. This is expected standalone behavior, not a Hermes-plugin side effect.

</Accordion>

<Accordion title="plans lists archived or completed plans">

`plans` lists all concept IDs under the `plans/` subdirectory, including active and completed plans not yet archived to `plans/archive/`.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Standalone CLI reference" href="/standalone-cli-reference">
Complete subcommand, flag, and exit-code reference for every `hermes-okf` command.
</Card>

<Card title="Quickstart" href="/quickstart">
Install, init, write a concept, search, and validate in one pass.
</Card>

<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, `Concept` fields, reserved files, and Hermes-native directories.
</Card>

<Card title="Example: OKF bundle basics" href="/example-okf-bundle-basics">
Python SDK recipe mirroring the CLI workflows above.
</Card>

<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Register `hermes-okf` with Hermes for `hermes okf` subcommands and `restore`.
</Card>
</CardGroup>

---

## 09. Python agent integration

> Wire OKF memory into custom agent classes with `HermesMemoryMixin`, `wrap_decision`/`wrap_tool`/`wrap_observation`, `HermesAgent` session/plan/tool lifecycle, snapshots, and `with_context` recall.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/09-python-agent-integration.md
- Generated: 2026-06-15T19:26:18.828Z

### Source Files

- `src/hermes_okf/agent.py`
- `src/hermes_okf/hermes.py`
- `src/hermes_okf/memory.py`
- `docs/HERMES_INTEGRATION.md`
- `examples/hermes_integration.py`
- `examples/full_agent.py`
- `tests/test_agent.py`

---
title: "Python agent integration"
description: "Wire OKF memory into custom agent classes with `HermesMemoryMixin`, `wrap_decision`/`wrap_tool`/`wrap_observation`, `HermesAgent` session/plan/tool lifecycle, snapshots, and `with_context` recall."
---

The `hermes_okf` Python SDK exposes two integration surfaces for custom agents: `HermesMemoryMixin` in `hermes_okf.agent` for drop-in memory on existing classes, and `HermesAgent` in `hermes_okf.hermes` for agents whose configuration, sessions, tools, plans, and snapshots live entirely in an OKF bundle. Both paths persist to the same filesystem bundle via `HermesMemory`; neither requires a hosted model provider.

## Integration surfaces

| Surface | Import | Best for |
|---------|--------|----------|
| `HermesMemoryMixin` | `from hermes_okf.agent import HermesMemoryMixin` | Existing agent classes that need decision, observation, and tool-call logging without restructuring |
| `HermesAgent` | `from hermes_okf import HermesAgent` | New agents where the OKF bundle is the single source of truth for state, tools, plans, and resume |
| `HermesMemory` | `from hermes_okf import HermesMemory` | Direct memory API without mixin or full agent lifecycle |

<Note>
`HermesMemoryMixin` and the `memorize_*` decorators are not exported from `hermes_okf.__init__`. Import them from `hermes_okf.agent`.
</Note>

```mermaid
classDiagram
    class HermesMemoryMixin {
        +memory: HermesMemory
        +wrap_decision(fn)
        +wrap_observation(fn)
        +wrap_tool(fn)
        +with_context(query, top_k)
    }
    class HermesAgent {
        +model: str
        +current_session_id: str
        +current_plan_id: str
        +start_session()
        +end_session()
        +register_tool()
        +create_plan()
        +complete_step()
        +snapshot()
        +restore()
        +build_context()
    }
    class HermesMemory {
        +bundle: OKFBundle
        +record_decision()
        +record_observation()
        +record_tool_call()
        +recall()
    }
    HermesMemoryMixin --> HermesMemory
    HermesAgent --|> HermesMemoryMixin
```

## HermesMemoryMixin

`HermesMemoryMixin` attaches a `HermesMemory` instance to any agent class. Construction takes a bundle path and optional `agent_id` (default `"hermes"`).

<ParamField body="bundle_path" type="string" required>
Filesystem path to the OKF bundle root. Passed to `HermesMemory(bundle_path, agent_id=agent_id)`.
</ParamField>

<ParamField body="agent_id" type="string" default="hermes">
Identifier stamped into log entries and decision rationale strings.
</ParamField>

### Apply wrappers after `super().__init__`

Memory wrappers bind to `self.memory`, which exists only after the mixin constructor runs. Reassign methods in `__init__` after calling `super().__init__`:

```python
from hermes_okf.agent import HermesMemoryMixin

class MyAgent(HermesMemoryMixin):
    def __init__(self):
        super().__init__("./agent_knowledge", agent_id="my-agent-v1")
        self.choose_model = self.wrap_decision(self.choose_model)
        self.scrape_data = self.wrap_tool(self.scrape_data)
        self.inspect = self.wrap_observation(self.inspect)

    def choose_model(self, task: str) -> str:
        return "anthropic/claude-3.5-sonnet" if "code" in task.lower() else "openai/gpt-4o"

    def scrape_data(self, url: str) -> dict:
        return {"url": url, "items": 42}

    def inspect(self, msg: str) -> str:
        return msg.upper()
```

<Warning>
Do not decorate methods at class definition time with `@memorize_decision` unless you pass an explicit `memory=` argument. The mixin pattern relies on `wrap_*` reassignment so `self.memory` is available at call time.
</Warning>

## Memory decorators and `wrap_*` helpers

Three module-level decorators in `hermes_okf.agent` persist function outcomes. Each runs the wrapped function first, then writes to OKF.

| Decorator | `wrap_*` alias | Persistence target | Stored shape |
|-----------|----------------|-------------------|--------------|
| `memorize_decision` | `wrap_decision` | `decisions/` concept | `type: Decision` with title, rationale, tags |
| `memorize_observation` | `wrap_observation` | `log.md` append | Category `"Observation"` |
| `memorize_tool` | `wrap_tool` | `log.md` append | Category `"Tool-Call"`; result summary truncated to 500 chars |

All three capture bound arguments (excluding `self`), format `func.__qualname__(args) -> result`, and resolve memory from either an explicit `memory=` kwarg or `self.memory` when `self` is a `HermesMemoryMixin` instance.

### Standalone decorator usage

Pass a `HermesMemory` instance when decorating free functions or non-mixin methods:

```python
from hermes_okf.agent import memorize_decision
from hermes_okf import HermesMemory

mem = HermesMemory("./knowledge", agent_id="standalone")

@memorize_decision(memory=mem)
def choose(x: int) -> int:
    return x * 2
```

Decision auto-tags include `"decision"`, `"auto-decision"`, and the function name. Rationale defaults to `Called by {agent_id}`.

## Context recall with `with_context`

`HermesMemoryMixin.with_context(query, top_k=3)` delegates to `HermesMemory.recall`, which invalidates the search index and returns up to `top_k` matching `Concept` objects from full-text search.

```python
concepts = agent.with_context("python script", top_k=3)
for c in concepts:
    print(c.title, c.type, c.description)
```

Additional recall methods on `agent.memory`:

| Method | Returns | Behavior |
|--------|---------|----------|
| `recall_by_tag(tag)` | `list[Concept]` | All concepts with the given tag |
| `recall_project(name)` | `Concept \| None` | Exact title or ID suffix match under `projects/` |
| `get_decisions()` | `list[Concept]` | All concepts tagged `decision` |
| `get_recent_log(n_lines=50)` | `str` | Tail of `log.md` |

### Session logging on the mixin path

`HermesMemoryMixin` does not define `start_session` or `end_session`. Call through `self.memory`:

```python
sid = self.memory.start_session()          # optional session_id
self.memory.end_session(sid)
```

`start_session` appends `Session started: {sid}` to the log. `end_session` appends `Session ended: {session_id}`.

## HermesAgent

`HermesAgent` extends `HermesMemoryMixin` and treats the OKF bundle as the agent's complete state backend. On construction it:

1. Creates Hermes-native directories: `config/`, `tools/`, `sessions/`, `plans/`, `plans/archive/`
2. Writes `config/agent` if missing (`type: AgentConfig`, model, system prompt, version)
3. Loads model and system prompt from `config/agent`
4. Calls `start_session()` automatically

<ParamField body="bundle_path" type="string" required>
OKF bundle root path.
</ParamField>

<ParamField body="agent_id" type="string" required>
Unique agent identifier; no default on `HermesAgent` (unlike the mixin default `"hermes"`).
</ParamField>

<ParamField body="model" type="string" default="gpt-4o">
Default LLM model identifier stored in `config/agent`. Any provider-neutral model string is accepted.
</ParamField>

```python
from hermes_okf import HermesAgent

agent = HermesAgent(
    bundle_path="./hermes_agent_brain",
    agent_id="hermes-alpha",
    model="anthropic/claude-3.5-sonnet",
)
```

## Session lifecycle

`HermesAgent.start_session(session_id=None)` overrides the mixin path with richer persistence:

- Sets `current_session_id` (auto-generated filename-safe timestamp if omitted)
- Appends a log entry via `memory.start_session`
- Writes `sessions/{session_id}` as a `Session` concept with `status: active`, `agent_id`, `model`, `started_at`

`HermesAgent.end_session()` takes no arguments. It marks the current session concept `status: completed`, sets `ended_at`, clears `current_session_id`, and appends a log line.

| Method | Returns | Notes |
|--------|---------|-------|
| `list_sessions()` | `list[str]` | Sorted session concept IDs under `sessions/` |
| `recall_session(session_id=None)` | `Concept \| None` | Defaults to most recent session |

<Info>
Re-instantiating `HermesAgent` on an existing bundle reloads `config/agent` and starts a new session. Use `snapshot()` / `restore()` or read `current_session_id` from a snapshot to resume interrupted work.
</Info>

## Tool registry

`register_tool(name, description, schema=None, example="")` writes `tools/{name}` as a `Tool` concept. JSON schemas are serialized to the `schema` frontmatter field. `list_tools()` and `get_tool(name)` read back registered definitions.

```python
agent.register_tool(
    name="search_web",
    description="Search the web for current information.",
    schema={
        "type": "object",
        "properties": {"query": {"type": "string"}},
        "required": ["query"],
    },
    example='search_web(query="Python 3.12 release date")',
)
```

Tool definitions are versioned markdown concepts, readable by other agents or processes that open the same bundle.

## Plan lifecycle

| Method | Behavior |
|--------|----------|
| `create_plan(task, steps)` | Writes `plans/{slug}_{date}` with unchecked markdown steps; sets `current_plan_id`; logs observation with category `Plan` |
| `complete_step(step_index, result="")` | Marks step `step_index` (0-based) as `[x]`; updates `progress` percentage; sets `status: completed` at 100% |
| `archive_plan(plan_id=None)` | Copies plan to `plans/archive/{slug}_{date}`, deletes active plan, clears `current_plan_id` if matched |

`complete_step` is a no-op when `current_plan_id` is unset or `step_index` is out of range.

## Snapshots and restore

`agent.snapshot(note="")` writes `snapshots/{timestamp}` with embedded JSON state:

<ResponseField name="snapshot metadata" type="object">
Includes `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, and `note`.
</ResponseField>

`agent.restore(snapshot_id=None)` reads metadata from the given snapshot or the latest snapshot under `snapshots/`, then restores `model`, `current_session_id`, `current_plan_id`, and `system_prompt` in-process. Returns the snapshot metadata dict, or `{}` when no snapshot exists.

```python
agent.snapshot(note="After completing research")
# ... process continues or crashes ...
resumed = HermesAgent("./hermes_agent_brain", agent_id="hermes-alpha")
meta = resumed.restore()  # restores from latest snapshot
```

## LLM context with `build_context`

`HermesAgent.build_context(query, top_k=5)` assembles a markdown string for model prompts. Sections are included when data exists:

1. Agent ID header
2. System prompt from `config/agent`
3. Active plan body (when `current_plan_id` is set)
4. Relevant memory from `memory.recall(query, top_k)`
5. Recent activity (last 20 log lines)
6. Available tools with descriptions

```python
context = agent.build_context(
    query="What model should I use for this task?",
    top_k=3,
)
# Pass context to your LLM client (provider-neutral)
```

Preview the same output from CLI: `hermes-okf context --path ./hermes_agent_brain "query"`.

## End-to-end integration

<Steps>
<Step title="Choose integration depth">
Use `HermesMemoryMixin` when adding memory to an existing class. Use `HermesAgent` when the bundle should own config, sessions, tools, plans, and snapshots.
</Step>

<Step title="Initialize the bundle">
Point `bundle_path` at an existing OKF bundle or a new directory. `HermesAgent` creates Hermes-native subdirectories automatically; `HermesMemoryMixin` writes concepts and log entries on first use.
</Step>

<Step title="Wire memory capture">
On the mixin path, reassign `wrap_decision`, `wrap_tool`, and `wrap_observation` in `__init__`. On the agent path, call `register_tool`, `create_plan`, and `memory.record_decision` explicitly.
</Step>

<Step title="Recall before LLM calls">
Use `with_context(query)` on mixin agents or `build_context(query)` on `HermesAgent` to inject relevant bundle state into prompts.
</Step>

<Step title="Persist and resume">
Call `snapshot()` before shutdown or at checkpoints. Re-instantiate `HermesAgent` and call `restore()` to reload in-process pointers. End sessions with `agent.end_session()` on the full agent path.
</Step>
</Steps>

### Mixin agent (decorator path)

```python
from hermes_okf.agent import HermesMemoryMixin

class MyAgent(HermesMemoryMixin):
    def __init__(self):
        super().__init__("./agent_knowledge", agent_id="my-agent-v1")
        self.choose_model = self.wrap_decision(self.choose_model)
        self.scrape_data = self.wrap_tool(self.scrape_data)

    def run(self):
        sid = self.memory.start_session()
        model = self.choose_model("Write a Python script")
        data = self.scrape_data("https://example.com")
        context = self.with_context("python script", top_k=3)
        self.memory.end_session(sid)
```

### Full agent path

See `examples/full_agent.py` for register tools → create plan → complete steps → record decision → build context → snapshot → resume.

## Relationship to Hermes provider integration

`HermesOKFProvider` in `hermes_okf.hermes_integration` constructs a `HermesAgent` internally and routes Hermes session hooks through a `HotMemoryBuffer` before flushing to the cold OKF archive. Custom Python agents using `HermesMemoryMixin` or `HermesAgent` write directly to cold storage without the hot buffer unless you implement buffering yourself.

<CardGroup>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
Session hooks, hot/cold memory, and `search_memory` / `snapshot_memory` tool schemas for the Hermes CLI plugin path.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot buffer flush triggers and how provider callbacks map to OKF concept types.
</Card>
</CardGroup>

## Failure modes

| Symptom | Cause | Recovery |
|---------|-------|----------|
| No decisions after wrapped call | `wrap_*` applied before `super().__init__` | Reassign wrappers after mixin construction |
| `complete_step` has no effect | `current_plan_id` unset or wrong index | Call `create_plan` first; use 0-based `step_index` |
| `restore()` returns `{}` | No snapshots in bundle | Call `snapshot()` before restore |
| Session not marked completed | Mixin path used without `HermesAgent.end_session()` | Use `agent.end_session()` on `HermesAgent`, or `memory.end_session(sid)` on mixin path |
| Empty `with_context` results | Bundle has no searchable concepts yet | Write decisions or projects first; lower `top_k` only after content exists |

## Related pages

<CardGroup>
<Card title="Python SDK reference" href="/python-sdk-reference">
Complete public API signatures for `HermesMemory`, `HermesAgent`, `OKFBundle`, and related types.
</Card>
<Card title="Full Hermes agent example" href="/example-full-agent">
End-to-end bundle state from `examples/full_agent.py` and `examples/hermes_integration.py`.
</Card>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout for `config/`, `sessions/`, `plans/`, `tools/`, and `snapshots/`.
</Card>
<Card title="Quickstart" href="/quickstart">
Initialize a bundle and verify persistence before wiring an agent class.
</Card>
</CardGroup>

---

## 10. Hermes provider integration

> Integrate via `HermesOKFProvider` and `HermesOKFMemoryProvider`: session hooks, memory/tool/decision/plan callbacks, `prefetch`/`sync_turn`, exposed tool schemas (`search_memory`, `snapshot_memory`), and model sync from Hermes `config.yaml`.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/10-hermes-provider-integration.md
- Generated: 2026-06-15T19:26:42.313Z

### Source Files

- `src/hermes_okf/hermes_integration.py`
- `src/hermes_okf/memory_plugin.py`
- `docs/HERMES_USERS.md`
- `docs/HERMES_PLUGIN.md`
- `tests/test_hermes_integration.py`
- `tests/test_hermes.py`

---
title: "Hermes provider integration"
description: "Integrate via `HermesOKFProvider` and `HermesOKFMemoryProvider`: session hooks, memory/tool/decision/plan callbacks, `prefetch`/`sync_turn`, exposed tool schemas (`search_memory`, `snapshot_memory`), and model sync from Hermes `config.yaml`."
---

`hermes-okf` exposes two integration surfaces for Hermes Agent: `HermesOKFProvider` (the core hook implementation backed by `HermesAgent` and an OKF bundle) and `HermesOKFMemoryProvider` (the Hermes `MemoryProvider` ABC adapter that Hermes loads at session start). Together they mirror Hermes lifecycle events into typed OKF concepts, buffer fast writes through `HotMemoryBuffer`, and sync the active LLM model from `~/.hermes/config.yaml` into the `config/agent` concept.

## Architecture

```mermaid
flowchart TB
    subgraph Hermes["Hermes Agent runtime"]
        MP["MemoryProvider ABC"]
        CLI["hermes okf CLI"]
    end

    subgraph Adapter["hermes_okf.memory_plugin"]
        MMP["HermesOKFMemoryProvider"]
    end

    subgraph Core["hermes_okf.hermes_integration"]
        HOP["HermesOKFProvider"]
        HMB["HotMemoryBuffer"]
        CFG["HermesOKFConfig"]
    end

    subgraph State["OKF bundle (~/.hermes/okf_memory)"]
        HA["HermesAgent"]
        OKF["OKFBundle + SearchIndex"]
    end

    MP --> MMP
    MMP --> HOP
    CLI --> HOP
    HOP --> CFG
    HOP --> HMB
    HOP --> HA
    HA --> OKF
    HMB -->|"flush on session end / buffer max"| HA
```

| Component | Module | Role |
|-----------|--------|------|
| `HermesOKFProvider` | `hermes_integration.py` | Session, memory, tool, decision, and plan hooks; search, snapshot, and optional RAG |
| `HermesOKFMemoryProvider` | `memory_plugin.py` | Hermes `MemoryProvider` ABC: `initialize`, `sync_turn`, `prefetch`, `shutdown`, tool schemas |
| `get_provider()` | `hermes_integration.py` | Process-wide singleton `HermesOKFProvider` |
| `HermesOKFConfig` | `hermes_integration.py` | Config dataclass with env/file resolution |

<Note>
Hermes discovers memory providers from `~/.hermes/plugins/` (filesystem) and the `hermes.memory_providers` pip entry point. Run `hermes-okf-install` to create the plugin wrapper and update `~/.hermes/config.yaml`.
</Note>

## Discovery and registration

Hermes loads `HermesOKFMemoryProvider` through two paths:

1. **Filesystem plugin** — `hermes-okf-install` writes `~/.hermes/plugins/hermes-okf/__init__.py` and `plugin.yaml`, and sets `memory.provider: hermes-okf` in `config.yaml`.
2. **Pip entry point** — `pyproject.toml` registers `hermes-okf = "hermes_okf.memory_plugin:HermesOKFMemoryProvider"` under `hermes.memory_providers`.

The `hermes okf` CLI subcommand registers separately via the `hermes_agent.plugins` entry point (`hermes_okf.plugin.register` → `cli_extension.register_cli`).

<Steps>
<Step title="Install and register">

```bash
pip install hermes-okf
hermes-okf-install
```

Creates `~/.hermes/plugins/hermes-okf/` and updates `plugins.enabled` and `memory.provider` in `~/.hermes/config.yaml`.

</Step>
<Step title="Configure memory keys">

```yaml
plugins:
  enabled:
    - hermes-okf

memory:
  provider: hermes-okf
  bundle_path: ~/.hermes/okf_memory
  agent_id: hermes-agent
  auto_snapshot: true
  log_tool_calls: true
  hot_memory_max: 50
```

`plugins.enabled` must be a YAML list, not a string.

</Step>
<Step title="Activate and verify">

```bash
hermes memory setup   # optional: customize bundle_path, agent_id, auto_snapshot
hermes                # start a session — provider initializes on first turn
hermes okf show config/agent
```

Expect an `AgentConfig` concept with the model read from Hermes `config.yaml`.

</Step>
</Steps>

## Configuration resolution

`HermesOKFConfig.from_hermes_config()` resolves settings in this order:

1. `HERMES_OKF_*` environment variables (`BUNDLE_PATH`, `AGENT_ID`, `AUTO_SNAPSHOT`, `ENABLE_RAG`)
2. `~/.hermes/hermes-okf.yaml`
3. `~/.hermes/config.yaml` → `plugins.hermes_okf`
4. Built-in defaults

`HermesOKFMemoryProvider.initialize()` reads the `memory` block from `~/.hermes/config.yaml` directly and additionally pulls the active model from the top-level `model` key or `llm.model`.

<ParamField body="bundle_path" type="string" default="~/.hermes/okf_memory">
OKF bundle root directory.
</ParamField>

<ParamField body="agent_id" type="string" default="hermes">
Agent identifier written into OKF concepts and log entries.
</ParamField>

<ParamField body="model" type="string" default="">
LLM model identifier. Empty in `HermesOKFConfig` defaults; populated from Hermes `config.yaml` during `initialize()`.
</ParamField>

<ParamField body="auto_snapshot" type="boolean" default="true">
Save a snapshot on session start and end (and on plan completion).
</ParamField>

<ParamField body="log_tool_calls" type="boolean" default="true">
Record tool invocations to the hot buffer and lazy-register tools in OKF.
</ParamField>

<ParamField body="log_decisions" type="boolean" default="true">
Record strategic decisions to the hot buffer.
</ParamField>

<ParamField body="hot_memory_max" type="integer" default="50">
Maximum hot-buffer items before automatic flush to the cold archive.
</ParamField>

<ParamField body="enable_rag" type="boolean" default="false">
Enable ChromaDB vector search (requires `pip install hermes-okf[rag]`).
</ParamField>

## Model sync from Hermes config.yaml

On every `HermesOKFMemoryProvider.initialize()` call, the adapter:

1. Reads `model` from `config.yaml` (top-level `model`, falling back to `llm.model`)
2. Sets `config.model` on the underlying `HermesOKFProvider`
3. Writes or updates the `config/agent` OKF concept with `type: AgentConfig`, the synced model, system prompt, and package version

```yaml
# ~/.hermes/config.yaml
model: anthropic/claude-sonnet-4
# or
llm:
  model: openai/gpt-4o
```

If `config/agent` shows a stale model, restart Hermes to trigger re-initialization. Inspect with `hermes okf show config/agent`.

## Session hooks

### HermesOKFProvider

| Hook | Signature | OKF effect |
|------|-----------|------------|
| Session start | `on_session_start(session_id)` | Calls `agent.start_session`, records a `Session` observation, optional snapshot |
| Session end | `on_session_end(session_id)` | Flushes hot buffer, records end observation, calls `agent.end_session`, optional snapshot |

On init, `HermesOKFProvider` creates the Hermes-native directory layout under the bundle: `hermes/sessions`, `hermes/tools`, `hermes/plans`, `hermes/plans/archive`, `hermes/decisions`, `hermes/observations`, `hermes/snapshots`.

### HermesOKFMemoryProvider

| Hook | When called | Delegates to |
|------|-------------|--------------|
| `initialize(session_id, **kwargs)` | Session bootstrap | Builds `HermesOKFConfig` from `config.yaml`, creates `HermesOKFProvider`, calls `on_session_start`, syncs `config/agent` |
| `shutdown()` | Clean exit | `on_session_end` + hot-buffer flush |
| `on_session_start(session_id)` | Post-initialize lifecycle | `provider.on_session_start` |
| `on_session_end(messages)` | End-of-session extraction | `provider.on_session_end` |

`initialize` accepts `hermes_home`, `platform`, and `agent_identity` kwargs from Hermes but reads bundle settings from the `memory` config block.

## Memory, tool, decision, and plan callbacks

### Memory writes

`HermesOKFProvider.on_memory_write(target, content)` maps Hermes flat memory to typed OKF storage:

| `target` | Storage | OKF type |
|----------|---------|----------|
| `"memory"` | Hot buffer → flush | `Observation` (category `HermesMemory`) |
| `"user"` | Direct write | `UserProfile` at `hermes/observations/user_profile_<date>` |

`HermesOKFMemoryProvider.on_memory_write(action, target, content, metadata)` mirrors built-in Hermes writes when `action == "add"` and `target` is `"memory"` or `"user"`.

<Warning>
`sync_turn` passes `target="assistant"` for assistant content, but `HermesOKFProvider.on_memory_write` does not handle the `"assistant"` target. Assistant turn text is not persisted unless you extend the provider or route it through `target="memory"`.
</Warning>

### Tool calls

`on_tool_call(tool_name, args, result)`:

- Pushes a `tool_call` item to `HotMemoryBuffer` when `log_tool_calls` is true
- Lazy-registers the tool via `_ensure_tool_registered` if not already in the registry
- Optionally snapshots when `snapshot_on_tool_call` is true

### Decisions

`on_decision(decision, rationale="", tags=None)` pushes a `decision` item to the hot buffer when `log_decisions` is true. On flush, `HermesAgent.memory.record_decision` writes a `Decision` concept under `decisions/`.

### Plans

| Hook | Effect |
|------|--------|
| `on_plan_create(plan_name, steps)` | Creates a `Plan` concept; returns `plan_id` |
| `on_plan_step_complete(plan_id, step_index, result)` | Marks step complete, updates progress |
| `on_plan_complete(plan_id)` | Records completion observation, archives plan, flushes hot buffer, optional snapshot |

## Hot memory and flush behavior

`HotMemoryBuffer` holds in-process items (`observation`, `decision`, `tool_call`) with UTC timestamps. Flush triggers:

- `on_session_end`
- Buffer length ≥ `hot_memory_max` (checked after each push via `_maybe_flush`)
- Explicit `snapshot()`, `restore()`, or `on_plan_complete`

On flush, items route to `HermesAgent.memory.record_observation`, `record_decision`, or `record_tool_call`.

## Turn sync and prefetch

### `sync_turn`

```python
def sync_turn(
    self,
    user_content: str,
    assistant_content: str,
    *,
    session_id: str = "",
    messages: list[dict[str, Any]] | None = None,
) -> None
```

Persists a completed conversation turn:

- **User content** → `on_memory_write("user", user_content)` (writes `UserProfile`)
- **Assistant content** → `on_memory_write("assistant", assistant_content)` (no-op at provider level today)
- **Messages** — extracts `tool_calls` from assistant messages and tool results from `role: tool` messages, forwarding each to `on_tool_call`

### `prefetch`

```python
def prefetch(self, query: str, *, session_id: str = "") -> str
```

Runs full-text search (`provider.search(query, top_k=5)`), reads matching concepts, and returns a formatted markdown block:

```
## Relevant Memory (from OKF bundle)

- [Decision] Use Python (score: 0.85)
  User prefers Python for scripting tasks...
```

Returns an empty string when the provider is uninitialized or the query is empty. Hermes injects this block into context before the model call.

## Exposed tool schemas

`get_tool_schemas()` returns function definitions Hermes can expose to the agent:

### `search_memory`

<ResponseField name="query" type="string" required>
Full-text search query against the OKF bundle.
</ResponseField>

<ResponseField name="top_k" type="integer" default="5">
Number of results to return.
</ResponseField>

### `snapshot_memory`

<ResponseField name="note" type="string" default="">
Optional note stored with the snapshot.
</ResponseField>

<Info>
The `get_tool_schemas` docstring references `restore_memory`, but the current implementation returns only `search_memory` and `snapshot_memory`. Use `hermes okf restore` or `provider.restore()` for snapshot recovery.
</Info>

## Direct Python usage

For custom integrations without the Hermes `MemoryProvider` ABC, use `HermesOKFProvider` directly:

```python
from hermes_okf import HermesOKFProvider

provider = HermesOKFProvider()
provider.on_session_start("deploy-001")

provider.on_memory_write("memory", "User prefers dark mode")
provider.on_tool_call("search_web", {"query": "OKF"}, "Found 5 results")
provider.on_decision("Use 3 replicas", "Moderate traffic expected", tags=["deploy"])

plan_id = provider.on_plan_create("Deploy service", ["Build", "Push", "Deploy"])
provider.on_plan_step_complete(plan_id, 0, "Built in 45s")
provider.on_plan_complete(plan_id)

provider.on_session_end("deploy-001")
```

Or use the singleton:

```python
from hermes_okf import get_provider

provider = get_provider()
results = provider.search("deployment", top_k=5)
context = provider.build_context("What did we decide about replicas?")
```

`HermesOKFProvider.__getattr__` delegates unknown attributes to the underlying `HermesAgent`, so `provider.agent`, `provider.create_plan`, and `provider.list_tools` are available.

## State management

| Method | Behavior |
|--------|----------|
| `snapshot(note="")` | Flushes hot buffer, saves full agent state to `hermes/snapshots/` |
| `restore()` | Flushes hot buffer, returns snapshot metadata dict |
| `resume()` | Calls `restore()` and records a `Resume` observation |

CLI equivalents: `hermes okf snapshot --note "..."` and `hermes okf restore`.

## System prompt block

`HermesOKFMemoryProvider.system_prompt_block()` returns static text informing the model that memory is persisted as OKF markdown at `~/.hermes/okf_memory/`. This supplements Hermes' native `MEMORY.md` / `USER.md` hot memory.

## Config wizard integration

`get_config_schema()` defines fields for `hermes memory setup`:

- `bundle_path` (default `~/.hermes/okf_memory`)
- `agent_id` (default `hermes-agent`)
- `auto_snapshot` (default `true`, choices `[True, False]`)

`save_config(values, hermes_home)` writes non-secret keys into `config.yaml` under the `memory` block and sets `memory.provider` to `hermes-okf`.

## Hook event reference

| Hermes event | Provider method | OKF output |
|--------------|-----------------|------------|
| Session start | `on_session_start` | `Session` concept, optional snapshot |
| Session end | `on_session_end` | Hot flush, end observation, optional snapshot |
| `memory(action="add", target="memory")` | `on_memory_write("memory", …)` | `Observation` via hot buffer |
| `memory(action="add", target="user")` | `on_memory_write("user", …)` | `UserProfile` concept |
| Tool invocation | `on_tool_call` | `Tool-Call` log entry + tool registry |
| Strategic choice | `on_decision` | `Decision` concept |
| Plan created | `on_plan_create` | `Plan` concept with checkable steps |
| Plan step done | `on_plan_step_complete` | Step marked `[x]`, progress updated |
| Plan finished | `on_plan_complete` | Plan archived, hot flush, snapshot |
| Turn completed | `sync_turn` | User profile update + tool call extraction |
| Pre-turn recall | `prefetch` | Top-5 search results as markdown context |

## Troubleshooting

| Symptom | Cause | Recovery |
|---------|-------|----------|
| Provider not loaded | Missing plugin directory or wrong `plugins.enabled` shape | Run `hermes-okf-install`; ensure `plugins.enabled` is a YAML list |
| Stale model in `config/agent` | Session not re-initialized after config change | Restart Hermes to re-run `initialize()` |
| `is_available()` returns false | `pyyaml` not installed | `pip install pyyaml` (core dependency) |
| Empty `prefetch` results | Bundle empty or query mismatch | Write concepts first; verify with `hermes okf search` |
| Assistant turns not in OKF | `"assistant"` target unmapped in `on_memory_write` | Route important assistant facts through `target="memory"` |

## Related pages

<CardGroup>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Register via `hermes-okf-install`, configure `plugins.enabled`, and run `hermes memory setup`.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot `HotMemoryBuffer` vs cold OKF archive, flush triggers, and Hermes flat-memory mapping.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, `HERMES_OKF_*` env vars, and resolution order.
</Card>
<Card title="Hermes CLI reference" href="/hermes-cli-reference">
`hermes okf` subcommands dispatched through `HermesOKFProvider`.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
`HermesAgent`, `get_provider()`, and exported public API.
</Card>
<Card title="Example: full agent" href="/example-full-agent">
End-to-end session, plan, tool, and snapshot workflow.
</Card>
</CardGroup>

---

## 11. Enable RAG

> Optional vector retrieval over OKF bundles: `pip install hermes-okf[rag]`, LangChain `DirectoryLoader` + `MarkdownHeaderTextSplitter`, Chroma persistence, `HERMES_OKF_ENABLE_RAG` config flag, and provider-neutral embedding model selection.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/11-enable-rag.md
- Generated: 2026-06-15T19:27:16.570Z

### Source Files

- `examples/rag_integration.py`
- `pyproject.toml`
- `README.md`
- `src/hermes_okf/hermes_integration.py`
- `docs/HERMES_USERS.md`

---
title: "Enable RAG"
description: "Optional vector retrieval over OKF bundles: `pip install hermes-okf[rag]`, LangChain `DirectoryLoader` + `MarkdownHeaderTextSplitter`, Chroma persistence, `HERMES_OKF_ENABLE_RAG` config flag, and provider-neutral embedding model selection."
---

Vector retrieval in `hermes-okf` is an optional layer on top of the filesystem OKF bundle. The core package depends only on `pyyaml` and ships inverted-index full-text search via `SearchIndex`. Installing `hermes-okf[rag]` adds LangChain and ChromaDB so you can embed bundle markdown, persist vectors locally, and run semantic queries. OKF markdown remains the source of truth; Chroma is a derived index you rebuild when the bundle changes.

## When to use RAG vs full-text search

| Capability | Mechanism | Best for |
|------------|-----------|----------|
| Keyword / token match | `SearchIndex` (stdlib inverted index) | Exact terms, concept IDs, tags |
| `hermes-okf search` / `provider.search()` | Same `SearchIndex` | CLI and provider keyword recall |
| Semantic similarity | Chroma + embeddings via `[rag]` extra | Paraphrased queries, conceptually related content |

<Note>
RAG does not replace OKF storage or the built-in search index. It adds a vector index derived from the same `.md` files. For hybrid recall, combine `provider.search()` (keywords) with `provider.rag_search()` (semantics).
</Note>

## Install the RAG extra

<Steps>
<Step title="Install optional dependencies">

```bash
pip install hermes-okf[rag]
```

This pulls in:

| Package | Role |
|---------|------|
| `langchain` | Text splitting utilities |
| `langchain-community` | `DirectoryLoader`, `TextLoader` |
| `langchain-chroma` | Chroma vector store |
| `langchain-openai` | Default `OpenAIEmbeddings` adapter |

</Step>

<Step title="Set embedding provider credentials">

The built-in provider path uses `OpenAIEmbeddings`. Export the API key your embedding provider expects (for OpenAI-compatible endpoints, typically `OPENAI_API_KEY`). Custom pipelines can swap in any LangChain `Embeddings` implementation without changing OKF files.

</Step>

<Step title="Verify import">

```python
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
```

If this raises `ImportError`, the `[rag]` extra is not installed in the active environment.

</Step>
</Steps>

## Architecture

```mermaid
flowchart LR
  subgraph OKF["OKF bundle (source of truth)"]
    MD["**/*.md concepts"]
  end

  subgraph LC["LangChain ingestion"]
    DL["DirectoryLoader + TextLoader"]
    SP["MarkdownHeaderTextSplitter"]
  end

  subgraph VS["Vector store"]
    CH["Chroma persist dir"]
  end

  subgraph EMB["Embedding provider (swappable)"]
    OE["OpenAIEmbeddings (default)"]
  end

  MD --> DL --> SP --> CH
  OE --> CH
  CH --> RET["retriever.invoke() / rag_search()"]
```

Chroma persistence locations differ by integration path:

| Path | Persist directory |
|------|-------------------|
| `HermesOKFProvider.rag_search()` | `{bundle_path}/.chroma` |
| `examples/rag_integration.py` | `./chroma_okf_db` (caller-defined) |

## Provider API: `HermesOKFProvider.rag_search()`

`HermesOKFProvider` in `hermes_okf.hermes_integration` exposes semantic search when the `[rag]` extra is installed.

<RequestExample>

```python
from hermes_okf import HermesOKFProvider

provider = HermesOKFProvider()

results = provider.rag_search("deployment strategies for Python services", top_k=5)
for r in results:
    print(f"{r['source']}: {r['content'][:100]}")
```

</RequestExample>

<ResponseExample>

```python
[
    {"source": "/path/to/bundle/hermes/decisions/3_replicas.md", "content": "## Decision\nUse 3 replicas for..."},
    # ...
]
```

</ResponseExample>

### Index build behavior

On the first `rag_search()` call, if `{bundle_path}/.chroma` does not exist, the provider runs `_build_rag_index()`:

1. `DirectoryLoader` loads all `**/*.md` under `bundle_path` with UTF-8 `TextLoader`.
2. `MarkdownHeaderTextSplitter` splits on `#` (Header 1) and `##` (Header 2).
3. `Chroma.from_documents()` embeds splits and writes to `{bundle_path}/.chroma`.

Subsequent calls load the existing Chroma directory. The index is **not** automatically rebuilt when bundle markdown changes.

<Warning>
After adding or editing concepts, delete `{bundle_path}/.chroma` (or call your own rebuild logic) to refresh vectors. Stale indexes return outdated chunks.
</Warning>

### Missing extra error

If `[rag]` is not installed, `rag_search()` raises:

```
ImportError: RAG requires hermes-okf[rag]. Install: pip install hermes-okf[rag]
```

## Custom pipeline: `examples/rag_integration.py`

For full control over persist path, chunking, and embedding provider, build the index yourself. The example script mirrors the provider's ingestion steps but keeps Chroma outside the bundle:

<CodeGroup>

```python title="Load bundle markdown"
from hermes_okf.bundle import OKFBundle
from langchain_community.document_loaders import DirectoryLoader, TextLoader

bundle = OKFBundle("./my_knowledge")

loader = DirectoryLoader(
    str(bundle.root),
    glob="**/*.md",
    loader_cls=TextLoader,
    loader_kwargs={"encoding": "utf-8"},
)
docs = loader.load()
```

```python title="Split on headers"
from langchain.text_splitter import MarkdownHeaderTextSplitter

splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[("#", "Header 1"), ("##", "Header 2")]
)
splits = []
for doc in docs:
    splits.extend(splitter.split_text(doc.page_content))
```

```python title="Embed and query"
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=OpenAIEmbeddings(),  # swap for your preferred embedding model
    persist_directory="./chroma_okf_db",
)

retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
results = retriever.invoke("What GPU decisions did we make?")
```

</CodeGroup>

OKF files are unchanged regardless of which embedding class you pass to Chroma.

## Provider-neutral embedding selection

The `[rag]` extra installs `langchain-openai` as the default adapter, but embedding choice is not tied to OKF format or Hermes runtime.

<Tabs>
<Tab title="OpenAI (default)">

```python
from langchain_openai import OpenAIEmbeddings

embedding = OpenAIEmbeddings(model="openai/text-embedding-3-small")
```

Used by `HermesOKFProvider` via `self.config.rag_model` (default `openai/text-embedding-3-small`).

</Tab>

<Tab title="OpenAI-compatible / OpenRouter">

```python
from langchain_openai import OpenAIEmbeddings

embedding = OpenAIEmbeddings(
    model="openai/text-embedding-3-small",
    openai_api_base="https://openrouter.ai/api/v1",
    openai_api_key="your-key",
)
```

The example script comment references `OpenRouterEmbeddings` as an alternative — any LangChain `Embeddings` subclass works in custom pipelines.

</Tab>

<Tab title="Custom provider">

```python
from langchain_core.embeddings import Embeddings

class MyEmbeddings(Embeddings):
    def embed_documents(self, texts: list[str]) -> list[list[float]]: ...
    def embed_query(self, text: str) -> list[float]: ...

vectorstore = Chroma.from_documents(documents=splits, embedding=MyEmbeddings(), ...)
```

</Tab>
</Tabs>

<Info>
`HermesOKFProvider._build_rag_index()` and `rag_search()` hardcode `OpenAIEmbeddings`. To use a non-OpenAI embedding provider with the provider API today, either point `OpenAIEmbeddings` at a compatible API base URL or use the custom pipeline in `examples/rag_integration.py`.
</Info>

## Configuration

RAG settings live on `HermesOKFConfig` and resolve through the same order as other provider settings: environment variables → `~/.hermes/hermes-okf.yaml` → `plugins.hermes_okf` in `~/.hermes/config.yaml` → defaults.

<ParamField body="enable_rag" type="boolean" default="false">
Documents whether your deployment uses vector retrieval. Loaded from `HERMES_OKF_ENABLE_RAG` (truthy: `1`, `true`, `yes`) or YAML `enable_rag`. Does not gate `rag_search()` — call that method explicitly when you need semantic results.
</ParamField>

<ParamField body="rag_model" type="string" default="openai/text-embedding-3-small">
Embedding model passed to `OpenAIEmbeddings` in `_build_rag_index()` and `rag_search()`. Set in `~/.hermes/hermes-okf.yaml` or under `plugins.hermes_okf` in Hermes `config.yaml`. No dedicated `HERMES_OKF_RAG_MODEL` environment variable.
</ParamField>

<ParamField body="HERMES_OKF_ENABLE_RAG" type="string">
Environment override for `enable_rag`. Accepted truthy values: `1`, `true`, `yes` (case-insensitive).
</ParamField>

### Example YAML

```yaml
# ~/.hermes/hermes-okf.yaml
bundle_path: ~/.hermes/okf_memory
enable_rag: true
rag_model: openai/text-embedding-3-small
```

Or under Hermes main config:

```yaml
plugins:
  hermes_okf:
    enable_rag: true
    rag_model: openai/text-embedding-3-small
```

## Hybrid memory model

RAG fits the two-memory pattern alongside Hermes hot memory and the OKF cold archive:

| Layer | Role | Search type |
|-------|------|-------------|
| Hermes hot memory (`MEMORY.md`, `USER.md`) | Always-in-prompt facts | None (inline) |
| OKF cold archive | Typed concepts, graph links | Full-text (`SearchIndex`) |
| Chroma vector index | Derived semantic index | Vector (`rag_search` / custom retriever) |

Use hot memory for critical facts, OKF search for typed/linked knowledge, and RAG when the query is semantic rather than lexical.

## Operational notes

<AccordionGroup>
<Accordion title="No CLI subcommand for RAG">

Neither `hermes-okf` nor `hermes okf` exposes a RAG-specific subcommand. Semantic search runs through Python (`provider.rag_search()`) or a custom script based on `examples/rag_integration.py`. Keyword search remains available via `hermes-okf search` and `hermes okf search`.

</Accordion>

<Accordion title="Chroma lives beside OKF markdown">

The provider stores vectors at `{bundle_path}/.chroma`. Add `.chroma/` to `.gitignore` if you version-control the bundle — vectors are reproducible from markdown.

</Accordion>

<Accordion title="Header splitting limits chunk boundaries">

Both the provider and the example split only on `#` and `##` headers. Content without those headers becomes a single chunk per file. Adjust `headers_to_split_on` in custom pipelines for deeper splits.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Install `hermes-okf[rag]` and optional extras (`[dev]`, `[all]`).
</Card>

<Card title="Configuration reference" href="/configuration-reference">
Full `HermesOKFConfig` fields, env vars, and resolution order.
</Card>

<Card title="RAG pipeline example" href="/example-rag-pipeline">
End-to-end vector workflow from `examples/rag_integration.py`.
</Card>

<Card title="Hermes provider integration" href="/hermes-provider-integration">
`HermesOKFProvider` lifecycle hooks and `search()` vs `rag_search()`.
</Card>

<Card title="Two-memory model" href="/two-memory-model">
Hot buffer vs cold OKF archive and flush triggers.
</Card>
</CardGroup>

---

## 12. Standalone CLI reference

> All `hermes-okf` subcommands, global flags (`--version`, `--path`), per-command arguments (`--force`, `--json`, `--top-k`, `--note`, `--agent-id`, `--category`), exit codes, and stdout/error message shapes.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/12-standalone-cli-reference.md
- Generated: 2026-06-15T19:28:12.563Z

### Source Files

- `src/hermes_okf/cli.py`
- `pyproject.toml`
- `CHANGELOG.md`
- `README.md`
- `tests/test_bundle.py`

---
title: Standalone CLI reference
description: All hermes-okf subcommands, global flags (--version, --path), per-command arguments (--force, --json, --top-k, --note, --agent-id, --category), exit codes, and stdout/error message shapes.
---

The `hermes-okf` command is the standalone terminal interface for operating an OKF bundle without the Hermes agent runtime. It is registered as a console script in `pyproject.toml` and dispatches through `hermes_okf.cli:main`. Use it to initialise bundles, validate conformance, inspect concepts, search, append logs, traverse the knowledge graph, and run agent-adjacent operations (snapshots, context building, session/plan/tool listing).

For task-oriented examples, see [Standalone CLI workflows](/standalone-cli-workflows). For Hermes-integrated commands (`hermes okf …`), see [Hermes CLI reference](/hermes-cli-reference).

## Invocation

```bash
hermes-okf [--version] [-h]
hermes-okf <command> [command-args] [--path PATH]
```

When no subcommand is given, the CLI prints top-level help to stdout and exits `0`.

<RequestExample>

```bash
hermes-okf --version
```

</RequestExample>

<ResponseExample>

```
hermes-okf 0.4.6
```

</ResponseExample>

## Global flags

<ParamField body="--version" type="flag">
Print the installed package version (`hermes-okf {version}`) and exit. Implemented via argparse `action="version"`. Does not require a subcommand.
</ParamField>

<ParamField body="-h, --help" type="flag">
Print help for the root parser or the active subcommand, then exit `0`.
</ParamField>

<ParamField body="--path" type="string" default=".">
Path to the OKF bundle root directory. Shared across bundle-scoped subcommands via a `path_parent` parser (v0.4.6+). Expands `~` and resolves to an absolute path before use. Default is the current working directory.

Placement is flexible: `--path` may appear before or after the subcommand name and other flags.

```bash
hermes-okf validate --path ./knowledge
hermes-okf --path ./knowledge search "ffmpeg GPU"
```
</ParamField>

Commands **without** `--path`: `install-plugin`, `uninstall-plugin`.

### `init` path resolution

`init` accepts the bundle location through either a positional path or `--path`. The handler uses `init_path or path`:

```bash
hermes-okf init ./knowledge          # positional init_path
hermes-okf init --path ./knowledge     # --path only (v0.4.5+)
```

## Exit codes

| Code | When |
|------|------|
| `0` | Success; empty-but-valid results (`No concepts found.`, `No results.`, `Log is empty.`); root help with no subcommand; `install-plugin` / `uninstall-plugin` completion |
| `1` | Application errors: validation failures, missing concept, non-empty `init` target without `--force` |
| `2` | Argparse usage errors: unknown subcommand, missing required positional, invalid flag |

Uncaught Python exceptions (for example `ModuleNotFoundError` when `pyyaml` is missing) propagate as a traceback with a non-zero exit code (typically `1`).

## Subcommand reference

| Command | Bundle `--path` | Positional args | Notable flags |
|---------|-------------------|-----------------|---------------|
| `init` | Yes | `[init_path]` | `--force` |
| `validate` | Yes | — | — |
| `list` | Yes | — | `--subdir` |
| `show` | Yes | `concept_id` | `--json` |
| `search` | Yes | `query` | `--top-k` (default `10`) |
| `log` | Yes | — | — |
| `log-append` | Yes | `entry` | `--category` (default `Update`) |
| `graph-edges` | Yes | — | — |
| `graph-neighbors` | Yes | `concept_id` | — |
| `install-plugin` | No | — | — |
| `uninstall-plugin` | No | — | — |
| `snapshot` | Yes | — | `--note`, `--agent-id` (default `hermes`) |
| `context` | Yes | `query` | `--top-k` (default `5`), `--agent-id` (default `hermes`) |
| `sessions` | Yes | — | — |
| `plans` | Yes | — | — |
| `tools` | Yes | — | — |

---

### `init`

Initialise a new OKF bundle at the target path. Creates `index.md`, `log.md`, and seed subdirectories (`projects/`, `decisions/`, `context/`) when the bundle is new.

<ParamField body="init_path" type="string">
Optional positional bundle path. Takes precedence over `--path` when both are set.
</ParamField>

<ParamField body="--force" type="flag">
Bypass the non-empty directory guard. Does **not** delete or overwrite existing bundle files; `OKFBundle` skips re-initialisation when `index.md` already exists.
</ParamField>

<RequestExample>

```bash
hermes-okf init ./knowledge
hermes-okf init --path ~/.hermes/okf_memory
```

</RequestExample>

<ResponseExample>

```
Initialised OKF bundle at /absolute/path/to/knowledge
```

</ResponseExample>

**Error (exit `1`):**

```
Error: Directory '/path/to/dir' is not empty. Use --force to overwrite.
```

---

### `validate`

Run `OKFValidator` over the bundle: reserved files (`index.md`, `log.md`), frontmatter presence, YAML shape, and required `type` field on every concept `.md` file.

<RequestExample>

```bash
hermes-okf validate --path ./knowledge
```

</RequestExample>

<ResponseExample>

Success:

```
Bundle is valid.
```

Failure:

```
Found 1 validation error(s):
  - OKFValidationError(bad.md:1 -> Missing required 'type' field in frontmatter)
```

</ResponseExample>

Each error line is the `repr()` of an `OKFValidationError` object (`file`, optional `line`, `message`). See [OKF validation reference](/okf-validation-reference) for the full rule set.

---

### `list`

Print concept IDs, one per line (relative paths without `.md`). Excludes `index.md` and `log.md`.

<ParamField body="--subdir" type="string">
Restrict listing to concepts under this subdirectory (for example `projects`).
</ParamField>

<RequestExample>

```bash
hermes-okf list --path ./knowledge
hermes-okf list --path ./knowledge --subdir projects
```

</RequestExample>

<ResponseExample>

```
projects/gpu
decisions/model_strategy
```

Empty bundle:

```
No concepts found.
```

</ResponseExample>

---

### `show`

Display a single concept by ID (for example `projects/my_project`, `config/agent`).

<ParamField body="concept_id" type="string" required>
Concept ID — bundle-relative path without `.md`.
</ParamField>

<ParamField body="--json" type="flag">
Emit the full `Concept` dataclass as indented JSON (`asdict`, `default=str`) instead of the human-readable layout.
</ParamField>

<RequestExample>

```bash
hermes-okf show --path ./knowledge projects/gpu
hermes-okf show --path ./knowledge projects/gpu --json
```

</RequestExample>

<ResponseExample>

Human-readable (default):

```
ID:         projects/gpu
Type:       Project
Title:      GPU Project
Tags:       ffmpeg, gpu
Timestamp:  2026-06-15T19:27:38Z

# GPU Project

Use ffmpeg with GPU acceleration.
```

`Resource:` is printed only when `concept.resource` is set.

JSON (`--json`):

```json
{
  "id": "projects/gpu",
  "type": "Project",
  "title": "GPU Project",
  "description": "",
  "tags": ["ffmpeg", "gpu"],
  "resource": null,
  "timestamp": "2026-06-15T19:27:38Z",
  "body": "# GPU Project\n\nUse ffmpeg with GPU acceleration.",
  "metadata": { "type": "Project", "title": "GPU Project", "tags": ["ffmpeg", "gpu"] }
}
```

Not found (exit `1`):

```
Concept 'nonexistent' not found.
```

</ResponseExample>

---

### `search`

Full-text search via `SearchIndex` over concept title, description, and body. Results are ranked by relevance score.

<ParamField body="query" type="string" required>
Search query string.
</ParamField>

<ParamField body="--top-k" type="integer" default="10">
Maximum number of results to return.
</ParamField>

<RequestExample>

```bash
hermes-okf search --path ./knowledge "ffmpeg GPU"
hermes-okf search --path ./knowledge "deployment" --top-k 5
```

</RequestExample>

<ResponseExample>

```
1.00  projects/gpu
0.50  decisions/model_strategy
```

Format: `{score:.2f}  {concept_id}` — score left-aligned to two decimal places, two spaces, then concept ID.

No matches:

```
No results.
```

</ResponseExample>

---

### `log`

Print the full contents of `log.md` to stdout.

<RequestExample>

```bash
hermes-okf log --path ./knowledge
```

</RequestExample>

<ResponseExample>

```
# Agent Log


## 2026-06-15
* **Decision**: Chose GPU path
```

Empty log:

```
Log is empty.
```

</ResponseExample>

---

### `log-append`

Append a dated entry to `log.md` via `OKFBundle.append_log`.

<ParamField body="entry" type="string" required>
Log entry text.
</ParamField>

<ParamField body="--category" type="string" default="Update">
Entry category label (for example `Decision`, `Observation`, `Tool-Call`). Rendered as `* **{category}**: {entry}` under the current date heading.
</ParamField>

<RequestExample>

```bash
hermes-okf log-append --path ./knowledge "New decision made" --category Decision
```

</RequestExample>

<ResponseExample>

```
Log entry added.
```

</ResponseExample>

---

### `graph-edges`

List all directed edges extracted from markdown links in concept files.

<RequestExample>

```bash
hermes-okf graph-edges --path ./knowledge
```

</RequestExample>

<ResponseExample>

```
a -> b  (other)
projects/gpu -> decisions/model_strategy  (see also)
```

Format: `{source} -> {target}  ({context})` where `context` is the link label. External `http` links are excluded.

Empty graph:

```
No edges found.
```

</ResponseExample>

---

### `graph-neighbors`

Print outgoing neighbor concept IDs for a given source concept.

<ParamField body="concept_id" type="string" required>
Source concept ID.
</ParamField>

<RequestExample>

```bash
hermes-okf graph-neighbors --path ./knowledge projects/gpu
```

</RequestExample>

<ResponseExample>

```
decisions/model_strategy
context/env
```

One target ID per line. Empty:

```
No neighbors found.
```

</ResponseExample>

---

### `snapshot`

Save agent state snapshot to `snapshots/{timestamp}` via `HermesAgent.snapshot`. Creates a `Snapshot` concept with JSON state (agent ID, model, session, plan, system prompt, note).

<ParamField body="--note" type="string" default="">
Optional note stored in the snapshot metadata.
</ParamField>

<ParamField body="--agent-id" type="string" default="hermes">
Agent identifier passed to `HermesAgent`.
</ParamField>

<RequestExample>

```bash
hermes-okf snapshot --path ./knowledge --note "Before deploy"
hermes-okf snapshot --path ./knowledge --agent-id my-agent --note "Checkpoint"
```

</RequestExample>

<ResponseExample>

```
Snapshot saved.
```

</ResponseExample>

The standalone CLI does not expose `restore`; use `hermes okf restore` or the Python SDK. See [Hermes CLI reference](/hermes-cli-reference).

---

### `context`

Build a markdown context string for LLM prompts via `HermesAgent.build_context`. Combines system prompt, active plan, relevant memory (`recall` with `top_k`), recent log lines, and registered tools.

<ParamField body="query" type="string" required>
Query to rank relevant concepts against.
</ParamField>

<ParamField body="--top-k" type="integer" default="5">
Number of relevant concepts to include in the **Relevant Memory** section.
</ParamField>

<ParamField body="--agent-id" type="string" default="hermes">
Agent identifier passed to `HermesAgent`.
</ParamField>

<RequestExample>

```bash
hermes-okf context --path ./knowledge "What should I prioritize?"
```

</RequestExample>

<ResponseExample>

```markdown
# Agent Context: hermes

## System Prompt

You are a helpful, autonomous Hermes agent.

## Relevant Memory

- **GPU Project** (Project): # GPU Project

Use ffmpeg with GPU acceleration.

## Recent Activity

```
# Agent Log

## 2026-06-15
* **Decision**: Chose GPU path
```

## Available Tools

- `search_web` — Search the web for information
```

Sections are omitted when empty (no active plan, no tools, etc.). Output is printed verbatim to stdout with no trailing status line.
</ResponseExample>

---

### `sessions`

List session concept IDs from the `sessions/` subdirectory, sorted chronologically. Uses a fixed `HermesAgent` with `agent_id="hermes"`.

<RequestExample>

```bash
hermes-okf sessions --path ./knowledge
```

</RequestExample>

<ResponseExample>

```
sessions/2026-06-14T22-14-58Z
sessions/2026-06-15T10-30-00Z
```

Empty: prints nothing (exit `0`).
</ResponseExample>

---

### `plans`

List concept IDs under the `plans/` subdirectory via `bundle.list_concepts("plans")`.

<RequestExample>

```bash
hermes-okf plans --path ./knowledge
```

</RequestExample>

<ResponseExample>

```
plans/refactor_auth_2026-06-15
```

Empty:

```
No active plans.
```

</ResponseExample>

---

### `tools`

List registered tool concept IDs from `HermesAgent.list_tools()` (`tools/` subdirectory).

<RequestExample>

```bash
hermes-okf tools --path ./knowledge
```

</RequestExample>

<ResponseExample>

```
tools/search_web
tools/run_shell
```

Empty: prints nothing (exit `0`).
</ResponseExample>

---

### `install-plugin`

Install the Hermes plugin wrapper to `~/.hermes/plugins/hermes-okf/` and optionally update `~/.hermes/config.yaml`. Delegates to `hermes_okf.install_plugin.install_plugin`. Does not accept `--path`.

<RequestExample>

```bash
hermes-okf install-plugin
```

</RequestExample>

<ResponseExample>

```
Installed hermes-okf plugin to /home/user/.hermes/plugins/hermes-okf
  Updated ~/.hermes/config.yaml
  Run 'hermes memory setup' to finish activation
```

When config is unchanged:

```
Installed hermes-okf plugin to /home/user/.hermes/plugins/hermes-okf
  Run 'hermes memory setup' to activate
```

</ResponseExample>

Equivalent standalone entry point: `hermes-okf-install`. See [Install Hermes plugin](/install-hermes-plugin).

---

### `uninstall-plugin`

Remove `~/.hermes/plugins/hermes-okf/`. Does not delete the OKF bundle. Always exits `0`.

<RequestExample>

```bash
hermes-okf uninstall-plugin
```

</RequestExample>

<ResponseExample>

```
Removed hermes-okf plugin from /home/user/.hermes/plugins/hermes-okf
```

Or when not installed:

```
hermes-okf plugin not installed
```

</ResponseExample>

Equivalent standalone entry point: `hermes-okf-uninstall`.

---

## Argparse error shapes

Invalid subcommand (exit `2`, stderr):

```
hermes-okf: error: argument command: invalid choice: 'invalid-cmd' (choose from 'init', 'validate', ...)
```

Missing required positional (exit `2`, stderr):

```
usage: hermes-okf show [-h] [--path PATH] [--json] concept_id
hermes-okf show: error: the following arguments are required: concept_id
```

## Bundle auto-creation note

Most bundle-scoped commands construct `OKFBundle(args.path)`. If the path does not exist, `OKFBundle` creates the directory and initialises a minimal conformant structure. Prefer `hermes-okf init` for explicit setup; use `validate` to confirm an existing bundle. See [OKF bundle model](/okf-bundle-model).

## Related pages

<CardGroup>
<Card title="Standalone CLI workflows" href="/standalone-cli-workflows">
Task-oriented recipes for init, validate, search, log, graph, snapshot, and context without Hermes.
</Card>
<Card title="Hermes CLI reference" href="/hermes-cli-reference">
`hermes okf` subcommands (`search`, `list --type`, `show --raw`, `snapshot`, `restore`) registered via the Hermes plugin.
</Card>
<Card title="Installation" href="/installation">
PyPI install, optional extras, and entry-point scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`).
</Card>
<Card title="OKF validation reference" href="/okf-validation-reference">
`OKFValidator` rules and the error object shape behind `validate` output.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery when commands are not found, bundles are missing, or plugin install fails.
</Card>
</CardGroup>

---

## 13. Hermes CLI reference

> `hermes okf` subcommands registered via `cli_extension.py` and `plugin.py`: `search`, `list --type`, `show --raw`, `snapshot --note`, `restore`, default `top_k`, and dispatch through `HermesOKFProvider`.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/13-hermes-cli-reference.md
- Generated: 2026-06-15T19:27:27.450Z

### Source Files

- `src/hermes_okf/cli_extension.py`
- `src/hermes_okf/plugin.py`
- `docs/HERMES_PLUGIN.md`
- `README.md`
- `src/hermes_okf/hermes_integration.py`

---
title: "Hermes CLI reference"
description: "`hermes okf` subcommands registered via `cli_extension.py` and `plugin.py`: `search`, `list --type`, `show --raw`, `snapshot --note`, `restore`, default `top_k`, and dispatch through `HermesOKFProvider`."
---

The `hermes okf` command group exposes five memory-inspection subcommands on the Hermes Agent CLI. Registration flows through `plugin.py` into `cli_extension.py`, which builds the argparse tree; each handler instantiates `HermesOKFProvider` and delegates to its search, bundle-read, snapshot, and restore methods. Bundle path and agent identity resolve from Hermes configuration — there is no `--path` flag on these commands.

<Note>
These commands require `hermes-okf` installed and registered via `hermes-okf-install`. They operate on the configured OKF bundle (default `~/.hermes/okf_memory`), not an arbitrary path on the command line.
</Note>

## Prerequisites

<Steps>
<Step title="Install and register the plugin">

```bash
pip install hermes-okf
hermes-okf-install
```

`hermes-okf-install` creates `~/.hermes/plugins/hermes-okf/` and updates `~/.hermes/config.yaml` with `plugins.enabled` and `memory.provider`.

</Step>
<Step title="Verify commands are available">

```bash
hermes okf list
```

If argparse recognizes the `okf` group, registration succeeded. Run `hermes memory setup` to customize `bundle_path` or `agent_id`.

</Step>
</Steps>

## Registration and dispatch

Pip-installed packages cannot rely on Hermes' convention-based `plugins/memory/<name>/cli.py` discovery. Instead, `hermes-okf` registers through the general plugin entry point and delegates CLI construction to `cli_extension.py`.

```mermaid
sequenceDiagram
    participant Hermes as Hermes CLI
    participant PM as PluginManager
    participant Plugin as plugin.py
    participant CLI as cli_extension.py
    participant Prov as HermesOKFProvider

    Hermes->>PM: Load hermes_agent.plugins entry point
    PM->>Plugin: register(ctx)
    Plugin->>CLI: ctx.register_cli_command("okf", setup_fn=register_cli)
    Hermes->>CLI: User runs hermes okf &lt;subcommand&gt;
    CLI->>Prov: HermesOKFProvider()
    Prov-->>CLI: search / bundle / snapshot / restore
    CLI-->>Hermes: stdout
```

| Component | Role |
|-----------|------|
| `pyproject.toml` → `hermes_agent.plugins` | Entry point `hermes-okf = "hermes_okf.plugin"` (module-only; no `:register` suffix) |
| `plugin.py` | Calls `ctx.register_cli_command(name="okf", setup_fn=register_cli, handler_fn=None)` |
| `cli_extension.py` | Builds subparsers; each subcommand sets `func=_cli_*` as its dispatch handler |
| `HermesOKFProvider` | Loads config, wraps `HermesAgent`, exposes `search`, `snapshot`, `restore`, and bundle access |

`handler_fn=None` because subparsers set their own `func` defaults — the parent `okf` parser has no handler of its own. Omitting a subcommand triggers argparse's standard usage error.

## Configuration resolution

Every `_cli_*` handler calls `HermesOKFProvider()` with no arguments. The provider reads `HermesOKFConfig` in this order:

1. `HERMES_OKF_*` environment variables
2. `~/.hermes/hermes-okf.yaml`
3. `~/.hermes/config.yaml` → `plugins.hermes_okf` keys
4. Defaults (`bundle_path=~/.hermes/okf_memory`, `agent_id=hermes`)

<Info>
The `memory.bundle_path` key written by `hermes-okf-install` is used by Hermes' memory provider at runtime. CLI handlers resolve bundle path through `HermesOKFConfig.from_hermes_config()`, which also checks `plugins.hermes_okf` in `config.yaml`.
</Info>

## Command summary

| Subcommand | Positional args | Flags | Provider method |
|------------|-----------------|-------|-----------------|
| `search` | `query` | `--top-k` (default `5`) | `provider.search(query, top_k=...)` |
| `list` | — | `--type` | `provider.agent.memory.bundle.list_concepts()` + filter |
| `show` | `path` | `--raw` | `provider.agent.memory.bundle.read_concept(path)` |
| `snapshot` | — | `--note` (default `""`) | `provider.snapshot(note=...)` |
| `restore` | — | — | `provider.restore()` |

---

## `hermes okf search`

Full-text search over all concepts in the configured bundle. Uses `SearchIndex` with token-frequency scoring; the index rebuilds on each invocation because the handler calls `invalidate()` before searching.

<ParamField body="query" type="string" required>
Search string. Tokenized into lowercase alphanumeric terms.
</ParamField>

<ParamField body="--top-k" type="integer">
Maximum results to return. Default: `5`. The provider's Python API defaults to `10`, but the CLI overrides this at the argparse layer.
</ParamField>

<RequestExample>

```bash
hermes okf search "dark mode"
hermes okf search "deployment strategy" --top-k 10
```

</RequestExample>

<ResponseExample>

```text
  [0.50] hermes/decisions/model_strategy
  [0.33] hermes/observations/user_profile_2026-06-15
```

When no concepts match:

```text
No results found.
```

</ResponseExample>

---

## `hermes okf list`

Lists every concept in the bundle by scanning all `.md` files except reserved `index.md` and `log.md`. Output shows concept type and ID.

<ParamField body="--type" type="string">
Filter results to concepts whose frontmatter `type` field matches exactly (e.g. `Decision`, `Plan`, `Session`, `AgentConfig`, `Snapshot`).
</ParamField>

<RequestExample>

```bash
hermes okf list
hermes okf list --type Decision
hermes okf list --type Session
```

</RequestExample>

<ResponseExample>

```text
  [Decision] hermes/decisions/model_strategy
  [Plan] hermes/plans/research_ai_trends
  [AgentConfig] config/agent
```

When the bundle is empty or all concepts are filtered out:

```text
No concepts found.
```

</ResponseExample>

---

## `hermes okf show`

Displays a single concept by its bundle-relative path (without `.md` extension).

<ParamField body="path" type="string" required>
Concept path relative to bundle root. Examples: `config/agent`, `hermes/sessions/2026-06-14T22-14-58Z`, `hermes/decisions/model_strategy`.
</ParamField>

<ParamField body="--raw" type="boolean">
When set, prints only `concept.body` (markdown content). Omits type header, metadata block, and separator.
</ParamField>

<RequestExample>

```bash
hermes okf show config/agent
hermes okf show hermes/sessions/2026-06-14T22-14-58Z
hermes okf show hermes/decisions/model_strategy --raw
```

</RequestExample>

<ResponseExample>

Default output (metadata + body):

```text

[AgentConfig] config/agent
Metadata:
  type: AgentConfig
  title: hermes Configuration
  model: openai/gpt-4o
  system_prompt: You are a helpful, autonomous Hermes agent.
  timestamp: 2026-06-14T22:14:58Z
---
# Agent Configuration

System prompt and behaviour settings.
```

With `--raw`:

```text
# Agent Configuration

System prompt and behaviour settings.
```

When the path does not exist:

```text
Concept not found: config/agent
```

</ResponseExample>

---

## `hermes okf snapshot`

Saves a full agent state snapshot. The provider flushes the hot memory buffer first, then writes a `Snapshot` concept to `snapshots/<timestamp>.md` containing `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, and the note.

<ParamField body="--note" type="string">
Optional annotation stored in snapshot metadata. Default: empty string.
</ParamField>

<RequestExample>

```bash
hermes okf snapshot
hermes okf snapshot --note "Before deployment"
```

</RequestExample>

<ResponseExample>

```text
✓ Snapshot saved.
```

</ResponseExample>

<Warning>
`snapshot` flushes pending hot-buffer writes before saving. Observations, decisions, and tool calls buffered in `HotMemoryBuffer` are written to the cold OKF archive as part of this operation.
</Warning>

---

## `hermes okf restore`

Restores agent state from the most recent snapshot in the `snapshots/` subdirectory. Sorts snapshot concept IDs lexicographically and reads the last entry. Updates in-memory `model`, `current_session_id`, `current_plan_id`, and `system_prompt` on the underlying `HermesAgent`.

<RequestExample>

```bash
hermes okf restore
```

</RequestExample>

<ResponseExample>

On success:

```text
✓ Restored from: 2026-06-14T22:14:58Z
```

When no snapshots exist:

```text
No snapshots found.
```

</ResponseExample>

The timestamp printed comes from `state.get('timestamp', 'unknown')` on the restored snapshot metadata.

---

## Provider dispatch details

Each CLI handler follows the same pattern:

1. `from hermes_okf import HermesOKFProvider`
2. `provider = HermesOKFProvider()` — fresh instance per invocation
3. Delegate to the appropriate provider or bundle method
4. Print human-readable stdout (no JSON mode on Hermes CLI)

| Handler | Provider call chain |
|---------|---------------------|
| `_cli_search` | `provider.search(args.query, top_k=args.top_k)` → `SearchIndex.search()` |
| `_cli_list` | `provider.agent.memory.bundle.list_concepts(subdir=None)` → `read_concept()` per path |
| `_cli_show` | `provider.agent.memory.bundle.read_concept(args.path)` |
| `_cli_snapshot` | `provider.snapshot(note=args.note)` → `_flush_hot()` → `agent.snapshot()` |
| `_cli_restore` | `provider.restore()` → `_flush_hot()` → `agent.restore()` |

`HermesOKFProvider` also implements session hooks (`on_session_start`, `on_memory_write`, `on_tool_call`) used by `HermesOKFMemoryProvider` during live agent sessions. The CLI commands bypass those hooks and operate directly on the cold OKF archive.

## Hermes CLI vs standalone CLI

| Capability | `hermes okf` | `hermes-okf` (standalone) |
|------------|--------------|---------------------------|
| Bundle path | From Hermes config | `--path` flag on every command |
| Subcommands | `search`, `list`, `show`, `snapshot`, `restore` | Full set: `init`, `validate`, `log`, `graph-*`, `context`, `sessions`, `plans`, `tools`, etc. |
| JSON output | Not available | `--json` on some commands |
| Requires Hermes running | No (CLI only needs plugin registration) | No |

Use `hermes okf` when inspecting memory from within the Hermes ecosystem with config-driven bundle resolution. Use `hermes-okf` for bundle administration, graph inspection, and operations on arbitrary paths.

## Exit codes and errors

Handlers do not call `sys.exit()` explicitly. Successful commands exit `0`. Argparse errors (missing subcommand, missing positional argument) exit with argparse's default non-zero code. Uncaught exceptions (missing `pyyaml`, unreadable config, filesystem errors) propagate as Python tracebacks.

| Condition | Stdout message | Exit code |
|-----------|----------------|-----------|
| Search returns no hits | `No results found.` | `0` |
| List returns no concepts | `No concepts found.` | `0` |
| Show path missing | `Concept not found: <path>` | `0` |
| Restore with no snapshots | `No snapshots found.` | `0` |
| Missing subcommand | argparse usage error on stderr | non-zero |

<AccordionGroup>
<Accordion title="Plugin not visible in hermes CLI">

Confirm `~/.hermes/plugins/hermes-okf/` contains `plugin.yaml` and `__init__.py`. Verify `plugins.enabled` in `~/.hermes/config.yaml` is a YAML list containing `hermes-okf`, not a JSON-encoded string. Restart Hermes after install.

</Accordion>
<Accordion title="Commands target wrong bundle">

Check resolution order: `HERMES_OKF_BUNDLE_PATH` env var → `~/.hermes/hermes-okf.yaml` → `plugins.hermes_okf` in `config.yaml`. The install script sets `memory.bundle_path` but CLI handlers read through `HermesOKFConfig.from_hermes_config()`.

</Accordion>
<Accordion title="show prints empty or wrong content">

Concept body is stored in `concept.body`, not `concept.content`. Use `--raw` to verify markdown content without metadata formatting.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Register `hermes-okf` via `hermes-okf-install`, configure `~/.hermes/config.yaml`, and run `hermes memory setup`.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
Session hooks, memory callbacks, `prefetch`/`sync_turn`, and exposed tool schemas on `HermesOKFMemoryProvider`.
</Card>
<Card title="Standalone CLI reference" href="/standalone-cli-reference">
Full `hermes-okf` command set with `--path`, `--json`, graph inspection, and validation.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, `HERMES_OKF_*` environment variables, and config file resolution order.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery for missing install scripts, plugin discovery failures, and stale model config.
</Card>
</CardGroup>

---

## 14. Python SDK reference

> Exported public API from `hermes_okf`: `OKFBundle` CRUD/log/graph methods, `Concept` fields, `SearchIndex` query methods, `GraphExtractor` traversal, `HermesMemory` recall APIs, `HermesAgent` plan/snapshot/tool methods, and `get_provider()`.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/14-python-sdk-reference.md
- Generated: 2026-06-15T19:27:38.735Z

### Source Files

- `src/hermes_okf/__init__.py`
- `src/hermes_okf/bundle.py`
- `src/hermes_okf/search.py`
- `src/hermes_okf/graph.py`
- `src/hermes_okf/memory.py`
- `src/hermes_okf/hermes.py`
- `src/hermes_okf/hermes_integration.py`

---
title: "Python SDK reference"
description: "Exported public API from `hermes_okf`: `OKFBundle` CRUD/log/graph methods, `Concept` fields, `SearchIndex` query methods, `GraphExtractor` traversal, `HermesMemory` recall APIs, `HermesAgent` plan/snapshot/tool methods, and `get_provider()`."
---

The `hermes_okf` package (v0.4.6) exposes a filesystem-backed OKF bundle API, in-memory search and graph utilities, agent memory helpers, and a Hermes-native provider entry point. Import the public surface from the package root:

```python
from hermes_okf import (
    OKFBundle,
    Concept,
    SearchIndex,
    GraphExtractor,
    HermesMemory,
    HermesAgent,
    HermesOKFProvider,
    get_provider,
    OKFValidator,
)
```

```mermaid
classDiagram
    class OKFBundle {
        +Path root
        +read_concept(id)
        +write_concept(id, body, **frontmatter)
        +delete_concept(id)
        +list_concepts(subdir)
        +append_log(entry, category)
        +get_graph_edges()
    }
    class Concept {
        +str id
        +str type
        +str title
        +list tags
        +str body
        +dict metadata
    }
    class SearchIndex {
        +search(query, top_k)
        +search_concepts(query, top_k)
        +fuzzy_search(query, threshold)
    }
    class GraphExtractor {
        +traverse(start_id, max_depth)
        +get_backlinks(id)
        +to_networkx()
    }
    class HermesMemory {
        +recall(query, top_k)
        +record_decision(decision)
        +register_project(id, title)
    }
    class HermesAgent {
        +create_plan(task, steps)
        +snapshot(note)
        +build_context(query)
    }
    class HermesOKFProvider {
        +on_session_start(id)
        +on_memory_write(target, content)
        +search(query, top_k)
    }
    OKFBundle --> Concept : parses/writes
    SearchIndex --> OKFBundle
    GraphExtractor --> OKFBundle
    HermesMemory --> OKFBundle
    HermesMemory --> SearchIndex
    HermesAgent --> HermesMemory : .memory
    HermesOKFProvider --> HermesAgent : .agent
```

<Info>
`HermesMemoryMixin` and decorator helpers (`wrap_decision`, `wrap_tool`, `wrap_observation`, `with_context`) live in `hermes_okf.agent` and are used by `HermesAgent`. They are not re-exported from `__all__` but are the integration surface for custom agent classes.
</Info>

## Package exports

| Symbol | Module | Role |
|--------|--------|------|
| `OKFBundle` | `bundle` | Filesystem CRUD, log, tag search, link edges |
| `Concept` | `concept` | Dataclass for one `.md` concept file |
| `SearchIndex` | `search` | Inverted-index full-text search |
| `GraphExtractor` | `graph` | Link graph traversal, tag clusters, NetworkX export |
| `HermesMemory` | `memory` | Agent-oriented recall, decisions, projects |
| `HermesAgent` | `hermes` | Session, tool, plan, snapshot lifecycle |
| `HermesOKFProvider` | `hermes_integration` | Hermes session/memory/tool hooks |
| `get_provider()` | `hermes_integration` | Singleton `HermesOKFProvider` |
| `OKFValidator` | `validators` | Bundle and file conformance checks |

## `Concept`

Each concept maps to one `.md` file with YAML frontmatter and a markdown body. `OKFBundle._parse_concept` populates the dataclass; unknown frontmatter keys are preserved in `metadata`.

<ResponseField name="id" type="string">
Relative path without `.md` (e.g. `projects/my_project`).
</ResponseField>

<ResponseField name="type" type="string">
OKF `type` frontmatter field. Defaults to `"Unknown"` when absent.
</ResponseField>

<ResponseField name="title" type="string">
Human-readable title. Defaults to `id` when absent.
</ResponseField>

<ResponseField name="description" type="string">
Short summary from frontmatter. Defaults to `""`.
</ResponseField>

<ResponseField name="tags" type="list[string]">
Tag list for filtering and clustering. Defaults to `[]`.
</ResponseField>

<ResponseField name="resource" type="string | None">
Optional external URL reference.
</ResponseField>

<ResponseField name="timestamp" type="string | None">
ISO 8601 UTC timestamp (`YYYY-MM-DDTHH:MM:SSZ`). Auto-added by `write_concept` when omitted.
</ResponseField>

<ResponseField name="body" type="string">
Markdown body after frontmatter.
</ResponseField>

<ResponseField name="metadata" type="dict">
Full parsed frontmatter, including custom keys (e.g. `model`, `status`, `steps` on agent concepts).
</ResponseField>

## `OKFBundle`

<ParamField body="root_path" type="str | Path" required>
Bundle root directory. Created if missing; seeds `index.md`, `log.md`, and stub `projects/`, `decisions/`, `context/` indexes when `index.md` is absent.
</ParamField>

### Concept CRUD

| Method | Returns | Behavior |
|--------|---------|----------|
| `read_concept(concept_id)` | `Concept \| None` | Reads by relative ID; `None` if file missing |
| `write_concept(concept_id, body, **frontmatter)` | `Concept` | Creates parent dirs; auto-adds `timestamp` if not in frontmatter |
| `delete_concept(concept_id)` | `bool` | `True` if file existed and was removed |
| `list_concepts(subdir=None)` | `list[str]` | All concept IDs; skips `index.md` and `log.md` |
| `to_dict(concept_id)` | `dict \| None` | `dataclasses.asdict(Concept)` for JSON serialization |

`concept_id` uses forward slashes; internally normalized to OS path separators. `type` is not enforced at write time — use `OKFValidator` for conformance.

<RequestExample>

```python title="basic_usage.py pattern"
from hermes_okf import OKFBundle

bundle = OKFBundle("./my_knowledge")
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "data", "gpu"],
    resource="https://github.com/YOUR_USERNAME/my-project",
)
concept = bundle.read_concept("projects/my_project")
```

</RequestExample>

### Log

| Method | Returns | Behavior |
|--------|---------|----------|
| `append_log(entry, category="Update")` | `None` | Appends `* **{category}**: {entry}` under today's `## YYYY-MM-DD` heading |
| `read_log()` | `str` | Full `log.md` contents, or `""` if missing |

### Graph and tags

| Method | Returns | Behavior |
|--------|---------|----------|
| `search_by_tag(tag)` | `list[Concept]` | Linear scan of all concepts |
| `get_graph_edges()` | `list[dict]` | Directed edges from markdown links: `source`, `target`, `context` |
| `get_neighbors(concept_id)` | `list[dict]` | Outgoing edges where `source == concept_id` |

Non-HTTP link targets ending in `.md` have the suffix stripped. External URLs are excluded.

## `SearchIndex`

Wraps an `OKFBundle` with a lazy-built inverted index (`token → [concept_id]`). Tokenization is lowercase alphanumeric (`re.findall(r"[a-z0-9]+", text.lower())`).

<ParamField body="bundle" type="OKFBundle" required>
Bundle to index.
</ParamField>

| Method | Returns | Defaults | Behavior |
|--------|---------|----------|----------|
| `search(query, top_k=10)` | `list[tuple[str, float]]` | `top_k=10` | TF-like score: matching token count / query token count |
| `search_concepts(query, top_k=10)` | `list[Concept]` | `top_k=10` | Hydrates `search()` hits |
| `filter(predicate)` | `list[Concept]` | — | All concepts where `predicate(concept)` is true |
| `fuzzy_search(query, threshold=0.6)` | `list[tuple[str, float]]` | `threshold=0.6` | Uses `rapidfuzz.fuzz.partial_ratio` when installed; otherwise token-overlap fallback |
| `invalidate()` | `None` | — | Clears cache; rebuilds on next query |

<Warning>
Call `invalidate()` after bundle writes when using a long-lived `SearchIndex`. `HermesMemory.recall` and `HermesOKFProvider.search` invalidate automatically.
</Warning>

## `GraphExtractor`

Higher-level graph navigation over `OKFBundle.get_graph_edges()` plus directory and tag structure.

| Method | Returns | Behavior |
|--------|---------|----------|
| `get_edges()` | `list[dict]` | Delegates to `bundle.get_graph_edges()` |
| `get_neighbors(concept_id)` | `list[str]` | Outgoing target IDs |
| `get_backlinks(concept_id)` | `list[str]` | Incoming source IDs |
| `get_children(concept_id)` | `list[str]` | Sibling concepts in the same directory (excludes `index.md` and self) |
| `get_tag_clusters()` | `dict[str, list[str]]` | `tag → [concept_id]` |
| `traverse(start_id, max_depth=3)` | `dict` | BFS link traversal; nested tree with `id`, `title`, `type`, `depth`, optional `children` |
| `to_networkx()` | `networkx.DiGraph` | Nodes carry `concept.metadata`; edges carry `context`. Raises `ImportError` if `networkx` is not installed |

## `HermesMemory`

Agent-facing memory layer over `OKFBundle` with a bundled `SearchIndex`.

<ParamField body="bundle_path" type="str" required>
OKF bundle root path.
</ParamField>

<ParamField body="agent_id" type="str">
Identifier used in log entries. Default: `"hermes"`.
</ParamField>

### Session and logging

| Method | Returns | Behavior |
|--------|---------|----------|
| `start_session(session_id=None)` | `str` | Logs session start; generates timestamp ID if `session_id` is `None` |
| `end_session(session_id)` | `None` | Logs session end |
| `record_decision(decision, rationale=None, tags=None)` | `Concept` | Writes under `decisions/{slug}_{date}` with `type="Decision"` |
| `record_observation(observation, category="Observation", tags=None)` | `None` | Appends to `log.md` |
| `record_tool_call(tool_name, result_summary)` | `None` | Appends with category `Tool-Call` |

### Recall

| Method | Returns | Defaults | Behavior |
|--------|---------|----------|----------|
| `recall(query, top_k=5)` | `list[Concept]` | `top_k=5` | Invalidates index, then `search_concepts` |
| `recall_by_tag(tag)` | `list[Concept]` | — | Delegates to `bundle.search_by_tag` |
| `recall_project(project_name)` | `Concept \| None` | — | Exact title match or ID suffix match under `projects/` |
| `get_recent_log(n_lines=50)` | `str` | `n_lines=50` | Last *n* lines of `log.md` |
| `get_decisions()` | `list[Concept]` | — | Concepts tagged `decision` |

### Projects

| Method | Returns | Behavior |
|--------|---------|----------|
| `register_project(project_id, title, description="", tags=None, resource=None)` | `Concept` | Writes `projects/{project_id}` with `type="Project"` |
| `update_project(project_id, body, **metadata)` | `Concept` | Merges existing metadata, overwrites body |

## `HermesAgent`

Extends `HermesMemoryMixin`; stores full agent state in the OKF bundle. On construction: ensures `config/`, `tools/`, `sessions/`, `plans/`, `plans/archive/` directories, loads `config/agent`, and calls `start_session()`.

<ParamField body="bundle_path" type="str" required>
Agent OKF bundle root.
</ParamField>

<ParamField body="agent_id" type="str" required>
Unique agent identifier.
</ParamField>

<ParamField body="model" type="str">
Default LLM model. Default: `"gpt-4o"`. Overridden by `config/agent` metadata when present.
</ParamField>

### Session lifecycle

| Method | Returns | Behavior |
|--------|---------|----------|
| `start_session(session_id=None)` | `str` | Creates `sessions/{id}` concept with `status="active"` |
| `end_session()` | `None` | Sets current session `status="completed"`, clears `current_session_id` |
| `list_sessions()` | `list[str]` | Sorted session concept IDs |
| `recall_session(session_id=None)` | `Concept \| None` | Specific session or most recent |

### Tool registry

| Method | Returns | Behavior |
|--------|---------|----------|
| `register_tool(name, description, schema=None, example="")` | `None` | Writes `tools/{name}` with `type="Tool"`; `schema` stored as JSON string |
| `list_tools()` | `list[str]` | Concept IDs under `tools/` |
| `get_tool(name)` | `Concept \| None` | Reads `tools/{name}` |

### Plans

| Method | Returns | Behavior |
|--------|---------|----------|
| `create_plan(task, steps)` | `str` | Plan ID under `plans/`; sets `current_plan_id`; logs observation |
| `complete_step(step_index, result="")` | `None` | Checks off step in body; updates `progress` and `status` |
| `archive_plan(plan_id=None)` | `None` | Copies to `plans/archive/`, deletes original |

`complete_step` is a no-op when `current_plan_id` is unset or `step_index` is out of range.

### Snapshots and context

| Method | Returns | Behavior |
|--------|---------|----------|
| `snapshot(note="")` | `None` | Writes `snapshots/{timestamp}` with JSON state: `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, `note` |
| `restore(snapshot_id=None)` | `dict[str, Any]` | Restores from given ID or latest snapshot; returns metadata dict (empty if none) |
| `build_context(query, top_k=5)` | `str` | Markdown context: system prompt, active plan, recalled memory, recent log (20 lines), tool list |

Access underlying memory via `agent.memory` (`HermesMemory`).

## `HermesOKFProvider` and `get_provider()`

Hermes-native memory provider that auto-loads `HermesOKFConfig` from environment (`HERMES_OKF_*`), `~/.hermes/hermes-okf.yaml`, and `~/.hermes/config.yaml` → `plugins.hermes_okf`.

```python
from hermes_okf import get_provider

provider = get_provider()  # singleton; first call constructs HermesOKFProvider()
provider.on_session_start("session-123")
provider.on_memory_write("memory", "User prefers Python")
provider.search("Python preferences", top_k=5)
```

### Session and memory hooks

| Method | Behavior |
|--------|----------|
| `on_session_start(session_id)` | Starts agent session; optional auto-snapshot |
| `on_session_end(session_id)` | Flushes hot buffer, ends session; optional auto-snapshot |
| `on_memory_write(target, content)` | `target="memory"` → hot observation; `target="user"` → `UserProfile` concept |
| `on_tool_call(tool_name, args, result)` | Hot-buffered tool call; lazy tool registration |
| `on_decision(decision, rationale="", tags=None)` | Hot-buffered decision when `log_decisions` is true |

### Plan hooks

| Method | Returns | Behavior |
|--------|---------|----------|
| `on_plan_create(plan_name, steps)` | `str` | Delegates to `agent.create_plan` |
| `on_plan_step_complete(plan_id, step_index, result="")` | `None` | Delegates to `agent.complete_step` |
| `on_plan_complete(plan_id)` | `None` | Archives plan, flushes hot buffer, optional snapshot |

### Search, state, and RAG

| Method | Returns | Behavior |
|--------|---------|----------|
| `search(query, top_k=10)` | `list[tuple[str, float]]` | Full-text search over bundle |
| `recall_by_tag(tag)` | `list` | Tag recall via `agent.memory` |
| `build_context(query, top_k=5)` | `str` | Delegates to `agent.build_context` |
| `snapshot(note="")` | `None` | Flushes hot buffer, then `agent.snapshot` |
| `restore()` | `dict` | Flushes hot buffer, restores latest snapshot |
| `resume()` | `None` | `restore()` plus resume observation |
| `rag_search(query, top_k=5)` | `list[dict]` | Chroma semantic search; requires `hermes-okf[rag]` |

Unrecognized attribute access on `HermesOKFProvider` delegates to the wrapped `HermesAgent` via `__getattr__`.

### Hot memory buffer

When `use_hot_memory` is true (default), `HotMemoryBuffer` batches writes (`observation`, `decision`, `tool_call`) and flushes to the cold OKF archive on session end, buffer max (`hot_memory_max`, default 50), explicit `snapshot`/`restore`, or plan completion.

## `OKFValidator`

| Method | Returns | Behavior |
|--------|---------|----------|
| `validate()` | `list[OKFValidationError]` | Checks reserved files (`index.md`, `log.md`) and all concepts for frontmatter + required `type` |
| `is_valid()` | `bool` | `len(validate()) == 0` |
| `validate_file(path)` (static) | `list[OKFValidationError]` | Single-file check without bundle context |
| `quick_check(path)` (static) | `bool` | `validate_file` with no errors |

`OKFValidationError` exposes `file`, `message`, and optional `line`.

## `HermesMemoryMixin` (integration helper)

Import from `hermes_okf.agent` for custom agent classes:

| Method | Behavior |
|--------|----------|
| `wrap_decision(fn)` | Decorates method to persist return value as `Decision` |
| `wrap_observation(fn)` | Logs each call as observation |
| `wrap_tool(fn)` | Logs each call as tool call |
| `with_context(query, top_k=3)` | Returns `memory.recall(query, top_k)` |

Apply wrappers in `__init__` after `super().__init__(bundle_path, agent_id=...)`:

```python
from hermes_okf.agent import HermesMemoryMixin

class MyAgent(HermesMemoryMixin):
    def __init__(self):
        super().__init__("./knowledge", agent_id="my-agent")
        self.choose_model = self.wrap_decision(self.choose_model)
```

## Error and constraint summary

| Surface | Condition | Outcome |
|---------|-----------|---------|
| `read_concept` | Missing file | `None` |
| `delete_concept` | Missing file | `False` |
| `write_concept` | Missing `type` in frontmatter | Writes succeed; `OKFValidator` reports error |
| `SearchIndex.search` | Empty query tokens | `[]` |
| `GraphExtractor.to_networkx` | `networkx` not installed | `ImportError` with install hint |
| `SearchIndex.fuzzy_search` | `rapidfuzz` not installed | Token-overlap fallback |
| `HermesAgent.complete_step` | No active plan or bad index | Silent no-op |
| `HermesAgent.restore` | No snapshots | `{}` |
| `HermesOKFProvider.rag_search` | Missing `[rag]` extras | `ImportError` |
| `get_provider` | First call | Constructs singleton from resolved config |

<Note>
Core SDK depends only on `pyyaml`. Optional capabilities: `rapidfuzz` (fuzzy search), `networkx` (graph export), `hermes-okf[rag]` (vector retrieval via LangChain + Chroma).
</Note>

## Related pages

<CardGroup>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, frontmatter rules, and Hermes-native directories that `OKFBundle` and `HermesAgent` create.
</Card>
<Card title="Python agent integration" href="/python-agent-integration">
`HermesMemoryMixin`, decorator wrappers, and wiring memory into custom agent classes.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
`HermesOKFProvider` session hooks, hot/cold memory, and Hermes `MemoryProvider` plugin surface.
</Card>
<Card title="Example: OKF bundle basics" href="/example-okf-bundle-basics">
Copy-paste `OKFBundle` workflow from `examples/basic_usage.py`.
</Card>
<Card title="Example: full agent" href="/example-full-agent">
End-to-end `HermesAgent` session, plan, and snapshot lifecycle.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields and `HERMES_OKF_*` environment variables used by `get_provider()`.
</Card>
</CardGroup>

---

## 15. Configuration reference

> `HermesOKFConfig` fields and defaults, resolution order (env → `~/.hermes/hermes-okf.yaml` → `~/.hermes/config.yaml`), `HERMES_OKF_*` environment variables, `memory.*` keys written by install, and hot-memory/RAG toggles.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/15-configuration-reference.md
- Generated: 2026-06-15T19:28:26.926Z

### Source Files

- `src/hermes_okf/hermes_integration.py`
- `src/hermes_okf/install_plugin.py`
- `src/hermes_okf/memory_plugin.py`
- `docs/HERMES_USERS.md`
- `docs/HERMES_PLUGIN.md`

---
title: "Configuration reference"
description: "`HermesOKFConfig` fields and defaults, resolution order (env → `~/.hermes/hermes-okf.yaml` → `~/.hermes/config.yaml`), `HERMES_OKF_*` environment variables, `memory.*` keys written by install, and hot-memory/RAG toggles."
---

`HermesOKFConfig` is the central configuration dataclass for the Hermes-OKF memory provider. Values load from environment variables, dedicated YAML files, and Hermes' main `config.yaml`, with different entry points depending on whether you construct `HermesOKFProvider` directly or run through the Hermes plugin adapter.

## Configuration surfaces

hermes-okf reads configuration from three locations under `~/.hermes/`:

| Surface | Path | Used by |
|---------|------|---------|
| Dedicated OKF config | `~/.hermes/hermes-okf.yaml` | `HermesOKFConfig.from_hermes_config()` |
| Hermes main config | `~/.hermes/config.yaml` | Plugin install, `hermes memory setup`, and runtime |
| Environment variables | `HERMES_OKF_*` | `from_hermes_config()` (partial override set) |

<Note>
Two code paths load config differently. `HermesOKFProvider()` calls `HermesOKFConfig.from_hermes_config()`. The Hermes plugin adapter (`HermesOKFMemoryProvider.initialize()`) reads `memory.*` keys from `config.yaml` directly and does not call `from_hermes_config()`.
</Note>

```mermaid
flowchart TD
    subgraph env ["Environment"]
        E1["HERMES_OKF_BUNDLE_PATH"]
        E2["HERMES_OKF_AGENT_ID"]
        E3["HERMES_OKF_AUTO_SNAPSHOT"]
        E4["HERMES_OKF_ENABLE_RAG"]
    end

    subgraph files ["Config files"]
        F1["~/.hermes/hermes-okf.yaml\n(flat dataclass keys)"]
        F2["~/.hermes/config.yaml\nplugins.hermes_okf"]
        F3["~/.hermes/config.yaml\nmemory.*"]
    end

    subgraph loaders ["Loaders"]
        L1["from_hermes_config()"]
        L2["memory_plugin.initialize()"]
    end

    subgraph output ["Runtime"]
        C["HermesOKFConfig"]
        P["HermesOKFProvider"]
    end

    E1 & E2 & E3 & E4 --> L1
    F1 --> L1
    F2 -->|"only if bundle_path unset"| L1
    L1 --> C --> P
    F3 --> L2 --> C
```

## Resolution order (`from_hermes_config`)

`HermesOKFConfig.from_hermes_config()` merges sources in this order; later steps only fill keys not already set (with one exception noted below):

1. **Environment variables** — `HERMES_OKF_*` (see below)
2. **`~/.hermes/hermes-okf.yaml`** — flat keys matching dataclass field names
3. **`~/.hermes/config.yaml` → `plugins.hermes_okf`** — only when `bundle_path` is not already set from steps 1–2
4. **Dataclass defaults**

<Warning>
The `plugins.hermes_okf` block is skipped entirely once `bundle_path` is set by an environment variable or `hermes-okf.yaml`. Other fields from `plugins.hermes_okf` will not merge in that case.
</Warning>

```python
from hermes_okf.hermes_integration import HermesOKFConfig, HermesOKFProvider

# Auto-load from env → hermes-okf.yaml → plugins.hermes_okf → defaults
provider = HermesOKFProvider()

# Or pass an explicit config object
config = HermesOKFConfig(bundle_path="/data/okf", agent_id="my-agent")
provider = HermesOKFProvider(config)
```

## `HermesOKFConfig` fields

All fields are defined on the dataclass in `hermes_integration.py`. Any key present in `hermes-okf.yaml` or `plugins.hermes_okf` that matches a field name is accepted.

| Field | Default | Description |
|-------|---------|-------------|
| `bundle_path` | `~/.hermes/okf_memory` | Filesystem path to the OKF bundle |
| `agent_id` | `hermes` | Agent identifier written into concepts and sessions |
| `model` | `""` (empty) | LLM model string; falls back to `openai/gpt-4o` in `HermesOKFProvider` when empty |
| `auto_snapshot` | `true` | Save snapshots on session start and end |
| `snapshot_on_tool_call` | `false` | Save a snapshot after each tool invocation |
| `log_tool_calls` | `true` | Buffer and flush tool calls to the OKF archive |
| `log_decisions` | `true` | Buffer and flush strategic decisions to the OKF archive |
| `use_hot_memory` | `true` | Declared toggle for the in-process hot buffer (see hot memory section) |
| `hot_memory_max` | `50` | Maximum hot-buffer items before auto-flush |
| `enable_rag` | `false` | RAG feature flag (see RAG section) |
| `rag_model` | `openai/text-embedding-3-small` | Embedding model identifier for Chroma indexing |

<ParamField body="bundle_path" type="string" required={false}>
Directory where the OKF bundle is stored. Created automatically on first provider init if missing.
</ParamField>

<ParamField body="agent_id" type="string" required={false}>
Identifier stamped into session concepts, snapshots, and agent config. The Hermes plugin path defaults to `hermes-agent` when `memory.agent_id` is absent.
</ParamField>

<ParamField body="model" type="string" required={false}>
Model string passed to `HermesAgent`. The plugin adapter reads Hermes' top-level `model` or `llm.model` from `config.yaml` and writes it to the `config/agent` concept.
</ParamField>

<ParamField body="auto_snapshot" type="boolean" required={false}>
When `true`, calls `agent.snapshot()` at session start and end, and after plan completion.
</ParamField>

<ParamField body="snapshot_on_tool_call" type="boolean" required={false}>
When `true`, saves a snapshot on every `on_tool_call` hook.
</ParamField>

<ParamField body="log_tool_calls" type="boolean" required={false}>
When `true`, pushes tool invocations into the hot buffer and lazy-registers tools in the OKF registry.
</ParamField>

<ParamField body="log_decisions" type="boolean" required={false}>
When `true`, pushes decision events into the hot buffer for cold-archive flush.
</ParamField>

<ParamField body="use_hot_memory" type="boolean" required={false}>
Configuration field for the two-memory model. The provider always instantiates `HotMemoryBuffer` regardless of this value.
</ParamField>

<ParamField body="hot_memory_max" type="integer" required={false}>
Maximum items in `HotMemoryBuffer` before `_maybe_flush()` writes all buffered items to the cold OKF archive.
</ParamField>

<ParamField body="enable_rag" type="boolean" required={false}>
Feature flag stored in config. RAG retrieval is invoked explicitly via `HermesOKFProvider.rag_search()` rather than auto-routed from this flag.
</ParamField>

<ParamField body="rag_model" type="string" required={false}>
Embedding model passed to `OpenAIEmbeddings` when building or querying the Chroma index. Swap providers by changing this string and your embedding client credentials.
</ParamField>

## `~/.hermes/hermes-okf.yaml`

Place flat dataclass keys at the file root (not nested under `memory:`):

```yaml
bundle_path: ~/.hermes/okf_memory
agent_id: hermes-alpha
auto_snapshot: true
log_tool_calls: true
log_decisions: true
hot_memory_max: 100
enable_rag: false
rag_model: openai/text-embedding-3-small
```

Keys must match `HermesOKFConfig` field names exactly. Unknown keys are ignored. Parse errors fall back silently to defaults.

## `~/.hermes/config.yaml` — `memory.*` keys

When Hermes runs the plugin, `HermesOKFMemoryProvider.initialize()` reads these keys from the `memory` section:

| Key | Default (plugin path) | Description |
|-----|----------------------|-------------|
| `provider` | set by install to `hermes-okf` | Selects the memory backend |
| `bundle_path` | `~/.hermes/okf_memory` | OKF bundle directory |
| `agent_id` | `hermes-agent` | Agent identifier |
| `auto_snapshot` | `true` | Session snapshot toggle |
| `log_tool_calls` | `true` | Tool-call logging toggle |
| `hot_memory_max` | `50` | Hot-buffer capacity |

`hermes memory setup` exposes `bundle_path`, `agent_id`, and `auto_snapshot` via `get_config_schema()`. `save_config()` writes those values back under `memory.*`.

### Keys written by `hermes-okf-install`

Running `hermes-okf-install` (or `python -m hermes_okf.install_plugin`) modifies `~/.hermes/config.yaml` when the file already exists:

```yaml
plugins:
  enabled:
    - hermes-okf          # appended if missing

memory:
  provider: hermes-okf    # set if not already
  bundle_path: ~/.hermes/okf_memory  # set if key absent
```

<Info>
Install does not write `agent_id`, `auto_snapshot`, `log_tool_calls`, or `hot_memory_max`. Configure those via `hermes memory setup` or manual YAML edits.
</Info>

### `plugins.hermes_okf` block

`from_hermes_config()` can also read a `plugins.hermes_okf` map with the same flat field names as the dataclass. This block is consulted only when `bundle_path` has not been resolved from environment variables or `hermes-okf.yaml`.

## Environment variables

Only four `HERMES_OKF_*` variables are parsed by `from_hermes_config()`:

| Variable | Maps to | Accepted truthy values |
|----------|---------|------------------------|
| `HERMES_OKF_BUNDLE_PATH` | `bundle_path` | any non-empty string |
| `HERMES_OKF_AGENT_ID` | `agent_id` | any non-empty string |
| `HERMES_OKF_AUTO_SNAPSHOT` | `auto_snapshot` | `1`, `true`, `yes` (case-insensitive) |
| `HERMES_OKF_ENABLE_RAG` | `enable_rag` | `1`, `true`, `yes` (case-insensitive) |

<RequestExample>

```bash
export HERMES_OKF_BUNDLE_PATH=/data/my_okf
export HERMES_OKF_AGENT_ID=deploy-bot
export HERMES_OKF_AUTO_SNAPSHOT=false
export HERMES_OKF_ENABLE_RAG=true
```

</RequestExample>

<Warning>
`HERMES_OKF_HOT_MEMORY_MAX` is not read by `from_hermes_config()`. Set `hot_memory_max` in `memory.*` (plugin path) or `hermes-okf.yaml` instead.
</Warning>

Environment variables take highest precedence in `from_hermes_config()` and are not re-read by the plugin adapter's `initialize()` method.

## Hot memory configuration

The hot layer is `HotMemoryBuffer`, an in-process list that batches fast writes before flushing to the cold OKF archive.

**Flush triggers:**

- Session end (`on_session_end`)
- Plan completion (`on_plan_complete`)
- Buffer reaches `hot_memory_max` items (`_maybe_flush`)
- Explicit flush via `snapshot()`, `restore()`, or `_flush_hot()`

**Buffered item kinds:**

| `_kind` value | Cold-archive target |
|---------------|---------------------|
| `observation` | `record_observation()` |
| `decision` | `record_decision()` |
| `tool_call` | `record_tool_call()` |

Hermes `memory` writes (`target="memory"`) push observations into the hot buffer. `target="user"` writes bypass the buffer and go directly to a `UserProfile` concept.

<Tabs>
<Tab title="YAML (Hermes plugin)">

```yaml
memory:
  provider: hermes-okf
  hot_memory_max: 200
```

</Tab>
<Tab title="hermes-okf.yaml">

```yaml
hot_memory_max: 200
log_tool_calls: true
log_decisions: true
```

</Tab>
<Tab title="Python SDK">

```python
from hermes_okf.hermes_integration import HermesOKFConfig, HermesOKFProvider

config = HermesOKFConfig(
    bundle_path="/tmp/okf",
    hot_memory_max=200,
    log_tool_calls=True,
    log_decisions=True,
)
provider = HermesOKFProvider(config)
```

</Tab>
</Tabs>

## RAG configuration

RAG is optional and requires the `[rag]` extra (`pip install hermes-okf[rag]`). Vector storage persists under `{bundle_path}/.chroma`.

| Control | Purpose |
|---------|---------|
| `enable_rag` / `HERMES_OKF_ENABLE_RAG` | Stored feature flag; does not auto-enable retrieval in provider hooks |
| `rag_model` | Embedding model for `OpenAIEmbeddings` in `rag_search()` and `_build_rag_index()` |
| `HermesOKFProvider.rag_search()` | Explicit semantic search entry point |

```python
provider = HermesOKFProvider(
    HermesOKFConfig(
        bundle_path="~/.hermes/okf_memory",
        enable_rag=True,
        rag_model="openai/text-embedding-3-small",
    )
)
results = provider.rag_search("deployment decisions", top_k=5)
```

Embedding provider selection is configuration-driven: change `rag_model` and swap the LangChain embedding client to use any provider your credentials support. OKF markdown storage is unchanged.

## Model sync from Hermes

When the plugin initializes, it reads the Hermes model from `config.yaml`:

```yaml
model: anthropic/claude-sonnet-4   # top-level
# or
llm:
  model: anthropic/claude-sonnet-4
```

The resolved model is assigned to `config.model` and written to the `config/agent` OKF concept so the bundle reflects the live Hermes configuration.

## Quick reference: which file wins?

| Scenario | Primary source |
|----------|---------------|
| Standalone `HermesOKFProvider()` | env → `hermes-okf.yaml` → `plugins.hermes_okf` → defaults |
| Hermes Agent session (plugin) | `config.yaml` `memory.*` at `initialize()` |
| After `hermes-okf-install` | `memory.provider`, `memory.bundle_path`, `plugins.enabled` |
| After `hermes memory setup` | `memory.bundle_path`, `memory.agent_id`, `memory.auto_snapshot` |
| Override bundle path at runtime | `HERMES_OKF_BUNDLE_PATH` (highest in `from_hermes_config`) |

## Related pages

<CardGroup>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
How `hermes-okf-install` writes `plugins.enabled` and `memory.*` keys.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot buffer kinds, flush triggers, and Hermes-to-OKF mapping.
</Card>
<Card title="Enable RAG" href="/enable-rag">
Optional vector retrieval setup with Chroma and LangChain.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
`HermesOKFProvider` hooks, `prefetch`, and model sync behavior.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery for stale config, missing bundle paths, and `plugins.enabled` shape errors.
</Card>
</CardGroup>

---

## 16. OKF validation reference

> `OKFValidator` rules: reserved files (`index.md`, `log.md`), frontmatter presence and YAML shape, required `type` field, `validate_file`/`quick_check` helpers, and `hermes-okf validate` error output format.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/16-okf-validation-reference.md
- Generated: 2026-06-15T19:28:44.519Z

### Source Files

- `src/hermes_okf/validators.py`
- `src/hermes_okf/cli.py`
- `docs/ARCHITECTURE.md`
- `tests/test_validators.py`
- `src/hermes_okf/bundle.py`

---
title: OKF validation reference
description: OKFValidator rules for reserved files, YAML frontmatter shape, the required type field, validate_file and quick_check helpers, and hermes-okf validate CLI output.
---

Hermes OKF enforces a **minimal OKF v0.1 conformance profile**: every concept markdown file must have parseable YAML frontmatter containing a `type` field, and the bundle root must contain `index.md` and `log.md`. Validation is intentionally narrow — additional frontmatter keys, body content, and directory layout are not checked. Use `OKFValidator` programmatically or `hermes-okf validate` from the terminal.

`OKFBundle.write_concept()` does **not** enforce the `type` requirement at write time; conformance is a separate check you run after edits or in CI.

## Validation scope

`OKFValidator` performs two passes on a bundle:

| Pass | What is checked | What is skipped |
|------|-----------------|-----------------|
| Reserved files | `index.md` and `log.md` must exist at the **bundle root** | Frontmatter on any `index.md` or `log.md` anywhere in the tree |
| Concept files | Every other `*.md` file under the bundle root (recursive) | Files named `index.md` or `log.md` at any depth |

The validator does **not** inspect:

- Whether `type` values match a fixed taxonomy (types are user-defined)
- Optional frontmatter fields (`title`, `tags`, `timestamp`, and so on)
- Markdown body syntax or link targets
- Hermes-native subdirectories (`config/`, `sessions/`, `plans/`, and similar) beyond the generic markdown rules above

For bundle layout and concept field semantics, see the [OKF bundle model](/okf-bundle-model) page.

## Reserved files

<ParamField body="REQUIRED_RESERVED_FILES" type="tuple[str, str]">
  Class constant on `OKFValidator`: `("index.md", "log.md")`.
</ParamField>

**Existence rule.** Both files must be present directly under the bundle root. If either is missing, validation records:

```
Missing required reserved file: index.md
```

(or the equivalent message for `log.md`).

**Frontmatter exemption.** Any markdown file whose basename is `index.md` or `log.md` — including subdirectory stubs like `projects/index.md` — is excluded from frontmatter and `type` checks. This is why a freshly initialized bundle passes validation even though root `log.md` has no YAML frontmatter and root `index.md` carries `okf_version` but no `type`.

`OKFBundle._init_bundle()` creates conformant starter content: root `index.md` with `okf_version: "0.1"`, plain `log.md`, and seeded subdirectory `index.md` files with `type: Directory`.

## Concept frontmatter rules

For every non-reserved `*.md` file, `OKFValidator._check_file()` applies these rules in order. The first failure stops further checks on that file.

### 1. Opening delimiter

Content must start with `---`.

<ResponseField name="error" type="string">
  `Missing YAML frontmatter (must start with ---)`
</ResponseField>

### 2. Closing delimiter

The file is split on `---` with a maximum of two splits (`content.split("---", 2)`). The result must have at least three segments: empty prefix, YAML block, body.

<ResponseField name="error" type="string">
  `Malformed frontmatter: missing closing ---`
</ResponseField>

### 3. Valid YAML

The block between delimiters is parsed with `yaml.safe_load()`. Parse failures produce:

<ResponseField name="error" type="string">
  `Invalid YAML frontmatter: {exc}`
</ResponseField>

where `{exc}` is the PyYAML error string.

### 4. Mapping type

Frontmatter must deserialize to a YAML mapping (Python `dict`). A scalar or list top-level value fails with:

<ResponseField name="error" type="string">
  `Frontmatter must be a YAML mapping (dict)`
</ResponseField>

### 5. Required type field

The mapping must contain a `type` key. Absence fails with:

<ResponseField name="error" type="string">
  `Missing required 'type' field in frontmatter`
</ResponseField>

The error includes `line=1` in bundle-level validation (frontmatter starts at line 1).

No other frontmatter keys are required. A minimal valid concept:

```markdown
---
type: Project
---

# My project
```

## OKFValidationError

Each failure is an `OKFValidationError` with three attributes:

<ParamField body="file" type="string" required>
  Relative path from the bundle root during full validation (e.g. `projects/my_app.md`), or the path string passed to `validate_file()` (often absolute).
</ParamField>

<ParamField body="message" type="string" required>
  Human-readable failure description.
</ParamField>

<ParamField body="line" type="int | None">
  Optional line number. Set to `1` for missing `type` in bundle validation; omitted for most other errors.
</ParamField>

`repr()` formats errors for CLI output:

| Condition | Format |
|-----------|--------|
| `line` is set | `OKFValidationError({file}:{line} -> {message})` |
| `line` is `None` | `OKFValidationError({file} -> {message})` |

Access fields directly when building custom reporting:

```python
from hermes_okf.bundle import OKFBundle
from hermes_okf.validators import OKFValidator

bundle = OKFBundle("./knowledge")
validator = OKFValidator(bundle)
errors = validator.validate()

for err in errors:
    print(f"{err.file}: {err.message}")
```

## OKFValidator instance API

Construct with an `OKFBundle`:

```python
from hermes_okf.bundle import OKFBundle
from hermes_okf.validators import OKFValidator

validator = OKFValidator(OKFBundle("/path/to/bundle"))
```

<ResponseField name="validate()" type="list[OKFValidationError]">
  Runs `_check_reserved_files()` then `_check_all_concepts()`, stores results in `validator.errors`, and returns the list. Replaces any prior errors.
</ResponseField>

<ResponseField name="is_valid()" type="bool">
  Calls `validate()` and returns `True` when the error list is empty.
</ResponseField>

<ResponseField name="errors" type="list[OKFValidationError]">
  Populated after the most recent `validate()` call.
</ResponseField>

`OKFValidator` is exported from the public `hermes_okf` package alongside `OKFBundle`.

## Static helpers

Use these when validating a single file outside full bundle context — for example, a pre-commit hook on one edited concept.

### validate_file

```python
errors = OKFValidator.validate_file("projects/my_project.md")
```

<ParamField body="path" type="str | Path" required>
  Path to a single `.md` file.
</ParamField>

<ResponseField name="returns" type="list[OKFValidationError]">
  Zero or more errors. Does not check reserved-file presence or scan sibling files.
</ResponseField>

`validate_file()` applies the same frontmatter and `type` rules but uses **shorter error messages**:

| Full bundle check | `validate_file()` equivalent |
|-------------------|------------------------------|
| `Missing YAML frontmatter (must start with ---)` | `Missing YAML frontmatter` |
| `Malformed frontmatter: missing closing ---` | `Malformed frontmatter` |
| `Invalid YAML frontmatter: {exc}` | `Invalid YAML: {exc}` |
| `Frontmatter must be a YAML mapping (dict)` | *(combined)* `Missing required 'type' field` |
| `Missing required 'type' field in frontmatter` | `Missing required 'type' field` |

Additional `validate_file()`-only checks:

| Condition | Message |
|-----------|---------|
| Path does not exist | `File does not exist` |
| Frontmatter is valid YAML but not a dict, or dict lacks `type` | `Missing required 'type' field` |

### quick_check

```python
ok = OKFValidator.quick_check("projects/my_project.md")
```

Returns `True` when `validate_file()` returns an empty list. Useful for guard clauses:

```python
if not OKFValidator.quick_check(path):
    raise ValueError(f"Non-conformant OKF file: {path}")
```

## hermes-okf validate

The standalone CLI subcommand wraps `OKFValidator` for terminal and CI use.

```bash
hermes-okf validate
hermes-okf validate --path ./knowledge
hermes-okf validate --path ~/.hermes/okf_memory
```

<ParamField body="--path" type="string" default=".">
  Bundle root directory. Inherited from the shared path parent parser; may appear after the subcommand name.
</ParamField>

### Success output

<RequestExample>
```bash
hermes-okf validate --path ./knowledge
```
</RequestExample>

<ResponseExample>
```
Bundle is valid.
```
</ResponseExample>

Exit code: **0**.

### Error output

When one or more errors exist, the CLI prints a summary line followed by one bullet per error, using each error's `repr()`:

<RequestExample>
```bash
hermes-okf validate --path ./knowledge
```
</RequestExample>

<ResponseExample>
```
Found 2 validation error(s):
  - OKFValidationError(bad.md:1 -> Missing required 'type' field in frontmatter)
  - OKFValidationError(log.md -> Missing required reserved file: log.md)
```
</ResponseExample>

Exit code: **1**.

Multiple concept failures are all listed in a single run; the validator does not stop at the first error.

### Interaction with OKFBundle initialization

`hermes-okf validate` constructs `OKFBundle(args.path)` before validating. If the path does not exist, `OKFBundle` creates the directory and runs `_init_bundle()`, then validation typically succeeds on the freshly seeded bundle. Point `--path` at an existing bundle when checking real content. See [Quickstart](/quickstart) for the expected first-run workflow.

## Write-time vs validate-time enforcement

| Operation | Enforces `type`? | Notes |
|-----------|------------------|-------|
| `OKFBundle.write_concept()` | No | Docstring directs callers to `OKFValidator` |
| `OKFBundle.read_concept()` | No | Missing `type` parses as `type="Unknown"` |
| `OKFValidator.validate()` | Yes | Authoritative conformance check |
| `hermes-okf validate` | Yes | CLI wrapper around `OKFValidator` |

Run validation after manual edits, scripted imports, or agent writes that bypass `write_concept()`.

## Common failure modes

<AccordionGroup>
<Accordion title="Concept missing type after manual edit">
A file with frontmatter but no `type` key fails bundle validation:

```
OKFValidationError(my_concept.md:1 -> Missing required 'type' field in frontmatter)
```

Add `type` to the YAML block or rewrite via `write_concept(..., type="YourType")`.
</Accordion>

<Accordion title="No frontmatter at all">
Plain markdown without leading `---` fails with:

```
OKFValidationError(notes.md -> Missing YAML frontmatter (must start with ---))
```
</Accordion>

<Accordion title="Malformed YAML delimiter">
A single `---` or unclosed frontmatter block fails with:

```
OKFValidationError(broken.md -> Malformed frontmatter: missing closing ---)
```
</Accordion>

<Accordion title="Invalid YAML syntax">
PyYAML parse errors surface the parser message:

```
OKFValidationError(broken.md -> Invalid YAML frontmatter: while parsing a flow sequence ...
```
</Accordion>

<Accordion title="Missing root reserved file">
Deleting root `log.md` or `index.md` fails even when all concepts are valid:

```
OKFValidationError(log.md -> Missing required reserved file: log.md)
```

Re-run `hermes-okf init` on an empty directory or restore the file manually.
</Accordion>
</AccordionGroup>

## Extending validation

`OKFValidator` is hardcoded to the minimal OKF v0.1 profile. To add project-specific rules — mandatory `title`, allowed `type` values, link integrity — subclass `OKFValidator` and override `_check_file()` or add post-pass hooks. The architecture docs describe this as the supported extension point for custom conformance policies.

## Related pages

<Card href="/okf-bundle-model" title="OKF bundle model">
  Bundle layout, `Concept` fields, reserved directories, and how `write_concept()` formats frontmatter.
</Card>

<Card href="/standalone-cli-reference" title="Standalone CLI reference">
  Full `hermes-okf` subcommand list, global flags, and exit codes.
</Card>

<Card href="/standalone-cli-workflows" title="Standalone CLI workflows">
  When to run `validate` in day-to-day bundle operations and CI.
</Card>

<Card href="/quickstart" title="Quickstart">
  First successful `hermes-okf init` → write concept → `hermes-okf validate` flow.
</Card>

<Card href="/python-sdk-reference" title="Python SDK reference">
  Public exports including `OKFValidator` and `OKFBundle`.
</Card>

<Card href="/troubleshooting" title="Troubleshooting">
  Recovery when bundles are missing, paths are wrong, or plugin install issues mask validation targets.
</Card>

---

## 17. OKF bundle basics

> Copy-paste recipe from `examples/basic_usage.py`: create bundle, write a `Project` concept, read by ID, search by tag, append a `Decision` log entry, and print graph edges with expected field values.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/17-okf-bundle-basics.md
- Generated: 2026-06-15T19:28:29.783Z

### Source Files

- `examples/basic_usage.py`
- `src/hermes_okf/bundle.py`
- `src/hermes_okf/concept.py`
- `tests/test_bundle.py`
- `README.md`

---
title: "OKF bundle basics"
description: "Copy-paste recipe from `examples/basic_usage.py`: create bundle, write a `Project` concept, read by ID, search by tag, append a `Decision` log entry, and print graph edges with expected field values."
---

`OKFBundle` in `hermes_okf.bundle` is the filesystem-backed entry point for OKF memory: it auto-initializes a conformant directory tree, writes concepts as `.md` files with YAML frontmatter, appends dated log entries to `log.md`, and extracts directed graph edges from markdown links. The workflow below mirrors `examples/basic_usage.py` and requires only `pyyaml` (no Hermes runtime).

<Note>
This recipe uses the Python SDK directly. Equivalent operations exist on the standalone CLI (`hermes-okf init`, `log-append`, `graph-edges`). See [Standalone CLI workflows](/standalone-cli-workflows) for command-line equivalents.
</Note>

## Prerequisites

- Python 3.10+
- `pip install hermes-okf` (core install; no `[rag]` extra required)
- A writable directory path for the bundle root (e.g. `./my_knowledge`)

## Complete recipe

<Steps>
<Step title="Import and create the bundle">

Pass a path to `OKFBundle`. If the directory does not exist, it is created. If `index.md` is missing, `_init_bundle()` seeds the conformant layout.

```python
from hermes_okf.bundle import OKFBundle

bundle = OKFBundle("./my_knowledge")
```

On first init, the bundle contains:

:::files
my_knowledge/
├── index.md          # okf_version + directory links
├── log.md            # chronological agent log
├── projects/
│   └── index.md
├── decisions/
│   └── index.md
└── context/
    └── index.md
:::

Re-opening an existing bundle path does not overwrite files.

</Step>

<Step title="Write a Project concept">

`write_concept(concept_id, body, **frontmatter)` writes `concept_id.md` under the bundle root. The `concept_id` is a relative path without `.md` (use forward slashes). A UTC `timestamp` is auto-added when omitted.

```python
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "data", "gpu"],
    resource="https://github.com/YOUR_USERNAME/my-project",
)
```

On disk, `projects/my_project.md` looks like:

```yaml
---
type: Project
title: My Project
tags:
- ml
- data
- gpu
resource: https://github.com/YOUR_USERNAME/my-project
timestamp: '2026-06-15T19:28:01Z'
---

# My Project

Describe your project here.
```

`write_concept` returns a parsed `Concept` instance with the same field values.

</Step>

<Step title="Read the concept by ID">

`read_concept` loads frontmatter and body into a `Concept`. Missing IDs return `None`.

```python
concept = bundle.read_concept("projects/my_project")
print(f"Title: {concept.title}")
print(f"Tags: {concept.tags}")
```

<ResponseExample>

```text
Title: My Project
Tags: ['ml', 'data', 'gpu']
```

</ResponseExample>

</Step>

<Step title="Search concepts by tag">

`search_by_tag` scans all non-reserved concepts and returns those whose `tags` list contains the given string (exact match).

```python
gpu_projects = bundle.search_by_tag("gpu")
print(f"Found {len(gpu_projects)} GPU-related concepts")
```

<ResponseExample>

```text
Found 1 GPU-related concepts
```

</ResponseExample>

Each result is a full `Concept` object. Reserved files `index.md` and `log.md` are excluded from `list_concepts()` and therefore from tag search.

</Step>

<Step title="Append a Decision log entry">

`append_log(entry, category="Update")` appends a bullet under today's UTC date heading in `log.md`. Creates the date section if missing.

```python
bundle.append_log(
    "Dropped PyTorch due to ROCm issues on RX 5500 XT",
    category="Decision",
)
```

<ResponseExample>

```markdown
# Agent Log


## 2026-06-15
* **Decision**: Dropped PyTorch due to ROCm issues on RX 5500 XT
```

</ResponseExample>

Log entries are plain markdown bullets — they are not stored as typed `Concept` files. For structured decision concepts, write a file under `decisions/` with `type="Decision"` instead.

</Step>

<Step title="Inspect graph edges">

`get_graph_edges()` scans every concept `.md` file (excluding `index.md` and `log.md`), finds markdown links `[text](target)`, and returns directed edges. External `http` links are skipped; `.md` suffixes on relative targets are stripped.

```python
edges = bundle.get_graph_edges()
for edge in edges:
    print(f"{edge['source']} -> {edge['target']}")
```

Each edge is a dict with three keys:

<ResponseField name="source" type="string">
Concept ID of the file containing the link (relative path without `.md`).
</ResponseField>

<ResponseField name="target" type="string">
Resolved link target — another concept ID or relative path, never an external URL.
</ResponseField>

<ResponseField name="context" type="string">
Link label text from the markdown `[context](target)` pair.
</ResponseField>

<Warning>
The stock `basic_usage.py` body has no markdown links, so `get_graph_edges()` returns an empty list. That is expected — edges appear only when concept bodies link to other concepts.
</Warning>

To produce edges, add a link in a concept body:

```python
bundle.write_concept(
    "decisions/gpu_choice",
    body="# GPU Choice\n\nSee [my project](projects/my_project.md).",
    type="Decision",
    title="GPU Choice",
)
```

<ResponseExample>

```text
decisions/gpu_choice -> projects/my_project
```

</ResponseExample>

For neighbor queries, backlinks, tag clusters, and BFS traversal, use `GraphExtractor` — see [Knowledge graph](/knowledge-graph).

</Step>
</Steps>

## Copy-paste script

The full runnable script from `examples/basic_usage.py`:

```python
from hermes_okf.bundle import OKFBundle

# Create a new OKF bundle
bundle = OKFBundle("./my_knowledge")

# Write a concept
bundle.write_concept(
    "projects/my_project",
    body="# My Project\n\nDescribe your project here.",
    type="Project",
    title="My Project",
    tags=["ml", "data", "gpu"],
    resource="https://github.com/YOUR_USERNAME/my-project",
)

# Read it back
concept = bundle.read_concept("projects/my_project")
print(f"Title: {concept.title}")
print(f"Tags: {concept.tags}")

# Search by tag
gpu_projects = bundle.search_by_tag("gpu")
print(f"Found {len(gpu_projects)} GPU-related concepts")

# Log an agent action
bundle.append_log("Dropped PyTorch due to ROCm issues on RX 5500 XT", category="Decision")

# Inspect the graph
edges = bundle.get_graph_edges()
for edge in edges:
    print(f"{edge['source']} -> {edge['target']}")
```

Run from the repository root after install:

```bash
python examples/basic_usage.py
```

## Concept field reference

`write_concept` and `read_concept` surface the `Concept` dataclass:

| Field | Type | Set by recipe | Notes |
|-------|------|---------------|-------|
| `id` | `str` | `"projects/my_project"` | Relative path without `.md` |
| `type` | `str` | `"Project"` | Required by OKF spec; not enforced at write time |
| `title` | `str` | `"My Project"` | Defaults to `concept_id` if omitted |
| `description` | `str` | `""` | Optional frontmatter |
| `tags` | `list[str]` | `["ml", "data", "gpu"]` | Used by `search_by_tag` |
| `resource` | `str \| None` | GitHub URL | External reference |
| `timestamp` | `str \| None` | Auto UTC ISO 8601 | Added when not in frontmatter |
| `body` | `str` | Markdown after frontmatter | Stripped of leading/trailing whitespace |
| `metadata` | `dict` | Raw frontmatter | Full YAML dict for extensions |

<ParamField body="type" type="string" required>
OKF concept type (e.g. `Project`, `Decision`, `Metric`). Pass as a `write_concept` keyword argument.
</ParamField>

<ParamField body="concept_id" type="string" required>
Relative bundle path without `.md`. Slashes map to subdirectories (`projects/my_project` → `projects/my_project.md`).
</ParamField>

## API methods used

| Method | Returns | Behavior |
|--------|---------|----------|
| `OKFBundle(path)` | `OKFBundle` | Creates root dir and seeds structure if new |
| `write_concept(id, body, **fm)` | `Concept` | Write/overwrite `.md` with YAML frontmatter |
| `read_concept(id)` | `Concept \| None` | Parse frontmatter + body |
| `search_by_tag(tag)` | `list[Concept]` | Linear scan of all concepts |
| `append_log(entry, category)` | `None` | Append dated bullet to `log.md` |
| `get_graph_edges()` | `list[dict]` | Extract markdown link edges |
| `list_concepts(subdir?)` | `list[str]` | All concept IDs, sorted |
| `read_log()` | `str` | Full `log.md` contents |

`delete_concept`, `get_neighbors`, and `to_dict` are available on the same class but not used in the basic recipe.

## Validation

`write_concept` does not enforce OKF conformance (missing `type` is allowed). Run `OKFValidator` or `hermes-okf validate` before sharing a bundle. See [OKF validation reference](/okf-validation-reference).

<AccordionGroup>
<Accordion title="read_concept returns None">
The `concept_id` must match the relative path exactly (no `.md` suffix). Check `bundle.list_concepts()` for available IDs.
</Accordion>

<Accordion title="search_by_tag returns empty list">
Tag matching is exact and case-sensitive. Verify `concept.tags` via `read_concept` or inspect the YAML frontmatter on disk.
</Accordion>

<Accordion title="get_graph_edges returns no output">
Concept bodies must contain markdown links to other bundle paths, e.g. `[label](projects/my_project.md)`. Links to `http` URLs are ignored.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, reserved files, `Concept` fields, and Hermes-native directories.
</Card>
<Card title="Knowledge graph" href="/knowledge-graph">
Link edges, tag clustering, traversal, and `GraphExtractor` APIs.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
Full `OKFBundle` method signatures and exported public API.
</Card>
<Card title="Quickstart" href="/quickstart">
CLI-first path: `hermes-okf init`, write, search, and `validate`.
</Card>
</CardGroup>

---

## 18. Full Hermes agent

> End-to-end agent state in an OKF bundle: register tools with JSON schemas, create/complete/archive plans, record decisions, save snapshots, resume sessions — from `examples/full_agent.py` and `examples/hermes_integration.py`.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/18-full-hermes-agent.md
- Generated: 2026-06-15T19:29:11.454Z

### Source Files

- `examples/full_agent.py`
- `examples/hermes_integration.py`
- `src/hermes_okf/hermes.py`
- `src/hermes_okf/agent.py`
- `docs/HERMES_INTEGRATION.md`
- `tests/test_agent.py`

---
title: "Full Hermes agent"
description: "End-to-end agent state in an OKF bundle: register tools with JSON schemas, create/complete/archive plans, record decisions, save snapshots, resume sessions — from `examples/full_agent.py` and `examples/hermes_integration.py`."
---

`HermesAgent` in `hermes_okf.hermes` stores configuration, sessions, tool definitions, plans, decisions, and snapshots as OKF concepts under a single bundle path. A custom agent can instead inherit `HermesMemoryMixin` and wrap methods with `wrap_decision`, `wrap_tool`, and `wrap_observation` as shown in `examples/hermes_integration.py`. Both patterns persist state to the filesystem so the agent can stop and resume without an external database.

## Integration paths

| Path | Entry class | Best for |
|------|-------------|----------|
| Full state backend | `HermesAgent` | Agents that need tool registry, plan tracking, snapshots, and LLM context assembly |
| Memory hooks only | `HermesMemoryMixin` | Existing agent classes where you want automatic decision/tool/observation logging |

<Note>
Model identifiers such as `anthropic/claude-3.5-sonnet` and `openai/gpt-4o` are plain strings stored in `config/agent` frontmatter. Swap providers without changing OKF storage.
</Note>

## Bundle layout

On first `HermesAgent` construction, `_ensure_structure()` creates Hermes-native directories and writes a default `config/agent` concept if none exists.

:::files
hermes_agent_brain/
├── index.md
├── log.md
├── config/
│   └── agent.md
├── tools/
│   └── search_web.md
├── sessions/
│   └── 2026-06-15T10-00-00Z.md
├── plans/
│   ├── research_and_summarize_ai_trends_2026-06-15.md
│   └── archive/
│       └── completed_plan_2026-06-15.md
├── decisions/
│   └── use_claude_for_reasoning_tasks_2026-06-15.md
└── snapshots/
    └── 2026-06-15T14-30-00Z.md
:::

`write_concept` creates parent directories on demand, so `snapshots/` appears when the first snapshot is saved even though it is not pre-created by `_ensure_structure`.

```mermaid
stateDiagram-v2
    [*] --> Initializing: HermesAgent(bundle_path, agent_id)
    Initializing --> Active: start_session()
    Active --> Working: register_tool / create_plan / record_decision
    Working --> Working: complete_step / snapshot
    Working --> SessionEnded: end_session()
    SessionEnded --> Active: new HermesAgent(same bundle_path, agent_id)
    Active --> Restored: restore(snapshot_id)
    Restored --> Working
```

## Full agent workflow (`examples/full_agent.py`)

<Steps>
<Step title="Create or resume the agent">

```python
from hermes_okf.hermes import HermesAgent

agent = HermesAgent(
    bundle_path="./hermes_agent_brain",
    agent_id="hermes-alpha",
    model="anthropic/claude-3.5-sonnet",
)
```

<ParamField body="bundle_path" type="string" required>
Filesystem path to the OKF bundle root. Created on first write if missing.
</ParamField>

<ParamField body="agent_id" type="string" required>
Unique identifier stored in session and snapshot metadata.
</ParamField>

<ParamField body="model" type="string">
Default LLM model. Default: `gpt-4o`. Overridden by `config/agent` on resume when that concept exists.
</ParamField>

On init, the agent calls `_ensure_structure()`, `_load_config()`, and `start_session()`. Expect `current_session_id` to be set and at least one session concept under `sessions/`.
</Step>

<Step title="Register tools with JSON schemas">

```python
agent.register_tool(
    name="search_web",
    description="Search the web for current information.",
    schema={
        "type": "object",
        "properties": {"query": {"type": "string"}},
        "required": ["query"],
    },
    example='search_web(query="Python 3.12 release date")',
)
```

Each tool becomes a `Tool` concept at `tools/{name}.md`. The JSON schema is serialized into the `schema` frontmatter field. `list_tools()` returns concept IDs like `tools/search_web`; `get_tool(name)` reads a single definition.
</Step>

<Step title="Create and complete a plan">

```python
plan_id = agent.create_plan(
    task="Research and summarize AI trends",
    steps=[
        "Search for latest AI news",
        "Summarize key findings",
        "Write a concise report",
    ],
)

agent.complete_step(0, result="Found 5 major trends in LLMs and agents")
agent.complete_step(1, result="Key trend: multi-agent orchestration is growing")
agent.complete_step(2, result="Report written to output.md")
```

`create_plan` returns an ID shaped like `plans/{slug}_{date}`. Step checkboxes in the plan body flip from `[ ]` to `[x]`; optional `result` text is inserted on the next line. Frontmatter `progress` is `int((step_index + 1) / len(steps) * 100)`; status becomes `completed` at 100%. Each plan action also appends an observation to `log.md`.

To move a finished plan out of the active folder:

```python
agent.archive_plan(plan_id)  # copies to plans/archive/, deletes original
```
</Step>

<Step title="Record decisions and build LLM context">

```python
agent.memory.record_decision(
    "Use Claude for reasoning tasks, GPT-4o for coding tasks",
    rationale="Claude shows better long-context reasoning; GPT-4o is faster for code",
    tags=["model-selection", "architecture"],
)

context = agent.build_context(
    query="What model should I use for this task?",
    top_k=3,
)
```

`record_decision` writes a `Decision` concept under `decisions/`. `build_context` assembles markdown containing, in order: system prompt, active plan body, recalled concepts from `memory.recall`, the last 20 log lines, and registered tool summaries.
</Step>

<Step title="Snapshot, end session, and resume">

```python
agent.snapshot(note="After completing AI trends research")
agent.end_session()

# Later
resumed = HermesAgent(
    bundle_path="./hermes_agent_brain",
    agent_id="hermes-alpha",
)
print(resumed.model)           # from config/agent
print(resumed.list_sessions())
print(resumed.list_tools())
print(resumed.memory.bundle.list_concepts("plans"))

# Optional point-in-time restore
# resumed.restore("snapshots/2026-06-15T14-30-00Z")
resumed.end_session()
```

`snapshot()` writes a `Snapshot` concept with JSON state: `agent_id`, `model`, `current_session`, `current_plan`, `system_prompt`, and `note`. `restore()` reads metadata from the given snapshot ID or the latest snapshot and updates in-memory fields. `end_session()` sets the session concept `status` to `completed` and appends `ended_at`.
</Step>
</Steps>

<RequestExample>

```python title="examples/full_agent.py"
agent = HermesAgent("./hermes_agent_brain", "hermes-alpha", model="anthropic/claude-3.5-sonnet")
agent.register_tool("run_python", "Execute Python code", schema={"type": "object", ...})
plan_id = agent.create_plan("Research AI trends", ["Search", "Summarize", "Report"])
agent.complete_step(0, result="Found 5 trends")
agent.snapshot(note="After research")
agent.end_session()
```

</RequestExample>

## Decorator-based integration (`examples/hermes_integration.py`)

For agents with their own class hierarchy, inherit `HermesMemoryMixin` and apply wrappers after `super().__init__`:

```python title="examples/hermes_integration.py"
from hermes_okf.agent import HermesMemoryMixin

class MyAgent(HermesMemoryMixin):
    def __init__(self):
        super().__init__("./agent_knowledge", agent_id="my-agent-v1")
        self.start_session()
        self.choose_model = self.wrap_decision(self.choose_model)
        self.scrape_data = self.wrap_tool(self.scrape_data)

    def choose_model(self, task: str) -> str:
        if "code" in task.lower():
            return "anthropic/claude-3.5-sonnet"
        return "openai/gpt-4o"

    def scrape_data(self, url: str) -> dict:
        return {"url": url, "items": 42}

    def run(self):
        model = self.choose_model("Write a Python script")
        data = self.scrape_data("https://example.com")
        context = self.with_context("python script", top_k=3)
        self.end_session("session-001")
```

| Wrapper | Decorator | OKF effect |
|---------|-----------|------------|
| `wrap_decision(fn)` | `memorize_decision` | Writes `Decision` concept with call signature and return value |
| `wrap_tool(fn)` | `memorize_tool` | Appends `Tool-Call` log entry (summary truncated to 500 chars) |
| `wrap_observation(fn)` | `memorize_observation` | Appends `Observation` log entry |

`with_context(query, top_k=3)` delegates to `memory.recall`, which runs full-text search over bundle concepts. Standalone decorators also accept an explicit `memory=` argument when not used on a mixin method.

<Warning>
Apply `wrap_*` calls after `super().__init__` completes. The mixin must initialize `self.memory` before wrappers bind to it.
</Warning>

## `HermesAgent` API reference

### Constructor

```python
HermesAgent(bundle_path: str, agent_id: str, model: str = "gpt-4o")
```

Inherits `HermesMemoryMixin.__init__`, which creates `self.memory: HermesMemory`.

### Session lifecycle

| Method | Returns | Behavior |
|--------|---------|----------|
| `start_session(session_id=None)` | `str` | Sets `current_session_id`, logs to bundle, writes `Session` concept with `status: active` |
| `end_session()` | `None` | Flushes session via `memory.end_session`, updates concept to `status: completed` |
| `list_sessions()` | `list[str]` | Sorted concept IDs under `sessions/` |
| `recall_session(session_id=None)` | `Concept \| None` | Reads one session; defaults to most recent |

### Tool registry

| Method | Returns | Behavior |
|--------|---------|----------|
| `register_tool(name, description, schema=None, example="")` | `None` | Writes `Tool` concept; `schema` stored as JSON string in frontmatter |
| `list_tools()` | `list[str]` | All `tools/*` concept IDs |
| `get_tool(name)` | `Concept \| None` | Reads `tools/{name}` |

### Plan execution

| Method | Returns | Behavior |
|--------|---------|----------|
| `create_plan(task, steps)` | `str` | Plan ID; sets `current_plan_id` |
| `complete_step(step_index, result="")` | `None` | No-op if `current_plan_id` unset or index out of range |
| `archive_plan(plan_id=None)` | `None` | Copies to `plans/archive/`, deletes active plan |

### State snapshots

| Method | Returns | Behavior |
|--------|---------|----------|
| `snapshot(note="")` | `None` | Writes `snapshots/{timestamp}` with embedded JSON state |
| `restore(snapshot_id=None)` | `dict` | Restores `model`, `current_session_id`, `current_plan_id`, `system_prompt` from metadata |

### Context assembly

| Method | Returns | Behavior |
|--------|---------|----------|
| `build_context(query, top_k=5)` | `str` | Markdown context for LLM calls |

### Inherited from `HermesMemoryMixin`

| Method | Returns | Behavior |
|--------|---------|----------|
| `with_context(query, top_k=3)` | `list[Concept]` | Search-based recall |
| `wrap_decision` / `wrap_tool` / `wrap_observation` | wrapped callable | Auto-persist on each call |

## Concept types written by a full agent

| `type` frontmatter | Path pattern | Written by |
|--------------------|--------------|------------|
| `AgentConfig` | `config/agent` | `_create_default_config` on first init |
| `Session` | `sessions/{id}` | `start_session` / `end_session` |
| `Tool` | `tools/{name}` | `register_tool` |
| `Plan` | `plans/{slug}_{date}` | `create_plan`, `complete_step`, `archive_plan` |
| `Decision` | `decisions/{slug}_{date}` | `memory.record_decision` or `wrap_decision` |
| `Snapshot` | `snapshots/{timestamp}` | `snapshot` |

Log-only events (observations, tool calls, session start/end messages) append to `log.md` via `OKFBundle.append_log`.

## Verification

Run the full example from the repository root:

```bash
python examples/full_agent.py
```

Expected stdout signals:

- `Created plan: plans/...`
- `--- LLM Context ---` block containing system prompt, plan, and recalled memory
- `Agent session ended. State preserved in OKF bundle.`
- `--- Resuming agent ---` with model, sessions list, tools list, and plans list

Inspect the bundle with the standalone CLI:

```bash
hermes-okf list --path ./hermes_agent_brain --type Tool
hermes-okf show --path ./hermes_agent_brain config/agent
hermes-okf context --path ./hermes_agent_brain "What model should I use?"
hermes-okf validate --path ./hermes_agent_brain
```

Unit tests in `tests/test_hermes.py` cover structure creation, tool registration, plan progress (50% after one of two steps), snapshot restore reverting `model`, `build_context` content, and session `status: completed` after `end_session`.

## Multi-agent coordination

Multiple `HermesAgent` instances can share one `bundle_path` with different `agent_id` values. Plans, tools, and decisions are bundle-global concepts; session concepts record which `agent_id` started them. Agent B can call `complete_step` on a plan Agent A created when both use the same bundle and `current_plan_id` is set appropriately.

<Tip>
Copy a bundle directory and change `agent_id` to fork an agent brain while preserving tools, plans, and decisions.
</Tip>

## Failure modes

| Symptom | Cause | Recovery |
|---------|-------|----------|
| `complete_step` has no effect | `current_plan_id` is `None` or step index out of range | Call `create_plan` first; pass valid zero-based index |
| Resumed agent uses constructor `model` | `config/agent` missing or has no `model` key | Pass `model` on first init; verify `config/agent` frontmatter |
| `restore()` returns `{}` | No snapshots in bundle | Call `snapshot()` before relying on restore |
| Wrappers do not persist | `wrap_*` called before `super().__init__` | Reorder `__init__` so mixin initializes `self.memory` first |

## Related pages

<CardGroup>
<Card title="Python agent integration" href="/python-agent-integration">
`HermesMemoryMixin`, decorators, and wiring OKF memory into custom agent classes.
</Card>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, `Concept` fields, and Hermes-native directories.
</Card>
<Card title="Two-memory model" href="/two-memory-model">
Hot buffer vs cold OKF archive and flush semantics.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
Full exported API including `HermesAgent`, `HermesMemory`, and `OKFBundle`.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
Plugin-level integration via `HermesOKFProvider` for the Hermes CLI runtime.
</Card>
</CardGroup>

---

## 19. RAG pipeline

> Complete vector retrieval workflow from `examples/rag_integration.py`: load all bundle markdown, split on headers, embed into Chroma, query with `retriever.invoke`, and swap embedding providers without changing OKF storage.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/19-rag-pipeline.md
- Generated: 2026-06-15T19:29:41.850Z

### Source Files

- `examples/rag_integration.py`
- `pyproject.toml`
- `src/hermes_okf/bundle.py`
- `README.md`
- `src/hermes_okf/search.py`

---
title: "RAG pipeline"
description: "Complete vector retrieval workflow from `examples/rag_integration.py`: load all bundle markdown, split on headers, embed into Chroma, query with `retriever.invoke`, and swap embedding providers without changing OKF storage."
---

Vector retrieval over an OKF bundle is an optional sidecar: `hermes-okf` core persists concepts as filesystem markdown with only `pyyaml`, while the `[rag]` extra wires LangChain loaders, `MarkdownHeaderTextSplitter`, Chroma persistence, and `retriever.invoke` against that same bundle. OKF files are never rewritten during indexing — embeddings live in a separate Chroma directory.

<Info>
RAG is not exposed through `hermes-okf` or `hermes okf` CLI commands. Use `examples/rag_integration.py` for a standalone script, or `HermesOKFProvider.rag_search()` from Python.
</Info>

## Architecture

The pipeline reads from the OKF bundle root, chunks by markdown headers, embeds into Chroma, and queries through a LangChain retriever. Full-text search via `SearchIndex` remains the default in-process path; RAG adds semantic similarity on top without replacing OKF storage.

```mermaid
flowchart LR
  subgraph okf["OKF storage (unchanged)"]
    B["OKFBundle.root"]
    MD["**/*.md concepts"]
    B --> MD
  end

  subgraph rag["RAG sidecar (optional [rag] extra)"]
    DL["DirectoryLoader + TextLoader"]
    SP["MarkdownHeaderTextSplitter"]
    CH["Chroma.from_documents"]
    RT["retriever.invoke"]
    DL --> SP --> CH --> RT
  end

  subgraph embed["Embedding provider (swappable)"]
    EM["OpenAIEmbeddings or compatible"]
  end

  MD --> DL
  EM --> CH
  CH --> VS["persist_directory"]
  VS --> RT
```

| Layer | Module / artifact | Role |
|-------|-------------------|------|
| Source of truth | `OKFBundle` | Filesystem bundle at `bundle.root`; concepts are `.md` + YAML frontmatter |
| Load | `DirectoryLoader`, `TextLoader` | Recursively loads `**/*.md` under the bundle root with UTF-8 encoding |
| Split | `MarkdownHeaderTextSplitter` | Splits on `#` and `##` headers into LangChain `Document` chunks |
| Index | `Chroma.from_documents` | Embeds chunks and persists vectors to disk |
| Query | `vectorstore.as_retriever` | Returns top-`k` chunks via `retriever.invoke(query)` |
| Built-in alternative | `SearchIndex` | Stdlib inverted-index full-text search; no embeddings required |

<Warning>
`DirectoryLoader` includes every `**/*.md` file under the bundle root — including reserved `index.md` and `log.md`. `OKFBundle.list_concepts()` skips those files, but the RAG loader does not.
</Warning>

## Prerequisites

<Steps>
<Step title="Install the RAG extra">

```bash
pip install hermes-okf[rag]
```

This pulls in `langchain`, `langchain-community`, `langchain-chroma`, and `langchain-openai` (see `pyproject.toml` optional-dependencies).

</Step>

<Step title="Prepare an OKF bundle">

Initialize or point at an existing bundle. The example uses `./my_knowledge`; the Hermes provider defaults to `~/.hermes/okf_memory`.

```bash
hermes-okf init ./my_knowledge
```

</Step>

<Step title="Configure an embedding provider">

`OpenAIEmbeddings` (default in examples) expects credentials for your chosen provider — typically `OPENAI_API_KEY` for OpenAI-compatible endpoints. Swap the embedding class without touching OKF files.

</Step>
</Steps>

## Pipeline stages

### 1. Bind the bundle

`OKFBundle` resolves the bundle root path. Pass `str(bundle.root)` to LangChain loaders.

```python
from hermes_okf.bundle import OKFBundle

bundle = OKFBundle("./my_knowledge")
```

### 2. Load all markdown

`DirectoryLoader` walks the bundle tree and loads every `.md` file as a LangChain document.

```python
from langchain_community.document_loaders import DirectoryLoader, TextLoader

loader = DirectoryLoader(
    str(bundle.root),
    glob="**/*.md",
    loader_cls=TextLoader,
    loader_kwargs={"encoding": "utf-8"},
)
docs = loader.load()
```

Each loaded document carries a `source` metadata field (file path) that flows into Chroma and query results.

### 3. Split on headers

`MarkdownHeaderTextSplitter` breaks documents at `#` (Header 1) and `##` (Header 2) boundaries. Header text is attached to chunk metadata.

```python
from langchain.text_splitter import MarkdownHeaderTextSplitter

splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[("#", "Header 1"), ("##", "Header 2")]
)
splits = []
for doc in docs:
    splits.extend(splitter.split_text(doc.page_content))
```

### 4. Embed into Chroma

`Chroma.from_documents` embeds all splits and writes a persistent vector store. The example uses a project-local directory; the provider writes under the bundle.

```python
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=OpenAIEmbeddings(),
    persist_directory="./chroma_okf_db",
)
```

### 5. Query with the retriever

Build a retriever with `search_kwargs={"k": 5}` and invoke it with a natural-language query string.

```python
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
results = retriever.invoke("What GPU decisions did we make?")

for r in results:
    print(f"---\n{r.page_content[:300]}...\n")
```

<ResponseField name="Document" type="langchain Document">
Each result is a LangChain `Document` with `page_content` (chunk text) and `metadata` (including `source` file path and header keys from the splitter).
</ResponseField>

## Complete example script

The canonical reference is `examples/rag_integration.py`. Run it after installing `[rag]` and populating a bundle:

```bash
pip install hermes-okf[rag]
python examples/rag_integration.py
```

The script chains all five stages: `OKFBundle` → `DirectoryLoader` → `MarkdownHeaderTextSplitter` → `Chroma.from_documents` → `retriever.invoke`.

## Provider integration

`HermesOKFProvider` in `hermes_okf.hermes_integration` embeds the same pipeline in `rag_search()` and `_build_rag_index()`. Differences from the standalone example:

| Aspect | `examples/rag_integration.py` | `HermesOKFProvider` |
|--------|------------------------------|---------------------|
| Bundle path | `OKFBundle("./my_knowledge")` | `self.config.bundle_path` (default `~/.hermes/okf_memory`) |
| Chroma persist dir | `./chroma_okf_db` | `{bundle_path}/.chroma` |
| Embedding model | `OpenAIEmbeddings()` (default) | `OpenAIEmbeddings(model=self.config.rag_model)` |
| Index build | Eager in script | Lazy — `_build_rag_index()` runs when `.chroma` is missing |
| Return shape | Raw `Document` list | `list[dict]` with `source` and `content` (truncated to 500 chars) |

```python
from hermes_okf import get_provider

provider = get_provider()
results = provider.rag_search("deployment strategies for Python services", top_k=5)

for r in results:
    print(f"{r['source']}: {r['content'][:100]}")
```

<Note>
`rag_search()` raises `ImportError` with install instructions if `hermes-okf[rag]` is not installed. It does not check `enable_rag` before running — call the method explicitly when you want semantic search.
</Note>

## Swap embedding providers

OKF storage is embedding-agnostic. Only the LangChain `embedding` / `embedding_function` argument changes. The example comment notes `OpenRouterEmbeddings` as an alternative to `OpenAIEmbeddings`.

<Tabs>
<Tab title="OpenAI (default)">

```python
from langchain_openai import OpenAIEmbeddings

embedding = OpenAIEmbeddings()
# Provider default model: openai/text-embedding-3-small
embedding = OpenAIEmbeddings(model="openai/text-embedding-3-small")
```

</Tab>
<Tab title="OpenRouter or other compatible">

```python
# Any LangChain Embeddings implementation works here.
# from langchain_openai import OpenAIEmbeddings  # OpenRouter-compatible base URL
# embedding = OpenAIEmbeddings(base_url="https://openrouter.ai/api/v1", ...)

vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=your_embedding_instance,
    persist_directory="./chroma_okf_db",
)
```

</Tab>
</Tabs>

Rebuilding the Chroma index after swapping models is required — delete the persist directory and re-run `from_documents` or `_build_rag_index()`.

## Configuration

RAG-related fields on `HermesOKFConfig`:

<ParamField body="enable_rag" type="boolean" default="false">
Loaded from `HERMES_OKF_ENABLE_RAG` (values `1`, `true`, `yes`), `~/.hermes/hermes-okf.yaml`, or `plugins.hermes_okf` in `~/.hermes/config.yaml`. Stored on the config object; `rag_search()` does not gate on this flag today.
</ParamField>

<ParamField body="rag_model" type="string" default="openai/text-embedding-3-small">
Embedding model passed to `OpenAIEmbeddings(model=...)` in `_build_rag_index()` and `rag_search()`.
</ParamField>

<ParamField body="bundle_path" type="string" default="~/.hermes/okf_memory">
OKF bundle root. Chroma index is written to `{bundle_path}/.chroma` by the provider.
</ParamField>

Environment variable resolution for `enable_rag`:

```bash
export HERMES_OKF_ENABLE_RAG=true
```

## RAG vs full-text search

| Capability | `SearchIndex` | RAG (Chroma) |
|------------|---------------|--------------|
| Dependency | Core (`pyyaml` only) | `hermes-okf[rag]` extra |
| Match style | Token overlap / optional `rapidfuzz` | Vector semantic similarity |
| Index location | In-memory, rebuilt per session | Persistent Chroma directory |
| CLI exposure | `hermes-okf search`, `hermes okf search` | Python API only |
| OKF writes | Reads concepts via `list_concepts()` | Reads all `**/*.md` via `DirectoryLoader` |

Use full-text search for exact keyword recall; use RAG when queries need semantic similarity ("find things like this idea") over bundle content.

## Persistence and reindexing

- **Standalone example:** vectors persist in `./chroma_okf_db` (or any path you pass to `persist_directory`).
- **Provider:** vectors persist in `{bundle_path}/.chroma`.
- **Stale index:** the provider rebuilds only when `.chroma` is absent. After OKF concept writes, delete the Chroma directory and call `_build_rag_index()` or re-run the example script to refresh embeddings.
- **OKF unchanged:** concept CRUD through `OKFBundle.write_concept()` and agent memory hooks continue writing plain markdown; RAG is a read-side projection.

## Failure modes

<AccordionGroup>
<Accordion title="ImportError: RAG requires hermes-okf[rag]">

Install the optional extra:

```bash
pip install hermes-okf[rag]
```

</Accordion>

<Accordion title="Empty or irrelevant query results">

Confirm the bundle contains markdown with meaningful `#` / `##` structure. Chunks are header-bound — flat files with no headers become single large chunks. Verify the Chroma index was built after the latest bundle writes.

</Accordion>

<Accordion title="Embedding API errors">

Check provider credentials and network access for your chosen embedding class. `OpenAIEmbeddings` requires a valid API key for the configured endpoint.

</Accordion>

<Accordion title="Reserved files indexed unexpectedly">

`DirectoryLoader` does not exclude `index.md` or `log.md`. Filter documents after `loader.load()` if those files should not appear in semantic results.

</Accordion>
</AccordionGroup>

## Next

<CardGroup>
<Card title="Enable RAG" href="/enable-rag">
Install `[rag]`, config flags, and provider-neutral embedding selection.
</Card>
<Card title="OKF bundle model" href="/okf-bundle-model">
Filesystem layout, concept frontmatter, and reserved files that feed the loader.
</Card>
<Card title="Python SDK reference" href="/python-sdk-reference">
`OKFBundle`, `SearchIndex`, and `get_provider()` public API.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, env vars, and resolution order.
</Card>
<Card title="Hermes provider integration" href="/hermes-provider-integration">
Session hooks, `search()`, and `rag_search()` on `HermesOKFProvider`.
</Card>
</CardGroup>

---

## 20. Troubleshooting

> Known failure modes and recovery: `hermes-okf-install` not in PATH, plugin not listed in `hermes memory setup`, stale model in `config/agent`, missing bundle, Windows filename constraints, and `plugins.enabled` YAML list shape.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/20-troubleshooting.md
- Generated: 2026-06-15T19:29:30.738Z

### Source Files

- `README.md`
- `src/hermes_okf/install_plugin.py`
- `src/hermes_okf/memory_plugin.py`
- `docs/HERMES_PLUGIN.md`
- `CHANGELOG.md`
- `wiki/Troubleshooting.md`

---
title: "Troubleshooting"
description: "Known failure modes and recovery: `hermes-okf-install` not in PATH, plugin not listed in `hermes memory setup`, stale model in `config/agent`, missing bundle, Windows filename constraints, and `plugins.enabled` YAML list shape."
---

Hermes OKF surfaces install, discovery, config, and filesystem errors through CLI exit codes, stderr messages, and missing files under `~/.hermes/`. Most failures trace to one of six areas: PyPI script PATH, Hermes filesystem plugin discovery, `config.yaml` shape, bundle initialization, Windows-safe timestamp filenames, or stale `config/agent` metadata. Use the sections below to match a symptom to a verified recovery path.

## Symptom index

| Symptom | Likely cause | First check |
|---------|--------------|-------------|
| `hermes-okf-install: command not found` | Script not on `PATH` | `python -m hermes_okf.install_plugin` |
| `hermes memory setup` omits hermes-okf | Plugin wrapper or `plugins.enabled` shape | `ls ~/.hermes/plugins/hermes-okf/` |
| `hermes okf show config/agent` shows old model | Session not re-initialized after model change | Restart Hermes |
| `Concept not found` / empty bundle | Bundle path missing or uninitialized | `hermes-okf init ~/.hermes/okf_memory` |
| Windows `OSError` on write | Colon in legacy filenames | `pip install --upgrade hermes-okf` |
| Plugin silently ignored | `plugins.enabled` is a string, not a list | Fix YAML list shape |

<Note>
Hermes discovers plugins from `~/.hermes/plugins/` via filesystem layout (`plugin.yaml` + `__init__.py`). The `hermes.memory_providers` pip entry point exists but Hermes does not read it — `hermes-okf-install` is required.
</Note>

## `hermes-okf-install` not in PATH

PyPI registers `hermes-okf-install` as a console script in your active Python environment's `bin/` directory. If that directory is not on `PATH`, the shell reports `command not found` even though the package is installed.

<Steps>
<Step title="Confirm the package is installed">

```bash
python -c "import hermes_okf; print(hermes_okf.__version__)"
```

Expected: a version string (for example `0.4.6`). If import fails, run `pip install hermes-okf` first.

</Step>
<Step title="Run install via module or standalone CLI">

<CodeGroup>

```bash title="Module form (always works)"
python -m hermes_okf.install_plugin
```

```bash title="Standalone CLI subcommand"
hermes-okf install-plugin
```

```bash title="Hermes agent venv"
~/.hermes/hermes-agent/venv/bin/python -m hermes_okf.install_plugin
```

</CodeGroup>

</Step>
<Step title="Verify install output">

```
Installed hermes-okf plugin to /home/username/.hermes/plugins/hermes-okf
  Updated ~/.hermes/config.yaml
  Run 'hermes memory setup' to finish activation
```

The `Updated` line appears when `~/.hermes/config.yaml` already exists and was modified.

</Step>
</Steps>

<Tabs>
<Tab title="Virtual environment">

```bash
source /path/to/venv/bin/activate
hermes-okf-install
```

</Tab>
<Tab title="Find Python bin">

```bash
which python
# Scripts live in the sibling bin/ directory of that Python
```

</Tab>
<Tab title="uv">

```bash
uv run --python /path/to/python hermes-okf-install
```

</Tab>
</Tabs>

## Plugin not listed in `hermes memory setup`

`hermes memory setup` lists memory providers that Hermes can load. If `hermes-okf` is missing, the plugin wrapper directory or `~/.hermes/config.yaml` is misconfigured.

### Diagnosis

```bash
ls ~/.hermes/plugins/hermes-okf/
```

Expected files:

| File | Purpose |
|------|---------|
| `plugin.yaml` | Plugin metadata and hooks (`on_session_end`) |
| `__init__.py` | Exports `HermesOKFMemoryProvider` |

`hermes-okf-install` writes both files and, when `~/.hermes/config.yaml` exists, sets:

<ParamField body="memory.provider" type="string">
Must be `hermes-okf` for the OKF memory backend.
</ParamField>

<ParamField body="memory.bundle_path" type="string" default="~/.hermes/okf_memory">
Directory where the OKF bundle is stored.
</ParamField>

### Recovery

<Steps>
<Step title="Re-run the installer">

```bash
hermes-okf-install
# or: python -m hermes_okf.install_plugin
```

</Step>
<Step title="Confirm config keys">

```yaml
plugins:
  enabled:
    - hermes-okf

memory:
  provider: hermes-okf
  bundle_path: ~/.hermes/okf_memory
```

</Step>
<Step title="Restart Hermes and re-run setup">

```bash
hermes memory setup
hermes
```

</Step>
</Steps>

<Warning>
Reinstalling only via `pip install hermes-okf` does not create `~/.hermes/plugins/hermes-okf/`. You must run `hermes-okf-install` (or `hermes-okf install-plugin`) after every fresh environment.
</Warning>

## Stale model in `config/agent`

The OKF `config/agent` concept stores the active model in frontmatter. As of v0.3.7, `HermesOKFMemoryProvider.initialize()` reads Hermes `config.yaml` on every session start and overwrites the concept.

Model resolution order:

1. Top-level `model` in `~/.hermes/config.yaml`
2. Fallback: `llm.model`

<Steps>
<Step title="Upgrade and restart">

```bash
pip install --upgrade hermes-okf
hermes
```

Session start triggers `initialize()`, which calls `write_concept("config/agent", ..., model=hermes_model)`.

</Step>
<Step title="Verify the synced model">

```bash
hermes okf show config/agent
```

The `model:` metadata field should match your Hermes config. Use `--raw` to show only the markdown body.

</Step>
</Steps>

<Info>
Changing the model in `config.yaml` while Hermes is running does not update OKF until the next session. Exit and restart Hermes after model changes.
</Info>

## Missing OKF bundle

Commands that read or write concepts require a valid bundle root containing at least `index.md` and `log.md`. `OKFBundle` auto-creates these when the root path exists but lacks `index.md`; the Hermes plugin also initializes on first session start.

### Symptoms

- `hermes okf show <path>` prints `Concept not found: <path>`
- Standalone CLI reports empty listings or validation errors
- Custom `bundle_path` points to a non-existent directory

### Recovery

<Steps>
<Step title="Initialize the default bundle path">

```bash
hermes-okf init ~/.hermes/okf_memory
```

Expected:

```
Initialised OKF bundle at /home/username/.hermes/okf_memory
```

</Step>
<Step title="Handle non-empty directories">

If the target directory already has files:

```bash
hermes-okf init ~/.hermes/okf_memory --force
```

Without `--force`, init exits with code `1` and:

```
Error: Directory '...' is not empty. Use --force to overwrite.
```

</Step>
<Step title="Point Hermes at the bundle">

Ensure `memory.bundle_path` in `~/.hermes/config.yaml` matches the initialized path, or override with:

```bash
export HERMES_OKF_BUNDLE_PATH=~/.hermes/okf_memory
```

</Step>
<Step title="Validate">

```bash
hermes-okf validate --path ~/.hermes/okf_memory
```

Expected: `Bundle is valid.`

</Step>
</Steps>

## Windows filename constraints

Windows rejects `:` in filenames. Older hermes-okf versions used ISO timestamps with colons (for example `2026-06-15T10:30:00Z`) for session and snapshot IDs. Current releases replace colons with hyphens in the time segment.

| Version behavior | Filename example |
|------------------|------------------|
| Legacy (invalid on Windows) | `2026-06-15T10:30:00Z` |
| v0.2.0+ (safe) | `2026-06-15T10-30-00Z` |

`HermesAgent._now_filename_safe()` formats timestamps as `%Y-%m-%dT%H-%M-%SZ` for session IDs, snapshot paths under `snapshots/`, and related concept filenames.

<Steps>
<Step title="Upgrade">

```bash
pip install --upgrade hermes-okf
```

</Step>
<Step title="Use hyphenated paths in CLI commands">

```bash
hermes okf show sessions/2026-06-14T22-14-58Z
```

Not `sessions/2026-06-14T22:14:58Z`.

</Step>
</Steps>

## `plugins.enabled` YAML list shape

`install_plugin._configure_hermes_config()` ensures `plugins.enabled` is a Python list and appends `hermes-okf` if absent. If the key is a JSON-encoded string or scalar, Hermes ignores it and the plugin never activates.

### Correct shape

```yaml
plugins:
  enabled:
    - hermes-okf
```

### Incorrect shapes

<AccordionGroup>
<Accordion title="JSON string (ignored)">

```yaml
plugins:
  enabled: "[\"hermes-okf\"]"
```

Hermes expects a native YAML sequence, not a quoted string.

</Accordion>
<Accordion title="Scalar string (ignored)">

```yaml
plugins:
  enabled: hermes-okf
```

Must be a list even for a single plugin.

</Accordion>
<Accordion title="Missing key (installer adds it)">

If `plugins` or `plugins.enabled` is absent, `hermes-okf-install` creates an empty list and appends `hermes-okf`. Re-run the installer to repair a corrupted shape:

```bash
python -m hermes_okf.install_plugin
```

The installer coerces non-list values to `[]` before appending.

</Accordion>
</AccordionGroup>

## Additional CLI failures

### `hermes okf show` AttributeError on `content`

Versions before v0.3.5 referenced `concept.content` instead of `concept.body`. Upgrade:

```bash
pip install --upgrade hermes-okf
```

### Slow memory writes

Increase the hot-memory buffer before flush:

```yaml
memory:
  hot_memory_max: 200
```

Or:

```bash
export HERMES_OKF_HOT_MEMORY_MAX=200
```

Default is `50`.

## Uninstall and clean reinstall

```bash
hermes-okf-uninstall
# or: hermes-okf uninstall-plugin
```

Removes `~/.hermes/plugins/hermes-okf/` but does not delete the OKF bundle. To fully re-register:

```bash
pip install --upgrade hermes-okf
hermes-okf-install
hermes memory setup
```

## Check version and report issues

```bash
hermes-okf --version
python -c "import hermes_okf; print(hermes_okf.__version__)"
```

If problems persist after the steps above, open an issue at [github.com/EliaszDev/hermes-okf/issues](https://github.com/EliaszDev/hermes-okf/issues) with your hermes-okf version, OS, and the output of `ls ~/.hermes/plugins/hermes-okf/`.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
PyPI install targets, entry-point scripts, and PATH recovery when install scripts are not found.
</Card>
<Card title="Install Hermes plugin" href="/install-hermes-plugin">
Full `hermes-okf-install` flow, wrapper layout, and `hermes memory setup` activation.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`HermesOKFConfig` fields, env overrides, and `memory.*` keys written by install.
</Card>
<Card title="OKF validation reference" href="/okf-validation-reference">
`hermes-okf validate` rules and error output when bundle structure is wrong.
</Card>
</CardGroup>

---

## 21. Contributing

> Dev setup (`pip install -e ".[dev]"`), test/lint/type-check commands, pre-commit hooks, branch naming, coverage expectations, CI workflow gates, and documentation update requirements for user-facing changes.

- Page Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/pages/21-contributing.md
- Generated: 2026-06-15T19:29:33.467Z

### Source Files

- `CONTRIBUTING.md`
- `pyproject.toml`
- `.github/workflows/ci.yml`
- `.pre-commit-config.yaml`
- `tests/__init__.py`
- `docs/ARCHITECTURE.md`

---
title: "Contributing"
description: "Dev setup (`pip install -e \".[dev]\"`), test/lint/type-check commands, pre-commit hooks, branch naming, coverage expectations, CI workflow gates, and documentation update requirements for user-facing changes."
---

Contributions to **hermes-okf** run through an editable install with the `[dev]` extra, a four-tool quality stack (`black`, `ruff`, `mypy`, `pytest`), optional pre-commit hooks, and a GitHub Actions pipeline that gates merges to `main` on lint, type-check, tests, and OKF bundle validation. The package targets Python 3.9+ and keeps the core runtime lean (`pyyaml` only); development dependencies are isolated in `pyproject.toml` under `[project.optional-dependencies].dev`.

<Note>
By contributing, you agree that your contributions are licensed under the MIT License.
</Note>

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Python | `>=3.9` per `pyproject.toml` (`requires-python`) |
| Git | Fork/clone workflow against `EliaszDev/hermes-okf` |
| Package manager | `pip` (CI and docs use `pip install -e ".[dev]"`) |

The `[dev]` extra installs `pytest`, `pytest-cov`, `black`, `ruff`, `mypy`, `pre-commit`, and `types-PyYAML`. Install RAG extras separately with `pip install -e ".[rag]"` when working on vector retrieval features.

## Development setup

<Steps>
<Step title="Fork and clone">

Fork the repository on GitHub, then clone your fork:

```bash
git clone https://github.com/YOUR_USERNAME/hermes-okf.git
cd hermes-okf
```

</Step>

<Step title="Install in editable mode">

```bash
pip install -e ".[dev]"
```

This registers console scripts (`hermes-okf`, `hermes-okf-install`, `hermes-okf-uninstall`) and exposes the `hermes_okf` package from `src/hermes_okf`.

</Step>

<Step title="Verify the install">

```bash
pytest --version
hermes-okf --version
```

</Step>
</Steps>

For end-user install paths and optional extras (`[rag]`, `[all]`), see the [Installation](/installation) page.

## Branch naming

| Branch type | Pattern | Notes |
|-------------|---------|-------|
| Main | `main` | Always deployable; CI runs on push and PRs targeting `main` |
| Features | `feature/your-feature-name` | New functionality |
| Bug fixes | `fix/issue-description` | Targeted repairs |

Create feature or fix branches from `main`, keep changes focused, and open pull requests back into `main`.

## Code quality tools

All four tools below run in CI. Run them locally before opening a PR.

### Formatter — black

<ParamField body="line-length" type="number" default="100">
Configured in `[tool.black]`; targets Python 3.9 syntax.
</ParamField>

```bash
# Format
black src tests

# CI-equivalent check
black --check src tests
```

### Linter — ruff

Ruff selects rules `E`, `F`, `I`, `N`, `W`, `UP`, `B`, `C4`, `SIM` and ignores `E501` (line length handled by black).

```bash
ruff check src tests
```

### Type checker — mypy

Mypy runs in **strict** mode on `src/` only (`ignore_missing_imports = true`).

```bash
mypy src
```

### Tests — pytest

Local `pytest` inherits coverage options from `pyproject.toml`:

```bash
pytest
```

Equivalent to:

```bash
pytest --cov=hermes_okf --cov-report=term-missing --cov-report=xml
```

CI overrides `addopts` to avoid double-reporting:

```bash
pytest -o addopts="" --cov=hermes_okf --cov-report=xml
```

## Pre-commit hooks

Pre-commit is encouraged but not required for local work. The repo ships `.pre-commit-config.yaml` with three hooks:

| Hook | Repo pin | Behavior |
|------|----------|----------|
| `black` | `24.4.2` | Auto-format |
| `ruff` | `v0.4.7` | Lint with `--fix`; exits non-zero if fixes were applied |
| `mypy` | `v1.10.0` | Strict check on staged files; includes `types-PyYAML` |

<Steps>
<Step title="Install pre-commit">

```bash
pip install pre-commit   # also included in [dev]
pre-commit install
```

</Step>

<Step title="Run on all files">

```bash
pre-commit run --all-files
```

</Step>
</Steps>

<Tip>
Pre-commit `ruff` uses `--fix`, while CI runs `ruff check` without auto-fix. Run `black --check` locally to match CI exactly.
</Tip>

## Writing tests

Tests live under `tests/` and follow pytest discovery rules from `[tool.pytest.ini_options]`:

- Files: `test_*.py`
- Classes: `Test*`
- Functions: `test_*`

Current modules include `test_bundle.py`, `test_validators.py`, `test_search.py`, `test_graph.py`, `test_memory.py`, `test_agent.py`, `test_hermes.py`, and `test_hermes_integration.py`.

### Filesystem isolation

Use `tempfile.TemporaryDirectory` for bundle and filesystem tests so each case gets a clean OKF root:

```python
import tempfile
from hermes_okf.bundle import OKFBundle

def test_creates_bundle_structure():
    with tempfile.TemporaryDirectory() as tmp:
        bundle = OKFBundle(tmp)
        assert (bundle.root / "index.md").exists()
```

### Coverage expectations

| Scope | Expectation |
|-------|-------------|
| New code | Aim for **>80%** coverage on additions |
| CI enforcement | No `fail_under` threshold; coverage is collected and reported |
| Local default | `--cov=hermes_okf --cov-report=term-missing` via `addopts` |
| CI upload | Codecov receives `coverage.xml` from Python **3.12** only (`fail_ci_if_error: false`) |

Add tests alongside the module you change. For OKF conformance rules exercised by `hermes-okf validate`, see [OKF validation reference](/okf-validation-reference).

## CI workflow gates

The `.github/workflows/ci.yml` workflow triggers on:

- `push` to `main`
- `pull_request` targeting `main`
- `workflow_dispatch` (optional tag input for publish)

```mermaid
flowchart TB
  subgraph triggers["Triggers"]
    push["push → main"]
    pr["PR → main"]
    dispatch["workflow_dispatch"]
  end

  subgraph test_job["job: test"]
    matrix["Python 3.9–3.13 matrix"]
    ruff["ruff check src tests"]
    black["black --check src tests"]
    mypy["mypy src"]
    pytest["pytest + coverage.xml"]
    codecov["codecov upload (3.12 only)"]
    matrix --> ruff --> black --> mypy --> pytest --> codecov
  end

  subgraph validate_job["job: validate-okf"]
    init["hermes-okf init examples/example_bundle"]
    validate["hermes-okf validate --path examples/example_bundle"]
    init --> validate
  end

  subgraph publish_job["job: publish"]
    gate["needs: test + validate-okf success"]
    pypi["PyPI publish if version new"]
    gate --> pypi
  end

  triggers --> test_job
  triggers --> validate_job
  test_job --> publish_job
  validate_job --> publish_job
```

### `test` job

| Step | Command | Gate |
|------|---------|------|
| Install | `pip install -e ".[dev]"` + `types-PyYAML` | Must succeed |
| Lint | `ruff check src tests` | Must pass |
| Format | `black --check src tests` | Must pass |
| Types | `mypy src` | Must pass |
| Tests | `pytest -o addopts="" --cov=hermes_okf --cov-report=xml` | Must pass |

Runs across Python **3.9, 3.10, 3.11, 3.12, and 3.13** on `ubuntu-latest`.

### `validate-okf` job

Exercises the standalone CLI against a freshly initialized bundle:

```bash
hermes-okf init examples/example_bundle
hermes-okf validate --path examples/example_bundle
```

This job runs on Python 3.12 and must pass before publish.

### `publish` job

Runs only when:

- `test` and `validate-okf` both succeed, **and**
- Event is `push` to `main` **or** `workflow_dispatch`

Publish skips if the version in `src/hermes_okf/__init__.py` already exists on PyPI.

<Warning>
A green local `pytest` run does not replace CI. Always run `black --check`, `ruff check`, and `mypy src` before pushing—CI failures on formatting have shipped in past releases.
</Warning>

## Documentation requirements

Update docs when your change affects users or architecture:

| Change type | Update |
|-------------|--------|
| User-facing behavior, CLI flags, install steps | `README.md` |
| Design, module responsibilities, layer boundaries | `docs/ARCHITECTURE.md` |
| New integration surfaces or workflows | `examples/` (e.g. `basic_usage.py`, `full_agent.py`, `rag_integration.py`) |

`CONTRIBUTING.md` is the canonical short guide; this wiki page expands CI and tooling detail.

For layered module ownership when editing `docs/ARCHITECTURE.md`, the stack spans CLI surfaces, Hermes plugin layer, universal provider, core OKF layer, and filesystem persistence—see [Overview](/overview) for runtime assumptions.

## Submitting a pull request

<Steps>
<Step title="Run the local gate suite">

```bash
pytest
ruff check src tests && black --check src tests
mypy src
```

</Step>

<Step title="Push your branch and open a PR to main">

Write a description that explains **why** and **what**. Link related GitHub issues.

</Step>

<Step title="Wait for CI">

All matrix Python versions, `validate-okf`, and lint/type jobs must pass.

</Step>
</Steps>

### PR checklist

- [ ] Tests pass (`pytest`)
- [ ] Lint passes (`ruff check src tests`)
- [ ] Format passes (`black --check src tests`)
- [ ] Types pass (`mypy src`)
- [ ] User-facing docs updated (`README.md`, `examples/` as needed)
- [ ] Architecture docs updated for design changes (`docs/ARCHITECTURE.md`)
- [ ] New code has tests; coverage target >80% on additions

## Community

- **Bugs and feature requests**: Open a GitHub issue (templates under `.github/ISSUE_TEMPLATE/`)
- **Architectural proposals**: Start a discussion before large refactors
- **Questions**: Use issues with environment details (OS, Python version, `hermes-okf` version)

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
PyPI install targets, optional extras, and entry-point scripts for local and production environments.
</Card>
<Card title="Overview" href="/overview">
Exposed surfaces (standalone CLI, Hermes plugin, Python SDK) and the shortest path from install to first memory write.
</Card>
<Card title="OKF validation reference" href="/okf-validation-reference">
Rules enforced by `OKFValidator` and the `hermes-okf validate` command—directly exercised in CI.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery steps when install scripts, plugin discovery, or bundle paths fail during development.
</Card>
</CardGroup>

---