Files
weichen 4675a25c18 Make the kernel transcript the authoritative cross-turn context
Why: Every turn rebuilt context from UI messages with 160-char tool-result
summaries and truncated arguments (quality diagnosis root cause 1). The kernel
session transcript already persists full-fidelity messages; P2-I2 switches the
direct path to it as the authority, per the approved plan (Documents
2026-10-09 §4 P2, user ruling 2).

First run (or after structural divergence) seeds the transcript once with the
full ctxasm assembly; later runs pass only the current user message. Dynamic
context - tail notes (personal instructions, session notes, world-book
entries), env update (date, workspace files) and newly hit task-layer prompt
modules - refreshes once per non-seed run via Reminders, so the frozen seed
system prompt stays accurate. Structural divergence (excluded messages,
variant switching, regeneration) changes a fingerprint and reseeds the
transcript from the message store - the regenerate/exclude/variant mapping.
Legacy transcripts without a fingerprint are adopted, not wiped, preserving
crash-pairing repair. Near the window threshold a 9-section compaction
(aux.session-compact, extractive fallback) summarizes older turns with the
kernel's append-only compaction entry; the kernel request-side micro-clear
still bounds within-run spikes. Backend's nested agent-native pin moves to
the reasoning-capture/replay kernel head.

Evidence: SELF - new tests TestContextRevisionStableOnAppendAndChangesOnDivergence,
TestTranscriptReseedsOnVariantSwitch, TestBuildCurrentUserCarriesAttachmentsAndKnowledge;
TestDirectRunSeedsStoreHistoryOnce updated to authoritative semantics; go vet
+ go test ./... pass; live before/after on the golden task set recorded on
the phase02 task card per the policy clause.

Impact: Direct conversations with a workspace sandbox now carry full tool
results, arguments and attachment text across turns; MiniMax thinking replays
from the transcript via the kernel change. No-conversation fallback keeps the
pre-I2 assembly path.
2026-10-09 15:38:33 +00:00

109 lines
3.0 KiB
Go

package runtime
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"os"
"sort"
core "agent-system-core"
)
// seedMeta 是会话转录旁的播种状态:<transcript>.seed.json。
// Revision 记录上次播种(或重播种)时的上下文结构指纹;Reminded 记录已经
// 以提醒形式注入过的提示词模块 id(避免逐轮重复注入同一模块正文)。
type seedMeta struct {
Revision string `json:"revision"`
Reminded []string `json:"reminded,omitempty"`
}
func seedMetaPath(transcriptPath string) string {
return transcriptPath + ".seed.json"
}
func readSeedMeta(transcriptPath string) *seedMeta {
raw, err := os.ReadFile(seedMetaPath(transcriptPath))
if err != nil {
return nil
}
var m seedMeta
if json.Unmarshal(raw, &m) != nil || m.Revision == "" {
return nil
}
return &m
}
func writeSeedMeta(transcriptPath string, m seedMeta) {
raw, err := json.Marshal(m)
if err != nil {
return
}
_ = os.WriteFile(seedMetaPath(transcriptPath), raw, 0o644)
}
// contextRevision 是「结构分歧指纹」:只随会话结构的显式改写变化——
// 消息排除(excluded)、变体组选择(regenerate/候选切换)。正常的追加
// (新的一轮)不改变指纹。指纹变化 ⇒ 转录与消息库已分歧 ⇒ 重播种转录。
func contextRevision(history []core.Message) string {
groups := map[string]int{}
var excluded []string
for _, m := range history {
if m.Excluded {
excluded = append(excluded, string(m.ID))
}
if m.VariantGroup != "" && !m.Excluded && m.VariantIndex > groups[m.VariantGroup] {
groups[m.VariantGroup] = m.VariantIndex
}
}
sort.Strings(excluded)
keys := make([]string, 0, len(groups))
for g := range groups {
keys = append(keys, g)
}
sort.Strings(keys)
h := sha256.New()
for _, id := range excluded {
h.Write([]byte("x" + id + ";"))
}
for _, g := range keys {
h.Write([]byte(g + "=" + string(rune('0'+groups[g])) + ";"))
}
return hex.EncodeToString(h.Sum(nil))[:16]
}
// resetTranscript 丢弃现有转录(分歧重播种)。Store 是无状态句柄
// (每次操作按路径开文件),删除文件即可;旁挂 meta 一并清理。
func resetTranscript(transcriptPath string) error {
if err := os.Remove(transcriptPath); err != nil && !os.IsNotExist(err) {
return err
}
_ = os.Remove(transcriptPath + ".meta.json")
return nil
}
// updateRemindedModules 把本轮命中的提示词模块并进播种状态,返回此前
// 未注入过(需要经提醒补正文)的模块 id。播种轮的模块正文已在系统段里,
// 只登记不注入。
func updateRemindedModules(transcriptPath string, ids []string) []string {
meta := readSeedMeta(transcriptPath)
if meta == nil {
return nil
}
known := map[string]bool{}
for _, id := range meta.Reminded {
known[id] = true
}
var fresh []string
for _, id := range ids {
if !known[id] {
fresh = append(fresh, id)
meta.Reminded = append(meta.Reminded, id)
}
}
if len(fresh) > 0 {
writeSeedMeta(transcriptPath, *meta)
}
return fresh
}