Skip to main content
Routines are deterministic Python workflows that process data consistently. We call them “routines” (not “skills”) because they’re designed to be deterministic—same input always produces same output.

Why Routines Matter

When testing agents, we found they waste enormous amounts of tokens processing data and computing indicators during runtime. An agent might get candles, then write Python code to compute EMAs and support/resistance—spending tokens on computation that should be deterministic. By moving this into routines:
  • Session time dropped from 2 minutes to under 1 minute
  • Token usage reduced significantly
  • Results became reproducible and debuggable
If Python code can be created by the agent, it should be encapsulated in a routine so it can be reused without spending tokens.

Routines vs LLM Reasoning

AspectLLM ReasoningRoutines
ExecutionProbabilisticDeterministic
PurposeStrategy decisionsData processing and automation
VariabilityMay produce different outputsSame input → same output
CostLLM tokensNone
SpeedSecondsMilliseconds

Use Cases

Custom Indicators

Define technical indicators not built into exchanges:
# routines/indicators/vwap.py
async def calculate_vwap(connector: str, trading_pair: str, periods: int = 20):
    """Calculate Volume-Weighted Average Price."""
    candles = await get_candles(connector, trading_pair, periods)
    total_volume = sum(c.volume for c in candles)
    vwap = sum(c.close * c.volume for c in candles) / total_volume
    return {
        "vwap": vwap,
        "periods": periods,
        "total_volume": total_volume
    }
# routines/indicators/bollinger.py
async def calculate_bollinger_bands(connector: str, trading_pair: str, periods: int = 20, std_dev: float = 2.0):
    """Calculate Bollinger Bands."""
    candles = await get_candles(connector, trading_pair, periods)
    closes = [c.close for c in candles]

    sma = sum(closes) / len(closes)
    variance = sum((c - sma) ** 2 for c in closes) / len(closes)
    std = variance ** 0.5

    return {
        "upper": sma + (std_dev * std),
        "middle": sma,
        "lower": sma - (std_dev * std),
        "std": std
    }

TradingView Webhooks

Receive alerts from TradingView and trigger agent actions:
# routines/webhooks/tradingview.py
async def handle_tradingview_alert(request: Request):
    """Process TradingView webhook alerts."""
    payload = await request.json()

    alert = {
        "symbol": payload.get("ticker"),
        "action": payload.get("action"),  # "buy" or "sell"
        "price": float(payload.get("price", 0)),
        "message": payload.get("message"),
    }

    # Route to appropriate agent
    agent_id = get_agent_for_symbol(alert["symbol"])
    await notify_agent(agent_id, alert)

    return {"status": "received", "alert": alert}

Daily Reports

Generate scheduled reports aggregating performance data:
# routines/reports/daily_performance.py
async def generate_daily_report(agent_id: str):
    """Generate daily performance report for an agent."""
    from datetime import datetime, timedelta

    cutoff = datetime.utcnow() - timedelta(days=1)
    executors = await get_executors(controller_id=agent_id, since=cutoff)

    return {
        "agent_id": agent_id,
        "period": "24h",
        "generated_at": datetime.utcnow().isoformat(),
        "summary": {
            "total_executors": len(executors),
            "win_rate": calculate_win_rate(executors),
            "net_pnl_quote": sum(e.net_pnl_quote for e in executors),
            "volume_quote": sum(e.volume_quote for e in executors),
        }
    }

Custom Alerts

Monitor conditions and trigger notifications:
# routines/alerts/funding_rate.py
async def check_funding_rates(threshold: float = 0.01):
    """Alert when funding rate exceeds threshold."""
    alerts = []

    for connector in perpetual_connectors:
        rates = await get_funding_rates(connector)
        for pair, rate in rates.items():
            if abs(rate) > threshold:
                alerts.append({
                    "type": "funding_rate",
                    "connector": connector,
                    "pair": pair,
                    "rate": rate,
                    "direction": "long_paying" if rate > 0 else "short_paying"
                })

    return alerts

Directory Structure

There are two types of routines:

Global Routines (Shared)

Routines in ~/condor/routines/ are available to all agents:
~/condor/routines/
├── indicators/
│   ├── vwap.py
│   ├── bollinger.py
│   └── rsi.py
├── webhooks/
│   ├── tradingview.py
│   └── custom.py
├── reports/
│   ├── daily_performance.py
│   └── weekly_summary.py
└── alerts/
    ├── price_alerts.py
    ├── volume_alerts.py
    └── funding_alerts.py

Agent-Specific Routines

Routines in an agent’s routines/ folder are specific to that agent:
trading_agents/my_scalper/
└── routines/
    ├── support_resistance_ema_levels.py
    └── process_news.py
This design lets you share an agent folder with someone else—they get everything needed to run it.

Calling Routines

Agents invoke routines via MCP tools:
# From within an agent's decision process
vwap = await mcp_tools.run_routine(
    routine="indicators.vwap",
    params={
        "connector": "binance",
        "trading_pair": "BTC-USDT",
        "periods": 50
    }
)

if current_price < vwap["vwap"] * 0.98:
    # Price 2% below VWAP - consider buying
    pass

Creating Routines with Agent Builder

When you create an agent via /agent → Agent Builder, the system can generate custom routines for your strategy. For example, if you specify:
“I want to use one-minute candles and look for support and resistance levels plus EMAs of 7, 25, and 99”
The Agent Builder creates a routine like:
# routines/support_resistance_ema_levels.py
async def analyze_levels(
    trading_pair: str,
    connector_name: str,
    candle_count: int = 100,
    lookback: int = 20,
    tolerance: float = 0.02
):
    """Identify support/resistance levels and EMA alignment."""
    candles = await get_candles(connector_name, trading_pair, candle_count)

    # Calculate EMAs
    ema_7 = calculate_ema(candles, 7)
    ema_25 = calculate_ema(candles, 25)
    ema_99 = calculate_ema(candles, 99)

    # Determine trend from EMA alignment
    if ema_7 > ema_25 > ema_99:
        trend = "bullish"
    elif ema_7 < ema_25 < ema_99:
        trend = "bearish"
    else:
        trend = "mixed"

    # Find support/resistance levels
    supports, resistances = find_sr_levels(candles, lookback, tolerance)

    return {
        "price": candles[-1].close,
        "trend": trend,
        "ema_7": ema_7,
        "ema_25": ema_25,
        "ema_99": ema_99,
        "supports": supports,
        "resistances": resistances
    }
The routine is configurable—you can adjust parameters without modifying code.

Scheduling Routines

Configure scheduled execution in config.yml:
routines:
  daily_report:
    module: routines.reports.daily_performance
    function: generate_daily_report
    schedule: "0 0 * * *"  # Midnight UTC daily
    notify: ["telegram"]

  funding_check:
    module: routines.alerts.funding_rate
    function: check_funding_rates
    schedule: "*/15 * * * *"  # Every 15 minutes
    params:
      threshold: 0.005