{
"meta": {
"agent": "planner",
"task_id": "444",
"title": "AW-8: Migrate EventEmitter2 events to EventBusService",
"created_at": "2026-04-13T12:00:00Z"
},
"inputs": [
{
"name": "issue-444",
"type": "context",
"ref": "https://github.com/AgentSDE/agent-core/issues/444",
"notes": "Full migration spec with 6 rounds"
},
{
"name": "AGENTS.md",
"type": "context",
"ref": "AGENTS.md",
"notes": "Codebase conventions and architecture"
}
],
"outputs": [
{
"name": "plan.md",
"type": "plan",
"format": "md",
"content": "6-round incremental migration plan"
}
],
"files": [
{
"path": "src/task-state/task-state.service.ts",
"action": "modify",
"reason": "R1: Replace EventEmitter2 with EventBusService for task.updated emits"
},
{
"path": "src/task-state/task-state.module.ts",
"action": "modify",
"reason": "R1: Import EventBusModule"
},
{
"path": "src/ws-gateway/ws-gateway.service.ts",
"action": "modify",
"reason": "R1-4: Replace all @OnEvent decorators with eventBus.on() handlers"
},
{
"path": "src/ws-gateway/ws-gateway.module.ts",
"action": "modify",
"reason": "R1+R6: Import EventBusModule, remove EventEmitterModule"
},
{
"path": "src/metrics/metrics.cache.ts",
"action": "modify",
"reason": "R1: Replace @OnEvent with eventBus.on()"
},
{
"path": "src/metrics/metrics.module.ts",
"action": "modify",
"reason": "R1: Import EventBusModule"
},
{
"path": "src/watchdog/watchdog.service.ts",
"action": "modify",
"reason": "R2: Replace EventEmitter2 with EventBusService"
},
{
"path": "src/watchdog/watchdog.module.ts",
"action": "modify",
"reason": "R2: Import EventBusModule"
},
{
"path": "src/phase-router/phase-router.service.ts",
"action": "modify",
"reason": "R3: Replace EventEmitter2 with EventBusService"
},
{
"path": "src/phase-router/phase-router.module.ts",
"action": "modify",
"reason": "R3: Import EventBusModule"
},
{
"path": "src/queue/sqlite-job-queue.ts",
"action": "modify",
"reason": "R4-5: Migrate job events, replace self-trigger with direct call"
},
{
"path": "src/queue/queue.module.ts",
"action": "modify",
"reason": "R4: Import EventBusModule"
},
{
"path": "package.json",
"action": "modify",
"reason": "R6: Remove @nestjs/event-emitter"
}
],
"steps": [
{
"id": "S1",
"summary": "Round 1: Migrate task.updated — replace 4 emits in TaskStateService, 2 @OnEvent listeners in WsGatewayService and MetricsCache, update 3 modules and specs",
"acceptance": [
"TaskStateService uses EventBusService.emit('task.updated') with try/catch at all 4 call sites",
"WsGatewayService.onTaskUpdated registered via eventBus.on() in onModuleInit",
"MetricsCache.invalidate registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"depends_on": []
},
{
"id": "S2",
"summary": "Round 2: Migrate task.stuck — replace 1 emit in WatchdogService, 1 @OnEvent in WsGatewayService, update module and specs",
"acceptance": [
"WatchdogService uses EventBusService.emit('task.stuck') with try/catch",
"WsGatewayService.onTaskStuck registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"depends_on": [
"S1"
]
},
{
"id": "S3",
"summary": "Round 3: Migrate artefacts.synced — replace 2 emits in PhaseRouterService, 1 @OnEvent in WsGatewayService, update module and specs",
"acceptance": [
"PhaseRouterService uses EventBusService.emit('artefacts.synced') with try/catch at both call sites",
"WsGatewayService.onArtefactsSynced registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"depends_on": [
"S2"
]
},
{
"id": "S4",
"summary": "Round 4: Migrate job.completed and job.failed — replace 2 emits in SqliteJobQueue, 2 @OnEvent in WsGatewayService, update module and specs",
"acceptance": [
"SqliteJobQueue uses EventBusService.emit() for job.completed and job.failed with try/catch",
"WsGatewayService listeners registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"depends_on": [
"S3"
]
},
{
"id": "S5",
"summary": "Round 5: Migrate job.created — replace 3 self-trigger emits with direct void this.processNext(), also emit to Redis, remove @OnEvent from processNext(), update specs",
"acceptance": [
"SqliteJobQueue calls void this.processNext() directly instead of emitting job.created",
"Redis emit added for future multi-instance support",
"@OnEvent('job.created') decorator removed from processNext()",
"No EventEmitter2 import remains in SqliteJobQueue",
"Job queue processes pending jobs end-to-end",
"npm run build && npm run test pass"
],
"depends_on": [
"S4"
]
},
{
"id": "S6",
"summary": "Round 6: Remove @nestjs/event-emitter — delete EventEmitterModule.forRoot() from all modules, npm uninstall, verify no remaining imports",
"acceptance": [
"No EventEmitter2 or OnEvent imports in the codebase",
"No EventEmitterModule.forRoot() calls in any module",
"@nestjs/event-emitter removed from package.json",
"npm run build && npm run test pass"
],
"depends_on": [
"S5"
]
}
],
"risks": [
{
"risk": "Round 5 job.created is pipeline-critical — broken self-trigger stops all phase execution",
"mitigation": "Direct processNext() call is simpler than pub/sub; validate in test environment with E2E before merging"
},
{
"risk": "AW-7 dependency — EventBusService must exist before any round begins",
"mitigation": "Plan blocked until #443 merges; re-evaluate if AW-7 scope changes"
}
],
"assumptions": [
"EventBusService from AW-7 (#443) exposes emit() and on() methods compatible with the migration pattern",
"EventBusService is provided via an EventBusModule that can be imported per-consumer module",
"Redis connection is available in all environments (dev, test, prod)"
],
"open_questions": []
}