{
"meta": {
"agent": "planner",
"task_id": "443",
"title": "AW-7: Create EventBusService abstraction backed by Redis",
"created_at": "2026-04-13T00:00:00Z"
},
"inputs": [
{
"name": "issue-443",
"type": "context",
"ref": "https://github.com/AgentSDE/agent-core/issues/443",
"notes": "EventBusService backed by Redis pub/sub"
},
{
"name": "ws-gateway.service.ts",
"type": "file",
"ref": "src/ws-gateway/ws-gateway.service.ts",
"notes": "Current @OnEvent listeners — all 6 events with payload: unknown"
},
{
"name": "task-state.service.ts",
"type": "file",
"ref": "src/task-state/task-state.service.ts",
"notes": "Emits task.updated with taskId, issue, repo, status, currentPhase, gatePhase, gateReason"
},
{
"name": "sqlite-job-queue.ts",
"type": "file",
"ref": "src/queue/sqlite-job-queue.ts",
"notes": "Emits job.created (no payload), job.completed, job.failed"
},
{
"name": "watchdog.service.ts",
"type": "file",
"ref": "src/watchdog/watchdog.service.ts",
"notes": "Emits task.stuck with taskId, issue, repo, phase, ageMs"
},
{
"name": "phase-router.service.ts",
"type": "file",
"ref": "src/phase-router/phase-router.service.ts",
"notes": "Emits artefacts.synced with taskId, issue, repo, phase"
},
{
"name": "health.module.ts",
"type": "file",
"ref": "src/health/health.module.ts",
"notes": "Existing ioredis factory pattern for reference"
}
],
"outputs": [
{
"name": "plan.md",
"type": "plan",
"format": "md",
"content": "Implementation plan for EventBusService"
}
],
"files": [
{
"path": "src/event-bus/event-bus.events.ts",
"action": "create",
"reason": "Typed event map (event name → payload interface)"
},
{
"path": "src/event-bus/event-bus.service.ts",
"action": "create",
"reason": "EventBusService — Redis pub/sub wrapper"
},
{
"path": "src/event-bus/event-bus.module.ts",
"action": "create",
"reason": "@Global() module with ioredis factory providers"
},
{
"path": "src/event-bus/index.ts",
"action": "create",
"reason": "Barrel export"
},
{
"path": "src/event-bus/event-bus.service.spec.ts",
"action": "create",
"reason": "Unit tests"
},
{
"path": "src/app.module.ts",
"action": "modify",
"reason": "Import EventBusModule"
}
],
"steps": [
{
"id": "S1",
"summary": "Create typed event map and EventBusService with Redis pub/sub",
"acceptance": [
"event-bus.events.ts defines EventMap with all 6 event names and typed payloads",
"EventBusService.emit() publishes to Redis channel with agentsde:events: prefix",
"EventBusService.emit() catches Redis errors, logs, and swallows — never throws",
"EventBusService.on() subscribes and dispatches to registered handlers",
"Multiple handlers per event all invoked; error in one does not crash others",
"onModuleDestroy() gracefully quits both pub and sub Redis connections"
],
"depends_on": []
},
{
"id": "S2",
"summary": "Create @Global() EventBusModule, register in AppModule, add unit tests",
"acceptance": [
"EventBusModule is @Global() and exports EventBusService",
"Module factory-provides two ioredis clients from REDIS_HOST/REDIS_PORT/REDIS_DB config",
"EventBusModule imported in AppModule",
"Unit tests cover: emit publishes, handler receives, multi-handler, error isolation, emit swallows errors, onModuleDestroy quits clients",
"npm run build passes",
"npm run test passes",
"npm run lint passes with zero warnings"
],
"depends_on": [
"S1"
]
}
],
"risks": [
{
"risk": "Redis unavailable at emit time",
"mitigation": "emit() catches all errors and logs — fire-and-forget by design"
},
{
"risk": "Channel naming collision with other Redis users",
"mitigation": "Prefix agentsde:events: scopes channels to this application"
}
],
"assumptions": [
"ioredis and Redis config (REDIS_HOST, REDIS_PORT, REDIS_DB) are already available on master",
"EventBusService runs alongside EventEmitter2 — no existing emitters or listeners are changed in this PR"
],
"open_questions": []
}