# CalendarScheduler Refactor Plan (v2) > Updated 2026-04-19 based on architecture discussion with hang ## Current Issues 1. `process.env.AGENT_ID` doesn't exist in plugins subprocess — always 'unknown' 2. Heartbeat is per-agent but should be per-claw-instance (global) 3. Scheduler only handles one agent — should manage all agents on this instance 4. wakeAgent used api.spawn (non-existent) → now uses dispatchInboundMessage (verified) ## Target Design ### Plugin State ```typescript // Local schedule cache: { agentId → [slots] } const schedules: Map = new Map(); ``` ### Sync Flow (every 5 min) ``` 1. GET /calendar/sync?claw_identifier=xxx - First call: server returns full { agentId → [slots] } - Subsequent: server returns diff since last sync 2. Update local schedules map 3. Scan schedules for due slots: for each agentId in schedules: if has slot where scheduled_at <= now && status == not_started: getAgentStatus(agentId, clawIdentifier) → busy? if not busy → wakeAgent(agentId) ``` ### Heartbeat (every 60s) Simplified to liveness ping only: ``` POST /monitor/server/heartbeat claw_identifier: xxx → server returns empty/ack ``` No slot data in heartbeat response. ### Wake Flow ``` dispatchInboundMessage: SessionKey: agent:{agentId}:hf-wakeup Body: "You have due slots. Follow the hf-wakeup workflow of skill hf-hangman-lab to proceed. Only reply WAKEUP_OK in this session." Agent reads workflow → calls hf tools → sets own status to busy ``` ### Agent ID Resolution - **Sync**: agentId comes from server response (dict keys) - **Wake**: agentId from local schedules dict key - **Tool calls by agent**: agentId from tool ctx (same as padded-cell) ## Backend API Changes Needed ### New: GET /calendar/sync ``` GET /calendar/sync?claw_identifier=xxx Headers: X-Claw-Identifier Response (first call): { "full": true, "schedules": { "developer": [slot1, slot2, ...], "operator": [slot3, ...] }, "syncToken": "abc123" } Response (subsequent, with ?since=abc123): { "full": false, "diff": [ { "op": "add", "agent": "developer", "slot": {...} }, { "op": "update", "agent": "developer", "slotId": 5, "patch": {...} }, { "op": "remove", "agent": "operator", "slotId": 3 } ], "syncToken": "def456" } ``` ### Existing: POST /calendar/agent/status Keep as-is but ensure it accepts agentId + clawIdentifier as params: ``` POST /calendar/agent/status { agent_id, claw_identifier, status } ``` ## Implementation Order 1. Backend: Add /calendar/sync endpoint 2. Plugin: Replace CalendarBridgeClient single-agent design with multi-agent 3. Plugin: Replace CalendarScheduler with new sync+check loop 4. Plugin: wakeAgent uses dispatchInboundMessage (done) 5. Plugin: Tool handlers get agentId from ctx (like padded-cell) ## Files to Change ### Backend (HarborForge.Backend) - New route: `/calendar/sync` - New service: schedule diff tracking per claw_identifier ### Plugin - `plugin/calendar/calendar-bridge.ts` — remove agentId binding, add sync() - `plugin/calendar/scheduler.ts` — rewrite to multi-agent sync+check - `plugin/calendar/schedule-cache.ts` — already exists, adapt to multi-agent - `plugin/index.ts` — update wakeAgent, getAgentStatus to accept agentId