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.

python
from praisonaiagents import Agent

agent = Agent(name="assistant", instructions="Be helpful")
agent.start("What is 2 + 2?")  # native praisonai runtime — no middleware needed

The user runs tools through a plugin harness; middleware normalises vendor-shaped results before hooks and memory run.

Diagram

Plugin Harness

Tool Execution

Middleware

Normalized Result

Hooks

Memory

Quick Start

  1. Simple Usage

    The native praisonai runtime 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?")
  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.

Diagram
MemoryAFTER_TOOL HookMiddlewareToolMemoryAFTER_TOOL HookMiddlewareToolraw vendor resultnormalize(result, tool_name, ctx)NormalizedToolResultconsistent payload

When agent._runtime_id is missing or equals "praisonai", middleware is bypassed entirely (zero allocation).

Configuration Options

MiddlewareContext

FieldTypeDefaultDescription
tool_namestr— (required)Name of the tool that was executed
runtime_idstr— (required)Runtime that produced the result
agent_idOptional[str]NoneAgent name, if known
session_idOptional[str]NoneSession id, if known
execution_time_msfloat0.0Tool execution time
timestampfloattime.time()When the result was produced
metadataDict[str, Any]{}Free-form context for middleware decisions

NormalizedToolResult

FieldTypeDefaultDescription
contentAny— (required)The actual tool result
successboolTrueWhether the tool succeeded
error_messageOptional[str]NoneError text when success=False
metadataDict[str, Any]{}Vendor/exec metadata for downstream consumers
execution_time_msfloat0.0Execution time in ms
timestampfloattime.time()When normalized
raw_resultOptional[Any]NoneOriginal vendor object for debugging

Registry API

MethodSignaturePurpose
register(runtime_id, middleware) -> NoneRegister middleware for a runtime
unregister(runtime_id) -> boolRemove middleware
get_middleware(runtime_id) -> RuntimeToolResultMiddlewareLook up; returns PassThroughMiddleware if not registered
has_middleware(runtime_id) -> boolCheck registration
list_runtimes() -> list[str]List registered runtime ids
clear() -> NoneDrop 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

Diagram

User prompt

Agent

Vendor tool

Middleware

Hook records event

Consistent result to user

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.

Related