yao/agent/robot/TECHNICAL.md
Max 51e4c6d208 Enhance P3 Execution with Multi-Turn Conversation and Validation Improvements
- Updated the `RunConfig` to include parameters for multi-turn conversation control, such as `ContinueOnFailure`, `ValidationThreshold`, and `MaxTurnsPerTask`.
- Implemented a new multi-turn conversation flow for assistant tasks, allowing for iterative interactions until completion or maximum turns are reached.
- Enhanced the `ValidationResult` structure to support multi-turn states, including fields for `Complete`, `NeedReply`, and `ReplyContent`.
- Refined the `ExecuteWithRetry` method to accommodate the new conversation flow, ensuring proper handling of task execution and validation.
- Revised the `Validator` to include logic for determining when to continue conversations based on validation results.
- Updated documentation and tests to reflect the new multi-turn capabilities and validation mechanisms, ensuring comprehensive coverage of the changes.
2026-01-18 10:02:11 +08:00

60 KiB

Robot Agent - Technical Design

1. Code Structure

yao/agent/robot/
├── DESIGN.md                 # Product design doc
├── TECHNICAL.md              # This file
│
├── robot.go                  # Package entry, Init(), Shutdown()
│
├── api/                      # All API forms
│   ├── api.go                # Go API (facade)
│   ├── process.go            # Yao Process: robot.*
│   └── jsapi.go              # JS API: robot (global) + Robot (class)
│
├── types/                    # Types only (no logic, no external deps)
│   ├── enums.go              # Phase, ClockMode, TriggerType, etc.
│   ├── config.go             # Config, Clock, Identity, Quota, etc.
│   ├── robot.go              # Robot, Execution
│   ├── task.go               # Goal, Task, TaskResult
│   ├── request.go            # InterveneRequest, EventRequest, etc.
│   ├── inspiration.go        # ClockContext, InspirationReport
│   ├── interfaces.go         # All interfaces (Manager, Trigger, etc.)
│   └── errors.go             # Error definitions
│
├── manager/                  # Manager package (orchestration)
│   └── manager.go            # Manager struct, Start/Stop, Tick
│
├── pool/                     # Worker pool & task dispatch
│   ├── pool.go               # Pool struct, Submit
│   ├── queue.go              # Priority queue
│   └── worker.go             # Worker goroutines
│
├── executor/                 # Executor package (pluggable architecture)
│   ├── executor.go           # Factory functions, unified entry
│   ├── types/
│   │   ├── types.go          # Executor interface, Config types
│   │   └── helpers.go        # Shared helper functions
│   ├── standard/
│   │   ├── executor.go       # Real Agent execution (production)
│   │   ├── agent.go          # AgentCaller for LLM calls
│   │   ├── input.go          # InputFormatter for prompts
│   │   ├── inspiration.go    # P0: Inspiration phase
│   │   ├── goals.go          # P1: Goals phase
│   │   ├── tasks.go          # P2: Tasks phase
│   │   ├── run.go            # P3: Run phase (main entry)
│   │   ├── runner.go         # P3: Task Runner (execution logic)
│   │   ├── validator.go      # P3: Validator (two-layer validation)
│   │   ├── delivery.go       # P4: Delivery phase
│   │   └── learning.go       # P5: Learning phase
│   ├── dryrun/
│   │   └── executor.go       # Simulated execution (testing/demo)
│   └── sandbox/
│       └── executor.go       # Container-isolated (NOT IMPLEMENTED)
│
├── utils/                    # Utility functions
│   ├── convert.go            # Type conversions (JSON, map, struct)
│   ├── time.go               # Time parsing, formatting, timezone
│   ├── id.go                 # ID generation (nanoid, uuid)
│   └── validate.go           # Validation helpers
│
├── trigger/                  # Trigger utilities (logic in manager/)
│   ├── trigger.go            # Validation helpers, action utilities
│   ├── clock.go              # ClockMatcher (reusable clock matching logic)
│   └── control.go            # ExecutionController (pause/resume/stop)
│
├── cache/                    # Cache package
│   ├── cache.go              # Cache struct, Get/List
│   ├── load.go               # LoadAll, LoadOne
│   └── refresh.go            # Refresh logic
│
├── dedup/                    # Deduplication package
│   ├── dedup.go              # Dedup struct
│   ├── fast.go               # Fast in-memory check
│   └── semantic.go           # Semantic check via agent
│
├── store/                    # Data store package (KB, FS, DB access)
│   ├── store.go              # Store struct, interface
│   ├── kb.go                 # Knowledge base operations
│   ├── fs.go                 # File system operations
│   ├── db.go                 # Database queries
│   └── learning.go           # Learning entry save (to KB)
│
├── job/                      # Job system integration
│   ├── job.go                # Create/Get job for robot
│   ├── execution.go          # Create/Update execution
│   └── log.go                # Write execution logs
│
└── plan/                     # Plan queue (deferred tasks)
    ├── plan.go               # Plan queue struct
    └── schedule.go           # Schedule for later

yao/assert/                       # Universal assertion library (global package)
├── types.go                      # Assertion, Result, interfaces
├── asserter.go                   # Asserter implementation (8 assertion types)
└── helpers.go                    # Utility functions (ExtractPath, ToString, etc.)

Dependency Graph (No Cycles)

Note: trigger/ is a utility package (validation, clock matching, execution control). All trigger logic flows through manager/.

                              ┌──────────┐
                              │  types/  │  (pure types, no deps)
                              └────┬─────┘
                                   │
    ┌───────┬───────┬───────┬──────┼──────┬───────┬───────┬───────┐
    │       │       │       │      │      │       │       │       │
    ▼       ▼       ▼       ▼      ▼      ▼       ▼       ▼       ▼
┌───────┐┌───────┐┌───────┐┌──────┐┌────┐┌──────┐┌───────┐┌─────────┐
│ cache ││ dedup ││ store ││ pool ││job ││ plan ││ utils ││ trigger │
└───┬───┘└───┬───┘└───┬───┘└──┬───┘└──┬─┘└──────┘└───────┘└────┬────┘
    │        │        │       │       │                        │
    └────────┴────────┴───────┴───────┴────────────────────────┘
                      │
                      ▼
               ┌────────────┐
               │  executor/ │
               └──────┬─────┘
                      │
                      ▼
               ┌────────────┐
               │  manager/  │  (imports trigger/ for utilities)
               └──────┬─────┘
                      │
       ┌──────────────┴──────────────┐
       │                             │
       ▼                             ▼
┌─────────────┐              ┌─────────────┐
│  robot.go   │              │    api/     │
└─────────────┘              └─────────────┘

Package Dependencies

Package Imports
types/ stdlib only
utils/ stdlib only
cache/ types/
dedup/ types/
store/ types/
pool/ types/
trigger/ types/
job/ types/, yao/job
plan/ types/
executor/ types/, cache/, dedup/, store/, pool/, job/, yao/assert
manager/ types/, cache/, pool/, trigger/, executor/
Manager handles all trigger logic (clock, intervene, event)
api/ types/, manager/
root all packages

Public API (api/)

Three API forms, all in api/ directory.

Go API (api/api.go)

package api

import (
    "github.com/yaoapp/yao/agent/robot/types"
)

