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.
109 lines
3.0 KiB
Go
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
|
|
}
|