persona-runtime
The conversation and agentic engine for Open Persona: the turn loop, the prompt builder, the router, and the plan, act, reflect cycle.
License: MIT. Free for any use, including commercial.
persona-runtime is the orchestration layer of Open Persona.
It turns a persona-core persona into a running
conversational agent, and it depends only on persona-core. No HTTP, no database,
no secrets.
What it is
The one turn the runtime owns, from arrival to the writes that happen after the reply.
The runtime owns the per turn lifecycle and the agentic loop, and nothing else. Every collaborator (the persona registry, the model tiers, the toolbox, the conversation object) is injected by the composition root: the API in production, the CLI for local use, the tests in CI. The loop itself is stateless per request.
ConversationLoop, the one turn keystone. Retrieve typed memory context, manage history (summarise and compact at K=10, keep the last 5 turns verbatim), build the prompt, route, stream generate with a tool call sub-loop, and write the turn back to the episodic store.PromptBuilderandRetrievedContext. Assembles the system prompt from identity, constraints, retrieved chunks, and the skill index, with a context window budget reducer. It also renders the graph knowledge block: an additive, independent source of what is known about the user, drawn from the shared knowledge graph, relevance gated and budgeted alongside the persona's own memory, with a versioned usage guidance artifact and a wellbeing care slot.retrieve_context, the per turn conditioning retrieval (identity viaget_all, the rest viaquery). It was extracted so the voice trunk shares the same conditioning instead of reimplementing it. Optionally enriched with an owner scoped graph retrieval (graph_selection.make_graph_retrieval), queried independently of the persona stores.- Routing, a deliberate surface to tier policy (
routing/policy.py).PolicyRoutersits behind theRouterProtocol and resolves each surface's stated tier: chat, authoring, and agentic get frontier, voice gets the latency tier, background gets small, recognition gets mid. A persona's pinnedtier_for_generationstill wins, because a deliberate override is honored. Layer-1 capability constraints such as vision still filter first. The earlier machinery is retained dormant:HeuristicRouter(the original rules),UnifiedRouter(constraint filter plus sweet spot scoring), and theIntelligentRoutermodel within tier scorer, the last of which is gated globally byPERSONA_ROUTING_INTELLIGENT_ENABLED(default off; repopulate model metadata before re-enabling). TierRegistry, a lazy cached backend registry per tier (frontier/mid/small), configured viaPERSONA_{TIER}_*env triples, with small to mid to frontier fallback and cross provider multi model per tier.AgenticLoop, the plan, act, reflect cycle. One model decides at each step whether to call a tool, ask the user, or produce a final answer, with step history compaction at the tier budget, a cancel token boundary, and an authoritative terminal status (completed/max_steps_reached/cancelled/error/awaiting_user). Two deterministic guards keep a long run from paying twice for the same answer: a per run call ledger refuses a repeat of a call that failed (handing the model the original error and telling it to change something) and serves a repeat of an allowlisted read from what the run already has, and a cost keyed tool result pruner trims output older than the last two steps to a readable head once a step's context passes a ceiling. Both report themselves on the step trace.persona_runtime.legs, the leg executor for the autonomous task model. One leg is one bounded run of the unmodifiedAgenticLoop, book-ended by context reconstruction and a checkpoint write. It enforces the leg box (a wall clock trip at a step boundary, never mid step), writes the checkpoint through a sink port (the api's compare and set append), and distils episodic memory at milestone granularity rather than spamming per leg. The checkpoint writer is async and comes in two: the token boundedCompactingCheckpointWriter, which cannot reason and so writes no plan, and the model backedSemanticCheckpointWriter, which reads the leg and returns merged conclusions, lessons, a plan and one next action. The deterministic one is always underneath: any timeout, refusal, malformed answer or over budget distillation delegates to it, so a leg never loses its checkpoint. A leg also carries what it already asked (the queries run and sources read), reads its own recent legs and memory before working, and records its measured shape (steps, tool calls, distinct questions, tokens, wall clock) beside its spend.- Safety, in the loop itself. Character adherence is a never break rule with researched carve-outs: the persona never claims to be human when sincerely asked, and never roleplays through a wellbeing signal. The turn time crisis gate takes the persona out of the loop entirely on an acute, explicit signal, backed by a trained encoder for euphemistic and non English phrasing, with documented limits. Explicit acute is the reliability claim; subtle phrasing is a named residual, not a solved problem.
TurnLogplusJSONLTurnLogWriterandMemoryTurnLogWriter, the per turn telemetry (model, tokens, cost, routing decision, latency, fallback), durable to JSONL or held in memory for tests.persona_runtime.extraction, the LLM half of the knowledge graph write paths: the grounded extraction pipeline (versioned prompt, one model call, grounded and restrained candidates), entity resolution with the AMBIGUOUS band judge, theSynthesizer(the off critical path reflection assembly), and the on by defaultrecord_user_factdirect write tool. It feeds the core graph's one merge.
Install
pip install persona-runtime # pulls in persona-core
Python 3.11 or newer. For workspace development from the monorepo:
git clone https://github.com/yasinhessnawi1/Open-Personas-ai.git
cd Open-Persona
uv sync --all-packages
Quickstart
persona-runtime is a library with no CLI of its own. Compose it on top of
persona-core:
import asyncio
from pathlib import Path
from persona.schema.persona import Persona
from persona.schema.conversation import Conversation, ConversationMessage
from persona.registry import PersonaRegistry
from persona.stores.chroma import ChromaMemoryStore
from persona.tools.toolbox import Toolbox
from persona_runtime import (
ConversationLoop, PromptBuilder, Router, tier_registry_from_env,
)
async def main() -> None:
persona = Persona.from_yaml(Path("examples/astrid_tenancy_law.yaml"))
registry = PersonaRegistry(store=ChromaMemoryStore.local("./.persona-data"))
registry.load(persona)
tiers = tier_registry_from_env()
loop = ConversationLoop(
registry=registry,
tiers=tiers,
router=Router(),
prompt_builder=PromptBuilder(),
toolbox=Toolbox.empty(),
)
conversation = Conversation.new(persona_id=persona.id)
user = ConversationMessage(role="user", content="Hva sier husleieloven om mugg?", created_at=None)
async for chunk in loop.turn(conversation, user):
print(chunk.delta, end="", flush=True)
await tiers.aclose()
asyncio.run(main())
Configuration
Each tier is configured by an env triple (see .env.example at the repo root):
PERSONA_FRONTIER_PROVIDER=anthropic PERSONA_FRONTIER_MODEL=claude-opus-...
PERSONA_MID_PROVIDER=deepseek PERSONA_MID_MODEL=deepseek-chat
PERSONA_SMALL_PROVIDER=groq PERSONA_SMALL_MODEL=llama-...
A single PERSONA_PROVIDER plus PERSONA_MODEL plus PERSONA_API_KEY triple is
the fallback when no per tier vars are set.
Architecture role
persona-runtime sits directly above persona-core and below
persona-api. The API composes the runtime, attaches it to HTTP routes, and
persists the per request state (conversation, run, turn log, event bus). The
runtime contains zero HTTP, zero database clients, zero secrets. The voice trunk
(persona-voice) reuses the runtime's reply producer, so a
voice turn is conditioned and routed exactly like a text turn.
Test
uv run pytest packages/runtime # unit (default)
uv run pytest packages/runtime -m integration # integration
uv run mypy packages/runtime/src
uv run ruff check packages/runtime
License
persona-runtime 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-core, the schema, memory stores, backends, toolspersona-voice, the real time voice trunk- CHANGELOG
Release files for persona-runtime 1.2.0
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_runtime-1.2.0.tar.gz | 745.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| persona_runtime-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / persona_runtime-1.2.0.tar.gz
| Download URL | persona_runtime-1.2.0.tar.gz |
|---|---|
| Size | 745.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d5258f166a6c79a37b93a0634a4f24053330932a8ad579c0d560222c3847082a
|
|
BLAKE2b-256 checksum How to use checksums |
a870ca45c66828d88bda1194172ec5b6bd7c158644e6c3997d423d664bdf683d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.9
|
Release files / persona_runtime-1.2.0-py3-none-any.whl
| Download URL | persona_runtime-1.2.0-py3-none-any.whl |
|---|---|
| Size | 401.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6c00f4ed98ba39828a0fdd2411791cfb0c20b828e8798f053e5ced390b161324
|
|
BLAKE2b-256 checksum How to use checksums |
716e99e304d7609f9bbdeb779fd6dc20a571eb710751ea6c72aee61fdf5d77d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.9
|