package backthink import ( "context" "fmt" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // LoopThinker adapts a *loop.Loop into a Thinker. It is the production wiring // that lets a background pass drive the full tool loop (memory injection, model // steps, tool calls such as IoT/calendar/reminders) without the service knowing // the loop's concrete shape. type LoopThinker struct { loop *loop.Loop } // NewLoopThinker wraps l as a Thinker. func NewLoopThinker(l *loop.Loop) *LoopThinker { return &LoopThinker{loop: l} } // Think runs one unattended think through the wrapped loop and returns the // conclusion text. It returns ErrNoLoop when no loop was injected. func (t *LoopThinker) Think(ctx context.Context, sessionID, trigger string) (string, error) { if t.loop == nil { return "", ErrNoLoop } messages := []model.Message{model.TextMessage(model.RoleUser, backgroundPrompt(trigger))} res, err := t.loop.Run(ctx, sessionID, messages) if err != nil { return "", fmt.Errorf("backthink: think: %w", err) } return res.Text, nil } // backgroundPrompt is the instruction handed to the runtime for one unattended // thought. It explicitly avoids asking for a chat-style message to the user. func backgroundPrompt(trigger string) string { return "[background autonomous reflection]\n" + "Trigger: " + trigger + "\n" + "Review recent context, device states, calendar and reminders. Decide " + "whether anything is worth proactively telling the owner. If nothing " + "matters, conclude \"nothing to report\". Keep the conclusion to one or " + "two sentences. Do not produce a chat-style message to the user." }