- Completed the implementation of the P3 Run phase, integrating task execution and validation mechanisms. - Introduced a new `RunConfig` struct to manage execution parameters such as retries and validation thresholds. - Developed a two-layer validation system using the new `yao/assert` package, supporting both natural language and structured JSON rules. - Enhanced the `RunExecution` method to execute tasks sequentially with progress tracking and a retry mechanism for validation failures. - Updated task structures to include comprehensive validation rules and expected outputs, ensuring robust task management. - Added unit tests for the new execution and validation features, achieving high test coverage across the implementation. - Revised documentation to reflect changes in the architecture and functionality of the P3 phase.
50 KiB
Robot Agent
1. What is it?
A Robot Agent is an AI team member. It works on its own, makes decisions, and runs tasks without waiting for user input.
Key points:
- Belongs to a Team, managed like human members
- Has clear job duties (e.g., "Sales Manager: track KPIs, make reports")
- Created and deleted via Team API
- Runs on schedule, or when triggered by humans or events
- Learns from each run, stores knowledge in private KB
2. Architecture
2.1 System Flow
Architecture Note: All trigger types flow through Manager.
- Clock:
Manager.Tick()(internal ticker)- Human:
Manager.Intervene()(API call)- Event:
Manager.HandleEvent()(webhook/db trigger)The
trigger/package provides utilities only (validation, clock matching, execution control).
flowchart TB
subgraph Triggers["Triggers"]
WC[/"⏰ Clock"/]
HI[/"👤 Human"/]
EV[/"📡 Event"/]
end
subgraph Manager["Manager (Central Orchestrator)"]
TC{"Enabled?"}
Cache[("Cache")]
Dedup{"Dedup?"}
Queue["Queue"]
end
subgraph Pool["Workers"]
W1["Worker"]
W2["Worker"]
W3["Worker"]
end
subgraph Executor["Executor"]
TT{"Trigger?"}
P0["P0: Inspiration"]
P1["P1: Goals"]
P2["P2: Tasks"]
P3["P3: Run"]
P4["P4: Deliver"]
P5["P5: Learn"]
end
subgraph Storage["Storage"]
KB[("KB")]
DB[("DB")]
Job[("Job")]
end
WC --> TC
HI & EV --> TC
TC -->|Yes| Cache
TC -->|No| X[/Skip/]
Cache --> Dedup
Dedup -->|OK| Queue
Dedup -->|Dup| Cache
Queue --> W1 & W2 & W3
W1 & W2 & W3 --> TT
TT -->|Clock| P0
TT -->|Human/Event| P1
P0 --> P1 --> P2 --> P3 --> P4 --> P5
P5 --> KB & DB & Job
KB -.->|History| P0
2.2 Executor Modes
Executor supports multiple execution modes for different use cases:
| Mode | Use Case | Status |
|---|---|---|
| Standard | Production with real Agent calls | ✅ Implemented |
| DryRun | Tests, demos, preview without LLM calls | ✅ Implemented |
| Sandbox | Container-isolated for untrusted code | ⬜ Not Implemented |
Standard Mode: Real execution with LLM calls, Job integration, full phase execution.
DryRun Mode: Simulated execution without LLM calls. Used for:
- Unit tests and integration tests
- Demo and preview modes
- Scheduling and concurrency testing
Sandbox Mode (Future): Container-level isolation (Docker/gVisor/Firecracker) for:
- Untrusted robot configurations
- Multi-tenant environments
- Resource-limited execution
⚠️ Sandbox requires infrastructure support. Current placeholder behaves like DryRun.
2.3 Team Structure
Uses existing __yao.member model (yao/models/member.mod.yao):
┌─────────────────────────────────────────────────────────────────┐
│ Team │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Robot Members │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │Sales Manager│ │Data Analyst │ │CS Specialist│ │ │
│ │ │ • Track KPIs│ │ • Analyze │ │ • Tickets │ │ │
│ │ │ • Reports │ │ • Reports │ │ • Inquiries │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ User Members │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ John (Owner)│ │ Jane (Admin)│ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Key fields in __yao.member for robot agents:
| Field | Type | Description |
|---|---|---|
member_type |
enum | user | robot |
autonomous_mode |
bool | Enable robot execution |
robot_config |
JSON | Agent configuration (see section 5) |
robot_status |
enum | idle | working | paused | error | maintenance |
system_prompt |
text | Identity & role prompt |
agents |
JSON | Accessible agents list |
mcp_servers |
JSON | Accessible MCP servers |
manager_id |
string | Direct manager user ID |
3. How It Works
3.1 Flow: Trigger → Schedule → Run
sequenceDiagram
autonumber
participant T as Trigger
participant M as Manager
participant S as Scheduler
participant W as Worker
participant E as Executor
participant A as Phase Agents
participant KB as KB
T->>M: Event
M->>M: Check enabled
M->>M: Get from cache
M->>M: Check dedup
M->>S: Submit
S->>S: Check quota
S->>S: Sort by priority
S->>W: Dispatch
W->>E: Run
alt Clock trigger
E->>A: P0: Inspiration (with clock context)
A-->>E: Report
end
loop P1 to P5
E->>A: Call agent
A-->>E: Result
end
E->>KB: Save learning
E-->>W: Done
3.2 Triggers
| Type | What | Config | Handler |
|---|---|---|---|
| Clock | Timer (times/interval/daemon) | triggers.clock |
Manager.Tick() |
| Human | Manual action | triggers.intervene |
Manager.Intervene() |
| Event | Webhook, DB change | triggers.event |
Manager.HandleEvent() |
All on by default. Turn off per agent:
triggers:
clock: { enabled: true }
intervene: { enabled: true, actions: ["task.add", "goal.adjust"] }
event: { enabled: false }
3.3 Concurrency
Two levels to prevent one agent from using all resources:
┌─────────────────────────────────────────────────────────────────┐
│ Global Pool (10 workers) │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Sales Manager │ │ Data Analyst │ │ CS Specialist │
│ Limit: 3 │ │ Limit: 2 │ │ Limit: 3 │
│ Now: 2 ✓ │ │ Now: 2 (full) │ │ Now: 1 ✓ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
3.4 Dedup
Fast check (in memory):
key := memberID + ":" + triggerType + ":" + window
if has(key) { skip }
Smart check (for goals/tasks):
- Dedup Agent looks at history
- Returns:
skip|merge|proceed
3.5 Cache
Keeps agents in memory. No DB query on each tick:
type AgentCache struct {
agents map[string]*Agent // member_id -> agent
byTeam map[string][]string // team_id -> member_ids
}
// Refresh: on start, on change, every hour
4. Phases
4.1 Overview
Clock: P0 → P1 → P2 → P3 → P4 → P5
Human/Event: P1 → P2 → P3 → P4 → P5
| Phase | Agent | In | Out | When |
|---|---|---|---|---|
| P0 | Inspiration | Clock + Data + News | Report | Clock only |
| P1 | Goal Gen | Report + history | Goals | Always |
| P2 | Task Plan | Goals + tools | Tasks | Always |
| P3 | Run + Valid | Tasks + Experts | TaskResults | Always |
| P4 | Delivery | All results | Email/File | Always |
| P5 | Learning | Summary | KB entries | Always |
4.2 P0: Inspiration (Clock only)
Skipped for Human/Event triggers. They already have clear intent.
Gathers info to help make good goals. Clock context is key input - Agent knows what time it is and can decide what to do (e.g., 5pm Friday → write weekly report).
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%
// ## Opportunities / Risks / World News / Pending
// ...
type ClockContext struct {
Now time.Time // Current time
Hour int // 0-23
DayOfWeek string // Monday, Tuesday...
DayOfMonth int // 1-31
IsWeekend bool
IsMonthStart bool // 1st-3rd
IsMonthEnd bool // last 3 days
IsQuarterEnd bool
// Agent uses this to decide: "It's 5pm Friday, time for weekly report"
}
Sources:
- Clock: Current time, day of week, month end, etc.
- Internal: Data changes, events, feedback, pending work
- External: Web search (news, competitors)
4.3 P1: Goals
For Clock: Uses inspiration report (with clock context) to make goals. Agent decides based on time what's important now.
For Human/Event: Uses the input directly as goals (or to generate goals).
type Goals struct {
Content string // markdown text (for LLM)
Delivery *DeliveryTarget // where to send results (for P4)
}
type DeliveryTarget struct {
Type DeliveryType // email | webhook | report | notification
Recipients []string // email addresses, webhook URLs, user IDs
Format string // markdown | html | json | text
Template string // template name
Options map[string]interface{}
}
Example prompt:
You are [Sales Manager]. Your job: [track KPIs, make reports].
## Report
### Key Items
- [High] Data: 15 new sales (+50%)
- [High] Deadline: Friday report due
- [High] News: Competitor launched product
### Chances
- Sales up 20% vs last week
- Market growing
Make today's goals.
Note: Validation criteria (ExpectedOutput, ValidationRules) are defined at the Task level (P2), not Goals level. This allows each task to have specific validation rules for P3.
4.4 P2: Tasks
P2 Agent reads Goals markdown and breaks into executable tasks:
type Task struct {
ID string // unique task ID
Messages []context.Message // original input (text, images, files, audio)
GoalRef string // reference to goal (e.g., "Goal 1")
Source TaskSource // auto | human | event
ExecutorType ExecutorType // assistant | mcp | process
ExecutorID string // agent ID or mcp tool name
Args []any // arguments for executor
Order int // execution order
// Validation criteria (used in P3)
ExpectedOutput string // what the task should produce
ValidationRules []string // specific checks to perform
}
4.5 P3: Run
Architecture: P3 uses a modular design with three components:
┌─────────────────────────────────────────────────────────────┐
│ run.go (P3 Entry) │
│ - RunConfig: retries, threshold, continue-on-failure │
│ - RunExecution: main execution loop │
└─────────────────────┬───────────────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ runner.go │ │ validator.go │
│ - Runner │ │ - Validator │
│ - Task exec │ │ - Two-layer │
│ - Multi-turn │ │ - Rule+Semantic│
└────────┬────────┘ └────────┬────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ yao/assert │
│ │ - 8 assertion │
│ │ types │
│ └─────────────────┘
▼
┌─────────────────────────────────────────┐
│ Executor Types │
│ - assistant: AI Agent (multi-turn) │
│ - mcp: MCP Tool (clientID.toolName) │
│ - process: Yao Process │
└─────────────────────────────────────────┘
Execution Flow:
For each task:
- Execute via appropriate executor (Assistant/MCP/Process)
- Validate using two-layer validation
- Retry if validation fails (with feedback to expert agent)
- Update task status and store result
Two-Layer Validation:
| Layer | Method | Speed | Use Case |
|---|---|---|---|
| 1. Rule-based | yao/assert |
Fast | Type check, contains, regex, json_path |
| 2. Semantic | Validation Agent | Slow | ExpectedOutput, complex criteria |
Executor Types:
| Type | ExecutorID Format | Example |
|---|---|---|
assistant |
Agent ID | experts.text-writer |
mcp |
clientID.toolName |
filesystem.read_file |
process |
Process name | models.user.Find |
Retry Mechanism:
- Retries only on validation failure (not execution error)
- Validation feedback sent to expert agent on retry
- Configurable:
MaxRetries,RetryOnValidationFailure
type RunConfig struct {
MaxRetries int // default: 3
RetryOnValidationFailure bool // default: true
ContinueOnFailure bool // default: false
ValidationThreshold float64 // default: 0.6
MaxTurnsPerTask int // default: 10
}
type ValidationResult struct {
Passed bool // overall validation passed
Score float64 // 0-1 confidence score
Issues []string // what failed
Suggestions []string // how to improve
Details string // detailed report (markdown)
}
yao/assert Package:
Universal assertion library supporting 8 types:
| Type | Description | Example |
|---|---|---|
equals |
Exact match | {"type": "equals", "value": "success"} |
contains |
Substring check | {"type": "contains", "value": "total"} |
not_contains |
Negative check | {"type": "not_contains", "value": "error"} |
json_path |
JSON path extraction | {"type": "json_path", "path": "data.count", "value": 10} |
regex |
Pattern matching | {"type": "regex", "value": "^[A-Z].*"} |
type |
Type checking | {"type": "type", "value": "array"} |
script |
Custom script | {"type": "script", "script": "scripts.validate"} |
agent |
AI validation | {"type": "agent", "use": "validator"} |
4.6 P4: Deliver
Send output:
delivery:
type: email # email | file | webhook | notify
opts:
to: ["manager@company.com"]
4.7 P5: Learn
Save to KB:
| Type | Examples |
|---|---|
execution |
What worked, what failed |
feedback |
Errors, fixes |
insight |
Patterns, tips |
5. Config
5.1 Structure
type Config struct {
Triggers *Triggers `json:"triggers,omitempty"`
Clock *Clock `json:"clock,omitempty"`
Identity *Identity `json:"identity"`
Quota *Quota `json:"quota"`
KB *KB `json:"kb,omitempty"` // shared KB (same as assistant)
DB *DB `json:"db,omitempty"` // shared DB (same as assistant)
Learn *Learn `json:"learn,omitempty"` // learning for private KB
Resources *Resources `json:"resources"`
Delivery *Delivery `json:"delivery"`
Events []Event `json:"events,omitempty"`
Executor *Executor `json:"executor,omitempty"` // executor mode settings
}
5.2 Types
// Phase - execution phase enum
type Phase string
const (
PhaseInspiration Phase = "inspiration" // P0: Clock only
PhaseGoals Phase = "goals" // P1
PhaseTasks Phase = "tasks" // P2
PhaseRun Phase = "run" // P3 (execution + validation)
PhaseDelivery Phase = "delivery" // P4
PhaseLearning Phase = "learning" // P5
)
// AllPhases for iteration
var AllPhases = []Phase{
PhaseInspiration, PhaseGoals, PhaseTasks,
PhaseRun, PhaseDelivery, PhaseLearning,
}
// ClockMode - clock trigger mode enum
type ClockMode string
const (
ClockModeTimes ClockMode = "times" // run at specific times
ClockModeInterval ClockMode = "interval" // run every X duration
ClockModeDaemon ClockMode = "daemon" // run continuously
)
// DeliveryType - output delivery type enum
type DeliveryType string
const (
DeliveryEmail DeliveryType = "email"
DeliveryFile DeliveryType = "file"
DeliveryWebhook DeliveryType = "webhook"
DeliveryNotify DeliveryType = "notify"
)
// ExecStatus - execution status enum
type ExecStatus string
const (
ExecPending ExecStatus = "pending"
ExecRunning ExecStatus = "running"
ExecCompleted ExecStatus = "completed"
ExecFailed ExecStatus = "failed"
)
// RobotStatus - matches __yao.member.robot_status enum
type RobotStatus string
const (
RobotIdle RobotStatus = "idle" // ready to run
RobotWorking RobotStatus = "working" // currently executing
RobotPaused RobotStatus = "paused" // manually paused
RobotError RobotStatus = "error" // encountered error
RobotMaintenance RobotStatus = "maintenance" // under maintenance
)
// Triggers - all on by default
type Triggers struct {
Clock *Trigger `json:"clock,omitempty"`
Intervene *Trigger `json:"intervene,omitempty"`
Event *Trigger `json:"event,omitempty"`
}
type Trigger struct {
Enabled bool `json:"enabled"`
Actions []string `json:"actions,omitempty"` // for intervene
}
// Clock - when to wake up
type Clock struct {
Mode ClockMode `json:"mode"`
Times []string `json:"times"` // for times: ["09:00", "14:00"]
Days []string `json:"days"` // ["Mon", "Tue"...] or ["*"]
Every string `json:"every"` // for interval: "30m", "1h"
TZ string `json:"tz"` // Asia/Shanghai
Timeout string `json:"timeout"` // max run time
}
// Identity
type Identity struct {
Role string `json:"role"`
Duties []string `json:"duties"`
Rules []string `json:"rules"`
}
// Quota
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)
}
// KB
// KB - shared knowledge base (same as assistant)
type KB struct {
Collections []string `json:"collections,omitempty"` // KB collection IDs
Options map[string]interface{} `json:"options,omitempty"`
}
// DB - shared database (same as assistant)
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 auto-created: robot_{team_id}_{member_id}_kb
type Learn struct {
On bool `json:"on"`
Types []string `json:"types"` // execution, feedback, insight
Keep int `json:"keep"` // days, 0 = forever
}
// Resources
type Resources struct {
Phases map[Phase]string `json:"phases,omitempty"` // optional, defaults to __yao.{phase}
Agents []string `json:"agents"`
MCP []MCP `json:"mcp"`
}
type MCP struct {
ID string `json:"id"`
Tools []string `json:"tools,omitempty"` // empty = all
}
// Delivery
type Delivery struct {
Type DeliveryType `json:"type"`
Opts map[string]interface{} `json:"opts"`
}
// 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)
)
// Executor - executor settings
type Executor struct {
Mode ExecutorMode `json:"mode,omitempty"` // standard | dryrun | sandbox
MaxDuration string `json:"max_duration,omitempty"` // max execution time (e.g., "30m")
}
// Note: Sandbox mode requires container infrastructure (Docker/gVisor).
// Current implementation falls back to DryRun behavior.
// Monitor
5.3 Example
Example record in __yao.member table:
{
"member_id": "mem_abc123",
"team_id": "team_xyz",
"member_type": "robot",
"display_name": "Sales Bot",
"autonomous_mode": true,
"robot_status": "idle",
"system_prompt": "You are a sales analyst...",
"robot_config": {
"triggers": {
"clock": { "enabled": true },
"intervene": { "enabled": true },
"event": { "enabled": false }
},
"clock": {
"mode": "times",
"times": ["09:00", "14:00", "17:00"],
"days": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"tz": "Asia/Shanghai",
"timeout": "30m"
},
"identity": {
"role": "Sales Analyst",
"duties": ["Analyze sales", "Make weekly reports"],
"rules": ["Only access sales data"]
},
"quota": { "max": 2, "queue": 10, "priority": 5 },
"kb": { "collections": ["sales-policies", "products"] },
"db": { "models": ["sales", "customers"] },
"learn": {
"on": true,
"types": ["execution", "feedback", "insight"],
"keep": 90
},
"resources": {
"phases": {
"inspiration": "__yao.inspiration",
"goals": "__yao.goals",
"tasks": "__yao.tasks",
"validation": "__yao.validation",
"delivery": "__yao.delivery",
"learning": "__yao.learning"
},
"agents": ["data-analyst", "chart-gen"],
"mcp": [{ "id": "database", "tools": ["query"] }]
},
"delivery": {
"type": "email",
"opts": { "to": ["manager@company.com"] }
},
"executor": {
"mode": "standard",
"max_duration": "30m"
}
},
"agents": ["data-analyst", "chart-gen"],
"mcp_servers": ["database"]
}
6. Lifecycle
6.1 Agent States
stateDiagram-v2
[*] --> Idle: POST create
Idle --> Working: trigger
Working --> Idle: done
Idle --> Paused: PATCH pause
Working --> Paused: PATCH pause
Paused --> Idle: PATCH resume
Idle --> Error: error
Working --> Error: error
Error --> Idle: PATCH reset
Idle --> [*]: DELETE
Paused --> [*]: DELETE
| From | To | How |
|---|---|---|
| - | idle | POST create |
| idle | working | trigger (clock/human/event) |
| working | idle | execution done |
| idle | paused | PATCH robot_status="paused" |
| paused | idle | PATCH robot_status="idle" |
| any | error | execution error |
| error | idle | PATCH robot_status="idle" |
| any | deleted | DELETE |
6.2 On Create
- Check config
- Generate member_id if missing
- Create KB:
robot_{team_id}_{member_id}_kb - Add to cache
- Create Job
- Set active
6.3 On Delete
- Stop running jobs
- Remove from cache
- Delete Job
- Delete or archive KB
- Soft delete record
6.4 Execution Flow
Single execution flow, depends on trigger type:
flowchart LR
subgraph Trigger
T{Trigger}
end
subgraph Schedule Path
P0[P0: Inspiration]
end
subgraph Common Path
P1[P1: Goals]
P2[P2: Tasks]
P3[P3: Run]
P4[P4: Deliver]
P5[P5: Learn]
end
T -->|Clock| P0
T -->|Human/Event| P1
P0 --> P1
P1 --> P2 --> P3 --> P4 --> P5
stateDiagram-v2
[*] --> Triggered
Triggered --> P0_Inspiration: Clock
Triggered --> P1_Goals: Human/Event
P0_Inspiration --> P1_Goals
P1_Goals --> P2_Tasks
P2_Tasks --> P3_Run
P3_Run --> P4_Deliver
P4_Deliver --> P5_Learn
P5_Learn --> [*]
7. Integrations
7.1 Job System
Relationship: 1 Robot : N Executions (concurrent), 1 Execution = 1 job.Job
Each trigger creates a new Execution, mapped to a job.Job for monitoring.
┌─────────────────────────────────────────────────────────────────┐
│ Activity Monitor (UI) │
│ • List jobs │
│ • See progress │
│ • View logs │
│ • Cancel/retry │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Job Framework │
│ Job → Execution → Progress → Logs │
└─────────────────────────────────────────────────────────────────┘
Go APIs (yao/job package):
| Action | API |
|---|---|
| List Jobs | job.ListJobs(param, page, pagesize) |
| Get Job | job.GetJob(jobID, param) |
| Save Job | job.SaveJob(j) |
| List Execs | job.ListExecutions(param, page, pagesize) |
| Get Exec | job.GetExecution(execID, param) |
| Save Exec | job.SaveExecution(exec) |
| List Logs | job.ListLogs(param, page, pagesize) |
| Save Log | job.SaveLog(log) |
| Push (start) | j.Push() |
| Stop | j.Stop() |
| Destroy | j.Destroy() |
| Active Jobs | job.GetActiveJobs() |
| Query by Cat | job.ListJobs({Wheres: [{Column: "category_id", ...}]}) |
7.2 Private KB
Made on robot member create: robot_{team_id}_{member_id}_kb
What it stores:
execution: What worked, what failedfeedback: Errors, fixesinsight: Patterns, tips
When:
- Create: On robot member create
- Update: After P5
- Clean: Based on
keepdays - Delete: On robot member delete
7.3 External Input
Types:
clock: Timer (with time context)intervene: Human actionevent: Webhook, DB changecallback: Async result
Human actions (InterventionAction):
task.add: Add a new tasktask.cancel: Cancel a tasktask.update: Update task detailsgoal.adjust: Modify current goalgoal.add: Add a new goalgoal.complete: Mark goal as completegoal.cancel: Cancel a goalplan.add: Schedule for laterplan.remove: Remove from plan queueplan.update: Update planned iteminstruct: Direct instruction to robot
Plan Queue:
- Holds tasks for later
- Runs at next cycle start
8. API
8.1 Manager (Internal)
Note: Manager is the central orchestrator, handling all trigger types.
type Manager interface {
// Lifecycle
Start() error
Stop() error
// Clock trigger (internal, called by 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 (called by API)
Intervene(ctx *Context, req *InterveneRequest) (*ExecutionResult, error)
// Event trigger (called by webhook/db 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
// Cache access
Cache() Cache
}
8.2 Trigger (Integrated into Manager)
Note: Trigger logic is integrated into Manager, not a separate interface. The
trigger/package provides utilities (validation, clock matching, execution control).
// TriggerType enum
type TriggerType string
const (
TriggerClock TriggerType = "clock"
TriggerHuman TriggerType = "human"
TriggerEvent TriggerType = "event"
)
// Manager handles all trigger types:
// - Clock: Manager.Tick() called by internal ticker
// - Human: Manager.Intervene() called by API
// - Event: Manager.HandleEvent() called by webhook/db trigger
// trigger/ package provides utilities:
// - trigger.ValidateIntervention(req) - validate human intervention request
// - trigger.ValidateEvent(req) - validate event request
// - trigger.BuildEventInput(req) - build TriggerInput from event
// - trigger.ClockMatcher - reusable clock matching logic
// - trigger.ExecutionController - pause/resume/stop execution
type InterveneRequest struct {
TeamID string
MemberID string
Action InterventionAction // task.add | goal.adjust | task.cancel | plan.add | instruct
Messages []context.Message // user input (text, images, files)
PlanTime *time.Time // for action=plan.add
ExecutorMode ExecutorMode // optional: standard | dryrun (override robot config)
}
type EventRequest struct {
MemberID string
Source string // webhook path or table name
EventType string // lead.created, etc.
Data map[string]interface{}
ExecutorMode ExecutorMode // optional: standard | dryrun (override robot config)
}
type ExecutionResult struct {
ExecutionID string // Job execution ID
Status ExecStatus // pending | running | completed | failed
Message string // status message
}
type RobotState struct {
MemberID string // member_id from __yao.member
Status RobotStatus // idle | working | paused | error | maintenance
LastRun time.Time
NextRun time.Time
Running int // current running execution count
MaxRunning int // max concurrent executions (from Quota.Max)
RunningIDs []string // list of running execution IDs
}
8.3 Execution (Uses Job System)
No separate autonomous_executions table. Uses existing Job system.
Each trigger creates a new job.Job:
// On each trigger (clock/human/event), create a new Job
execID := gonanoid.Must()
j, _ := job.Once(job.GOROUTINE, map[string]interface{}{
"job_id": "robot_exec_" + execID, // unique per execution
"category_id": "autonomous_robot",
"name": fmt.Sprintf("%s - %s", member.DisplayName, triggerType),
"metadata": map[string]interface{}{
"member_id": memberID,
"team_id": teamID,
"trigger_type": triggerType,
"exec_id": execID,
},
})
job.SaveJob(j)
// Configure and start
j.ExecutionConfig = &job.ExecutionConfig{
Type: job.ExecutionTypeProcess,
ProcessName: "robot.Execute",
ProcessArgs: []interface{}{memberID, execID, triggerData},
}
j.Push()
Query executions for a robot:
// List all executions for a robot member
param := model.QueryParam{
Wheres: []model.QueryWhere{
{Column: "category_id", Value: "autonomous_robot"},
{Column: "metadata->member_id", Value: memberID},
},
Orders: []model.QueryOrder{{Column: "created_at", Option: "desc"}},
}
jobs, _ := job.ListJobs(param, 1, 10)
Query examples:
// List all robot jobs (all robots, all executions)
param := model.QueryParam{
Wheres: []model.QueryWhere{
{Column: "category_id", Value: "autonomous_robot"},
},
}
jobs, _ := job.ListJobs(param, 1, 20)
// Get executions for a robot
execParam := model.QueryParam{
Wheres: []model.QueryWhere{
{Column: "job_id", Value: "robot_" + memberID},
},
Orders: []model.QueryOrder{{Column: "created_at", Option: "desc"}},
}
execs, _ := job.ListExecutions(execParam, 1, 10)
// Get logs for an execution
logParam := model.QueryParam{
Wheres: []model.QueryWhere{
{Column: "execution_id", Value: execID},
},
}
logs, _ := job.ListLogs(logParam, 1, 100)
9. Security
- Team only: Agent sees only its team's data
- Role rules: Uses role_id permissions
- Limited tools: Only what's in
resources - Timeout: Stops if runs too long
- Logs: All runs saved
10. Quick Ref
Triggers
triggers:
clock: { enabled: true }
intervene: { enabled: true, actions: [...] }
event: { enabled: false }
Clock
# Mode 1: Specific times
clock:
mode: times
times: ["09:00", "14:00", "17:00"]
days: ["Mon", "Tue", "Wed", "Thu", "Fri"]
tz: Asia/Shanghai
timeout: 30m
# Mode 2: Interval
clock:
mode: interval
every: 30m # run every 30 minutes
timeout: 10m
# Mode 3: Daemon (continuous thinking/analysis)
clock:
mode: daemon # restart immediately after each run
timeout: 10m # max time per run
# Use case: Research analyst, market monitor
Phase Agents
# Optional - defaults to __yao.{phase} if not specified
resources:
phases:
inspiration: "__yao.inspiration" # Clock only
goals: "__yao.goals"
tasks: "__yao.tasks"
validation: "__yao.validation"
delivery: "__yao.delivery"
learning: "__yao.learning"
Quota
quota:
max: 2 # max running
queue: 10 # queue size
priority: 5 # 1-10
Executor
# Standard mode (default) - real Agent calls
executor:
mode: standard
max_duration: 30m
# DryRun mode - simulated execution (for testing/demos)
executor:
mode: dryrun
# Sandbox mode (NOT IMPLEMENTED) - container-isolated
# Requires Docker/gVisor infrastructure
# executor:
# mode: sandbox
# max_duration: 10m
API Override:
// Override executor mode per trigger
const result = Process("robot.Trigger", "mem_abc123", {
type: "human",
action: "task.add",
messages: [{ role: "user", content: "Test task" }],
executor_mode: "dryrun", // override robot config
});
11. Examples
Each example shows a different trigger mode:
| Example | Trigger | Mode | Scenario |
|---|---|---|---|
| 11.1 | Clock | times | SEO/GEO Content - daily content optimization |
| 11.2 | Clock | interval | Competitor Monitor - check every 2 hours |
| 11.3 | Clock | daemon | Research Analyst - continuous insight mining |
| 11.4 | Human | intervene | Sales Assistant - manager assigns tasks |
| 11.5 | Event | webhook | Lead Processor - qualify and route new leads |
11.1 SEO/GEO Content Agent (Clock: times)
Trigger: Clock - specific times daily
Role: AI Marketing - auto-generate and optimize SEO/GEO content.
// robot_config for SEO Content Agent
{
"triggers": {
"clock": { "enabled": true },
"intervene": { "enabled": true }
},
"clock": {
"mode": "times",
"times": ["06:00", "18:00"],
"days": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"tz": "Asia/Shanghai"
},
"identity": {
"role": "SEO/GEO Content Specialist",
"duties": [
"Research trending keywords in our industry",
"Generate SEO-optimized articles (2-3 per day)",
"Optimize existing content for GEO (AI search)",
"Track keyword rankings and adjust strategy",
"A/B test titles and meta descriptions"
]
},
"resources": {
"agents": ["keyword-researcher", "content-writer", "seo-optimizer"],
"mcp": [
{ "id": "google-search", "tools": ["trends", "rankings"] },
{ "id": "cms", "tools": ["create", "update", "publish"] }
]
},
"delivery": {
"type": "notify",
"opts": { "channel": "marketing-team" }
}
}
Example run at 06:00 Monday:
P0 Inspiration:
Clock: Monday 06:00, start of week
Data:
- Keyword "AI app development" trending (+45% this week)
- Our article ranks #8, competitor #2
- 3 articles need GEO optimization
World: New AI regulation announced last Friday
P1 Goals:
1. Write new article targeting "AI app development"
2. Optimize 3 old articles for GEO
3. Update meta descriptions for top 5 pages
P2 Tasks:
1. Research "AI app development" keywords → keyword-researcher
2. Write article with SEO structure → content-writer
3. Add FAQ schema for GEO → seo-optimizer
4. Publish to CMS → cms.publish
P3 Execute:
- Keywords: "AI app development", "build AI apps", "AI dev guide" (12 total)
- Article: 2500 words, 8 sections, FAQ schema added
- Published to CMS, indexed by Google
P4 Delivery:
→ Notify: "Published: 'Complete Guide to AI App Development' - targeting 12 keywords"
P5 Learn:
- "AI app development" articles perform well on Monday morning
- FAQ schema improves GEO visibility by 30%
11.2 Competitor Monitor (Clock: interval)
Trigger: Clock - every 2 hours
Role: Monitor competitors, track market changes, alert on important updates.
// robot_config for Competitor Monitor
{
"triggers": {
"clock": { "enabled": true }
},
"clock": {
"mode": "interval",
"every": "2h"
},
"identity": {
"role": "Competitor Intelligence Analyst",
"duties": [
"Monitor competitor websites for changes",
"Track competitor pricing updates",
"Watch for new product launches",
"Analyze competitor content strategy",
"Alert team on significant changes"
]
},
"resources": {
"agents": ["web-scraper", "diff-analyzer", "report-writer"],
"mcp": [{ "id": "web-search", "tools": ["search", "news"] }]
},
"delivery": {
"type": "webhook",
"opts": { "url": "https://slack.com/webhook/competitor-alerts" }
}
}
Example run detecting competitor change:
P0 Inspiration:
Clock: Tuesday 14:00
Data:
- Competitor A: pricing page changed
- Competitor B: new blog post about "AI agents"
- Competitor C: no changes
P1 Goals:
1. Analyze Competitor A pricing change
2. Summarize Competitor B's new content
3. Assess impact on our positioning
P2 Tasks:
1. Scrape old vs new pricing → web-scraper
2. Compare pricing tiers → diff-analyzer
3. Generate competitive analysis → report-writer
P3 Execute:
- Competitor A: dropped price 20% on enterprise tier
- Competitor B: targeting same keywords as us
P4 Delivery:
→ Slack: "🚨 Competitor A cut enterprise price 20% - review needed"
P5 Learn:
- Competitor A tends to change pricing on Tuesdays
- Price changes often precede feature launches
11.3 Industry Research Analyst (Clock: daemon)
Trigger: Clock - continuous daemon mode
Role: Continuously read industry news, papers, social media; extract insights; build knowledge.
// robot_config for Research Analyst
{
"triggers": {
"clock": { "enabled": true }
},
"clock": {
"mode": "daemon",
"timeout": "10m"
},
"identity": {
"role": "Industry Research Analyst",
"duties": [
"Continuously scan industry news and papers",
"Analyze trends and extract key insights",
"Identify emerging technologies and competitors",
"Build and maintain industry knowledge base",
"Alert team on significant developments"
]
},
"resources": {
"agents": ["content-reader", "insight-extractor", "report-writer"],
"mcp": [
{ "id": "web-search", "tools": ["search", "news"] },
{ "id": "arxiv", "tools": ["search", "fetch"] },
{ "id": "twitter", "tools": ["search", "trends"] }
]
},
"delivery": {
"type": "notify",
"opts": { "channel": "research-insights" }
}
}
Example continuous run:
Run #1 (09:00):
P0: Scan sources
- 15 new AI news articles
- 3 new papers on arXiv
- Twitter: "AI Agent" trending
P1: Goals:
1. Read and analyze new content
2. Extract insights relevant to our business
3. Update knowledge base
P2: Tasks:
1. Read articles → content-reader
2. Analyze papers → content-reader
3. Extract insights → insight-extractor
P3: Execute:
- Article: "OpenAI releases new agent framework"
Insight: Validates our direction, watch for API changes
- Paper: "Multi-agent collaboration patterns"
Insight: Useful for our agent design, save to KB
- Twitter: Sentiment positive on AI agents
P4: Notify: "📚 3 new insights added to KB"
P5: Learn: OpenAI news = high relevance, prioritize
→ Restart immediately
Run #2 (09:12):
P0: Scan sources
- 2 new articles (low relevance)
- No new papers
- Twitter: Normal activity
P1: Low-value content, skip deep analysis
P5: Learn: Mid-morning usually quiet
→ Restart immediately
Run #3 (09:25):
P0: Scan sources
- Breaking: "Competitor X raises $100M for AI platform"
P1: Goals:
1. Deep analyze competitor news
2. Assess impact on our market
3. Alert team immediately
P2: Tasks:
1. Gather all competitor X info → web-search
2. Analyze their positioning → insight-extractor
3. Write competitive brief → report-writer
P3: Execute:
- Competitor X: Focus on enterprise, similar target market
- Funding: Will likely expand sales team
- Threat level: Medium-High
P4: Notify: "🚨 Competitor X raised $100M - brief attached"
P5: Learn: Funding news = always high priority
→ Restart immediately
11.4 Sales Assistant (Human: intervene)
Trigger: Human intervention - sales manager assigns tasks
Role: Help sales team with research, proposals, follow-ups when manager assigns work.
// robot_config for Sales Assistant
{
"triggers": {
"clock": { "enabled": false },
"intervene": {
"enabled": true,
"actions": ["task.add", "goal.adjust", "instruct"]
}
},
"identity": {
"role": "Sales Assistant",
"duties": [
"Research assigned prospects and companies",
"Prepare customized proposals and presentations",
"Draft follow-up emails",
"Analyze deal history and suggest strategies",
"Prepare meeting briefs"
]
},
"resources": {
"agents": ["company-researcher", "proposal-writer", "email-drafter"],
"mcp": [
{ "id": "crm", "tools": ["query", "update"] },
{ "id": "linkedin", "tools": ["search", "profile"] },
{ "id": "email", "tools": ["draft", "send"] }
]
},
"delivery": {
"type": "email",
"opts": { "to": ["sales-manager@company.com"] }
}
}
Example: Sales manager assigns task:
Sales Manager Input:
Action: task.add
Messages: [{ role: "user", content: "Meeting with BigCorp CTO tomorrow. Prepare materials.
They do smart manufacturing, $150M revenue, digital transformation." }]
Agent Execution (no P0 for human trigger):
P1 Goals (from human input):
1. Research BigCorp and their CTO
2. Prepare meeting brief
3. Draft customized proposal
P2 Tasks:
1. Research BigCorp → company-researcher
- Company background, recent news
- Digital transformation status
- Potential pain points
2. Research CTO profile → linkedin.profile
- Background, interests
- Recent posts/articles
3. Prepare meeting brief → proposal-writer
4. Draft proposal → proposal-writer
P3 Execute:
- BigCorp: Leading smart manufacturing, 3 factories, implementing MES
- CTO John: Ex-Google, focused on AI+Manufacturing, recent post on "AI QC"
- Pain point: High QC labor cost, 2% defect miss rate
- Opportunity: Our AI QC solution can reduce miss rate to 0.1%
P4 Delivery:
→ Email to sales manager:
- Attachment 1: BigCorp Research Report (PDF)
- Attachment 2: CTO Profile Brief
- Attachment 3: Custom Proposal - AI QC Solution
- Attachment 4: Meeting Agenda Suggestion
Sales Manager Follow-up:
Action: task.add
Messages: [{ role: "user", content: "Also prepare some similar case studies, manufacturing preferred" }]
Agent Continues:
P1: Find similar manufacturing case studies
P2: Search CRM for manufacturing wins
P3: Found 3 cases: Auto parts factory, Electronics plant, Food processing
P4: Email: "3 manufacturing case studies attached"
P5: Learn: Manufacturing prospects often need QC case studies
11.5 Lead Processor (Event: webhook)
Trigger: Event - new lead from website/CRM
Role: Instantly process and qualify new leads, route to sales.
// robot_config for Lead Processor
{
"triggers": {
"clock": { "enabled": false },
"event": { "enabled": true }
},
"events": [
{
"type": "webhook",
"source": "/webhook/leads",
"filter": { "event_types": ["lead.created"] }
},
{
"type": "database",
"source": "crm_leads",
"filter": { "trigger": "insert" }
}
],
"identity": {
"role": "Lead Qualification Specialist",
"duties": [
"Instantly process new leads",
"Enrich lead data (company info, LinkedIn)",
"Score lead quality (1-100)",
"Route hot leads to sales immediately",
"Add cold leads to nurture sequence"
]
},
"resources": {
"agents": ["data-enricher", "lead-scorer"],
"mcp": [
{ "id": "clearbit", "tools": ["enrich"] },
{ "id": "crm", "tools": ["update", "assign"] },
{ "id": "email", "tools": ["send"] }
]
},
"delivery": {
"type": "webhook",
"opts": { "url": "https://slack.com/webhook/sales-leads" }
}
}
Example: New lead event:
Event Received:
Type: lead.created
Data: {
name: "John Smith",
email: "john@bigcorp.com",
company: "BigCorp",
message: "Interested in Enterprise pricing, team of 50"
}
Agent Execution (no P0 for events):
P1 Goals:
1. Enrich lead data
2. Score lead quality
3. Route appropriately
P2 Tasks:
1. Lookup company info → clearbit.enrich
2. Calculate lead score → lead-scorer
3. Update CRM → crm.update
4. Notify sales → slack webhook
P3 Execute:
- Company: BigCorp, 500 employees, Series C
- LinkedIn: VP of Engineering
- Lead Score: 85/100 (HOT)
- Reason: Enterprise inquiry, decision maker, funded company
P4 Delivery:
→ Slack: "🔥 HOT LEAD (85/100): John Smith @ BigCorp
- 500 employees, Series C
- Interested in Enterprise (50 seats)
- Assigned to: Sales Rep A"
→ CRM: Lead updated, assigned to Sales Rep A
→ Email to lead: "Thanks for your inquiry. Our sales rep will contact you within 1 hour."
P5 Learn:
- BigCorp profile saved for future reference
- VP-level leads from funded companies = high conversion