Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/agent/planexec/plan_execute_agent.go
3434 views
1
package planexec
2
3
import (
4
"context"
5
"fmt"
6
"github.com/kardolus/chatgpt-cli/agent/core"
7
"github.com/kardolus/chatgpt-cli/agent/types"
8
"strings"
9
"unicode"
10
)
11
12
// Agent pipeline: goal → Planner → Steps → Runner → Tools (Shell | LLM | FileOps), governed by Budget/Policy.
13
14
// Iteration 1: goal → plan → run(plan)
15
// Iteration 2: goal → plan → run until failure → Planner(repair) → run repair
16
// Iteration 3: (repeat pipeline per chunk/step): goal → Planner(next) → step(s) → Runner → observe → Planner(next) ..
17
18
type PlanExecuteAgent struct {
19
*core.BaseAgent
20
Planner Planner
21
Runner core.Runner
22
}
23
24
func NewPlanExecuteAgent(clk core.Clock, pl Planner, run core.Runner, opts ...core.BaseOption) *PlanExecuteAgent {
25
base := core.NewBaseAgent(clk)
26
for _, o := range opts {
27
o(base)
28
}
29
30
return &PlanExecuteAgent{
31
BaseAgent: base,
32
Planner: pl,
33
Runner: run,
34
}
35
}
36
37
func (a *PlanExecuteAgent) RunAgentGoal(ctx context.Context, goal string) (string, error) {
38
start := a.StartTimer()
39
defer a.FinishTimer(start)
40
41
a.LogMode(goal, "Plan-Execute")
42
43
out := a.Out
44
dbg := a.Debug
45
46
if a.PromptHistory != nil {
47
a.PromptHistory.Reset()
48
}
49
if a.Transcript != nil {
50
a.Transcript.Reset()
51
}
52
53
a.AddHistory(fmt.Sprintf("USER: %s", goal))
54
a.AddTranscript(fmt.Sprintf("[goal]\n%s\n", goal))
55
56
plan, err := a.Planner.Plan(ctx, goal)
57
if err != nil {
58
dbg.Errorf("Planner error: %v", err)
59
a.AddTranscript(fmt.Sprintf("[planner:error] %v\n", err))
60
return "", err
61
}
62
63
dbg.Debugf("plan goal=%q steps=%d", plan.Goal, len(plan.Steps))
64
65
execCtx := types.ExecContext{Goal: goal, Plan: plan, Results: nil}
66
67
out.Infof("Goal: %s", plan.Goal)
68
out.Infof("Mode: Plan-and-Execute (plan first, then run tools)\n")
69
70
a.AddTranscript(fmt.Sprintf("[plan] goal=%q steps=%d\n", plan.Goal, len(plan.Steps)))
71
for i, s := range plan.Steps {
72
a.AddTranscript(fmt.Sprintf("[plan-step %d/%d] type=%s desc=%q\n", i+1, len(plan.Steps), s.Type, s.Description))
73
}
74
75
for i, s := range plan.Steps {
76
switch s.Type {
77
case types.ToolShell:
78
out.Infof("Step %d/%d: %s (shell %s %v)", i+1, len(plan.Steps), s.Description, s.Command, s.Args)
79
case types.ToolLLM:
80
out.Infof("Step %d/%d: %s (llm prompt_len=%d)", i+1, len(plan.Steps), s.Description, len(s.Prompt))
81
case types.ToolFiles:
82
out.Infof("Step %d/%d: %s (file op=%q path=%q)", i+1, len(plan.Steps), s.Description, s.Op, s.Path)
83
default:
84
out.Infof("Step %d/%d: %s (type=%q)", i+1, len(plan.Steps), s.Description, s.Type)
85
}
86
}
87
out.Info("")
88
89
var final string
90
for i, step := range plan.Steps {
91
rendered, err := ApplyTemplate(step, execCtx)
92
if err != nil {
93
out.Errorf("Template render failed (step %d): %s: %v", i+1, step.Description, err)
94
dbg.Errorf("template render failed step=%d desc=%q err=%v", i+1, step.Description, err)
95
a.AddTranscript(fmt.Sprintf("[template:error][step %d] %v\n", i+1, err))
96
return "", err
97
}
98
99
dbg.Debugf("rendered step %d/%d: %+v", i+1, len(plan.Steps), rendered)
100
a.AddTranscript(fmt.Sprintf("[step %d/%d][start] %s\n", i+1, len(plan.Steps), rendered.Description))
101
102
res, err := a.Runner.RunStep(ctx, a.Config, rendered)
103
if err != nil {
104
if core.IsBudgetStop(err, out) || core.IsPolicyStop(err, out) {
105
dbg.Errorf("stop error step=%d desc=%q err=%v", i+1, rendered.Description, err)
106
if strings.TrimSpace(res.Transcript) != "" {
107
a.AddTranscript(res.Transcript)
108
}
109
return "", err
110
}
111
112
out.Errorf("Step failed: %s: %v", rendered.Description, err)
113
dbg.Errorf("step failed step=%d desc=%q err=%v transcript=%q", i+1, rendered.Description, err, res.Transcript)
114
if strings.TrimSpace(res.Transcript) != "" {
115
a.AddTranscript(res.Transcript)
116
}
117
return "", err
118
}
119
120
out.Infof("Step %d finished in %s (outcome=%s)", i+1, res.Duration, res.Outcome)
121
122
if strings.TrimSpace(res.Transcript) != "" {
123
a.AddTranscript(res.Transcript)
124
dbg.Debugf("step %d transcript:\n%s", i+1, res.Transcript)
125
}
126
127
if res.Outcome == types.OutcomeError {
128
if res.Transcript != "" {
129
out.Errorf("Step failed: %s\n%s", rendered.Description, res.Transcript)
130
}
131
dbg.Errorf("step outcome error step=%d desc=%q", i+1, rendered.Description)
132
a.AddTranscript(fmt.Sprintf("[step %d/%d][outcome=error] %s\n", i+1, len(plan.Steps), rendered.Description))
133
return "", fmt.Errorf("step failed: %s", rendered.Description)
134
}
135
136
if res.Output != "" {
137
final = res.Output
138
}
139
140
execCtx.Results = append(execCtx.Results, res)
141
a.AddTranscript(fmt.Sprintf("[step %d/%d][outcome=%s] duration=%s\n", i+1, len(plan.Steps), res.Outcome, res.Duration))
142
}
143
144
result := strings.TrimRightFunc(final, unicode.IsSpace)
145
out.Infof("\nResult: %s\n", result)
146
dbg.Debugf("final (trimmed): %q", result)
147
148
a.AddTranscript(fmt.Sprintf("[final]\n%s\n", result))
149
return result, nil
150
}
151
152