p9: queue steps via agent.Core.Queue; yield message on plan

- queuePlanBackend now holds agent.Core and calls Queue() directly
  instead of writing to a filesystem path (enqueue is a virtual file)
- fallback.core wired after NewAgentCore in createSession
- Returns yield message so agent stops after planning and lets the
  queue drive execution step by step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-11 17:24:47 +02:00
parent 8fa4a8e101
commit 8070238390
2 changed files with 16 additions and 12 deletions

View File

@ -3,30 +3,30 @@ package p9
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"ollie/pkg/agent"
"ollie/pkg/tools" "ollie/pkg/tools"
) )
// queuePlanBackend implements tools.PlanBackend by enqueuing each step as a // queuePlanBackend implements tools.PlanBackend by enqueuing each step as a
// prompt into the session's queue file. It is used as the reasoning_plan // prompt into the session's queue via agent.Core.Queue. It is used as the
// fallback when no task_create MCP tool is available. // reasoning_plan fallback when no task_create MCP tool is available.
// //
// Steps are enqueued in topological order (blockers before dependents). The // Steps are enqueued in topological order (blockers before dependents). The
// goal description is prepended to the first step for context. Placeholder IDs // goal description is prepended to the first step for context. Placeholder IDs
// ("q1", "q2", …) are returned so the agent can refer to steps by name. // ("q1", "q2", …) are returned so the agent can refer to steps by name.
type queuePlanBackend struct { type queuePlanBackend struct {
enqueuePath string // absolute path to the session's enqueue file core agent.Core
} }
// CreatePlan enqueues each plan step as a prompt. Steps with After dependencies // CreatePlan enqueues each plan step as a prompt. Steps with After dependencies
// are sorted so blockers are enqueued before the steps that depend on them. // are sorted so blockers are enqueued before the steps that depend on them.
// The returned IDs are positional placeholders ("q1", "q2", …). // The returned IDs are positional placeholders ("q1", "q2", …).
func (b *queuePlanBackend) CreatePlan(_ context.Context, goal string, steps []tools.PlanStep) ([]string, error) { func (b *queuePlanBackend) CreatePlan(_ context.Context, goal string, steps []tools.PlanStep) ([]string, string, error) {
order, err := topoSort(steps) order, err := topoSort(steps)
if err != nil { if err != nil {
return nil, fmt.Errorf("queue plan: %w", err) return nil, "", fmt.Errorf("queue plan: %w", err)
} }
ids := make([]string, len(steps)) ids := make([]string, len(steps))
@ -61,11 +61,15 @@ func (b *queuePlanBackend) CreatePlan(_ context.Context, goal string, steps []to
sb.WriteString(")") sb.WriteString(")")
} }
if err := os.WriteFile(b.enqueuePath, []byte(sb.String()+"\n"), 0644); err != nil { b.core.Queue(sb.String())
return nil, fmt.Errorf("enqueue step %s: %w", ids[idx], err)
}
} }
return ids, nil
msg := fmt.Sprintf(
"Plan queued (%d steps). Stop here — do not execute. "+
"The queue will re-enter you with each step in order.",
len(steps),
)
return ids, msg, nil
} }
// topoSort returns the indices of steps in topological order (blockers first). // topoSort returns the indices of steps in topological order (blockers first).

View File

@ -857,8 +857,7 @@ func (s *Server) createSession(args []string) error {
}) })
sessID := agent.NewSessionID() sessID := agent.NewSessionID()
enqueuePath := s.sessionsDir + "/" + sessID + "/enqueue" fallback := &queuePlanBackend{}
fallback := &queuePlanBackend{enqueuePath: enqueuePath}
env := agent.BuildAgentEnv(cfg, newDisp(), workdir, agent.WithFallbackPlanBackend(fallback)) env := agent.BuildAgentEnv(cfg, newDisp(), workdir, agent.WithFallbackPlanBackend(fallback))
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -872,6 +871,7 @@ func (s *Server) createSession(args []string) error {
Env: env, Env: env,
NewDispatcher: newDisp, NewDispatcher: newDisp,
}) })
fallback.core = core
sess := &session{ sess := &session{
id: sessID, id: sessID,