Skip to main content

maivn

Python SDK for mAIvn agents, swarms, tools, structured output, conversations, triggers, resources, artifacts, and categorical decisions.

Install

pip install maivn

Set MAIVN_API_KEY in the environment or pass api_key= to Agent, Swarm, or Client. MAIVN_BASE_URL overrides the SDK's default API endpoint.

from maivn import Agent

assistant = Agent(name='Assistant')
response = assistant.invoke('Summarize the next action in one sentence.')
print(response.response)

Documentation

The public documentation lives in its own repository, maivn-docs, checked out as docs/ in the platform superproject. It is not part of this package and is not shipped in the source distribution, so a documentation fix never requires a release of this SDK.

Runnable public examples live in the maivn-examples repository. They cover typed tools, structured output, conversations, swarms, privacy, human input, triggers, and generated files.

Encrypted local storage

The SDK includes the native vault package. On a supported desktop with an available OS credential store, the SDK saves caller-supplied private thread values automatically. It creates the vault when those values are first used, not during installation. Keep the same account, API base address and thread ID to resume them after restarting your application. Rotating an API key for the same user, organization and project keeps access to the same vault.

Servers and containers

Configure two values in your hosting provider:

  • MAIVN_VAULT_DIRECTORY: an absolute path on durable storage.
  • MAIVN_VAULT_KEY: a base64-encoded, randomly generated 32-byte key, injected from your secret manager.

Create the key once and retain it across deployments. Do not generate a new key at startup or substitute your mAIvn API key. Ordinary Client() construction then uses these settings. Your provider handles secret delivery. The SDK does not create cloud resources or configure a secret manager for you.

If your application already retrieves secrets, pass a callable that returns the 32 key bytes using Client(local_vault=LocalVaultConfig(directory=path, key_provider=load_key)). Import both Client and LocalVaultConfig from maivn. The SDK calls the provider when it opens the vault.

Serverless temporary storage loses data between instances. Use a persistent filesystem with working file locks and atomic replacement. An object-store URL is not a vault directory. Qualify shared filesystems before running multiple writers against them.

Recovery and deletion

Back up encrypted files and retain their matching key. Replacing a missing key cannot recover existing data. Desktop credential entries are tied to the vault's location, so copying the files to another path or machine alone does not restore access. Server deployments must preserve both their durable storage and secret-manager key.

If secure storage cannot open, the SDK raises VaultSetupError when private storage is needed. It does not switch to plaintext or temporary storage.

Call client.forget_private_data(thread_id) to delete saved caller values for one thread. Async applications can await client.aforget_private_data(thread_id). These methods do not delete server-held data or uploaded documents. Explicit Client(private_data_store=None) disables local persistence. You can also supply your own PrivateDataStore implementation. Set Client(restore_private_values=False) to receive placeholder-only output instead of resolved caller values.

This automatic store holds caller-declared placeholder values. Private file uploads and document retrieval retain their separate API and authorization requirements.

Shielded markers in tool arguments

Pass shielded markers through exactly as you receive them. They can be resolved inside SDK-local tool arguments, including prose in a declared string field, when their values are available in the current private-data custody. The platform fills the keys it allocated; the caller's SDK process fills the keys it declared itself. This also applies to final tools. A final-tool declaration does not grant access to additional private data.

Markers are not a guarantee that a value is still available. Unknown or stale server keys fail with private_placeholder_unresolvable. Reacquire the source through an authorized tool or report that the data is unavailable. Do not invent a replacement or repeat the unchanged call. Hosted and external tools cannot receive vault-held plaintext through this SDK-local resolution path. Response text may retain a marker for display when its value is unavailable; that does not make the marker executable tool data.

Decisions

maivn.decisions answers categorical questions through the mAIvn platform and falls back to your own baseline when a decision is unavailable, refused or not confident enough. The SDK never calls a decision model directly: requests go to POST /v1/decisions with the same API key and MAIVN_BASE_URL as every other call. The platform chooses the decision model, shields explicitly marked private values and bills the call to your project.

