Runtime Tool Result Middleware
Normalize tool results from plugin harnesses into a standard shape before hooks and memory see them
Runtime tool result middleware converts vendor-shaped tool outputs from plugin harnesses into a standard format before hooks and memory adapters run.
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
agent.start("What is 2 + 2?") # native praisonai runtime — no middleware neededThe user runs tools through a plugin harness; middleware normalises vendor-shaped results before hooks and memory run.
Quick Start
- Simple Usage
The native
praisonairuntime needs no middleware — results pass straight to hooks:python from praisonaiagents import Agent agent = Agent(name="assistant", instructions="Be helpful") agent.start("What is 2 + 2?") - With Configuration
Register harness middleware once, then set
agent._runtime_id:python from typing import Any from praisonaiagents import Agent, tool from praisonaiagents.runtime import ( MiddlewareContext, NormalizedToolResult, register_middleware, ) class MyHarnessMiddleware: @property def runtime_id(self) -> str: return "my_plugin_harness" def normalize(self, result: Any, tool_name: str, ctx: MiddlewareContext) -> NormalizedToolResult: if isinstance(result, dict) and result.get("status") == "error": return NormalizedToolResult( content=None, success=False, error_message=result.get("message"), metadata={"vendor": "my_harness"}, raw_result=result, ) return NormalizedToolResult(content=result.get("data", result), raw_result=result) register_middleware("my_plugin_harness", MyHarnessMiddleware()) @tool def lookup(query: str) -> dict: """Look up data.""" return {"status": "ok", "data": f"Results for {query}"} agent = Agent(name="assistant", tools=[lookup]) agent._runtime_id = "my_plugin_harness" agent.start("Look up Python tutorials")
How It Works
Middleware runs in tool_execution.py before the AFTER_TOOL hook fires.
When agent._runtime_id is missing or equals "praisonai", middleware is bypassed entirely (zero allocation).
Configuration Options
MiddlewareContext
| Field | Type | Default | Description |
|---|---|---|---|
tool_name | str | — (required) | Name of the tool that was executed |
runtime_id | str | — (required) | Runtime that produced the result |
agent_id | Optional[str] | None | Agent name, if known |
session_id | Optional[str] | None | Session id, if known |
execution_time_ms | float | 0.0 | Tool execution time |
timestamp | float | time.time() | When the result was produced |
metadata | Dict[str, Any] | {} | Free-form context for middleware decisions |
NormalizedToolResult
| Field | Type | Default | Description |
|---|---|---|---|
content | Any | — (required) | The actual tool result |
success | bool | True | Whether the tool succeeded |
error_message | Optional[str] | None | Error text when success=False |
metadata | Dict[str, Any] | {} | Vendor/exec metadata for downstream consumers |
execution_time_ms | float | 0.0 | Execution time in ms |
timestamp | float | time.time() | When normalized |
raw_result | Optional[Any] | None | Original vendor object for debugging |
Registry API
| Method | Signature | Purpose |
|---|---|---|
register | (runtime_id, middleware) -> None | Register middleware for a runtime |
unregister | (runtime_id) -> bool | Remove middleware |
get_middleware | (runtime_id) -> RuntimeToolResultMiddleware | Look up; returns PassThroughMiddleware if not registered |
has_middleware | (runtime_id) -> bool | Check registration |
list_runtimes | () -> list[str] | List registered runtime ids |
clear | () -> None | Drop all registrations (testing) |
Module helpers: register_middleware, get_middleware, get_default_middleware_registry().
Common Patterns
Error handling — set success=False and fill error_message; failed tools surface as "Tool Error: <message>" to the agent.
Rich metadata — attach vendor, version, cache hit, or region in metadata for observability hooks.
Conditional normalization — branch on tool_name prefix (e.g. search_ vs db_) inside normalize().
User Interaction Flow
Best Practices
Keep normalize() allocation-light
Middleware runs on the hot path after every tool call. Avoid heavy imports or deep copies inside normalize().
Always set raw_result
Preserve the original vendor object in raw_result so hooks and debugging can inspect the pre-normalised payload.
Do not raise from normalize()
Middleware failures are caught and logged; tool execution continues with the raw result.
Skip middleware for native praisonai
Do not register middleware for "praisonai" — the core runtime bypasses it for zero overhead.