Cron Scheduler
Open Astra runs a built-in cron scheduler that manages 12 recurring maintenance jobs — from cost aggregation and token cleanup to memory decay and heartbeat monitoring. It also supports dynamic scheduled agents loaded from the database, allowing you to configure agents to run on any cron expression.
Built-in jobs
These jobs are registered automatically at gateway startup. All times are in the server's local timezone.
Schedule Job What it does
──────────────────────────────────────────────────────────────────────────────
0 1 * * * Cost snapshot Aggregate billing_usage → cost_snapshots
30 2 * * * Memory retention purge Per-workspace TTL-based data purge
0 3 * * * Token cleanup Delete expired/revoked refresh_tokens
15 3 * * * Revocation prune Delete expired rows from revoked_tokens
30 3 * * * Payload TTL Delete expired inference_payloads
0 4 * * * Memory consolidation Merge and compress daily_memory entries
15 4 * * * Memory weight decay Decay entries older than 7 days (0.12/day)
0 5 * * * Memory summarization Summarize for users active in last 2 days
30 4 * * 0 Graph reinforcement decay Weekly knowledge graph edge decay
*/5 * * * * Heartbeat monitor Run due heartbeat checks
*/30 * * * * Dream schedule check Check user_dream_configs, emit dream.scheduled
*/30 * * * * Research iteration Advance planning/researching tasksMemory weight decay
The daily memory decay job (4:15 AM) gradually reduces the weight of older memory entries, ensuring recent information is prioritized during retrieval. Entries lose 0.12 weight per day after a 7-day grace period, reaching zero (effectively archived) around day 16.
-- Memory entry weight decay formula
-- Applied to daily_memory.entries JSONB array
-- Only entries older than 7 days are affected
-- For entries with an existing weight:
new_weight = GREATEST(0.0, existing_weight - 0.12)
-- For entries without a weight field (legacy):
new_weight = GREATEST(0.0, 1.0 - (days_past_7 * 0.12))
-- Examples:
-- Day 7: weight = 1.0
-- Day 8: weight = 0.88
-- Day 14: weight = 0.16
-- Day 16: weight = 0.0 (floor)Dynamic scheduled agents
Beyond the built-in jobs, you can schedule any agent to run on a cron expression. Scheduled agents are stored in the scheduled_agents table and loaded at startup.
# In astra.yml or via API — schedule agents to run on a cron expression
# Table: scheduled_agents
id: UUID
agent_id: "researcher"
cron_expression: "0 9 * * 1-5" # weekdays at 9 AM
input_message: "Check for new papers on LLM safety"
surface: "scheduled"
surface_id: "weekly-papers"
enabled: true
last_run_at: 2026-02-28T09:00:00Z
last_run_error: nullEach scheduled agent runs the full agent loop with a synthetic user ID. Run status and errors are tracked per row.
// Dynamic scheduled agents use a synthetic UID:
uid = "scheduled:<id>" // e.g., "scheduled:abc-123"
// After each run, the row is updated:
UPDATE scheduled_agents
SET last_run_at = NOW(),
last_run_error = $error -- null on success, error message on failure
WHERE id = $idWhat each job maintains
| Job | Table(s) | Doc link |
|---|---|---|
| Cost snapshot | billing_usage → cost_snapshots | Daily cost aggregation |
| Memory retention | sessions, daily_memory, user_profiles | Per-workspace TTL purge |
| Token cleanup | refresh_tokens | JWT & Tokens |
| Revocation prune | revoked_tokens | JWT & Tokens |
| Payload TTL | inference_payloads | Payload Audit |
| Consolidation | daily_memory | 5-Tier System |
| Weight decay | daily_memory | Entry Weight Decay |
| Summarization | daily_memory, memory_summaries | Summarization |
| Graph decay | graph_edges | Relation Decay |
| Heartbeat | heartbeat_configs, heartbeat_runs | Heartbeat Daemon |
| Dream check | user_dream_configs | Dream Mode |
| Research | research_tasks | Deep Research |
Lifecycle
- The scheduler starts after the gateway HTTP server is listening
- Dynamic scheduled agents are loaded from the database at startup and when agents are updated via API
- On shutdown, all tasks are stopped via
stopScheduler() - Jobs that fail log errors but do not crash the scheduler — other jobs continue running