backthink 从"心跳空转(独立sess-backthink+写死nothing to report宏)"改成"看得见主人、 能读写记忆、能调工具":think 读主会话最近20条(history.Recent,倒序映射)作为上下文 + 重写引导prompt(温暖/有针对性/主动表达,删掉 Do not produce chat-style message) + 注入 loop.OwnerEnv(MemWritable=true/ActorOwner/全工具)。 - context.go: mainContext/recentProvider/thinkInput/guidancePrompt - thinker.go: LoopThinker 加 recent;Think 读主会话上下文+OwnerEnv+MutedBroadcast - wire.go: NewLoopThinker(a.loop, a.history) - 刻意偏离(安全): loop.Run session id 仍用 sess-backthink(不写"web"),避免与用户实时回合 checkpoint/中断窗口复用污染;上下文来源+运行环境=主会话/owner env,目标达成。红线靠actor元数据。 - 测试: prompt无压制/有鼓励;主会话消息映射;nil退化;端到端Think(owner env 生效) 验收: build/vet/test 全绿+windows/darwin交叉编译绿. P2(主动推送+渠道选择)后置.
48 lines
1.9 KiB
Go
48 lines
1.9 KiB
Go
package backthink
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
|
)
|
|
|
|
// LoopThinker adapts a *loop.Loop into a Thinker. It is the production wiring
|
|
// that lets an autonomous background pass drive the full tool loop (memory
|
|
// injection, model steps, tool calls such as IoT/calendar/reminders) while seeing
|
|
// the main session's recent conversation and running under the owner environment.
|
|
type LoopThinker struct {
|
|
loop *loop.Loop
|
|
recent recentProvider
|
|
}
|
|
|
|
// NewLoopThinker wraps l as a Thinker over the given recent-messages provider
|
|
// (*history.Store in production; nil degrades the think to the guidance prompt
|
|
// alone, see mainContext).
|
|
func NewLoopThinker(l *loop.Loop, recent recentProvider) *LoopThinker {
|
|
return &LoopThinker{loop: l, recent: recent}
|
|
}
|
|
|
|
// Think runs one unattended think through the wrapped loop and returns the
|
|
// conclusion text. It inherits the main session's recent conversation as input
|
|
// context and injects the owner session environment (MemWritable=true, ActorOwner,
|
|
// full tool set) so the think can read/write memory and call tools. The loop's
|
|
// Run session id is the service's own (not the main session id) so the think never
|
|
// collides with the live main session's durable checkpoint store (design/20).
|
|
// 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
|
|
}
|
|
recent := mainContext(ctx, t.recent)
|
|
messages := thinkInput(recent, trigger)
|
|
envCtx := loop.WithSessionEnv(ctx, loop.OwnerEnv())
|
|
// Mute UI broadcast: background reflection must not leak its intermediate
|
|
// reasoning into the user-facing chat stream (it only needs the conclusion).
|
|
res, err := t.loop.Run(loop.WithMutedBroadcast(envCtx), sessionID, messages)
|
|
if err != nil {
|
|
return "", fmt.Errorf("backthink: think: %w", err)
|
|
}
|
|
return res.Text, nil
|
|
}
|