Skip to main content

budgetloop

The tool-calling loop, written out: schemas from type hints, parallel calls, budgets and a trace. No dependencies.

Every agent framework hides the same forty lines. This is those lines, with the parts that actually bite — a tool that raises, arguments that nearly fit, a model that never stops asking — handled where you can see them.

The shape of it

from budgetloop import Budget, tool, run


@tool
def price(isin: str) -> float:
    """Look up the last price for an instrument.

    Args:
        isin: the twelve-character identifier
    """
    return lookup(isin)


result = run(
    complete,                      # your model, see below
    "What is SE0000108656 worth?",
    tools=[price],
    budget=Budget(max_steps=6, max_cost_usd=0.05),
)

print(result.answer, result.stop, result.cost_usd)

The model

One callable, provider-agnostic:

def complete(messages: list[Message], tools: list[dict]) -> Completion:
    ...

It gets the conversation and the tool schemas, and returns either text or tool calls. Adapting an SDK to it is a dozen lines, and in return the whole loop is testable with a scripted model and no network. Every test in this repository runs offline.

Run the example

python examples/offline.py
step 0: Looking both of them up.
  - price({"isin": "SE0000108656"}) -> 118.4
  x price({"isin": "SE0000000000"}) -> error: LookupError: SE0000000000 is not in the price list
step 1: One of those is unknown. Converting the one that resolved.
  - convert({"amount": "118.4", "rate": 0.091}) -> 10.77
step 2: SE0000108656 is 118.40 SEK, about 10.77 USD.

stop=answered tools=3 failed=1 cost=$0.0027

Three things are visible in that output, and each one is a decision.

A tool that raises becomes a result, not a crash. The second lookup failed, the model read the failure and worked around it. A loop that propagates the exception throws away a model that was about to recover on its own.

Arguments are checked, and corrected once where the intent is not in doubt. The model sent "118.4" as a string. A number written as a string is the mistake models make constantly, so it is coerced; "five" is not, and comes back as error: convert.amount: expected number, got str. Unknown argument, missing argument, value outside an enum: same treatment, reported to the model in words it can act on.

Both calls in step 0 ran at the same time. When a model asks for several tools in one turn it means they are independent, so they are run on a pool and the results go back in the order they were asked for.

Schemas come from the signature

@tool builds the JSON Schema from the type hints and the docstring. Writing the schema by hand next to the function is how the two drift apart, and a drifted schema shows up as a model calling the tool wrongly for reasons nobody can see.

Supported: str, int, float, bool, list[T], dict[str, T], Literal[...], unions with None, Any, and defaults. A parameter without a type hint is an error at import time, not a surprise at runtime. Args: lines in the docstring become parameter descriptions, which is most of what makes a model call a tool correctly.

Budgets

Budget(max_steps=10, max_seconds=30, max_cost_usd=0.50, max_tool_calls=25)

Only max_steps has a default, because a loop without a ceiling is a loop that bills you for a model's bad afternoon. A run that hits a limit ends with result.stop set to which one, and result.answered is False. Nothing raises.

The trace

result.to_dict() is plain data: every step, what the model said, what each tool was called with, what came back, how long it took and what it cost. Store it next to the request and a bad answer stops being a mystery.

Tests

pytest -q
ruff check .

65 tests, no network, no API key. MIT licensed.

Metadata

Release files for budgetloop 0.1.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 budgetloop 0.1.0
File Size Uploaded Provenance Actions
budgetloop-0.1.0.tar.gz 17.3 kB With provenance Details for budgetloop-0.1.0.tar.gz

Built distribution (wheel)

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

Total release size: 31.2 kB

Release files / budgetloop-0.1.0.tar.gz

Download URL budgetloop-0.1.0.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7d7cc09413915d94fc6ef714e08c99531984c089fb3a9d2889c640b8936aa7dd
BLAKE2b-256 checksum
How to use checksums
27e90555fb93cf7e63d205af10bd05134d36c9e3ad078923a12424e9e032abb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 23, 2026.

Transparency log

Release files / budgetloop-0.1.0-py3-none-any.whl

Download URL budgetloop-0.1.0-py3-none-any.whl
Size 13.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
33840d214e0a8469c41ef87c855b93bb36714252f4f09dfb29153608fb027d1c
BLAKE2b-256 checksum
How to use checksums
4387fd0a65ad697dc244069097456b41b684d599ff82ea4a16f9d4f9ff9b7dfe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 23, 2026.

Transparency log

Security for budgetloop 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for budgetloop-0.1.0.tar.gz

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 Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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