sequenceDiagram
actor User as User
participant UI as 🖥️ Frontend (React)
participant API as 🔧 FastAPI Backend
participant Redis as 💾 Redis
participant ADK as 🤖 Google ADK
participant Agent as 🧠 Agent
participant Tool as 🔨 request_image_generation
Note over User,Tool: Phase 1: Request start
User->>UI: "Genera 10 imágenes"
UI->>API: POST /api/agent/start<br/>{query: "Generate 10 images", user_id: "fabricio"}
API->>ADK: create_session(session_id="abc123")
API->>ADK: [runner.run](http://runner.run)_async(session_id, query)
ADK->>Agent: new_message: "Generate 10 images"
Agent->>Tool: request_image_generation(num_images=10)
Note over Tool: num_images (10) > THRESHOLD (1)<br/>tool_context.tool_confirmation es None
Tool->>Tool: tool_context.request_confirmation(<br/>hint="⚠️ Large request: 10 images"<br/>)
Tool-->>Agent: return {status: "pending", ...}
Agent->>ADK: Generate event adk_request_confirmation<br/>with invocation_id="inv_xyz"
Note over API: Detects special event in events[]
API->>Redis: SETEX paused_session:abc123 3600<br/>{<br/> approval_info: {...},<br/> invocation_id: "inv_xyz",<br/> user_id: "fabricio",<br/> hint: "⚠️ Large request: 10 images"<br/>}
API-->>UI: 200 OK<br/>{<br/> status: "pending_approval",<br/> session_id: "abc123",<br/> message: "⚠️ Large request: 10 images"<br/>}
UI->>UI: Display approval modal
UI-->>User: "Approve 10 images?"<br/>[✅ Approve] [❌ Reject]
Note over User,Tool: Phase 2: Wait for human (could be seconds or even minutes)
User->>UI: Click en "✅ Approve"
Note over User,Tool: Phase 3: Resume with Decision
UI->>API: POST /api/agent/resume<br/>{<br/> session_id: "abc123",<br/> approved: true<br/>}
API->>Redis: GET paused_session:abc123
Redis-->>API: {<br/> approval_info: {...},<br/> invocation_id: "inv_xyz",<br/> user_id: "fabricio"<br/>}
Note over API: create_approval_response(<br/>approval_info, approved=true<br/>)
API->>ADK: [runner.run](http://runner.run)_async(<br/> session_id="abc123",<br/> new_message=FunctionResponse(confirmed=true),<br/> invocation_id="inv_xyz" ⬅️ KEY<br/>)
Note over ADK: ADK detects invocation_id="inv_xyz"<br/>→ RESUME (does not restart)
ADK->>Agent: Loads saved state
Agent->>Tool: request_image_generation(num_images=10)<br/>tool_context.tool_confirmation.confirmed=True
Tool-->>Agent: return {<br/> status: "approved",<br/> order_id: "REQ-10-HUMAN",<br/> num_images: 10<br/>}
Agent->>Agent: Calls getTinyImage() 10 times
Agent-->>ADK: "Here are the 10 images"
ADK-->>API: events[] with final result
API->>Redis: DEL paused_session:abc123
API-->>UI: 200 OK<br/>{<br/> status: "completed",<br/> result: "10 images generated"<br/>}
UI->>UI: Hides modal, display result
UI-->>User: ✅ "10 images generated succesfully"
PHASE 1 - Start and Pause:
- The user makes the request
- The backend detects that it requires approval
- The state is saved in Redis with a TTL of 1 hour
- The frontend displays the approval modal
PHASE 2 - Wait:
- The time between pause and decision can be seconds, minutes, or even hours
- Redis persists the state even if the backend restarts
PHASE 3 - Resumption:
- The user approves or rejects
- The backend retrieves the
invocation_idfrom Redis - The agent resumes with the same
invocation_id→ this is critical - The agent completes the task and returns the final result
| Feature | Benefit |
|---|---|
| Persistence | State survives backend restarts |
| Automatic TTL | Abandoned sessions expire automatically |
| High speed | Read/write in microseconds |
| Scalability | Multiple backend instances can share state |