Files
zeekay 0e9c5a94da a persona is filed in the brain, so every runtime carries the same one
@hanzo/persona already owns what a persona IS — the JSON Schema and the
PERSONA.md frontmatter convention. This pack does not restate it: it loads the
profile, checks it against that schema, and files it in the store.

Filing it in the store is the point. brain.db is read from TS, Python, Rust and
Go, so a persona written once is the same persona in all of them. The
alternative is each runtime loading its own copy of a file and drifting, which
is the duplication the brain exists to end.

Stored twice, deliberately, because they answer different questions: as a PAGE
(the document, for search and for a human) and as FACTS, one row per trait, so
recall("persona/<id>") answers "who am I" without parsing markdown. Nested
traits become dotted predicates; an array is joined rather than exploded,
because a persona's tags is one answer and not five rows.

A half-written profile still loads — a missing required field is NAMED in a
warning, never thrown, since a persona you are still writing should not stop a
brain from opening. No PERSONA.md at all is the ordinary case and returns null.
Soft contracts throughout, like every other pack here.
2026-08-21 07:00:32 -07:00

2.5 KiB

@hanzo/bot-persona

The agent's persona, attached to the brain.

A persona is who an agent is — its name, its OCEAN traits, its technical preferences, the way it talks. @hanzo/persona owns that shape: the JSON Schema and the PERSONA.md frontmatter convention. This package does not restate it. It loads the profile, checks it against that schema, and files it in the brain store.

Filing it in the store is the point. brain.db is read by every Hanzo runtime — TS, Python, Rust, Go — so a persona written once is the same persona in all of them. The alternative is each runtime loading its own copy of a file and drifting, which is what the brain exists to end.

Use

It comes with the meta-pack; you do not register it separately.

import registerBrain from "@hanzo/bot-brain";

const { persona } = await registerBrain(api, {
  persona: { path: "~/.hanzo/workspace/PERSONA.md" },  // the default
});

persona?.profile;        // the loaded profile, or null
await persona?.reload(); // re-read after an edit

persona: false disables it.

What it writes

The profile is stored twice, because they answer different questions:

as where answers
a page persona/<id> the document — for search, and for a human to read
facts one row per trait "who am I", without parsing markdown

Nested traits become dotted predicates, so a runtime can ask for one without knowing the document's shape:

await store.recall("persona/ada");
// → { predicate: "ocean.openness", object: "0.9", … }
// → { predicate: "tags", object: "math, engines", … }

An array is joined rather than exploded: a persona's tags is one answer, not five rows.

A PERSONA.md

---
id: ada
name: Ada
ocean:
  openness: 0.9
tags: [math, engines]
---

I build things, and I check them before I say they work.

Behaviour worth knowing

  • No PERSONA.md is normal, not a failure — the pack returns profile: null and stays inert.
  • A half-written profile still loads. A missing required field is named in a warning, never thrown: a persona you are still writing should not stop a brain from opening.
  • A body with no frontmatter is a persona written as prose, and is kept.
  • Everything is a soft contract: where the host exposes no store or no tool registry, the pack is inert for that surface rather than throwing.

Tool

Registers brain.persona — reads the profile — so an MCP-native agent can ask who it is.