Skip to content

Latest commit

 

History

History
334 lines (254 loc) · 8.93 KB

File metadata and controls

334 lines (254 loc) · 8.93 KB

Triggers

Background tasks and external events.

Overview

Triggers are long-running async tasks that run concurrently with the agent loop. They are spawned when the agent starts and can push notifications to the agent via the connection. Common use cases include periodic health checks, heartbeat monitors, file watchers, and external event listeners.

Python SDK Comparison

The trigger concept is consistent across both SDKs. In Python, triggers are registered via Agent(triggers=[...]). In Rust, triggers are registered through the builder's .trigger() or .triggers() methods and implement the Trigger trait.


Trigger Trait

The Trigger trait defines a single run method that receives the active connection and executes for the lifetime of the agent:

use antigravity_sdk_rust::connection::AnyConnection;

/// A trait for defining asynchronous background tasks that execute
/// during a connection lifecycle.
pub trait Trigger: Send + Sync {
    /// Launches the trigger task with the active connection.
    ///
    /// This method runs for the lifetime of the agent. Use the connection
    /// to send notifications back to the agent.
    async fn run(&self, connection: AnyConnection) -> Result<(), anyhow::Error>;
}

The connection parameter gives triggers access to send_trigger_notification(), which pushes a message string into the agent's event stream.


DynTrigger (Object-Safe Wrapper)

Like the Hook/DynHook pattern, triggers have an object-safe counterpart that wraps the async run method in a BoxFuture:

use futures_util::future::BoxFuture;
use antigravity_sdk_rust::connection::AnyConnection;

/// Object-safe version of `Trigger`, automatically implemented
/// via a blanket impl for any `T: Trigger`.
pub trait DynTrigger: Send + Sync {
    fn run(&self, connection: AnyConnection) -> BoxFuture<'_, Result<(), anyhow::Error>>;
}

// Blanket impl: any type implementing Trigger automatically implements DynTrigger.
// impl<T: Trigger + ?Sized> DynTrigger for T { ... }

You always implement Trigger — the SDK handles the DynTrigger conversion automatically.


TriggerRunner

The TriggerRunner orchestrates the spawning and lifecycle of all registered triggers:

use antigravity_sdk_rust::triggers::TriggerRunner;
use std::sync::Arc;

// TriggerRunner stores triggers as Vec<Arc<dyn DynTrigger>>

API

Method Description
TriggerRunner::new(triggers) Creates a runner wrapping a Vec<Arc<dyn DynTrigger>>
runner.start(connection) Spawns each trigger as an independent tokio task

When start() is called, each trigger is cloned (via Arc) and spawned into its own tokio::spawn block. If a trigger's run method returns an error, it is logged via tracing::error! but does not crash the agent.

Note

Triggers run independently — one trigger failing does not affect others. Errors are logged but silently swallowed to maintain agent stability.


Trigger Helpers

every() — Periodic Timer

The every() factory function creates a trigger that fires at regular intervals, sending a message to the agent each time:

use antigravity_sdk_rust::trigger_helpers::every;
use std::time::Duration;

// Send "check_status" to the agent every 30 seconds
let heartbeat = every(Duration::from_secs(30), "check_status");

// With a custom message
let monitor = every(Duration::from_millis(500), "fast_poll");

The returned PeriodicTrigger loops indefinitely:

loop {
    sleep(interval)
    connection.send_trigger_notification(message)
}

PeriodicTrigger Internals

use std::time::Duration;

/// A trigger that fires at regular intervals.
/// Created via `every()`.
pub struct PeriodicTrigger {
    /// How long to wait between notifications.
    interval: Duration,
    /// The message sent to the agent on each tick.
    message: String,
}

Types

TriggerDelivery

Controls when trigger notifications are delivered to the agent:

/// Controls when trigger notifications are delivered to the agent.
#[derive(Debug, Clone, Copy)]
pub enum TriggerDelivery {
    /// Deliver the notification immediately, even if the agent is busy.
    SendImmediately,
    /// Wait until the agent is idle before delivering.
    WaitIdle,
}

FileChange

Represents a single filesystem change event, useful for file-watching triggers:

