Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Rohit-KK15/MetaVault-AI/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Agent automation enables continuous monitoring and management of your DeFi protocols without manual intervention. MetaVault AI uses node-cron to schedule periodic agent tasks.

Installation

pnpm add node-cron
pnpm add -D @types/node-cron

MonitoringService Class

The MonitoringService class manages automated agent execution using cron jobs.
src/crons/automation.ts
import cron, { type ScheduledTask } from "node-cron";
import dedent from "dedent";
import { getRootAgent } from "../agents/agent";
import type { EnhancedRunner } from "@iqai/adk";
import { env } from "../env";

export class MonitoringService {
  private isRunning = false;
  private monitoringJob: ScheduledTask | null = null;
  private yieldGenerateJob: ScheduledTask | null = null;

  constructor(
    private readonly monitoringCronExpr = "0 */1 * * *", // every 1 hour
    private readonly yieldGenerateCronExpr = "*/2 * * * *", // every 2 min
    private readonly telegramRunner: EnhancedRunner,
  ) { }

  start(): void {
    if (this.isRunning) {
      console.log("⚠️ MonitoringService already running");
      return;
    }
    this.isRunning = true;

    console.log("🤖 Starting MonitoringService...");
    console.log(`📅 Comprehensive cycle: ${this.monitoringCronExpr}`);
    console.log(`💹 Yield generation: ${this.yieldGenerateCronExpr}`);

    // Full monitoring every 1 Hour
    this.monitoringJob = cron.schedule(this.monitoringCronExpr, async () => {
      try {
        await this.runMonitoringCycle();
      } catch (err) {
        console.error("❌ runMonitoringCycle error:", (err as Error).message);
      }
    });

    // Yield generation every 2 minutes
    this.yieldGenerateJob = cron.schedule(this.yieldGenerateCronExpr, async () => {
      try {
        await this.yieldGeneration();
      } catch (err) {
        console.error("❌ yieldGeneration error:", (err as Error).message);
      }
    });

    // Run one cycle immediately
    void this.runMonitoringCycle();

    console.log("✅ MonitoringService started");
  }

  stop(): void {
    if (!this.isRunning) return;
    this.isRunning = false;

    if (this.yieldGenerateJob) this.yieldGenerateJob.stop();
    if (this.monitoringJob) this.monitoringJob.stop();

    console.log("🛑 MonitoringService stopped");
  }
}

Cron Expression Patterns

Cron expressions use the following format:
* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of week (0-7, 0 and 7 are Sunday)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)

Common Patterns

// Every minute
"* * * * *"

// Every 2 minutes
"*/2 * * * *"

// Every hour at minute 0
"0 * * * *"

// Every hour at minute 30
"30 * * * *"

// Every 6 hours
"0 */6 * * *"

// Every day at midnight
"0 0 * * *"

// Every Monday at 9 AM
"0 9 * * 1"

// Every 15 minutes
"*/15 * * * *"

Monitoring Cycle Implementation

The monitoring cycle executes a series of agent tasks to assess vault health.
src/crons/automation.ts
public async runMonitoringCycle(): Promise<void> {
  const timestamp = new Date().toISOString();
  console.log("\n" + "=".repeat(80));
  console.log(`🔄 Running monitoring cycle @ ${timestamp}`);
  console.log("=".repeat(80));

  try {
    const root = await getRootAgent();
    const runner = root.runner as EnhancedRunner;

    // 1. Market prices
    console.log("📊 Step 1: Market prices...");
    const priceCheck = await runner.ask(
      "Check real LINK and WETH prices using get_token_prices and evaluate volatility (>10%)."
    );

    // 2. Leverage strategy
    console.log("⚖️ Step 2: Leverage strategy...");
    const leverageCheck = await runner.ask(
      "Use get_leverage_strategy_state to evaluate LTV, borrow amounts, and pause status."
    );

    // 3. Risk assessment
    console.log("🚨 Step 3: Liquidation risk...");
    const riskCheck = await runner.ask(
      "Check liquidation risk using check_liquidation_risk and identify if deleveraging is required."
    );

    // 4. Vault & strategies
    console.log("💼 Step 4: Vault state...");
    const vaultCheck = await runner.ask(
      "Fetch get_vault_state and get_strategy_states to determine if rebalancing is required. Include the current Weights and the target weights of the strategies are 80% for leverage and 20% for the aave pool."
    );

    // 5. Actions to take
    console.log("🎯 Step 5: Decision making...");
    const actions = await runner.ask(
      dedent`
      Based on price, strategy, and risk:
      - initiate pausing or resuming leverage
      - initiate updating leverage parameters
      - initiate rebalancing if allocations diverge
      - initiate harvesting yields
      Provide reasoning and simulate transactions before recommending.
      `
    );

    const summary = dedent`
      🤖 *MetaVault Monitoring Report*
      🕒 ${timestamp}

      📊 *Price Analysis:*  
      ${priceCheck}

      ⚖️ *Leverage State:*  
      ${leverageCheck}

      🚨 *Risk Assessment:*  
      ${riskCheck}

      💼 *Vault State:*  
      ${vaultCheck}

      🎯 *Actions:*  
      ${actions}
    `;

    await this.sendTelegramSummary(summary);

    console.log("✅ Monitoring cycle finished\n");
  } catch (err: any) {
    console.error("❌ Monitoring cycle error:", err.message);

    const errorReport = dedent`
      ❌ *Monitoring Error*
      🕒 ${new Date().toISOString()}
      Error: ${err.message}
    `;
    await this.sendTelegramSummary(errorReport);
  }
}