// ==================== CRUD ====================

// Get returns a robot by member ID
func Get(ctx *types.Context, memberID string) (*types.Robot, error)

// List returns robots with pagination and filtering
func List(ctx *types.Context, query *ListQuery) (*ListResult, error)

// Create creates a new robot member
func Create(ctx *types.Context, teamID string, req *CreateRequest) (*types.Robot, error)

// Update updates robot config
func Update(ctx *types.Context, memberID string, req *UpdateRequest) (*types.Robot, error)

// Remove deletes a robot member
func Remove(ctx *types.Context, memberID string) error

// ==================== Status ====================

// Status returns current robot runtime state
func Status(ctx *types.Context, memberID string) (*RobotState, error)

// UpdateStatus updates robot status (idle, paused, etc.)
func UpdateStatus(ctx *types.Context, memberID string, status types.RobotStatus) error

// ==================== Trigger ====================

// Trigger starts execution with specified trigger type and request
func Trigger(ctx *types.Context, memberID string, req *TriggerRequest) (*TriggerResult, error)

// ==================== Execution ====================

// GetExecutions returns execution history
func GetExecutions(ctx *types.Context, memberID string, query *ExecutionQuery) (*ExecutionResult, error)

// GetExecution returns a specific execution by ID
func GetExecution(ctx *types.Context, execID string) (*types.Execution, error)

// Pause pauses a running execution
func Pause(ctx *types.Context, execID string) error

// Resume resumes a paused execution
func Resume(ctx *types.Context, execID string) error

// Stop stops a running execution
func Stop(ctx *types.Context, execID string) error

API Types

// ==================== CRUD Types ====================

// CreateRequest - request for Create()
type CreateRequest struct {
    DisplayName  string        `json:"display_name"`
    SystemPrompt string        `json:"system_prompt,omitempty"`
    Config       *types.Config `json:"robot_config"`
}

// UpdateRequest - request for Update()
type UpdateRequest struct {
    DisplayName  *string       `json:"display_name,omitempty"`
    SystemPrompt *string       `json:"system_prompt,omitempty"`
    Config       *types.Config `json:"robot_config,omitempty"`
}

// ListQuery - query options for List()
type ListQuery struct {
    TeamID    string            `json:"team_id,omitempty"`    // filter by team
    Status    types.RobotStatus `json:"status,omitempty"`     // idle | working | paused | error
    Keywords  string            `json:"keywords,omitempty"`   // search display_name, role
    ClockMode types.ClockMode   `json:"clock_mode,omitempty"` // times | interval | daemon
    Page      int               `json:"page,omitempty"`       // default 1
    PageSize  int               `json:"pagesize,omitempty"`   // default 20, max 100
    Order     string            `json:"order,omitempty"`      // e.g. "created_at desc"
}

// ListResult - result of List()
type ListResult struct {
    Data     []*types.Robot `json:"data"`
    Total    int            `json:"total"`
    Page     int            `json:"page"`
    PageSize int            `json:"pagesize"`
}

// RobotState - runtime state from Status()
type RobotState struct {
    MemberID    string            `json:"member_id"`
    TeamID      string            `json:"team_id"`
    DisplayName string            `json:"display_name"`
    Status      types.RobotStatus `json:"status"`                // idle | working | paused | error
    Running     int               `json:"running"`               // current running execution count
    MaxRunning  int               `json:"max_running"`           // max concurrent allowed
    LastRun     *time.Time        `json:"last_run,omitempty"`
    NextRun     *time.Time        `json:"next_run,omitempty"`
    RunningIDs  []string          `json:"running_ids,omitempty"` // list of running execution IDs
}

// ==================== Trigger Types ====================

// TriggerRequest - request for Trigger()
// Input uses []context.Message to support rich content (text, images, files, audio)
type TriggerRequest struct {
    Type types.TriggerType `json:"type"` // human | event

    // Human intervention fields (when Type = human)
    Action   types.InterventionAction `json:"action,omitempty"`    // task.add | goal.adjust | task.cancel | plan.add
    Messages []context.Message        `json:"messages,omitempty"`  // user's input (supports text, images, files)
    PlanAt   *time.Time               `json:"plan_at,omitempty"`   // for action=plan.add
    InsertAt InsertPosition           `json:"insert_at,omitempty"` // where to insert: first | last | next | at
    AtIndex  int                      `json:"at_index,omitempty"`  // index when insert_at=at

    // Event fields (when Type = event)
    Source    types.EventSource      `json:"source,omitempty"`     // webhook | database
    EventType string                 `json:"event_type,omitempty"` // lead.created, order.paid, etc.
    Data      map[string]interface{} `json:"data,omitempty"`       // event payload

    // Executor mode (optional, overrides robot config)
    ExecutorMode types.ExecutorMode `json:"executor_mode,omitempty"` // standard | dryrun
}

// InsertPosition - where to insert task in queue
type InsertPosition string

const (
    InsertFirst InsertPosition = "first" // insert at beginning (highest priority)
    InsertLast  InsertPosition = "last"  // append at end (default)
    InsertNext  InsertPosition = "next"  // insert after current task
    InsertAt    InsertPosition = "at"    // insert at specific index (use AtIndex)
)

// TriggerResult - result of Trigger()
type TriggerResult struct {
    Accepted  bool             `json:"accepted"`            // whether trigger was accepted
    Queued    bool             `json:"queued"`              // true if queued (quota full)
    Execution *types.Execution `json:"execution,omitempty"` // execution info if started
    JobID     string           `json:"job_id,omitempty"`    // job ID for tracking
    Message   string           `json:"message,omitempty"`   // status message
}

// ==================== Execution Types ====================

// ExecutionQuery - query options for GetExecutions()
type ExecutionQuery struct {
    Status   types.ExecStatus   `json:"status,omitempty"`  // pending | running | completed | failed
    Trigger  types.TriggerType  `json:"trigger,omitempty"` // clock | human | event
    Page     int                `json:"page,omitempty"`    // default 1
    PageSize int                `json:"pagesize,omitempty"`// default 20
}

// ExecutionResult - result of GetExecutions()
type ExecutionResult struct {
    Data     []*types.Execution `json:"data"`
    Total    int                `json:"total"`
    Page     int                `json:"page"`
    PageSize int                `json:"pagesize"`
}

Process API (api/process.go)

Yao Process registration. Naming convention: robot.<Action>

// Process registration
func init() {
    process.Register("robot.Get", processGet)
    process.Register("robot.List", processList)
    process.Register("robot.Create", processCreate)
    process.Register("robot.Update", processUpdate)
    process.Register("robot.Remove", processRemove)
    process.Register("robot.Status", processStatus)
    process.Register("robot.UpdateStatus", processUpdateStatus)
    process.Register("robot.Trigger", processTrigger)
    process.Register("robot.Executions", processExecutions)
    process.Register("robot.Execution", processExecution)
    process.Register("robot.Pause", processPause)
    process.Register("robot.Resume", processResume)
    process.Register("robot.Stop", processStop)
}
Process Args Returns Description
robot.Get memberID Robot Get robot by ID
robot.List query ListResult List robots
robot.Create teamID, data Robot Create robot
robot.Update memberID, data Robot Update robot
robot.Remove memberID null Delete robot
robot.Status memberID RobotState Get runtime status
robot.UpdateStatus memberID, status null Update status
robot.Trigger memberID, request TriggerResult Trigger execution
robot.Executions memberID, query ExecutionResult List executions
robot.Execution execID Execution Get execution
robot.Pause execID null Pause execution
robot.Resume execID null Resume execution
robot.Stop execID null Stop execution

