Skip to main content

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 .kurral artifact 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?

  1. Beyond OpenTelemetry: We capture LLM-specific data that generic telemetry misses (prompts, token usage, determinism scores, tool call graphs)
  2. Replay-First Design: Every artifact contains everything needed to reproduce the execution
  3. Security & Compliance: Immutable artifacts with cryptographic hashes, perfect for audit trails
  4. Cost Attribution: Track spend by semantic bucket, model, and tenant
  5. 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 operation
    • parent_id: Build execution graphs for nested/chained calls
    • start_time/end_time: Precise timing for concurrency analysis
    • type: Operation classification (tool, llm, agent, retrieval, etc.)
    • error_type/error_stack: Comprehensive error forensics
    • metadata: Extensible field for cost, tokens, custom attributes
  • graph_version: Pinned graph fingerprint for version tracking
  • outputs: 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 config
  • ReplayValidation: output hash match, structural match, diff
  • ReplayMetadata: 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_match and result.validation.structural_match
  • Use --diff to 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

kurral-0.1.1.tar.gz (84.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kurral-0.1.1-py3-none-any.whl (91.6 kB view details)

Uploaded Python 3

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

Hashes for kurral-0.1.1.tar.gz
Algorithm Hash digest
SHA256 067ff3c2dc26e8bfb63d40314e594e352285889da36b19374c98801eb69abbcb
MD5 19caa2cbf4c05d9c8d7964ff0a49ba49
BLAKE2b-256 854948631f30c481a6760ccbbf8d1a4659fb621479773d08f5525d0f7f2c4752

See more details on using hashes here.

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

Hashes for kurral-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f95fc679663a823c3b54fc90d21e37e3715236d464b13937f8bd306dc70c704e
MD5 a7efaf0f8393f2495f663fddaa5ba283
BLAKE2b-256 6b13e3e66787ce6233c7a3fbfaeab80666b17c538a1020b154d9c9ca174663ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page