Skip to main content

MPPI agent SDK for Python

Version 0.1.6 keeps the wire protocol, contract and signatures, uses protocol package 0.1.4 and works with the providers running 0.1.2 and 0.2.0. A caller of 0.1.5 notices these changes:

  • an opening in a fresh process takes two chain round trips from an account without code and three from a smart account, four before: the follower's snapshot carries the registry's funding wallet and its code, and a new session's first attachment is checked against its own opening with no read;
  • an RPC backend that serves a refresh from behind the block that proved the agent's own opening, top-up or close is lag: requests no longer stop at INCONSISTENT_STATE until a later refresh. Once refreshes have stayed behind that block for longer than max_age_ms, requests stop with SESSION_NOT_READY / STATE_UNAVAILABLE until one reaches it;
  • an opening the wallet left uncertain with no transaction hash is found by its SessionOpened log and proven by its receipt. An opening intent saved by 0.1.5 has no recorded block, so 0.1.6 never searches the log for it;
  • a request on a retained session that its provider or anyone else closed, whatever close-pending state the provider had reported and also after the agent's own requestClose, raises PaymentError with code SESSION_NOT_READY and reason SESSION_CLOSED, with no reconnect, where 0.1.5 reconnected on every request and failed as "Control is unavailable". sessions.open retires that session as after the agent's own close and funds a new one with the same signer, unless its settled total exceeds validated usage: then it raises CloseAccountingError, whose operation is None;
  • once the chain shows the session closed, opening proves a close the provider reported, where 0.1.5 raised "session close is pending" until sessions.close proved it: proven, the next request or sessions.open retires the session as after the agent's own close. A close the agent cannot prove, a reported close whose proof fails or its own withdraw or releaseIdle left UNKNOWN with no transaction hash, is a close by someone else, as above; the archived record of a session sessions.open retires that way holds closed_by_other, the proof and what it left unresolved;
  • opening first reconciles the agent's own withdraw or releaseIdle that is still PENDING or UNKNOWN, after a restart too, where 0.1.5 raised "session close is pending" until a sessions.reconcile of it; one with no transaction hash is traceable after all when the wallet holds one. The rule for what the wallet and chain then report applies;
  • a provider report that arrives while the agent retires a closed session no longer writes that session back into the checkpoint, where since 0.1.5 the next session could inherit the retired session's close;
  • a failed withdraw, idle release or close request can be retried; earlier agents, every version before 0.1.6, returned the failed result forever;
  • a withdraw, idle release or close request whose journal save failed before the wallet was asked can be called again, where it was kept as an unsent attempt that blocked its kind for good;
  • once it retires a session, the agent stops reading it from the chain, and hands the wallet's retire the operations of that session it holds a transaction hash for, never one it holds no hash for; the Circle adapter then forgets each of those records that holds a transaction hash. A wallet that cannot, or does not return within the client's timeout, is reported to on_error as HookError("retire", ...), and the opening goes on (wallet requirements);
  • the Circle adapter checks the CLI and wallet before an execute unless a check began within the last 60 seconds, where 0.1.5 checked once per process; a call it refuses for an invalid reference, or once its journal shows no record of the reference, is FAILED, never UNKNOWN, and a wallet's cause now reaches on_error as a HookError;
  • chain reads repeat after a random share of their wait and honour Retry-After within the same bound.

Do not downgrade to 0.1.5 over a Circle wallet journal 0.1.6 has saved: earlier releases refuse its compact records.