Usage:

// In Yao scripts
const robot = Process("robot.Get", "mem_abc123");

const list = Process("robot.List", {
  team_id: "team_xyz",
  status: "idle",
  page: 1,
  pagesize: 20,
});

// Trigger with text message
const result = Process("robot.Trigger", "mem_abc123", {
  type: "human",
  action: "task.add",
  messages: [
    { role: "user", content: "Prepare meeting materials for BigCorp" },
  ],
  insert_at: "first",
});

// Trigger with image (multimodal)
const imageResult = Process("robot.Trigger", "mem_abc123", {
  type: "human",
  action: "task.add",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Analyze this chart and summarize key trends" },
        {
          type: "image_url",
          image_url: { url: "https://example.com/chart.png" },
        },
      ],
    },
  ],
  insert_at: "first",
});

// Trigger with event
const eventResult = Process("robot.Trigger", "mem_abc123", {
  type: "event",
  source: "webhook",
  event_type: "lead.created",
  data: { name: "John", email: "john@example.com" },
});

const execs = Process("robot.Executions", "mem_abc123", {
  status: "completed",
  page: 1,
});

JSAPI (api/jsapi.go)

Register to V8 Runtime using constructor pattern, similar to new FS(), new Store(), new Query().

func init() {
    // Register Robot constructor
    v8.RegisterFunction("Robot", ExportFunction)
}

// ExportFunction exports the Robot constructor
func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
    return v8go.NewFunctionTemplate(iso, robotConstructor)
}

// robotConstructor: new Robot(memberID)
func robotConstructor(info *v8go.FunctionCallbackInfo) *v8go.Value {
    ctx := info.Context()
    args := info.Args()

    if len(args) < 1 {
        return bridge.JsException(ctx, "Robot requires member ID")
    }

    memberID := args[0].String()
    robotObj, err := RobotNew(ctx, memberID)
    if err != nil {
        return bridge.JsException(ctx, err.Error())
    }

    return robotObj
}

// RobotNew creates a Robot JS object with methods
func RobotNew(ctx *v8go.Context, memberID string) (*v8go.Value, error) {
    iso := ctx.Isolate()
    obj := v8go.NewObjectTemplate(iso)

    // Instance methods (operate on this robot)
    obj.Set("Status", v8go.NewFunctionTemplate(iso, jsStatus))
    obj.Set("UpdateStatus", v8go.NewFunctionTemplate(iso, jsUpdateStatus))
    obj.Set("Trigger", v8go.NewFunctionTemplate(iso, jsTrigger))
    obj.Set("Executions", v8go.NewFunctionTemplate(iso, jsExecutions))
    obj.Set("Pause", v8go.NewFunctionTemplate(iso, jsPause))
    obj.Set("Resume", v8go.NewFunctionTemplate(iso, jsResume))
    obj.Set("Stop", v8go.NewFunctionTemplate(iso, jsStop))

    // ... create instance with memberID stored
    return obj.NewInstance(ctx)
}

Global object robot (static methods):

func init() {
    // Register global robot object (lowercase, for static methods)
    v8.RegisterObject("robot", ExportObject)
}

// ExportObject exports the robot global object
func ExportObject(iso *v8go.Isolate) *v8go.ObjectTemplate {
    obj := v8go.NewObjectTemplate(iso)
    obj.Set("List", v8go.NewFunctionTemplate(iso, jsList))
    obj.Set("Get", v8go.NewFunctionTemplate(iso, jsGet))
    obj.Set("Create", v8go.NewFunctionTemplate(iso, jsCreate))
    obj.Set("Update", v8go.NewFunctionTemplate(iso, jsUpdate))
    obj.Set("Remove", v8go.NewFunctionTemplate(iso, jsRemove))
    obj.Set("Execution", v8go.NewFunctionTemplate(iso, jsExecution))
    return obj
}

TypeScript Interface:

// ==================== Types ====================

interface RobotData {
  member_id: string;
  team_id: string;
  display_name: string;
  robot_status: "idle" | "working" | "paused" | "error" | "maintenance";
  robot_config: RobotConfig;
}

interface RobotState {
  member_id: string;
  team_id: string;
  display_name: string;
  status: "idle" | "working" | "paused" | "error" | "maintenance";
  running: number; // current running execution count
  max_running: number; // max concurrent allowed
  last_run?: string;
  next_run?: string;
  running_ids?: string[]; // list of running execution IDs
}

interface TriggerResult {
  accepted: boolean;
  queued: boolean;
  execution?: Execution;
  job_id?: string;
  message?: string;
}

// Message - same as context.Message, supports rich content
interface Message {
  role: "user" | "assistant" | "system" | "tool";
  content: string | ContentPart[];
  name?: string;
  tool_call_id?: string;
  tool_calls?: ToolCall[];
}

interface ContentPart {
  type: "text" | "image_url" | "input_audio" | "file" | "data";
  text?: string;
  image_url?: { url: string; detail?: "auto" | "low" | "high" };
  input_audio?: { data: string; format: string };
  file?: { url: string; name?: string; mime_type?: string };
  data?: { data: string; mime_type: string };
}

interface TriggerRequest {
  type: "human" | "event";

  // Human intervention fields
  action?:
    | "task.add"
    | "task.cancel"
    | "task.update"
    | "goal.adjust"
    | "goal.add"
    | "goal.complete"
    | "goal.cancel"
    | "plan.add"
    | "plan.remove"
    | "plan.update"
    | "instruct";
  messages?: Message[]; // supports text, images, files, audio
  insert_at?: "first" | "last" | "next" | "at";
  at_index?: number;
  plan_at?: string; // ISO date for plan.add

  // Event fields
  source?: "webhook" | "database";
  event_type?: string; // lead.created, etc.
  data?: Record<string, any>;

  // Executor mode (optional, overrides robot config)
  executor_mode?: "standard" | "dryrun"; // sandbox not implemented
}

// ExecutorMode - executor mode type
type ExecutorMode = "standard" | "dryrun" | "sandbox";
// Note: "sandbox" requires container infrastructure, falls back to "dryrun"

interface ListQuery {
  team_id?: string;
  status?: "idle" | "working" | "paused" | "error" | "maintenance";
  keywords?: string;
  clock_mode?: "times" | "interval" | "daemon";
  page?: number;
  pagesize?: number;
}

interface ListResult {
  data: RobotData[];
  total: number;
  page: number;
  pagesize: number;
}

interface ExecutionQuery {
  status?: "pending" | "running" | "completed" | "failed" | "cancelled";
  trigger?: "clock" | "human" | "event";
  page?: number;
  pagesize?: number;
}

interface ExecutionResult {
  data: Execution[];
  total: number;
  page: number;
  pagesize: number;
}

interface CreateRequest {
  display_name: string;
  system_prompt?: string;
  robot_config: RobotConfig;
}

