2026-09-05 00:31:28 -07:00
//! Agent Lifecycle Handlers (Phase 6)
use actix_web ::{ web , HttpRequest , HttpResponse };
use serde ::{ Deserialize , Serialize };
use std ::sync ::Arc ;
use crate ::agent ::{ Agent , AgentConfig , AgentCapability , DefaultAgent };
use crate ::agent ::client_sdk ::SynthesisClient ;
use crate ::handlers ::response_builder ;
use tracing ::{ debug , info , error , warn };
/// Register agent request
#[derive(Debug, Deserialize)]
pub struct RegisterAgentRequest {
pub agent_id : String ,
pub project_id : String ,
pub capabilities : Vec < String > ,
pub webhook_url : Option < String > ,
pub rate_limit : Option < u32 > ,
}
/// Agent response
#[derive(Debug, Serialize)]
pub struct AgentResponse {
pub agent_id : String ,
pub project_id : String ,
pub capabilities : Vec < String > ,
pub webhook_url : Option < String > ,
pub rate_limit : u32 ,
pub created_at : String ,
pub status : String ,
}
2026-09-05 00:48:12 -07:00
// JWT token extraction is now in crate::handlers::jwt_utils
2026-09-05 00:31:28 -07:00
/// POST /agents - Register new agent
pub async fn register_agent_handler (
req : HttpRequest ,
body : web ::Json < RegisterAgentRequest > ,
state : web ::Data < crate ::AppState > ,
) -> HttpResponse {
if let Err ( response ) = crate ::handlers ::middleware ::validate_and_rate_limit (
& req , & state , "agent" , 50
) {
return response ;
}
if body . agent_id . is_empty () || body . project_id . is_empty () {
return response_builder ::bad_request ( "agent_id and project_id required" );
}
if body . capabilities . is_empty () {
return response_builder ::bad_request ( "At least one capability required" );
}
debug! ( "Registering agent: {}" , body . agent_id );
// Parse capabilities
let caps : Vec < AgentCapability > = body . capabilities . iter ()
. filter_map ( | c | match c . as_str () {
"entity_linking" => Some ( AgentCapability ::EntityLinking ),
"inference_facts" => Some ( AgentCapability ::InferenceFacts ),
"reason_query" => Some ( AgentCapability ::ReasonQuery ),
"summarization" => Some ( AgentCapability ::Summarization ),
"semantic_search" => Some ( AgentCapability ::SemanticSearch ),
"graph_traversal" => Some ( AgentCapability ::GraphTraversal ),
_ => None ,
})
. collect ();
if caps . is_empty () {
return response_builder ::bad_request ( "Invalid capabilities" );
}
let config = AgentConfig {
agent_id : body . agent_id . clone (),
project_id : body . project_id . clone (),
capabilities : caps . clone (),
webhook_url : body . webhook_url . clone (),
rate_limit : body . rate_limit . unwrap_or ( 1000 ),
metadata : std ::collections ::HashMap ::new (),
};
// Store agent config (stub: would persist to DB)
let agent = DefaultAgent ::new ( config );
// Extract JWT from request for agent reasoning calls
2026-09-05 00:48:12 -07:00
if let Some ( jwt ) = crate ::handlers ::extract_jwt_token ( & req ) {
2026-09-05 00:31:28 -07:00
debug! ( "Agent registered with JWT token (len: {})" , jwt . len ());
} else {
warn! ( "Agent registered without JWT token" );
}
info! ( "Agent registered: {}" , agent . config (). agent_id );
2026-09-05 00:37:58 -07:00
// Wire Temporal workflow (via api.riotpiao.com/workflow)
2026-09-05 00:48:12 -07:00
// Temporal activities will:
// 1. Persist agent state to temporal_workflow_links table
// 2. Execute LLMInferenceActivity (call LLM via api.riotpiao.com/v1/chat/completions)
// 3. Store reasoning traces to memory_entity/memory_edge
2026-09-05 00:52:30 -07:00
if let Some ( jwt ) = crate ::handlers ::extract_jwt_token ( & req ) {
2026-09-05 00:37:58 -07:00
let client = SynthesisClient ::new (
"https://api.riotpiao.com" . to_string (),
jwt ,
);
// Start Temporal workflow for agent initialization
2026-09-05 00:52:30 -07:00
// Include LLMInferenceActivity configuration for capability verification
2026-09-05 00:48:12 -07:00
let workflow_input = serde_json ::json! ({
"agent_id" : body . agent_id ,
"capabilities" : body . capabilities ,
2026-09-05 00:52:30 -07:00
"project_id" : body . project_id ,
// LLMInferenceActivity inputs for agent capability reasoning
"llm_activity" : {
"model" : "ornith:13b" ,
"system_prompt" : "You are an agent capability validator. Verify that the requested capabilities are valid for the memory system. Return JSON with 'valid' boolean and 'reason' string." ,
"user_prompt" : format ! ( "Validate agent capabilities: {:?}" , body . capabilities ),
"temperature" : 0.5 ,
"max_tokens" : 512
}
2026-09-05 00:48:12 -07:00
});
let workflow_req = crate ::handlers ::WorkflowBuilder ::new ( "AgentInitialization" )
. with_id ( & format! ( "agent-init- {} " , body . agent_id ))
. with_queue ( "agents" )
. with_input ( workflow_input )
. build ();
match tokio ::runtime ::Handle ::current (). block_on ( client . execute_workflow ( workflow_req )) {
Ok ( response ) => {
if let Some ( data ) = response . get ( "data" ) {
let workflow_id = data . get ( "workflow_id" ). and_then ( | v | v . as_str ()). unwrap_or ( "unknown" );
let run_id = data . get ( "run_id" ). and_then ( | v | v . as_str ()). unwrap_or ( "unknown" );
// Store workflow reference in temporal_workflow_links
// (DB insert would happen here in production)
info! ( "Agent workflow started: workflow_id={}, run_id={}" , workflow_id , run_id );
debug! ( "Temporal activity will persist agent state + reasoning traces" );
2026-09-05 00:37:58 -07:00
}
}
2026-09-05 00:48:12 -07:00
Err ( e ) => {
warn! ( "Failed to start agent workflow: {}" , e );
// Non-fatal: agent still created, just workflow unavailable
}
}
2026-09-05 00:37:58 -07:00
}
2026-09-05 00:31:28 -07:00
response_builder ::success_response ( AgentResponse {
agent_id : agent . config (). agent_id . clone (),
project_id : agent . config (). project_id . clone (),
capabilities : body . capabilities . clone (),
webhook_url : body . webhook_url . clone (),
rate_limit : agent . config (). rate_limit ,
created_at : chrono ::Utc ::now (). to_rfc3339 (),
status : "active" . to_string (),
})
}
/// GET /agents/{id} - Get agent status
pub async fn get_agent_handler (
req : HttpRequest ,
path : web ::Path < String > ,
state : web ::Data < crate ::AppState > ,
) -> HttpResponse {
let agent_id = path . into_inner ();
if let Err ( response ) = crate ::handlers ::middleware ::validate_and_rate_limit (
& req , & state , "agent" , 100
) {
return response ;
}
debug! ( "Getting agent: {}" , agent_id );
// Extract JWT for agent operations
2026-09-05 00:48:12 -07:00
let jwt = crate ::handlers ::extract_jwt_token ( & req )
2026-09-05 00:31:28 -07:00
. unwrap_or_else ( || {
warn! ( "No JWT token in get_agent request" );
"invalid" . to_string ()
});
// Stub: would fetch from DB
let config = AgentConfig {
agent_id : agent_id . clone (),
project_id : "poimen" . to_string (),
capabilities : vec ! [ AgentCapability ::Summarization ],
webhook_url : None ,
rate_limit : 1000 ,
metadata : std ::collections ::HashMap ::new (),
};
let agent = DefaultAgent ::new ( config );
match futures ::executor ::block_on ( agent . status ()) {
status => {
info! ( "Agent status: {} with JWT auth" , agent_id );
response_builder ::success_response ( status )
}
}
}
/// Metrics response
#[derive(Debug, Serialize)]
pub struct MetricsResponse {
pub agent_id : String ,
pub requests_total : u64 ,
pub requests_success : u64 ,
pub requests_failed : u64 ,
pub average_latency_ms : f32 ,
pub p95_latency_ms : f32 ,
pub p99_latency_ms : f32 ,
pub error_rate : f32 ,
}
/// GET /agents/{id}/metrics - Get agent metrics
pub async fn get_agent_metrics_handler (
req : HttpRequest ,
path : web ::Path < String > ,
state : web ::Data < crate ::AppState > ,
) -> HttpResponse {
let agent_id = path . into_inner ();
if let Err ( response ) = crate ::handlers ::middleware ::validate_and_rate_limit (
& req , & state , "agent" , 100
) {
return response ;
}
debug! ( "Getting metrics for agent: {}" , agent_id );
// Extract JWT token for all agent metric operations
2026-09-05 00:48:12 -07:00
if let Some ( jwt ) = crate ::handlers ::extract_jwt_token ( & req ) {
2026-09-05 00:31:28 -07:00
debug! ( "Metrics request authenticated with JWT (len: {})" , jwt . len ());
}
// Stub: would fetch from metrics store
let error_rate = if 0 == 0 { 0.0 } else { 0.05 };
let metrics = MetricsResponse {
agent_id : agent_id . clone (),
requests_total : 1000 ,
requests_success : 950 ,
requests_failed : 50 ,
average_latency_ms : 145.5 ,
p95_latency_ms : 310.0 ,
p99_latency_ms : 450.0 ,
error_rate ,
};
info! ( "Retrieved metrics for agent: {}" , agent_id );
response_builder ::success_response ( metrics )
}
/// PUT /agents/{id} - Update agent config
#[derive(Debug, Deserialize)]
pub struct UpdateAgentRequest {
pub webhook_url : Option < String > ,
pub rate_limit : Option < u32 > ,
pub capabilities : Option < Vec < String >> ,
}
pub async fn update_agent_handler (
req : HttpRequest ,
path : web ::Path < String > ,
body : web ::Json < UpdateAgentRequest > ,
state : web ::Data < crate ::AppState > ,
) -> HttpResponse {
let agent_id = path . into_inner ();
if let Err ( response ) = crate ::handlers ::middleware ::validate_and_rate_limit (
& req , & state , "agent" , 50
) {
return response ;
}
debug! ( "Updating agent: {}" , agent_id );
// Verify JWT present for update operations
2026-09-05 00:48:12 -07:00
if crate ::handlers ::extract_jwt_token ( & req ). is_none () {
2026-09-05 00:31:28 -07:00
warn! ( "Update request for {} without JWT" , agent_id );
}
// Stub: would update in DB
response_builder ::success_response ( serde_json ::json! ({
"agent_id" : agent_id ,
"updated" : true ,
"webhook_url" : body . webhook_url ,
"rate_limit" : body . rate_limit ,
}))
}
/// DELETE /agents/{id} - Deregister agent
pub async fn delete_agent_handler (
req : HttpRequest ,
path : web ::Path < String > ,
state : web ::Data < crate ::AppState > ,
) -> HttpResponse {
let agent_id = path . into_inner ();
if let Err ( response ) = crate ::handlers ::middleware ::validate_and_rate_limit (
& req , & state , "agent" , 50
) {
return response ;
}
debug! ( "Deregistering agent: {}" , agent_id );
// Require JWT for deletion (security)
2026-09-05 00:48:12 -07:00
if crate ::handlers ::extract_jwt_token ( & req ). is_none () {
2026-09-05 00:31:28 -07:00
return response_builder ::unauthorized ( "JWT token required for agent deletion" );
}
info! ( "Agent deregistered: {} with JWT auth" , agent_id );
response_builder ::success_response ( serde_json ::json! ({
"agent_id" : agent_id ,
"deregistered" : true ,
}))
}