Yield Generation Automation

Automate yield accrual to the pool:
src/crons/automation.ts
public async yieldGeneration(): Promise<void> {
  try {
    const root = await getRootAgent();
    const runner = root.runner as EnhancedRunner;

    const result = await runner.ask(
      "accrue yield to the vault."
    );

    console.log(`[${new Date().toISOString()}] Yield Generation →`, result);
  } catch (err: any) {
    console.error("yieldGeneration error:", err.message);
    await this.sendTelegramSummary(
      `❌ Yield generation failed: ${err.message}`
    );
  }
}

Telegram Notifications

Send monitoring summaries to Telegram:
src/crons/automation.ts
private async sendTelegramSummary(summary: string): Promise<void> {
  try {
    await this.telegramRunner.ask(
      dedent`
      Send the following monitoring summary to Telegram channel ${env.TELEGRAM_CHANNEL_ID}:
      
      ${summary}
    `
    );
    console.log("📨 Telegram summary sent");
  } catch (err: any) {
    console.error("❌ Error sending Telegram summary:", err.message);
  }
}

Initializing the Service

Start the monitoring service in your main application:
src/index.ts
import { createSamplingHandler } from "@iqai/adk";
import { getRootAgent } from "./agents/agent.js";
import { createTelegramAgent } from "./agents/telegram-agent/agent.js";
import { MonitoringService } from "./crons/automation.js";

async function main() {
  console.log("🤖 Initializing agents...");

  try {
    const { runner } = await getRootAgent();

    // Create sampling handler for the Telegram MCP
    const samplingHandler = createSamplingHandler(runner.ask);

    // Initialize Telegram agent
    const { runner: telegramRunner } = await createTelegramAgent(samplingHandler);

    console.log("✅ Agents initialized successfully!");
    console.log("🚀 Starting monitoring service...");

    // Start monitoring service
    const autoService = new MonitoringService(
      "0 */1 * * *",  // monitoring every 1 hour
      "*/2 * * * *",   // yield generation every 2 minutes
      telegramRunner
    );
    autoService.start();

    console.log("✅ Monitoring service started");

    // Keep the process running
    await keepAlive();
  } catch (error) {
    console.error("❌ Failed to initialize:", error);
    process.exit(1);
  }
}

main().catch(console.error);

Keep Alive Pattern

Keep the Node.js process running for continuous monitoring:
src/index.ts
import * as http from "node:http";

async function keepAlive() {
  const PORT = process.env.PORT || 3000;

  const server = http.createServer((req, res) => {
    if (req.url === "/" || req.url === "/health") {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ 
        status: "ok", 
        service: "metavault-ai",
        timestamp: new Date().toISOString()
      }));
    } else {
      res.writeHead(404);
      res.end();
    }
  });

  server.listen(PORT, () => {
    console.log(`🏥 Health check server running on port ${PORT}`);
  });

  // Graceful shutdown
  process.on("SIGINT", () => {
    console.log("\n👋 Shutting down gracefully...");
    process.exit(0);
  });

  // Keep event loop active
  setInterval(() => {
    // This keeps the event loop active
  }, 1000);
}

Advanced Scheduling

Multiple Cron Jobs

class AdvancedMonitoringService {
  private jobs: ScheduledTask[] = [];