interface UpdateRequest {
  display_name?: string;
  system_prompt?: string;
  robot_config?: RobotConfig;
}

// ==================== Global object: robot ====================
// Static methods, no instance needed

interface RobotStatic {
  List(query?: ListQuery): ListResult;
  Get(memberID: string): RobotData;
  Create(teamID: string, data: CreateRequest): RobotData;
  Update(memberID: string, data: UpdateRequest): RobotData;
  Remove(memberID: string): void;
  Execution(execID: string): Execution;
}

declare const robot: RobotStatic;

// ==================== Constructor: Robot ====================
// Instance methods, operate on specific robot

declare class Robot {
  constructor(memberID: string);

  // Properties
  readonly memberID: string;

  // Instance methods
  Status(): RobotState;
  UpdateStatus(status: string): void;
  Trigger(request: TriggerRequest): TriggerResult;
  Executions(query?: ExecutionQuery): ExecutionResult;
  Pause(execID: string): void;
  Resume(execID: string): void;
  Stop(execID: string): void;
}

Usage:

// ==================== Global object: robot ====================
// For CRUD and queries (no instance needed)

const list = robot.List({ team_id: "team_xyz", status: "idle" });
const data = robot.Get("mem_abc123");
const newRobot = robot.Create("team_xyz", {
  display_name: "Sales Bot",
  robot_config: { ... }
});
robot.Update("mem_abc123", { display_name: "Updated Bot" });
robot.Remove("mem_abc123");
const exec = robot.Execution("exec_456");

// ==================== Constructor: Robot ====================
// For operating on a specific robot instance

const bot = new Robot("mem_abc123");

// Instance methods
const state = bot.Status();
if (state.status === "idle") {
  const result = bot.Trigger({
    type: "human",
    action: "task.add",
    messages: [{ role: "user", content: "Analyze sales data" }],
    insert_at: "first",
  });
  console.log("Triggered:", result.accepted);
}

// Get execution history for this robot
const execs = bot.Executions({ status: "completed", page: 1 });

// Control execution
bot.Pause("exec_123");
bot.Resume("exec_123");
bot.Stop("exec_123");

// Update status
bot.UpdateStatus("paused");

Usage in Agent Hooks:

function Create(ctx, messages) {
  const bot = new Robot("mem_abc123");
  const state = bot.Status();

  if (state.status === "working") {
    ctx.Send({ type: "text", props: { content: "Robot is busy" } });
    return null;
  }

  const result = bot.Trigger({
    type: "human",
    action: "task.add",
    messages: [{ role: "user", content: "Analyze this data" }],
    insert_at: "first",
  });

  if (result.accepted) {
    ctx.memory.context.Set("robot_exec_id", result.execution.id);
  }

  return { messages };
}

function Next(ctx, payload) {
  const execID = ctx.memory.context.Get("robot_exec_id");
  if (execID) {
    const exec = robot.Execution(execID); // use global object
    if (exec.status === "completed") {
      ctx.Send({
        type: "text",
        props: { content: `Robot completed: ${exec.delivery?.summary}` },
      });
    }
  }
  return null;
}

2. Type Definitions

All types are in robot/types/ package. Other files import as:

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

2.1 Enums

// types/enums.go
package types

// Phase - execution phase
type Phase string

const (
    PhaseInspiration Phase = "inspiration" // P0: Clock only
    PhaseGoals       Phase = "goals"       // P1
    PhaseTasks       Phase = "tasks"       // P2
    PhaseRun         Phase = "run"         // P3
    PhaseDelivery    Phase = "delivery"    // P4
    PhaseLearning    Phase = "learning"    // P5
)

// AllPhases for iteration
var AllPhases = []Phase{
    PhaseInspiration, PhaseGoals, PhaseTasks,
    PhaseRun, PhaseDelivery, PhaseLearning,
}

// ClockMode - clock trigger mode
type ClockMode string

const (
    ClockTimes    ClockMode = "times"    // run at specific times
    ClockInterval ClockMode = "interval" // run every X duration
    ClockDaemon   ClockMode = "daemon"   // run continuously
)

// TriggerType - trigger source
type TriggerType string

const (
    TriggerClock TriggerType = "clock"
    TriggerHuman TriggerType = "human"
    TriggerEvent TriggerType = "event"
)

// ExecStatus - execution status
type ExecStatus string

const (
    ExecPending   ExecStatus = "pending"
    ExecRunning   ExecStatus = "running"
    ExecCompleted ExecStatus = "completed"
    ExecFailed    ExecStatus = "failed"
    ExecCancelled ExecStatus = "cancelled"
)

// RobotStatus - matches __yao.member.robot_status
type RobotStatus string

const (
    RobotIdle        RobotStatus = "idle"
    RobotWorking     RobotStatus = "working"
    RobotPaused      RobotStatus = "paused"
    RobotError       RobotStatus = "error"
    RobotMaintenance RobotStatus = "maintenance"
)

// InterventionAction - human intervention action
// Format: category.action (e.g., "task.add", "goal.adjust")
type InterventionAction string

const (
    // Task operations
    ActionTaskAdd    InterventionAction = "task.add"    // add a new task
    ActionTaskCancel InterventionAction = "task.cancel" // cancel a task
    ActionTaskUpdate InterventionAction = "task.update" // update task details

    // Goal operations
    ActionGoalAdjust   InterventionAction = "goal.adjust"   // modify current goal
    ActionGoalAdd      InterventionAction = "goal.add"      // add a new goal
    ActionGoalComplete InterventionAction = "goal.complete" // mark goal as complete
    ActionGoalCancel   InterventionAction = "goal.cancel"   // cancel a goal

    // Plan operations (schedule for later)
    ActionPlanAdd    InterventionAction = "plan.add"    // add to plan queue
    ActionPlanRemove InterventionAction = "plan.remove" // remove from plan queue
    ActionPlanUpdate InterventionAction = "plan.update" // update planned item

    // Instruction (direct command)
    ActionInstruct InterventionAction = "instruct" // direct instruction to robot
)

// Priority - task/goal priority
type Priority string

const (
    PriorityHigh   Priority = "high"
    PriorityNormal Priority = "normal"
    PriorityLow    Priority = "low"
)

// DeliveryType - output delivery type
type DeliveryType string

const (
    DeliveryEmail   DeliveryType = "email"
    DeliveryFile    DeliveryType = "file"
    DeliveryWebhook DeliveryType = "webhook"
    DeliveryNotify  DeliveryType = "notify"
)

// DedupResult - deduplication result
type DedupResult string

const (
    DedupSkip    DedupResult = "skip"    // skip execution
    DedupMerge   DedupResult = "merge"   // merge with existing
    DedupProceed DedupResult = "proceed" // proceed normally
)

// EventSource - event trigger source
type EventSource string

const (
    EventWebhook  EventSource = "webhook"  // HTTP webhook
    EventDatabase EventSource = "database" // DB change trigger
)

// LearningType - learning entry type
type LearningType string

const (
    LearnExecution LearningType = "execution" // execution record
    LearnFeedback  LearningType = "feedback"  // error/fix feedback
    LearnInsight   LearningType = "insight"   // pattern/tip insight
)

2.2 Context

// types/context.go
package types

import (
    "context"
    "github.com/yaoapp/yao/openapi/oauth/types"
)

