Skip to main content

py-agent-framework

PyPI version Python versions License CI

A stateful LLM agent framework built on pydantic-ai. It adds what Pydantic AI does not provide out of the box: session-level multi-turn persistence, context-window management, and a lifecycle extension system — with the smallest possible footprint.

Architecture inspired by Pi Agent — its extension-driven lifecycle, progressive-disclosure skills, and reload-by-reconstruction pattern. This is an independent implementation on pydantic-ai, not a port.

Why py-agent-framework?

Pydantic AI gives you Agent, Tool, and Hooks primitives. What it does not give you is memory across turns:

Bare pydantic-ai py-agent-framework
Multi-turn conversation ❌ you re-supply all history every call session_id in, history out
Long conversations ❌ you hand-roll truncation/summarization ✅ dual-watermark context management
Tool/session lifecycle hooks ⚠️ low-level Hooks only Extension protocol with 15 events
Storage ❌ none ✅ SQLite / PostgreSQL / stateless

If you were about to write your own "load history → truncate → call model → save" loop, that loop is this library.

Features

  • Session persistence — three backends for three stages of the lifecycle:
    • SingleTurnSessionManager — stateless, zero setup, every run is fresh.
    • LocalSessionManager — SQLite, file-based, local prototyping with full multi-turn memory.
    • PostgresSessionManager — production: connection pooling, JSONB, concurrent access.
    • Reconnect by passing session_id; the stored system prompt lets a prompt-less runner resume without re-supplying it.
  • Context management — dual watermarks keep long conversations inside the window: below the low watermark nothing happens; between watermarks old tool results are truncated; above the high watermark the distant past is summarized by an LLM. A single protect_turns value keeps the last N turns intact through both.
  • Extension system — a Protocol-defined lifecycle: observe and intercept every event (tool calls, token streams, compaction votes, persistence). Tools and skills are declared on AgentRunner; extensions own the lifecycle and may register SDK capabilities.
  • Tools & MCP — tools are plain pydantic-ai objects: Tool, raw callables, or MCPToolset with automatic multi-server disambiguation ({server}_{tool}), partial degradation when a server is down, and mandatory unique server names.
  • Skills — on-demand skill libraries via pydantic-ai harness Skills: the model sees names + descriptions, loads the full body only when needed.

Installation

Requires Python ≥ 3.11.

pip install py-agent-framework

Optional extras:

pip install "py-agent-framework[postgres]"   # PostgreSQL backend

Quick Start

import asyncio
from pydantic_ai.models.openai import OpenAIModel

from py_agent import AgentRunner

model = OpenAIModel("gpt-4o")   # any pydantic-ai model

runner = AgentRunner(
    model=model,
    system_prompt="You are a helpful assistant.",
)

async def main() -> None:
    result = await runner.run("Hello!")
    print(result.output)

asyncio.run(main())

By default the runner is stateless — every run() starts a fresh session. Pass a persistent session_manager to enable multi-turn memory.

Multi-turn Persistence

from py_agent import AgentRunner
from py_agent.session import LocalSessionManager

runner = AgentRunner(
    model=model,
    system_prompt="You are a helpful assistant.",
    session_manager=LocalSessionManager(db_path="sessions.db"),
)

r1 = await runner.run("Remember: the answer is 42.")
r2 = await runner.run("What did I ask you to remember?", session_id=r1.session_id)
# → "You asked me to remember that the answer is 42."

Swap the backend without touching the rest of your code:

from py_agent.session import PostgresSessionManager  # requires: pip install "py-agent-framework[postgres]"

session_manager = PostgresSessionManager(pg_url="postgres://user:pass@localhost/db")

Context Management

Long conversations stay inside your model's context window automatically:

  • Low watermark (default 0.6 × context_window_cap): old tool results are truncated.
  • High watermark (default 0.75 × context_window_cap): the distant past is summarized by an LLM (compaction runs in the background, so your turn is not blocked).
  • protect_turns (default 5) keeps the most recent turns intact through both.

Tune it per runner:

from py_agent import AgentRunner, ContextConfig, SummarizerConfig

