Run scheduled tasks through workflows

This commit is contained in:
Ian Gulliver
2026-10-22 13:38:40 -07:00
parent 9fca82abaa
commit 2524e1cb1f
9 changed files with 456 additions and 446 deletions
+15 -46
View File
@@ -20,20 +20,10 @@ func (m *Manager) SaveSchedule(ctx context.Context, projectID, scheduleID string
return api.Schedule{}, err
}
sc.Prompt = strings.TrimSpace(deref(save.Prompt))
sc.Model = strings.TrimSpace(deref(save.Model))
sc.Workflow = strings.TrimSpace(deref(save.Workflow))
switch {
case sc.Workflow != "" && sc.Model != "":
return api.Schedule{}, errModelWithWorkflow
case sc.Workflow == "" && sc.Prompt == "":
return api.Schedule{}, fmt.Errorf("%w: prompt is required without a workflow", ErrInvalid)
case sc.Workflow == "" && sc.Model == "":
return api.Schedule{}, fmt.Errorf("%w: model is required without a workflow", ErrInvalid)
case sc.Workflow != "":
_, err = m.store.GetWorkflow(ctx, sc.Workflow)
if err != nil {
return api.Schedule{}, err
}
sc.Workflow = strings.TrimSpace(save.Workflow)
_, err = m.store.GetWorkflow(ctx, sc.Workflow)
if err != nil {
return api.Schedule{}, err
}
if scheduleID != "" {
@@ -118,19 +108,8 @@ func (m *Manager) startScheduled(ctx context.Context, sc api.Schedule) error {
if user.GitName == "" || user.GitEmail == "" {
return ErrNoGitIdentity
}
if sc.Workflow != "" {
_, err = m.StartWorkflow(ctx, project, user, sc.Title, project.DefaultBranch, "byre", sc.Workflow, sc.Prompt, nil, "")
return err
}
task, err := m.CreateTask(ctx, project, sc.Title, user.Login, project.DefaultBranch, "byre")
if err != nil {
return err
}
_, err = m.create(ctx, task, user, "", sc.Prompt, sc.Model, "byre", "", "", nil)
if err != nil {
return fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
}
return nil
_, err = m.StartWorkflow(ctx, project, user, sc.Title, project.DefaultBranch, "byre", sc.Workflow, sc.Prompt, nil, "")
return err
}
func scheduleText(sc api.Schedule) string {
@@ -150,22 +129,17 @@ func scheduleText(sc api.Schedule) string {
default:
fmt.Fprintf(b, " at %02d:%02d", sc.Hour, sc.Minute)
}
fmt.Fprintf(b, ", next run %s", sc.NextRun.Local().Format(time.RFC3339))
if sc.Workflow != "" {
fmt.Fprintf(b, ", workflow %q", sc.Workflow)
} else {
fmt.Fprintf(b, ", model %s", sc.Model)
}
fmt.Fprintf(b, ", next run %s, workflow %q", sc.NextRun.Local().Format(time.RFC3339), sc.Workflow)
if sc.Prompt != "" {
fmt.Fprintf(b, "\n prompt: %s", clip(sc.Prompt, 500))
}
return b.String()
}
func (m *Manager) scheduleTools(l *live, tool func(name, description, schema string, run func(ctx context.Context, user api.User, raw json.RawMessage) (string, error)) mcp.Tool) []mcp.Tool {
func (m *Manager) scheduleTools(tool func(name, description, schema string, run func(ctx context.Context, user api.User, raw json.RawMessage) (string, error)) mcp.Tool) []mcp.Tool {
return []mcp.Tool{
tool("list_schedules",
"List the schedules: each one's id, project, owner, title, timing, model or workflow, prompt, and next run in the server's local time. Give project_id for one project's.",
"List the schedules: each one's id, project, owner, title, timing, workflow, prompt, and next run in the server's local time. Give project_id for one project's.",
`{"type": "object", "properties": {"project_id": {"type": "string"}}}`,
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
args := struct {
@@ -190,25 +164,24 @@ func (m *Manager) scheduleTools(l *live, tool func(name, description, schema str
}),
tool("save_schedule",
"Create a schedule, or with schedule_id replace one's settings (project_id is then ignored). On each run byre creates a task in the project from its default branch, as create_task does, "+
"as the schedule's owner (you, for one you create), and you get its created signal. Times are the server's local time. "+
"Create a schedule, or with schedule_id replace one's settings (project_id is then ignored). On each run byre starts the workflow in the project from its default branch, as create_task with workflow does, "+
"as the schedule's owner (you, for one you create). Times are the server's local time. "+
"every hour runs at minute past each hour (hour 0); day at hour:minute; week at hour:minute on each of weekdays; month at hour:minute on day_of_month, or the month's last day when shorter. "+
"weekdays only for week, day_of_month only for month. Each run's agent sees nothing else, so the prompt must be a complete brief. "+
"With workflow, each run starts that workflow, prompt is optional and is its brief, and model can't be given; the workflow is looked up by name at each run. "+
"weekdays only for week, day_of_month only for month. prompt is optional and is added to every stage's first message as the run's brief. "+
"The workflow is looked up by name at each run. "+
"A run missed while byre was down happens once when it starts. The human sees and edits schedules on the project's dashboard. Only when the human asks.",
`{"type": "object", "properties": {
"project_id": {"type": "string"},
"schedule_id": {"type": "string"},
"title": {"type": "string", "description": "The title of each task it creates."},
"title": {"type": "string", "description": "The title of each run."},
"every": {"type": "string", "enum": ["hour", "day", "week", "month"]},
"weekdays": {"type": "array", "items": {"type": "string", "enum": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]}},
"hour": {"type": "integer", "minimum": 0, "maximum": 23},
"minute": {"type": "integer", "minimum": 0, "maximum": 59},
"day_of_month": {"type": "integer", "minimum": 1, "maximum": 31},
"prompt": {"type": "string"},
"model": {"type": "string", "description": "Without workflow, the agent's model; defaults to yours."},
"workflow": {"type": "string", "description": "The name of a workflow for each run to freeze a copy of and run."}
}, "required": ["title", "every", "hour", "minute"]}`,
}, "required": ["title", "every", "hour", "minute", "workflow"]}`,
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
args := struct {
ProjectID string `json:"project_id"`
@@ -223,10 +196,6 @@ func (m *Manager) scheduleTools(l *live, tool func(name, description, schema str
if err != nil {
return "", err
}
if deref(save.Workflow) == "" {
model := m.modelOr(deref(save.Model), l)
save.Model = &model
}
sc, err := m.SaveSchedule(ctx, args.ProjectID, args.ScheduleID, save, user.Login, leadActor(user.Login))
if err != nil {
return "", err