package agents import ( "context" "fmt" "log/slog" "strings" "g.fc.run/ai/byre/internal/api" "g.fc.run/ai/byre/internal/git" ) func (m *Manager) SetAuditWorkflow(ctx context.Context, projectID, name, actor string) (api.Project, error) { name = strings.TrimSpace(name) if name != "" { _, err := m.store.GetWorkflow(ctx, name) if err != nil { return api.Project{}, err } project, err := m.store.GetProject(ctx, projectID) if err != nil { return api.Project{}, err } creator, err := m.store.GetUser(ctx, project.CreatedBy) if err != nil { return api.Project{}, err } if creator.GitName == "" || creator.GitEmail == "" { return api.Project{}, fmt.Errorf("%w: audits run as the project's creator %s, who has no git identity", ErrInvalid, creator.Login) } } p, err := m.store.SetAuditWorkflow(ctx, projectID, name, actor) if err != nil { return api.Project{}, err } go m.audit(p.Id) return p, nil } func (m *Manager) auditAll(ctx context.Context) { projects, err := m.store.ListProjects(ctx) if err != nil { slog.Error("start audits", "err", err) return } for _, p := range projects { tasks, err := m.store.ListTasks(ctx, p.Id) if err != nil { slog.Error("start audits", "project", p.Id, "err", err) continue } for _, t := range tasks { if t.Status == api.TaskStatusActive && t.Background && t.State == api.TaskStateDiscardSuggested { m.dismissAudit(t.Id) } } m.audit(p.Id) } } func (m *Manager) audit(projectID string) { err := m.startAudit(context.Background(), projectID) if err != nil { slog.Error("start audit", "project", projectID, "err", err) } } func (m *Manager) startAudit(ctx context.Context, projectID string) error { m.auditMu.Lock() defer m.auditMu.Unlock() project, err := m.store.GetProject(ctx, projectID) if err != nil || project.AuditWorkflow == "" { return err } last, active, err := m.store.LastAudit(ctx, projectID) if err != nil || active { return err } head, err := git.BranchHead(project.RepoPath, project.DefaultBranch) if err != nil { return err } if head == last { return nil } creator, err := m.store.GetUser(ctx, project.CreatedBy) if err != nil { return err } tasks, err := m.StartWorkflow(ctx, project, creator, "Audit of "+short(head), head, "byre", project.AuditWorkflow, auditBrief(project.DefaultBranch, head), nil, head) if err == nil { slog.Info("audit started", "project", projectID, "commit", head, "workflow", project.AuditWorkflow) return nil } for _, t := range tasks { m.auditError(ctx, t, fmt.Sprintf("The audit of %s didn't start: %v. Discard this task to let audits continue; the next commit on %s starts one.", short(head), err, project.DefaultBranch)) } return err } func (m *Manager) auditError(ctx context.Context, task api.Task, msg string) { err := m.store.AppendEvent(ctx, api.Event{ Kind: "session.error", ProjectId: &task.ProjectId, TaskId: &task.Id, Actor: "byre", Payload: map[string]any{"error": msg}, }) if err != nil { slog.Error("record audit error", "task", task.Id, "err", err) } } 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. "+ "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; "+ "if nothing needed changing, answer with suggest_discard, and byre discards the task without bothering the human.", branch, commit) } func (m *Manager) dismissAudit(taskID string) { ctx := context.Background() task, err := m.store.GetTask(ctx, taskID) if err != nil { slog.Error("dismiss audit", "task", taskID, "err", err) return } if !task.Background || task.Status != api.TaskStatusActive { return } finished, err := m.store.WorkflowFinished(ctx, *task.WorkflowRun) if err != nil { slog.Error("dismiss audit", "task", taskID, "err", err) return } if !finished { return } _, err = m.Discard(ctx, task, "byre") if err != nil { slog.Error("dismiss audit", "task", taskID, "err", err) return } slog.Info("audit dismissed itself", "task", taskID) }