Run each workflow stage as its own task, with fan-out and fan-in
This commit is contained in:
+268
-104
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
"g.fc.run/misc/byre/internal/git"
|
||||
"g.fc.run/misc/byre/internal/mcp"
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
"g.fc.run/misc/byre/internal/workflow"
|
||||
@@ -51,65 +52,107 @@ func (m *Manager) TaskWorkflow(ctx context.Context, taskID string) (api.TaskWork
|
||||
|
||||
var errModelWithWorkflow = fmt.Errorf("%w: model can't be combined with workflow: each stage names its own model", ErrInvalid)
|
||||
|
||||
func (m *Manager) freeze(ctx context.Context, name, brief string) (*store.FrozenWorkflow, error) {
|
||||
if name == "" {
|
||||
return nil, nil
|
||||
type parentStage struct {
|
||||
task api.Task
|
||||
output string
|
||||
commit string
|
||||
}
|
||||
|
||||
func (m *Manager) StartWorkflow(ctx context.Context, project api.Project, creator api.User, title, base, actor, name, brief string, todoItem *string) ([]api.Task, error) {
|
||||
if creator.GitName == "" || creator.GitEmail == "" {
|
||||
return nil, ErrNoGitIdentity
|
||||
}
|
||||
frozen, err := m.store.FreezeWorkflow(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frozen.Brief = brief
|
||||
return &frozen, nil
|
||||
frozen.Title = title
|
||||
|
||||
run := store.NewID()
|
||||
tasks := []api.Task{}
|
||||
for _, stage := range workflow.Starts(frozen.Definition) {
|
||||
from := base
|
||||
if workflow.FromMain(frozen.Definition, stage) {
|
||||
from = project.DefaultBranch
|
||||
}
|
||||
task, err := m.startStage(ctx, project, creator, frozen, run, stage, from, todoItem, nil, actor)
|
||||
if err != nil {
|
||||
return tasks, fmt.Errorf("workflow %q: stage %q did not start: %w", name, stage, err)
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func stageMessage(frozen store.FrozenWorkflow, n int, previous string) string {
|
||||
stages := frozen.Definition.Stages
|
||||
stage := stages[n-1]
|
||||
|
||||
b := &strings.Builder{}
|
||||
fmt.Fprintf(b, "Workflow %q, stage %d of %d: %s\n\n%s", frozen.Name, n, len(stages), stage.Name, strings.TrimSpace(stage.Prompt))
|
||||
if strings.TrimSpace(frozen.Brief) != "" {
|
||||
fmt.Fprintf(b, "\n\n## Brief\n\n%s", strings.TrimSpace(frozen.Brief))
|
||||
func (m *Manager) startStage(ctx context.Context, project api.Project, creator api.User, frozen store.FrozenWorkflow, run, stage, base string, todoItem *string, parents []parentStage, actor string) (api.Task, error) {
|
||||
task, err := m.createTask(ctx, project, api.Task{
|
||||
Title: fmt.Sprintf("%s: %s", frozen.Title, stage),
|
||||
MergeTitle: frozen.Title,
|
||||
CreatedBy: creator.Login,
|
||||
TodoItem: todoItem,
|
||||
WorkflowRun: &run,
|
||||
WorkflowStage: &stage,
|
||||
}, &frozen, base, actor)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
if n > 1 {
|
||||
fmt.Fprintf(b, "\n\n## From the previous stage (%s)\n\n%s", stages[n-2].Name, strings.TrimSpace(previous))
|
||||
_, err = m.beginStage(ctx, task, project, creator, frozen, parents, actor)
|
||||
if err != nil {
|
||||
return task, fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err)
|
||||
}
|
||||
return b.String()
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (m *Manager) startStage(ctx context.Context, task api.Task, creator api.User, frozen store.FrozenWorkflow, n int, previous, actor string) (api.AgentSession, error) {
|
||||
stage := frozen.Definition.Stages[n-1]
|
||||
func (m *Manager) beginStage(ctx context.Context, task api.Task, project api.Project, creator api.User, frozen store.FrozenWorkflow, parents []parentStage, actor string) (api.AgentSession, error) {
|
||||
stage := *task.WorkflowStage
|
||||
after := []string{}
|
||||
for _, p := range parents {
|
||||
after = append(after, p.task.Id)
|
||||
}
|
||||
err := m.store.AppendEvent(ctx, api.Event{
|
||||
Kind: "task.workflow_stage",
|
||||
ProjectId: &task.ProjectId,
|
||||
TaskId: &task.Id,
|
||||
Actor: actor,
|
||||
Payload: map[string]any{"id": task.Id, "workflow": frozen.Name, "stage": n, "stages": len(frozen.Definition.Stages), "name": stage.Name},
|
||||
Payload: map[string]any{"id": task.Id, "workflow": frozen.Name, "run": *task.WorkflowRun, "stage": stage, "after": after},
|
||||
})
|
||||
if err != nil {
|
||||
return api.AgentSession{}, err
|
||||
}
|
||||
return m.createAt(ctx, task, creator, stageMessage(frozen, n, previous), stage.Model, "byre", nil, n)
|
||||
return m.create(ctx, task, creator, stageMessage(frozen, stage, parents, project.DefaultBranch), workflow.Get(frozen.Definition, stage).Model, "byre", nil)
|
||||
}
|
||||
|
||||
func (m *Manager) startWorkflow(ctx context.Context, task api.Task, creator api.User, actor string) error {
|
||||
frozen, err := m.store.TaskWorkflow(ctx, task.Id)
|
||||
if err == nil {
|
||||
_, err = m.startStage(ctx, task, creator, frozen, 1, "", actor)
|
||||
func stageMessage(frozen store.FrozenWorkflow, stage string, parents []parentStage, defaultBranch string) string {
|
||||
b := &strings.Builder{}
|
||||
fmt.Fprintf(b, "Workflow %q, stage %q\n\n%s", frozen.Name, stage, strings.TrimSpace(workflow.Get(frozen.Definition, stage).Prompt))
|
||||
if strings.TrimSpace(frozen.Brief) != "" {
|
||||
fmt.Fprintf(b, "\n\n## Brief\n\n%s", strings.TrimSpace(frozen.Brief))
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("task %s created, but its workflow did not start: %w", task.Id, err)
|
||||
for _, p := range parents {
|
||||
fmt.Fprintf(b, "\n\n## From stage %q (task %s, branch %s)\n\n%s", *p.task.WorkflowStage, p.task.Id, p.task.Branch, strings.TrimSpace(p.output))
|
||||
}
|
||||
return nil
|
||||
switch {
|
||||
case len(parents) == 0:
|
||||
case workflow.FromMain(frozen.Definition, stage):
|
||||
fmt.Fprintf(b, "\n\nThis task starts from %s. The branch of each stage above holds that stage's work: `git merge` the ones this stage needs, or read them with `git show` and `git diff`.", defaultBranch)
|
||||
default:
|
||||
fmt.Fprintf(b, "\n\nThis task starts from the commit stage %q finished at, so its work is already in this worktree.", *parents[0].task.WorkflowStage)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m *Manager) advanceStage(l *live) {
|
||||
_, err := m.nextStage(context.Background(), l.id)
|
||||
ctx := context.Background()
|
||||
_, err := m.nextStage(ctx, l.id)
|
||||
if err != nil {
|
||||
slog.Error("advance workflow stage", "session", l.id, "err", err)
|
||||
m.fileError(l, fmt.Sprintf("The workflow didn't move on from this stage: %v. byre tries again when it next starts.", err))
|
||||
}
|
||||
err = m.Deliver(ctx, l.id)
|
||||
if err != nil {
|
||||
slog.Error("deliver inbox responses", "session", l.id, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) recoverStages(ctx context.Context) (map[string]bool, error) {
|
||||
@@ -124,133 +167,218 @@ func (m *Manager) recoverStages(ctx context.Context) (map[string]bool, error) {
|
||||
slog.Error("advance workflow stage", "session", id, "err", err)
|
||||
continue
|
||||
}
|
||||
if next != "" {
|
||||
started[next] = true
|
||||
for _, n := range next {
|
||||
started[n] = true
|
||||
}
|
||||
slog.Info("advanced workflow stage", "session", id, "next", next)
|
||||
}
|
||||
return started, nil
|
||||
}
|
||||
|
||||
func (m *Manager) nextStage(ctx context.Context, sessionID string) (string, error) {
|
||||
func (m *Manager) nextStage(ctx context.Context, sessionID string) ([]string, error) {
|
||||
sess, err := m.store.GetSession(ctx, sessionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
task, err := m.store.GetTask(ctx, sess.TaskId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if task.Status != api.TaskStatusActive {
|
||||
return "", m.store.ClearStage(ctx, sess.Id)
|
||||
return nil, m.store.ClearStage(ctx, sess.Id)
|
||||
}
|
||||
frozen, err := m.store.TaskWorkflow(ctx, task.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
output, err := m.store.StageOutput(ctx, sess.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
recorded, _, err := m.store.StageResult(ctx, task.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if recorded == "" {
|
||||
head, err := git.Head(task.WorktreePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = m.store.RecordStage(ctx, task.Id, output, head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
next := *sess.WorkflowStage + 1
|
||||
if next > len(frozen.Definition.Stages) {
|
||||
return "", m.store.FinishWorkflow(ctx, task, sess.Id, frozen.Name, output)
|
||||
m.workflowMu.Lock()
|
||||
defer m.workflowMu.Unlock()
|
||||
|
||||
next := workflow.Next(frozen.Definition, *task.WorkflowStage)
|
||||
if len(next) == 0 {
|
||||
err = m.discardStages(ctx, task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, m.store.FinishWorkflow(ctx, task, sess.Id, frozen.Name, output)
|
||||
}
|
||||
|
||||
creator, err := m.store.GetUser(ctx, sess.CreatedBy)
|
||||
started := []string{}
|
||||
for _, n := range next {
|
||||
id, err := m.advanceTo(ctx, task, frozen, n)
|
||||
if err != nil {
|
||||
return started, err
|
||||
}
|
||||
if id != "" {
|
||||
started = append(started, id)
|
||||
}
|
||||
}
|
||||
return started, m.store.ClearStage(ctx, sess.Id)
|
||||
}
|
||||
|
||||
func (m *Manager) advanceTo(ctx context.Context, from api.Task, frozen store.FrozenWorkflow, stage string) (string, error) {
|
||||
tasks, err := m.store.RunTasks(ctx, *from.WorkflowRun)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if sess.State != api.AgentSessionStateCompleted {
|
||||
m.logUndelivered(ctx, sess.Id, "stage finished")
|
||||
err = m.retire(ctx, sess, "byre")
|
||||
parents := []parentStage{}
|
||||
for _, a := range workflow.After(frozen.Definition, stage) {
|
||||
p, found, err := m.finishedStage(ctx, tasks, a)
|
||||
if err != nil || !found {
|
||||
return "", err
|
||||
}
|
||||
parents = append(parents, p)
|
||||
}
|
||||
project, err := m.store.GetProject(ctx, from.ProjectId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
creator, err := m.store.GetUser(ctx, from.CreatedBy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
existing, found := latestStage(tasks, stage)
|
||||
if found {
|
||||
if existing.Status != api.TaskStatusActive {
|
||||
return "", nil
|
||||
}
|
||||
return m.resumeStage(ctx, existing, project, creator, frozen, parents)
|
||||
}
|
||||
|
||||
base := parents[0].commit
|
||||
if workflow.FromMain(frozen.Definition, stage) {
|
||||
base = project.DefaultBranch
|
||||
}
|
||||
task, err := m.startStage(ctx, project, creator, frozen, *from.WorkflowRun, stage, base, from.TodoItem, parents, "byre")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sessions, err := m.store.ListSessions(ctx, task.Id)
|
||||
if err != nil || len(sessions) == 0 {
|
||||
return "", err
|
||||
}
|
||||
return sessions[0].Id, nil
|
||||
}
|
||||
|
||||
func latestStage(tasks []api.Task, stage string) (api.Task, bool) {
|
||||
for i := len(tasks) - 1; i >= 0; i-- {
|
||||
if *tasks[i].WorkflowStage == stage {
|
||||
return tasks[i], true
|
||||
}
|
||||
}
|
||||
return api.Task{}, false
|
||||
}
|
||||
|
||||
func (m *Manager) finishedStage(ctx context.Context, tasks []api.Task, stage string) (parentStage, bool, error) {
|
||||
for i := len(tasks) - 1; i >= 0; i-- {
|
||||
if *tasks[i].WorkflowStage != stage {
|
||||
continue
|
||||
}
|
||||
output, commit, err := m.store.StageResult(ctx, tasks[i].Id)
|
||||
if err != nil {
|
||||
return parentStage{}, false, err
|
||||
}
|
||||
if output != "" {
|
||||
return parentStage{task: tasks[i], output: output, commit: commit}, true, nil
|
||||
}
|
||||
}
|
||||
return parentStage{}, false, nil
|
||||
}
|
||||
|
||||
func (m *Manager) resumeStage(ctx context.Context, task api.Task, project api.Project, creator api.User, frozen store.FrozenWorkflow, parents []parentStage) (string, error) {
|
||||
sessions, err := m.store.ListSessions(ctx, task.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
sess, err := m.beginStage(ctx, task, project, creator, frozen, parents, "byre")
|
||||
return sess.Id, err
|
||||
}
|
||||
sess := sessions[0]
|
||||
started, err := m.store.SessionStarted(ctx, sess.Id)
|
||||
if err != nil || started || sess.State == api.AgentSessionStateCompleted {
|
||||
return "", err
|
||||
}
|
||||
if sess.State == api.AgentSessionStateRunning {
|
||||
err = m.store.SetSessionState(ctx, sess.Id, api.AgentSessionStateIdle, "byre", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
successor, found, err := m.successor(ctx, sess, next)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sent := true
|
||||
switch {
|
||||
case !found:
|
||||
successor, err = m.startStage(ctx, task, creator, frozen, next, output, "byre")
|
||||
default:
|
||||
sent, err = m.resendStage(ctx, successor, task, frozen, next, output)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = m.store.ClearStage(ctx, sess.Id)
|
||||
if err != nil || !sent {
|
||||
return "", err
|
||||
}
|
||||
return successor.Id, nil
|
||||
_, err = m.turn(ctx, sess, task, "byre", stageMessage(frozen, *task.WorkflowStage, parents, project.DefaultBranch), nil)
|
||||
return sess.Id, err
|
||||
}
|
||||
|
||||
func (m *Manager) successor(ctx context.Context, sess api.AgentSession, n int) (api.AgentSession, bool, error) {
|
||||
sessions, err := m.store.ListSessions(ctx, sess.TaskId)
|
||||
func (m *Manager) discardStages(ctx context.Context, final api.Task) error {
|
||||
tasks, err := m.store.RunTasks(ctx, *final.WorkflowRun)
|
||||
if err != nil {
|
||||
return api.AgentSession{}, false, err
|
||||
return err
|
||||
}
|
||||
for _, s := range sessions {
|
||||
if s.CreatedAt.Before(sess.CreatedAt) || s.Id == sess.Id || s.WorkflowStage == nil || *s.WorkflowStage != n {
|
||||
project, err := m.store.GetProject(ctx, final.ProjectId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range tasks {
|
||||
if t.Id == final.Id || t.Status != api.TaskStatusActive {
|
||||
continue
|
||||
}
|
||||
return s, true, nil
|
||||
}
|
||||
return api.AgentSession{}, false, nil
|
||||
}
|
||||
|
||||
func (m *Manager) resendStage(ctx context.Context, successor api.AgentSession, task api.Task, frozen store.FrozenWorkflow, n int, previous string) (bool, error) {
|
||||
started, err := m.store.SessionStarted(ctx, successor.Id)
|
||||
if err != nil || started || successor.State == api.AgentSessionStateCompleted {
|
||||
return false, err
|
||||
}
|
||||
if successor.State == api.AgentSessionStateRunning {
|
||||
err = m.store.SetSessionState(ctx, successor.Id, api.AgentSessionStateIdle, "byre", nil)
|
||||
_, err = m.Discard(ctx, t, project, "byre")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return fmt.Errorf("discard stage task %s: %w", t.Id, err)
|
||||
}
|
||||
}
|
||||
_, err = m.turn(ctx, successor, task, "byre", stageMessage(frozen, n, previous), nil)
|
||||
return err == nil, err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) workflowStatus(ctx context.Context, taskID string) (string, error) {
|
||||
finished, err := m.store.WorkflowFinished(ctx, taskID)
|
||||
if err != nil || finished {
|
||||
return "finished", err
|
||||
}
|
||||
frozen, err := m.store.TaskWorkflow(ctx, taskID)
|
||||
func (m *Manager) workflowStatus(ctx context.Context, task api.Task) (string, error) {
|
||||
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sessions, err := m.store.ListSessions(ctx, taskID)
|
||||
output, _, err := m.store.StageResult(ctx, task.Id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(sessions) == 0 || sessions[len(sessions)-1].WorkflowStage == nil {
|
||||
return "not started", nil
|
||||
status := fmt.Sprintf("stage %q", *task.WorkflowStage)
|
||||
if output != "" {
|
||||
status += " (finished)"
|
||||
}
|
||||
n := *sessions[len(sessions)-1].WorkflowStage
|
||||
stages := frozen.Definition.Stages
|
||||
return fmt.Sprintf("stage %d/%d %s", n, len(stages), stages[n-1].Name), nil
|
||||
if finished {
|
||||
status += ", workflow finished"
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (m *Manager) finishStageTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "finish_stage",
|
||||
Description: "Finish your stage of this task's workflow. Call it once the stage's work is done; byre starts the next stage, in a fresh session, when this turn ends " +
|
||||
"(after the last stage, the workflow is finished). Calling it again in the same turn replaces the output. " +
|
||||
Description: "Finish this task's stage of its workflow. Call it once the stage's work is done; when this turn ends, byre hands the output to the stages that come after this one, " +
|
||||
"each of which starts in its own task once every stage it comes after has finished (after the final stage, the workflow is finished). Calling it again in the same turn replaces the output. " +
|
||||
"The call counts only for the turn that makes it: if that turn ends in an error or is interrupted, it is discarded, and you call it again once the stage's work is done.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"output": {"type": "string", "description": "What the next stage starts from: everything it needs from this stage, since it sees nothing else of your context. For the last stage, what the workflow produced."}
|
||||
"output": {"type": "string", "description": "What the stages after this one start from: everything they need from this stage, since they see nothing else of your context. For the final stage, what the workflow produced."}
|
||||
},
|
||||
"required": ["output"]
|
||||
}`),
|
||||
@@ -265,26 +393,62 @@ func (m *Manager) finishStageTool(l *live) mcp.Tool {
|
||||
if strings.TrimSpace(args.Output) == "" {
|
||||
return "", fmt.Errorf("%w: output is required", ErrInvalid)
|
||||
}
|
||||
recorded, _, err := m.store.StageResult(ctx, l.taskID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if recorded != "" {
|
||||
return "", fmt.Errorf("%w: stage %q already finished; its output went to the stages after it", ErrConflict, l.stage)
|
||||
}
|
||||
frozen, err := m.store.TaskWorkflow(ctx, l.taskID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = m.store.SetStageOutput(ctx, l.id, args.Output)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if l.stage == l.stages {
|
||||
return "This is the workflow's last stage: it finishes when this turn ends. End your turn now with your summary.", nil
|
||||
next := workflow.Next(frozen.Definition, l.stage)
|
||||
if len(next) == 0 {
|
||||
return "This is the workflow's final stage: it finishes when this turn ends, and byre discards the workflow's other stage tasks. End your turn now with your summary.", nil
|
||||
}
|
||||
return fmt.Sprintf("byre starts stage %d when this turn ends. End your turn now with your summary.", l.stage+1), nil
|
||||
return fmt.Sprintf("When this turn ends, byre hands the output to %s. End your turn now with your summary.", quoteList(next)), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func stageGuidance(frozen store.FrozenWorkflow, n int) string {
|
||||
return fmt.Sprintf("\n- This task runs byre workflow %q, and you are its stage %d of %d, %q. When this stage's work is done, call byre's finish_stage tool with its output, then end your turn: "+
|
||||
"byre then starts the next stage in a fresh session, which gets the output and nothing else of your context. Until you call it, the task stays in your stage.",
|
||||
frozen.Name, n, len(frozen.Definition.Stages), frozen.Definition.Stages[n-1].Name)
|
||||
func describeTasks(tasks []api.Task) string {
|
||||
parts := []string{}
|
||||
for _, t := range tasks {
|
||||
parts = append(parts, fmt.Sprintf("task %s %q from %s", t.Id, t.Title, t.BaseRef))
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
func quoteList(names []string) string {
|
||||
quoted := []string{}
|
||||
for _, n := range names {
|
||||
quoted = append(quoted, fmt.Sprintf("%q", n))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
func stageGuidance(frozen store.FrozenWorkflow, stage string) string {
|
||||
next := workflow.Next(frozen.Definition, stage)
|
||||
then := "it is the workflow's final stage, so the workflow then finishes and byre discards its other stage tasks"
|
||||
if len(next) > 0 {
|
||||
then = fmt.Sprintf("byre then hands the output to %s, each in its own task, which gets the output and nothing else of your context", quoteList(next))
|
||||
}
|
||||
return fmt.Sprintf("\n- This task runs stage %q of byre workflow %q. When this stage's work is done, call byre's finish_stage tool with its output, then end your turn: "+
|
||||
"%s. Until you call it, the workflow waits on this stage.", stage, frozen.Name, then)
|
||||
}
|
||||
|
||||
const workflowFileHelp = "A workflow's file is YAML: an optional description, then stages, each with name (one line, unique in the workflow), " +
|
||||
"after (the names of the stages whose outputs it gets; it starts once all of them have finished; absent means the previous stage in the list, and for the first stage none; [] means it starts from the brief), " +
|
||||
"base (parent, the default: branch from the commit its one after stage finished at, or for a stage with none, from where the workflow was started; main: branch from the default branch; " +
|
||||
"refused on a stage with more than one after stage, which always branches from the default branch and is told the branches it can merge), " +
|
||||
"model (which also picks the vendor: gpt-* models run on Codex, every other model on Claude Code) and prompt (the stage agent's instructions). " +
|
||||
"Every stage runs as its own task. Exactly one stage is final (no other stage comes after it), and cycles are refused. " +
|
||||
"The workflow's name is not in the file."
|
||||
|
||||
func (m *Manager) workflowTools(tool func(name, description, schema string, run func(ctx context.Context, user api.User, raw json.RawMessage) (string, error)) mcp.Tool) []mcp.Tool {
|
||||
|
||||
Reference in New Issue
Block a user