# Lifecycle of a Cron-Triggered Proactive Agent in AstrBot: 9-Phase Execution Flow

> Explore the 9-phase lifecycle of a cron-triggered proactive agent in AstrBot. Discover how agents autonomously execute scheduled tasks and persist results.

- Repository: [AstrBot AI/AstrBot](https://github.com/AstrBotDevs/AstrBot)
- Tags: internals
- Published: 2026-03-12

---

**A cron-triggered proactive agent in AstrBot follows a 9-phase lifecycle from scheduler initialization through job persistence, culminating in the main agent waking autonomously to execute scheduled tasks and persist results to conversation history.**

The AstrBot framework enables autonomous agent execution through cron-triggered proactive jobs that wake the main conversational agent at scheduled intervals. Understanding the lifecycle of a cron-triggered proactive agent in AstrBot is essential for developers building scheduled reminders, health checks, or autonomous task workflows. This analysis examines the complete execution flow from `CronJobManager` initialization through history persistence, based on the actual implementation in the AstrBotDevs/AstrBot repository.

## Scheduler Initialization and Boot Sequence

The lifecycle begins when the star context starts the **CronJobManager** during bot initialization. In [`astrbot/core/cron/manager.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/astrbot/core/cron/manager.py), the `start()` method (lines 35-42) creates an **AsyncIOScheduler** instance and synchronizes persisted jobs from the database via `sync_from_db()`. This restoration phase ensures that recurring proactive agents survive bot restarts without losing their scheduled triggers or conversation context.

During boot, the manager loads all **CronJob** records marked as `enabled` from the SQLModel definitions in [`astrbot/core/db/po.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/astrbot/core/db/po.py). Each persisted job is rescheduled with APScheduler, reconstructing the trigger configurations (cron expressions or one-time dates) that were originally stored.

## Job Registration and Persistence

Developers register proactive agents through the `add_active_job()` method (lines 91-104). This method requires a serialized **MessageSession** in the payload under the `session` key, which identifies the specific conversation the agent should continue when triggered.

The registration supports two execution modes:

- **One-shot jobs**: Set `run_once=True` and provide a `run_at` datetime. These use APScheduler's `DateTrigger` and execute exactly once before automatic cleanup.
- **Recurring jobs**: Provide a `cron_expression` string (e.g., `"0 * * * *"`). These create `CronTrigger` objects for periodic execution.

When `persistent=True`, the job definition is stored in the database via SQLModel, ensuring the cron-triggered proactive agent resumes after bot restarts.

## Trigger Configuration and Scheduling

The `_schedule_job()` method (lines 44-71) bridges AstrBot's job definitions with APScheduler's trigger system. For active agents, it inspects the payload to determine trigger type:

```python

# From _schedule_job logic in astrbot/core/cron/manager.py

if job.payload.get("run_once"):
    trigger = DateTrigger(run_date=job.payload["run_at"])
else:
    trigger = CronTrigger.from_crontab(job.cron_expression)

```

The scheduler assigns a unique `job_id` and registers the callback `_run_job(job_id)` with APScheduler. At this point, the cron-triggered proactive agent enters a waiting state until the temporal condition satisfies the trigger.

## Execution Flow: From Fire to Completion

When the trigger fires, APScheduler invokes the internal `_run_job()` method (lines 191-206), initiating the active phase of the lifecycle.

### Trigger Activation and Status Recording

Upon invocation, `_run_job()` retrieves the job definition from the database and verifies the `enabled` flag. It records a "running" status timestamp before determining the job type. For entries where `job.job_type == "active_agent"`, execution delegates to `_run_active_agent_job()` (lines 34-44).

### Active Agent Job Dispatch

The `_run_active_agent_job()` method extracts the serialized session and optional note from the job payload. It constructs an `extras` dictionary containing cron metadata (job ID, name, execution timestamps) and prepares the environment for autonomous execution. This method serves as the bridge between the scheduler's generic job runner and AstrBot's conversational agent system.

### Main Agent Invocation and Autonomous Execution

The `_woke_main_agent()` method (signature lines 63-70, core logic lines 81-90) handles the actual wake sequence:

1. **Session Reconstruction**: Deserializes the `MessageSession` from the payload to restore conversation context.
2. **Event Creation**: Instantiates a `CronMessageEvent` wrapping the session.
3. **Request Enrichment**: Builds a `ProviderRequest` containing the system prompt `PROACTIVE_AGENT_CRON_WOKE_SYSTEM_PROMPT` from [`astrbot/core/astr_main_agent_resources.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/astrbot/core/astr_main_agent_resources.py).
4. **Tool Injection**: Automatically attaches `SEND_MESSAGE_TO_USER_TOOL` to the request's `func_tool` registry, enabling the agent to deliver proactive messages without human prompting.

The main agent then executes autonomously, potentially calling tools, reasoning about the scheduled task, and generating responses. The agent runs until completion or timeout, with full access to the conversation history through the reconstructed session.

### History Persistence and Context Retention

After the agent finishes, `persist_agent_history()` (lines 66-71) stores a concise execution summary in the conversation manager. This record includes the cron job metadata, LLM response content, and execution status, ensuring the autonomous action appears in the user's chat history as a coherent part of the conversation thread.

### Post-Run Cleanup and Job Lifecycle Management

The final phase of `_run_job()` (lines 221-224) updates the database with completion status (`completed` or `failed`) and calculates the next run time for recurring jobs. For one-shot executions, the method triggers deletion from both the APScheduler registry and the database, completely removing the ephemeral job.

## Scheduler Shutdown

When AstrBot shuts down, the `shutdown()` method (lines 44-49) gracefully stops the APScheduler event loop and marks the manager as stopped. This prevents orphaned trigger callbacks and ensures clean termination of any running proactive agent tasks.

## Code Examples

### Registering a One-Shot Proactive Agent

One-shot jobs execute once at a specific datetime and automatically clean up afterward:

```python
from datetime import datetime, timezone

await cron_manager.add_active_job(
    name="Morning reminder",
    cron_expression=None,
    run_once=True,
    run_at=datetime(2026, 3, 13, 9, 0, tzinfo=timezone.utc),
    payload={
        "session": message_session.to_str(),
        "note": "Good morning! Here's your daily summary.",
        "sender_id": "user-123",
    },
    enabled=True,
    persistent=False,
)

```

### Registering a Recurring Proactive Agent

Recurring jobs persist across restarts and execute on the specified cron schedule:

```python
await cron_manager.add_active_job(
    name="Hourly health check",
    cron_expression="0 * * * *",
    payload={
        "session": message_session.to_str(),
        "note": "Performing hourly system health check.",
        "origin": "api",
    },
    enabled=True,
    persistent=True,
)

```

### Internal Agent Context

When triggered, the proactive agent receives a `ProviderRequest` with:

- **System prompt**: Pre-loaded with `PROACTIVE_AGENT_CRON_WOKE_SYSTEM_PROMPT` describing the cron context.
- **Extras**: `extras["cron_job"]` contains the job ID, name, run-once flag, and timestamps.
- **Tools**: The `SEND_MESSAGE_TO_USER_TOOL` is automatically injected into `req.func_tool`, allowing autonomous message delivery.

```python

# Inside _woke_main_agent implementation

req.func_tool.add_tool(SEND_MESSAGE_TO_USER_TOOL)

```

## Summary

- **CronJobManager** initializes APScheduler and restores persisted jobs from [`astrbot/core/db/po.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/astrbot/core/db/po.py) during the boot sequence.
- **Job registration** requires a serialized **MessageSession** in the payload to maintain conversation continuity across autonomous executions.
- **Trigger scheduling** supports both `DateTrigger` for one-shot jobs and `CronTrigger` for recurring schedules via `_schedule_job()`.
- **Execution dispatch** flows through `_run_job()` → `_run_active_agent_job()` → `_woke_main_agent()`, reconstructing the session and injecting messaging tools.
- **History persistence** records execution summaries in the conversation manager, maintaining coherent chat context.
- **Cleanup logic** removes one-shot jobs automatically while updating recurring job schedules for subsequent executions.

## Frequently Asked Questions

### How does the proactive agent know which conversation to resume?

The agent identifies the conversation through the **MessageSession** serialized in the job payload's `session` field during registration. When `_woke_main_agent()` executes, it deserializes this session to reconstruct the exact conversation context, including previous messages and user identity, ensuring the proactive message appears in the correct chat thread.

### Can proactive agents send messages to users without receiving a prompt?

Yes. During the `_woke_main_agent()` phase, AstrBot automatically injects `SEND_MESSAGE_TO_USER_TOOL` into the `ProviderRequest`'s function tool registry. This allows the LLM to actively call the messaging tool during its autonomous execution cycle, delivering reminders or reports without waiting for user input.

### What happens to one-time cron jobs after they execute?

One-shot jobs (`run_once=True`) are automatically deleted from both the APScheduler registry and the database immediately after completion (lines 221-224 in [`manager.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/manager.py)). This ephemeral behavior prevents accumulation of expired jobs while ensuring the execution history remains persisted in the conversation log via `persist_agent_history()`.

### How does AstrBot handle proactive agent jobs during bot restarts?

When `persistent=True`, jobs are stored in the database using SQLModel definitions in [`astrbot/core/db/po.py`](https://github.com/AstrBotDevs/AstrBot/blob/main/astrbot/core/db/po.py). During the `start()` sequence, `sync_from_db()` reloads these jobs and recreates their triggers in APScheduler. This ensures recurring proactive agents resume their schedules accurately after bot restarts without manual re-registration.