diff --git a/internal/agents/manager.go b/internal/agents/manager.go index 38b93ff..6a009af 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -35,6 +35,8 @@ const agentActor = "agent" const restartNotice = "byre restarted while you were mid-turn, so your last turn was cut off. Re-check the working tree (git status and the files you were editing) before continuing where you left off." +const subagentsLostNotice = "Your agent process ended while background subagents you started were still running, so they were stopped and will never report back. Anything they found is lost; redo that work (run them again or do it yourself) and carry on where you left off." + var progressSubtypes = map[string]bool{"status": true, "thinking_tokens": true} type Manager struct { @@ -456,11 +458,15 @@ func (m *Manager) Recover(ctx context.Context) error { if err != nil { return err } + orphaned := []string{} for _, sess := range stale { err = m.store.SetSubagents(ctx, sess.Id, 0, "byre") if err != nil { return err } + if sess.State == api.AgentSessionStateIdle { + orphaned = append(orphaned, sess.Id) + } } sessions, err := m.store.SessionsInState(ctx, api.AgentSessionStateRunning) @@ -488,6 +494,10 @@ func (m *Manager) Recover(ctx context.Context) error { } } + for _, id := range orphaned { + m.wakeOrphaned(ctx, id) + } + idle, err := m.store.SessionsInState(ctx, api.AgentSessionStateIdle) if err != nil { return err @@ -508,6 +518,15 @@ func (m *Manager) Recover(ctx context.Context) error { return nil } +func (m *Manager) wakeOrphaned(ctx context.Context, sessionID string) { + _, _, err := m.Message(ctx, sessionID, "byre", Prompt{Text: subagentsLostNotice}) + if err != nil { + slog.Error("wake session whose subagents were stopped", "session", sessionID, "err", err) + return + } + slog.Info("woke session whose subagents were stopped", "session", sessionID) +} + func (m *Manager) failResume(ctx context.Context, sess api.AgentSession, err error) error { slog.Error("resume session", "session", sess.Id, "err", err) if sess.Role == api.Reviewer { @@ -982,7 +1001,8 @@ func (m *Manager) pump(l *live) { quiet := closing || l.stopping m.mu.Unlock() - if len(l.subagents) > 0 && !closing { + orphaned := len(l.subagents) > 0 && !closing + if orphaned { err := m.store.SetSubagents(ctx, l.id, 0, "byre") if err != nil { slog.Error("clear exited session's subagents", "session", l.id, "err", err) @@ -1000,6 +1020,10 @@ func (m *Manager) pump(l *live) { return } + if sess.State == api.AgentSessionStateIdle && orphaned { + m.wakeOrphaned(ctx, l.id) + return + } if sess.State != api.AgentSessionStateRunning { return }