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

# Runtime

The runtime determines how an agent's inner loop runs — how each turn calls the
model, interprets intent, and invokes tools. The default runtime is built on
Google ADK's execution flow and works out of the box; you can also switch the
execution backend or inject shared processing logic into the flow, without
changing the agent itself.

## Default runtime

The ADK runtime is the default and needs no extra configuration:

```python lines theme={null}
from veadk import Agent

agent = Agent(name="assistant")  # uses the ADK runtime by default
```

## Switch the execution backend

Use `runtime` to select the inner-loop execution backend:

```python lines theme={null}
agent = Agent(name="assistant", runtime="codex")
```

| Value | Description |
| - | - |
| `adk` (default) | Google ADK's built-in execution flow; suitable for most cases. |
| `codex` | Runs the inner loop through the Codex SDK and bridges function tools, MCP tools, and skills. |
| `piagent` | Runs the inner loop through PiAgent RPC and bridges function tools, MCP tools, and skills. Added in 1.0.5. |

### Use the Codex runtime

The Codex runtime requires the optional `codex` dependency group, which is not
installed with VeADK by default. Install the required dependencies before using
it:

```bash lines theme={null}
pip install "veadk-python[codex]"
```

The `codex` extra includes the OpenAI Codex SDK and the Codex CLI binary.

The model name, API endpoint, and API key still come from the agent's model
configuration:

```python lines theme={null}
from veadk import Agent

agent = Agent(
    name="coding_assistant",
    runtime="codex",
    model_name="doubao-seed-2-1-pro-260628",
)
```

Tools and skills use the standard `Agent` configuration; the Codex runtime does
not require a separate registration path:

* [function tools](/productions/veadk/preview/en/components/tools/custom-function) in `tools` are exposed as callable tools to the model;
* an [MCPToolset](/productions/veadk/preview/en/components/tools/custom-mcp) in `tools` supports tool discovery and invocation;
* [skills](/productions/veadk/preview/en/components/agent/skills) loaded through `SkillToolset` or VeADK's legacy entry are made available through the Codex skill mechanism.

### Codex security configuration

The Codex runtime defaults to least-privilege settings suited to a
multi-tenant service: each invocation uses a session-isolated workspace, the
`workspace_write` sandbox, no network access, and denial of escalated
operations. Broader access must be enabled explicitly.

Pass a `CodexRuntimeConfig` through the `Agent` `codex_runtime_config`
parameter:

```python title="agent.py" lines theme={null}
from veadk import Agent
from veadk.runtime.codex import CodexRuntimeConfig

agent = Agent(
    name="assistant",
    runtime="codex",
    codex_runtime_config=CodexRuntimeConfig(
        sandbox="workspace_write",
        approval_mode="auto_review",
        network_access=True,
        # Set this explicitly to work inside an existing project.
        workspace_root="/workspace/codex",
    ),
)
```

The full set of `CodexRuntimeConfig` parameters:

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `approval_mode` | `"deny_all"` \| `"auto_review"` | `"deny_all"` | Review policy for escalated operations. `deny_all` rejects every escalation request; `auto_review` lets the model's self-review decide whether to run. |
| `sandbox` | `"read_only"` \| `"workspace_write"` \| `"full_access"` | `"workspace_write"` | Sandbox level. `read_only` only reads the workspace; `workspace_write` reads and writes inside the workspace; `full_access` grants full host access. |
| `network_access` | `bool` | `False` | Whether network access is allowed, effective only under the `workspace_write` sandbox. |
| `workspace_root` | `str` \| `None` | `None` | Workspace root directory. When unset, a session-isolated temporary directory is used. |
| `reuse_workspace` | `bool` | `False` | Whether to reuse the same workspace directory across invocations, effective only when `workspace_root` is set. |
| `reasoning_effort` | `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` | `"medium"` | Reasoning effort level. |
| `personality` | `"none"` \| `"friendly"` \| `"pragmatic"` | `"pragmatic"` | Reply personality. |
| `max_tool_iterations` | `int` | `8` | Maximum tool-call iterations per turn, range 1–64. |
| `tool_timeout_seconds` | `float` \| `None` | `120.0` | Timeout in seconds for a single tool call. |

The following environment variables override the corresponding `CodexRuntimeConfig` field without changing code, and take precedence over `codex_runtime_config`:

