Runtime
The PaprikaRuntime is the entry point. It wraps agent execution, records events, enforces policies, and enables replay.
Basic Setup
#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:
@runtime.agent(name=#a3d95f]">"my_agent")
#a3d95f]">"text-[#9ecbff]">def my_agent(ctx):
# ctx is PaprikaContext, injected automatically
#a3d95f]">"text-[#9ecbff]">passThe decorator wraps your function to:
- Inject
PaprikaContextas the first argument - Record execution start/end
- Enforce policies
- Capture all LLM and tool calls
- Save an
ExecutionRecord
Your agent function signature must be:
#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
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 providermodel(str): model identifierinput(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
result = ctx.tools.call(
name=#a3d95f]">"search",
args={#a3d95f]">"query": "AI trends"}
)Keyword arguments:
name(str): registered tool nameargs(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:
run_id = ctx.run_idTool Registration
Register tools before running agents:
#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
result = my_agent({}) # Returns the agent's "text-[#9ecbff]">return valueThis works like a normal function. The @runtime.agent() decorator injects ctx automatically.
Method 2: Use runtime.run()
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
#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:
@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 errorLimitations
- 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
- Understand execution records: Execution Records
- Set policies: Policies
- Master replay: Replay Engine