Version 0.1.5 keeps the wire protocol, contract and signatures, uses protocol package 0.1.3 and works with the providers running 0.1.2. A caller of 0.1.4.1 notices these changes:

  • after the agent's own confirmed close (a cooperative close, or its own withdraw or releaseIdle), the next request or sessions.open funds a new session with the same signer instead of raising "session close is pending". From then on the closed session is no longer retained: sessions.get, terms, run and reconcile of its operations raise ClientError, and list no longer shows it. Its last record stays beside the checkpoint as <checkpoint name>.<session id>.closed<suffix> (for the built-in setup, .mppi/agent.json.payments.<session id>.closed.json). A close or exit whose settled total exceeds validated usage is never retired: opening raises its CloseAccountingError. After its own withdraw or releaseIdle, that check may read the chain, so opening can raise a chain error while the RPC fails;
  • Client(..., voucher_headroom=H) and Client.from_config(..., voucher_headroom=H) (default 0) sign each voucher up to H base units above its requirement, within the confirmed deposit, so the provider admits a later request it covers without first asking for a voucher. A provider can capture up to the signed voucher whatever it delivered, so the headroom adds at most H to the agent's exposure;
  • a Receipt is checked against its run's accepted terms with no chain read. The settled total is proven at close and at every attachment, and sessions.get reports it;
  • a retried top-up first reads whether USDC already holds its authorization as used: then the earlier top-up took effect, nothing is sent, and new top-ups are refused until that authorization expires. An uncertain top-up whose authorization expired unused reconciles to FAILED, and a top-up event below the confirmed deposit plus its amount raises ClientError, after which a retry sends that same authorization again, never a new one;
  • onboarding names a missing Circle CLI, a CLI other than 1.1.4, a failed wallet check (with the circle wallet list command that shows Circle's reason and the login the network needs), an address that login does not list, and the network of an empty wallet, as OnboardingError, where 0.1.4.1 raised the adapter's CircleAgentWalletError. MPPI_CIRCLE_CLI names the CLI executable.

Version 0.1.4.1 fixes one defect in 0.1.4. When a background chain refresh accepted a block after an opening's own while the opening was being signed and submitted, the next refresh could report the new session's state as regressed and hold its requests at INCONSISTENT_STATE until its provider next collected payment. The opening now records its activity time only from a block no later than its own; otherwise the attachment reads the session at the provider's block, with no extra chain read. Nothing else changes.

Version 0.1.4 keeps the wire protocol, contract, signatures and payment semantics and works with the providers running 0.1.2. It reads the chain in a few batched round trips over kept-alive connections, and resolves outcomes that 0.1.3 left uncertain:

  • each chain lookup is one request: a refresh is one batch around one Multicall3 call anchored to its block, and opening, top-up and close are each proved by their transaction's receipt and event. Arc blocks are final, so none is re-read. A mined transaction whose call did not take effect is FAILED with its cause, and a retried top-up resends the same authorization while it is valid;

  • requests within an attached session read no chain: a slow or unreachable RPC no longer refuses them with CHAIN_STATE_STALE. They use the confirmed deposit, accepted prices and attachment; opening, attachment, top-up, close and accepting new prices refresh first when evidence is older than max_age_ms. The built-in client polls once a minute, and a failed background refresh reaches the error hook. A refresh that finds evidence contradicting the deployment (another chain, code, implementation or signing domain, or state going backwards) stops requests with INCONSISTENT_STATE until a later refresh is accepted;

  • each step reads everything it needs at one block in one JSON-RPC batch. Top-up and close repeat no whole refresh; an attachment reads only its session once the follower has reached the provider's block, and everything at that block otherwise, as a new session's first attachment does. Fixed facts (the chain id, deployed code, the Circle CLI and wallet selection) are read once, implementation slots every time;

  • any Circle Agent Wallet account generation works: no account code is pinned. Before submitting an opening, the agent checks in one read that the registry binds its agent id to the wallet and that USDC would accept the signature, and otherwise raises FundingAuthorizationError (reason AGENT_ID_REQUIRED, AGENT_WALLET_MISMATCH or INVALID_SIGNATURE) with nothing sent; a top-up relies on its opening's proof. A wallet whose service signs as it executes declares signs_at_execute = True: the opening then checks only the binding and hands the placeholder to execute, and an undeclared all-zero placeholder is SIGNATURE_DEFERRED_UNDECLARED. Funding now requires Client(..., agent_id=...); exits do not;

  • a timed-out read is repeated once; a reported cooperative close whose proof cannot be read yet is PENDING with its transaction, and calling sessions.close again proves it without a second close request;

  • a wallet call the adapter proves never reached its service is FAILED, with cause beginning NotSubmittedError; the next sessions.open sends the same intent again while its authorization is valid. An uncertain opening is FAILED once its authorization has expired with no session funded;

  • a request the provider refuses before admission is recorded never admitted, so a cleanly closed session shuts down with nothing unresolved;

  • a plain low-credit notice never replaces an undelivered shortfall, and a top-up confirmed while Control reattaches renews vouchers once attached;

  • integrations read a session's accepted prices (sessions.terms) and a run's outcome (sessions.run) through public types instead of client internals. Hooks(on_timing=...) reports where each opening, top-up and close spent its time: chain reads, the wallet's signing and submission, and the rest.

Composed integrations keep the ChainReader, ChainFollower and Client constructors and FundingNotice; new fields and methods are additive. Three interfaces changed: OpeningEvidence and ClosingEvidence carry a ChainObservation as observation instead of block; ChainReader.agent_wallet(agent_id, observation) takes one too; and CircleAgentWallet(chain=..., address=..., checkpoint_path=...) takes the wallet address instead of account pins, so supported_account_pins is gone. A follower passed to Client keeps its own on_refresh_failure; give it poll_interval_ms=max_age_ms for the built-in cadence.

Do not downgrade to 0.1.3 over a checkpoint that holds a close not yet proven, or over a Circle wallet journal 0.1.4 has rewritten (format 2).

Version 0.1.3 keeps the wire protocol, contract, signatures and payment semantics of 0.1.2 and works with the providers running 0.1.2. It changes how the client survives concurrency, rate limits and lagging RPCs:

  • one invalid or unprovable Control report is rejected alone; Control loss or a reattach never cancels a Complete stream, so completed answers arrive;
  • a coverage request overtaken by another stream's final is ignored, not fatal, and judged by price;
  • chain reads repeat rate-limited, unavailable and not-yet-imported reads; a lagging RPC head keeps accepted evidence; a failed refresh is repeated;
  • a top-up no longer blocks requests; from_config polls every 15 s and takes max_age_ms and poll_interval_ms; a Receipt observation is proven once;
  • on_low_credit receives a LowCredit only for what the deposit cannot cover; the new on_funding_notice gets a FundingNotice with its shortfall, worked out at delivery;
  • settled runs are forgotten, so the payment checkpoint stays bounded. It is written as format 2; do not downgrade to 0.1.2 over it.

Since 0.1.3, on_low_credit receives fewer notices than in 0.1.2: only shortfalls the deposit cannot cover and plain low-credit notices, coalesced to the latest undelivered one.

Version 0.1.2 supports explicit Arc mainnet and testnet selection with built-in Circle CLI onboarding and Client.from_config(...). See the agent quickstart.

Version 0.1.0 includes a synchronous Client with an owning background asyncio thread for native TLS gRPC Complete and Control. It verifies discovery, positive funding, fixed-signer attachment, signed run prices, cumulative vouchers, provider usage reports and canonical settlement/close evidence. Version 0.1.0 is published for official Arc testnet. The package includes resumable ERC-8004 onboarding and a Circle Agent Wallet adapter tested through live Arc testnet onboarding, funding, payment, authenticated reconnect and cooperative close.

Start with the Python installation and paid-exchange walkthrough. See onboarding and wallet setup for mppi-agent onboard, deployment profiles, wallet integration and rerun behavior.

from mppi_agent import Rates, funding_amount

assert funding_amount(1_000_000, Rates(input=1_000_000, output=3_000_000)) == 3_000_000

The result is six-decimal USDC base units. This calculation transfers no funds. The caller selects funding; actual usage retains its individual accepted rates.

Construct Client(provider, wallet=..., signer=..., chain=..., contract=..., checkpoint_path=..., signer_reference=..., initial_session_tokens=..., agent_id=...) with the shared ChainFollower and ContractCalls configured for the verified deployment. The funding wallet and fixed ECDSA signer are distinct capabilities. The caller supplies wallet integration; no login or key creation is implicit. Funding requires agent_id, the ERC-8004 identity the registry binds to the funding wallet.

  • sessions.open(provider, model=..., initial_session_tokens=...) explicitly prewarms a positively funded session. chat.completions.create(...) can open lazily using the caller's configured positive funding choice.
  • chat.completions.create(model=..., messages=..., stream=True) returns an iterator of original JSON bytes with terminal and receipt records. create_raw(payload, model=..., stream=...) preserves original request bytes.
  • accept_catalog(rate_card_hash) explicitly accepts the exact fresh published catalog for subsequent runs. Active and historical runs keep their prices.
  • sessions.topUp(session_id, amount) takes positive USDC base units. Funding is explicit; low-credit guidance never triggers a wallet transfer.
  • sessions.terms(session_id) returns the session's accepted catalogs by rate-card hash, its initial one and the provider's current one (SessionTerms.rates(model)). sessions.run(session_id, run_id) returns a retained run's RunOutcome: its signed prices, accepted counts, charge and RunOutcomeStatus (PENDING, COMPLETED, FAILED, CANCELLED or NOT_ADMITTED, which the provider never held). Neither reads the chain.
  • Client(..., on_low_credit=callback) delivers funding notices to caller code outside the Control event loop, as a detached pb.LowCredit. A provider's LowCredit both asks for a voucher and signals low funding: the SDK signs every coverage the confirmed deposit covers itself, so only a requirement the deposit cannot cover, or a plain low-credit notice, reaches the callback. Client(..., on_funding_notice=callback) receives the same notices as a FundingNotice(notice, deposit, required, shortfall), in USDC base units (required and shortfall are None for a plain notice), for example client.sessions.topUp(session_id, funding.shortfall). The deposit and shortfall are worked out at delivery, a notice that a confirmed top-up has since covered is dropped, and an undelivered notice is replaced by a newer one, so a slow callback never sees a stale shortfall. The SDK adds no funding policy. Keep callbacks bounded: an exception fails the attachment. An undelivered notice is discarded when Control is lost; an already running callback cannot be interrupted. Confirmed top-up rechecks the retained voucher target against fresh chain funding.
  • Client(..., voucher_headroom=amount) signs each voucher amount USDC base units above what the provider requires, within the confirmed deposit (the default 0 signs exactly the requirement). The provider then admits a later request the headroom covers at once, without first asking for a voucher; one that does not fit asks as before, and its voucher restores the headroom. Size it to the prompt and the provider's output buffer (cover_output_tokens) of the requests it should admit, at their prices. A provider can capture up to the signed voucher whatever it delivered, so the headroom adds at most amount to what you trust it with; close refunds what it did not capture.
  • sessions.recover(session_id) restores retained state and authenticates a new Control generation. It neither funds a session nor replays inference. Current observed prices still require explicit acceptance for later runs.
  • sessions.close(session_id) requests signed cooperative close. The explicit requestClose, withdraw, releaseIdle and reconcile methods retain their respective chain eligibility and evidence checks. After the agent's own close is confirmed, the next request or sessions.open funds a new session with the same signer and keeps the closed record beside the checkpoint.
  • Client.close() and context-manager exit shut down local streams and return unresolved work. They do not implicitly close a funded session.

The checkpoint path is required and exclusively locked on macOS/Linux. Reuse it for the same payment scope across restarts. It contains descriptors, signed payment intent and operation references, with mode 0600; it contains no prompts or private signing key. Keep the recoverable signer credential in the supplied secret service. The client owns the supplied follower's lifecycle.

An explicit open can continue the same retained, never-submitted opening intent while its authorization remains valid. A pending or unknown submission is reconciled using its original reference; it cannot create replacement funding.

Retained session inspection and wallet-only exits restore from the checkpoint and verified chain state without requiring the provider's HTTP endpoint. A CloseAccountingError reports capture above validated usage while retaining the confirmed transaction in its operation (None for a close the client did not make); it does not imply a retry is safe.

Queue bounds and wait timeouts are explicit constructor settings. Slow output consumption cannot grow its queue without bound; failed or cancelled inference is never automatically replayed. Transport errors can leave final accounting unresolved. Fresh cached chain evidence is required for new authorization.

See SDK interface definitions and the protocol profile. The shared protocol dependency is an implementation support library, not a third role SDK.

See runtime building blocks for the new APIs and their verification boundaries.

Application unit tests

mppi_agent.testing.MockProvider runs the real generated Chat and Control gRPC services in-process on an ephemeral loopback port. Supply asynchronous handlers for the exact protobuf messages and errors your application should encounter:

import asyncio
from mppi.v1 import mppi_pb2 as pb
from mppi_agent.testing import MockProvider


async def complete(request, context):
    yield pb.ChatChunk(payload=b'{"answer":"Inference successful"}')


async def control(requests, context):
    async for message in requests:
        # A test script can inspect the request and yield its selected reply.
        yield pb.ControlMessage(reconnect_challenge=pb.ReconnectChallenge(nonce=b"n" * 32))


async def test_application():
    async with MockProvider(completions=complete, control=control) as provider:
        call = provider.chat.Completions(
            pb.CompleteRequest(model="test/model", payload=b"{}"), timeout=2
        )
        assert (await call.read()).payload == b'{"answer":"Inference successful"}'


asyncio.run(test_application())

The chat and control properties expose asynchronous generated stubs. Their RPCs accept normal gRPC deadlines; context exit closes the channel and cancels unfinished calls. Each instance is used once. Both handlers run on the test's event loop, so keep them asynchronous.

Scripts control every reply, including omitted final reports and rejected requests. The helper implements no discovery, TLS, wallet, chain state or payment validation. It does not change Client verification or turn scripted messages into proof of settlement. Use deployed testnet integration for those boundaries. No provider package is needed to import this helper.

Lifecycle observers

Pass hooks=Hooks(...) to the runtime/client for typed lifecycle observations. See the hook payloads, ordering and bounds.

License

Copyright 2026 MPPI contributors. Licensed under the Apache License, Version 2.0.

Metadata

Release files for mppi-agent 0.1.6

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

Source distribution (sdist)

Source distribution for mppi-agent 0.1.6
File Size Uploaded Provenance Actions
mppi_agent-0.1.6.tar.gz 163.2 kB Without provenance Details for mppi_agent-0.1.6.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mppi-agent 0.1.6
File Interpreter ABI Platform Actions
mppi_agent-0.1.6-py3-none-any.whl Python 3 none any Details for mppi_agent-0.1.6-py3-none-any.whl

Total release size: 245.7 kB

Release files / mppi_agent-0.1.6.tar.gz

Download URL mppi_agent-0.1.6.tar.gz
Size 163.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ee41e192190fac1e48ba8d2a16cbc62b15af3d22efe55cb9b64e0141ed727520
BLAKE2b-256 checksum
How to use checksums
adde6e76e51442a3547f529376391ba95216072f75e5bcec6264a9c08bf53ad8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / mppi_agent-0.1.6-py3-none-any.whl

Download URL mppi_agent-0.1.6-py3-none-any.whl
Size 82.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
87406e764f906d19957e8e3e477f1bbb2ceaf5a179c7bdd50a38f5f1d8b572f1
BLAKE2b-256 checksum
How to use checksums
571b381cf06cd1cdd5468c44876b800fd2e917ea6972dddc056737c5fef709dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for mppi-agent 0.1.6

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

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