Path: blob/main/agent/planexec/plan_execute_agent.go
3434 views
package planexec12import (3"context"4"fmt"5"github.com/kardolus/chatgpt-cli/agent/core"6"github.com/kardolus/chatgpt-cli/agent/types"7"strings"8"unicode"9)1011// Agent pipeline: goal → Planner → Steps → Runner → Tools (Shell | LLM | FileOps), governed by Budget/Policy.1213// Iteration 1: goal → plan → run(plan)14// Iteration 2: goal → plan → run until failure → Planner(repair) → run repair15// Iteration 3: (repeat pipeline per chunk/step): goal → Planner(next) → step(s) → Runner → observe → Planner(next) ..1617type PlanExecuteAgent struct {18*core.BaseAgent19Planner Planner20Runner core.Runner21}2223func NewPlanExecuteAgent(clk core.Clock, pl Planner, run core.Runner, opts ...core.BaseOption) *PlanExecuteAgent {24base := core.NewBaseAgent(clk)25for _, o := range opts {26o(base)27}2829return &PlanExecuteAgent{30BaseAgent: base,31Planner: pl,32Runner: run,33}34}3536func (a *PlanExecuteAgent) RunAgentGoal(ctx context.Context, goal string) (string, error) {37start := a.StartTimer()38defer a.FinishTimer(start)3940a.LogMode(goal, "Plan-Execute")4142out := a.Out43dbg := a.Debug4445if a.PromptHistory != nil {46a.PromptHistory.Reset()47}48if a.Transcript != nil {49a.Transcript.Reset()50}5152a.AddHistory(fmt.Sprintf("USER: %s", goal))53a.AddTranscript(fmt.Sprintf("[goal]\n%s\n", goal))5455plan, err := a.Planner.Plan(ctx, goal)56if err != nil {57dbg.Errorf("Planner error: %v", err)58a.AddTranscript(fmt.Sprintf("[planner:error] %v\n", err))59return "", err60}6162dbg.Debugf("plan goal=%q steps=%d", plan.Goal, len(plan.Steps))6364execCtx := types.ExecContext{Goal: goal, Plan: plan, Results: nil}6566out.Infof("Goal: %s", plan.Goal)67out.Infof("Mode: Plan-and-Execute (plan first, then run tools)\n")6869a.AddTranscript(fmt.Sprintf("[plan] goal=%q steps=%d\n", plan.Goal, len(plan.Steps)))70for i, s := range plan.Steps {71a.AddTranscript(fmt.Sprintf("[plan-step %d/%d] type=%s desc=%q\n", i+1, len(plan.Steps), s.Type, s.Description))72}7374for i, s := range plan.Steps {75switch s.Type {76case types.ToolShell:77out.Infof("Step %d/%d: %s (shell %s %v)", i+1, len(plan.Steps), s.Description, s.Command, s.Args)78case types.ToolLLM:79out.Infof("Step %d/%d: %s (llm prompt_len=%d)", i+1, len(plan.Steps), s.Description, len(s.Prompt))80case types.ToolFiles:81out.Infof("Step %d/%d: %s (file op=%q path=%q)", i+1, len(plan.Steps), s.Description, s.Op, s.Path)82default:83out.Infof("Step %d/%d: %s (type=%q)", i+1, len(plan.Steps), s.Description, s.Type)84}85}86out.Info("")8788var final string89for i, step := range plan.Steps {90rendered, err := ApplyTemplate(step, execCtx)91if err != nil {92out.Errorf("Template render failed (step %d): %s: %v", i+1, step.Description, err)93dbg.Errorf("template render failed step=%d desc=%q err=%v", i+1, step.Description, err)94a.AddTranscript(fmt.Sprintf("[template:error][step %d] %v\n", i+1, err))95return "", err96}9798dbg.Debugf("rendered step %d/%d: %+v", i+1, len(plan.Steps), rendered)99a.AddTranscript(fmt.Sprintf("[step %d/%d][start] %s\n", i+1, len(plan.Steps), rendered.Description))100101res, err := a.Runner.RunStep(ctx, a.Config, rendered)102if err != nil {103if core.IsBudgetStop(err, out) || core.IsPolicyStop(err, out) {104dbg.Errorf("stop error step=%d desc=%q err=%v", i+1, rendered.Description, err)105if strings.TrimSpace(res.Transcript) != "" {106a.AddTranscript(res.Transcript)107}108return "", err109}110111out.Errorf("Step failed: %s: %v", rendered.Description, err)112dbg.Errorf("step failed step=%d desc=%q err=%v transcript=%q", i+1, rendered.Description, err, res.Transcript)113if strings.TrimSpace(res.Transcript) != "" {114a.AddTranscript(res.Transcript)115}116return "", err117}118119out.Infof("Step %d finished in %s (outcome=%s)", i+1, res.Duration, res.Outcome)120121if strings.TrimSpace(res.Transcript) != "" {122a.AddTranscript(res.Transcript)123dbg.Debugf("step %d transcript:\n%s", i+1, res.Transcript)124}125126if res.Outcome == types.OutcomeError {127if res.Transcript != "" {128out.Errorf("Step failed: %s\n%s", rendered.Description, res.Transcript)129}130dbg.Errorf("step outcome error step=%d desc=%q", i+1, rendered.Description)131a.AddTranscript(fmt.Sprintf("[step %d/%d][outcome=error] %s\n", i+1, len(plan.Steps), rendered.Description))132return "", fmt.Errorf("step failed: %s", rendered.Description)133}134135if res.Output != "" {136final = res.Output137}138139execCtx.Results = append(execCtx.Results, res)140a.AddTranscript(fmt.Sprintf("[step %d/%d][outcome=%s] duration=%s\n", i+1, len(plan.Steps), res.Outcome, res.Duration))141}142143result := strings.TrimRightFunc(final, unicode.IsSpace)144out.Infof("\nResult: %s\n", result)145dbg.Debugf("final (trimmed): %q", result)146147a.AddTranscript(fmt.Sprintf("[final]\n%s\n", result))148return result, nil149}150151152