From 1ac50d97e1e95db73f4c6d49aedea09b5473e63e Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 13 Aug 2026 11:59:11 +0200 Subject: [PATCH] agent: drain FIFO after interrupted/failed turns Fix holes where queued prompts were orphaned: - After turn loop exits (interrupt, toolsrv unavailable), drain remaining FIFO items - On panic recovery, pop next FIFO item and re-submit in a new goroutine - fifo file write triggers Submit when agent is idle --- cmd/olliesrv/internal/agent/turn.go | 16 ++++++++++++++++ cmd/olliesrv/internal/fs/spec.go | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/cmd/olliesrv/internal/agent/turn.go b/cmd/olliesrv/internal/agent/turn.go index 826dc19..2749a36 100644 --- a/cmd/olliesrv/internal/agent/turn.go +++ b/cmd/olliesrv/internal/agent/turn.go @@ -30,6 +30,11 @@ func (ag *Agent) Submit(ctx context.Context, input string) { } ag.SetState("idle") ag.emit(Event{Role: "error", Content: fmt.Sprintf("%v", r)}) + // Re-submit next FIFO item in a new goroutine so queued + // prompts aren't orphaned by the panic. + if next, ok := ag.fifo.Pop(); ok { + go ag.Submit(ctx, next) + } } }() ag.log.Debug("Agent.Submit() input_len=%d running=%v", len(input), ag.IsRunning()) @@ -63,6 +68,17 @@ func (ag *Agent) Submit(ctx context.Context, input string) { for input != "" && ctx.Err() == nil { input = ag.executeTurn(ctx, input) } + // Drain any remaining FIFO items (covers cases where executeTurn + // returned "" early: interrupt, toolsrv unavailable, etc.). + for ctx.Err() == nil { + next, ok := ag.fifo.Pop() + if !ok { + break + } + for next != "" && ctx.Err() == nil { + next = ag.executeTurn(ctx, next) + } + } } // executeTurn runs a single agent turn and returns the next prompt to execute, diff --git a/cmd/olliesrv/internal/fs/spec.go b/cmd/olliesrv/internal/fs/spec.go index 09aedb5..6cb89bb 100644 --- a/cmd/olliesrv/internal/fs/spec.go +++ b/cmd/olliesrv/internal/fs/spec.go @@ -373,6 +373,15 @@ func buildAgentChildren(a *agent.Agent, s *session.Session) []virtfs.FsNodeDecl return nil } a.Queue(input) + // If the agent is idle, trigger processing. + if !a.IsRunning() { + go func() { + if next, ok := a.PopQueue(); ok { + a.Submit(s.Ctx, next) + a.EnsureTrailingNewline() + } + }() + } return nil }), virtfs.Read(func() ([]byte, error) {