// Context - robot execution context (lightweight)
type Context struct {
    context.Context                          // embed standard context
    Auth      *types.AuthorizedInfo          `json:"auth,omitempty"`       // reuse oauth AuthorizedInfo
    MemberID  string                         `json:"member_id,omitempty"`  // current robot member ID
    RequestID string                         `json:"request_id,omitempty"` // request trace ID
    Locale    string                         `json:"locale,omitempty"`     // locale (e.g., "en-US")
}

// NewContext creates a new robot context
func NewContext(parent context.Context, auth *types.AuthorizedInfo) *Context {
    if parent == nil {
        parent = context.Background()
    }
    return &Context{
        Context: parent,
        Auth:    auth,
    }
}

// UserID returns user ID from auth
func (c *Context) UserID() string {
    if c.Auth == nil {
        return ""
    }
    return c.Auth.UserID
}

// TeamID returns team ID from auth
func (c *Context) TeamID() string {
    if c.Auth == nil {
        return ""
    }
    return c.Auth.TeamID
}

2.3 Config Types

// types/config.go
package types

import "time"

// Config - robot_config in __yao.member
type Config struct {
    Triggers  *Triggers  `json:"triggers,omitempty"`
    Clock     *Clock     `json:"clock,omitempty"`
    Identity  *Identity  `json:"identity"`
    Quota     *Quota     `json:"quota,omitempty"`
    KB        *KB        `json:"kb,omitempty"`        // shared knowledge base (same as assistant)
    DB        *DB        `json:"db,omitempty"`        // shared database (same as assistant)
    Learn     *Learn     `json:"learn,omitempty"`     // learning config for private KB
    Resources *Resources `json:"resources,omitempty"`
    Delivery  *Delivery  `json:"delivery,omitempty"`
    Events    []Event    `json:"events,omitempty"`
}

// Validate validates the config
func (c *Config) Validate() error {
    if c.Identity == nil || c.Identity.Role == "" {
        return ErrMissingIdentity
    }
    if c.Clock != nil {
        if err := c.Clock.Validate(); err != nil {
            return err
        }
    }
    return nil
}

// Triggers - trigger enable/disable
type Triggers struct {
    Clock     *TriggerSwitch `json:"clock,omitempty"`
    Intervene *TriggerSwitch `json:"intervene,omitempty"`
    Event     *TriggerSwitch `json:"event,omitempty"`
}

type TriggerSwitch struct {
    Enabled bool     `json:"enabled"`
    Actions []string `json:"actions,omitempty"` // for intervene
}

// IsEnabled checks if trigger is enabled (default: true)
func (t *Triggers) IsEnabled(typ TriggerType) bool {
    if t == nil {
        return true
    }
    switch typ {
    case TriggerClock:
        return t.Clock == nil || t.Clock.Enabled
    case TriggerHuman:
        return t.Intervene == nil || t.Intervene.Enabled
    case TriggerEvent:
        return t.Event == nil || t.Event.Enabled
    }
    return false
}

// Clock - when to wake up
type Clock struct {
    Mode    ClockMode `json:"mode"`              // times | interval | daemon
    Times   []string  `json:"times,omitempty"`   // ["09:00", "14:00"]
    Days    []string  `json:"days,omitempty"`    // ["Mon", "Tue"] or ["*"]
    Every   string    `json:"every,omitempty"`   // "30m", "1h"
    TZ      string    `json:"tz,omitempty"`      // "Asia/Shanghai"
    Timeout string    `json:"timeout,omitempty"` // "30m"
}

// Validate validates clock config
func (c *Clock) Validate() error {
    switch c.Mode {
    case ClockTimes:
        if len(c.Times) == 0 {
            return ErrClockTimesEmpty
        }
    case ClockInterval:
        if c.Every == "" {
            return ErrClockIntervalEmpty
        }
    case ClockDaemon:
        // no extra validation
    default:
        return ErrClockModeInvalid
    }
    return nil
}

// GetTimeout returns parsed timeout duration
func (c *Clock) GetTimeout() time.Duration {
    if c.Timeout == "" {
        return 30 * time.Minute // default
    }
    d, err := time.ParseDuration(c.Timeout)
    if err != nil {
        return 30 * time.Minute
    }
    return d
}

// GetLocation returns timezone location
func (c *Clock) GetLocation() *time.Location {
    if c.TZ == "" {
        return time.Local
    }
    loc, err := time.LoadLocation(c.TZ)
    if err != nil {
        return time.Local
    }
    return loc
}

// Identity - who is this robot
type Identity struct {
    Role   string   `json:"role"`
    Duties []string `json:"duties,omitempty"`
    Rules  []string `json:"rules,omitempty"`
}

// Quota - concurrency limits
type Quota struct {
    Max      int `json:"max"`      // max running (default: 2)
    Queue    int `json:"queue"`    // queue size (default: 10)
    Priority int `json:"priority"` // 1-10 (default: 5)
}

// GetMax returns max with default
func (q *Quota) GetMax() int {
    if q == nil || q.Max <= 0 {
        return 2
    }
    return q.Max
}

// GetQueue returns queue size with default
func (q *Quota) GetQueue() int {
    if q == nil || q.Queue <= 0 {
        return 10
    }
    return q.Queue
}

// GetPriority returns priority with default
func (q *Quota) GetPriority() int {
    if q == nil || q.Priority <= 0 {
        return 5
    }
    return q.Priority
}

// KB - knowledge base config (same as assistant, from store/types)
// Shared KB collections accessible by this robot
type KB struct {
    Collections []string               `json:"collections,omitempty"` // KB collection IDs
    Options     map[string]interface{} `json:"options,omitempty"`
}

// DB - database config (same as assistant, from store/types)
// Shared database models accessible by this robot
type DB struct {
    Models  []string               `json:"models,omitempty"` // database model names
    Options map[string]interface{} `json:"options,omitempty"`
}

// Learn - learning config for robot's private KB
// Private KB is auto-created: robot_{team_id}_{member_id}_kb
type Learn struct {
    On    bool     `json:"on"`
    Types []string `json:"types,omitempty"` // execution, feedback, insight
    Keep  int      `json:"keep,omitempty"`  // days, 0 = forever
}

// Resources - available agents and tools
type Resources struct {
    Phases map[Phase]string `json:"phases,omitempty"` // phase -> agent ID
    Agents []string         `json:"agents,omitempty"`
    MCP    []MCPConfig      `json:"mcp,omitempty"`
}

// GetPhaseAgent returns agent ID for phase (default: __yao.{phase})
func (r *Resources) GetPhaseAgent(phase Phase) string {
    if r != nil && r.Phases != nil {
        if id, ok := r.Phases[phase]; ok && id != "" {
            return id
        }
    }
    return "__yao." + string(phase)
}

type MCPConfig struct {
    ID    string   `json:"id"`
    Tools []string `json:"tools,omitempty"` // empty = all
}

// Delivery - output delivery
type Delivery struct {
    Type DeliveryType           `json:"type"`
    Opts map[string]interface{} `json:"opts,omitempty"`
}

// Event - event trigger config
type Event struct {
    Type   EventSource            `json:"type"`   // webhook | database
    Source string                 `json:"source"` // webhook path or table name
    Filter map[string]interface{} `json:"filter,omitempty"`
}

