Lifecycle of a Cron-Triggered Proactive Agent in AstrBot: 9-Phase Execution Flow
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, 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. 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=Trueand provide arun_atdatetime. These use APScheduler'sDateTriggerand execute exactly once before automatic cleanup. - Recurring jobs: Provide a
cron_expressionstring (e.g.,"0 * * * *"). These createCronTriggerobjects 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:
# 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:
- Session Reconstruction: Deserializes the
MessageSessionfrom the payload to restore conversation context. - Event Creation: Instantiates a
CronMessageEventwrapping the session. - Request Enrichment: Builds a
ProviderRequestcontaining the system promptPROACTIVE_AGENT_CRON_WOKE_SYSTEM_PROMPTfromastrbot/core/astr_main_agent_resources.py. - Tool Injection: Automatically attaches
SEND_MESSAGE_TO_USER_TOOLto the request'sfunc_toolregistry, 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:
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:
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_PROMPTdescribing the cron context. - Extras:
extras["cron_job"]contains the job ID, name, run-once flag, and timestamps. - Tools: The
SEND_MESSAGE_TO_USER_TOOLis automatically injected intoreq.func_tool, allowing autonomous message delivery.
# 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.pyduring the boot sequence. - Job registration requires a serialized MessageSession in the payload to maintain conversation continuity across autonomous executions.
- Trigger scheduling supports both
DateTriggerfor one-shot jobs andCronTriggerfor 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). 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →