Metadata-Version: 2.4
Name: persona-runtime
Version: 0.1.0
Summary: Generation loop, router, and agentic engine for persona-core.
Project-URL: Homepage, https://github.com/yasinhessnawi1/Open-Persona
Project-URL: Repository, https://github.com/yasinhessnawi1/Open-Persona
Project-URL: Issues, https://github.com/yasinhessnawi1/Open-Persona/issues
Project-URL: Changelog, https://github.com/yasinhessnawi1/Open-Persona/blob/main/CHANGELOG.md
Author-email: Yasin Hessnawi <yasinhessnawi1@gmail.com>
License-Expression: PolyForm-Noncommercial-1.0.0
License-File: LICENSE
Keywords: agent,agentic,ai,llm,persona,router,tool-use
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: persona-core
Requires-Dist: tiktoken<1,>=0.7
Description-Content-Type: text/markdown

# persona-runtime

> Conversation loop, prompt builder, router, and agentic engine for Persona.
> Source-available; noncommercial use only.

**Status:** PolyForm Noncommercial 1.0.0 · Source Available (Noncommercial Use Only)

## What it is

`persona-runtime` is the orchestration layer that turns a `persona-core`
persona into a running conversational agent. It owns six things and nothing
else:

- **`ConversationLoop`** — the one-turn keystone: retrieve typed-memory
  context, manage history (summarise-and-compact at K=10, keep last 5
  verbatim), build the prompt, route, stream-generate with a tool-call
  sub-loop, write the turn back to the episodic store.
- **`PromptBuilder`** + `RetrievedContext` — assembles the system prompt
  from identity + constraints + retrieved chunks + skill index, with a
  context-window budget reducer.
- **Routing** — `Router` (`@runtime_checkable` Protocol) with two concrete
  implementations: `HeuristicRouter` (rule-based, per-turn,
  per-persona-overridable) and `UnifiedRouter` (two-layer: hard-filter via
  `apply_constraint_filter` then sweet-spot scoring, with bounded fallback
  and per-tier metadata).
- **`TierRegistry`** — lazy-cached backend registry per tier
  (`frontier` / `mid` / `small`); configured via `PERSONA_{TIER}_*`
  env triples; small→mid→frontier fallback; cross-provider multi-model
  per tier (Spec 20).
- **`AgenticLoop`** — the plan-act-reflect cycle in `persona_runtime.agentic`:
  one model decides at each step whether to call a tool, ask the user, or
  produce a final answer; `[ASK_USER]` / `[FINAL]` markers as the primary
  classification signal; step-history compaction at the tier budget;
  cancel-token boundary; terminal status (`completed` /
  `max_steps_reached` / `cancelled` / `error`) authoritative.
- **`TurnLog`** + `JSONLTurnLogWriter` / `MemoryTurnLogWriter` — per-turn
  telemetry record (model, tokens, cost, routing decision, latency,
  fallback) durable to JSONL or held in memory for tests.

The runtime depends only on `persona-core`; it does not depend on the API
or web app. The composition root (the API in production, the CLI for
local use, the tests in CI) owns the `Conversation` object and the
`TierRegistry` lifecycle — the loop itself is stateless per request.

## Install

From PyPI (planned):

```bash
pip install persona-runtime
```

Workspace development:

```bash
git clone https://github.com/yasinhessnawi1/Open-Persona.git
cd open-persona
uv sync --all-packages
```

## Run

`persona-runtime` is a library — no CLI of its own. Compose it on top of
`persona-core`:

```python
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, TurnLog, 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())
```

Env vars (per tier; 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` + `PERSONA_MODEL` + `PERSONA_API_KEY` pair is
the fallback when no per-tier vars are set.

## Test

```bash
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
```

## Architecture role

`persona-runtime` is layer 3 of the Open Persona stack. It 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 client, zero secrets — every collaborator is injected by the
composition root.

## Contribute

Contributions welcome under the same PolyForm Noncommercial 1.0.0 license.
The package is source-available for noncommercial use; commercial use
requires a separate license — contact the rights holder. Issues and pull
requests welcome at
[github.com/yasinhessnawi1/Open-Persona](https://github.com/yasinhessnawi1/Open-Persona).
See [CHANGELOG.md](CHANGELOG.md) for the spec-by-spec history.