// Monitor - monitoring config

2.3 Core Types

// types/robot.go
package types

import (
    "context"
    "sync"
    "time"
)

// Robot - runtime representation of an autonomous robot (from __yao.member)
// Relationship: 1 Robot : N Executions (concurrent)
// Each trigger creates a new Execution (mapped to job.Job)
type Robot struct {
    // From __yao.member
    MemberID       string      `json:"member_id"`
    TeamID         string      `json:"team_id"`
    DisplayName    string      `json:"display_name"`
    SystemPrompt   string      `json:"system_prompt"`
    Status         RobotStatus `json:"robot_status"`
    AutonomousMode bool        `json:"autonomous_mode"`

    // Parsed config (from robot_config JSON field)
    Config *Config `json:"-"`

    // Runtime state
    LastRun   time.Time `json:"-"` // last execution start time
    NextRun   time.Time `json:"-"` // next scheduled execution (for clock trigger)

    // Concurrency control
    // Each Robot can run multiple Executions concurrently (up to Quota.Max)
    executions map[string]*Execution // execID -> Execution
    execMu     sync.RWMutex
}

// CanRun checks if robot can accept new execution
func (r *Robot) CanRun() bool {
    r.execMu.RLock()
    defer r.execMu.RUnlock()
    return len(r.executions) < r.Config.Quota.GetMax()
}

// RunningCount returns current running execution count
func (r *Robot) RunningCount() int {
    r.execMu.RLock()
    defer r.execMu.RUnlock()
    return len(r.executions)
}

// AddExecution adds an execution to tracking
func (r *Robot) AddExecution(exec *Execution) {
    r.execMu.Lock()
    defer r.execMu.Unlock()
    if r.executions == nil {
        r.executions = make(map[string]*Execution)
    }
    r.executions[exec.ID] = exec
}

// RemoveExecution removes an execution from tracking
func (r *Robot) RemoveExecution(execID string) {
    r.execMu.Lock()
    defer r.execMu.Unlock()
    delete(r.executions, execID)
}

// GetExecution returns an execution by ID
func (r *Robot) GetExecution(execID string) *Execution {
    r.execMu.RLock()
    defer r.execMu.RUnlock()
    return r.executions[execID]
}

// GetExecutions returns all running executions
func (r *Robot) GetExecutions() []*Execution {
    r.execMu.RLock()
    defer r.execMu.RUnlock()
    execs := make([]*Execution, 0, len(r.executions))
    for _, exec := range r.executions {
        execs = append(execs, exec)
    }
    return execs
}

// Execution - single execution instance
// Each trigger creates a new Execution, mapped to a job.Job for monitoring
// Relationship: 1 Execution = 1 job.Job
type Execution struct {
    ID          string      `json:"id"`           // unique execution ID
    MemberID    string      `json:"member_id"`    // robot member ID
    TeamID      string      `json:"team_id"`
    TriggerType TriggerType `json:"trigger_type"` // clock | human | event
    StartTime   time.Time   `json:"start_time"`
    EndTime     *time.Time  `json:"end_time,omitempty"`
    Status      ExecStatus  `json:"status"`
    Phase       Phase       `json:"phase"`
    Error       string      `json:"error,omitempty"`

    // Job integration (each Execution = 1 job.Job)
    JobID string `json:"job_id"` // corresponding job.Job ID

    // Trigger input (stored for traceability)
    Input *TriggerInput `json:"input,omitempty"` // original trigger input

    // Phase outputs
    Inspiration *InspirationReport `json:"inspiration,omitempty"` // P0: markdown
    Goals       *Goals             `json:"goals,omitempty"`       // P1: markdown
    Tasks       []Task             `json:"tasks,omitempty"`       // P2: structured tasks
    Current     *CurrentState      `json:"current,omitempty"`     // current executing state
    Results     []TaskResult       `json:"results,omitempty"`     // P3: task results
    Delivery    *DeliveryResult    `json:"delivery,omitempty"`
    Learning    []LearningEntry    `json:"learning,omitempty"`

    // Runtime (internal, not serialized)
    ctx    context.Context    `json:"-"`
    cancel context.CancelFunc `json:"-"`
    robot  *Robot             `json:"-"`
}

// TriggerInput - stored trigger input for traceability
type TriggerInput struct {
    // For human intervention
    Action   InterventionAction `json:"action,omitempty"`   // task.add, goal.adjust, etc.
    Messages []context.Message  `json:"messages,omitempty"` // user's input (text, images, files)
    UserID   string             `json:"user_id,omitempty"`  // who triggered

    // For event trigger
    Source    EventSource            `json:"source,omitempty"`     // webhook | database
    EventType string                 `json:"event_type,omitempty"` // lead.created, etc.
    Data      map[string]interface{} `json:"data,omitempty"`       // event payload

    // For clock trigger
    Clock *ClockContext `json:"clock,omitempty"` // time context when triggered
}

// CurrentState - current executing goal and task
type CurrentState struct {
    Task      *Task  `json:"task,omitempty"`     // current task being executed
    TaskIndex int    `json:"task_index"`         // index in Tasks slice
    Progress  string `json:"progress,omitempty"` // human-readable progress (e.g., "2/5 tasks")
}

// Goals - P1 output (markdown for LLM + structured metadata)
// P1 Agent reads InspirationReport and generates goals as markdown
// Example:
// ## Goals
// 1. [High] Analyze sales data and identify trends
//    - Reason: Sales up 50%, need to understand why
// 2. [Normal] Prepare weekly report for manager
//    - Reason: Friday 5pm, weekly report due
// 3. [Low] Update CRM with new leads
//    - Reason: 3 pending leads from yesterday
type Goals struct {
    Content  string          `json:"content"`            // markdown text
    Delivery *DeliveryTarget `json:"delivery,omitempty"` // where to send results (for P4)
}

// DeliveryTarget - where to deliver results (defined in P1, used in P4)
type DeliveryTarget struct {
    Type       DeliveryType           `json:"type"`                 // email | webhook | report | notification
    Recipients []string               `json:"recipients,omitempty"` // email addresses, webhook URLs, user IDs
    Format     string                 `json:"format,omitempty"`     // markdown | html | json | text
    Template   string                 `json:"template,omitempty"`   // template name or inline template
    Options    map[string]interface{} `json:"options,omitempty"`    // channel-specific options
}

// Task - planned task (structured, for execution)
type Task struct {
    ID       string            `json:"id"`
    Messages []context.Message `json:"messages"`           // original input (text, images, files)
    GoalRef  string            `json:"goal_ref,omitempty"` // reference to goal (e.g., "Goal 1")
    Source   TaskSource        `json:"source"`             // auto | human | event

    // Executor
    ExecutorType ExecutorType `json:"executor_type"`
    ExecutorID   string       `json:"executor_id"`
    Args         []any        `json:"args,omitempty"`

    // Validation (defined in P2, used in P3)
    ExpectedOutput  string   `json:"expected_output,omitempty"`  // what the task should produce
    // ValidationRules supports two formats:
    // 1. Natural language: "output must be valid JSON", "must contain 'field'"
    // 2. JSON assertions: `{"type": "type", "value": "object"}`, `{"type": "contains", "value": "success"}`
    ValidationRules []string `json:"validation_rules,omitempty"` // specific checks to perform

    // Runtime
    Status    TaskStatus `json:"status"`
    Order     int        `json:"order"` // execution order (0-based)
    StartTime *time.Time `json:"start_time,omitempty"`
    EndTime   *time.Time `json:"end_time,omitempty"`
}

