Skip to main content
This example shows how to implement a custom policy to influence model behavior dynamically during execution. At a certain step the policy instructs the model to speak like a pirate.
import asyncio
import json
from dedalus_labs import AsyncDedalus, DedalusRunner
from dedalus_labs.utils.streaming import stream_async
from dotenv import load_dotenv
from dedalus_labs.utils.streaming import stream_async

load_dotenv()

def on_tool(evt: dict) -> None:
    print("[policy tool evt]", json.dumps(evt))

def calculate_ship_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Calculate nautical distance between two coordinates."""
    print(f"[tool:calculate_distance] from ({lat1}, {lon1}) to ({lat2}, {lon2})")
    
    # Simplified distance calculation
    lat_diff = abs(lat2 - lat1)
    lon_diff = abs(lon2 - lon1)
    distance = ((lat_diff ** 2 + lon_diff ** 2) ** 0.5) * 60  # Rough nautical miles
    return round(distance, 2)

def estimate_sailing_time(distance: float, wind_speed: int = 15) -> dict:
    """Estimate sailing time based on distance and wind conditions."""
    print(f"[tool:estimate_time] distance={distance} nautical miles, wind={wind_speed} knots")
    
    # Assume average sailing speed based on wind
    if wind_speed < 10:
        sailing_speed = 4  # knots
        conditions = "light winds"
    elif wind_speed < 20:
        sailing_speed = 7  # knots  
        conditions = "moderate winds"
    else:
        sailing_speed = 10  # knots
        conditions = "strong winds"
    
    hours = distance / sailing_speed
    days = hours / 24
    
    return {
        "hours": round(hours, 1),
        "days": round(days, 1),
        "conditions": conditions,
        "sailing_speed": sailing_speed
    }

def check_treasure_coordinates(lat: float, lon: float) -> str:
    """Check if coordinates match any famous treasure locations."""
    print(f"[tool:check_treasure] checking ({lat}, {lon})")
    
    famous_locations = {
        (25.7, -80.3): "Near the Florida Keys - known pirate waters!",
        (18.2, -77.3): "Jamaica - Port Royal, the old pirate haven!",
        (12.1, -68.9): "Aruba - Caribbean treasure waters!",
        (32.3, -64.7): "Bermuda - mysterious triangle territory!"
    }
    
    # Check if close to any famous location
    for (treasure_lat, treasure_lon), description in famous_locations.items():
        if abs(lat - treasure_lat) < 2 and abs(lon - treasure_lon) < 2:
            return description
    
    return "Open seas - chart yer own course, matey!"

def policy(ctx: dict) -> dict:
    step = ctx.get("step", 1)
    print(f"[policy] step={step}")
    pol: dict = {}

    if step == 3:
        pol.update({"message_prepend": [{"role": "system", "content": "You must speak like a pirate."}]})
    
    # Cap total steps for safety
    pol.setdefault("max_steps", 4)
    return pol

async def main() -> None:
    client = AsyncDedalus()
    runner = DedalusRunner(client)

    prompt = (
        "Step 1) Calculate the sailing distance from Miami (25.7617, -80.1918) to Nassau, Bahamas (25.0443, -77.3504). "
        "Step 2) Estimate how long it would take to sail there with 18-knot winds. "
        "Step 3) Check if Nassau coordinates are near any famous treasure locations and search for historical pirate activity in the Bahamas. "
    )

    result = runner.run(
        input=prompt,
        model="openai/gpt-5",
        tools=[calculate_ship_distance, estimate_sailing_time, check_treasure_coordinates],
        mcp_servers=["tsion/brave-search-mcp"],
        stream=True,
        on_tool_event=on_tool,
        policy=policy
    )

    await stream_async(result)    

if __name__ == "__main__":
    asyncio.run(main())
I