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
- 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.
- 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.
- 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.
- 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/`).
- Knowledge graphImplicit directed edges from markdown links, tag clustering, directory hierarchy, BFS traversal, neighbor/backlink queries, and optional NetworkX export via `GraphExtractor`.
- 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.
- 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.
- 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.
- 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.
- 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`.
- 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.
- 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 <subcommand>
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>
---