执行方案阶段 2 · P2-2(`DevDocs/audit/_plan.md`)。四条裁决: - 1.8/07.4.2:`think()` 不再走忽略 importance 的 `disturbAllowed`(已删),**接回 `Decide` 级联**; 安静时段只挡常规推送、**critical 安全域穿透**;`DecisionInput` 增 `ConclusionPush`/`Heartbeat`。 睡眠仍绝对不打扰(裁决只豁免安静时段,此点已在设计与报告写明)。 - 07.4.3:长静默(>2h)分支对**心跳类**放行(常规事件仍需 ≥high),刚聊过天 10min 内扣留不变。 - 07.4.4:`tickLoop` **启动立即先思考一轮** + 拟人化启动消息;启动轮与心跳**同门同限频** (重启风暴不会刷屏),ctx 已取消则跳过。 - 07.4.5:核对一致(`THINK_INTERVAL_SEC` 默认 900s 与 design/20 §0 权威口径一致)⇒ **代码零改动**。 验证:go build 绿;backthink 包 vet/测试绿;`-count=20`(抗 flake)+ `-race` 绿; linux/amd64 + darwin/arm64 交叉编译绿;gofmt 干净;文件 ≤500 行、无 >80 行函数。 变异验证(三条,均带还原证明):① 安静时段改回"全拦" → 安全域穿透断言 FAIL(首次只删 critical 分支时仍绿——`critical >= high` 会从 high 分支漏过,故真实变异必须是"全拦"); ② 删心跳豁免 → 长静默心跳断言 FAIL;③ 删启动思考 → 启动即思考断言 FAIL。 未验证:无真实模型凭据 ⇒ 启动轮问候与 `[我决定:…]` 解析仅经 mock + 真 loop 验证,端到端 LLM 未验。
118 lines
4.7 KiB
Go
118 lines
4.7 KiB
Go
package backthink
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||
)
|
||
|
||
// TriggerKind classifies what woke a thinking pass, so the push path can apply the
|
||
// right policy: a heartbeat is gate-governed (60min≤1 / cooldown / per-flavor), an
|
||
// event is a real signal the owner wants to hear about and bypasses the rate gate.
|
||
type TriggerKind int
|
||
|
||
const (
|
||
// TriggerHeartbeat is the periodic autonomous reflection. Its proactive push is
|
||
// throttled by the PushGate so the owner is never spammed by empty reflections.
|
||
TriggerHeartbeat TriggerKind = iota
|
||
// TriggerEvent is a real event (IoT device change, reminder, owner-relevant
|
||
// system event). Its push bypasses the PushGate — "该发就发" — subject only to
|
||
// same-event dedup.
|
||
TriggerEvent
|
||
)
|
||
|
||
// String returns a stable label for logs and records.
|
||
func (k TriggerKind) String() string {
|
||
switch k {
|
||
case TriggerEvent:
|
||
return "event"
|
||
case TriggerHeartbeat:
|
||
return "heartbeat"
|
||
default:
|
||
return fmt.Sprintf("triggerkind(%d)", int(k))
|
||
}
|
||
}
|
||
|
||
// Bus subjects the service subscribes to for event-triggered thinking. They are
|
||
// the contract defined by design/12 §1: an IoT state change, a reminder firing,
|
||
// or a generic system event.
|
||
const (
|
||
// SubjectReminderTrigger is the bus subject on which reminders fire.
|
||
SubjectReminderTrigger = "reminder.trigger"
|
||
// SubjectSystemEvent is the bus subject for generic system events.
|
||
SubjectSystemEvent = "system.event"
|
||
)
|
||
|
||
// SystemEvent is the optional typed payload a caller may publish to
|
||
// SubjectSystemEvent to convey a system notice. Import defaults to Normal. Under
|
||
// design/12 §6 策略一, a low-value system notice (a join/leave ping, generic
|
||
// platform noise) is downgraded to a registration-only record and does not wake a
|
||
// full thinking pass; only a high-importance (owner-relevant) system event thinks,
|
||
// so a busy public platform cannot trigger a flood of expensive reflections.
|
||
type SystemEvent struct {
|
||
Summary string
|
||
Import Importance
|
||
}
|
||
|
||
// Trigger describes what woke a thinking pass: the source subsystem, a
|
||
// human-readable summary and the event's estimated importance.
|
||
type Trigger struct {
|
||
Source string
|
||
Summary string
|
||
Import Importance
|
||
// Kind records whether this pass was the periodic heartbeat or a real event, so
|
||
// the push path can bypass the rate gate for a genuine event (owner mandate).
|
||
Kind TriggerKind
|
||
}
|
||
|
||
// heartbeatTrigger is the periodic, low-importance trigger. Low importance means
|
||
// the pass rarely interrupts the user, honouring "默认不发送 chat 类型消息".
|
||
func heartbeatTrigger() Trigger {
|
||
return Trigger{Source: "heartbeat", Summary: "periodic autonomous reflection", Import: ImportanceLow, Kind: TriggerHeartbeat}
|
||
}
|
||
|
||
// StartupSummary 是启动轮思考(裁决 07.4.4,design/12 §1)的触发摘要。它同时是
|
||
// context.go 里切换「拟人化启动消息」专属引导提示词的标识。
|
||
const StartupSummary = "process startup"
|
||
|
||
// startupTrigger 是进程启动后的第一轮思考(裁决 07.4.4):不等满一个心跳周期,
|
||
// 启动立即触发,并引导她以角色口吻产出一条拟人化启动问候(如「我回来啦」)。
|
||
// Kind 仍为 TriggerHeartbeat:启动轮的推送与心跳走完全相同的门与限频
|
||
// (PushGate / 打扰级联 / 活动门),进程反复重启也不会刷屏主人。
|
||
func startupTrigger() Trigger {
|
||
return Trigger{Source: "startup", Summary: StartupSummary, Import: ImportanceLow, Kind: TriggerHeartbeat}
|
||
}
|
||
|
||
// triggerForEvent maps a bus event to a Trigger. An IoT device change is treated
|
||
// as normal importance, a reminder firing as high importance, and a generic
|
||
// system event as normal importance. Every event is tagged TriggerEvent so its
|
||
// proactive push bypasses the heartbeat PushGate.
|
||
func triggerForEvent(e bus.Event) Trigger {
|
||
switch e.Subject {
|
||
case iot.DeviceUpdateSubject:
|
||
if d, ok := e.Payload.(iot.DeviceUpdate); ok {
|
||
return Trigger{
|
||
Source: e.Subject,
|
||
Summary: fmt.Sprintf("device %s (%s) %s", d.Device.ID, d.Device.Type, d.Action),
|
||
Import: ImportanceNormal,
|
||
Kind: TriggerEvent,
|
||
}
|
||
}
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
case SubjectReminderTrigger:
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceHigh, Kind: TriggerEvent}
|
||
case SubjectSystemEvent:
|
||
tr := Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
if sy, ok := e.Payload.(SystemEvent); ok {
|
||
if sy.Summary != "" {
|
||
tr.Summary = sy.Summary
|
||
}
|
||
tr.Import = sy.Import
|
||
}
|
||
return tr
|
||
default:
|
||
return Trigger{Source: e.Subject, Summary: e.Subject, Import: ImportanceNormal, Kind: TriggerEvent}
|
||
}
|
||
}
|