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

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