1. ScheduleCache: local cache of today's schedule, synced every 5 min
from HF backend via new getDaySchedule() API
2. Wakeup prompts updated to reference daily-routine skill workflows
(task-handson, plan-schedule, slot-complete)
3. Agent wakeup via dispatchInboundMessageWithDispatcher (in-process)
- Same mechanism as Discord plugin
- Creates unique session per slot: agent:{agentId}:hf-calendar:slot-{slotId}
- No WebSocket, CLI, or cron dependency
- Verified working on test environment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.2 KiB
3.2 KiB
CalendarScheduler Refactor Plan (v2)
Updated 2026-04-19 based on architecture discussion with hang
Current Issues
process.env.AGENT_IDdoesn't exist in plugins subprocess — always 'unknown'- Heartbeat is per-agent but should be per-claw-instance (global)
- Scheduler only handles one agent — should manage all agents on this instance
- wakeAgent used api.spawn (non-existent) → now uses dispatchInboundMessage (verified)
Target Design
Plugin State
// Local schedule cache: { agentId → [slots] }
const schedules: Map<string, CalendarSlotResponse[]> = 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
- Backend: Add /calendar/sync endpoint
- Plugin: Replace CalendarBridgeClient single-agent design with multi-agent
- Plugin: Replace CalendarScheduler with new sync+check loop
- Plugin: wakeAgent uses dispatchInboundMessage (done)
- 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+checkplugin/calendar/schedule-cache.ts— already exists, adapt to multi-agentplugin/index.ts— update wakeAgent, getAgentStatus to accept agentId