"encoding/json"
"errors"
"fmt"
+ "path/filepath"
"strings"
"sync"
"time"
// input of turnInput. The loop runs the tool calls of each response and sends
// their results back, until a response has no tool call: the final answer.
//
+// The view goes out as the messages of a viewLayout, so the first request of
+// a turn reads the view of the turn before from the prompt cache.
+//
// Each later request of a turn begins with the whole input of the request
// before it, then its output items, then the tool results. The output items
// hold the reasoning with its encrypted content, because the calls do not
memory *Memory
client modelClient
tools *toolbox
+ layout *viewLayout
clock func() time.Time
notify func(Event)
wake chan struct{}
// newLoop returns the loop of agent with its memory. Its calls go through
// client, and notify gets each event. The clock gives the date of a turn.
-func newLoop(agent *Agent, memory *Memory, client modelClient, clock func() time.Time, notify func(Event)) *loop {
+func newLoop(agent *Agent, memory *Memory, client modelClient, clock func() time.Time, notify func(Event)) (*loop, error) {
+ layout, err := loadLayout(filepath.Join(agent.dir, layoutFileName))
+ if err != nil {
+ return nil, err
+ }
+
l := &loop{
agent: agent,
memory: memory,
client: client,
+ layout: layout,
clock: clock,
notify: notify,
wake: make(chan struct{}, 1),
workdir: agent.config.Workdir,
}
- return l
+ return l, nil
}
// Submit logs a user message and wakes the loop. The message joins the next
skip[message.ID] = true
}
- input := turnInput(l.memory.ViewWithout(skip), l.state(), messages)
- for {
+ view, err := l.layout.Split(l.memory.ViewLines(skip))
+ if err != nil {
+ return err
+ }
+
+ input := turnInput(view, l.state(), messages)
+ for turn := "first"; ; turn = "next" {
response, err := l.client.Create(ctx, Request{
Model: l.agent.config.Model,
Effort: l.agent.config.Effort,
Instructions: instructions,
Tools: agentTools(),
Input: input,
+ Turn: turn,
})
if err != nil {
return err
return input, nil
}
-// state returns the per-turn state: the date and time of the turn.
+// state returns the per-turn state: the end of the view, and the date and time
+// of the turn.
func (l *loop) state() string {
- return "Now: " + l.clock().Format("Monday 2006-01-02 15:04 -07:00")
+ return "</chat>\n\nNow: " + l.clock().Format("Monday 2006-01-02 15:04 -07:00")
}
-// turnInput returns the input of the first request of a turn: one user message
-// with the view, the per-turn state, and each new message whole. The view
-// leaves out the leaf lines of the new messages, which come whole after it.
-func turnInput(view, state string, messages []Message) []any {
- parts := []string{view + "\n", state + "\n"}
+// turnInput returns the input of the first request of a turn, stable parts
+// first: a user message for each view message, one with the per-turn state,
+// and one for each new message, whole. The view leaves out the leaf lines of
+// the new messages.
+func turnInput(view []string, state string, messages []Message) []any {
+ input := make([]any, 0, len(view)+1+len(messages))
+ for _, text := range view {
+ input = append(input, userMessage(text))
+ }
+
+ input = append(input, userMessage(state))
for _, message := range messages {
- parts = append(parts, message.Text)
+ input = append(input, userMessage(message.Text))
}
- return []any{userMessage(parts...)}
+ return input
}
// inputItem returns an output item as an input item for the next request. The