runner = AgentRunner(
    model=model,
    system_prompt="You are a helpful assistant.",
    session_manager=LocalSessionManager(db_path="sessions.db"),
    context_config=ContextConfig(
        low_watermark_ratio=0.6,
        high_watermark_ratio=0.75,
        protect_turns=5,
        truncate_chars=1_000,
        context_window_cap=128_000,
    ),
    summarizer_config=SummarizerConfig(),  # defaults: reuse main model
)

Tools, MCP & Skills

from pydantic_ai import Tool
from pydantic_ai.mcp import MCPToolset
from pydantic_ai_harness.skills import Skills

from py_agent import AgentRunner
from py_agent.session import SingleTurnSessionManager


def web_search(q: str) -> str:
    """Search the web."""
    ...


runner = AgentRunner(
    model=model,
    system_prompt="You are a helpful assistant.",
    tools=[
        Tool(web_search),
        MCPToolset("http://search:8000/mcp", id="search"),  # server name required
    ],
    skills=Skills(["skills/"]),
    session_manager=SingleTurnSessionManager(),
)
  • Multi-server MCP tools are auto-prefixed ({server}_{tool}) so identical names never collide.
  • If an MCP server is down, it degrades gracefully — other servers keep working (customize via toolset_failure).

Extensions

Extension is a Protocol with a single hook: on_agent_runner_event(event, data). Subscribe to any of the 15 lifecycle events:

from py_agent import AgentRunner
from py_agent.types import AgentRunnerEvent, Extension


class Audit(Extension):
    async def on_agent_runner_event(self, event, data):
        if event == AgentRunnerEvent.TOOL_CALL:
            audit_log(data["tool_name"], data["args"])
        return None


runner = AgentRunner(..., extensions=[Audit()])

Selected events:

Event When Can modify?
BEFORE_AGENT_RUN before the model sees messages ✅ replace messages (injected messages are persisted)
TOOL_CALL before a tool runs {"block": true} or rewrite args
TOOL_RESULT after a tool runs ✅ rewrite content
TOKEN_STREAM each token chunk (run_stream)
SESSION_SAVE before persistence ✅ replace delta_messages
COMPACTION_TRIGGER compaction flagged {"cancel": true} to veto

Public API

from py_agent import AgentRunner, RunResult, ContextConfig, SummarizerConfig
from py_agent.session import SessionManager, SingleTurnSessionManager, LocalSessionManager, PostgresSessionManager
from py_agent.types import Extension, AgentRunnerEvent, MessageRole, ToolsetFailureHandler

AgentRunner also offers run_stream() — an async iterator yielding lifecycle events and token chunks as they happen, ending with run_end.

Development

Contributors: see CONTRIBUTING.md for setup and the make workflow (make bootstrapmake check).

Contributing

This is a personal project and not yet ready for external contributors. Bug reports and feature discussions via issues are welcome. If you would still like to submit a change, see CONTRIBUTING.md — PRs are required and the project's standards are enforced.

License

MIT © Shunji Wan

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_agent_framework-0.1.0.tar.gz (31.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_agent_framework-0.1.0-py3-none-any.whl (41.5 kB view details)

Uploaded Python 3

File details

Details for the file py_agent_framework-0.1.0.tar.gz.

File metadata

  • Download URL: py_agent_framework-0.1.0.tar.gz
  • Upload date:
  • Size: 31.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for py_agent_framework-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6fec6c5d0659ccd30d40eeabf5780de4e5f39ce62362ef210a04526c47f0d92f
MD5 1d87a67d056be3245005623edbd22d4c
BLAKE2b-256 303cd3dee1c8bfabf3ad728fa53abbf9667d297f9014b95f3dc413f7b4216c63

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_agent_framework-0.1.0.tar.gz:

Publisher: publish.yml on sjwan01/py-agent-framework

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_agent_framework-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for py_agent_framework-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f42694eefec33a72251c9cfc6ec5af23decbe77f5e1168eea5be0d4edeb46817
MD5 ea03cb0c226b3134b35a738ee3e7f8c8
BLAKE2b-256 a4497bfd9d627621531591ad565705e64b66ca35dd299e425a150e05f5ee1e65

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_agent_framework-0.1.0-py3-none-any.whl:

Publisher: publish.yml on sjwan01/py-agent-framework

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page