// TaskSource - how task was created
type TaskSource string

const (
    TaskSourceAuto   TaskSource = "auto"   // generated by P2 (task planning)
    TaskSourceHuman  TaskSource = "human"  // added via human intervention
    TaskSourceEvent  TaskSource = "event"  // added via event trigger
)

// ExecutorType - task executor type
type ExecutorType string

const (
    ExecutorAssistant ExecutorType = "assistant"
    ExecutorMCP       ExecutorType = "mcp"
    ExecutorProcess   ExecutorType = "process"
)

// TaskStatus - task execution status
type TaskStatus string

const (
    TaskPending    TaskStatus = "pending"
    TaskRunning    TaskStatus = "running"
    TaskCompleted  TaskStatus = "completed"
    TaskFailed     TaskStatus = "failed"
    TaskSkipped    TaskStatus = "skipped"
    TaskCancelled  TaskStatus = "cancelled"
)

// TaskResult - task execution result
type TaskResult struct {
    TaskID     string            `json:"task_id"`
    Success    bool              `json:"success"`
    Output     interface{}       `json:"output,omitempty"`
    Error      string            `json:"error,omitempty"`
    Duration   int64             `json:"duration_ms"`
    Validation *ValidationResult `json:"validation,omitempty"` // P3 validation result
}

// ValidationResult - P3 validation result with multi-turn conversation support
type ValidationResult struct {
    // Basic validation result
    Passed      bool     `json:"passed"`                // overall validation passed
    Score       float64  `json:"score,omitempty"`       // 0-1 confidence score
    Issues      []string `json:"issues,omitempty"`      // what failed
    Suggestions []string `json:"suggestions,omitempty"` // how to improve
    Details     string   `json:"details,omitempty"`     // detailed validation report (markdown)

    // Execution state (for multi-turn conversation control)
    Complete     bool   `json:"complete"`                // whether expected result is obtained
    NeedReply    bool   `json:"need_reply,omitempty"`    // whether to continue conversation
    ReplyContent string `json:"reply_content,omitempty"` // content for next turn (if NeedReply)
}

// DeliveryResult - P4 delivery output
type DeliveryResult struct {
    Type       DeliveryType `json:"type"`
    Success    bool         `json:"success"`
    Recipients []string     `json:"recipients,omitempty"` // who received
    Content    string       `json:"content,omitempty"`    // formatted content delivered
    Details    interface{}  `json:"details,omitempty"`    // channel-specific response
    Error      string       `json:"error,omitempty"`
    SentAt     *time.Time   `json:"sent_at,omitempty"`
}

// LearningEntry - knowledge to save
type LearningEntry struct {
    Type    LearningType `json:"type"` // execution | feedback | insight
    Content string       `json:"content"`
    Tags    []string     `json:"tags,omitempty"`
    Meta    interface{}  `json:"meta,omitempty"`
}

2.4 Clock Context

// types/clock.go
package types

import "time"

// ClockContext - time context for P0 inspiration
type ClockContext struct {
    Now          time.Time `json:"now"`
    Hour         int       `json:"hour"`          // 0-23
    DayOfWeek    string    `json:"day_of_week"`   // Monday, Tuesday...
    DayOfMonth   int       `json:"day_of_month"`  // 1-31
    WeekOfYear   int       `json:"week_of_year"`  // 1-52
    Month        int       `json:"month"`         // 1-12
    Year         int       `json:"year"`
    IsWeekend    bool      `json:"is_weekend"`
    IsMonthStart bool      `json:"is_month_start"` // 1st-3rd
    IsMonthEnd   bool      `json:"is_month_end"`   // last 3 days
    IsQuarterEnd bool      `json:"is_quarter_end"`
    IsYearEnd    bool      `json:"is_year_end"`
    TZ           string    `json:"tz"`
}

// NewClockContext creates clock context from time
func NewClockContext(t time.Time, tz string) *ClockContext {
    loc := time.Local
    if tz != "" {
        if l, err := time.LoadLocation(tz); err == nil {
            loc = l
        }
    }
    t = t.In(loc)

    _, week := t.ISOWeek()
    dayOfMonth := t.Day()
    lastDay := time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, loc).Day()

    return &ClockContext{
        Now:          t,
        Hour:         t.Hour(),
        DayOfWeek:    t.Weekday().String(),
        DayOfMonth:   dayOfMonth,
        WeekOfYear:   week,
        Month:        int(t.Month()),
        Year:         t.Year(),
        IsWeekend:    t.Weekday() == time.Saturday || t.Weekday() == time.Sunday,
        IsMonthStart: dayOfMonth <= 3,
        IsMonthEnd:   dayOfMonth >= lastDay-2,
        IsQuarterEnd: (t.Month()%3 == 0) && dayOfMonth >= lastDay-2,
        IsYearEnd:    t.Month() == 12 && dayOfMonth >= 29,
        TZ:           loc.String(),
    }
}

2.5 Inspiration Report

// types/inspiration.go
package types

// InspirationReport - P0 output (simple markdown for LLM)
type InspirationReport struct {
    Clock   *ClockContext `json:"clock"`   // time context
    Content string        `json:"content"` // markdown text for LLM
}

// Content is markdown like:
// ## Summary
// ...
// ## Highlights
// - [High] Sales up 50%
// - [Medium] New lead from BigCorp
// ## Opportunities
// ...
// ## Risks
// ...
// ## World News
// ...
// ## Pending
// ...

2.6 Request/Response Types

// types/request.go
package types

import (
    "context"
    "time"
)

// InterveneRequest - human intervention request
// Processed by Manager.Intervene()
type InterveneRequest struct {
    TeamID       string                    `json:"team_id"`
    MemberID     string                    `json:"member_id"`
    Action       InterventionAction        `json:"action"`               // task.add, goal.adjust, etc.
    Messages     []agentcontext.Message    `json:"messages,omitempty"`   // user input (text, images, files)
    PlanTime     *time.Time                `json:"plan_time,omitempty"`  // for action=plan.add
    ExecutorMode ExecutorMode              `json:"executor_mode,omitempty"` // optional: standard | dryrun
}

// EventRequest - event trigger request
// Processed by Manager.HandleEvent()
type EventRequest struct {
    MemberID     string                 `json:"member_id"`
    Source       string                 `json:"source"`               // webhook path or table name
    EventType    string                 `json:"event_type"`           // lead.created, etc.
    Data         map[string]interface{} `json:"data,omitempty"`
    ExecutorMode ExecutorMode           `json:"executor_mode,omitempty"` // optional: standard | dryrun
}

// ExecutorMode - executor mode enum
type ExecutorMode string

const (
    ExecutorStandard ExecutorMode = "standard" // real Agent calls (default)
    ExecutorDryRun   ExecutorMode = "dryrun"   // simulated, no LLM calls
    ExecutorSandbox  ExecutorMode = "sandbox"  // container-isolated (NOT IMPLEMENTED)
)

