Nudge workflow stages that end a turn without finish_stage

This commit is contained in:
Ian Gulliver
2026-10-20 22:04:18 -07:00
parent 57324fe9a2
commit 0877cecab0
3 changed files with 34 additions and 3 deletions
+28 -1
View File
@@ -11,6 +11,7 @@ import (
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/mcp"
"g.fc.run/ai/byre/internal/store"
"g.fc.run/ai/byre/internal/workflow"
)
const readyPromptAsk = "If it isn't ready (work remains that you mean to do, or you're waiting on the human), call neither: say what's left in one line and end your turn. " +
@@ -24,6 +25,17 @@ const readyPromptUnchanged = "Your turn has ended, and this task's branch has no
"If the task is finished, or shouldn't go further (it needed no changes, the approach failed, or it was already done), call suggest_discard with your reasoning, then end your turn with one line. " +
readyPromptAsk
func finishStagePrompt(frozen store.FrozenWorkflow, stage string) string {
waiting := "the workflow can't finish"
next := workflow.Next(frozen.Definition, stage)
if len(next) > 0 {
waiting = fmt.Sprintf("%s can't start", quoteList(next))
}
return fmt.Sprintf("Your turn has ended and the review of your latest work has finished, but you haven't called finish_stage, so stage %q of workflow %q hasn't finished and %s. "+
"If the stage's work is done, call finish_stage with its output, then end your turn with one line. If work remains, do it now and then call finish_stage. "+
"If you're waiting on the human for a decision or input, ask it with ask_human and end your turn.", stage, frozen.Name, waiting)
}
type readyArgs struct {
Overview string `json:"overview"`
Merge string `json:"merge"`
@@ -157,11 +169,23 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
if task.Status != api.TaskStatusActive || !readyState(task.State) {
return nil
}
stagePrompt := ""
if task.WorkflowStage != nil {
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
if err != nil || !finished {
if err != nil {
return err
}
if !finished {
output, _, err := m.store.StageResult(ctx, taskID)
if err != nil || output != "" {
return err
}
frozen, err := m.store.TaskWorkflow(ctx, taskID)
if err != nil {
return err
}
stagePrompt = finishStagePrompt(frozen, *task.WorkflowStage)
}
}
sess, err = m.store.GetSession(ctx, sess.Id)
if err != nil || sess.State == api.AgentSessionStateRunning || m.interrupted(sess.Id) {
@@ -215,6 +239,9 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
if len(reviews) == 1 && reviews[0].State == api.ReviewStateSkipped {
prompt = readyPromptUnchanged
}
if stagePrompt != "" {
prompt = stagePrompt
}
_, err = m.Send(ctx, sess.Id, "byre", prompt, "", "")
if err != nil {
return err