  start(): void {
    // Price monitoring every 5 minutes
    this.jobs.push(cron.schedule("*/5 * * * *", async () => {
      await this.checkPrices();
    }));

    // Risk assessment every 15 minutes
    this.jobs.push(cron.schedule("*/15 * * * *", async () => {
      await this.assessRisk();
    }));

    // Full rebalance check every hour
    this.jobs.push(cron.schedule("0 * * * *", async () => {
      await this.checkRebalance();
    }));

    // Daily report at midnight
    this.jobs.push(cron.schedule("0 0 * * *", async () => {
      await this.sendDailyReport();
    }));
  }

  stop(): void {
    this.jobs.forEach(job => job.stop());
    this.jobs = [];
  }
}

Conditional Execution

public async runMonitoringCycle(): Promise<void> {
  const runner = (await getRootAgent()).runner as EnhancedRunner;

  // Check prices first
  const priceCheck = await runner.ask(
    "Check LINK and WETH prices and calculate 24h volatility."
  );

  // Only run risk assessment if volatility is high
  const volatilityMatch = priceCheck.match(/volatility: ([0-9.]+)%/);
  if (volatilityMatch && parseFloat(volatilityMatch[1]) > 10) {
    console.log("⚠️ High volatility detected, running risk assessment...");
    await runner.ask("Check liquidation risk and recommend actions.");
  }
}

Time-Based Logic

public async runMonitoringCycle(): Promise<void> {
  const hour = new Date().getHours();
  
  // Full monitoring during business hours
  if (hour >= 9 && hour <= 17) {
    await this.runFullMonitoring();
  } else {
    // Light monitoring outside business hours
    await this.runLightMonitoring();
  }
}

Error Handling

Retry Logic

private async runWithRetry(
  fn: () => Promise<void>,
  maxRetries: number = 3
): Promise<void> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await fn();
      return;
    } catch (err: any) {
      console.error(`Attempt ${i + 1} failed:`, err.message);
      if (i === maxRetries - 1) throw err;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Circuit Breaker

class MonitoringServiceWithCircuitBreaker {
  private failureCount = 0;
  private readonly maxFailures = 5;
  private circuitOpen = false;

  async runMonitoringCycle(): Promise<void> {
    if (this.circuitOpen) {
      console.log("⚠️ Circuit breaker open, skipping cycle");
      return;
    }

    try {
      await this.executeMonitoring();
      this.failureCount = 0; // Reset on success
    } catch (err: any) {
      this.failureCount++;
      console.error(`Monitoring failed (${this.failureCount}/${this.maxFailures})`);

      if (this.failureCount >= this.maxFailures) {
        this.circuitOpen = true;
        console.error("🚨 Circuit breaker opened!");
        await this.sendAlert("Circuit breaker opened due to repeated failures");
      }
    }
  }
}

Monitoring Service Logs

Expected console output:
🤖 Starting MonitoringService...
📅 Comprehensive cycle: 0 */1 * * *
💹 Yield generation: */2 * * * *
 MonitoringService started

================================================================================
🔄 Running monitoring cycle @ 2024-03-02T10:00:00.000Z
================================================================================
📊 Step 1: Market prices...
⚖️ Step 2: Leverage strategy...
🚨 Step 3: Liquidation risk...
💼 Step 4: Vault state...
🎯 Step 5: Decision making...
📨 Telegram summary sent
 Monitoring cycle finished

[2024-03-02T10:02:00.000Z] Yield Generation → {"message": "Yield Accrued Successfully!", "txHash": "0x..."}

Environment Configuration

.env
# Cron Schedule (optional, defaults in code)
MONITORING_CRON="0 */1 * * *"
YIELD_CRON="*/2 * * * *"

# Telegram Configuration
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHANNEL_ID=@your_channel

Best Practices

1. Graceful Shutdown

Always handle shutdown signals to stop cron jobs cleanly:
process.on("SIGTERM", () => {
  console.log("Received SIGTERM, shutting down...");
  monitoringService.stop();
  process.exit(0);
});

2. Avoid Overlapping Executions

Prevent concurrent executions of the same job:
private isExecuting = false;

public async runMonitoringCycle(): Promise<void> {
  if (this.isExecuting) {
    console.log("⏭️ Skipping cycle, previous execution still running");
    return;
  }

  this.isExecuting = true;
  try {
    await this.executeMonitoring();
  } finally {
    this.isExecuting = false;
  }
}

3. Logging and Observability

Log all cron executions with timestamps:
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] Starting monitoring cycle`);

4. Test Cron Expressions

Use crontab.guru to validate cron expressions before deployment.

Next Steps

Building Agents

Learn how to build agents for automation

Tools & Functions

Create tools that agents use in automated tasks

ADK-TS Integration

Understand the ADK-TS integration patterns

Deployment

Deploy your automated agents to production