Runtime

Runtime

The PaprikaRuntime is the entry point. It wraps agent execution, records events, enforces policies, and enables replay.

Basic Setup

python
#a3d95f]">"text-[#9ecbff]">from paprika "text-[#9ecbff]">import PaprikaRuntime, PolicyConfig

runtime = PaprikaRuntime(
    policy=PolicyConfig(
        max_steps=10,
        max_tokens=10000
    )
)

The runtime manages:

  • Trace storage (default: ~/.paprika/traces/)
  • Policy enforcement
  • Agent registration
  • Tool registration
  • Execution recording

Agent Registration

Register an agent with the @runtime.agent() decorator:

python
@runtime.agent(name=#a3d95f]">"my_agent")
#a3d95f]">"text-[#9ecbff]">def my_agent(ctx):
    # ctx is PaprikaContext, injected automatically
    #a3d95f]">"text-[#9ecbff]">pass

The decorator wraps your function to:

  • Inject PaprikaContext as the first argument
  • Record execution start/end
  • Enforce policies
  • Capture all LLM and tool calls
  • Save an ExecutionRecord

Your agent function signature must be:

python
#a3d95f]">"text-[#9ecbff]">def agent_name(ctx: PaprikaContext, *args, **kwargs) -> Any:
    ...

The ctx parameter is always first. Additional arguments are passed through.

Context Injection

Inside your agent function, use the injected ctx:

LLM Calls

python
response = ctx.llm.call(
    provider=#a3d95f]">"openai",
    model=#a3d95f]">"gpt-4o",
    input={
        #a3d95f]">"messages": [
            {#a3d95f]">"role": "user", "content": "Your prompt"}
        ]
    }
)

Keyword arguments:

  • provider (str): "openai", "mock", or custom provider
  • model (str): model identifier
  • input (dict): full input dict (no argument reconstruction)

The LLM call is automatically recorded:

  • Input data and input hash
  • Output data
  • Token usage
  • Duration
  • Any errors

Tool Calls

python
result = ctx.tools.call(
    name=#a3d95f]">"search",
    args={#a3d95f]">"query": "AI trends"}
)

Keyword arguments:

  • name (str): registered tool name
  • args (dict): tool arguments

The tool call is automatically recorded:

  • Tool name
  • Arguments and input hash
  • Output data
  • Duration
  • Any errors

Run ID

Access the current run's UUID:

python
run_id = ctx.run_id

Tool Registration

Register tools before running agents:

python
#a3d95f]">"text-[#9ecbff]">def search(query: str) -> str:
    # Your tool implementation
    #a3d95f]">"text-[#9ecbff]">return f"Results ">for {query}"

runtime.register_tool(#a3d95f]">"search", search)

Tools can be:

  • Simple Python functions
  • Async functions
  • Classes with __call__
  • Any callable

The tool is automatically recorded when called via ctx.tools.call().

Running Agents

Two ways to run an agent:

Method 1: Call the decorated function

python
result = my_agent({})  # Returns the agent's "text-[#9ecbff]">return value

This works like a normal function. The @runtime.agent() decorator injects ctx automatically.

Method 2: Use runtime.run()

python
result = runtime.run(
    agent_name=#a3d95f]">"my_agent",
    input={#a3d95f]">"some": "data"}
)

Both methods:

  • Create a new ExecutionRecord
  • Enforce policies
  • Capture all LLM and tool calls
  • Save the trace to disk

Full Example

python
#a3d95f]">"text-[#9ecbff]">from paprika "text-[#9ecbff]">import PaprikaRuntime, PolicyConfig

runtime = PaprikaRuntime(
    policy=PolicyConfig(max_steps=10)
)

# Register a tool
#a3d95f]">"text-[#9ecbff]">def weather(location: str) -> str:
    #a3d95f]">"text-[#9ecbff]">return f"Sunny in {location}"

runtime.register_tool(#a3d95f]">"weather", weather)

# Define an agent
@runtime.agent(name=#a3d95f]">"weather_agent")
#a3d95f]">"text-[#9ecbff]">def weather_agent(ctx, location: str) -> str:
    # LLM call
    response = ctx.llm.call(
        provider=#a3d95f]">"mock",
        model=#a3d95f]">"gpt-4o",
        input={
            #a3d95f]">"messages": [
                {#a3d95f]">"role": "user", "content": f"What's the weather in {location}?"}
            ]
        }
    )

    # Tool call
    weather_result = ctx.tools.call(
        name=#a3d95f]">"weather",
        args={#a3d95f]">"location": location}
    )

    #a3d95f]">"text-[#9ecbff]">return weather_result

# Run the agent
result = weather_agent(#a3d95f]">"San Francisco")
print(result)  # "Sunny in San Francisco"

Auto-Recorded Fields

Paprika automatically records:

Per LLM call:

  • Provider and model
  • Full input dict
  • Input hash (for mismatch detection)
  • Full output dict
  • Token usage (if available)
  • Duration
  • Errors (if any)

Per tool call:

  • Tool name
  • Full arguments dict
  • Input hash (for repeat detection)
  • Full output
  • Duration
  • Errors (if any)

Per run:

  • Start and end time
  • Duration
  • Final status (success, error, policy_violation)
  • Final output or error message
  • Total tokens and step counts
  • All policies that fired

No manual instrumentation required beyond using the context.

What Happens on Error

If your agent raises an exception:

python
@runtime.agent(name=#a3d95f]">"failing_agent")
#a3d95f]">"text-[#9ecbff]">def failing_agent(ctx):
    raise ValueError(#a3d95f]">"Something went wrong")

#a3d95f]">"text-[#9ecbff]">try:
    result = failing_agent()
#a3d95f]">"text-[#9ecbff]">except ValueError "text-[#9ecbff]">as e:
    # You handle the error
    #a3d95f]">"text-[#9ecbff]">pass

# The ExecutionRecord is still saved "text-[#9ecbff]">with:
# - execution.status = "error"
# - execution.error = "Something went wrong"
# - All steps recorded up to the error

Limitations

  • Agents must be synchronous (async agents coming soon)
  • Agents are recorded sequentially (no parallel agent execution)
  • Context is per-agent (nested agent calls not yet supported)

Next Steps