Metadata-Version: 2.4
Name: marona
Version: 0.5.5
Summary: Unified AI runtime and MCP/Skill gateway for online, hybrid, and offline applications.
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
Requires-Dist: openai-agents<1.0,>=0.4
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

Unified AI runtime and MCP/Skill gateway for Python.

```bash
pip install marona
```

Marona has three request concepts:

- `mode`: where execution is allowed: `online`, `hybrid`, or `offline`.
- `model`: the model handling this request.
- `input`: the user's text, image, document, or mixed request.

Apps, Skills, discovery, identity, permissions, approvals, service connections,
and MCP execution stay inside the same Marona runtime.

## 1. Configure Access

`MARONA_API_KEY` authenticates your application with Marona.

```bash
export MARONA_API_KEY="mrn_live_..."
export OPENAI_API_KEY="..."          # Only for openai/...
export ANTHROPIC_API_KEY="..."       # Only for anthropic/...
export GEMINI_API_KEY="..."          # Only for gemini/...
export LITELLM_API_BASE="https://llm.example.com"
export LITELLM_API_KEY="..."
export OLLAMA_API_BASE="http://127.0.0.1:11434"
```

## 2. Send A Request

```python
import asyncio
import os
from marona import Marona


async def main():
    identity_token = os.environ["MARONA_IDENTITY_TOKEN"]

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

        response = await marona.client(
            model="marona/default",
            input=[{"role": "user", "content": "What is my group fund?"}],
            interface="api",
            identity_token=identity_token,
        )
        print(response.text)


asyncio.run(main())
```

## 3. Images And Documents

```python
response = await marona.client(
    model="openai/gpt-4o",
    input=[
        {
            "role": "developer",
            "content": "Keep answers clear and concise.",
        },
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": "Compare the image and report."},
                {"type": "input_image", "image_url": "https://example.com/match.jpg"},
                {
                    "type": "input_file",
                    "filename": "report.pdf",
                    "file_data": "data:application/pdf;base64,...",
                    "detail": "high",
                },
            ],
        },
    ],
    interface="api",
    identity_token=identity_token,
)
```

## 4. Change Models

The request shape does not change:

```python
model="marona/default"
model="openai/gpt-4o"
model="anthropic/claude-sonnet-4"
model="gemini/gemini-2.5-pro"
model="ollama/llama3.2"
model="litellm/local-llama"
model="office/company-assistant"
model="local/gemma-4-e2b"
```

Known providers need no registration. Register only custom access or an
in-process model:

```python
async def office_native_adapter(request):
    result = await office_sdk.generate(request)
    return {"choices": [{"message": {"role": "assistant", "content": result.text}}]}


marona.models.register(
    name="office/company-assistant",
    provider="custom",
    endpoint="https://models.office.example/v1",
    model="company-assistant-v2",
    api_key=os.environ["OFFICE_MODEL_API_KEY"],
    adapter=office_native_adapter,
)

marona.models.register(
    name="local/gemma-4-e2b",
    executor=gemma_executor,
    context_window=2048,
    max_output_tokens=128,
)
```

Registration configures access. Select the model on each `client(...)` or
`message(...)` request. `models.use(...)` is deprecated.

## 5. Install Apps For Offline Use

Install only while network access is available. Marona downloads and verifies
the selected offline packages, imports their synchronized state, and indexes
their capabilities locally.

```python
await marona.hub.install(
    apps=["sda-books", "zimsec"],
    identity_token=identity_token,
)
```

Marona imposes no fixed installed-App limit. The practical limit is available
device storage divided by each package and its data size.

## 6. Connect Installed Apps

In offline mode, `connect()` performs no network request. It exposes one lazy
discovery gateway backed by the local SQLite capability index.

```python
tools = await marona.hub.connect(
    apps=["sda-books", "zimsec"],
    identity_token=identity_token,
)
```

Omit `apps` to expose every App installed on this device:

```python
tools = await marona.hub.connect(
    identity_token=identity_token,
)
```

The model initially receives only `marona__discover_capabilities`. Marona
searches `capabilities.sqlite` after the model requests discovery, supplies only
the matching exact schemas, and executes the selected package locally.

## 7. Use Marona Tools In An OpenAI Agent

`connect()` returns executable OpenAI Agents SDK `FunctionTool` objects already
bound to Marona. Pass them directly to `Agent(tools=tools)`:

```python
from agents import Agent


tools = await marona.hub.connect(
    apps=["sda-books", "zimsec"],
    skills=["create-group-fund"],
    identity_token=identity_token,
    conversation_id="conversation-1",
    model="openai/gpt-4o",
)

agent = Agent(
    name="Marona OpenAI Tool Agent",
    model="gpt-5-mini",
    tools=tools,
)
```

When a Skill returns `status="planned"`, call it again only after the user
approves, passing `{"request": "Yes", "approved": True}`.

Calling a returned tool automatically executes its original descriptor through
Marona. App tools execute their MCP target. Skill tools execute through Marona's
governed runtime. Internal Skill steps and Skill-managed raw capabilities are
not exported to the agent.

## 8. Publish A Skill

Every workflow entry uses `step()`; `type` selects reasoning, approval, or App
execution.

```python
from marona.skills import skill, step


@skill(
    name="create-group-fund",
    description="Create a group fund after explicit user approval.",
    governs=["group-fund.create_group"],
)
def create_group_fund():
    request = step(
        id="understand-request",
        type="reasoning",
        instruction="Extract the group name and currency.",
        inputs={"message": "{{ context.user_message }}"},
        outputs={"name": "string", "currency": "string"},
    )
    permission = step(
        id="confirm-create",
        type="approval",
        message=f"Create '{request.name}' in {request.currency}?",
        outputs={"approved": "boolean"},
    )
    return step(
        id="create-group",
        type="app",
        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.skills.publish(create_group_fund, version="1.0.0")
```

## Mode Rules

- `online`: network models and online MCP targets are allowed.
- `hybrid`: try the selected local/private model, then use Edge fallback.
- `offline`: use cached data, a local model, and installed offline-capable targets only.

Changing `model` never changes App, Skill, permission, approval, or MCP rules.
