Metadata-Version: 2.4
Name: marona
Version: 0.3.7
Summary: Use Marona to build AI apps for any AI interface, edge device, or intelligent agent. Build once and deploy online, offline, or in hybrid environments through a unified runtime.
Author: Blessing Nyuwani
License-Expression: MIT
Project-URL: Homepage, https://www.marona.ai
Project-URL: Documentation, https://hub.marona.ai
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.9
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.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0,>=0.28
Provides-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

Official Python client for Marona-compatible AI runtimes and Hub integrations.

Use Marona to build AI apps for any AI interface, edge device, or intelligent
agent. Build once and deploy online, offline, or in hybrid environments through
a unified runtime.

## Install

```bash
pip install marona
```

## Complete Example

This example shows the normal flow for a developer application:

1. Create a Marona client.
2. Sync Hub metadata into the local cache.
3. Connect approved Hub apps by app ID.
4. Send a user message through the runtime.
5. Print the final assistant response.

The optional `developer` role controls app behavior.

```python
import asyncio
import os

from marona import Marona


async def main() -> None:
    api_key = os.environ["MARONA_API_KEY"]
    identity_token = os.getenv("MARONA_IDENTITY_TOKEN")  # Optional

    async with Marona(api_key=api_key, mode="online") as marona:
        await marona.sync(
            interface="api",
            identity_token=identity_token,
        )

        tools = await marona.hub.connect(
            ["sda-books", "zimsec"],
            adapter="tools",
        )

        response = await marona.client(
            input=[
                {
                    "role": "developer",
                    "content": "Keep answers clear and concise.",
                },
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "input_text",
                            "text": "What teams are playing in this image?",
                        },
                        {
                            "type": "input_image",
                            "image_url": "https://api.nga.gov/iiif/a2e6da57-3cd1-4235-b20e-95dcaefed6c8/full/!800,800/0/default.jpg",
                        },
                        {
                            "type": "input_file",
                            "filename": "document.pdf",
                            "file_data": "data:application/pdf;base64,...",
                            "detail": "high",
                        },
                    ],
                }
            ],
            interface="api",
            identity_token=identity_token,
            tools=tools,
        )

        print(response.text)


asyncio.run(main())
```

## Build And Publish Skills

Skills define reusable, ordered workflows over Apps. Publishing validates step
IDs, references, required Apps, exact capabilities, and the immutable version.

```python
from marona import Marona
from marona.skills import app, approval, reasoning, skill


@skill(
    name="create-group-fund",
    description="Create a group fund after explicit user approval.",
    governs=["group-fund.create_group"],
)
def create_group_fund():
    request = reasoning(
        id="understand-request",
        instruction="Extract the group name and currency.",
        inputs={"message": "{{ context.user_message }}"},
        outputs={"name": "string", "currency": "string"},
    )
    permission = approval(
        id="confirm-create",
        message=f"Create '{request.name}' in {request.currency}?",
    )
    return app(
        id="create-group",
        app="group-fund",
        capability="group-fund.create_group",
        instruction="Create the approved group.",
        condition=permission.approved,
        inputs={"name": request.name, "currency": request.currency},
        outputs={"group_id": "string", "name": "string"},
    )


marona = Marona(api_key=os.environ["MARONA_API_KEY"])
marona.skills.publish(create_group_fund, version="1.0.0")
```

`marona.sync()` synchronizes published Apps and Skills. Users still call
`marona.client(...)`; the model discovers a matching Skill and the runtime
enforces its ordered steps and approvals. There is no separate Skill run API.
`governs` declares raw capabilities that must only run through that workflow.

Run it:

```bash
export MARONA_API_KEY="mrn_live_..."
python app.py
```

## Pair A User

Use pairing when an interface needs to become the same user across web, mobile,
WhatsApp, wearables, or another client surface.

```python
async with Marona(api_key=os.environ["MARONA_API_KEY"]) as marona:
    pairing = await marona.start_pairing(
        interface="web",
        device_name="Customer web chat",
    )

    print(pairing.display_code)
    print(pairing.whatsapp_url)

    status = await marona.pairing_status(pairing.pairing_id)
    print(status.status)
```

After the user confirms pairing, store the returned identity token and pass it
to `marona.client(...)`.

## Online, Offline, And Hybrid Modes

Choose one runtime mode for the client:

- `online`: use online runtime and online app routes.
- `offline`: use only local cache, local/private models, and installed offline-capable app targets.
- `hybrid`: try local/private execution first, then use online runtime when allowed.

Apps also declare one availability mode:

- `online`: online only.
- `offline`: offline only.
- `hybrid`: online and offline capable.

Hybrid is a single mode. Do not declare `online + offline + hybrid`; declare
`hybrid`.

## Hybrid Or Offline With A Local/Private Model

Register a local or private model endpoint once, then use it as the default for
hybrid or offline execution.

```python
async with Marona(api_key=os.environ["MARONA_API_KEY"], mode="hybrid") as marona:
    await marona.sync(interface="api")

    marona.models.register(
        name="office-model",
        endpoint="http://localhost:9379",
    )
    marona.models.use("office-model")

    tools = await marona.hub.connect(["sda-books", "zimsec"], adapter="tools")

    response = await marona.client(
        input=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_text",
                        "text": "Summarize the chapter about faith.",
                    }
                ],
            }
        ],
        interface="desktop",
        tools=tools,
    )

    print(response.text)
```

In `offline` mode, Marona never calls public cloud runtime. If a model or an
offline-capable app target is missing, the client returns a clear offline error.

Synced conversation context and connected tool schemas are supplied to the
configured model on every turn. The current user prompt remains the active task,
and the model decides whether to answer directly or call an available tool.

## Interface Names

`interface` identifies the client surface making the request. Standard values:

- `api`
- `web`
- `mobile_app`
- `desktop`
- `whatsapp`

Future devices can use custom lowercase slugs such as `smart_glasses`,
`vehicle_console`, or `kiosk`.

## Related Packages

- Python client: `marona`, import `marona`
- Dart client: `marona`, import `package:marona/marona.dart`
- TypeScript client: `marona`, import `Marona` from `"marona"`
- Developer SDK for building apps: `marona-sdk`, import `marona_sdk`
