From 9e14d8b7af7a2f6e69d131de17937a91aa3a92eb Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 18 Jan 2026 11:55:33 +0800 Subject: [PATCH] Update DESIGN.md, TECHNICAL.md, and TODO.md for P4 Delivery Implementation - Revised DESIGN.md to clarify the architecture of the P4 delivery process, emphasizing the separation of content generation and channel decision-making. - Expanded TECHNICAL.md with detailed notes on the DeliveryRequest structure, DeliveryContent generation, and the role of the Delivery Center in managing delivery channels. - Updated TODO.md to reflect the completion of the Delivery Agent setup and the integration of delivery preferences, ensuring comprehensive tracking of the P4 implementation progress. - Enhanced documentation to outline the new delivery channels and their configurations, including email and webhook options, for improved clarity and usability. --- agent/robot/DESIGN.md | 175 +++++++++++++- agent/robot/TECHNICAL.md | 488 ++++++++++++++++++++++++++++++++++++++- agent/robot/TODO.md | 263 ++++++++++++++++----- 3 files changed, 844 insertions(+), 82 deletions(-) diff --git a/agent/robot/DESIGN.md b/agent/robot/DESIGN.md index adb22164..571b9129 100644 --- a/agent/robot/DESIGN.md +++ b/agent/robot/DESIGN.md @@ -262,7 +262,7 @@ Human/Event: P1 → P2 → P3 → P4 → P5 | 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 | +| P4 | Delivery | All results | Email/Webhook | Always | | P5 | Learning | Summary | KB entries | Always | ### 4.2 P0: Inspiration (Clock only) @@ -316,7 +316,7 @@ type Goals struct { } type DeliveryTarget struct { - Type DeliveryType // email | webhook | report | notification + Type DeliveryType // Preferred delivery type (P4 will use Delivery Center) Recipients []string // email addresses, webhook URLs, user IDs Format string // markdown | html | json | text Template string // template name @@ -489,13 +489,169 @@ Universal assertion library supporting 8 types: ### 4.6 P4: Deliver -Send output: +P4 generates delivery content and pushes to Delivery Center. **Agent only generates content, Delivery Center decides channels.** + +**Architecture:** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ P4 Delivery Agent │ +│ Role: Generate content only (Summary, Body, Attachments) │ +│ NOT responsible for: Channel selection │ +└─────────────────────┬───────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ DeliveryRequest │ +│ - Content: Summary, Body, Attachments │ +│ - Context: robot_id, exec_id, trigger_type, team_id │ +│ (No Channels - Delivery Center decides) │ +└─────────────────────┬───────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Delivery Center │ +│ Role: │ +│ 1. Read Robot/User delivery preferences │ +│ 2. Decide which channels to use │ +│ 3. Execute delivery (email, webhook) │ +│ 4. Future: auto-notify based on user subscriptions │ +│ │ +│ (Current: internal, future: yao/delivery) │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Key Design:** +- **Separation of concerns**: Agent generates content, Delivery Center handles channels +- **User preferences**: Channels decided by Robot/User configuration, not Agent +- **Automatic delivery**: If webhook configured, every execution pushes automatically +- **Future-ready**: Delivery Center can be extracted to `yao/delivery` package + +**Delivery Request Structure:** + +```go +// DeliveryRequest - pushed to Delivery Center +// No Channels field - Delivery Center decides based on preferences +type DeliveryRequest struct { + Content *DeliveryContent `json:"content"` // Agent-generated content + Context *DeliveryContext `json:"context"` // Tracking info +} + +// DeliveryContent - content generated by Delivery Agent +type DeliveryContent struct { + Summary string `json:"summary"` // Brief 1-2 sentence summary + Body string `json:"body"` // Full markdown report + Attachments []DeliveryAttachment `json:"attachments,omitempty"` // Output artifacts +} + +// DeliveryAttachment - task output attachment with metadata +type DeliveryAttachment struct { + Title string `json:"title"` // Human-readable title + Description string `json:"description,omitempty"` // What this artifact is + TaskID string `json:"task_id,omitempty"` // Which task produced this + File string `json:"file"` // Wrapper: __:// +} + +// DeliveryContext - tracking and audit info +type DeliveryContext struct { + RobotID string `json:"robot_id"` + ExecutionID string `json:"execution_id"` + TriggerType TriggerType `json:"trigger_type"` + TeamID string `json:"team_id"` +} +``` + +**File Wrapper Format:** + +Attachments use the standard `yao/attachment` wrapper format: +- Format: `__://` +- Example: `__yao.attachment://ccd472d11feb96e03a3fc468f494045c` +- Parse: `attachment.Parse(value)` → `(uploader, fileID, isWrapper)` +- Read: `attachment.Base64(ctx, value)` → base64 content + +**Delivery Channels (Delivery Center decides):** + +| Channel | Description | When | +|---------|-------------|------| +| `email` | Send via yao/messenger | If configured in preferences | +| `webhook` | POST to URL (Slack, 飞书, etc.) | If configured, every execution | +| `notify` | In-app push notification | Based on user subscriptions (future) | + +**Delivery Agent:** + +The Delivery Agent **only generates content**, does NOT decide channels: + +```go +// Delivery Agent Input +type DeliveryAgentInput struct { + Robot *Robot `json:"robot"` + TriggerType TriggerType `json:"trigger"` + Inspiration *InspirationReport `json:"inspiration"` // P0 + Goals *Goals `json:"goals"` // P1 + Tasks []Task `json:"tasks"` // P2 + Results []TaskResult `json:"results"` // P3 +} + +// Delivery Agent Output - only content, no channels +type DeliveryAgentOutput struct { + Content *DeliveryContent `json:"content"` +} +``` + +**Example Agent Output:** + +```json +{ + "content": { + "summary": "Sales report completed: 15 new leads processed", + "body": "## Weekly Sales Report\n\n### Summary\n...", + "attachments": [ + {"title": "Sales Report.pdf", "file": "__yao.attachment://abc123"}, + {"title": "Lead Analysis.xlsx", "file": "__yao.attachment://def456"} + ] + } +} +``` + +**Delivery Result:** + +```go +// DeliveryResult - returned by Delivery Center +type DeliveryResult struct { + RequestID string `json:"request_id"` // Delivery request ID + Content *DeliveryContent `json:"content"` // What was delivered + Success bool `json:"success"` // All channels succeeded + Results []ChannelResult `json:"results"` // Per-channel results + Error string `json:"error,omitempty"` +} + +// ChannelResult - result for a single channel +type ChannelResult struct { + Type DeliveryType `json:"type"` + Success bool `json:"success"` + Recipients []string `json:"recipients,omitempty"` // For email + Details interface{} `json:"details,omitempty"` + Error string `json:"error,omitempty"` + SentAt *time.Time `json:"sent_at,omitempty"` +} +``` + +**Config (Delivery Preferences):** + +Robot config defines delivery **preferences** (Delivery Center reads and executes): ```yaml delivery: - type: email # email | file | webhook | notify - opts: - to: ["manager@company.com"] + preferences: + email: + enabled: true + to: ["manager@company.com"] + cc: ["team@company.com"] + webhook: + enabled: true + url: "https://slack.com/webhook/reports" + # Every execution pushes to webhook automatically +# Note: notify handled by Delivery Center based on user subscriptions (future) ``` ### 4.7 P5: Learn @@ -564,10 +720,9 @@ const ( type DeliveryType string const ( - DeliveryEmail DeliveryType = "email" - DeliveryFile DeliveryType = "file" - DeliveryWebhook DeliveryType = "webhook" - DeliveryNotify DeliveryType = "notify" + DeliveryEmail DeliveryType = "email" // Email via yao/messenger + DeliveryWebhook DeliveryType = "webhook" // POST to URL + DeliveryNotify DeliveryType = "notify" // In-app notification (future) ) // ExecStatus - execution status enum diff --git a/agent/robot/TECHNICAL.md b/agent/robot/TECHNICAL.md index 4c42fe49..e9757468 100644 --- a/agent/robot/TECHNICAL.md +++ b/agent/robot/TECHNICAL.md @@ -854,10 +854,9 @@ const ( type DeliveryType string const ( - DeliveryEmail DeliveryType = "email" - DeliveryFile DeliveryType = "file" - DeliveryWebhook DeliveryType = "webhook" - DeliveryNotify DeliveryType = "notify" + DeliveryEmail DeliveryType = "email" // Email via yao/messenger + DeliveryWebhook DeliveryType = "webhook" // POST to URL + DeliveryNotify DeliveryType = "notify" // In-app notification (future) ) // DedupResult - deduplication result @@ -1309,9 +1308,11 @@ type Goals struct { Delivery *DeliveryTarget `json:"delivery,omitempty"` // where to send results (for P4) } -// DeliveryTarget - where to deliver results (defined in P1, used in P4) +// DeliveryTarget - where to deliver results (defined in P1, used by P4) +// Note: This is a hint from P1 Goals. Actual delivery is handled by Delivery Center +// based on Robot/User preferences, not strictly by this target. type DeliveryTarget struct { - Type DeliveryType `json:"type"` // email | webhook | report | notification + Type DeliveryType `json:"type"` // Preferred delivery type 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 @@ -1399,13 +1400,74 @@ type ValidationResult struct { ReplyContent string `json:"reply_content,omitempty"` // content for next turn (if NeedReply) } -// DeliveryResult - P4 delivery output +// DeliveryRequest - pushed to Delivery Center +// Agent only generates content, Delivery Center decides channels based on preferences +type DeliveryRequest struct { + Content *DeliveryContent `json:"content"` // Agent-generated content + Context *DeliveryContext `json:"context"` // Tracking info + // No Channels field - Delivery Center decides based on Robot/User preferences +} + +// DeliveryContent - content generated by Delivery Agent +type DeliveryContent struct { + Summary string `json:"summary"` // Brief summary (1-2 sentences) + Body string `json:"body"` // Full markdown report + Attachments []DeliveryAttachment `json:"attachments,omitempty"` // Output artifacts +} + +// DeliveryAttachment - task output attachment with metadata +// File uses wrapper format: __:// +// Example: __yao.attachment://ccd472d11feb96e03a3fc468f494045c +// Parse with attachment.Parse(value) → (uploader, fileID, isWrapper) +type DeliveryAttachment struct { + Title string `json:"title"` // Human-readable title, e.g., "Market Analysis Report" + Description string `json:"description,omitempty"` // Description of what this artifact is + TaskID string `json:"task_id,omitempty"` // Which task produced this artifact + File string `json:"file"` // Wrapper format: __:// +} + +// DeliveryContext - tracking and audit info +type DeliveryContext struct { + RobotID string `json:"robot_id"` + ExecutionID string `json:"execution_id"` + TriggerType TriggerType `json:"trigger_type"` + TeamID string `json:"team_id"` +} + +// DeliveryPreferences - Robot/User delivery preferences (read by Delivery Center) +type DeliveryPreferences struct { + Email *EmailPreference `json:"email,omitempty"` + Webhook *WebhookPreference `json:"webhook,omitempty"` + // notify is handled automatically based on user subscriptions +} + +type EmailPreference struct { + Enabled bool `json:"enabled"` + To []string `json:"to"` + CC []string `json:"cc,omitempty"` +} + +type WebhookPreference struct { + Enabled bool `json:"enabled"` + URL string `json:"url"` + // If enabled, every execution pushes automatically +} + +// DeliveryResult - P4 delivery output (returned by Delivery Center) type DeliveryResult struct { - Type DeliveryType `json:"type"` + RequestID string `json:"request_id"` // Delivery request ID + Content *DeliveryContent `json:"content,omitempty"` // What was delivered + Success bool `json:"success"` // All channels succeeded + Results []ChannelResult `json:"results,omitempty"` // Per-channel results + Error string `json:"error,omitempty"` // Overall error if any +} + +// ChannelResult - result for a single delivery channel +type ChannelResult struct { + Type DeliveryType `json:"type"` // email | webhook | notify 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 + Recipients []string `json:"recipients,omitempty"` // Who received (for email) + Details interface{} `json:"details,omitempty"` // Channel-specific response Error string `json:"error,omitempty"` SentAt *time.Time `json:"sent_at,omitempty"` } @@ -1893,3 +1955,407 @@ Supported assertion types: - `type` - type checking (with optional path) - `script` - custom script validation - `agent` - AI agent validation + +--- + +## 6. P4 Delivery Implementation + +### 6.1 Overview + +P4 Delivery summarizes P3 execution results and delivers to configured channels. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ delivery.go (P4 Entry) │ +│ - DeliveryExecution: main entry point │ +│ - Calls Delivery Agent with full execution context │ +│ - Routes DeliveryContent to configured channels │ +└─────────────────────┬───────────────────────────────────────┘ + │ + ┌────────────┴────────────┐ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ Delivery Agent │ │ Channel Handlers │ +│ - Summarize │ │ - Email │ +│ - Format body │ │ - Webhook │ +│ - List files │ │ - Notify │ +└─────────────────┘ │ - File │ + └─────────────────┘ +``` + +### 6.2 Delivery Request Structure + +P4 generates a `DeliveryRequest` with **only content** and pushes to Delivery Center. +**Delivery Center decides channels** based on Robot/User preferences. + +```go +// DeliveryRequest - pushed to Delivery Center +// No Channels - Delivery Center decides based on preferences +type DeliveryRequest struct { + Content *DeliveryContent `json:"content"` // Agent-generated content + Context *DeliveryContext `json:"context"` // Tracking info +} + +// DeliveryContent - content generated by Delivery Agent +type DeliveryContent struct { + Summary string `json:"summary"` // Brief 1-2 sentence summary + Body string `json:"body"` // Full markdown report + Attachments []DeliveryAttachment `json:"attachments,omitempty"` // Output artifacts from P3 +} + +// DeliveryAttachment - file attachment with metadata +type DeliveryAttachment struct { + Title string `json:"title"` // Human-readable title + Description string `json:"description,omitempty"` // What this artifact is + TaskID string `json:"task_id,omitempty"` // Which task produced this + File string `json:"file"` // Wrapper: __:// +} + +// DeliveryContext - tracking and audit info +type DeliveryContext struct { + RobotID string `json:"robot_id"` + ExecutionID string `json:"execution_id"` + TriggerType TriggerType `json:"trigger_type"` + TeamID string `json:"team_id"` +} +``` + +**Example DeliveryRequest:** + +```json +{ + "content": { + "summary": "Sales report completed: 15 new leads", + "body": "## Weekly Sales Report\n...", + "attachments": [{"title": "Report.pdf", "file": "__yao.attachment://abc123"}] + }, + "context": { + "robot_id": "mem_abc123", + "execution_id": "exec_xyz789", + "trigger_type": "clock", + "team_id": "team_123" + } +} +``` + +**Channel Decision by Delivery Center:** + +Delivery Center reads Robot/User preferences and decides channels: + +```go +// DeliveryPreferences - from Robot config +type DeliveryPreferences struct { + Email *EmailPreference `json:"email,omitempty"` + Webhook *WebhookPreference `json:"webhook,omitempty"` +} + +type EmailPreference struct { + Enabled bool `json:"enabled"` + To []string `json:"to"` + CC []string `json:"cc,omitempty"` +} + +type WebhookPreference struct { + Enabled bool `json:"enabled"` + URL string `json:"url"` + // If enabled, every execution pushes automatically +} +``` + +### 6.3 File Wrapper Format + +Attachments use the standard `yao/attachment` wrapper format: + +```go +// Format: __:// +// Example: __yao.attachment://ccd472d11feb96e03a3fc468f494045c + +import "github.com/yaoapp/yao/attachment" + +// Parse wrapper to get uploader and fileID +uploader, fileID, isWrapper := attachment.Parse(wrapper) +// uploader: "__yao.attachment" +// fileID: "ccd472d11feb96e03a3fc468f494045c" +// isWrapper: true + +// Get file info +manager := attachment.Managers[uploader] +fileInfo, err := manager.Info(ctx, fileID) + +// Read file content as base64 +base64Content := attachment.Base64(ctx, wrapper) + +// Read with data URI format +dataURI := attachment.Base64(ctx, wrapper, true) +// "data:image/png;base64,..." +``` + +### 6.4 Delivery Agent + +The Delivery Agent **only generates content**, does NOT decide channels. +Channel decisions are made by Delivery Center based on Robot/User preferences. + +**Input:** +```go +type DeliveryAgentInput struct { + Robot *Robot `json:"robot"` // Robot identity and config + TriggerType TriggerType `json:"trigger"` // clock | human | event + Inspiration *InspirationReport `json:"inspiration"` // P0 (clock only) + Goals *Goals `json:"goals"` // P1 + Tasks []Task `json:"tasks"` // P2 + Results []TaskResult `json:"results"` // P3 +} +``` + +**Output:** +```go +// DeliveryAgentOutput - only content, no channels +type DeliveryAgentOutput struct { + Content *DeliveryContent `json:"content"` // Generated content +} +``` + +**Agent Responsibilities:** + +The agent focuses on content generation: +- **Summary**: Brief 1-2 sentence summary of execution results +- **Body**: Full markdown report with details +- **Attachments**: Select which P3-generated files to include + +**Example Output:** + +```json +{ + "content": { + "summary": "Sales report completed: 15 new leads processed, 3 high-priority", + "body": "## Weekly Sales Report\n\n### Summary\n- Total leads: 15\n- High priority: 3\n...", + "attachments": [ + {"title": "Sales Report.pdf", "task_id": "task_1", "file": "__yao.attachment://abc123"}, + {"title": "Lead Analysis.xlsx", "task_id": "task_2", "file": "__yao.attachment://def456"} + ] + } +} +``` + +### 6.5 Delivery Center + +The Delivery Center receives `DeliveryRequest`, **decides channels based on preferences**, and executes delivery. + +**Current implementation:** Internal to P4 (in `executor/delivery.go`) +**Future:** Can be extracted to standalone `yao/delivery` package + +```go +// DeliveryCenter - handles channel decision and delivery execution +type DeliveryCenter struct { + handlers map[DeliveryType]ChannelHandler +} + +// ChannelHandler - interface for channel implementations +type ChannelHandler interface { + Deliver(ctx context.Context, content *DeliveryContent, opts map[string]interface{}) (*ChannelResult, error) +} + +// Deliver - main entry point +func (dc *DeliveryCenter) Deliver(ctx context.Context, req *DeliveryRequest) *DeliveryResult { + requestID := generateID() + + // 1. Get Robot/User delivery preferences + prefs := dc.getDeliveryPreferences(ctx, req.Context.RobotID) + + // 2. Decide channels based on preferences + channels := dc.decideChannels(prefs) + + // 3. Execute delivery to each channel + var results []ChannelResult + allSuccess := true + + for _, ch := range channels { + handler, ok := dc.handlers[ch.Type] + if !ok { + results = append(results, ChannelResult{ + Type: ch.Type, + Success: false, + Error: fmt.Sprintf("unsupported channel: %s", ch.Type), + }) + allSuccess = false + continue + } + + result, err := handler.Deliver(ctx, req.Content, ch.Options) + if err != nil { + result = &ChannelResult{Type: ch.Type, Success: false, Error: err.Error()} + allSuccess = false + } + results = append(results, *result) + } + + // 4. Future: check user subscriptions and send notifications + // dc.sendNotifications(ctx, req) + + return &DeliveryResult{ + RequestID: requestID, + Content: req.Content, + Success: allSuccess, + Results: results, + } +} + +// decideChannels - decide which channels to use based on preferences +func (dc *DeliveryCenter) decideChannels(prefs *DeliveryPreferences) []channelWithOpts { + var channels []channelWithOpts + + if prefs.Email != nil && prefs.Email.Enabled { + channels = append(channels, channelWithOpts{ + Type: DeliveryEmail, + Options: map[string]interface{}{"to": prefs.Email.To, "cc": prefs.Email.CC}, + }) + } + + if prefs.Webhook != nil && prefs.Webhook.Enabled { + channels = append(channels, channelWithOpts{ + Type: DeliveryWebhook, + Options: map[string]interface{}{"url": prefs.Webhook.URL}, + }) + } + + return channels +} +``` + +### 6.6 Channel Handlers + +Each delivery channel has a dedicated handler implementing `ChannelHandler`: + +```go +// EmailHandler - uses yao/messenger +type EmailHandler struct { + messenger *messenger.Manager +} + +func (h *EmailHandler) Deliver(ctx context.Context, content *DeliveryContent, opts map[string]interface{}) (*ChannelResult, error) { + // Convert attachments to messenger format + var attachments []messenger.Attachment + for _, att := range content.Attachments { + uploader, fileID, _ := attachment.Parse(att.File) + manager := attachment.Managers[uploader] + data, _ := manager.Read(ctx, fileID) + info, _ := manager.Info(ctx, fileID) + + attachments = append(attachments, messenger.Attachment{ + Filename: att.Title, + ContentType: info.ContentType, + Content: data, + }) + } + + to := opts["to"].([]string) + err := h.messenger.Send(ctx, &messenger.Message{ + To: to, + Subject: content.Summary, // Use summary as subject + Body: content.Body, + Attachments: attachments, + }) + + now := time.Now() + return &ChannelResult{ + Type: DeliveryEmail, + Success: err == nil, + Recipients: to, + SentAt: &now, + }, err +} + +// WebhookHandler - POST JSON to URL +type WebhookHandler struct{} + +func (h *WebhookHandler) Deliver(ctx context.Context, content *DeliveryContent, opts map[string]interface{}) (*ChannelResult, error) { + payload, _ := json.Marshal(content) + req, _ := http.NewRequestWithContext(ctx, "POST", opts["url"].(string), bytes.NewReader(payload)) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return &ChannelResult{Type: DeliveryWebhook, Success: false}, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return &ChannelResult{Type: DeliveryWebhook, Success: false}, fmt.Errorf("webhook failed: %d", resp.StatusCode) + } + + now := time.Now() + return &ChannelResult{ + Type: DeliveryWebhook, + Success: true, + Details: map[string]interface{}{"status_code": resp.StatusCode}, + SentAt: &now, + }, nil +} + +``` + +**Note on Notifications:** + +`notify` is NOT configured per-Robot. Future Delivery Center will: +1. Check user subscription preferences after receiving DeliveryRequest +2. Automatically send in-app notifications to subscribed users +3. This is transparent to P4 and Delivery Agent + +### 6.7 Execution Persistence + +Robot execution history is stored in `__yao.agent_execution` table for UI display: + +```go +// Model: yao/models/agent/execution.mod.yao +// Table: __yao.agent_execution + +type ExecutionRecord struct { + ID string `json:"id"` // Execution ID + MemberID string `json:"member_id"` // Robot member ID + TeamID string `json:"team_id"` // Team ID + JobID string `json:"job_id"` // Linked job.Job ID + TriggerType TriggerType `json:"trigger_type"` // clock | human | event + Status ExecStatus `json:"status"` // pending | running | completed | failed + Phase Phase `json:"phase"` // Current phase + Input *TriggerInput `json:"input"` // Original trigger input + Inspiration *InspirationReport `json:"inspiration"` // P0 result + Goals *Goals `json:"goals"` // P1 result + Tasks []Task `json:"tasks"` // P2 result + Results []TaskResult `json:"results"` // P3 results + Delivery *DeliveryResult `json:"delivery"` // P4 result + Learning []LearningEntry `json:"learning"` // P5 entries + Error string `json:"error"` // Error message if failed + StartTime time.Time `json:"start_time"` + EndTime *time.Time `json:"end_time"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} +``` + +**Store Interface:** + +```go +// store/execution.go +type ExecutionStore interface { + // Save creates or updates an execution record + Save(ctx context.Context, record *ExecutionRecord) error + + // Get retrieves an execution by ID + Get(ctx context.Context, execID string) (*ExecutionRecord, error) + + // List retrieves executions with filters + List(ctx context.Context, opts ListOptions) ([]*ExecutionRecord, int, error) + + // UpdatePhase updates the current phase + UpdatePhase(ctx context.Context, execID string, phase Phase, data interface{}) error +} + +type ListOptions struct { + MemberID string // Filter by robot + TeamID string // Filter by team + Status ExecStatus // Filter by status + TriggerType TriggerType // Filter by trigger + Page int + PageSize int +} +``` diff --git a/agent/robot/TODO.md b/agent/robot/TODO.md index d6ec3ca1..5d4d8643 100644 --- a/agent/robot/TODO.md +++ b/agent/robot/TODO.md @@ -62,7 +62,7 @@ - [x] `RobotStatus` - robot status (idle, working, paused, error, maintenance) - [x] `InterventionAction` - human actions (task.add, goal.adjust, etc.) - [x] `Priority` - priority levels (high, normal, low) -- [x] `DeliveryType` - delivery types (email, file, webhook, notify) +- [x] `DeliveryType` - delivery types (email, webhook, notify) - [x] `DedupResult` - dedup results (skip, merge, proceed) - [x] `EventSource` - event sources (webhook, database) - [x] `LearningType` - learning types (execution, feedback, insight) @@ -555,7 +555,7 @@ yao-dev-app/assistants/ - [x] `robot/delivery/package.yao` - config - [x] `robot/delivery/prompts.yml` - system prompt: - - Input: Task results, delivery target (email, report, notification) + - Input: Full execution context (P0-P3 results) - Output: Formatted delivery content - Style: Clear, professional @@ -869,92 +869,231 @@ Created new `yao/assert` package for universal assertion/validation: **Depends on:** Phase 9 (P3 Run) -### 10.1 Delivery Agent Setup +### 10.1 Execution Persistence (Prerequisite) + +> **Background:** Each Robot execution (P0-P5) needs persistent storage for UI history queries. + +- [ ] `yao/models/agent/execution.mod.yao` - Execution record model (`agent_execution` table) + - [ ] id, execution_id (unique) + - [ ] member_id, team_id, job_id + - [ ] trigger_type (enum: clock, human, event) + - [ ] status (enum: pending, running, completed, failed, cancelled) + - [ ] phase (enum: inspiration, goals, tasks, run, delivery, learning) + - [ ] start_time, end_time, error + - [ ] input (JSON) - trigger input + - [ ] inspiration (JSON) - P0 output + - [ ] goals (JSON) - P1 output + - [ ] tasks (JSON) - P2 output + - [ ] results (JSON) - P3 output + - [ ] delivery (JSON) - P4 output + - [ ] learning (JSON) - P5 output + - [ ] Relations: member (hasOne __yao.member) +- [ ] `agent/robot/store/execution.go` - Execution record storage + - [ ] `SaveExecution()` - save/update execution record + - [ ] `GetExecution()` - get execution by ID + - [ ] `ListExecutions()` - query execution history by member_id +- [ ] Integrate into Executor - update record after each phase + +### 10.2 Messenger Attachment Support ✅ + +> **Conclusion:** `yao/messenger` already supports attachments + +```go +// messenger/types/types.go +type Attachment struct { + Filename string `json:"filename"` + ContentType string `json:"content_type"` + Content []byte `json:"content"` + Inline bool `json:"inline,omitempty"` + CID string `json:"cid,omitempty"` +} +``` + +Supported channels: +- [x] Email - Full attachment support +- [x] SMS - No attachment (text only) +- [x] WhatsApp - TBD + +### 10.3 Type Updates (Prerequisite) + +- [ ] Update `types/enums.go` - Remove `DeliveryFile` from `DeliveryType` +- [ ] Update `types/robot.go` - Delivery types for new architecture + - [ ] `DeliveryResult` - update to new structure (RequestID, Content, Results[]) + - [ ] Add `DeliveryContent` struct + - [ ] Add `DeliveryAttachment` struct + - [ ] Add `DeliveryRequest` struct + - [ ] Add `DeliveryContext` struct + - [ ] Add `DeliveryPreferences` struct + - [ ] Add `ChannelResult` struct +- [ ] Update `types/enums_test.go` - Remove `DeliveryFile` test +- [ ] Update `types/robot_test.go` - Update delivery result tests + +### 10.4 Delivery Agent Setup - [ ] `robot/delivery/package.yao` - Delivery Agent config - [ ] `robot/delivery/prompts.yml` - delivery prompts + - [ ] Input: Full execution context (P0-P3 results) + - [ ] Output: DeliveryContent (Summary, Body, Attachments) - **only content, no channels** + - [ ] Agent focuses on content generation, NOT channel selection -### 10.2 Implementation +### 10.5 Delivery Content Structure -- [ ] `executor/delivery.go` - `RunDelivery(ctx, exec, data)` - real implementation -- [ ] `executor/delivery.go` - build delivery content from results -- [ ] `executor/delivery.go` - support email delivery -- [ ] `executor/delivery.go` - support file delivery -- [ ] `executor/delivery.go` - support webhook delivery -- [ ] `executor/delivery.go` - support notify delivery +```go +// DeliveryRequest - pushed to Delivery Center +// No Channels - Delivery Center decides based on preferences +type DeliveryRequest struct { + Content *DeliveryContent `json:"content"` // Agent-generated content + Context *DeliveryContext `json:"context"` // Tracking info +} -### 10.3 Tests +// DeliveryContent - Content generated by Delivery Agent (only content) +type DeliveryContent struct { + Summary string `json:"summary"` // Brief 1-2 sentence summary + Body string `json:"body"` // Full markdown report + Attachments []DeliveryAttachment `json:"attachments,omitempty"` // Output artifacts from P3 +} + +// DeliveryAttachment - Task output attachment with metadata +type DeliveryAttachment struct { + Title string `json:"title"` // Human-readable title + Description string `json:"description,omitempty"` // What this artifact is + TaskID string `json:"task_id,omitempty"` // Which task produced this + File string `json:"file"` // Wrapper: __:// +} + +// DeliveryContext - tracking info +type DeliveryContext struct { + RobotID string `json:"robot_id"` + ExecutionID string `json:"execution_id"` + TriggerType string `json:"trigger_type"` + TeamID string `json:"team_id"` +} +``` + +**Key Design:** +- **Agent only generates content** (Summary, Body, Attachments) +- **Delivery Center decides channels** based on Robot/User preferences +- If webhook configured, every execution pushes automatically + +**File Wrapper:** +- Format: `__://` +- Parse: `attachment.Parse(value)` → `(uploader, fileID, isWrapper)` +- Read: `attachment.Base64(ctx, value)` → base64 content + +**Delivery Channels (Delivery Center decides):** +| Channel | Description | +|---------|-------------| +| `email` | Send via yao/messenger (if configured in preferences) | +| `webhook` | POST JSON to URL (if configured, every execution) | +| `notify` | In-app notification based on user subscriptions (future) | + +### 10.6 Implementation + +**P4 Entry (executor/delivery.go):** +- [ ] `RunDelivery(ctx, exec, data)` - P4 entry point + - [ ] Call Delivery Agent to generate content (only content, no channels) + - [ ] Build DeliveryRequest (Content + Context) + - [ ] Push to Delivery Center + - [ ] Store DeliveryResult in exec.Delivery + +**Delivery Center (executor/delivery.go, future: yao/delivery):** +- [ ] `DeliveryCenter.Deliver(ctx, request)` - main entry + - [ ] Read Robot/User delivery preferences + - [ ] Decide which channels to use based on preferences + - [ ] Call appropriate handler for each channel + - [ ] Aggregate ChannelResults into DeliveryResult +- [ ] `ChannelHandler` interface + - [ ] `Deliver(ctx, content, opts) (*ChannelResult, error)` + +**Channel Handlers:** +- [ ] `EmailHandler` - uses yao/messenger + - [ ] Convert DeliveryAttachment to messenger.Attachment + - [ ] Use Summary as email subject + - [ ] Support to, cc from preferences +- [ ] `WebhookHandler` - POST JSON + - [ ] POST DeliveryContent as JSON payload + - [ ] If enabled, every execution pushes automatically + +### 10.7 Tests - [ ] `executor/delivery_test.go` - P4 delivery -- [ ] Test: delivery content generated correctly -- [ ] Test: email delivery (mock or real) -- [ ] Test: file delivery to configured path +- [ ] Test: Delivery Agent generates content (only content) +- [ ] Test: DeliveryCenter reads preferences and decides channels +- [ ] Test: DeliveryCenter dispatches to multiple channels +- [ ] Test: EmailHandler with attachments +- [ ] Test: WebhookHandler POST JSON +- [ ] Test: Partial success (some channels fail) +- [ ] Test: DeliveryResult aggregation --- -## Phase 11: P5 Learning Implementation +## Phase 11: API & Integration -**Goal:** Implement P5 (Learning). Full execution flow complete. +**Goal:** Complete API implementation, end-to-end tests. Main flow: P0 → P1 → P2 → P3 → P4. **Depends on:** Phase 10 (P4 Delivery) -### 11.1 Learning Agent Setup +> **Note:** P5 Learning is an advanced feature (async, background, user-invisible). +> Main flow works without it. Moved to Phase 12 (Advanced Features). -- [ ] `robot/learning/package.yao` - Learning Agent config -- [ ] `robot/learning/prompts.yml` - learning prompts - -### 11.2 Store Implementation - -- [ ] `store/store.go` - Store interface and struct -- [ ] `store/kb.go` - KB operations (create, save, search) -- [ ] `store/learning.go` - save learning entries to private KB - -### 11.3 Implementation - -- [ ] `executor/learning.go` - `RunLearning(ctx, exec, data)` - real implementation -- [ ] `executor/learning.go` - extract learnings from execution -- [ ] `executor/learning.go` - call Learning Agent -- [ ] `executor/learning.go` - save to private KB - -### 11.4 Tests - -- [ ] `executor/learning_test.go` - P5 learning -- [ ] Test: learnings extracted from execution -- [ ] Test: learnings saved to KB -- [ ] Test: KB can be queried for past learnings - ---- - -## Phase 12: API & Integration - -**Goal:** Complete API implementation, end-to-end tests. - -### 12.1 API Implementation +### 11.1 API Implementation - [ ] `api/api.go` - implement all Go API functions - [ ] `api/process.go` - implement all Process handlers - [ ] `api/jsapi.go` - implement JSAPI -### 12.2 End-to-End Tests +### 11.2 End-to-End Tests -- [ ] Full clock trigger flow (P0 → P5) -- [ ] Human intervention flow (P1 → P5) -- [ ] Event trigger flow (P1 → P5) +- [ ] Full clock trigger flow (P0 → P1 → P2 → P3 → P4) +- [ ] Human intervention flow (P1 → P2 → P3 → P4) +- [ ] Event trigger flow (P1 → P2 → P3 → P4) - [ ] Concurrent execution test - [ ] Pause/Resume/Stop test -### 12.3 Integration with OpenAPI +### 11.3 Integration with OpenAPI - [ ] HTTP endpoints for human intervention - [ ] Webhook endpoints for events --- -## Phase 13: Advanced Features +## Phase 12: Advanced Features -**Goal:** Implement dedup, semantic dedup, plan queue. +**Goal:** Implement P5 Learning, dedup, semantic dedup, plan queue. -### 13.1 Fast Dedup (Time-Window) +> **Note:** These are optional advanced features. Main flow works without them. + +### 12.1 P5 Learning Implementation + +> **Background:** P5 Learning is async, runs after P4 Delivery completes. +> User doesn't wait for it. Results stored in private KB for future reference. + +#### 12.1.1 Learning Agent Setup + +- [ ] `robot/learning/package.yao` - Learning Agent config +- [ ] `robot/learning/prompts.yml` - learning prompts + +#### 12.1.2 Store Implementation + +- [ ] `store/store.go` - Store interface and struct +- [ ] `store/kb.go` - KB operations (create, save, search) +- [ ] `store/learning.go` - save learning entries to private KB + +#### 12.1.3 Implementation + +- [ ] `executor/learning.go` - `RunLearning(ctx, exec, data)` - real implementation +- [ ] `executor/learning.go` - extract learnings from execution +- [ ] `executor/learning.go` - call Learning Agent +- [ ] `executor/learning.go` - save to private KB + +#### 12.1.4 Tests + +- [ ] `executor/learning_test.go` - P5 learning +- [ ] Test: learnings extracted from execution +- [ ] Test: learnings saved to KB +- [ ] Test: KB can be queried for past learnings + +### 12.2 Fast Dedup (Time-Window) > **Note:** Manager has `// TODO: dedup check` comment placeholder. Integrate after implementation. @@ -966,13 +1105,13 @@ Created new `yao/assert` package for universal assertion/validation: - [ ] Integrate into Manager.Tick() - [ ] Test: dedup check/mark, window expiry -### 13.2 Semantic Dedup +### 12.3 Semantic Dedup - [ ] `dedup/semantic.go` - call Dedup Agent for goal/task level dedup - [ ] Dedup Agent setup (`assistants/robot/dedup/`) - [ ] Test: semantic dedup with real LLM -### 13.3 Plan Queue +### 12.4 Plan Queue - [ ] `plan/plan.go` - plan queue implementation - [ ] Store planned tasks/goals @@ -1100,13 +1239,15 @@ func TestWithLLM(t *testing.T) { | 7. P1 Goals | ✅ | Goal Generation Agent integration | | 8. P2 Tasks | ✅ | Task Planning Agent integration | | 9. P3 Run | ✅ | Task execution + validation + yao/assert + multi-turn conversation | -| 10. P4 Delivery | ⬜ | Output delivery (email/file/webhook/notify) | -| 11. P5 Learning | ⬜ | Learning Agent + KB save | -| 12. API & Integration | ⬜ | Complete API, end-to-end tests | -| 13. Advanced | ⬜ | Semantic dedup, plan queue, Sandbox mode (requires container infrastructure) | +| 10. P4 Delivery | ⬜ | Output delivery (email/webhook, notify future) | +| 11. API & Integration | ⬜ | Complete API, end-to-end tests (main flow: P0→P1→P2→P3→P4) | +| 12. Advanced | ⬜ | P5 Learning, dedup, plan queue, Sandbox mode | Legend: ⬜ Not started | 🟡 In progress | ✅ Complete +**Main Flow (MVP):** P0 Inspiration → P1 Goals → P2 Tasks → P3 Run → P4 Delivery +**Advanced (Optional):** P5 Learning (async), Dedup, Plan Queue, Sandbox + --- ## Quick Commands