- "Agent can register via POST /api/v1/agents/register with agentId, name, group, version, routeIds, capabilities and receive a response containing SSE endpoint URL and server config"
- "Re-registration with the same agentId resumes existing identity (transitions back to LIVE, updates metadata)"
- "Agent can send heartbeat via POST /api/v1/agents/{id}/heartbeat and receive 200 (or 404 if unknown)"
- "Server transitions agents LIVE->STALE after 90s without heartbeat, STALE->DEAD 5 minutes after staleTransitionTime"
- "Agent list endpoint GET /api/v1/agents returns all agents, filterable by ?status=LIVE|STALE|DEAD"
Build the agent registry domain model, registration/heartbeat REST endpoints, and lifecycle monitoring.
Purpose: Agents need to register with the server, send periodic heartbeats, and the server must track their LIVE/STALE/DEAD states. This is the foundation that the SSE push layer (Plan 02) builds on.
Output: Core domain types (AgentInfo, AgentState, AgentCommand, CommandStatus, CommandType), AgentRegistryService in core module, registration/heartbeat/list controllers in app module, lifecycle monitor, unit + integration tests.
- state (AgentState), registeredAt (Instant), lastHeartbeat (Instant)
- staleTransitionTime (Instant, nullable -- set when transitioning to STALE)
- Use synchronized methods or volatile fields for thread safety since ConcurrentHashMap only protects the map, not the values.
- Actually, prefer immutable-style: store as records in the ConcurrentHashMap and use computeIfPresent to atomically swap. AgentInfo can be a record with wither-style methods (withState, withLastHeartbeat, etc.).
5.**AgentCommand**: Record with fields: id (String, UUID), type (CommandType), payload (String -- raw JSON), targetAgentId (String), createdAt (Instant), status (CommandStatus). Provide withStatus method.
6.**AgentEventListener**: Interface with methods `onCommandReady(String agentId, AgentCommand command)` -- this allows the SSE layer (Plan 02) to be notified when a command is added. The core module defines the interface; the app module implements it.
7.**AgentRegistryService**: Plain class (no Spring annotations), constructor takes staleThresholdMs (long), deadThresholdMs (long), commandExpiryMs (long). Uses ConcurrentHashMap<String, AgentInfo> for agents and ConcurrentHashMap<String, List<AgentCommand>> (or ConcurrentHashMap<String, ConcurrentLinkedQueue<AgentCommand>>) for pending commands per agent.
4.**Update application.yml**: Add agent-registry section with all defaults (see RESEARCH.md code example). Also add `spring.mvc.async.request-timeout: -1` for SSE support (Plan 02 needs it, but set it now).
5.**AgentLifecycleMonitor** (@Component):
- Inject AgentRegistryService
-@Scheduled(fixedDelayString = "${agent-registry.lifecycle-check-interval-ms:10000}") calls registryService.checkLifecycle() and registryService.expireOldCommands()
- Follow ClickHouseFlushScheduler pattern but simpler (no SmartLifecycle needed -- agent state is ephemeral)
-`POST /register`: Accept raw String body, parse JSON with ObjectMapper. Extract: agentId (required), name (required), group (default "default"), version, routeIds (default empty list), capabilities (default empty map). Call registryService.register(). Build response JSON: { agentId, sseEndpoint: "/api/v1/agents/{agentId}/events", heartbeatIntervalMs: from config, serverPublicKey: null (Phase 4 placeholder) }. Return 200.
-`POST /{id}/heartbeat`: Call registryService.heartbeat(id). Return 200 if true, 404 if false.
-`GET /`: Accept optional @RequestParam status. If status provided, parse to AgentState and call findByState. Otherwise call findAll. Serialize with ObjectMapper, return 200. Handle invalid status with 400.
- Add @Tag(name = "Agent Management") and @Operation annotations for OpenAPI.
<done>POST /register returns 200 with agentId + sseEndpoint + heartbeatIntervalMs. POST /{id}/heartbeat returns 200 for known agents, 404 for unknown. GET /agents returns all agents with optional ?status= filter. AgentLifecycleMonitor runs on schedule. All integration tests pass. mvn clean verify passes.</done>
</task>
</tasks>
<verification>
mvn clean verify -- full suite green (existing Phase 1+2 tests still pass, new agent tests pass)
</verification>
<success_criteria>
- Agent registration flow works end-to-end via REST
- Heartbeat updates agent state correctly
- Lifecycle monitor transitions LIVE->STALE->DEAD based on configured thresholds
- Agent list endpoint with optional status filter returns correct results
- All 7+ integration tests pass
- Existing test suite unbroken
</success_criteria>
<output>
After completion, create `.planning/phases/03-agent-registry-sse-push/03-01-SUMMARY.md`