> ## 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.

# Runtime Resolution

> General runtime-resolution subsystem for resolving agent runtimes at turn-time

The runtime-resolution subsystem maps agent and model references to concrete runtime instances at turn-time, enabling dynamic model switching and custom routing logic.

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

researcher = Agent(
    name="Researcher",
    instructions="Research the topic and summarise it",
    llm="gpt-4o-mini",
)
writer = Agent(
    name="Writer",
    instructions="Write a polished article",
    llm="gpt-4o-mini",
    handoffs=[researcher],
)

researcher.llm = "claude-3-sonnet"  # picked up on the next turn
writer.start("Research and write about ocean currents")
```

<Warning>
  **Handoffs no longer use this subsystem.** As of [PR #2178](https://github.com/MervinPraison/PraisonAI/pull/2178), handoffs always delegate directly to `agent.chat()` / `agent.achat()`. The runtime-resolution layer is used by other parts of the SDK for agent-level runtime configuration. If you came here for handoff execution behaviour, see [Agent Handoffs](/docs/features/handoffs).
</Warning>

The user changes model or runtime references on an agent; the resolver picks the concrete runtime on the next turn.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Runtime Resolution"
        A[🤖 Agent] --> B[🔍 Resolve Runtime]
        B --> C[(Cache)]
        C -->|hit| E[✅ Execute]
        C -->|miss| F[🏗️ Create Runtime]
        F --> C
        F --> E
    end

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef cache fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff

    class A agent
    class B,F process
    class C cache
    class E success
```

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Update the target model at any time — the next invocation reads the live `llm` value.

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

    analyst = Agent(name="Analyst", instructions="Analyse data", llm="gpt-4o-mini")
    coordinator = Agent(name="Coordinator", instructions="Coordinate", handoffs=[analyst])

    coordinator.start("Quick summary of Q1 sales")

    analyst.llm = "gpt-4o"
    coordinator.start("Full analysis of Q1 vs Q2 with trend forecasting")
    ```
  </Step>

  <Step title="With Configuration">
    Clear the runtime cache after credential rotation or register a custom resolver:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.runtime import clear_runtime_cache, set_global_resolver
    from praisonaiagents.runtime.resolve import RuntimeResolver, LLMRuntimeWrapper
    from praisonaiagents.llm.llm import LLM

    clear_runtime_cache()  # force fresh resolution

    class MyResolver(RuntimeResolver):
        def supports_model(self, model_ref: str) -> bool:
            return model_ref.startswith("my-model-")

        def resolve(self, agent_id, model_ref, session_ctx, **kwargs):
            llm = LLM(model="gpt-4o-mini", api_base="https://my.endpoint/v1")
            return LLMRuntimeWrapper(llm=llm, model_ref=model_ref, agent_id=agent_id)

    set_global_resolver(MyResolver())

    agent = Agent(name="MyAgent", instructions="Help users", llm="my-model-fast")
    agent.start("Hello!")
    ```
  </Step>
</Steps>

***

## How It Works

The subsystem reads the agent's current `llm` (or `model`) attribute **at invocation time**, not at construction time.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant RuntimeSubsystem
    participant Cache

    User->>Agent: start("Research and write...")
    Agent->>RuntimeSubsystem: resolve(agent_id, current_llm)
    RuntimeSubsystem->>Cache: check TTL cache
    alt cache hit (< 5 min)
        Cache-->>RuntimeSubsystem: cached runtime
    else cache miss or expired
        RuntimeSubsystem->>RuntimeSubsystem: create LLMRuntimeWrapper
        RuntimeSubsystem->>Cache: store & return
    end
    RuntimeSubsystem-->>Agent: runtime instance
    Agent->>Agent: agent.chat(prompt)
    Agent-->>User: response
