> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Runtime Protocol

> Swap the engine that runs an agent turn — built-in, custom, or plugin runtime.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(name="runtime-agent", instructions="Execute tasks via the runtime protocol.")
agent.start("Process this request using the standard runtime protocol.")
```

One protocol, many runtimes — pick the engine that runs a single agent turn without changing your Agent code.

The user swaps runtimes without changing agent code; the registry resolves the engine and streams `RuntimeResult` deltas.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[Agent + prompt] --> R{Runtime Registry}
    R -->|resolve_runtime "praisonai"| B[Built-in PraisonAIRuntime]
    R -->|resolve_runtime "echo"| C[Custom Runtime]
    R -->|entry-point praisonai.runtimes| D[Plugin Runtime]
    B --> O[RuntimeResult / RuntimeDelta]
    C --> O
    D --> O

    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff

    class A agent
    class R,B,C,D,O tool
```

<Note>
  `AgentRuntimeProtocol`, `resolve_runtime`, `register_runtime`, and `list_runtimes` are importable directly from `praisonaiagents` as of PR #2335. The deep `praisonaiagents.runtime.*` paths remain available for backward compatibility.
</Note>

## Quick Start

<Steps>
  <Step title="Run a turn with the built-in runtime">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    from praisonaiagents import resolve_runtime

    async def main():
        runtime = resolve_runtime("praisonai")
        result = await runtime.run_turn("Who built the Eiffel Tower?")
        print(result.content)

    asyncio.run(main())
    ```
  </Step>

  <Step title="Stream the response">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    from praisonaiagents import resolve_runtime

    async def main():
        runtime = resolve_runtime("praisonai")
        async for delta in runtime.stream_turn("Tell me a story"):
            if delta.type == "text":
                print(delta.content, end="")

    asyncio.run(main())
    ```

    <Note>
      The built-in `praisonai` runtime currently yields a single delta containing the full response. True token-by-token streaming is pending Agent-side support.
    </Note>
  </Step>

  <Step title="Register and use a custom runtime">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    from praisonaiagents import register_runtime, resolve_runtime
    from praisonaiagents.runtime import RuntimeResult, RuntimeDelta

    class EchoRuntime:
        def supports(self, model_ref=None):
            return True

        async def run_turn(self, prompt, **kwargs):
            return RuntimeResult(content=f"echo: {prompt}")

        async def stream_turn(self, prompt, **kwargs):
            yield RuntimeDelta(type="text", content=f"echo: {prompt}")

    register_runtime("echo", lambda: EchoRuntime())

    async def main():
        runtime = resolve_runtime("echo")
        result = await runtime.run_turn("Hello!")
        print(result.content)

    asyncio.run(main())
    ```
  </Step>
</Steps>

***

## How It Works

The registry auto-discovers built-in and plugin runtimes, then hands back an instance implementing `AgentRuntimeProtocol`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant App as Your App
    participant Registry as RuntimeRegistry
    participant Runtime as AgentRuntimeProtocol

    User->>App: "Who built the Eiffel Tower?"
    App->>Registry: resolve_runtime("praisonai")
    Registry-->>App: PraisonAIRuntime instance
    App->>Runtime: await run_turn(prompt)
    Runtime-->>App: RuntimeResult(content=...)
    App-->>User: response
```

| Stage | What happens |
| - | - |
| Discover | Registry auto-loads built-ins and plugin runtimes from `praisonai.runtimes` entry points |
| Resolve | `resolve_runtime("praisonai")` returns an `AgentRuntimeProtocol` instance |
| Execute | `await runtime.run_turn(prompt)` returns a `RuntimeResult`; `runtime.stream_turn(prompt)` yields `RuntimeDelta`s |
| Fail-closed | Unknown runtime IDs raise `ValueError` with the list of available IDs |

***

