Versioning & Stability
What the SDK version and event-contract version mean, which surfaces are stable, and how deprecations are communicated.
#Versioning & Stability
mAIvn ships two version numbers, and they answer two different questions. One tells you which release of the SDK you have installed. The other tells event consumers (your backend, your frontend, your webhooks) which shape of streaming events to expect. This page explains both, what counts as stable versus experimental, and how we tell you when something is changing.
#What this is
When you build on top of any SDK, two things can move underneath you: the code you import and the data that flows out of it. mAIvn versions each of these separately so that a change to one never silently surprises consumers of the other.
- The SDK version (
maivn.__version__) is the release of themaivnpackage on your machine. Check its release notes and test upgrades; a version number alone does not establish compatibility. - The event contract version (
APP_EVENT_CONTRACT_VERSION) describes the shape of the normalized events your application receives when it streams an agent's execution. It travels stamped onto every event, so a consumer can read it off the wire and decide how to handle the payload.
In plain terms: the SDK version is for you, the developer upgrading a dependency. The contract version is for the systems that read your agent's output, especially a frontend you do not redeploy in lockstep with your backend. The rest of this page makes both precise, with runnable examples.
#The SDK version (__version__) and what semver means for you
The installed release is available as a string on the top-level package and is part of the public surface:
import maivn
print(maivn.__version__)It is also exposed through the CLI for quick diagnostics, useful in a bug report or a CI log:
maivn version
# Prints the installed SDK version.The SDK currently uses a 0.x version. Under semantic versioning, major version zero denotes initial development and does not promise a stable API. Pin the version you tested, review changes before upgrading, and run your integration tests against the new package.
For example, a PEP 621 pyproject.toml dependency goes in a list:
[project]
# Illustrative pin: select the release qualified for your application.
dependencies = ["maivn==0.1.0"]A requirements.txt uses maivn==0.1.0 on its own line. Neither format uses a standalone maivn = "..." assignment. Keep package pins and deployed service compatibility under review together.
#The AppEvent contract version (APP_EVENT_CONTRACT_VERSION) and why it matters for event consumers
When you stream an agent's execution, the raw transport events are normalized into a stable, app-facing shape called AppEvent (see the Events reference and the Frontend Events guide). Every normalized event carries the contract version it was produced under:
from maivn.events import APP_EVENT_CONTRACT_VERSION
print(APP_EVENT_CONTRACT_VERSION) # "v1"The same constant is also importable from the events module, which is where most event-handling code lives:
from maivn.events import APP_EVENT_CONTRACT_VERSIONEvery AppEvent is stamped with it. You can read it off any event you receive:
from maivn import Agent
from maivn.events import RawSSEEvent, normalize_stream
from maivn.messages import HumanMessage
agent = Agent(name="assistant", api_key="your-api-key")
raw_events = (
RawSSEEvent(name=event.name, payload=event.payload)
for event in agent.stream([HumanMessage(content="Hello!")])
)
for event in normalize_stream(raw_events):
assert event.contract_version == "v1"
send_to_frontend(event.model_dump())Why this is separate from the SDK version. The SDK version changes when you upgrade a dependency. The contract version changes only when the shape of the events changes, and those two events rarely happen at the same time. The classic case is a frontend you ship independently of your backend: a browser app may run for weeks against a backend you have since upgraded. By keying on the contract version rather than the package version, that frontend can recognize which event shape it is being handed and adapt, without ever knowing which maivn release produced it.
How the contract evolves without breaking you. The contract is designed to grow additively:
- The nested descriptors on
AppEvent(tool,assistant,assignment,enrichment,interrupt,output,error_info, and the rest) are declared with Pydantic'sextra="allow". New fields can appear on a descriptor without invalidating consumers that validate the payload. Unknown fields are accepted, not rejected. - Normalized payloads intentionally preserve the older flat fields alongside the newer nested descriptors, so a consumer written against an earlier shape can keep reading the fields it already knows while it migrates.
- Unknown or custom event names pass through normalization unchanged, which is the supported path for domain-specific event extensions (set
event_kindto"custom"and include bothcontract_versionandevent_name).
A new value of APP_EVENT_CONTRACT_VERSION (for example, a future "v2") is reserved for changes that are not purely additive: a rename or a removal that older consumers cannot absorb. Because the version rides on every event, you can support more than one frontend at a time by branching on it:
def handle(event):
if event.contract_version == "v1":
render_v1(event)
else:
# A version you do not recognize: degrade gracefully rather than crash.
log.warning("Unrecognized event contract version: %s", event.contract_version)
render_best_effort(event)Tip Preservecontract_versionend to end. When you forwardevent.model_dump()from your backend to your frontend, the field comes along for free. Keep it on the wire so the frontend can make the same decision the backend could.
This contract is the same one your own application routes expose when you mount the event stream. For example, mount_events(app) adds GET /maivn/events/{session_id} to your FastAPI app, and the events it serves carry the same contract_version your frontend can branch on:
from fastapi import FastAPI
from maivn.events.fastapi import mount_events
app = FastAPI()
mount_events(app) # -> GET /maivn/events/{session_id}#Which surfaces are stable vs experimental
The public boundary is defined by the __all__ exports of maivn, maivn.events, and maivn.messages. The owned wire contracts they expose come from the installed maivn-contracts dependency.
Stable — import and rely on these:
- The core classes:
Agent,Swarm,Client,ClientBuilder,BaseScope,MCPServer,MCPAutoSetup,ToolOverride. - The decorators:
tool_output,toolify,toolset,depends_on_tool,depends_on_agent,depends_on_private_data,depends_on_interrupt,depends_on_await_for,depends_on_reevaluate, andrequires_producer_reference. - Public models exported through
maivn, includingInvokeResponse, privacy/configuration models, permissions, memory models, and provider metadata. Per-runtoken_usageis a dictionary property onInvokeResponse, not a public model. - The
maivn.eventssurface:AppEvent,RawSSEEvent,EventBridge,normalize_stream, thebuild_*_payloadhelpers, andAPP_EVENT_CONTRACT_VERSION. - Configuration, logging, scheduling, and interrupt helpers exported at the top level.
- Public SDK exceptions exported from
maivn, led byMaivnSDKErrorandMaivnHTTPError.
Note The authoritative list of the public surface is the package's__all__. If a name appears there (and on the per-class pages underapi/), it is part of the supported interface. If it does not, treat it as internal.
Internal — do not import or depend on these:
Anything under the maivn._internal package is private. The single-underscore name is the signal: these are implementation details that can change, move, or disappear in any release, including patch releases, with no notice and no deprecation cycle. This holds even though some internal symbols are re-exported under their public import path. Rely on the public path, never the _internal one.
Catch public SDK-owned failures through MaivnSDKError, and handle typed HTTP responses through MaivnHTTPError:
from maivn import Agent, MaivnHTTPError, MaivnSDKError
from maivn.messages import HumanMessage
agent = Agent(name="assistant", api_key="your-api-key")
try:
response = agent.invoke([HumanMessage(content="Hello!")])
except MaivnHTTPError as exc:
log.error("mAIvn API failed (%s): %s", exc.status_code, exc.reason)
raise
except MaivnSDKError as exc:
log.error("mAIvn SDK failed: %s", exc)
raiseImporting a concrete type from maivn._internal is unsupported. That path can change at any release. See Errors and Exceptions for the current public hierarchy.
#How deprecations are communicated
Read the release notes and migration guidance for the specific SDK version you intend to install. Do not assume that a compatibility alias or an internal helper will remain available because it imported successfully in an older build.
To expose Python deprecation warnings during development:
python -W default::DeprecationWarning your_app.pyWarnings help where the relevant path emits one; their absence does not prove that an upgrade is compatible. Test public API usage and normalized event payloads before updating a production dependency.
#Next steps
- Overview — what mAIvn is, the one mental model, and your first agent.
- Core Concepts — the building blocks the public surface is made of.
- SDK Reference — the per-class home of the public surface; the
__all__of every page. - Events — the full
AppEventcontract, normalization helpers, and payload builders. - Frontend Events guide — how to stream the contract to a browser and key UI state off
contract_version. - Errors & Exceptions — the public SDK exception hierarchy and transport behavior.
- Troubleshooting — the concrete errors you may see and how to fix each one.
- Shared Data Models — result and privacy model shapes, including
InvokeResponseand its normalized token-usage dictionary.