from maivn.decisions import (
    DecisionAnswer, DecisionChoice, DecisionClient, DecisionConfig, DecisionRequest,
)

question = DecisionRequest(
    name='urgent',
    state='Public bulletin: planned API maintenance on Saturday.',
    instructions='Does this bulletin need immediate action?',
    privacy='public',
    choices=(
        DecisionChoice(key='yes', description='Immediate action'),
        DecisionChoice(key='no', description='No immediate action'),
    ),
)


async def baseline(request: DecisionRequest) -> DecisionAnswer:
    return DecisionAnswer(value='yes', provider='local', token_category=None)


decisions = DecisionClient(
    fallback=baseline,
    config=DecisionConfig(min_confidence=0.8),
    authorize=lambda request: request == question,
)
result = decisions.decide(question)
print(result.value, result.source, result.primary.status)

Only requests declared privacy='public' and approved by authorize are sent. Mark private values inside otherwise public text with DecisionRequest(..., private_data={'customer_name': 'Priya Raman'}): the platform replaces them with placeholders before the decision model sees anything. A placeholder verdict is resolved locally and must still match a declared choice. Raw marked values reach the platform shield, never the decision model. Plain unmarked text receives no automatic redaction. Your fallback receives the original request and must enforce your application's privacy policy. Pass client=Client(...) to reuse an existing client's settings.

Decision token usage

Decision workflows report two separate categories: decision for platform decisions and llm for ordinary model invocations, including model fallbacks. Use the additive token_usage_by_category property on a decision result, builder outcome, argument preparation, matching result, or decorated call. For a list of decisions, use:

from maivn.decisions import summarize_decision_usage

usage = summarize_decision_usage([result])
print(usage.decision.input_tokens)
print(usage.llm.input_tokens)

Native batch followers do not count the shared receipt again. Missing counts remain None; known_input_tokens and known_output_tokens retain the observed subtotal. A skipped agent invocation contributes zero invocation tokens while its decision receipts remain visible. Declared local fallbacks use DecisionAnswer(..., token_category=None); a decision-model backup declares token_category='decision'. Ordinary model fallbacks default to llm.

These counts are not interchangeable billing units. LLM invocation counts mirror the existing public SDK input/output surface; provider totals and cache counts remain available through provider_usage. Price each capability with its applicable provider tariff and retain costs separately. The existing token_usage property keeps its original behavior.

Decision reference and workflows

All symbols below are imported from maivn.decisions. These workflows are opt-in; creating a request, builder or decorator performs no inference. Synchronous execution methods run outside an active event loop; use their async counterparts inside async applications. The examples below reuse question, baseline and decisions from the Decisions example. They make platform calls when executed unless a feature is explicitly disabled.

Requests, routing and results

Public type Contract
DecisionChoice key (1–128 characters, containing a non-whitespace character), description (1–2,048 characters).
DecisionRequest name uses the choice-key bounds; state is 1–16,384 characters; instructions is 1–4,096 characters; choices is a tuple of 2–32 choices with unique keys. privacy is public, private or unknown (default).
DecisionConfig min_confidence=0.8 (0–1), timeout_seconds=5.0 (positive finite), enabled=True, max_batch_questions=32 (1–128). Timeout bounds the primary attempt, not your fallback.
DecisionUsage Optional nonnegative input_tokens and output_tokens; missing values mean unknown.
DecisionAnswer Fallback value, provider, optional model and usage; token_category='llm' by default. Use None for local code with no model call, which cannot report model usage.
DecisionPrediction An answer with confidence (0–1); requires token_category='decision'. Returned primary metadata identifies the model actually served, rather than configuring a model.
DecisionStatus accepted, blocked, disabled, timeout, unavailable, invalid, low_confidence.
DecisionAttempt Status, latency in seconds, optional provider/model/confidence/usage, token_category, usage_scope (individual or batch), optional usage_batch_size, and usage_owner.
DecisionResult Final value, source (primary or fallback), primary attempt and optional fallback attempt; token_usage_by_category keeps both receipts.

