Run each workflow stage as its own task, with fan-out and fan-in

This commit is contained in:
Ian Gulliver
2026-10-15 22:53:54 -07:00
parent f7a0ffcd4c
commit 2180a7e824
15 changed files with 739 additions and 408 deletions
+8 -25
View File
@@ -58,6 +58,8 @@ type Manager struct {
reviewMu sync.Mutex
workflowMu sync.Mutex
bad *badLines
watchStop chan struct{}
}
@@ -74,8 +76,7 @@ type live struct {
projectID string
owner string
reviewID string
stage int
stages int
stage string
stopping bool
interrupted bool
asked bool
@@ -130,18 +131,6 @@ func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, p
}
func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, prompt, model, actor string, images []string) (api.AgentSession, error) {
existing, err := m.store.ListSessions(ctx, task.Id)
if err != nil {
return api.AgentSession{}, err
}
stage := 0
if len(existing) > 0 && existing[len(existing)-1].WorkflowStage != nil {
stage = *existing[len(existing)-1].WorkflowStage
}
return m.createAt(ctx, task, creator, prompt, model, actor, images, stage)
}
func (m *Manager) createAt(ctx context.Context, task api.Task, creator api.User, prompt, model, actor string, images []string, stage int) (api.AgentSession, error) {
if creator.GitName == "" || creator.GitEmail == "" {
return api.AgentSession{}, ErrNoGitIdentity
}
@@ -173,9 +162,6 @@ func (m *Manager) createAt(ctx context.Context, task api.Task, creator api.User,
CreatedBy: creator.Login,
CreatedAt: time.Now().UTC(),
}
if stage > 0 {
sess.WorkflowStage = &stage
}
err = m.store.CreateSession(ctx, sess, task.ProjectId)
if err != nil {
@@ -549,13 +535,12 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
return nil, err
}
tools := []mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.createTaskTool(l), m.setMergeTitleTool(l), m.stopMergeTool(l), m.exposePortTool(l)}
if sess.Role == api.Implementer && sess.WorkflowStage != nil {
if sess.Role == api.Implementer && task.WorkflowStage != nil {
frozen, err := m.store.TaskWorkflow(ctx, task.Id)
if err != nil {
return nil, err
}
l.stage = *sess.WorkflowStage
l.stages = len(frozen.Definition.Stages)
l.stage = *task.WorkflowStage
g += stageGuidance(frozen, l.stage)
tools = append(tools, m.finishStageTool(l))
}
@@ -629,7 +614,7 @@ func (m *Manager) pump(l *live) {
}
m.logUndelivered(ctx, l.id, "agent process exited mid-turn")
if l.stage > 0 {
if l.stage != "" {
err = m.store.DropStageOutput(ctx, l.id)
if err != nil {
slog.Error("drop stage output", "session", l.id, "err", err)
@@ -837,7 +822,7 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error {
stageFinished := false
switch {
case l.stage == 0:
case l.stage == "":
case finished && !handoff && mergingBy == "":
stageFinished, err = m.store.FinishStage(ctx, l.id)
default:
@@ -870,9 +855,7 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error {
}
if stageFinished {
go m.advanceStage(l)
if l.stage < l.stages {
return nil
}
return nil
}
err = m.Deliver(ctx, l.id)
if err != nil {