yao/openapi/agent/robot/DESIGN.md
Max d590bd7557 Enhance Execution Management with UI Display Fields and Localization Support
- Added `Name` and `CurrentTaskName` fields to the `Execution` struct for improved UI display during execution phases.
- Implemented localization support for UI messages, allowing dynamic updates based on the execution context and user locale.
- Updated the executor to manage these fields at various phases, ensuring accurate representation of execution status.
- Enhanced OpenAPI documentation to reflect the new fields and their usage in execution responses.
- Added unit tests to validate the functionality of UI fields and localization handling.
2026-01-24 10:10:48 +08:00

33 KiB

Robot OpenAPI - Design Document

Based on: yao/agent/robot/ (Backend), cui/packages/cui/pages/mission-control/ (Frontend) Gap Analysis: yao/openapi/agent/robot/GAPS.md

1. Overview

1.1 Purpose

Provide HTTP REST API endpoints for Robot Agent management, designed to support the Mission Control frontend UI.

1.2 Implementation Strategy

Low-risk phases first. Medium-risk features (Chat API, SSE Event Bus) can be deferred.

Phase Risk Features Frontend Fallback
1. Core CRUD 🟢 Low List, Get, Create, Update, Delete -
2. Execution Management 🟢 Low List, Get, Control executions -
3. Results & Activities 🟢 Low Deliverables, Activity feed -
4. i18n 🟢 Low Locale parameter support -
5. Chat API 🟡 Medium (Deferred) Multi-turn conversation Single-submit mode
6. SSE Event Bus 🟡 Medium (Deferred) Real-time status streams Polling every 3-5s

1.3 Route Decision: /v1/agent/robots

Analysis of existing openapi/ route structure:

