Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nearai/ironclaw/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Routines are named, persistent, user-owned tasks that fire automatically based on triggers (cron schedules, events, webhooks, or manual invocation). Each routine has an action (lightweight LLM call or full job) and guardrails to prevent runaway execution.
Architecture
┌──────────┐ ┌─────────┐ ┌──────────────────┐
│ Trigger │────▶│ Engine │────▶│ Execution Mode │
│ cron/event│ │guardrail│ │lightweight│full_job│
│ webhook │ │ check │ └──────────────────┘
│ manual │ └─────────┘ │
└──────────┘ ▼
┌──────────────┐
│ Notify user │
│ if needed │
└──────────────┘
Core Concepts
Routine Structure
Every routine has:
- ID: Unique UUID
- Name: Human-readable identifier
- Description: What the routine does
- Trigger: When to fire (cron/event/webhook/manual)
- Action: What to execute (lightweight/full_job)
- Guardrails: Safety constraints (cooldown, concurrency, dedup)
- Notify Config: Where and when to send notifications
Triggers
Cron Trigger
Fires on a schedule:
trigger:
type: cron
schedule: "0 9 * * MON-FRI" # 9 AM weekdays
Supported formats:
- Standard cron:
"0 9 * * MON-FRI"
- Human-readable:
"every 2h", "daily at 9am"
Event Trigger
Fires when a channel message matches a pattern:
trigger:
type: event
channel: telegram # Optional filter
pattern: "deploy\\s+\\w+" # Regex pattern
Webhook Trigger
Fires on incoming HTTP POST:
trigger:
type: webhook
path: custom-webhook # Optional suffix (defaults to routine ID)
secret: my-secret # Optional HMAC validation
Webhook URL: https://your-domain.com/hooks/routine/{id}
Manual Trigger
Only fires via CLI or tool call:
Actions
Lightweight Action
Single LLM call, no tools. Fast and cheap:
action:
type: lightweight
prompt: |
Check for open PRs labeled 'urgent' and summarize them.
context_paths:
- context/priorities.md
max_tokens: 4096
Execution:
- Load context files from workspace
- Send single LLM request with prompt + context
- Check response for
ROUTINE_OK (nothing to report) or content (attention needed)
- Send notification based on status
Full Job Action
Multi-turn agent with full tool access:
action:
type: full_job
title: Deploy Review
description: Review pending deploys and create summary
max_iterations: 10
Execution:
- Create a job via the scheduler
- Run full agent loop with tools
- Return when complete or max iterations reached
Guardrails
Prevents runaway execution:
guardrails:
cooldown: 300s # Min time between fires
max_concurrent: 1 # Max simultaneous runs
dedup_window: 3600s # Content-hash dedup for event triggers
Notification Config
notify:
channel: telegram # Where to send notifications
user: myuser # Who to notify
on_attention: true # Notify when routine finds something
on_failure: true # Notify on errors
on_success: false # Notify on ROUTINE_OK
Creating Routines
Via CLI
# Create from YAML file
ironclaw routine create --file routine.yaml
# List all routines
ironclaw routine list
# Enable/disable
ironclaw routine enable <id>
ironclaw routine disable <id>
# Fire manually
ironclaw routine fire <id>
The agent can create routines:
<RoutineCreate>
<name>pr-check</name>
<trigger>cron:0 9 * * MON-FRI</trigger>
<action>
Check GitHub PRs labeled 'urgent' and create summary in
context/pr-summary.md
</action>
</RoutineCreate>
Programmatic API
use ironclaw::agent::routine::{
Routine, Trigger, RoutineAction, RoutineGuardrails, NotifyConfig
};
use std::time::Duration;
let routine = Routine {
id: Uuid::new_v4(),
name: "pr-check".to_string(),
description: "Check for urgent PRs".to_string(),
user_id: "alice".to_string(),
enabled: true,
trigger: Trigger::Cron {
schedule: "0 9 * * MON-FRI".to_string(),
},
action: RoutineAction::Lightweight {
prompt: "Check PRs labeled 'urgent'".to_string(),
context_paths: vec![],
max_tokens: 4096,
},
guardrails: RoutineGuardrails {
cooldown: Duration::from_secs(300),
max_concurrent: 1,
dedup_window: None,
},
notify: NotifyConfig {
channel: Some("telegram".to_string()),
user: "alice".to_string(),
on_attention: true,
on_failure: true,
on_success: false,
},
// Runtime state (managed by DB)
last_run_at: None,
next_fire_at: None,
run_count: 0,
consecutive_failures: 0,
state: serde_json::json!({}),
created_at: Utc::now(),
updated_at: Utc::now(),
};
db.create_routine(&routine).await?;
Runtime State
Routine State
Routines can maintain state across runs:
// Read previous state
let state_path = format!("routines/{}/state.md", routine_name);
let state = workspace.read(&state_path).await?;
// Update state after run
workspace.write(&state_path, new_state).await?;
State files are stored in workspace/routines/{name}/state.md.
Run History
Every execution creates a RoutineRun record:
pub struct RoutineRun {
pub id: Uuid,
pub routine_id: Uuid,
pub trigger_type: String,
pub trigger_detail: Option<String>,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub status: RunStatus, // Running | Ok | Attention | Failed
pub result_summary: Option<String>,
pub tokens_used: Option<i32>,
pub job_id: Option<Uuid>, // For full_job actions
}
Query run history:
ironclaw routine runs <id> --limit 10
Execution Engine
Cron Ticker
Polls the database every N seconds for due routines:
pub fn spawn_cron_ticker(
engine: Arc<RoutineEngine>,
interval: Duration,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
let mut ticker = tokio::time::interval(interval);
ticker.tick().await; // Skip immediate first tick
loop {
ticker.tick().await;
engine.check_cron_triggers().await;
}
})
}
Default interval: 30 seconds
Event Matcher
Called synchronously from the agent main loop:
// After handling incoming message
let fired = engine.check_event_triggers(&message).await;
if fired > 0 {
tracing::info!("Fired {} event-triggered routines", fired);
}
Matching process:
- Load event routines from cache
- Filter by channel (if specified)
- Match regex pattern against message content
- Check guardrails (cooldown, concurrency, dedup)
- Spawn execution in background task
Guardrail Checks
Cooldown
fn check_cooldown(&self, routine: &Routine) -> bool {
if let Some(last_run) = routine.last_run_at {
let elapsed = Utc::now().signed_duration_since(last_run);
let cooldown = chrono::Duration::from_std(routine.guardrails.cooldown)
.unwrap_or(chrono::Duration::seconds(300));
if elapsed < cooldown {
return false;
}
}
true
}
Concurrency
async fn check_concurrent(&self, routine: &Routine) -> bool {
match self.store.count_running_routine_runs(routine.id).await {
Ok(count) => count < routine.guardrails.max_concurrent as i64,
Err(e) => {
tracing::error!("Failed to check concurrent runs: {}", e);
false
}
}
}
Deduplication
For event triggers, prevents firing on duplicate content:
pub fn content_hash(content: &str) -> u64 {
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
Hashes are stored for the dedup_window duration.
Notifications
Routines send notifications based on status:
pub enum RunStatus {
Running, // In progress
Ok, // Nothing to report
Attention, // Needs user attention
Failed, // Error occurred
}
Notification format:
✅ Routine 'pr-check': ok
🔔 Routine 'pr-check': attention
Found 3 PRs labeled 'urgent':
- PR #123: Fix auth bug
- PR #124: Update deps
- PR #125: Performance fix
❌ Routine 'pr-check': failed
Error: GitHub API rate limit exceeded
Database Schema
Routines Table
CREATE TABLE routines (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
user_id TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
trigger_type TEXT NOT NULL,
trigger_config JSONB NOT NULL,
action_type TEXT NOT NULL,
action_config JSONB NOT NULL,
guardrail_cooldown_seconds INTEGER NOT NULL,
guardrail_max_concurrent INTEGER NOT NULL,
guardrail_dedup_window_seconds INTEGER,
notify_config JSONB NOT NULL,
last_run_at TIMESTAMP,
next_fire_at TIMESTAMP,
run_count BIGINT NOT NULL DEFAULT 0,
consecutive_failures INTEGER NOT NULL DEFAULT 0,
state JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Routine Runs Table
CREATE TABLE routine_runs (
id UUID PRIMARY KEY,
routine_id UUID NOT NULL REFERENCES routines(id) ON DELETE CASCADE,
trigger_type TEXT NOT NULL,
trigger_detail TEXT,
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP,
status TEXT NOT NULL,
result_summary TEXT,
tokens_used INTEGER,
job_id UUID REFERENCES jobs(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Configuration
# Enable routines
ROUTINES_ENABLED=true
# Cron ticker interval (seconds)
ROUTINES_CRON_INTERVAL=30
# Global max concurrent routines
ROUTINES_MAX_CONCURRENT=10
# Event cache refresh interval (seconds)
ROUTINES_EVENT_CACHE_REFRESH=60
Examples
Daily PR Summary
name: daily-pr-summary
description: Summarize open PRs every morning
trigger:
type: cron
schedule: "0 9 * * MON-FRI"
action:
type: lightweight
prompt: |
Check GitHub for open PRs. For each PR:
1. Title and number
2. Author and review status
3. Labels and age
Save summary to context/pr-summary.md
context_paths:
- context/priorities.md
max_tokens: 4096
guardrails:
cooldown: 3600s
max_concurrent: 1
notify:
channel: slack
user: team
on_attention: true
on_failure: true
on_success: false
Deploy Webhook
name: deploy-webhook
description: Run tests and deploy on webhook
trigger:
type: webhook
secret: ${DEPLOY_SECRET}
action:
type: full_job
title: Deploy
description: Run tests, build, and deploy to production
max_iterations: 20
guardrails:
cooldown: 60s
max_concurrent: 1
notify:
channel: ops
user: oncall
on_attention: true
on_failure: true
on_success: true
Security Alert Monitor
name: security-alerts
description: Monitor for security alerts in logs
trigger:
type: event
channel: monitoring
pattern: "(?i)(security|auth|breach|hack)"
action:
type: lightweight
prompt: |
Analyze the security-related message.
If it's a false positive, reply ROUTINE_OK.
If it needs attention, summarize the threat.
max_tokens: 2048
guardrails:
cooldown: 300s
max_concurrent: 3
dedup_window: 3600s
notify:
channel: security
user: security-team
on_attention: true
on_failure: true
on_success: false
Source Code
Key files:
src/agent/routine.rs - Core types (Routine, Trigger, Action, Guardrails)
src/agent/routine_engine.rs - Execution engine (cron ticker, event matcher)
src/agent/scheduler.rs - Job scheduling for full_job actions
migrations/ - Database schema migrations