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

- Repository: EliaszDev/hermes-okf
- GitHub: https://github.com/EliaszDev/hermes-okf
- Human docs: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02
- Complete Markdown: https://www.grok-wiki.com/public/docs/eliaszdev-hermes-okf-b71befaafe02/llms-full.txt

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