Request and answer models are strict, reject extra fields and are immutable. private_data accepts a mapping or tuple of key/value pairs: up to 64 unique keys, each following the choice-key bounds, with string values of 1–4,096 characters. Mappings become sorted immutable pairs; request.private_values returns a detached dictionary.

DecisionClient(fallback=..., config=..., authorize=..., client=None) requires an async fallback returning DecisionAnswer and a synchronous authorization callback. Only an exact True approves a complete request. Authorization errors, private/unknown privacy, or a disabled client prevent primary dispatch. Approval is a local export permission, not a substitute for declaring private values. No vendor key or decision-model identifier is needed. An existing Client can supply the authenticated platform transport.

Use decide(request) / await adecide(request) for one question. Confidence below the threshold, refusal, malformed or out-of-choice answers, timeout and platform failure use the fallback at most once per question. Its returned value must be a declared choice. Invalid fallback answers raise ValueError; fallback exceptions propagate. Cancellation propagates rather than starting another route. Request validation errors also propagate. Your fallback is not subject to the primary attempt's timeout.

with_fallback(callback) creates a client sharing the route, authorization and configuration with a different fallback. The original client is unchanged.

Ordered and native batches

batch_requests = (question, question.model_copy(update={'name': 'urgent_again'}))
batch_client = DecisionClient(
    fallback=baseline, config=DecisionConfig(),
    authorize=lambda request: request in batch_requests,
)
results = batch_client.batch(batch_requests, max_concurrency=2, native_batch=True)
print([result.source for result in results])

batch / abatch return results in input order. max_concurrency=4 bounds outstanding work by default and must be a positive integer. native_batch=False runs scalar decisions; True combines questions with identical state and private_data, splitting at max_batch_questions. native_batch_question_limit reports that limit (one when the primary is disabled). Approve every full request in your authorization policy. If any member of a shared packet is not approved, that packet falls back without dispatch. Private or unknown requests fall back individually. On caller cancellation or failure, outstanding batch tasks are cancelled and drained.

A native packet has one token receipt. The first member owns it; followers expose usage_scope='batch', the batch size and usage_owner=False, without duplicating usage. Aggregate the whole result collection to retain the owned receipt.

Before and after an invocation

Agent.decide and Swarm.decide return a DecisionInvocationBuilder. The same operation composes with their structured-output, event and follow-up builders. Stage names and question labels start with a letter and contain letters, digits, underscores, dots or hyphens, up to 128 characters; stage names must be unique.

from maivn import Agent

plan = (
    Agent(name='Bulletin assistant')
    .decide(
        'triage', client=decisions, questions={'urgent': question},
        require={'urgent': 'yes'},
    )
    .decide('review', questions={'urgent': question}, at='after')
)
outcome = plan.invoke('Summarize the public maintenance bulletin.')
print(outcome.status, outcome.skipped_by)
if outcome.response is not None:
    print(outcome.response.response)

Each .decide(name, client=..., questions=..., at='before', require=None, max_concurrency=4, native_batch=False) stage has 1–32 questions. Subsequent stages may omit client to reuse the previous stage's client. DecisionQuestion values are requests or synchronous factories taking DecisionContext. Each factory must explicitly construct its own request. Context contains the original input (also original_input), output (absent before invocation), and prior nested decisions[stage][question]. Context does not authorize exporting input or output.

A require gate skips invocation only when an accepted primary answer differs from the required choice. Any fallback preserves normal invocation, even when its value differs. After stages annotate the completed response and cannot have require. .map_input(mapper) explicitly changes invocation input after before stages; without it, decisions do not change messages.

invoke / ainvoke return DecisionInvocationResult: status is completed or skipped, response is an InvokeResponse or None, decisions is the nested result mapping, and skipped_by identifies a blocking stage. batch / abatch accept an iterable of inputs and max_concurrency=4, preserve order and share a bound across decisions and invocations. Invocation keyword arguments pass through to the bound scope.

