Metadata-Version: 2.4
Name: marona
Version: 0.7.3
Summary: Provider-neutral AI agent runtime and MCP/Skill gateway for Marona Hub connections, managed agents, and bring-your-own-agent integrations.
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 Python SDK

Provider-neutral AI agent runtime and MCP/Skill gateway for Marona Hub
connections, managed agents, and bring-your-own-agent integrations.

```bash
pip install marona
```

## 1. Marona Hub

Connect Apps and governed Skills as one neutral MCP tool collection.

```python
from marona import Marona

marona = Marona(api_key="YOUR_MARONA_API_KEY")

# Connect every App and Skill available to this developer key.
connection = marona.hub.connect()
```

Select a smaller capability set when needed:

```python
connection = marona.hub.connect(
    apps=["group-fund"],
    skills=["create-group-fund"],
)
```

Use the connection directly:

```python
tools = connection.list_tools()
result = connection.call_tool(
    "skill__create_group_fund",
    {"request": "Create a family savings group fund"},
)
```

`MCPConnection` is not tied to OpenAI, LangGraph, CrewAI, or another model
vendor. It exposes:

```python
connection.list_tools()
connection.call_tool(name, arguments)
connection.server_url
connection.server_urls
connection.warnings
connection.session
```

An unresolved App or Skill name does not discard valid tools. Check
`connection.warnings` for its `code`, `selector_type`, `slug`, and corrective
message. Authentication, permission, and configured-server failures
remain blocking errors.

Marona Hub owns discovery, identity, permissions, App and Skill resolution,
approvals, governed execution, and online, offline, or hybrid availability.
With no selectors, online and hybrid connections include every capability
available to the developer key; offline connections include every capability
installed on the device.

Use the asynchronous methods inside an event loop:

```python
connection = await marona.hub.connect_async(
    apps=["group-fund"],
    skills=["create-group-fund"],
)
tools = await connection.list_tools_async()
result = await connection.call_tool_async(
    "skill__create_group_fund",
    {"request": "Create a family savings group fund"},
)
```

## 2. Marona Agent

Use Marona's `Agent` and `Runner` when you want one simple managed agent API.

```python
from marona import Agent, Marona, Runner

marona = Marona(api_key="YOUR_MARONA_API_KEY")

tools = marona.hub.connect(
    apps=["sda-books"],
)

agent = Agent(
    name="Customer Assistant",
    model="gpt-5.6",
    instructions="Help the customer.",
    tools=tools,
)

result = await Runner.run(
    agent,
    "Download Steps to Christ",
    user_id="customer_482",
    session_id="chat_91a7",
)

print(result.final_output)
```

`user_id` is optional. `session_id` is also optional and defaults to `default`.
Marona derives the developer scope from the authenticated API key and keeps
conversation history isolated by developer, user, and session. In synchronous
applications, use `Runner.run_sync(...)`.

## 3. Marona Runtime

Use `responses.create(...)` when Marona should manage model reasoning, tool
selection, permission and approval checks, execution, and the final response.

```python
from marona import Marona

marona = Marona(api_key="YOUR_MARONA_API_KEY")

tools = marona.hub.connect(
    apps=["group-fund"],
    skills=["create-group-fund"],
)

response = marona.responses.create(
    model="gpt-5.6",
    tools=tools,
    input="Create a family savings group fund",
)

print(response.output)
```

The same request supports managed, direct-provider, private, and local models:

```python
model="gpt-5.6"                    # Marona-managed model
model="openai/gpt-5.6"
model="anthropic/claude-sonnet"
model="google/gemini"
model="ollama/qwen3"
model="litellm/local-qwen"
model="local/qwen"
```

Register only custom providers or downloaded in-process models:

```python
marona.models.register(
    name="office/company-assistant",
    endpoint="https://models.office.example/v1",
    model="company-assistant-v2",
    api_key="YOUR_PROVIDER_KEY",
)

marona.models.register(
    name="local/qwen",
    executor=qwen_executor,
    context_window=8192,
    max_output_tokens=512,
)
```

For asynchronous applications, call `await marona.responses.create_async(...)`.

### Images

```python
from marona import Marona

marona = Marona(api_key="YOUR_MARONA_API_KEY")

response = marona.responses.create(
    model="openai/gpt-5.6",
    input=[
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": "Summarize this image."},
                {"type": "input_image", "image_url": "https://example.com/image.jpg"},
            ],
        }
    ],
)

print(response.output)
```

### Files

```python
from marona import Marona

marona = Marona(api_key="YOUR_MARONA_API_KEY")

response = marona.responses.create(
    model="openai/gpt-5.6",
    input=[
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": "Summarize this file."},
                {
                    "type": "input_file",
                    "filename": "report.pdf",
                    "file_data": "data:application/pdf;base64,...",
                    "detail": "high",
                },
            ],
        }
    ],
)

print(response.output)
```

## 4. Bring Your Own Agent

The external framework owns its Agent, reasoning, and orchestration. Marona
supplies neutral MCP tools and retains authorization, approvals, and execution.

### OpenAI Agents SDK Example

```python
from marona import Marona
from agents import Agent, Runner

marona = Marona(api_key="YOUR_MARONA_API_KEY")
connection = marona.hub.connect(
    apps=["group-fund"],
    skills=["create-group-fund"],
)

# Adapt only at the framework boundary. Marona itself remains vendor-neutral.
framework_tools = your_openai_agents_mcp_adapter(connection)

agent = Agent(
    name="Group Fund Assistant",
    model="gpt-5.6",
    instructions="Help users create and manage group funds.",
    tools=framework_tools,
)

result = Runner.run_sync(agent, "Create a family savings group fund")
print(result.final_output)
```

`your_openai_agents_mcp_adapter(...)` represents the OpenAI-specific adapter at
the framework boundary; it is not part of Marona's vendor-neutral core API.

An MCP-compatible framework can map its standard tool-list and tool-call hooks
directly to `connection.list_tools()` and `connection.call_tool(...)`. Marona
does not claim that one Python tool object automatically satisfies every agent
framework's proprietary interface.

## 8. Publish A Skill

Every workflow entry uses `step()`; `type` selects reasoning, approval, or App
execution. Set `visibility="public"` for Hub discovery or `"private"` for only
the owning developer workspace. New Skills default to private.

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


@skill(
    name="create-group-fund",
    description="Create a group fund after explicit user approval.",
    visibility="public",
    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 = Marona(api_key="YOUR_MARONA_API_KEY")
marona.skills.publish(create_group_fund, version="1.0.0")
```

## Execution Modes

Set mode when creating Marona:

```python
marona = Marona(
    api_key="YOUR_MARONA_API_KEY",
    mode="hybrid",
)
```

- `online`: network models and online MCP targets are allowed.
- `hybrid`: local/private execution may fall back to online execution.
- `offline`: only installed local Apps, Skills, data, and local models run.

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