Package Route Description
agent/ /v1/agent/assistants Assistant CRUD, info
chat/ /v1/chat/completions Chat completions
kb/ /v1/kb/collections Knowledge base
job/ /v1/job/jobs Job management
file/ /v1/file/* File operations
user/ /v1/user/* User management
team/ /v1/team/* Team management

Decision: Put Robot routes under /v1/agent/robots because:

  1. Semantic Alignment: Robot is a type of Agent (Autonomous Robot Agent), just like Assistant is a type of Agent
  2. Existing Pattern: openapi/agent/ already handles /v1/agent/assistants
  3. Logical Grouping: Agent-related APIs grouped together
  4. Consistent Hierarchy: /v1/agent/{type} pattern

Route Comparison:

Option Path Verdict
/v1/robots New top-level namespace Inconsistent with agent grouping
/v1/agent/robots Under agent namespace Follows existing pattern
/v1/members?type=robot Reuse members Less intuitive for operations

1.4 Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                      Frontend (Mission Control)                          │
│  cui/packages/cui/pages/mission-control/                                │
└───────────────────────────────┬─────────────────────────────────────────┘
                                │ HTTP REST / SSE
                                ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                      OpenAPI Layer                                       │
│  yao/openapi/agent/                                                      │
│  - Routes: /v1/agent/assistants/* (existing)                             │
│  - Routes: /v1/agent/robots/* (NEW)                                      │
│  - Auth: OAuth2 via Guard middleware                                     │
│  - SSE: Real-time updates                                               │
└───────────────────────────────┬─────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                      Robot API Layer                                     │
│  yao/agent/robot/api/                                                    │
│  - Go functions: Get(), List(), Trigger(), etc.                          │
│  - Business logic                                                        │
└───────────────────────────────┬─────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                      Robot Core                                          │
│  yao/agent/robot/                                                        │
│  - Manager, Executor, Cache, Pool, Store                                 │
└─────────────────────────────────────────────────────────────────────────┘

1.5 Design Principles

  1. Layered Architecture: OpenAPI layer only handles HTTP concerns (routing, request parsing, response formatting). Business logic stays in robot/api/.
  2. Consistent with Existing Patterns: Follow yao/openapi/agent/ conventions, extend existing agent package
  3. Incremental Implementation: Start with core CRUD, then add real-time features
  4. Frontend-Backend Balance: API design considers both frontend needs and backend capabilities

2. Differences Analysis

2.1 Frontend Expectations vs Backend Reality

Feature Frontend (API.md) Backend (robot/api/) Gap Solution
Robot List GET /v1/robots with name, description List() returns types.Robot Field mapping needed Map in OpenAPI layer
Robot Detail GET /v1/robots/:id with full config Get() returns Robot + Config Need format conversion Map to frontend format
Create Robot POST with work_mode Not implemented New feature Add Create()
Update Robot PUT with partial update Not implemented New feature Add Update()
Delete Robot DELETE Not implemented New feature Add Remove()
Trigger Immediate execution Trigger() returns sync result Works Wrap with SSE events
Intervene Immediate intervention Intervene() returns sync result Works Wrap with SSE events
Multi-turn Chat Chat before execute Not implemented Deferred Frontend uses single-submit
Results List /results endpoint No separate results API New feature Derive from executions
Activities /activities endpoint No activities tracking New feature Derive from executions
Real-time Stream SSE /stream endpoints No SSE support Deferred Frontend uses polling
i18n ?locale= query param No i18n support New feature Add locale handling

2.2 Field Mapping (Backend → Frontend API)

The __yao.member model already has the necessary fields, with different names:

Frontend API Backend DB (__yao.member) Backend Go (types.Robot) Mapping
member_id member_id MemberID Direct
name member_id MemberID Reuse (slug-like identifier)
display_name display_name DisplayName Direct
description bio Need to add Bio field Map in OpenAPI layer
email robot_email RobotEmail Direct

Required Backend Changes:

  1. Add Bio field to types.Robot struct
  2. Add bio to cache/load.go memberFields

2.3 Type Differences

Frontend Type Backend Type Solution
RobotState.name Robot.MemberID Map member_id to name
RobotState.description Robot.Bio (new) Add field, map to description
Execution.name Not in types.Execution Derive from goals or input in OpenAPI layer
Execution.current_task_name Not in types.Execution Derive from current task in OpenAPI layer
ResultFile No equivalent New type in OpenAPI layer (derive from delivery)
Activity No equivalent New type in OpenAPI layer (derive from executions)

3. API Endpoints

Base Path: /v1/agent/robots

3.1 Robot Management

Method Path Handler Description
GET /v1/agent/robots ListRobots List all robots
GET /v1/agent/robots/:id GetRobot Get robot details
POST /v1/agent/robots CreateRobot Create robot
PUT /v1/agent/robots/:id UpdateRobot Update robot
DELETE /v1/agent/robots/:id DeleteRobot Delete robot

3.2 Execution Management

Method Path Handler Description
GET /v1/agent/robots/:id/executions ListExecutions List executions
GET /v1/agent/robots/:id/executions/:exec_id GetExecution Get execution detail
POST /v1/agent/robots/:id/trigger TriggerRobot Trigger execution (SSE)
POST /v1/agent/robots/:id/intervene InterveneRobot Intervene execution (SSE)
POST /v1/agent/robots/:id/executions/:exec_id/pause PauseExecution Pause execution
POST /v1/agent/robots/:id/executions/:exec_id/resume ResumeExecution Resume execution
POST /v1/agent/robots/:id/executions/:exec_id/cancel CancelExecution Cancel execution
POST /v1/agent/robots/:id/executions/:exec_id/retry RetryExecution Retry execution

3.3 Results Management

Method Path Handler Description
GET /v1/agent/robots/:id/results ListResults List deliverables
GET /v1/agent/robots/:id/results/:result_id GetResult Get deliverable detail

3.4 Activities & Real-time

Method Path Handler Description
GET /v1/agent/robots/activities ListActivities List recent activities
GET /v1/agent/robots/stream StreamRobots Robot status SSE
GET /v1/agent/robots/:id/executions/:exec_id/stream StreamExecution Execution progress SSE

4. Response Types

4.1 RobotResponse (for list and detail)

// RobotResponse - formatted robot for API response
// Maps backend fields to frontend expected format
type RobotResponse struct {
    MemberID    string          `json:"member_id"`
    TeamID      string          `json:"team_id"`
    Name        string          `json:"name"`         // From Robot.MemberID (slug-like identifier)
    DisplayName string          `json:"display_name"` // From Robot.DisplayName
    Description string          `json:"description,omitempty"` // From Robot.Bio
    Status      string          `json:"status"`       // idle | working | paused | error | maintenance
    Running     int             `json:"running"`      // Current running count
    MaxRunning  int             `json:"max_running"`  // From Config.Quota.Max
    LastRun     *string         `json:"last_run,omitempty"`     // ISO timestamp
    NextRun     *string         `json:"next_run,omitempty"`     // ISO timestamp
    RunningIDs  []string        `json:"running_ids,omitempty"`  // Execution IDs
    Config      *ConfigResponse `json:"config,omitempty"`       // Full config (for detail)
}

// NewRobotResponse converts backend Robot to API response
func NewRobotResponse(robot *types.Robot) *RobotResponse {
    return &RobotResponse{
        MemberID:    robot.MemberID,
        TeamID:      robot.TeamID,
        Name:        robot.MemberID,    // Use MemberID as unique identifier
        DisplayName: robot.DisplayName,
        Description: robot.Bio,          // Map Bio to Description
        Status:      string(robot.Status),
        // ... other fields
    }
}

4.2 ConfigResponse (robot config)

// ConfigResponse - formatted config for API response
type ConfigResponse struct {
    Identity  *IdentityConfig  `json:"identity,omitempty"`
    Clock     *ClockConfig     `json:"clock,omitempty"`
    Events    []EventConfig    `json:"events,omitempty"`
    Quota     *QuotaConfig     `json:"quota,omitempty"`
    Resources *ResourcesConfig `json:"resources,omitempty"`
    Delivery  *DeliveryConfig  `json:"delivery,omitempty"`
    Triggers  *TriggersConfig  `json:"triggers,omitempty"`
    Learn     *LearnConfig     `json:"learn,omitempty"`
    Executor  *ExecutorConfig  `json:"executor,omitempty"`
}

4.3 ExecutionResponse

// ExecutionResponse - formatted execution for API response
type ExecutionResponse struct {
    ID              string           `json:"id"`
    MemberID        string           `json:"member_id"`
    TeamID          string           `json:"team_id"`
    TriggerType     string           `json:"trigger_type"`
    StartTime       string           `json:"start_time"`
    EndTime         *string          `json:"end_time,omitempty"`
    Status          string           `json:"status"`
    Phase           string           `json:"phase"`
    Error           *string          `json:"error,omitempty"`

    // UI display fields (from backend Execution)
    // These are updated by executor at each phase for frontend display
    Name            string           `json:"name,omitempty"`             // Execution title
    CurrentTaskName string           `json:"current_task_name,omitempty"` // Current task description

    // Phase outputs (for detail view)
    Goals           *GoalsResponse   `json:"goals,omitempty"`
    Tasks           []TaskResponse   `json:"tasks,omitempty"`
    Current         *CurrentState    `json:"current,omitempty"`
    Delivery        *DeliveryResult  `json:"delivery,omitempty"`
}

UI Display Fields Update Timeline:

Phase Name CurrentTaskName
Created Human: from input.messages[0]
Clock/Event: "Preparing..."
"Starting..."
inspiration - "Analyzing context..."
goals complete Extracted from first goal in goals.content "Planning goals..."
tasks - "Breaking down tasks..."
run (each task) - Current task description
Completed/Failed - "Completed" / "Failed: {error}"

4.4 ResultResponse

// ResultResponse - deliverable file for Results tab
type ResultResponse struct {
    ID            string `json:"id"`
    MemberID      string `json:"member_id"`
    ExecutionID   string `json:"execution_id"`
    Name          string `json:"name"`
    Type          string `json:"type"`  // pdf, xlsx, csv, json, md
    Size          int64  `json:"size"`  // bytes
    CreatedAt     string `json:"created_at"`
    TriggerType   string `json:"trigger_type,omitempty"`
    ExecutionName string `json:"execution_name,omitempty"`
}

4.5 ActivityResponse

// ActivityResponse - activity item
type ActivityResponse struct {
    ID          string `json:"id"`
    Type        string `json:"type"`  // completed | file | error | started | paused
    MemberID    string `json:"member_id"`
    RobotName   string `json:"robot_name"`   // Localized
    Title       string `json:"title"`        // Localized
    Description string `json:"description,omitempty"`  // Localized
    FileID      string `json:"file_id,omitempty"`
    Timestamp   string `json:"timestamp"`
}

5. Request Types

5.1 CreateRobotRequest

// CreateRobotRequest - create robot request
type CreateRobotRequest struct {
    Locale      string          `json:"locale,omitempty"` // zh-CN | en-US
    Name        string          `json:"name"`             // Unique identifier
    DisplayName string          `json:"display_name"`     // Display name
    Email       string          `json:"email,omitempty"`  // Robot email
    ManagerID   string          `json:"manager_id,omitempty"`  // Manager user ID
    WorkMode    string          `json:"work_mode"`        // autonomous | on-demand
    Identity    *IdentityConfig `json:"identity"`
    Resources   *ResourcesConfig `json:"resources,omitempty"`
}

5.2 UpdateRobotRequest

// UpdateRobotRequest - update robot request
type UpdateRobotRequest struct {
    Locale      string          `json:"locale,omitempty"`
    DisplayName *string         `json:"display_name,omitempty"`
    Config      *ConfigResponse `json:"config,omitempty"` // Partial update supported
}

5.3 TriggerRequest (SSE)

// TriggerRequest - trigger robot execution
type TriggerRequest struct {
    Locale      string    `json:"locale,omitempty"`
    Messages    []Message `json:"messages"`
    Attachments []Attachment `json:"attachments,omitempty"`
}

// Message - chat message
type Message struct {
    Role    string `json:"role"`    // user | assistant
    Content string `json:"content"`
}

// Attachment - file attachment
type Attachment struct {
    File string `json:"file"` // __yao.attachment://fileID
    Name string `json:"name,omitempty"`
}

5.4 InterveneRequest (SSE)

// InterveneRequest - intervene during execution
type InterveneRequest struct {
    Locale      string    `json:"locale,omitempty"`
    ExecutionID string    `json:"execution_id"`
    Action      string    `json:"action"`   // task.add | goal.adjust | instruct
    Messages    []Message `json:"messages"`
    Priority    string    `json:"priority,omitempty"` // high | normal | low
    Position    string    `json:"position,omitempty"` // first | last | next | at
}

6. Deferred Features

6.1 Multi-turn Chat API (Phase 5 - Deferred)

Risk Level: 🟡 Medium - Requires new stateful component Frontend Fallback: Single-submit mode (user input → immediate execution)

The frontend ChatDrawer component expects multi-turn conversation before execution:

User: "Help me analyze competitor pricing"
       ↓
Robot: "Got it. Which competitors?"
       ↓
User: "Focus on Company A and B"
       ↓
Robot: "Understood. Ready to start?"
       ↓
User clicks [Confirm] → Execution starts

Current backend behavior: Trigger() immediately submits to execution pool.

Deferred implementation:

POST /v1/agent/robots/:id/chat
{
  "conversation_id": "conv_001",  // For continuing conversation
  "messages": [{ "role": "user", "content": "..." }]
}

Response (SSE):
event: message
data: {"role": "assistant", "content": "..."}

event: state
data: {"conversation_id": "conv_001", "ready_to_execute": false}

For now: Frontend can skip chat flow, directly call /trigger with user message.

6.2 SSE Event Bus (Phase 6 - Deferred)

Risk Level: 🟡 Medium - Requires modification of executor/manager Frontend Fallback: Polling (GET /executions every 3-5 seconds)

Real-time status updates via SSE require an event bus integrated with:

  • Manager (robot status changes)
  • Executor (execution progress)

For now: Frontend uses polling to refresh status.


7. SSE Events

7.1 Trigger/Intervene SSE Events

event: received
data: {"message": "Task received, creating execution..."}

event: execution
data: {"execution_id": "exec_002", "status": "pending"}

event: message
data: {"role": "assistant", "content": "好的,我开始处理..."}

event: phase
data: {"phase": "goals", "message": "正在生成目标..."}

event: complete
data: {"execution_id": "exec_002", "status": "running"}

event: error
data: {"error": "Something went wrong"}

7.2 Robot Stream SSE Events (Phase 6 - Deferred)

event: robot_status
data: {"member_id": "robot_001", "status": "working", "running": 1}

event: execution_start
data: {"member_id": "robot_001", "execution_id": "exec_001", "name": "每日报表生成"}

event: execution_complete
data: {"member_id": "robot_001", "execution_id": "exec_001", "status": "completed"}

event: activity
data: {"id": "act_001", "type": "completed", "member_id": "robot_001", ...}

7.3 Execution Stream SSE Events (Phase 6 - Deferred)

event: phase
data: {"phase": "tasks", "progress": "2/5 tasks"}

event: task_start
data: {"task_id": "task_002", "order": 2}

event: task_complete
data: {"task_id": "task_002", "status": "completed"}

event: message
data: {"role": "assistant", "content": "正在分析数据..."}

event: delivery
data: {"summary": "...", "attachments": [...]}

event: complete
data: {"status": "completed"}

event: error
data: {"error": "Something went wrong", "phase": "run"}

8. i18n Support

8.1 Locale Detection

For API requests (Human trigger):

Priority order:

  1. Request body field: locale: "zh-CN" (in TriggerRequest)
  2. Query parameter: ?locale=zh-CN
  3. Accept-Language header
  4. Robot's default_locale config
  5. System default: en-US

For Clock/Event triggers (no user context):

Priority order:

  1. Robot's default_locale config (from robot_config.default_locale)
  2. System default: en-US

8.2 Robot Default Locale

Robots can configure a default language for clock/event triggered executions:

// In RobotConfig (robot_config field in __yao.member)
type Config struct {
    // ... other fields ...
    DefaultLocale string `json:"default_locale,omitempty"` // "en-US", "zh-CN"
}

Language resolution:

func getLocale(robot *Robot, input *TriggerInput) string {
    // 1. Human trigger with explicit locale
    if input != nil && input.Locale != "" {
        return input.Locale
    }
    // 2. Robot configured default
    if robot.Config != nil && robot.Config.DefaultLocale != "" {
        return robot.Config.DefaultLocale
    }
    // 3. System default
    return "en-US"
}

8.3 Localized Fields

Response Type Localized Fields
RobotResponse display_name, description
ExecutionResponse name, current_task_name
TaskResponse (none - tasks use executor_id)
ResultResponse name, execution_name
ActivityResponse robot_name, title, description

9. Authentication & Authorization

9.1 Guard Middleware

All endpoints require OAuth2 authentication via oauth.Guard middleware.

// In router registration
router.Use(oauth.Guard())

9.2 Permission Checks

Endpoint Required Scope
GET /robots robots:read
POST /robots robots:write
PUT/DELETE /robots/:id robots:write + ownership check
Trigger/Intervene robots:execute
Stream endpoints robots:read

9.3 Team Isolation

Robots are team-scoped. Users can only access robots in their team.

func checkTeamAccess(ctx context.Context, memberID string) error {
    auth := oauth.GetAuthorized(ctx)
    robot, _ := robotapi.Get(memberID)
    if robot.TeamID != auth.TeamID {
        return errors.New("access denied")
    }
    return nil
}

10. File Structure

10.1 Backend Store + API Layers

yao/agent/robot/
├── store/                      # Store Layer (Core CRUD)
│   ├── store.go               # Common interfaces
│   ├── execution.go           # ExecutionStore (EXISTS)
│   └── robot.go               # RobotStore (NEW)
│
├── api/                        # API Layer (Thin wrappers)
│   ├── robot.go               # Get, List, Create, Update, Remove
│   ├── execution.go           # Execution management
│   ├── trigger.go             # Trigger, Intervene
│   ├── results.go             # ListResults, GetResult (NEW)
│   └── activities.go          # ListActivities (NEW)
│
├── types/                      # Type definitions
│   └── robot.go               # Add Bio field
│
└── cache/                      # Cache Layer
    └── load.go                # Add bio to memberFields

10.2 OpenAPI Layer

Decision: Sub-package under openapi/agent/

Robot logic is complex enough to warrant its own package. This keeps code organized and follows the pattern used by other complex modules.

yao/openapi/agent/
├── agent.go            # Main route registration (MODIFY: add robot.Attach)
├── assistant.go        # Assistant handlers (existing)
├── filter.go           # Query filtering (existing)
├── models.go           # LLM models (existing)
├── types.go            # Types (existing)
│
└── robot/              # Robot sub-package (NEW)
    ├── DESIGN.md       # This document ✅
    ├── TODO.md         # Implementation plan ✅
    ├── GAPS.md         # Gap analysis ✅
    │
    ├── robot.go        # Route registration (Attach function)
    ├── types.go        # Request/Response types
    │
    ├── list.go         # GET /v1/agent/robots
    ├── detail.go       # GET/POST/PUT/DELETE /v1/agent/robots/:id
    │
    ├── execution.go    # Execution list/detail/control handlers
    ├── trigger.go      # POST /trigger, POST /intervene (SSE)
    │
    ├── results.go      # GET /results, GET /results/:id
    ├── activities.go   # GET /activities
    │
    ├── stream.go       # GET /stream, GET /executions/:id/stream (SSE)
    │
    ├── filter.go       # Query param parsing helpers
    └── utils.go        # Locale, time formatting utilities

Route Registration (in openapi/agent/agent.go):

import "github.com/yaoapp/yao/openapi/agent/robot"

func Attach(group *gin.RouterGroup, oauth types.OAuth) {
    group.Use(oauth.Guard)
    
    // Assistant routes (existing)
    group.GET("/assistants", ListAssistants)
    group.POST("/assistants", CreateAssistant)
    // ...
    
    // Robot routes (NEW)
    robot.Attach(group.Group("/robots"), oauth)
}

Robot Route Registration (robot/robot.go):

package robot

func Attach(group *gin.RouterGroup, oauth types.OAuth) {
    // Robot CRUD
    group.GET("", ListRobots)
    group.POST("", CreateRobot)
    group.GET("/:id", GetRobot)
    group.PUT("/:id", UpdateRobot)
    group.DELETE("/:id", DeleteRobot)
    
    // Activities (before :id to avoid conflict)
    group.GET("/activities", ListActivities)
    group.GET("/stream", StreamRobots)
    
    // Execution management
    group.GET("/:id/executions", ListExecutions)
    group.GET("/:id/executions/:exec_id", GetExecution)
    group.GET("/:id/executions/:exec_id/stream", StreamExecution)
    group.POST("/:id/executions/:exec_id/pause", PauseExecution)
    group.POST("/:id/executions/:exec_id/resume", ResumeExecution)
    group.POST("/:id/executions/:exec_id/cancel", CancelExecution)
    group.POST("/:id/executions/:exec_id/retry", RetryExecution)
    
    // Trigger & Intervene (SSE)
    group.POST("/:id/trigger", TriggerRobot)
    group.POST("/:id/intervene", InterveneRobot)
    
    // Results
    group.GET("/:id/results", ListResults)
    group.GET("/:id/results/:result_id", GetResult)
}

11. Error Handling

11.1 Error Response Format

{
  "error": {
    "code": "ROBOT_NOT_FOUND",
    "message": "Robot not found",
    "details": {
      "member_id": "robot_001"
    }
  }
}

11.2 Error Codes

Code HTTP Status Description
ROBOT_NOT_FOUND 404 Robot does not exist
EXECUTION_NOT_FOUND 404 Execution does not exist
ROBOT_BUSY 409 Robot at max capacity
TRIGGER_DISABLED 403 Trigger type disabled
EXECUTION_NOT_RUNNING 400 Cannot pause/resume non-running execution
INVALID_REQUEST 400 Request validation failed
UNAUTHORIZED 401 Not authenticated
FORBIDDEN 403 No permission

12. Implementation Notes

12.1 Backend Architecture: Store + API Layers

Principle: Store layer handles database CRUD, API layer handles business logic. This enables reuse across Golang API, JSAPI, and Yao Process.

Consumers (Golang API / JSAPI / Yao Process)
              │
              ▼
┌─────────────────────────────────────────┐
│         API Layer (robot/api/)          │
│  Thin wrappers: validation, cache ops   │
└─────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────┐
│       Store Layer (robot/store/)        │
│  Core CRUD: RobotStore, ExecutionStore  │
└─────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────┐
│       Model Layer (__yao.member)        │
└─────────────────────────────────────────┘

12.2 Store Layer Extensions

File: store/robot.go (NEW) - Core Robot CRUD

type RobotStore struct {
    modelID string  // "__yao.member"
}

func (s *RobotStore) Save(ctx context.Context, record *RobotRecord) error
func (s *RobotStore) Get(ctx context.Context, memberID string) (*RobotRecord, error)
func (s *RobotStore) List(ctx context.Context, opts *ListOptions) ([]*RobotRecord, error)
func (s *RobotStore) Delete(ctx context.Context, memberID string) error
func (s *RobotStore) UpdateConfig(ctx context.Context, memberID string, config map[string]interface{}) error

File: store/execution.go (extend)

func (s *ExecutionStore) ListResults(ctx context.Context, memberID string, opts *ResultsQuery) ([]*ResultRecord, error)
func (s *ExecutionStore) GetResult(ctx context.Context, resultID string) (*ResultRecord, error)
func (s *ExecutionStore) ListActivities(ctx context.Context, opts *ActivityQuery) ([]*ActivityRecord, error)

12.3 API Layer Extensions

File: api/robot.go (extend) - Thin wrappers

// Create - calls store.RobotStore.Save() + cache refresh
func Create(ctx *types.Context, teamID string, req *CreateRobotRequest) (*types.Robot, error)

// Update - calls store.RobotStore.UpdateConfig() + cache refresh
func Update(ctx *types.Context, memberID string, req *UpdateRobotRequest) (*types.Robot, error)

// Remove - calls store.RobotStore.Delete() + cache invalidate
func Remove(ctx *types.Context, memberID string) error

File: api/results.go (NEW)

func ListResults(ctx *types.Context, memberID string, query *ResultQuery) (*ResultsResult, error)
func GetResult(ctx *types.Context, resultID string) (*ResultFile, error)

File: api/activities.go (NEW)

func ListActivities(ctx *types.Context, query *ActivityQuery) (*ActivitiesResult, error)

12.4 Localization

Add Locale parameter support for localized responses.

12.5 SSE Implementation

Use standard Go SSE pattern:

func streamHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")
    
    flusher, _ := w.(http.Flusher)
    
    for event := range events {
        fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, event.Data)
        flusher.Flush()
    }
}

12.6 Localization Strategy

  • Store display names in __yao.member.display_name (single language) initially
  • Future: Add display_name_cn, display_name_en or use JSON {"en": "...", "cn": "..."}
  • Execution names derived from goals or input message
  • Activities derive titles from execution data

13. API Base Path Decision

Based on analysis of existing openapi/ structure:

Option Path Pros Cons
A /v1/robots Shorter path New namespace, inconsistent
B /v1/agent/robots Groups with agent APIs, consistent Longer path
C /v1/members?type=robot Uses existing members Less intuitive

Decision: Use /v1/agent/robots as base path.

Rationale:

  1. openapi/agent/ already exists with /v1/agent/assistants
  2. Robot is conceptually an Agent type (Autonomous Robot Agent)
  3. Follows the established pattern: /v1/agent/{agent-type}
  4. Keeps agent-related APIs logically grouped

Frontend Impact:

  • Update cui/packages/cui/pages/mission-control/API.md base path from /v1/robots to /v1/agent/robots
  • Minimal code change (just update base URL constant)

14. References

  • Frontend API Requirements: cui/packages/cui/pages/mission-control/API.md
  • Backend Robot Design: yao/agent/robot/DESIGN.md
  • Backend Technical Spec: yao/agent/robot/TECHNICAL.md
  • Existing OpenAPI Patterns: yao/openapi/kb/, yao/openapi/chat/