Cowboy’s Actor Message Scheduler is a protocol-level mechanism that enables truly autonomous actors through native timer support. Unlike traditional blockchains that require external infrastructure for scheduled execution, Cowboy embeds timers directly into the consensus layer.
Key Innovation: Actors can schedule their own future execution without relying on centralized keepers, cron jobs, or external bots.
Examples in this page are conceptual and illustrative (non-normative). Final interfaces are defined by the SDK and normative CIPs; do not treat these snippets as fixed APIs.
On many traditional chains, contracts cannot self-schedule execution; external actors must trigger functions, which introduces centralization and reliability risks.
Copy
// ❌ Ethereum: This never executes itselfcontract Liquidator { function checkPosition() public { if (position.isUndercollateralized()) { liquidate(); } // Who calls this? When? 🤷 }}
Current workarounds all have significant drawbacks:
Cron Jobs
Keeper Networks
User-Triggered
Approach: Off-chain server runs scheduled tasksProblems:
❌ Centralized (single point of failure)
❌ Requires infrastructure maintenance
❌ Trust assumption (will it run?)
❌ No SLA guarantees
Approach: Decentralized network of bots (e.g., Chainlink Keepers)Problems:
❌ Complex integration
❌ Expensive (additional fees)
❌ External dependency
❌ Still requires registration/management
Approach: Users call functions when neededProblems:
Actors can schedule future execution by specifying a target height and a Gas Bidding Agent (GBA). The protocol will trigger the handler when due, under a fixed per‑block timer budget (CIP‑1). The GBA dynamically prices execution based on protocol‑supplied context.
Purpose: Handle timers scheduled for the immediate future.Structure:
Copy
Layer 1 (ring buffer) maps imminent blocks to buckets; placing and retrieving timers is O(1). Exact sizes are governance‑defined per CIP‑1.
Operations:
Copy
Enqueue/dequeue at Layer 1 are O(1); due timers for the current height are retrieved from the corresponding bucket.
Example:
Copy
Current block: 1000Timer scheduled for block 1050Bucket: 1050 % 256 = 26Action: Add to ring_buffer[26]At block 1050:Action: Retrieve all timers from ring_buffer[26]
Purpose: Hold timers scheduled for the next few hours/days.Structure:
Copy
Layer 2 (epoch queue) groups timers by future epochs; sizes and epochs are governance‑defined. At epoch boundaries, timers entering range are promoted into Layer 1.
Epoch Maintenance:
Copy
Epoch maintenance promotes timers from the current epoch bucket into ring buckets; all work is amortized and bounded (CIP‑1).
Example:
Copy
Current block: 1000 (epoch 2)Timer scheduled for block 1500 (epoch 4)Action: Add to epoch_queue[4]At block 1440 (start of epoch 4):Action: Move all timers from epoch_queue[4] to ring_buffer
Layer 3 (overflow sorted set) holds very long‑horizon timers in a Merkleized balanced tree, providing O(log N) operations and efficient range queries.
Maintenance:
Copy
Overflow migration moves timers that enter the migration horizon into their epoch buckets during epoch maintenance.
Example:
Copy
Current block: 1000Timer scheduled for block 50,000Action: Insert into overflow_setLater, when block 45,000 arrives:Action: Migrate from overflow_set to epoch_queue
Concept: Actors specify a smart contract (GBA) that dynamically determines the execution bid at runtime.
Copy
Gas Bidding Agents (GBA) are read‑only contracts queried at execution time; they return bidding parameters (e.g., max_fee_per_cycle, tip_per_cycle) using protocol‑supplied context (trigger/current heights, basefees, last block usage, owner balance) per CIP‑1.
The protocol provides rich context to GBAs for informed bidding:
Copy
class BiddingContext: # Timing information trigger_block_height: int # When timer was scheduled current_block_height: int # Current height # Fee market information basefee_cycle: int # Current cycle basefee basefee_cell: int # Current cell basefee last_block_cycle_usage: int # Congestion indicator # Actor information owner_actor_balance: int # Available funds for payment
Example Use Cases:
DeFi Liquidation
Oracle Update
Gaming Tick
Copy
def get_gas_bid(self, context): # Check collateral ratio position = load_position(self.position_id) risk = calculate_liquidation_risk(position) # Bid based on risk if risk > 0.9: # Very high risk return ultra_aggressive_bid() elif risk > 0.7: return aggressive_bid() else: return normal_bid()
Copy
def get_gas_bid(self, context): # Check how stale current price is last_update = self.last_price_update_block staleness = context.current_block_height - last_update # Bid based on staleness if staleness > 100: # Very stale return high_priority_bid() else: return normal_bid()
Copy
def get_gas_bid(self, context): # Game tick should execute reliably but not aggressively # Consistent, moderate bidding return moderate_bid()
Mechanism: Hard limit on timer resource consumption per block.
Copy
At each block: collect due timers, query GBA for bids, order by effective tip, execute until the per‑block timer budget is exhausted, roll over the rest (CIP‑1).
Benefits:
✅ Regular user transactions always have guaranteed space
Execution order is by effective tip (min(tip_per_cycle, max_fee_per_cycle − basefee_cycle)); deterministic ordering and rollover rules ensure bounded work and liveness (CIP‑1/CIP‑3).
Example Scenario:
Copy
Block 1000 timer queue (10 timers, budget for 6):Priority Queue (sorted by tip):1. Liquidation Bot A: tip=1000 → Execute ✅2. Liquidation Bot B: tip=800 → Execute ✅3. Oracle Update: tip=700 → Execute ✅4. DeFi Rebalance: tip=500 → Execute ✅5. NFT Auction End: tip=400 → Execute ✅6. Game Tick: tip=300 → Execute ✅--- Budget exhausted (10M cycles) ---7. Low Priority Bot: tip=200 → Rollover to block 10018. Reminder Service: tip=100 → Rollover to block 10019. Data Archive: tip=50 → Rollover to block 100110. Test Timer: tip=10 → Rollover to block 1001