```

<Note>
  Handoffs always execute via `agent.chat()` / `agent.achat()` directly — they do not call into this subsystem. Runtime resolution is used for agent-level model configuration and caching, not for handoff execution.
</Note>

***

## Configuration Options

### `SessionContext`

Passed to `resolve_runtime` to scope caching and track depth.

| Field             | Type            | Default                 | Description                                          |
| ----------------- | --------------- | ----------------------- | ---------------------------------------------------- |
| `session_id`      | `str`           | —                       | Required. Used as the first segment of the cache key |
| `timestamp`       | `float`         | `time.time()` if `<= 0` | Session start time                                   |
| `parent_agent_id` | `Optional[str]` | `None`                  | Name of the agent that triggered resolution          |
| `handoff_depth`   | `int`           | `0`                     | Current nesting depth                                |

### Cache constants

| Constant             | Value | Meaning                                         |
| -------------------- | ----- | ----------------------------------------------- |
| `_cache_ttl_seconds` | `300` | Each cached runtime lives for 5 minutes         |
| `_cleanup_interval`  | `600` | Background cleanup daemon runs every 10 minutes |

Cache keys use the format `"{session_id}:{agent_id}:{model_ref}"` — caches are session-isolated so different conversations never share runtimes.

***

## Common Patterns

### Mid-conversation model swap

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

analyst = Agent(name="Analyst", instructions="Analyse data", llm="gpt-4o-mini")
coordinator = Agent(name="Coordinator", instructions="Coordinate", handoffs=[analyst])

# First few turns use gpt-4o-mini
coordinator.start("Quick summary of Q1 sales")

# Upgrade to a more capable model for a detailed report
analyst.llm = "gpt-4o"
coordinator.start("Full analysis of Q1 vs Q2 with trend forecasting")
```

### Force cache refresh

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

# After rotating API keys or changing model config
clear_runtime_cache()

agent.start("Continue with the updated model settings")
```

### Introspect resolved runtimes

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

cache = get_runtime_cache()
total = sum(len(entries) for entries in cache.values())
print(f"Active runtimes: {total} across {len(cache)} sessions")
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Change llm before starting a new turn">
    Model re-resolution happens at each invocation boundary. Changing `agent.llm` is effective immediately for the next call — no restart needed.
  </Accordion>

  <Accordion title="Use clear_runtime_cache after credential rotation">
    The 5-minute TTL means old runtimes may linger after you rotate API keys. Call `clear_runtime_cache()` to evict all entries and force fresh connections.
  </Accordion>

  <Accordion title="Implement supports_model narrowly in custom resolvers">
    Return `False` from `supports_model` for models you do not handle. The built-in `DefaultRuntimeResolver` acts as the final fallback, so returning `False` simply delegates back to it.
  </Accordion>

  <Accordion title="Handoffs execute via agent.chat() — not this subsystem">
    If you need to change how a handoff target executes, configure the target agent itself (instructions, tools, llm). The target agent's full `chat()` pipeline runs on every handoff — this subsystem is not in that path.
  </Accordion>
</AccordionGroup>

***

## Public API

All names are importable from `praisonaiagents.runtime`:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.runtime import (
    resolve_runtime,
    SessionContext,
    RuntimeProtocol,
    get_runtime_cache,
    clear_runtime_cache,
    set_global_resolver,
)
```

### `resolve_runtime`

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
def resolve_runtime(
    agent_id: str,
    model_ref: str,
    session_ctx: SessionContext,
    **kwargs,
) -> AgentRuntimeProtocol:
    ...
```

The main entry point. Checks the TTL cache first; creates and caches a new runtime if none exists or the entry expired.

### `RuntimeProtocol` / `AgentRuntimeProtocol`

Protocols that custom runtimes must satisfy. Only relevant when building a custom resolver.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
class RuntimeProtocol(Protocol):
    def execute(self, prompt: str, **kwargs) -> Any: ...
    async def aexecute(self, prompt: str, **kwargs) -> Any: ...
    @property
    def model_ref(self) -> str: ...
    @property
    def provider(self) -> str: ...

class AgentRuntimeProtocol(RuntimeProtocol):
    @property
    def supports_streaming(self) -> bool: ...
    @property
    def supports_tools(self) -> bool: ...
```

***

## Related

<CardGroup cols={2}>
  <Card title="Runtime Selection" icon="play" href="/docs/features/runtime-selection">
    Choose which runtime executes each model
  </Card>

  <Card title="Agent Handoffs" icon="handshake" href="/docs/features/handoffs">
    Agent-to-agent delegation — handoffs always use agent.chat()
  </Card>
</CardGroup>
