Skip to main content

summonpot

SummonPot

CI PyPI version Python versions License: MIT

One API framework for deterministic and agentic endpoints.

summonpot brings traditional exact API execution and bounded agent reasoning under one endpoint contract. You declare the request model, response model, fixed goal, and exact capabilities. When one complete legal path exists, the target runtime executes it deterministically. When a real choice remains, an agent may choose and order only the declared operations.

The public API stays the same in both modes: no separate handler implementation, agent graph, caller-provided action, or framework selector. The endpoint declaration defines what must happen; request JSON carries business data; summonpot chooses the least-powerful sufficient execution path.

The function signature is the endpoint. Its request model, docstring goal, declared capabilities, and response type form the complete executable contract. You do not write orchestration or business logic under the endpoint:

request model
+ fixed goal in the docstring
+ Depends(...) / Required(...) capabilities
+ response model
= executable endpoint

function body
= raise NotImplementedError

Summonpot inspects the declaration and never calls the decorated function body. Deterministic business logic lives inside the exact application-owned capabilities, not inside a hidden handler.

Current status: Pydantic contracts, provider-neutral agent execution, closed capabilities, and runtime-enforced required operations are shipped. Automatic deterministic endpoint execution and SQLAlchemy/SQLite capability adapters are the next implementation milestones.

You define routes with Pydantic request and response models, a docstring, and exact deterministic capabilities. The framework owns validation, capability orchestration, structured output, and the agent loop when one is needed. You define an endpoint. Summonpot decides whether it needs reasoning.

Endpoint outcome Execution path
Traditional exact API behavior with one legal path Deterministic, without an LLM (planned compiler)
Several valid declared paths require a bounded choice Agentic, using only declared capabilities
No declared legal path Typed deterministic error (planned compiler)
from my_service.operations import record_research, search_web
from pydantic import BaseModel, Field
from summonpot import Depends, Pot, Required


class ResearchRequest(BaseModel):
    query: str = Field(min_length=3)
    depth: int = Field(default=3, ge=1, le=5)


class ResearchResponse(BaseModel):
    summary: str
    key_findings: list[str]
    sources: list[str]


pot = Pot("my-service")


@pot.summon("/research")
def research_topic(
    request: ResearchRequest,
    sources=Depends(search_web),
    receipt=Required(record_research),
) -> ResearchResponse:
    """Research this topic thoroughly and return a sourced report."""
    raise NotImplementedError


pot.serve()

Call it like any API:

curl -X POST http://localhost:8000/research \
  -H "Content-Type: application/json" \
  -d '{"query": "quantum computing", "depth": 5}'

The imported functions are real, application-owned operations: search_web must query the approved source service, and record_research must perform the actual write. Summonpot does not replace their implementations with generated behavior. It exposes only those exact operations to the endpoint agent, rejects a final response until record_research has completed, validates the Pydantic output locally, and never executes the decorated function body.

Why another framework?

Every existing approach to building agentic APIs has the same problem: you first learn an agent framework (LangChain, CrewAI, AutoGen), then bolt an HTTP server on top. The mental model is "configure an agent" — which is complex, brittle, and framework-y.

summonpot flips this: the web framework IS the agent framework. The routing is the agentic logic. The decorator is the incantation. The framework owns the smart parts.

Existing frameworks summonpot
Mental model "Configure an agent" "Define an endpoint"
Surface area Large (chains, agents, tools, memory, callbacks...) Tiny (decorator + types + docstring)
API exposure Bolt-on HTTP wrapper Native (routing IS the agent)
Complexity User manages the loop Framework owns the loop, user provides intent
Testability Heavy mocking required Test like a regular HTTP endpoint
Onboarding Learn the framework's ontology If you know HTTP, you know this

Project status

The current foundation includes Pydantic request and response contracts, provider-neutral model selection, declarative optional and mandatory capabilities, runtime-enforced required use, HTTP/OpenAPI generation, and local output validation.

Next milestones focus on typed operation inputs and outputs, strict SQLAlchemy and SQLite statement capabilities, deterministic-versus-agentic execution selection, proof-backed write receipts, stable error semantics, and optional larger execution harnesses. See the roadmap for scope and ordering.

Deterministic and agentic execution

A summonpot endpoint does not need a separate decorator or caller-provided action to become deterministic or agentic. It always declares the same four things:

request model
+ fixed endpoint goal
+ exact capabilities
+ response model