| Environment variable | Type | Default | Description |
| :- | :- | :- | :- |
| `VEADK_CODEX_SANDBOX` | `str` | — | Overrides `sandbox`. |
| `VEADK_CODEX_APPROVAL_MODE` | `str` | — | Overrides `approval_mode`. |
| `VEADK_CODEX_WORKSPACE_ROOT` | `str` | — | Overrides `workspace_root`. |
| `VEADK_CODEX_NETWORK_ACCESS` | `str` | — | Overrides `network_access`; accepts `1`, `true`, `yes`, `on` (case-insensitive) as enabled. |

<Warning>
  `sandbox="full_access"` and `reuse_workspace=True` relax filesystem isolation between invocations and should only be enabled in trusted environments. In production, use a least-privilege isolated container and restrict the credentials, files, and network destinations it can access.
</Warning>

### Codex observability

Codex-native lifecycle notifications and ADK Function/MCP tool calls are converted into standard ADK Events, so tool calls, results, state changes, confirmations, and authentication surface in Session, Trace, and the UI. Runtime logs use stable `codex_*` event names and attribution fields such as `invocation_id`, `call_id`, `tool`, `status`, and `duration_ms`. Tool arguments, tool results, API tokens, credentials, and backend addresses are not logged. Token usage is exposed through `codex_event_type=token_usage` events and the corresponding log entry.

### Configure transient-error retries

When the Codex runtime calls the model backend, it retries transient failures
such as rate limits, server errors, overloads, and timeouts. It retries at most
twice by default. Use these environment variables to change the behavior:

| Environment variable | Type | Default | Description |
| - | - | - | - |
| `CODEX_SHIM_NUM_RETRIES` | `int` | `2` | Maximum retries for transient failures. Set to `0` to disable retries. |
| `CODEX_SHIM_TIMEOUT` | `float` | `0` | Timeout in seconds for one model-backend call. `0` uses the underlying client's default timeout. |

### Use PiAgent

```python lines theme={null}
from veadk import Agent

agent = Agent(
    name="coding_assistant",
    runtime="piagent",
    model_name="doubao-seed-2-1-pro-260628",
)
```

The VeADK Python package does not vendor the PiAgent binary. The runtime checks
the following locations in order:

1. the executable referenced by `PIAGENT_BINARY`;
2. the managed cache under `PIAGENT_INSTALL_DIR`, which defaults to `~/.cache/veadk/piagent`;
3. if the cache is missing, a downloaded and verified PiAgent release.

| Environment variable | Default | Description |
| - | - | - |
| `PIAGENT_BINARY` | — | Path to an installed PiAgent executable. Recommended for production. |
| `PIAGENT_INSTALL_DIR` | `~/.cache/veadk/piagent` | Managed download and cache directory. |
| `PIAGENT_AGENT_DIR` | A temporary directory per run | Isolated PiAgent configuration and session directory; it cannot point to the user's real `~/.pi/agent`. |
| `PIAGENT_WORKDIR` | Current directory | PiAgent working directory. |
| `PIAGENT_TIMEOUT_SECONDS` | `600` | Per-run timeout in seconds. |
| `PIAGENT_TOOL_ALLOWLIST` | Empty | Comma-separated built-in tools that may run. |
| `PIAGENT_EXCLUDE_TOOLS` | Empty | Comma-separated tools that may not run. |

<Note>
  Automatic installation requires access to the PiAgent release host. In offline or restricted networks, install the binary in advance and set `PIAGENT_BINARY`.
</Note>

## Output persistence

`Agent` inherits from Google ADK's `LlmAgent` and supports persisting the
agent's final text response to session state through the `output_key` parameter.
Other agents that run later in the same session can read that state, making it
useful for passing results between agents in a multi-agent workflow.

<Note>
  `output_key` works across all runtimes, including the default ADK runtime
  and external runtimes such as `codex` and `piagent`. With an external runtime,
  the final response is still written to session state.
</Note>

The following example uses `SequentialAgent` to chain two agents: the planner's
output is written to session state via `output_key="plan"`, and the writer reads
that state in the same session:

```python title="pipeline.py" lines theme={null}
from veadk import Agent
from veadk.agents.sequential_agent import SequentialAgent

planner = Agent(
    name="planner",
    runtime="codex",
    model_name="doubao-seed-2-1-pro-260628",
    model_api_key="ARK_API_KEY",
    instruction="Generate a writing outline based on the user's request.",
    output_key="plan",
)

writer = Agent(
    name="writer",
    runtime="piagent",
    model_name="doubao-seed-2-1-pro-260628",
    model_api_key="ARK_API_KEY",
    instruction="Write the article based on the outline in session state.",
    output_key="draft",
)

pipeline = SequentialAgent(
    name="pipeline",
    sub_agents=[planner, writer],
)
```

After running, the session state keys `plan` and `draft` hold the final
responses from the planner and the writer respectively.

## Model callbacks

`Agent` inherits from Google ADK's `LlmAgent`, so you can use
`before_model_callback`, `after_model_callback`, and
`on_model_error_callback` to inject custom logic before and after the model
call and on errors. These callbacks, together with the same-named methods on
ADK plugins (subclasses of `BasePlugin`), run in every runtime — the default
ADK runtime and external runtimes such as `codex` and `piagent`. In external
runtimes, callbacks run in ADK order: plugin callbacks first, then agent
callbacks.

| Callback | Description |
| :- | :- |
| `before_model_callback(callback_context, llm_request)` | Runs before the model is called. It can mutate `llm_request` directly (including `contents`, `config.system_instruction`, `output_schema`, and `tools_dict`); the external runtime builds the actual input from the mutated request. If it returns an `LlmResponse`, the model call is skipped and that response is used as the turn result. |
| `after_model_callback(callback_context, llm_response)` | Runs after the model emits its final text response. External runtimes merge the turn's final text events into a single `LlmResponse` before passing it to this callback; a returned `LlmResponse` replaces the original reply. Final-text buffering is enabled only when an after callback is configured (an agent callback or a plugin override). |
| `on_model_error_callback(callback_context, llm_request, error)` | Runs when the model call raises an exception. If it returns an `LlmResponse`, that response is used as the turn result and the run completes normally; otherwise the exception is re-raised. |

<Note>
  The `LlmRequest` built by an external runtime exposes only the stable fields
  callbacks need (conversation contents, system instruction, output schema,
  tools, and generation config); it does not run ADK's full preprocessing
  pipeline. Callback behavior that depends on ADK-internal preprocessing stages
  may therefore differ in external runtimes.
</Note>

Callbacks may be synchronous or asynchronous (`async def`). The following
example uses `before_model_callback` in the Codex runtime to render PDF
attachments into images so a vision-capable model can read the document:

```python title="agent.py" lines theme={null}
from veadk import Agent
from veadk.utils.pdf_to_images import pdf_to_images_before_model_callback

agent = Agent(
    name="coding_assistant",
    runtime="codex",
    model_name="doubao-seed-2-1-pro-260628",
    before_model_callback=pdf_to_images_before_model_callback,
)
```

Use `on_model_error_callback` to return a fallback response when the model call
fails, instead of letting the exception propagate to the caller:

```python title="agent.py" lines theme={null}
from veadk import Agent
from google.adk.models.llm_response import LlmResponse
from google.genai import types

def fallback_on_error(callback_context, llm_request, error):
    return LlmResponse(
        content=types.Content(
            role="model",
            parts=[types.Part(text="The model is temporarily unavailable. Please retry later.")],
        )
    )

agent = Agent(
    name="coding_assistant",
    runtime="codex",
    model_name="doubao-seed-2-1-pro-260628",
    on_model_error_callback=fallback_on_error,
)
```

## Choose where each tool runs

Whereas a runtime replaces an agent's entire inner loop, a `RuntimeProvider`
operates at a narrower layer: it decides where each individual tool call
executes while the model reasoning loop remains unchanged. It works as an ADK
plugin — by intercepting `before_tool_callback`, it takes over the call before
Google ADK invokes the tool implementation, so the local implementation is not
executed a second time.

VeADK exposes the following public classes:

| Class | Description |
| :- | :- |
| `RuntimeProvider` | Abstract base class, inheriting from ADK `BasePlugin`. Subclasses implement the `execute` method to decide where each tool call runs. |
| `DispatchRuntimeProvider` | Dispatches selected non-MCP tools to a remote runtime; all other tools fall back to local execution. |
| `LocalRuntimeProvider` | Executes tools through their original ADK implementation; used as the default local fallback for `DispatchRuntimeProvider`. |
| `ToolCall` | The tool-call descriptor passed to `execute`, containing the tool name, arguments, context, and more. |

