mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
feat: structured compaction summary with identifier preservation
Compaction summaries were too generic ("provide a concise summary"),
causing loss of task progress, decisions, and identifier corruption
after summarization.
Port from OpenClaw TS (compaction.ts):
- Structured MUST PRESERVE sections: active tasks, progress, last
request, decisions, TODOs, commitments
- Identifier preservation: preserve UUIDs, hashes, URLs, file names
exactly as written (no shortening/reconstruction)
- Prioritize recent context over older history
- Shared prompt constant used by both mid-loop and background
compaction paths
This commit is contained in:
1 parent
ab5bad11ff
commit
0f6cebc783
2 files changed
+30
-6
No files matched your search
@@ -10,6 +10,30 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
||||
)
|
||||
|
||||
// compactionSummaryPrompt is the structured summarization instruction used by both
|
||||
// mid-loop compaction and background summarization. Matching OpenClaw TS compaction.ts
|
||||
// MERGE_SUMMARIES_INSTRUCTIONS + IDENTIFIER_PRESERVATION_INSTRUCTIONS.
|
||||
const compactionSummaryPrompt = `Summarize this conversation concisely for the AI agent to resume work.
|
||||
|
||||
MUST PRESERVE:
|
||||
- Active tasks and their current status (in-progress, blocked, pending)
|
||||
- Batch operation progress (e.g., "5/17 items completed")
|
||||
- The last thing the user requested and what was being done about it
|
||||
- Decisions made and their rationale
|
||||
- TODOs, open questions, and constraints
|
||||
- Any commitments or follow-ups promised
|
||||
|
||||
IDENTIFIER PRESERVATION:
|
||||
Preserve all opaque identifiers exactly as written (no shortening or reconstruction),
|
||||
including UUIDs, hashes, IDs, tokens, API keys, hostnames, IPs, ports, URLs, and file names.
|
||||
|
||||
PRIORITIZE recent context over older history. The agent needs to know
|
||||
what it was doing, not just what was discussed.
|
||||
|
||||
Conversation to summarize:
|
||||
|
||||
`
|
||||
|
||||
// compactMessagesInPlace summarizes the first ~70% of messages into a condensed
|
||||
// summary, keeping the last ~30% intact. Operates purely on the local messages
|
||||
// slice — no session state touched, no locks needed.
|
||||
@@ -63,7 +87,7 @@ func (l *Loop) compactMessagesInPlace(ctx context.Context, messages []providers.
|
||||
resp, err := l.provider.Chat(sctx, providers.ChatRequest{
|
||||
Messages: []providers.Message{{
|
||||
Role: "user",
|
||||
Content: "Provide a concise summary of this conversation, preserving key findings, data, and context:\n\n" + sb.String(),
|
||||
Content: compactionSummaryPrompt + sb.String(),
|
||||
}},
|
||||
Model: l.model,
|
||||
Options: map[string]any{"max_tokens": 1024, "temperature": 0.3},
|
||||
|
||||
@@ -617,14 +617,14 @@ func (l *Loop) maybeSummarize(ctx context.Context, sessionKey string) {
|
||||
}
|
||||
|
||||
var prompt strings.Builder
|
||||
prompt.WriteString("Provide a concise summary of this conversation, preserving key context:\n")
|
||||
prompt.WriteString(compactionSummaryPrompt)
|
||||
if len(mediaKinds) > 0 {
|
||||
// Deduplicate and count media types for a compact note.
|
||||
counts := make(map[string]int)
|
||||
for _, k := range mediaKinds {
|
||||
counts[k]++
|
||||
}
|
||||
prompt.WriteString("\nNote: user shared media files (")
|
||||
prompt.WriteString("Note: user shared media files (")
|
||||
first := true
|
||||
for k, n := range counts {
|
||||
if !first {
|
||||
@@ -633,12 +633,12 @@ func (l *Loop) maybeSummarize(ctx context.Context, sessionKey string) {
|
||||
prompt.WriteString(fmt.Sprintf("%d %s(s)", n, k))
|
||||
first = false
|
||||
}
|
||||
prompt.WriteString(") which are no longer in context. Mention briefly if relevant.\n")
|
||||
prompt.WriteString(") which are no longer in context. Mention briefly if relevant.\n\n")
|
||||
}
|
||||
if summary != "" {
|
||||
prompt.WriteString("Existing context: " + summary + "\n")
|
||||
prompt.WriteString("Existing context: " + summary + "\n\n")
|
||||
}
|
||||
prompt.WriteString("\n" + sb.String())
|
||||
prompt.WriteString(sb.String())
|
||||
|
||||
resp, err := l.provider.Chat(sctx, providers.ChatRequest{
|
||||
Messages: []providers.Message{{Role: "user", Content: prompt.String()}},
|
||||
|
||||
Reference in new issue
Block a user