Async Lifecycle
OpenAlice isn't just a chatbot that responds when you talk to it. It has an autonomous lifecycle — things happen even when you're not looking. This page explains the event-driven architecture that makes this work.
The Event Bus
At the center is the EventLog — a persistent, append-only JSONL event bus. Everything that happens asynchronously flows through it:
CronEngine ──fires──→ EventLog ──subscribes──→ CronListener
│ → Heartbeat
│ → SnapshotScheduler
↓
data/event-log/events.jsonl
The EventLog has two faces:
- Disk — Append-only JSONL file, the source of truth. Survives crashes and restarts.
- Memory — Ring buffer of the 500 most recent entries for fast queries. Rebuilt from disk on startup.
Subscribers register for specific event types. When an event is appended, all matching subscribers are notified synchronously. This fan-out is what connects the pieces.
Three Autonomous Actors
Three systems subscribe to cron.fire events, each with a different job:
CronListener — User-Defined Jobs
Handles jobs that the user or AI created. When a cron.fire event arrives for a non-internal job:
- Sends the job's payload to AgentCenter as a prompt
- Alice processes it through the full AI pipeline (tools, reasoning, response)
- The result is delivered via ConnectorCenter to the last-interacted channel
- Success/failure is logged as
cron.done/cron.error
Jobs run serially — if one is already processing, the next fire is skipped. This prevents overlapping AI calls.
Heartbeat — Market Monitoring
Handles the special __heartbeat__ job. Similar to CronListener but with extra intelligence:
- Active hours guard — Skip if outside configured time window
- AI call — Alice evaluates market conditions
- Response parsing — Structured protocol:
HEARTBEAT_OK(stay quiet) orCHAT_YES(send message) - Dedup — Suppress identical messages within 24 hours
- Delivery — Route through ConnectorCenter
See Heartbeat for the full protocol.
SnapshotScheduler — Portfolio Capture
Handles the special __snapshot__ job. No AI involved — purely mechanical:
- Iterates all enabled trading accounts
- Calls
buildSnapshot()for each (fetches positions, equity, P&L from broker) - Stores snapshots as JSONL per account
- Failed accounts get one retry after 3 seconds
The Trading Lifecycle
Trading has its own async lifecycle that connects to this system:
User/AI Decision
↓
stage operations → commit → push (requires approval)
↓ ↓
↓ Guard Pipeline runs
↓ ↓
↓ Broker executes orders
↓ ↓
↓ ┌──── Post-Push Hooks ────┐
↓ │ • Snapshot (immediate) │
↓ │ • EventLog recording │
↓ └──────────────────────────┘
↓
tradingSync (async — exchanges settle later)
↓
Order filled / cancelled / expired
↓
Sync commit recorded
Key lifecycle events:
- Post-push — Immediately after orders are sent to the broker, a snapshot captures the account state. This is event-driven, not cron-driven.
- Post-reject — When you reject a commit, a snapshot is also taken to record the state at the time of rejection.
- Sync — Order settlement is asynchronous. The AI calls
tradingSyncto check for fills, which may happen seconds or hours after the push.
Internal vs User Jobs
The cron engine distinguishes jobs by naming convention:
| Pattern | Type | Handler |
|---|---|---|
__heartbeat__ | Internal | Heartbeat system |
__snapshot__ | Internal | SnapshotScheduler |
| Everything else | User | CronListener → AI |
Internal jobs (double-underscore prefix/suffix) are routed to their dedicated handlers. User jobs go through the CronListener → AgentCenter → ConnectorCenter pipeline.
Startup Sequence
On boot, the async systems start in dependency order:
- EventLog — Created first. Everything depends on it.
- CronEngine — Loads persisted jobs from
data/cron/jobs.json. Arms timers. - CronListener — Subscribes to
cron.fireevents. - SnapshotScheduler — Registers/updates
__snapshot__job. Subscribes. - Heartbeat — Registers/updates
__heartbeat__job. Subscribes. - Plugins — Web, Telegram, MCP start and register connectors.
By the time plugins are up, the event bus is running and all subscribers are listening. The first cron fire after boot triggers the whole chain.
Error Resilience
- Cron jobs — Failed jobs get exponential backoff: 30s → 1m → 5m → 15m → 1h. Resets on success.
- Snapshots — Failed accounts get one retry. Failures are logged but don't crash the system.
- Heartbeat — Errors are logged as
heartbeat.error. The next scheduled fire tries again fresh. - EventLog — Dual-write to disk + memory. If the process crashes, the disk log survives and the memory buffer is rebuilt on restart.