/// The kind of filesystem change detected by a file-watching trigger.
#[derive(Debug, Clone, Copy)]
pub enum FileChangeKind {
    /// A new file was created.
    Added,
    /// An existing file was modified.
    Modified,
    /// A file was deleted.
    Deleted,
}

/// A single filesystem change event.
#[derive(Debug, Clone)]
pub struct FileChange {
    /// The type of change.
    pub kind: FileChangeKind,
    /// The path of the affected file.
    pub path: String,
}

Examples

1. Heartbeat Trigger

The simplest trigger — periodic status checks using the built-in every() helper:

use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::trigger_helpers::every;
use antigravity_sdk_rust::triggers::DynTrigger;
use std::sync::Arc;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    let heartbeat = every(Duration::from_secs(60), "heartbeat_check");

    let agent = Agent::builder()
        .trigger(Arc::new(heartbeat) as Arc<dyn DynTrigger>)
        .allow_all()
        .build();

    let agent = agent.start().await?;
    // The heartbeat trigger is now running in the background,
    // sending "heartbeat_check" every 60 seconds.

    let response = agent.chat("Monitor the system").await?;
    println!("{}", response.text);

    agent.stop().await?;
    Ok(())
}

2. Custom Monitoring Trigger

A trigger that monitors a resource and sends notifications when conditions change:

use antigravity_sdk_rust::connection::AnyConnection;
use antigravity_sdk_rust::connection::Connection;
use antigravity_sdk_rust::triggers::Trigger;
use std::time::Duration;

struct DiskSpaceMonitor {
    path: String,
    threshold_mb: u64,
    check_interval: Duration,
}

impl DiskSpaceMonitor {
    fn new(path: impl Into<String>, threshold_mb: u64) -> Self {
        Self {
            path: path.into(),
            threshold_mb,
            check_interval: Duration::from_secs(300), // every 5 minutes
        }
    }
}

impl Trigger for DiskSpaceMonitor {
    async fn run(&self, connection: AnyConnection) -> Result<(), anyhow::Error> {
        loop {
            tokio::time::sleep(self.check_interval).await;

            // Simulate checking disk space
            // In production, use a filesystem API
            let available_mb = 500u64; // placeholder

            if available_mb < self.threshold_mb {
                let msg = format!(
                    "⚠️ Low disk space on {}: {}MB remaining (threshold: {}MB)",
                    self.path, available_mb, self.threshold_mb
                );
                connection.send_trigger_notification(&msg).await?;
            }
        }
    }
}

3. Builder Integration

Registering multiple triggers with the agent builder:

use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::trigger_helpers::every;
use antigravity_sdk_rust::triggers::DynTrigger;
use std::sync::Arc;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    let agent = Agent::builder()
        // Add individual triggers
        .trigger(Arc::new(every(Duration::from_secs(30), "health_check")) as Arc<dyn DynTrigger>)
        .trigger(Arc::new(every(Duration::from_secs(300), "metrics_report")) as Arc<dyn DynTrigger>)
        .allow_all()
        .build();

    let agent = agent.start().await?;
    // Both triggers are now running concurrently in the background.

    agent.stop().await?;
    Ok(())
}

Or register after construction but before starting:

use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::trigger_helpers::every;
use antigravity_sdk_rust::triggers::DynTrigger;
use std::sync::Arc;
use std::time::Duration;

let mut agent = Agent::builder().allow_all().build();
agent.register_trigger(
    Arc::new(every(Duration::from_secs(60), "late_trigger")) as Arc<dyn DynTrigger>
).expect("trigger registration failed");
// agent.start().await?;

4. Batch Triggers via .triggers()

You can set all triggers at once using the plural form:

use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::trigger_helpers::every;
use antigravity_sdk_rust::triggers::DynTrigger;
use std::sync::Arc;
use std::time::Duration;

let all_triggers: Vec<Arc<dyn DynTrigger>> = vec![
    Arc::new(every(Duration::from_secs(30), "check_a")),
    Arc::new(every(Duration::from_secs(60), "check_b")),
    Arc::new(every(Duration::from_secs(120), "check_c")),
];

let agent = Agent::builder()
    .triggers(all_triggers)
    .allow_all()
    .build();