Deterministic LLM agent testing and replay framework
Project description
Kurral ๐ฏ
Deterministic LLM Agent Testing & Replay Framework
Kurral enables you to capture, replay, and evaluate LLM agent behaviors with deterministic precision. Built for teams that need reliable testing, regression detection, and reproducible AI workflows.
Features
- ๐ฌ Capture & Replay: Record LLM traces with full tool/MCP calls and replay them deterministically with stubbed responses
- ๐ Determinism Scoring: Automatic analysis of replay reliability (Levels A/B/C)
- ๐งฌ LLM State Hydration: Captures complete sampler state (temperature, top_p, seed, etc.) for Level B promotion
- โ Validation & Diff: Hash + structural comparison with detailed diff output
- ๐ Regression Testing: Compare agent versions with ARS (Agent Regression Score)
- ๐ชฃ Semantic Buckets: Organize traces by business logic (e.g.,
refund_flow,support_chat) - ๐ LangSmith Integration: Seamless import from LangSmith traces
- ๐ง MCP Support: Full Model Context Protocol tool capture and stubbing
- ๐พ Dual Storage: PostgreSQL metadata + Cloudflare R2 artifact storage
- ๐จ Beautiful CLI: Rich terminal UI with progress bars, diffs, and debug views
Quick Start
Installation
pip install kurral
Usage with Decorator
from kurral import trace_llm
from openai import OpenAI
client = OpenAI()
@trace_llm(semantic_bucket="customer_support", tenant_id="acme_prod")
def handle_customer_query(query: str) -> str:
"""Handle customer support query with LLM"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": query}],
seed=12345, # Important for determinism!
)
return response.choices[0].message.content
# This call is automatically traced and exported as a .kurral artifact
result = handle_customer_query("I need a refund for order #12345")
Export from LangSmith
# Export a specific run
kurral export --run-id ls_abc123 --output trace.kurral
# Export from local trace file
kurral export --input trace.json --output trace.kurral
Replay
# Basic replay - stubs all tool/MCP calls, reconstructs output stream
kurral replay trace.kurral
# Show diff between original and replayed outputs
kurral replay trace.kurral --diff
# Debug mode - shows LLM sampler state, stub status, graph hashes
kurral replay trace.kurral --debug --verbose
# Replay with prompt override for testing
kurral replay trace.kurral --prompt-override new_prompt.txt --diff
What Replay Does:
- Loads the
.kurralartifact with all recorded data - Primes cache with every tool/MCP call (inputs, outputs, status, latency, hashes)
- Stubs external callsโnothing hits live APIs during replay
- Reconstructs the assistant output stream (items/full_text/stream_map)
- Validates outputs via hash + structural comparison
- Returns enriched metadata: LLM sampler state, replay ID, validation results, graph fingerprint
Backtest (Regression Testing)
# Test new agent version against golden bucket
kurral backtest \
--baseline semantic:golden_tests \
--candidate ./new_config.yaml \
--threshold 0.90 \
--output report.json
Query Artifacts
# List semantic buckets
kurral buckets list
# Show artifacts in a bucket
kurral buckets show --semantic refund_flow
# Filter by tenant and date
kurral buckets show --tenant acme_prod --since 2024-01-01
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your LLM Agent โ
โ (with @trace_llm decorator) โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ Kurral Deep Capture โ
โ โข LLM interactions โ
โ โข Tool calls (I/O) โ
โ โข Timing (start/end) โ
โ โข Execution graphs โ
โ โข Error forensics โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโ
โ Optional: LangSmith โ
โ (parallel enrichment) โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ .kurral Artifact โ
โ โข Complete trace โ
โ โข Determinism score โ
โ โข Replay metadata โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ Kurral API Backend โ
โ โข Authentication โ
โ โข Artifact upload โ
โ โข Query & analytics โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ Dual Storage System โ
โ โข PostgreSQL (metadata)โ
โ โข R2 (full artifacts) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
Why This Architecture?
- Beyond OpenTelemetry: We capture LLM-specific data that generic telemetry misses (prompts, token usage, determinism scores, tool call graphs)
- Replay-First Design: Every artifact contains everything needed to reproduce the execution
- Security & Compliance: Immutable artifacts with cryptographic hashes, perfect for audit trails
- Cost Attribution: Track spend by semantic bucket, model, and tenant
- Drift Detection: Graph and prompt hashing detects when agent behavior changes
.kurral Artifact Format
A .kurral file is a JSON artifact containing everything needed for deterministic replay:
{
"kurral_id": "550e8400-e29b-41d4-a716-446655440000",
"run_id": "ls_abc123",
"tenant_id": "acme_prod",
"semantic_buckets": ["refund_flow", "tier_3_support"],
"environment": "production",
"deterministic": true,
"replay_level": "B",
"timestamp": "2024-01-15T10:30:00Z",
"inputs": {"query": "I need a refund"},
"outputs": {
"items": ["Refund", " initiated", "..."],
"full_text": "Refund initiated...",
"stream_map": [...]
},
"llm_config": {
"model_name": "gpt-4-0613",
"provider": "openai",
"parameters": {
"temperature": 0.0,
"top_p": 1.0,
"seed": 12345,
"max_tokens": 500
}
},
"resolved_prompt": {
"template": "You are a helpful assistant...",
"variables": {"tone": "professional"},
"final_text": "You are a helpful assistant. Be professional..."
},
"graph_version": {
"graph_hash": "d35d48cb...",
"graph_checksum": "2f2011f2..."
},
"tool_calls": [
{
"id": "tc_abc123",
"parent_id": null,
"tool_name": "check_order_status",
"type": "http",
"start_time": "2024-01-15T10:30:00.123Z",
"end_time": "2024-01-15T10:30:00.265Z",
"latency_ms": 142,
"input": {"order_id": "12345"},
"output": {"status": "shipped"},
"status": "ok",
"error_flag": false,
"cache_key": "sha256:abc123...",
"output_hash": "sha256:def456...",
"effect_type": "http",
"metadata": {
"cost_usd": 0.001,
"retry_count": 0,
"cache_hit": false
}
}
]
}
Key Fields for Advanced Observability:
llm_config.parameters: Complete sampler state (temperature, top_p, seed, etc.)tool_calls[]: Enhanced tool call records with:id: Unique identifier for each operationparent_id: Build execution graphs for nested/chained callsstart_time/end_time: Precise timing for concurrency analysistype: Operation classification (tool, llm, agent, retrieval, etc.)error_type/error_stack: Comprehensive error forensicsmetadata: Extensible field for cost, tokens, custom attributes
graph_version: Pinned graph fingerprint for version trackingoutputs: Stream reconstruction data (items, full_text, stream_map)
What This Enables:
- Execution Graphs: Reconstruct parent-child call relationships
- Concurrency Analysis: Find overlapping operations and bottlenecks
- Cost Attribution: Track spend by operation type and business context
- Error Pattern Detection: Group and analyze failures by type
- External Correlation: Link to infrastructure events (DB outages, API issues)
- Future-Proof: Add new metadata fields without schema migrations
Determinism & Replay Levels
Kurral classifies every trace by determinism score:
- Level A (score โฅ 0.90): Byte-for-byte reproducible (frozen model + seed + tool cache + clock)
- Level B (0.50 โค score < 0.90): Structurally equivalent (same tool I/O + sampler settings captured)
- Level C (score < 0.50): Task-level equivalence only (use recorded tool outputs, validate task metrics)
Promoting C โ B:
Replay now hydrates LLM sampler state (temperature, top_p, top_k, max_tokens, seed, penalties) so you can validate structural equivalence and lift a trace from Level C to B if conditions align.
Replay Validation:
Every replay returns:
ReplayLLMState: complete sampler configReplayValidation: output hash match, structural match, diffReplayMetadata: replay_id, record_ref, replay_level, assertion hooks- Stubbed tool calls flagged with
stubbed_in_replay=True
Environment Variables
Create a .env file:
# LangSmith (optional)
LANGSMITH_API_KEY=your_key_here
# Storage
DATABASE_URL=postgresql://user:pass@localhost:5432/kurral
KURRAL_R2_BUCKET=kurral-artifacts
KURRAL_R2_ACCOUNT_ID=your_cloudflare_account_id
KURRAL_R2_ACCESS_KEY_ID=your_r2_access_key
KURRAL_R2_SECRET_ACCESS_KEY=your_r2_secret_key
# Application
KURRAL_ENVIRONMENT=production
How to Integrate
1. Decorate Your Agent Functions
from kurral import trace_llm
@trace_llm(semantic_bucket="customer_support", tenant_id="acme_prod")
def my_agent(query: str) -> str:
# Your LLM logic here
response = llm.invoke(query, temperature=0.0, seed=42)
return response
2. Configure MCP Servers (Optional)
If using Model Context Protocol tools:
from kurral.core.decorator import trace_llm
@trace_llm(
semantic_bucket="data_ops",
tenant_id="acme",
mcp_servers=["filesystem", "database"] # Auto-captures MCP tool calls
)
def process_data(request):
# MCP tools are automatically stubbed during replay
return agent.run(request)
3. Configure Storage (Recommended)
Storage Options:
- local - Save to disk (default:
./artifacts/) - memory - Store in RAM for fast access, zero I/O overhead (great for testing/dev)
- r2 - Auto-upload to Cloudflare R2 for production
Option 1: Interactive Setup (Easiest)
kurral config init
This wizard will:
- Choose storage backend (local/memory/r2)
- Save credentials to
~/.config/kurral/config.json - Enable auto-upload (for r2)
- No need to set env vars every time!
Option 2: Environment Variables
# Use in-memory storage (fast, no I/O)
export KURRAL_STORAGE_BACKEND=memory
export KURRAL_MEMORY_MAX_ARTIFACTS=1000 # optional, default: 1000
export KURRAL_MEMORY_MAX_SIZE_MB=500 # optional, default: 500MB
# Or use Cloudflare R2
export KURRAL_STORAGE_BACKEND=r2
export KURRAL_R2_BUCKET=kurral-artifacts
export KURRAL_R2_ACCOUNT_ID=<your_account_id>
export KURRAL_R2_ACCESS_KEY_ID=<your_access_key>
export KURRAL_R2_SECRET_ACCESS_KEY=<your_secret_key>
Option 3: Project-Specific Config
# Save config to ./.kurral/config.json (project-specific)
kurral config init --local
Once configured, all traces automatically save/uploadโno manual steps needed!
# View current config
kurral config show
# Memory storage commands (when using KURRAL_STORAGE_BACKEND=memory)
kurral memory stats # Show memory usage
kurral memory list # List all artifacts in memory
kurral memory get <artifact_id> # Get artifact details
kurral memory export <artifact_id> <output_path> # Export to disk
kurral memory delete <artifact_id> # Delete from memory
kurral memory clear # Clear all artifacts
# R2 commands (when using KURRAL_STORAGE_BACKEND=r2)
kurral list-r2 --tenant-id acme --limit 10
kurral download <kurral_id> <tenant_id> <created_at> ./artifacts/
4. Replay Your Traces
# Replay captured artifact
kurral replay path/to/artifact.kurral
# Debug mode shows LLM params + stub status
kurral replay path/to/artifact.kurral --debug
Development
# Clone repo
git clone https://github.com/your-org/kurral.git
cd kurral
# Create virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black kurral/
ruff check kurral/
# Run CLI
kurral --help
Configuration
from kurral import KurralConfig
config = KurralConfig(
storage_backend="r2", # or "local"
r2_bucket="my-bucket",
r2_account_id="your_account_id",
database_url="postgresql://...",
langsmith_enabled=True,
auto_export=True, # Automatically export traces
determinism_threshold=0.90,
)
Troubleshooting
Missing boto3 Error
If you see ModuleNotFoundError: No module named 'boto3', R2 storage is optional. Use local storage instead:
config = KurralConfig(storage_backend="local")
Artifact Schema Mismatch
If a .kurral file fails to load, check schema_version. Upgrade with:
kurral migrate artifact.kurral --to-version 1.0.0
LangSmith Connection Issues
Verify LANGSMITH_API_KEY is set and the run ID exists:
export LANGSMITH_API_KEY=your_key
kurral export --run-id <id> --output test.kurral
Replay Output Doesn't Match
- Check
result.validation.hash_matchandresult.validation.structural_match - Use
--diffto see what changed - Verify LLM sampler state via
result.llm_state(temperature, seed, etc.)
Kurral API Backend
For centralized artifact management, deploy the Kurral API:
cd kurral-api
docker-compose up -d
Services:
- FastAPI Backend (port 8000) - Artifact management, authentication, analytics
- PostgreSQL - Metadata storage with indexes for fast queries
- Cloudflare R2 - Scalable artifact storage
Features:
- ๐ API key authentication with scopes
- ๐ฆ Artifact upload/download via REST API
- ๐ Advanced filtering (tenant, environment, semantic bucket, model, dates)
- ๐ Real-time statistics and time series analytics
- ๐พ Dual storage: metadata in PostgreSQL, full artifacts in R2
- ๐ Production-ready with health checks and monitoring
Quick Start:
# Configure
cp kurral-api/.env.template kurral-api/.env
# Edit .env with your R2 credentials
# Start services
cd kurral-api && docker-compose up -d
# Create user and get API key
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secure", "tenant_id": "acme"}'
# Upload artifact
curl -X POST http://localhost:8000/api/v1/artifacts/upload \
-H "X-API-Key: kurral_YOUR_KEY" \
-d @artifact.kurral
See kurral-api/README.md for complete API documentation.
Production Deployment
CLI:
# Install globally
pip install kurral
# Configure to use API backend
export KURRAL_STORAGE_BACKEND=api
export KURRAL_API_URL=https://api.kurral.your-domain.com
export KURRAL_API_KEY=kurral_YOUR_KEY
# Artifacts automatically upload to API
python your_agent.py # Traces are captured and uploaded
# Run replay in CI
kurral replay artifacts/*.kurral --diff
Docker Compose (Full Stack):
# Deploy API + Database + your agents
docker-compose -f docker-compose.production.yml up -d
License
MIT License - see LICENSE file
Support
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kurral-0.1.1.tar.gz.
File metadata
- Download URL: kurral-0.1.1.tar.gz
- Upload date:
- Size: 84.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
067ff3c2dc26e8bfb63d40314e594e352285889da36b19374c98801eb69abbcb
|
|
| MD5 |
19caa2cbf4c05d9c8d7964ff0a49ba49
|
|
| BLAKE2b-256 |
854948631f30c481a6760ccbbf8d1a4659fb621479773d08f5525d0f7f2c4752
|
File details
Details for the file kurral-0.1.1-py3-none-any.whl.
File metadata
- Download URL: kurral-0.1.1-py3-none-any.whl
- Upload date:
- Size: 91.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f95fc679663a823c3b54fc90d21e37e3715236d464b13937f8bd306dc70c704e
|
|
| MD5 |
a7efaf0f8393f2495f663fddaa5ba283
|
|
| BLAKE2b-256 |
6b13e3e66787ce6233c7a3fbfaeab80666b17c538a1020b154d9c9ca174663ba
|