.structured_output(Model), .events(include=None, exclude=None, on_event=None, auto_verbose=True) and .allow_followup_questions(**kwargs) retain the decision stages and configure the existing invocation wrappers.

stream / astream forward ordinary invocation events and add decision.stage.started, decision.stage.completed and decision.plan.completed. The final DecisionStreamEvent.outcome holds the typed result locally and is excluded from serialized event data; read it before discarding the event.

from maivn.decisions import DecisionStreamEvent

for event in plan.stream('Summarize the public maintenance bulletin.'):
    if isinstance(event, DecisionStreamEvent) and event.outcome is not None:
        print(event.outcome.status)

Typed argument preparation

from maivn.decisions import DecisionArgument, DecisionArgumentPreparer

prepared = DecisionArgumentPreparer(decisions).prepare([
    DecisionArgument(
        name='notify', question=question, values={'yes': True, 'no': False},
    ),
])
# Preserve the existing value when the optional decision falls back.
notify = prepared.values.get('notify', True)
print(notify, prepared.decisions['notify'].source)

DecisionArgument(name, question, values) maps every choice exactly once to a boolean or string; its name must be a valid Python identifier, excluding keywords. DecisionArgumentPreparer.prepare / aprepare accept unique named arguments, max_concurrency=4 and native_batch=False. DecisionArgumentPreparation.values contains only accepted primary mappings; fallbacks leave the field absent so your existing argument preparation can continue. .decisions retains every result. It does not register a tool or automatically inject arguments into one.

Function and local tool dependencies

from maivn.decisions import (
    DecisionResults, DecisionStep, decision_flow, depends_on_decision,
)

@depends_on_decision(
    client=decisions,
    decisions=[DecisionStep(
        'triage', questions={'urgent': question}, require={'urgent': 'yes'},
    )],
    arg_name='decision_results',
)
def notify_bulletin(message: str, decision_results: DecisionResults) -> str:
    return message

call = decision_flow(notify_bulletin).invoke({'message': 'Maintenance bulletin'})
print(call.status, call.value)

depends_on_decision(*, client, decisions, arg_name=None, max_concurrency=4, state=None, native_batch=False) declares metadata. Direct Python calls keep their original behavior; use decision_flow(function) (a DecisionFunction) or register the decorated function as an SDK-local tool to execute its dependencies. A tool's injected decision parameter is hidden from its public input schema.

DecisionStep(name, *, questions, after=(), require=None, client=None, max_concurrency=4) declares a stage. after names a predecessor or tuple of predecessors within the same decorator group. Unknown predecessors and cycles are rejected; ready steps can run concurrently. A step may override its client. Factories receive a local snapshot of bound function arguments, including defaults, through DecisionContext.input. The optional synchronous state mapper narrows this snapshot once per factory stage; its result must support deep copying. A factory explicitly chooses which content goes into its exported request before authorization. Static requests need no input snapshot. arg_name injects nested results into a parameter annotated exactly as DecisionResults. The caller must not supply that hidden parameter.

Managed invoke / ainvoke accept a mapping of function arguments; batch / abatch accept iterables of mappings with max_concurrency=4. DecisionCallResult contains status (completed or skipped), the native return value (or None when skipped), nested decisions, skipped_by and category token usage. Gates apply the same accepted-primary rule as invocation builders. Fallbacks preserve function execution; errors from the function propagate. DecisionResults is the mapping type stage -> question -> DecisionResult.

Optional candidate matching

Matching operates on an already authorized, retrieved candidate set. It neither retrieves records nor grants access to them. It is disabled by default, with no primary call while disabled.

from maivn.decisions import (
    DecisionCandidateMatcher, DecisionMatchCandidate, DecisionMatchConfig,
    DecisionMatchRequest, candidate_match_questions,
)

