- 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.
60 KiB
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 throughmanager/.
┌──────────┐
│ 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 byManager.
// 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")
)