Summonpot's target execution compiler will choose the mode for each validated request:

Resolved contract Execution
One complete legal operation path Deterministic
A bounded choice remains Agentic
No legal path exists Typed deterministic error

The capabilities themselves remain deterministic in both modes. Agentic execution means the model chooses or orders only those declared operations; it does not gain arbitrary application access.

Current status: declarative capabilities and required-use enforcement are shipped. Automatic deterministic endpoint execution is a planned milestone. Today, @pot.summon requests still run through the provider-neutral agent runtime.

Deterministic example

This endpoint has one fixed result path: load the account, calculate the exact balance, and return it. Once every input and output binding is declared, the planned compiler can execute it without an LLM.

from accounts.operations import calculate_balance, load_account
from pydantic import BaseModel
from summonpot import Pot, Required


class BalanceRequest(BaseModel):
    account_id: str


class BalanceResponse(BaseModel):
    account_id: str
    balance: str
    currency: str


pot = Pot("accounts")


@pot.summon("/balance")
def get_balance(
    request: BalanceRequest,
    account=Required(load_account),
    balance=Required(calculate_balance),
) -> BalanceResponse:
    """Return the exact current balance for this account."""
    raise NotImplementedError
{
  "account_id": "acc_123"
}

There is no action to interpret and no valid alternative to choose. Hard rules and money calculations remain inside the declared operations.

Agentic example

This endpoint has a fixed goal, but inventory results may leave several legal fulfilment paths. The agent may compare only the declared options and must complete the real order operation before returning success.

from orders.operations import check_inventory, create_order, find_substitutes
from pydantic import BaseModel
from summonpot import Depends, Pot, Required


class OrderItem(BaseModel):
    sku: str
    quantity: int


class OrderRequest(BaseModel):
    customer_id: str
    items: list[OrderItem]


class OrderResponse(BaseModel):
    order_id: str
    selected_items: list[OrderItem]
    status: str


pot = Pot("orders")


@pot.summon("/orders")
def fulfil_order(
    request: OrderRequest,
    inventory=Depends(check_inventory),
    substitutes=Depends(find_substitutes),
    creation=Required(create_order),
) -> OrderResponse:
    """Fulfil the order using the best valid available option."""
    raise NotImplementedError
{
  "customer_id": "123",
  "items": [{"sku": "A", "quantity": 1}]
}

The endpoint declaration supplies the fixed goal: fulfil the order using the best valid option. The request supplies business data only. If inventory leaves one complete path, the planned compiler can run it deterministically. If several valid substitutions remain, the agent chooses among those bounded results. It cannot call undeclared operations or grant itself a stronger runtime.

Restricted database operations

Database access follows the same capability rule: pass exact prepared operations, never database authority.

Target API — planned, not shipped yet: the SQLAlchemy and SQLite adapters below show the intended security boundary. Final names may change during implementation.

SQLAlchemy ORM statement

The developer prepares one exact Select using an ORM model. The framework binds customer_id from validated request data, opens the session internally, executes the statement, and validates the projected result. The agent sees load_customer(customer_id) -> CustomerView; it never receives the statement, ORM registry, session, or engine.

from orders.database import Customer, orders_session
from pydantic import BaseModel
from sqlalchemy import bindparam, select
from summonpot import FromRequest, Pot, Required, SQLAlchemyOperation


class CustomerRequest(BaseModel):
    customer_id: str


class CustomerView(BaseModel):
    customer_id: str
    tier: str
    active: bool


customer_statement = (
    select(
        Customer.id.label("customer_id"),
        Customer.tier,
        Customer.active,
    )
    .where(Customer.id == bindparam("customer_id"))
)

load_customer = SQLAlchemyOperation(
    name="load_customer",
    statement=customer_statement,
    session_factory=orders_session,
    bind={"customer_id": FromRequest("customer_id")},
    output=CustomerView,
)

pot = Pot("customers")


@pot.summon("/customers/resolve")
def resolve_customer(
    request: CustomerRequest,
    customer=Required(load_customer),
) -> CustomerView:
    """Return the exact approved customer projection."""
    raise NotImplementedError

Only the predefined SELECT can run. The model cannot change the table, columns, predicate, join, or SQL text.

SQLite operation

The SQLite adapter receives one fixed parameterized statement. The framework owns the connection and parameter binding; the agent cannot access a connection, cursor, or generic SQL executor.

