persona-core
One YAML file describes a persona. This library turns it into a running agent with typed memory, real tools, and a model tier picked per turn.
License: MIT. Free for any use, including commercial.
persona-core is the foundation of Open Persona: the
openly licensed engine every other package builds on, and the one package that
depends on nothing else in the project.
What it is
Four typed stores, versioned and never overwritten; one graph per account; recall that brings back what the turn needs; forget that reaches every layer and every version.
A persona is a single typed YAML document: identity, constraints, self facts,
worldview claims with epistemic tags, tools, skills, routing preferences.
persona-core reads that document and gives you an agent you can drive from
Python or from the terminal. It ships:
- the persona schema, validator, and registry (frozen Pydantic v2 boundary
models,
extra="forbid", deterministic chunk IDs); - four typed memory stores (identity / self_facts / worldview / episodic)
behind a
MemoryStoreprotocol, with a file based Chroma backend (the default, zero infra) and a Postgres + pgvector backend (hosted); - a model backend layer behind a
ChatBackendprotocol: Anthropic, OpenAI, DeepSeek, Groq, Together, NVIDIA, Cloudflare, OpenRouter (native tool calls), plus local Ollama and local Hugging Face (prompt shim fallback); - a sandboxed tool layer (
Toolbox, an MCP client, a known tool catalog, and built in tools) and a skills layer (SkillScanner+SkillInjector+ composition + askills.tomlcatalog + built in skill packs); - an image generation layer, vision input, document ingestion and
generation, a code execution sandbox protocol, an
AuditLoggerprotocol with a JSONL default, per component loguru logging, and thepersonaCLI; - a durable job contract (
persona.jobs): the job model and state machine, frozen payloads, lease and retry policies, and a typed handler registry that the hosted worker composes (the queue and worker live inpersona-api). - a schedule contract (
persona.schedules): the frozen schedule entity (RRULE class recurrence or a one time future, with the user's IANA timezone), the pure DST correct next fire computation (spring forward gap → adjusted instant; fall back fold → fire once), the missed fire policy decision, and theschedule_id + fire_timeidempotency key and handoff contract (the durable store and the single leader tick live inpersona-api). - an initiative contract (
persona.initiative): the restraint first policy core for a persona that notices and acts unprompted. The frozen, source taggedInitiativeCandidate(at least one grounding citation, a CLOSED trigger catalogue, a required concrete next step), the act within envelope versus propose at gates decision (fail closed: borderline proposes), the pure restraint policy (value threshold plus an acceptance SUPPRESSOR, per persona and per user cadence caps over trailing windows, quiet hours absolute delivery resolution, hold staleness), the per persona dial (off / propose_only / act_within_envelope, default propose only), and deterministic duplicate suppression arbitration (the scan, pipeline, and durable stores live inpersona-runtimeandpersona-api). - a task contract (
persona.tasks): the durable entity above runs. The frozenTaskCheckpoint(conclusions, intent, pointers, size bounded, never transcripts) plus theTaskstate machine (defined → active → waiting(…) → … → completed | failed | cancelled), the cost ledger, the monotonic checkpoint sequence idempotency anchor, the A4 authoredContract(goal, scope, acceptance criteria, permission matrix, and the agreedDeliverableshape), the pure context reconstruction ordering (contract, method, checkpoint, recent legs, retrieval, trigger, recite), the per leg findings ledger (the queries run and sources seen, capped and folded like every other accumulating field), the leg box, the resume trigger seam, and the outcome reports (the leg executor and the durable stores live inpersona-runtimeandpersona-api).
Install
pip install persona-core # core + Chroma + frontier provider SDKs
pip install persona-core[local] # + torch / transformers for local HF inference
pip install persona-core[postgres] # + psycopg + pgvector for the Postgres backend
pip install persona-core[sandbox] # + docker SDK for the LocalDockerSandbox
pip install persona-core[turbovec] # + turbovec for the optional quantized graph index
Python 3.11 or newer. For workspace development from the monorepo:
git clone https://github.com/yasinhessnawi1/Open-Personas-ai.git
cd Open-Personas-ai
uv sync --all-packages
Quickstart
Author a persona and talk to it from the terminal. No API, no web app:
persona init # interactive → a persona.yaml
persona validate examples/astrid_tenancy_law.yaml
export PERSONA_PROVIDER=groq # any of the ten providers
export PERSONA_MODEL=llama-3.3-70b-versatile
export PERSONA_API_KEY=<your-key>
persona chat examples/astrid_tenancy_law.yaml # local REPL chat
persona audit examples/astrid_tenancy_law.yaml # tail the JSONL audit log
Examples
examples/ ships five personas and four runnable scripts. Every
script except 03 runs without a model key, and each one is verified end to
end.
| Persona | What it shows |
|---|---|
astrid_tenancy_law.yaml |
A domain expert: Norwegian tenancy law, tools and skills wired, nb default language, honesty constraints. |
kai_research.yaml |
A research assistant. |
maren_writing_coach.yaml |
A tool free coach, where everything is voice and judgement. |
viggo_code_reviewer.yaml |
A senior code reviewer: sandboxed execution, diffs, severity ordered constraints. |
embla_storyteller.yaml |
A folklore bedtime storyteller. Worldview rich, deliberately tool free. |
| Script | What it shows | Key needed |
|---|---|---|
01_load_and_inspect.py |
A persona is typed data: identity, constraints, confidence tagged self facts, epistemic tagged worldview. | no |
02_typed_memory.py |
Write, semantic query, version, history(), rollback() on a real store, with the audit trail. |
no |
03_chat_backend.py |
One streamed in character turn. The same code runs on any of the ten providers via env vars. | yes |
04_toolbox.py |
Compose the persona's toolbox, dispatch real tools, and watch the allow list refuse an ungranted one. | no |
cd packages/core/examples
uv run python 02_typed_memory.py
v1 written: 'Prefers espresso; drinks tea only when it rains.'
query hit: 'Prefers espresso; drinks tea only when it rains.'
v2 written: 'Switched to oat-milk cortados; espresso demoted to deadline fuel.'
history (2 versions):
v1 (superseded) 'Prefers espresso; drinks tea only when it rains.'
v2 (current) 'Switched to oat-milk cortados; espresso demoted to deadline fuel.'
after rollback, current: 'Prefers espresso; drinks tea only when it rains.'
Usage
One streamed, in character turn. The provider is chosen entirely by env vars
(PERSONA_PROVIDER / PERSONA_MODEL / PERSONA_API_KEY):
import asyncio
from datetime import UTC, datetime
from pathlib import Path
from persona.backends import BackendConfig, load_backend
from persona.schema.conversation import ConversationMessage
from persona.schema.persona import Persona
async def main() -> None:
persona = Persona.from_yaml(Path("examples/astrid_tenancy_law.yaml"))
backend = load_backend(BackendConfig())
system = f"You are {persona.identity.name}, {persona.identity.role}."
now = datetime.now(UTC)
async for chunk in backend.chat_stream(messages=[
ConversationMessage(role="system", content=system, created_at=now),
ConversationMessage(role="user", content="Hva sier husleieloven om mugg?", created_at=now),
]):
if chunk.delta:
print(chunk.delta, end="", flush=True)
if chunk.is_final:
break
asyncio.run(main())
For the full conversation loop (router, tool dispatch, episodic write back, per
turn logging), compose persona-core with
persona-runtime.
Capabilities
- Typed memory, versioned. Identity is immutable at runtime. self_facts,
worldview, and episodic are append only with
history()androllback(). Every write is tagged with its source (system/user/persona_self) under a per store update policy, with a SHA-256content_hashand exactly oneAuditEventper mutation. - What the store promises, and what it does not. A versioned write is one write:
kill the process in the middle of it and you get the old version or the new one,
never a chain broken between them. That is measured against a real killed process on
both transports rather than inferred, on chromadb 1.5.9 and on Postgres in one
transaction, and it is a promise about a process dying, not about power loss or a
lost disk flush. A forget removes every version of what it is given, not just the
current one. A chain damaged by an older release is repaired by
persona repair, which moves version pointers only, so it never re-embeds and needs no model loaded. One thing it does not promise yet: writes are not serialised across processes, so two processes writing the same fact at the same instant can both win, and the repair is what puts that right. - Episodic memory is a multi resolution pyramid. Raw chunks are kept forever
(text plus embedding, because summaries never replace evidence), and a background
engine builds gists above them with drill down pointers back to the untouched
originals. Decay is usage reinforced (
R = exp(−Δt/(τ₀·strength)), recall reinforces viaEpisodicStore.reinforce), pinned or important memories never compress, and old memory is down rankable but never rank dead. Tunables live inPERSONA_EPISODIC_*(persona.stores.lifecycle.EpisodicSettings). - Ten model providers behind one protocol, with native tool calls for
Anthropic, OpenAI, DeepSeek, Groq, Together, NVIDIA, Cloudflare, and OpenRouter,
plus a prompt shim fallback for local Ollama and HF. Embeddings via
bge-small-en-v1.5(384 dim), recorded in the schema for re-index safety. - What a write costs, and how to pay less. The first write loads the embedding model,
which on a cold cache is around a hundred and forty packages and roughly a hundred seconds,
inside that write. If you only ever write memories and read them back by id, you can skip
it:
PERSONA_EMBEDDER=hash(or injectingHashEmbedder()yourself) starts instantly and writes real, stable, normalised vectors. The catch is worth saying twice, because nothing will error when you hit it: similarity search stops working.querystill returns chunks, they are just arbitrary ones, and so is anything built on recall. Writing, reading by id,recent,history,rollbackand forget are all exact either way. The CLI prints a notice whenever the hash embedder is in use, so nobody discovers this from bad recall three weeks later. - Tools. Built ins include
web_search,web_fetch, sandboxedfile_readandfile_write(the path resolver rejects.., absolute paths, symlink escape, NUL bytes, mixed separators),calculator(safe AST eval),datetime,currency_convert,regex_match(RE2, immune to ReDoS),json_query(JMESPath),text_diff,text_summarize, andrender_diagram. ATOOL_CATALOGenumerates the full set for persona driven tool selection. - MCP. A Streamable HTTP MCP client and adapter, plus built in MCP servers
(
time/calculator/filesystem/weather) as thin FastMCP subprocesses, indexed by a declarativemcp_catalog.toml. - Skills. Four built in packs:
web_research,data_analysis,document_generation(one parameterized skill spanning docx, pdf, pptx, xlsx, md, txt), andcode_review. Injection is budgeted at 2k tokens (SkillInjector.TOKEN_BUDGET), composition goes three deep (cycle detection plus a shared budget),collection:refs resolve, and an alias shim keeps deprecated skill names working. - Skill injection trust. Skills are prompt content the persona follows, so every
skill, built in or an untrusted external
SKILL.md, is injected through a subordination guard (persona.skills.guard): a nonce delimited, tier labelled envelope under a scope don't suppress authority preamble. Skill content can guide how the persona works but structurally cannot override its identity, the platform rules, the prompt's confidentiality, or its loyalties. Every skill carries a trust tier (SkillTrust: builtin / vetted / community / third_party, always source assigned, never self declared) and provenance (sha256content_hash). Activating a skill abovevettedis consent gated (SkillConsentPort, default deny), and every injection, plus every consent refusal, emits anAuditEvent. This is defense in depth: structurally subordinated, tiered, consented, audited. It is not immunity (seeDEFENSE_CLAIM). - Image generation (OpenAI gpt-image-1, fal.ai Flux 1.1 [pro]) with a three
layer safety filter plus a categorical hard line, and
craft_avatar_prompt, a deterministic, demographically safe avatar prompt crafter. - Vision, documents, sandbox.
ImageContentvision input, document ingestion and generation, and aCodeSandboxprotocol with aLocalDockerSandboxreference implementation. - Knowledge graph (
persona.graph). A user scoped bigger brain that all of a user's personas read from and write to: concept nodes connected by typed links (semantic, entity, temporal, causal), kept coherent by canonical entity resolution (deterministic, no LLM) plus accumulate via merge, with Postgres as the source of truth and an optional turbovec quantized in RAM dense index (pgvector is the default; 4-bit with mandatory exact rerank). RLS isolated per user, configured viaPERSONA_GRAPH_*env vars. Hybrid retrieval (HybridRetriever) fuses the dense semantic leg and the sparse BM25/FTS leg via reciprocal rank fusion, in parallel and never gated, with bounded type aware traversal and an allowlist seam for user scope and wellbeing subtraction. This is the foundation of the K track: write paths, graph aware prompts, wellbeing, graph UI. - Write path contracts (
persona.extraction,persona.wellbeing). The frozen, LLM free shapes the graph's two feeders produce: the groundedExtractionCandidate(a verbatim evidence span is required, so no quotable basis means no candidate), theExtractorandEntityRecognizerports, and the sharedWellbeingCategoryvocabulary tagged at write time. The LLM extraction pipeline that fills them lives in the runtime.
Architecture role
persona-core is the bottom layer of the Open Persona stack and the source
available foundation. A persona is a YAML document; the schema, the typed memory
stores, the model provider adapters, and the tool and skill machinery all live
here. persona-runtime composes the orchestration loop on
top, persona-api exposes it over HTTP, and persona-web is the browser front
end. The dependency arrow points one way: this library imports nothing from the
upper layers.
It is also where the community edition does its persistence. The file based
Chroma backend holds typed memory locally with zero infrastructure. The Postgres
and pgvector backend is the same MemoryStore interface, swapped in for the cloud
edition.
Test
uv run pytest packages/core # unit + contract (default)
uv run pytest packages/core -m integration # needs Postgres in Docker
uv run mypy packages/core/src --strict
uv run ruff check packages/core
License
persona-core is licensed under the MIT License, free for any use including
commercial. See LICENSE. The application layer of Open Persona
(persona-api, persona-web) is separately licensed PolyForm Noncommercial
1.0.0; see the root README for the full per package table.
Links
- Open Persona root README
persona-runtime, the conversation and agentic enginepersona-voice, the real time voice trunk- CHANGELOG
Release files for persona-core 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| persona_core-1.2.1.tar.gz | 2.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| persona_core-1.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.5 MB
Release files / persona_core-1.2.1.tar.gz
| Download URL | persona_core-1.2.1.tar.gz |
|---|---|
| Size | 2.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ae2ac92d4f389d80726758875aae3128c9e9935c02ff84166d5c6ae01925093c
|
|
BLAKE2b-256 checksum How to use checksums |
65f824fd94b5b51795dd65866c921c2ce1ec9be8d0d64905ff20829527474ea1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.9
|
Release files / persona_core-1.2.1-py3-none-any.whl
| Download URL | persona_core-1.2.1-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d512307253c355ebb797070dbcb176e9248482071ef5b4ec128a0c5bf19e8fcc
|
|
BLAKE2b-256 checksum How to use checksums |
152f2157c0c9e28ebe51f0b115e0316fc8deb27126e2917d249790929b62c768
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.9
|