- 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>
113 lines
3.9 KiB
Markdown
113 lines
3.9 KiB
Markdown
# 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:**
|
|
1. Every heartbeat queries backend for pending slots — no local awareness of full schedule
|
|
2. Cannot detect slots assigned by other agents between heartbeats
|
|
3. 60s interval is too frequent for sync but too infrequent for precise wakeup
|
|
4. 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`:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
// 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()`:
|
|
1. `bridge.getDaySchedule(today)` → update cache
|
|
2. Still send heartbeat to keep backend informed of agent liveness
|
|
|
|
`runCheck()`:
|
|
1. `cache.getDueSlots(now)` → find due slots
|
|
2. Filter out session-deferred slots
|
|
3. 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
|
|
|
|
1. Add `ScheduleCache` class (new file: `plugin/calendar/schedule-cache.ts`)
|
|
2. Add `getDaySchedule()` to `CalendarBridgeClient`
|
|
3. 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)
|
|
4. Update state persistence for new structure
|
|
5. 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
|