match_request = DecisionMatchRequest(
    query='When is maintenance?',
    candidates=(DecisionMatchCandidate(key='bulletin', text=question.state),),
    baseline_keys=('bulletin',),
    privacy='public',
)
# Review the complete generated requests before enabling platform matching.
approved = candidate_match_questions(match_request)
match_client = DecisionClient(
    fallback=baseline, config=DecisionConfig(),
    authorize=lambda request: request in approved,
)
matcher = DecisionCandidateMatcher(match_client, DecisionMatchConfig())
match_result = matcher.match(match_request)
print(match_result.selected_keys, match_result.reason)  # ('bulletin',), disabled

DecisionMatchCandidate has a unique local key (1–512 characters) and excerpt (1–1,200 characters). DecisionMatchRequest has query (1–16,384 characters), a tuple of up to 16 candidates, unique baseline_keys from those candidates, privacy='unknown', and max_results=5 (1–16). DecisionMatchConfig defaults to enabled=False, max_candidates=5 (1–16), max_state_characters=8192 (1–16,384 serialized characters), and timeout_seconds=1.0 (positive finite).

candidate_match_questions(request) constructs public-only questions for a local authorization allowlist; it raises ValueError for private/unknown input. Source keys and baseline keys stay local; the model sees synthetic candidate labels and bounded text. There is no private_data option on matching requests: use only approved public text, or retain the baseline.

match / amatch, and ordered batch / abatch(max_concurrency=4), return DecisionMatchResult(selected_keys, source, reason, decisions, latency_seconds). Source is primary or baseline; reasons are accepted, blocked, disabled, empty, oversized, rejected, timeout, unavailable or no_match. Only when every candidate question has an accepted primary yes or no answer, and at least one matches, can selection change. Matches preserve candidate order and are capped at max_results. Any unknown, refusal, malformed answer, timeout, privacy failure or no-match result preserves the exact baseline_keys, without truncating or reordering them. Matching uses its own local abstaining fallback rather than spending the decision client's caller fallback.

Usage types and privacy guidance

TokenUsageCategory is decision or llm; TokenUsageByCategory holds one TokenCategoryUsage per category. Each category has input_tokens, output_tokens, total_tokens, known_input_tokens, known_output_tokens, unknown_input_calls and unknown_output_calls. Unknown totals remain None; known subtotals remain available. summarize_decision_usage(results) accepts a collection of decision results. All workflow outcomes expose token_usage_by_category; aggregating native receipts counts each owner once. These counters alone do not prove cost savings or production qualification.

Ordinary InvokeResponse.privacy_guidance contains advisory over-redaction suggestions when delivered by the platform. Guidance includes the actual delivered value; it does not change redaction or reveal model input. repr=False keeps it out of the response representation, but model_dump() and JSON serialization still include it. Display it only in a trusted developer interface; do not log or persist the guidance or serialize whole responses into diagnostics. Normalized SDK event builders do not guarantee a guidance field. Decision results have no privacy_guidance field.

Metadata

Release files for maivn 2.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 maivn 2.2.0
File Size Uploaded Provenance
maivn-2.2.0.tar.gz 373.4 kB With provenance Details

Built distribution (wheel)

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

Total release size: 847.2 kB

Release files / maivn-2.2.0.tar.gz

Download URL maivn-2.2.0.tar.gz
Size 373.4 kB
Tags Source
SHA-256 checksum
How to use checksums
19ecbb4a61e9afd2b19e84d57312590bca1133368377cedd8d609c8c9ac6ac48
BLAKE2b-256 checksum
How to use checksums
fb729f8a4a3b80ac3d5b5a2231f00d407590e1c8d4690a3aced8d36b53e883da
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 Oct 9, 2026.

Transparency log

Release files / maivn-2.2.0-py3-none-any.whl

Download URL maivn-2.2.0-py3-none-any.whl
Size 473.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a69051597bdf4faf1c4a846794b93e103899e1c285fa636a2812f5069792464b
BLAKE2b-256 checksum
How to use checksums
62f2cc908750b28d13ade2b964777178abf8b58346499f0fdba9e68c975d251c
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 Oct 9, 2026.

Transparency log

Security for maivn 2.2.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 maivn-2.2.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 9, 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.

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