## Picking a Runtime

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start([What do you need?]):::input --> Q1{Different engine?}
    Q1 -->|No - default behaviour| A[resolve_runtime praisonai]:::process
    Q1 -->|Yes - in-process| B[Implement AgentRuntimeProtocol\nregister_runtime my-id factory]:::process
    Q1 -->|Yes - ship as a package| C[Declare praisonai.runtimes\nentry point in pyproject.toml]:::process
    A --> R[✅ RuntimeResult]:::result
    B --> R
    C --> R

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
```

| You want… | Use |
| - | - |
| Default behaviour — wraps existing Agent | `resolve_runtime("praisonai")` |
| Different engine *in-process* | Implement `AgentRuntimeProtocol`, `register_runtime("my-id", factory)` |
| Different engine *shipped as a package* | Declare a `praisonai.runtimes` entry point |

***

## Configuration Options

### `AgentRuntimeProtocol`

Any object implementing these three methods is a valid runtime:

| Method | Signature | Notes |
| - | - | - |
| `supports` | `(model_ref=None) -> bool` | Return `True` if this runtime can handle the given model |
| `run_turn` | `(prompt, *, system_prompt=None, model_ref=None, **kwargs) -> RuntimeResult` | Async; run one agent turn |
| `stream_turn` | `(prompt, **kwargs) -> AsyncIterator[RuntimeDelta]` | Async generator; stream one agent turn |

`**kwargs` understood by the built-in runtime: `tools`, `max_tokens`, `temperature`.

### `RuntimeConfig`

| Field | Type | Default | Notes |
| - | - | - | - |
| `runtime_id` | `str` | required | Unique runtime identifier |
| `metadata` | `Dict[str, Any]` | `{}` | Optional runtime metadata |

### `RuntimeResult`

<Warning>
  The built-in runtime returns `RuntimeResult(error=...)` instead of raising an exception when execution fails. Custom runtimes should follow the same pattern so callers handle failure uniformly.
</Warning>

| Field | Type | Default | Notes |
| - | - | - | - |
| `content` | `str` | required | Agent response text |
| `metadata` | `Dict[str, Any]` | `{}` | Includes `model`, `agent_id`, `runtime` |
| `error` | `Optional[str]` | `None` | Set on failure — runtime returns errors, does not raise |

### `RuntimeDelta`

| Field | Type | Default | Notes |
| - | - | - | - |
| `type` | `str` | required | `"text"`, `"tool_call"`, `"thinking"`, `"error"` |
| `content` | `str` | `""` | Delta payload |
| `metadata` | `Dict[str, Any]` | `{}` | Runtime metadata |

### `RuntimeRegistryEntry`

| Field | Type | Default | Notes |
| - | - | - | - |
| `runtime_id` | `str` | required | |
| `display_name` | `Optional[str]` | falls back to `runtime_id` | |
| `description` | `Optional[str]` | `None` | |
| `is_builtin` | `bool` | `False` | `True` for the `praisonai` runtime |
| `metadata` | `Dict[str, Any]` | `{}` | |

### `RuntimeRegistry` methods

| Method | Purpose |
| - | - |
| `register(runtime_id, factory_or_instance, **metadata)` | Register a runtime; raises `ValueError` if already registered |
| `unregister(runtime_id) -> bool` | Remove a runtime and any aliases pointing to it |
| `is_registered(runtime_id) -> bool` | Check if registered (or aliased) |
| `is_available(runtime_id) -> bool` | Alias for `is_registered` |
| `list_runtimes() -> List[RuntimeRegistryEntry]` | All registered runtimes with metadata |
| `list_names() -> List[str]` | IDs only |
| `get_entry(runtime_id) -> Optional[RuntimeRegistryEntry]` | Metadata for one runtime |
| `resolve(runtime_id, config_overrides=None)` | Instantiate runtime; raises `ValueError` with available IDs if not found |
| `add_alias(alias, canonical_runtime_id)` | Alias an existing runtime |
| `clear()` | Reset (for tests) |

***

## Common Patterns

### Implement a custom runtime

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import register_runtime
from praisonaiagents.runtime import RuntimeResult, RuntimeDelta

class MyRuntime:
    def supports(self, model_ref=None):
        return True

    async def run_turn(self, prompt, *, system_prompt=None, model_ref=None, **kwargs):
        response = await my_llm_client.complete(prompt, system=system_prompt)
        if response.error:
            return RuntimeResult(content="", error=response.error)
        return RuntimeResult(content=response.text)

    async def stream_turn(self, prompt, **kwargs):
        async for token in my_llm_client.stream(prompt):
            yield RuntimeDelta(type="text", content=token)

register_runtime("my-llm", lambda: MyRuntime())
```

### Plugin runtime via pyproject.toml

Ship your runtime as an installable package. The registry discovers it automatically at import time.

```toml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
[project.entry-points."praisonai.runtimes"]
my-runtime = "my_package.runtime:MyRuntimeClass"
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# my_package/runtime.py
from praisonaiagents.runtime import RuntimeResult, RuntimeDelta

class MyRuntimeClass:
    def supports(self, model_ref=None):
        return True

    async def run_turn(self, prompt, **kwargs):
        return RuntimeResult(content=f"handled: {prompt}")

    async def stream_turn(self, prompt, **kwargs):
        yield RuntimeDelta(type="text", content=f"handled: {prompt}")
```

### Alias a runtime

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.runtime.registry import add_runtime_alias

add_runtime_alias("default", "praisonai")
```

### Replace the default runtime

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.runtime.registry import unregister_runtime
from praisonaiagents import register_runtime

unregister_runtime("praisonai")
register_runtime("praisonai", lambda: MyCustomRuntime())
```

### List available runtimes

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import list_runtimes

for runtime_id in list_runtimes():
    print(runtime_id)
```

### Check if a runtime is available

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.runtime.registry import is_runtime_available

if is_runtime_available("my-runtime"):
    runtime = resolve_runtime("my-runtime")
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Return errors, don't raise">
    The built-in runtime returns `RuntimeResult(error=...)` instead of raising. Custom runtimes should follow the same pattern so callers can handle failure uniformly with a single `if result.error:` check.
  </Accordion>

  <Accordion title="Use a factory for stateful runtimes">
    Pass `register_runtime("id", lambda: MyRuntime())` so each `resolve_runtime` call gets a fresh instance. This is important for runtimes that hold per-turn state or open connections.
  </Accordion>

  <Accordion title="Emit typed deltas in stream_turn">
    `stream_turn` yields `RuntimeDelta`s — emit `type="text"` for tokens, `type="error"` on failure, `type="tool_call"` or `type="thinking"` for richer UIs. Always end with an `error` delta rather than raising.
  </Accordion>

  <Accordion title="Use registry.clear() only in tests">
    The registry is process-wide and thread-safe. Call `RuntimeRegistry().clear()` only in test teardown to avoid affecting other parts of your application.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card icon="cloud" href="/docs/features/managed-runtime-protocol">Run the whole agent loop on remote provider infrastructure</Card>
  <Card icon="bolt" href="/docs/features/runtime-selection">Select runtimes by model or provider at the YAML level</Card>
  <Card icon="route" href="/docs/features/runtime-resolution">Resolve and cache runtimes at turn time</Card>
  <Card icon="plug" href="/docs/features/load-mcp-tools">Plug in tools from any Model Context Protocol server</Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.