- New ScheduleCache class: maintains today's full schedule locally - CalendarBridgeClient.getDaySchedule(): fetch all slots for a date - Scheduler now runs two intervals: - Heartbeat (60s): existing slot execution flow (unchanged) - Sync (5min): pulls full day schedule into local cache - Exposes getScheduleCache() for tools and status reporting This enables the plugin to detect slots assigned by other agents between heartbeats and provides a complete local view of the schedule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.9 KiB
3.9 KiB
CalendarScheduler Refactor Plan
Current Design
Every 60s:
heartbeat() → POST /calendar/agent/heartbeat → returns pending slots
if idle → select highest priority → executeSlot → wakeAgent(spawn)
if busy → defer all pending slots
Problems:
- Every heartbeat queries backend for pending slots — no local awareness of full schedule
- Cannot detect slots assigned by other agents between heartbeats
- 60s interval is too frequent for sync but too infrequent for precise wakeup
- Wakeup via
api.spawn()creates a plain session, not a Discord private channel
Target Design
Every 5-10 min (sync interval):
syncSchedule() → GET /calendar/day → update local today cache
Every 30s (check interval):
checkDueSlots() → scan local cache for due slots
if due slot found:
confirmAgentStatus() → GET /calendar/agent/status
if not busy → wakeAgent (via Dirigent moderator bot private channel)
Changes Required
1. Add Local Schedule Cache
New class ScheduleCache:
class ScheduleCache {
private slots: Map<string, CalendarSlotResponse>; // slotId → slot
private lastSyncAt: Date | null;
async sync(bridge: CalendarBridgeClient): Promise<void>; // fetch today's full schedule
getDueSlots(now: Date): CalendarSlotResponse[]; // scheduled_at <= now && NOT_STARTED/DEFERRED
updateSlot(id: string, update: Partial<CalendarSlotResponse>): void; // local update
getAll(): CalendarSlotResponse[];
}
2. Add CalendarBridgeClient.getDaySchedule()
New endpoint call:
async getDaySchedule(date: string): Promise<CalendarSlotResponse[]>
// GET /calendar/day?date=YYYY-MM-DD
This fetches ALL slots for the day, not just pending ones. The existing heartbeat() only returns NOT_STARTED/DEFERRED.
3. Split Heartbeat into Sync + Check
Replace single runHeartbeat() with two intervals:
// Sync: every 5 min — pull full schedule from backend
this.syncInterval = setInterval(() => this.runSync(), 300_000);
// Check: every 30s — scan local cache for due slots
this.checkInterval = setInterval(() => this.runCheck(), 30_000);
runSync():
bridge.getDaySchedule(today)→ update cache- Still send heartbeat to keep backend informed of agent liveness
runCheck():
cache.getDueSlots(now)→ find due slots- Filter out session-deferred slots
- If agent idle → select highest priority → execute
4. Wakeup via Dirigent (future)
Change wakeAgent() to create a private Discord channel via Dirigent moderator bot instead of api.spawn(). This requires:
- Access to Dirigent's moderator bot token or cross-plugin API
- Creating a private channel with only the target agent
- Posting the wakeup prompt as a message
For now: Keep api.spawn() as the wakeup method. The Dirigent integration can be added later as it requires cross-plugin coordination.
Implementation Order
- Add
ScheduleCacheclass (new file:plugin/calendar/schedule-cache.ts) - Add
getDaySchedule()toCalendarBridgeClient - Refactor
CalendarScheduler:- Replace single interval with sync + check intervals
- Use cache instead of heartbeat for slot discovery
- Keep heartbeat for agent liveness reporting (reduced frequency)
- Update state persistence for new structure
- Keep existing wakeAgent/completion/abort/pause/resume tools unchanged
Files to Modify
| File | Changes |
|---|---|
plugin/calendar/schedule-cache.ts |
New file |
plugin/calendar/calendar-bridge.ts |
Add getDaySchedule() |
plugin/calendar/scheduler.ts |
Refactor heartbeat → sync + check |
plugin/calendar/index.ts |
Export new types |
Risk Assessment
- Low risk: ScheduleCache is additive, doesn't break existing behavior
- Medium risk: Splitting heartbeat changes core scheduling logic
- Mitigation: Keep
heartbeat()method intact, use it for liveness reporting alongside new sync