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

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

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