### Usage example

The following example dispatches every non-MCP tool to a remote runtime; MCP
tools retain their original ADK implementation:

```python title="agent.py" lines theme={null}
from veadk import Agent, Runner
from veadk.runtime import DispatchRuntimeProvider, ToolCall

async def dispatch_task(tool_call: ToolCall):
    return await remote_client.dispatch(
        tool_call.name,
        tool_call.arguments,
        dispatch_id=tool_call.id,
    )

agent = Agent(name="assistant", tools=[bash, read_file])
runtime_provider = DispatchRuntimeProvider(
    dispatch_task,
    dispatchable_tools=None,  # dispatch every non-MCP tool
)
runner = Runner(agent=agent, plugins=[runtime_provider])
```

In the example above, every non-MCP tool only goes through `dispatch_task` and
its local function is not invoked a second time. Pass a set of tool names to
dispatch only those non-MCP tools. The dispatch function may be synchronous or
asynchronous.

To register directly on an `Agent` instead of through the `Runner` `plugins`
parameter, pass the `DispatchRuntimeProvider`'s `before_tool_callback` to the
`Agent`'s `before_tool_callback` parameter:

```python title="agent.py" lines theme={null}
agent = Agent(
    name="assistant",
    tools=[bash, read_file],
    before_tool_callback=runtime_provider.before_tool_callback,
)
```

### DispatchRuntimeProvider parameters

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `dispatch_task` | `Callable[[ToolCall], Any]` | — | Required. The function called for each dispatched tool call; receives a `ToolCall` and returns the tool result. May be synchronous or asynchronous. |
| `dispatchable_tools` | `Collection[str] \| None` | `("bash",)` | Names of non-MCP tools to dispatch. Set to `None` to dispatch every non-MCP tool. |
| `local_runtime` | `RuntimeProvider \| None` | `None` | Local provider for non-dispatched tools. Defaults to `LocalRuntimeProvider`. |
| `name` | `str` | `"veadk_dispatch_runtime_provider"` | Plugin name. |

### ToolCall fields

| Field / Property | Type | Description |
| :- | :- | :- |
| `name` | `str` | Tool name. |
| `arguments` | `dict[str, Any]` | Arguments passed to the tool. |
| `tool` | `BaseTool` | The ADK tool object, usable for local execution. |
| `context` | `ToolContext` | The ADK tool context. |
| `id` | `str` | Property. ADK function-call identifier; empty string when unavailable. |
| `session_id` | `str` | Property. Current VeADK session identifier; empty string when unavailable. |

<Note>
  MCP tools always retain their original ADK implementation and are not affected
  by the `dispatchable_tools` configuration, because MCP tool invocation must go
  through its own MCP session manager.
</Note>

## Request processing

Without touching business logic, you can inject shared cross-cutting processing
into each run. Pass a processor to `run_processor`, for example to enforce login
via identity authentication:

```python lines theme={null}
from veadk import Agent
from veadk.integrations.ve_identity import AuthRequestProcessor

agent = Agent(name="assistant", run_processor=AuthRequestProcessor())
```

When unset, the default processor is used and behavior is unchanged. VeADK ships
`AuthRequestProcessor` as a ready-made implementation for identity authentication.

### Custom processors

Every processor subclasses the abstract base `BaseRunProcessor` and implements
its `process_run(runner, message)` method. That method returns a decorator
wrapping the run's event stream, letting you:

* inject logic **before and after** the whole run (auth, logging, monitoring);
* **intercept, rewrite, or inject** events produced during the run;
* build control logic such as retries on top of that.

```python lines theme={null}
from veadk.processors.base_run_processor import BaseRunProcessor

class LoggingProcessor(BaseRunProcessor):
    def process_run(self, runner, message, **kwargs):
        def decorator(event_generator):
            async def wrapper():
                # Before: record start, validate, ...
                async for event in event_generator():
                    yield event  # intercept or rewrite events here
                # After: summarize, report, ...
            return wrapper
        return decorator
```