from orders.database import orders_database
from pydantic import BaseModel
from summonpot import FromRequest, Pot, Required, SQLiteOperation


class CancelRequest(BaseModel):
    order_id: str


class CancelReceipt(BaseModel):
    order_id: str
    rows_affected: int
    status: str


cancel_order = SQLiteOperation(
    name="cancel_order",
    database=orders_database,
    sql="""
        UPDATE orders
        SET status = 'cancelled'
        WHERE id = :order_id AND status = 'pending'
    """,
    bind={"order_id": FromRequest("order_id")},
    output=CancelReceipt,
    exactly_one_row=True,
)

pot = Pot("orders")


@pot.summon("/orders/cancel")
def cancel(
    request: CancelRequest,
    receipt=Required(cancel_order),
) -> CancelReceipt:
    """Cancel this order only when the declared operation permits it."""
    raise NotImplementedError

The endpoint can execute only that parameterized UPDATE. It cannot issue another query, interpolate SQL, inspect unrelated tables, or claim success without the validated receipt.

The planned adapters will enforce these boundaries outside the model:

  • only developer-declared Select, Insert, Update, or Delete objects and fixed SQLite statements;
  • explicit argument sources such as validated request fields or prior operation results;
  • typed input, projection, and receipt validation;
  • framework-owned sessions, connections, transactions, and serialization;
  • affected-row, call-count, ordering, and once-only constraints;
  • no raw Session, Engine, Connection, cursor, model registry, arbitrary SQL, shell, or filesystem access.

Installation

pip install summonpot            # core
pip install summonpot[serve]     # + HTTP server (FastAPI/uvicorn)
pip install summonpot[cli]       # + Typer CLI
pip install summonpot[all]       # everything

Install the provider you want to use:

pip install "summonpot[openai]"       # OpenAI
pip install "summonpot[anthropic]"    # Anthropic
pip install "summonpot[google]"       # Google Gemini
pip install "summonpot[groq]"         # Groq
pip install "summonpot[mistral]"      # Mistral
pip install "summonpot[openrouter]"   # OpenRouter
pip install "summonpot[xai]"          # xAI
pip install "summonpot[all]"          # serving, CLI, and every provider

Choose a model with an explicit provider:model identifier and set that provider's standard API-key environment variable:

export SUMMONPOT_MODEL=anthropic:claude-sonnet-4-5
export ANTHROPIC_API_KEY=...

OpenRouter keeps the upstream provider and model in the portion after the first colon:

export SUMMONPOT_MODEL=openrouter:anthropic/claude-sonnet-4
export OPENROUTER_API_KEY=...

The endpoint API does not change between providers. Unprefixed legacy model names such as gpt-4o-mini continue to resolve as openai:gpt-4o-mini.

Quick Start

Create a file app.py:

from typing import Literal

from pydantic import BaseModel, Field
from summonpot import Pot


class AnalyzeRequest(BaseModel):
    text: str = Field(min_length=1)
    max_topics: int = Field(default=5, ge=1, le=20)


class AnalyzeResponse(BaseModel):
    sentiment: Literal["positive", "negative", "neutral"]
    topics: list[str]
    explanation: str


pot = Pot("my-service")


@pot.summon("/analyze")
def analyze(request: AnalyzeRequest) -> AnalyzeResponse:
    """Analyze the text and return its sentiment, topics, and explanation."""
    raise NotImplementedError

Serve it:

summonpot serve app.py                  # serves on 0.0.0.0:8000
summonpot serve app.py --port 9000

Or from Python:

pot.serve()                             # 0.0.0.0:8000
pot.serve(host="127.0.0.1", port=9000)

The Summoning Model

Concept As summoning
Route definition "At this path, I summon..."
Docstring The incantation (system prompt)
Capabilities Exact operations placed in the circle
Request model What the summoner brings
Return type What appears

Declarative dependencies

An endpoint signature can combine one Pydantic request model, exact deterministic dependencies, and one Pydantic response model:

from my_service.operations import check_capacity, load_constraints
from pydantic import BaseModel, Field
from summonpot import Depends, Required


class PlanRequest(BaseModel):
    goal: str
    constraints: list[str] = Field(default_factory=list)


class PlanResponse(BaseModel):
    steps: list[str]
    risks: list[str]


@pot.summon("/plan")
def plan(
    request: PlanRequest,
    stored_constraints=Depends(load_constraints),
    capacity=Required(check_capacity),
) -> PlanResponse:
    """Create an actionable plan that respects every constraint."""
    raise NotImplementedError

