Metadata-Version: 2.4
Name: marona
Version: 2.4.0
Summary: Agent SDK with extensible communication protocols, B2A, structured responses, MCP tools and A2A.
Author: Blessing Nyuwani
License-Expression: MIT
Project-URL: Homepage, https://www.marona.ai
Project-URL: Documentation, https://platform.marona.ai/docs
Keywords: marona,sdk,mcp,agents,ai,runtime
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0,>=0.28
Requires-Dist: pydantic<3.0,>=2.7
Requires-Dist: websockets<17,>=15
Provides-Extra: b2a
Requires-Dist: mcp<2,>=1.28; extra == "b2a"
Provides-Extra: dev
Requires-Dist: mcp<2,>=1.28; extra == "dev"
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
Requires-Dist: build<2.0,>=1.2; extra == "dev"
Requires-Dist: twine<7.0,>=6.0; extra == "dev"
Dynamic: license-file

# Marona Python SDK

Run Agents through one managed API, with structured output, local tools,
Hub capabilities, streaming, realtime and A2A collaboration.

## Install

Version **2.4.0**. [Package registry](https://pypi.org/project/marona/2.4.0/).

Python 3.10+.

```bash
python -m pip install --upgrade marona==2.4.0
```

Set `MARONA_API_KEY` in your backend environment. Never commit it, log it,
or embed a developer key in browser or mobile code. Use an exact accessible
`marona/*` model alias from [Marona Platform](https://platform.marona.ai/).

## Agent quickstart

Save as `examples/agent_example.py`. Run without arguments for text, or with a real
PNG/JPEG path for document extraction. Use stable application user IDs and a
distinct session ID for each conversation.

```python
import asyncio
import base64
import os
import sys
from pathlib import Path

from marona import Agent, Marona, Runner


async def main():
    async with Marona(
        api_key=os.environ["MARONA_API_KEY"],
        base_url=os.getenv("MARONA_RUNTIME_URL", "https://edge.marona.ai"),
    ) as marona:
        agent = Agent(
            name="Document Assistant",
            model=os.getenv("MARONA_MODEL", "marona/qwen-qwen3.8-max-0902"),
            instructions="Answer clearly and concisely.",
            tool_choice="none",
        )
        user_input = "Reply with exactly OK."
        if len(sys.argv) > 1:
            path = Path(sys.argv[1])
            mime = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"}[path.suffix.lower()]
            data_url = f"data:{mime};base64," + base64.b64encode(path.read_bytes()).decode("ascii")
            user_input = [{"role": "user", "content": [
                {"type": "input_text", "text": "Describe this image and transcribe any visible text."},
                {"type": "input_image", "image_url": data_url},
            ]}]
        result = await Runner.run(
            agent, user_input, user_id="example-user", session_id="example-session"
        )
        print(result.output)


if __name__ == "__main__":
    asyncio.run(main())
```

```bash
python examples/agent_example.py
python examples/agent_example.py image.png
```

Encode actual file bytes with standard Base64. Image and file blocks accept
raw Base64 in `image_data` / `file_data`, or complete matching data URLs.
Do not encode a path, encode twice, or send placeholder data. Remote URLs must
be reachable by the runtime. PDF, audio and realtime support depend on the
selected model; render PDF pages to images when native PDF is unavailable.

## Structured output through Agent

The following fragment uses the authenticated client from the quickstart.
`document_input` is your text or document message input.

```python
from pydantic import BaseModel, ConfigDict
from marona import Agent, Runner

class Extraction(BaseModel):
    model_config = ConfigDict(extra="forbid")
    text: str

agent = Agent(
    name="Document Reader",
    model="marona/qwen-qwen3.8-max-0902",
    instructions="Extract the document text.",
    output_type=Extraction,
    output_schema_strict=True,
)
result = await Runner.run(agent, document_input, user_id="user-123")
print(result.output.text)
```

The explicit strict option asks the provider to enforce the supplied schema;
the SDK also validates the final result before returning it. Strict mode requires
a compatible model and schema. Declare every object property as required, use
nullable values for optional fields, and set additionalProperties to false.
Unsupported model capabilities or invalid output produce errors; they do not silently fall
back to unstructured text. Input schemas, function tool schemas, guardrails,
dynamic instructions and lifecycle hooks are also supported.

## Apps and user connections

```python
page = await marona.apps.list_async(user_id="user-123", limit=20)
action = await marona.apps.connect_async("calendar", user_id="user-123")
# Complete any returned authorization_url before calling protected tools.
await marona.apps.disconnect_async("calendar")
```

Listing an App does not authorize its protected operations. Follow the returned
connection action for the end user. Use `hub.connect` to expose a scoped set of
App or Skill capabilities to an Agent; Apps authorization and Hub tool discovery
are separate operations.

## A2A collaboration and serving

```python
import os
from marona import A2APeer, A2AServer, Agent

peer = A2APeer(name="reviewer", url="https://reviewer.example.com")
coordinator = Agent(name="Coordinator", peers=[peer])
server = A2AServer(
    agent=coordinator,
    url="https://coordinator.example.com",
    skills=[{"id": "review", "name": "Review", "description": "Review a document"}],
)
# Apply production authentication and TLS at your service boundary.
server.run(host="127.0.0.1", port=8100)
```

Peers support Agent Card discovery, REST/JSON-RPC messages, task retrieval,
cancellation, continuation and streamed task events. Servers persist task state.
Use explicit credentials, restricted skills, HTTPS, and an appropriate durable
task directory for production. Local JSON stores are for a single service
instance; multi-instance deployments require coordinated task storage.


Push notifications are not implemented.

## Brain2Agent (B2A)

Native B2A v0.1 exchanges decoded semantic intent and agent responses. The
independent neural service owns signal decoding and feedback encoding, with
vendor-hosted MCP, HTTP or local plugin backends. Models and device drivers
are supplied by the interface provider. Configure `Agent.protocols` and connect with
`Runner.connect`, following the persistent realtime session lifecycle. These examples
read one captured signal file using your configured provider.

One configured protocol is selected automatically. With several configured protocols,
select by name (`protocol="b2a"` in Python, the corresponding option/overload in
other SDKs). Duplicate names and ambiguous selection are rejected. Implement
`AgentProtocol` to add another protocol without adding Agent properties. Existing
A2A peers and servers retain their agent-to-agent role; they are not automatically
converted into input protocols. `Agent.b2a` and `B2ARunner` remain compatibility APIs.

Save as `examples/b2a_intent.py`:

```python
"""Run a captured signal through an independent provider and an Agent-bound B2A session."""

import asyncio
import json
import os
import sys
from pathlib import Path
from marona import Agent, B2A, B2ACodec, Runner, Marona

INSTRUCTIONS = """Interpret the decoded intent using its context.
Preserve uncertainty and ask for clarification when needed.
Use connected tools under Marona's permissions and confirmation requirements.
"""


async def main():
    signal = json.loads(Path(sys.argv[1]).read_text())
    neural_service = B2ACodec(
        os.environ["B2A_CODEC_URL"],
        token=os.environ["B2A_NEURAL_TOKEN"],
        transport=os.environ.get("B2A_CODEC_TRANSPORT", "mcp"),
    )
    async with Marona() as client:
        agent = Agent(
            name="B2A Intent Agent",
            model=os.environ.get("MARONA_MODEL", "marona/auto"),
            instructions=INSTRUCTIONS,
            tools=client.hub.connect(),
            protocols=[B2A(
                provider=neural_service, output_profile=os.environ["B2A_OUTPUT_PROFILE"]
            )],
        )
        async with await Runner.connect(
            agent,
            user_id=os.environ["B2A_USER_ID"],
            session_id=os.environ["B2A_SESSION_ID"],
        ) as session:
            await session.send_signal(signal)
            async for event in session:
                print(event)
                if event.type in {"feedback", "signal.status", "error"}:
                    break  # This file sends one captured window; keep open for continuous input.


if __name__ == "__main__":
    asyncio.run(main())
```

### Single run with structured output

Save as `examples/b2a_structured.py`. This opens a connection, decodes the signal, runs
the agent, validates the response, encodes feedback, and closes the connection.

```python
"""Run a captured signal through an independent provider and an Agent-bound B2A session."""

import asyncio
import json
import os
import sys
from pathlib import Path
from pydantic import BaseModel, ConfigDict

from marona import Agent, B2A, B2ACodec, Runner, Marona

class NeuralResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")
    text: str


INSTRUCTIONS = """Interpret the decoded intent using its context.
Preserve uncertainty and ask for clarification when needed.
Use connected tools under Marona's permissions and confirmation requirements.
"""


async def main():
    signal = json.loads(Path(sys.argv[1]).read_text())
    neural_service = B2ACodec(
        os.environ["B2A_CODEC_URL"],
        token=os.environ["B2A_NEURAL_TOKEN"],
        transport=os.environ.get("B2A_CODEC_TRANSPORT", "mcp"),
    )
    async with Marona() as client:
        agent = Agent(
            name="B2A Intent Agent",
            model=os.environ.get("MARONA_MODEL", "marona/qwen-qwen3.8-max-0902"),
            instructions=INSTRUCTIONS,
            output_type=NeuralResponse,
            output_schema_strict=True,
            tools=client.hub.connect(),
            protocols=[B2A(
                provider=neural_service, output_profile=os.environ["B2A_OUTPUT_PROFILE"]
            )],
        )
        result = await Runner.run(
            agent, signal,
            user_id=os.environ["B2A_USER_ID"],
            session_id=os.environ["B2A_SESSION_ID"],
        )
        print(result.output.text)



if __name__ == "__main__":
    asyncio.run(main())
```

Python returns the declared Pydantic model in `result.output`. Other SDKs validate
the declared JSON schema and expose their language's structured output representation.
Invalid output is rejected before encoding. Decoder abstention, required approval,
or failure does not become a successful typed result; use a persistent connection
for interactive clarification, approvals, and feedback delivery acknowledgments.

The SDK manages protocol messages, sequencing, event cursors, decoding and encoding.
Set B2A_CODEC_URL, B2A_NEURAL_TOKEN, B2A_OUTPUT_PROFILE, B2A_USER_ID and
B2A_SESSION_ID. Use B2A_CODEC_TRANSPORT=mcp or http. `session_id` identifies
an application conversation; each connection gets a separate native transport
session. The application authorizes processing before connecting; device and
protocol scope fields are supplied internally. Closing revokes those scopes.

Keep the session open to send more signals. Feedback events contain encoded
output; printing them does not deliver feedback. An interface driver acknowledges
presentation only after delivery. Failed or uncertain sends are never replayed
automatically. Native B2A supports server-hosted Hub tools and agent graphs;
SDK-local functions, callbacks and peer callbacks require server-hosted equivalents.
B2A v0.1 uses HTTP JSON and retained cursor events, not WebSocket transport.
See the repository's `docs/b2a-protocol.md` for the complete wire contract.

## Execution and errors

Use `Runner.stream` for streamed Agent events and
`RealtimeRunner` for a live session. Delegation and handoffs use explicit Agent
graphs. Set execution limits and validate permissions before tools with side
effects; cancellation cannot roll back an operation that has already executed.

Inspect the canonical error code, HTTP status, request ID and retry metadata.
Correct invalid input and authorization errors before retrying. Use bounded
retries for transient failures. Never include credentials or document content
in error reports.

See [Platform documentation](https://platform.marona.ai/docs) for language
specific examples and migration guidance. These files are generated from
`docs/client-sdk-guides.json`; verify them with
`python scripts/sync_client_sdk_docs.py --check`.

Communication protocols are configured through `protocols`; `Runner.connect` opens persistent connections and `Runner.run` completes a single input. The 2.3 B2A entry points remain compatible.
