Ask each last workflow stage to merge as soon as it finishes
This commit is contained in:
@@ -115,7 +115,7 @@ func (m *Manager) auditError(ctx context.Context, task api.Task, msg string) {
|
|||||||
func auditBrief(branch, commit string) string {
|
func auditBrief(branch, commit string) string {
|
||||||
return fmt.Sprintf("Audit of %s at %s. This isn't a review of a change: check the whole codebase as it stands against the project's standards and expectations, and fix what falls short. "+
|
return fmt.Sprintf("Audit of %s at %s. This isn't a review of a change: check the whole codebase as it stands against the project's standards and expectations, and fix what falls short. "+
|
||||||
"byre runs this task in the background: the human doesn't see it until you ask a question, call ready_for_merge or hit an error. "+
|
"byre runs this task in the background: the human doesn't see it until you ask a question, call ready_for_merge or hit an error. "+
|
||||||
"Finish your stage with finish_stage as usual, even when you found nothing. Once the workflow has finished, byre asks whether the task is ready to merge; "+
|
"Finish your stage with finish_stage as usual, even when you found nothing. Once your stage has finished and its review has come back, byre asks whether the task is ready to merge;"+
|
||||||
"if nothing needed changing, answer with suggest_discard, and byre discards the task without bothering the human.", branch, commit)
|
"if nothing needed changing, answer with suggest_discard, and byre discards the task without bothering the human.", branch, commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,12 +129,12 @@ func (m *Manager) dismissAudit(taskID string) {
|
|||||||
if !task.Background || task.Status != api.TaskStatusActive {
|
if !task.Background || task.Status != api.TaskStatusActive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
output, _, err := m.store.StageResult(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("dismiss audit", "task", taskID, "err", err)
|
slog.Error("dismiss audit", "task", taskID, "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !finished {
|
if output == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = m.Discard(ctx, task, "byre")
|
_, err = m.Discard(ctx, task, "byre")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const leadGuidance = `You are the human's lead agent in byre, shown in the left
|
|||||||
- Dig into context with list_tasks, read_transcript, read_inbox and read_status (vendor rate limits and agent health), and with your own Read, Grep and Bash on a task's worktree path (from list_tasks). Don't edit files in task worktrees; ask the task's agent with send_message.
|
- Dig into context with list_tasks, read_transcript, read_inbox and read_status (vendor rate limits and agent health), and with your own Read, Grep and Bash on a task's worktree path (from list_tasks). Don't edit files in task worktrees; ask the task's agent with send_message.
|
||||||
- You can create tasks, start TODO items, message, interrupt and compact task agents, and merge or discard tasks. Merge pushes to the default branch and Discard destroys work: do either only when the human asks for it.
|
- You can create tasks, start TODO items, message, interrupt and compact task agents, and merge or discard tasks. Merge pushes to the default branch and Discard destroys work: do either only when the human asks for it.
|
||||||
- Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine.
|
- Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine.
|
||||||
- Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. A workflow is a tree of steps run in order, each a stage, an order or a parallel; every stage runs as its own task on the stage's model once the step before it has finished, so stages fan out (a parallel after a stage) and fan in (a stage after a parallel); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and the workflow finishes once every stage has; byre then discards the run's other stage tasks, leaving the task of each last stage (one per branch when it ends in a parallel) to merge. Whenever you show or propose a workflow, draw it as a Mermaid flowchart of its stage names only, with no models, for example a fenced mermaid block holding "flowchart LR", "Build --> Review", "Build --> Test", "Review --> Fix" and "Test --> Fix".
|
- Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. A workflow is a tree of steps run in order, each a stage, an order or a parallel; every stage runs as its own task on the stage's model once the step before it has finished, so stages fan out (a parallel after a stage) and fan in (a stage after a parallel); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and the workflow finishes once every stage has; byre then discards the run's other stage tasks, leaving the task of each last stage (one per branch when it ends in a parallel) to merge. Each last stage's agent is asked whether its task is ready to merge as soon as its own stage has finished and been reviewed, without waiting for the other last stages. Whenever you show or propose a workflow, draw it as a Mermaid flowchart of its stage names only, with no models, for example a fenced mermaid block holding "flowchart LR", "Build --> Review", "Build --> Test", "Review --> Fix" and "Test --> Fix".
|
||||||
- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/tskDa7e4K7305e). byre draws it in that task's color, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer or a finished turn, link to #/task/<task_id>/transcript.
|
- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/tskDa7e4K7305e). byre draws it in that task's color, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer or a finished turn, link to #/task/<task_id>/transcript.
|
||||||
- Keep messages short; the human reads them in half the screen. Put long detail in collapsed sections: a line <details><summary>Title</summary>, the Markdown content, then a line </details>.
|
- Keep messages short; the human reads them in half the screen. Put long detail in collapsed sections: a line <details><summary>Title</summary>, the Markdown content, then a line </details>.
|
||||||
- You are long-running. Your context window is large. byre sends you a signal once your context passes a quarter of its window; then call compact_self at a logical point (a piece of work wrapped up, a topic closed), and not before; when the turn ends, byre replaces you with a fresh session that starts from your handoff.
|
- You are long-running. Your context window is large. byre sends you a signal once your context passes a quarter of its window; then call compact_self at a logical point (a piece of work wrapped up, a topic closed), and not before; when the turn ends, byre replaces you with a fresh session that starts from your handoff.
|
||||||
@@ -366,12 +366,6 @@ func (m *Manager) Observe(events []api.Event) {
|
|||||||
if to, _ := ev.Payload["to"].(string); ev.Kind == "task.state" && readyState(api.TaskState(to)) && ev.TaskId != nil {
|
if to, _ := ev.Payload["to"].(string); ev.Kind == "task.state" && readyState(api.TaskState(to)) && ev.TaskId != nil {
|
||||||
go m.promptReady(*ev.TaskId, ev.Payload["from"])
|
go m.promptReady(*ev.TaskId, ev.Payload["from"])
|
||||||
}
|
}
|
||||||
if ev.Kind == "task.workflow_finished" && ev.TaskId != nil {
|
|
||||||
kept, _ := ev.Payload["kept"].([]string)
|
|
||||||
for _, id := range kept {
|
|
||||||
go m.promptReady(id, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case (ev.Kind == "task.merged" || ev.Kind == "task.merged_to_main" || ev.Kind == "task.discarded") && ev.ProjectId != nil:
|
case (ev.Kind == "task.merged" || ev.Kind == "task.merged_to_main" || ev.Kind == "task.discarded") && ev.ProjectId != nil:
|
||||||
go m.audit(*ev.ProjectId)
|
go m.audit(*ev.ProjectId)
|
||||||
|
|||||||
@@ -171,19 +171,22 @@ func (m *Manager) tryPromptReady(ctx context.Context, taskID string, fromReview
|
|||||||
}
|
}
|
||||||
stagePrompt := ""
|
stagePrompt := ""
|
||||||
if task.WorkflowStage != nil {
|
if task.WorkflowStage != nil {
|
||||||
|
frozen, err := m.store.TaskWorkflow(ctx, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
output, _, err := m.store.StageResult(ctx, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if output != "" && len(workflow.Next(frozen.Definition, *task.WorkflowStage)) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !finished {
|
if output == "" && !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)
|
stagePrompt = finishStagePrompt(frozen, *task.WorkflowStage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -689,9 +689,9 @@ func quoteList(names []string) string {
|
|||||||
func lastStageText(def api.WorkflowDefinition) string {
|
func lastStageText(def api.WorkflowDefinition) string {
|
||||||
ends := workflow.Ends(def)
|
ends := workflow.Ends(def)
|
||||||
if len(ends) == 1 {
|
if len(ends) == 1 {
|
||||||
return "This is the workflow's last stage: the workflow finishes when this turn ends, and byre discards its other stage tasks."
|
return "This is the workflow's last stage: the workflow finishes when this turn ends, byre discards its other stage tasks, and once the review of your latest work has come back, byre asks whether this task is ready to merge."
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("This is one of the workflow's last stages (%s): the workflow finishes once all of them have, and byre then discards its other stage tasks, keeping each last stage's task.", quoteList(ends))
|
return fmt.Sprintf("This is one of the workflow's last stages (%s): once the review of your latest work has come back, byre asks whether this task is ready to merge, without waiting for the others. The workflow finishes once all of them have, and byre then discards its other stage tasks, keeping each last stage's task.", quoteList(ends))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stageGuidance(frozen store.FrozenWorkflow, stage string) string {
|
func stageGuidance(frozen store.FrozenWorkflow, stage string) string {
|
||||||
|
|||||||
@@ -816,11 +816,7 @@ func (s *Store) ReadyPromptDue(ctx context.Context, id string) (bool, error) {
|
|||||||
WHERE t.id = ? AND e.task_id = t.id AND e.seq > t.ready_prompt_seq
|
WHERE t.id = ? AND e.task_id = t.id AND e.seq > t.ready_prompt_seq
|
||||||
AND ((e.kind = 'snapshot.created' AND json_extract(e.payload, '$.trigger') NOT IN (?, ?, ?))
|
AND ((e.kind = 'snapshot.created' AND json_extract(e.payload, '$.trigger') NOT IN (?, ?, ?))
|
||||||
OR (e.kind = 'user.message' AND e.actor != ? AND e.actor NOT LIKE '% (lead)' AND json_extract(e.payload, '$.bounce') IS NOT 1))
|
OR (e.kind = 'user.message' AND e.actor != ? AND e.actor NOT LIKE '% (lead)' AND json_extract(e.payload, '$.bounce') IS NOT 1))
|
||||||
) OR EXISTS (
|
)`, id, string(api.SnapshotTriggerAnswer), string(api.SnapshotTriggerFork), string(api.SnapshotTriggerAskHuman), agentActor).Scan(&due)
|
||||||
SELECT 1 FROM events e, tasks t, tasks r
|
|
||||||
WHERE t.id = ? AND t.workflow_run != '' AND r.workflow_run = t.workflow_run AND e.task_id = r.id
|
|
||||||
AND e.kind = 'task.workflow_finished' AND e.seq > t.ready_prompt_seq
|
|
||||||
)`, id, string(api.SnapshotTriggerAnswer), string(api.SnapshotTriggerFork), string(api.SnapshotTriggerAskHuman), agentActor, id).Scan(&due)
|
|
||||||
return due, err
|
return due, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func (s *Store) RunEvents(ctx context.Context, run string) ([]api.Event, error)
|
|||||||
|
|
||||||
func (s *Store) RecordStage(ctx context.Context, taskID, output, commit string) error {
|
func (s *Store) RecordStage(ctx context.Context, taskID, output, commit string) error {
|
||||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||||
_, err := tx.ExecContext(ctx, `UPDATE tasks SET stage_output = ?, stage_commit = ? WHERE id = ?`, output, commit, taskID)
|
_, err := tx.ExecContext(ctx, `UPDATE tasks SET stage_output = ?, stage_commit = ?, ready_prompt_seq = 0 WHERE id = ?`, output, commit, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user