T11 — Auto compaction continuation
Sau T8 compact xong, agent cần message mới để tiếp tục. T11 quyết định inject gì: replay user message, mô tả media bằng text, hay gửi synthetic
continue khi đang mid-task.Tổng quan Context
Tại sao quan trọng. Sau khi T8 compact xong, messages array
đã được thay thế bằng [summary_system, ...tail]. Nhưng T1 agent loop cần
một "new user message" để tiếp tục — nếu không inject gì, agent dừng lại
chờ input người dùng, dù giữa chừng đang thực hiện task quan trọng.
T11 giải quyết bằng logic 3-branch dựa trên trạng thái trước compaction:
- Branch 1 — Media message: user turn cuối chứa image/attachment → inject text description thay thế (không replay media thật — có thể đã expire hoặc quá nặng).
- Branch 2 — Unanswered user message: user vừa gửi lệnh nhưng agent chưa reply (đang pre-processing) → replay nguyên văn lệnh đó.
- Branch 3 — Mid-task (default): agent đang giữa chuỗi tool calls →
inject synthetic
"continue"với metadatacompaction_continue: true.
Synthetic message + metadata. Message synthetic có thể được
filter/hide ở UI nhờ
metadata.compaction_continue. User không thấy
"continue" lạ xuất hiện trong chat — chỉ thấy agent tiếp tục làm việc.
Phân tích code chi tiết Anatomy
Branch detection — xác định trạng thái trước compact
session/compaction.ts — branch detection
TS
{`
function detectPostCompactionState(
messages: Message[] // messages TRƯỚC khi compact
): 'media' | 'unanswered' | 'mid-task' {
// Tìm last user message
const lastUser = [...messages]
.reverse()
.find(m => m.role === 'user')
if (!lastUser) return 'mid-task'
// Branch 1: user message có media
if (hasMedia(lastUser)) return 'media'
// Branch 2: user message chưa được reply
// (không có assistant message sau nó)
const lastUserIdx = messages.lastIndexOf(lastUser)
const hasReply = messages
.slice(lastUserIdx + 1)
.some(m => m.role === 'assistant')
if (!hasReply) return 'unanswered'
// Default: agent đang mid-task
return 'mid-task'
}
`}Branch 1 — Media message: inject text description
session/compaction.ts — media branch
TS
{`
function buildMediaContinuation(lastUserMsg: Message): Message {
// Không replay media thật — mô tả bằng text
const description = lastUserMsg.content
.filter(block => block.type !== 'image')
.map(block => block.text ?? '')
.join(' ')
.trim()
return {
role: 'user',
content: description
? \`[Continuing from compaction] \${description}\`
: '[Continuing task — previous message contained media attachments]',
metadata: { compaction_continue: true, had_media: true },
}
}
`}Branch 2 — Unanswered: replay user message
session/compaction.ts — unanswered branch
TS
{`
function buildUnansweredContinuation(lastUserMsg: Message): Message {
// Replay nguyên văn — không cần synthetic message
// metadata vẫn đánh dấu là continuation để UI biết
return {
...lastUserMsg,
metadata: {
...lastUserMsg.metadata,
compaction_continue: true,
},
}
}
`}Branch 3 — Mid-task: synthetic continue
session/compaction.ts — mid-task branch
TS
{`
function buildMidTaskContinuation(): Message {
return {
role: 'user',
content: 'continue',
metadata: {
compaction_continue: true,
// UI filter: ẩn message này khỏi chat display
// Agent vẫn nhận và xử lý bình thường
},
}
}
`}Main entry point — kết hợp 3 branches
session/compaction.ts — buildContinuation
TS
{`
export function buildContinuation(
messagesBeforeCompact: Message[]
): Message {
const state = detectPostCompactionState(messagesBeforeCompact)
switch (state) {
case 'media':
return buildMediaContinuation(
messagesBeforeCompact
.filter(m => m.role === 'user')
.at(-1)!
)
case 'unanswered':
return buildUnansweredContinuation(
messagesBeforeCompact
.filter(m => m.role === 'user')
.at(-1)!
)
case 'mid-task':
default:
return buildMidTaskContinuation()
}
}
`}Tương tác với các kỹ thuật khác Interaction
T8 compact() hoàn tất
│
├── returns: [summary_sys, ...tail] (messages mới)
│
▼
T11 buildContinuation(messagesBeforeCompact)
│
├── detectPostCompactionState()
│ │
│ ┌─────┼──────────────────────┐
│ │ │ │
│ media unanswered mid-task
│ │ │ │
│ ▼ ▼ ▼
│ text replay "continue"
│ desc user msg + metadata
│ │ │ │
└───┴─────┴──────────────────────┘
│
▼
T1 Agent loop nhận message mới
→ tiếp tục xử lý bình thường
UI layer đọc metadata.compaction_continue
→ ẩn synthetic messages trong chat display
T11 là bước cuối cùng của compaction pipeline. Nó nhận messages trước compact (để detect state), nhưng inject message vào session sau compact (messages array đã được T8 thay thế).
Quan trọng: snapshot trước compact. T11 cần
messagesBeforeCompact — không phải messages sau compact.
T8 phải truyền snapshot này sang T11 trước khi overwrite messages array.
Failure modes Risk
FM-1: Không inject continuation
Anti-pattern — no continuation after compact
TS
{`
// ❌ Compact xong nhưng không inject gì
async function compactAndContinue(session) {
const newMessages = await compact(session.messages, ...)
session.messages = newMessages
// Không inject continuation → T1 loop không có new user message
// → agent stop, chờ user input dù đang mid-task
}
`}FM-2: Replay media attachment
Replay media thật thay vì text description:
Anti-pattern — replay media
TS
{`
// ❌ Replay nguyên văn kể cả media
function buildContinuation(lastUserMsg) {
return lastUserMsg // chứa image blocks có thể đã expire
// Anthropic media blocks có URL expiry 1 giờ
// → 400 error "image URL expired" nếu compact xảy ra sau >1h
}
`}FM-3: Hiển thị synthetic message cho user
Nếu UI không filter metadata.compaction_continue,
user thấy "continue" xuất hiện trong chat như thể họ đã gõ — confusing UX.
Ưu điểm
- 3-branch — xử lý đúng 3 trạng thái khác nhau sau compact
- Media safety — không replay expired media
- Metadata filter — UI có thể ẩn synthetic messages
- Unanswered replay — user không cần gõ lại lệnh
Nhược điểm
- Cần snapshot messages trước compact — thêm complexity
- Synthetic "continue" có thể confuse model trong edge cases
- Branch logic dựa trên heuristic — không 100% accurate
So sánh với các harness khác Compare
| Harness | Continuation sau compact | Media handling? | Mid-task aware? |
|---|---|---|---|
| opencode | 3-branch: media/unanswered/mid-task | Có — text description thay thế | Có — synthetic "continue" |
| Claude Code | Tương tự (known from Anthropic blog) | Có | Có |
| LangChain | Replay last message đơn giản, không phân biệt state | Không có special handling | Không |
| ForgeCode | Documented continuation pattern (single branch) | Không rõ | Partial |
Implementation recipe Recipe
compaction.ts — 3-branch continuation recipe
TS
{`
// continuation.ts — post-compaction continuation logic
export function buildContinuation(
messagesBeforeCompact: Message[]
): Message {
const userMessages = messagesBeforeCompact.filter(m => m.role === 'user')
const lastUser = userMessages.at(-1)
if (!lastUser) return syntheticContinue()
// Branch 1: media
if (contentHasMedia(lastUser.content)) {
const textOnly = extractTextContent(lastUser.content)
return {
role: 'user',
content: textOnly
? \`[Resuming] \${textOnly}\`
: '[Resuming — previous message had media attachments]',
metadata: { compaction_continue: true, had_media: true },
}
}
// Branch 2: unanswered user message
const lastUserIdx = messagesBeforeCompact.lastIndexOf(lastUser)
const answered = messagesBeforeCompact
.slice(lastUserIdx + 1)
.some(m => m.role === 'assistant')
if (!answered) {
return { ...lastUser, metadata: { ...lastUser.metadata, compaction_continue: true } }
}
// Branch 3: mid-task
return syntheticContinue()
}
function syntheticContinue(): Message {
return {
role: 'user',
content: 'continue',
metadata: { compaction_continue: true },
}
}
`}Tham khảo Refs
Tham khảo
- ForgeCode docs — Context compaction và continuation
- Microsoft Agent Framework — Compaction continuation patterns
- Anthropic blog — Best practices for Claude agents
- opencode source — session/compaction.ts (lines 372–451)
- LangChain — Conversation summary memory (simple continuation)
- Anthropic docs — File/media handling và URL expiry