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

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