The signature is the complete execution contract. The decorated function is declarative—the runtime does not execute its body.

  • The request model alone defines and validates incoming JSON.
  • The response model defines OpenAPI output and validates the final result.
  • Depends(operation) gives the agent an exact deterministic operation it may call.
  • Required(operation) rejects final output until the exact operation has run successfully.
  • Dependencies never become HTTP request fields.
  • The agent receives no undeclared application operations.
  • Provider output is retried within a bounded budget when it violates the response contract or skips a required operation.

A Pydantic endpoint has exactly one request parameter plus any declarative dependencies. Put all incoming fields inside the request model so there is one clear JSON body.

How it works

summonpot inspects your endpoint function:

  • Docstring → becomes the fixed endpoint goal
  • Pydantic request model → becomes the validated JSON request body and OpenAPI input schema
  • Pydantic response model → becomes the provider's structured-output schema, runtime validator, and OpenAPI response schema
  • Dependencies → become the endpoint's closed set of optional or mandatory deterministic capabilities

The framework owns the agent loop, capability orchestration, required-operation enforcement, and structured-output validation. The endpoint body contains no handler code.

See Declarative capability endpoints for the execution and security contract.

Provider and model configuration

Summonpot uses provider-qualified model identifiers. Provider SDKs, authentication, tool calling, structured-output negotiation, and model-specific behavior are handled internally by the provider-agnostic runtime.

Provider Install extra Model example API-key variable
OpenAI summonpot[openai] openai:gpt-4o-mini OPENAI_API_KEY
Anthropic summonpot[anthropic] anthropic:claude-sonnet-4-5 ANTHROPIC_API_KEY
Google summonpot[google] google:gemini-2.5-flash GOOGLE_API_KEY
Groq summonpot[groq] groq:llama-3.3-70b-versatile GROQ_API_KEY
Mistral summonpot[mistral] mistral:mistral-large-latest MISTRAL_API_KEY
OpenRouter summonpot[openrouter] openrouter:anthropic/claude-sonnet-4 OPENROUTER_API_KEY
xAI summonpot[xai] xai:grok-4 XAI_API_KEY

SUMMONPOT_MODEL sets the default for every endpoint:

export SUMMONPOT_MODEL=openrouter:anthropic/claude-sonnet-4

An endpoint can override it without changing its request, response, or capabilities:

@pot.summon(
    "/research",
    model="anthropic:claude-sonnet-4-5",
)
def research_topic(
    request: ResearchRequest,
    sources=Depends(search_web),
    receipt=Required(record_research),
) -> ResearchResponse:
    """Research this topic."""
    raise NotImplementedError

Pydantic AI is an internal runtime dependency. Summonpot users do not construct Pydantic AI agents or provider clients; the stable public contract remains Pot, @pot.summon, declarative capabilities, and Pydantic endpoint models.

Development

Requires uv.

git clone https://github.com/tugrulguner/summonpot.git
cd summonpot
uv sync --all-extras
make check    # lint + typecheck + test
make lint     # ruff check + format check
make test     # pytest
make format   # auto-format

See CONTRIBUTING.md for pull-request and single-source release instructions.

License

MIT

Release files for summonpot 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for summonpot 0.2.0
File Size Uploaded
summonpot-0.2.0.tar.gz 2.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for summonpot 0.2.0
File Interpreter ABI Platform
summonpot-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.6 MB

Release files / summonpot-0.2.0.tar.gz

Download URL summonpot-0.2.0.tar.gz
Size 2.6 MB
Tags Source
SHA-256 checksum
How to use checksums
c624bcfa6a5fd52e6e24185c2690b846223dae81bb22392f722c42f6960b78da
BLAKE2b-256 checksum
How to use checksums
57f27c5218915c8d22d84baeed13dc4935324880dc6da2aad8dcc53e9d115915
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release files / summonpot-0.2.0-py3-none-any.whl

Download URL summonpot-0.2.0-py3-none-any.whl
Size 18.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9fa97e57df73542cd7dbc6819c72ab36d6fe2b8d4e532f5a497a1e1667c45a49
BLAKE2b-256 checksum
How to use checksums
0af4fd311baabcb4dc6fb66eee9aa266d60acb274012b8a053ef692c15c0e259
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release history Release notifications | RSS feed

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page