// ExecutionResult - trigger result
type ExecutionResult struct {
    ExecutionID string     `json:"execution_id"`
    Status      ExecStatus `json:"status"`
    Message     string     `json:"message,omitempty"`
}

// RobotState - robot status query result
type RobotState struct {
    MemberID    string      `json:"member_id"`
    TeamID      string      `json:"team_id"`
    DisplayName string      `json:"display_name"`
    Status      RobotStatus `json:"status"`
    Running     int         `json:"running"`               // current running execution count
    MaxRunning  int         `json:"max_running"`           // max concurrent allowed
    LastRun     *time.Time  `json:"last_run,omitempty"`
    NextRun     *time.Time  `json:"next_run,omitempty"`
    RunningIDs  []string    `json:"running_ids,omitempty"` // list of running execution IDs
}

3. Interfaces

Interfaces are also in types/ package to avoid cycles.

3.1 Manager Interface

// types/interfaces.go
package types

import "time"

// ==================== Internal Interfaces ====================
// These are internal implementation interfaces, not exposed via API.
// External API is defined in api/api.go
// All interfaces use *Context (not context.Context) for consistency.

// Manager - robot lifecycle, scheduling, and all trigger handling
// Manager is the central orchestrator, handling:
// - Clock triggers (via Tick)
// - Human intervention (via Intervene)
// - Event triggers (via HandleEvent)
// - Execution control (pause/resume/stop)
type Manager interface {
    // Lifecycle
    Start() error
    Stop() error

    // Clock trigger (called by internal ticker)
    Tick(ctx *Context, now time.Time) error

    // Manual trigger (for testing/API)
    TriggerManual(ctx *Context, memberID string, trigger TriggerType, data interface{}) (string, error)

    // Human intervention
    Intervene(ctx *Context, req *InterveneRequest) (*ExecutionResult, error)

    // Event trigger
    HandleEvent(ctx *Context, req *EventRequest) (*ExecutionResult, error)

    // Execution control
    PauseExecution(ctx *Context, execID string) error
    ResumeExecution(ctx *Context, execID string) error
    StopExecution(ctx *Context, execID string) error
}

// Executor - executes robot phases
type Executor interface {
    Execute(ctx *Context, robot *Robot, trigger TriggerType, data interface{}) (*Execution, error)
}

// Pool - worker pool for concurrent execution
type Pool interface {
    Start() error
    Stop() error
    Submit(ctx *Context, robot *Robot, trigger TriggerType, data interface{}) (string, error)
    Running() int
    Queued() int
}

// Cache - in-memory robot cache
type Cache interface {
    Load(ctx *Context) error
    Get(memberID string) *Robot
    List(teamID string) []*Robot
    Refresh(ctx *Context, memberID string) error
    Add(robot *Robot)
    Remove(memberID string)
}

// Dedup - deduplication check
type Dedup interface {
    Check(ctx *Context, memberID string, trigger TriggerType) (DedupResult, error)
    Mark(memberID string, trigger TriggerType, window time.Duration)
}

// Store - data storage operations (KB, DB)
type Store interface {
    SaveLearning(ctx *Context, memberID string, entries []LearningEntry) error
    GetHistory(ctx *Context, memberID string, limit int) ([]LearningEntry, error)
    SearchKB(ctx *Context, collections []string, query string) ([]interface{}, error)
    QueryDB(ctx *Context, models []string, query interface{}) ([]interface{}, error)
}

3.2 Trigger Utilities (trigger/ package)

Note: The trigger/ package provides utilities, not the main trigger logic. All trigger handling is done by Manager.

// trigger/trigger.go - Validation and helper functions

// ValidateIntervention validates a human intervention request
func ValidateIntervention(req *InterveneRequest) error

// ValidateEvent validates an event trigger request
func ValidateEvent(req *EventRequest) error

// BuildEventInput creates a TriggerInput from an event request
func BuildEventInput(req *EventRequest) *TriggerInput

// GetActionCategory returns the category of an intervention action
// e.g., "task.add" -> "task", "goal.adjust" -> "goal"
func GetActionCategory(action InterventionAction) string

// GetActionDescription returns a human-readable description of an action
func GetActionDescription(action InterventionAction) string
// trigger/clock.go - Clock matching logic (reusable)

// ClockMatcher provides clock trigger matching logic
type ClockMatcher struct{}

// ShouldTrigger checks if a robot should be triggered based on its clock config
func (cm *ClockMatcher) ShouldTrigger(robot *Robot, now time.Time) bool

// ParseTime parses a time string in "HH:MM" format
func ParseTime(timeStr string) (hour, minute int, err error)

// FormatTime formats hour and minute to "HH:MM" string
func FormatTime(hour, minute int) string
// trigger/control.go - Execution control (pause/resume/stop)

// ExecutionController manages execution lifecycle
type ExecutionController struct {
    executions map[string]*ControlledExecution
    mu         sync.RWMutex
}

// Track starts tracking an execution
func (c *ExecutionController) Track(execID, memberID, teamID string) *ControlledExecution

// Untrack stops tracking an execution
func (c *ExecutionController) Untrack(execID string)

// Pause pauses an execution
func (c *ExecutionController) Pause(execID string) error

// Resume resumes a paused execution
func (c *ExecutionController) Resume(execID string) error

// Stop stops an execution
func (c *ExecutionController) Stop(execID string) error

// ControlledExecution represents an execution that can be controlled
type ControlledExecution struct {
    ID        string
    MemberID  string
    TeamID    string
    Status    ExecStatus
    Phase     Phase
    StartTime time.Time
    PausedAt  *time.Time
    // ... internal fields for context and channels
}

// IsPaused returns true if the execution is paused
func (e *ControlledExecution) IsPaused() bool

// IsCancelled returns true if the execution is cancelled
func (e *ControlledExecution) IsCancelled() bool

// WaitIfPaused blocks until the execution is resumed or cancelled
func (e *ControlledExecution) WaitIfPaused() error

// CheckCancelled checks if the execution is cancelled and returns error if so
func (e *ControlledExecution) CheckCancelled() error

4. Errors

// types/errors.go
package types

import "errors"

var (
    // Config errors
    ErrMissingIdentity    = errors.New("identity.role is required")
    ErrClockTimesEmpty    = errors.New("clock.times is required for times mode")
    ErrClockIntervalEmpty = errors.New("clock.every is required for interval mode")
    ErrClockModeInvalid   = errors.New("clock.mode must be times, interval, or daemon")

    // Runtime errors
    ErrRobotNotFound      = errors.New("robot not found")
    ErrRobotPaused        = errors.New("robot is paused")
    ErrRobotBusy          = errors.New("robot has reached max concurrent executions")
    ErrTriggerDisabled    = errors.New("trigger type is disabled for this robot")
    ErrExecutionCancelled = errors.New("execution was cancelled")
    ErrExecutionTimeout   = errors.New("execution timed out")

    // Phase errors
    ErrPhaseAgentNotFound = errors.New("phase agent not found")
    ErrGoalGenFailed      = errors.New("goal generation failed")
    ErrTaskPlanFailed     = errors.New("task planning failed")
    ErrDeliveryFailed     = errors.New("delivery failed")
)