T5 — Synthetic system reminder cho mid-turn messages
tags ở step > 1 — nhấn mạnh để model không bỏ qua input mới lẫn trong history.Tổng quan Overview
Trong một agent session dài, user có thể gõ thêm message trong khi agent đang xử lý multi-step task. Ví dụ: agent đang đọc và sửa file, user nhắn thêm "hãy thêm comment tiếng Việt cho tất cả function". Nếu message này đi vào history như một user message bình thường, model có thể không nhận ra rằng đây là instruction mới cần xử lý ngay.
opencode giải quyết bằng cách transform text của user message mới thành một block
được bọc trong <system-reminder>...</system-reminder>. XML tag
này signal cho model rằng đây là instruction ưu tiên, cần được address trước khi tiếp tục.
Kỹ thuật này chỉ áp dụng ở step > 1 (step 1 là initial prompt, không cần reminder).
<system-reminder>.
Phân tích code chi tiết Anatomy
Điều kiện áp dụng reminder
Ba điều kiện phải thỏa đồng thời để wrap được áp dụng:
session/prompt.ts:1453-1468 — synthetic reminder injection
{`
// Chỉ áp dụng từ step 2 trở đi
if (step > 1 && lastFinished) {
for (const m of msgs) {
// Điều kiện 1: phải là user message
if (m.info.role !== "user") continue
// Điều kiện 2: phải là message MỚI (sau lần loop cuối hoàn thành)
if (m.info.id <= lastFinished.id) continue
for (const p of m.parts) {
// Điều kiện 3: chỉ wrap text, không wrap media/synthetic/ignored
if (p.type !== "text" || p.ignored || p.synthetic) continue
if (!p.text.trim()) continue
// Wrap trong system-reminder XML block
p.text = [
"<system-reminder>",
"The user sent the following message:",
p.text,
"",
"Please address this message and continue with your tasks.",
"</system-reminder>",
].join("\n")
}
}
}
`}lastFinished — tracking loop state
lastFinished là reference đến message cuối cùng đã được process trong
iteration trước. Bất kỳ user message nào có id > lastFinished.id là
message mới — chưa được model "see" trong context tự nhiên.
lastFinished tracking
{`
// Được update mỗi lần kết thúc một step:
lastFinished = lastAssistant // assistant response cuối cùng
// Mỗi iteration: user messages mới (id > lastFinished.id)
// là những message đến trong khi loop đang chạy
const newUserMsgs = msgs.filter(
m => m.role === "user" && m.id > lastFinished.id
)
`}Chỉ wrap text — không wrap media hay synthetic parts
Điều kiện p.type !== "text" || p.ignored || p.synthetic bảo vệ các loại
part khác: image attachments, file references, và các synthetic messages được inject
bởi harness (vd: compaction continue message).
Part type filtering
{`
// Các loại part có thể có trong một user message:
// - type: "text" → ← CHỈ WRAP type này
// - type: "image" → skip (không wrap media)
// - type: "file" → skip
// - synthetic: true → skip (harness-injected, vd compaction)
// - ignored: true → skip (user đã mark là irrelevant)
`}Tương tác với các kỹ thuật khác Interaction
T5 chạy trước khi request được gửi đến LLM, sau khi messages đã được filter bởi compaction logic. Nó không tương tác trực tiếp với T2-T4 (streaming), nhưng ảnh hưởng đến quality của model response trong iteration đó.
Failure modes Failures
Failure 1: User message bị bỏ qua trong long loop
Không có reminder, user message mới lẫn vào history và model tiếp tục task cũ:
Không có reminder — user instruction bị miss
{`
// Step 3, history:
// [user: "viết service X"]
// [assistant: "OK tôi sẽ..."]
// [tool: read_file result]
// [tool: write_file result]
// [user: "hãy dùng TypeScript strict mode"] ← message mới!
// [assistant: ...] ← model có thể không address vì nó "buried"
// Với reminder:
// [user: "<system-reminder>The user sent:\nhãy dùng TypeScript strict mode\nPlease address...</system-reminder>"]
// Model NOW sees it as priority instruction
`}Failure 2: Wrap ở step 1 — không cần thiết
Nếu áp dụng reminder ngay cả ở step 1, initial user message sẽ bị wrap không cần thiết — overhead token và có thể confuse model về context:
Sai: wrap ở step 1
{`
// WRONG: không check step > 1
for (const m of msgs) {
if (m.role === "user") wrapWithReminder(m)
}
// Initial user message cũng bị wrap:
// "<system-reminder>The user sent:\nViết hàm sort\nPlease address...</system-reminder>"
// → unneccesary và verbose
// CORRECT:
if (step > 1 && lastFinished) { // chỉ step > 1
// ...
}
`}Failure 3: Wrap media parts — format error
Nếu vô tình wrap image attachment hoặc file content trong system-reminder text, model sẽ nhận string biểu diễn của binary data:
Wrap sai part type
{`
// WRONG: wrap tất cả parts
for (const p of m.parts) {
p.text = wrapReminder(p.text || p.toString())
// Image part: p.text = undefined → "undefined" được wrap
}
// CORRECT: chỉ text parts không phải synthetic/ignored
if (p.type !== "text" || p.ignored || p.synthetic) continue
`}So sánh với các harness khác Compare
| Harness | Mid-turn message handling | XML wrapping | Step-aware |
|---|---|---|---|
| opencode | |||
| Claude Code | |||
| Aider | |||
| Cline | |||
| AutoGPT |
opencode và Claude Code (theo Anthropic documentation) là hai harness duy nhất trong danh sách có explicit mid-turn message handling. Aider và Cline xử lý user input theo turn-based model truyền thống — agent xong mới nhận input mới.
Implementation recipe Recipe
Message transformer function — có thể integrate vào bất kỳ agent loop nào:
system-reminder-injector.ts
{`
interface Message {
id: string
role: "user" | "assistant" | "tool"
parts: MessagePart[]
}
interface MessagePart {
type: "text" | "image" | "file"
text?: string
ignored?: boolean
synthetic?: boolean
}
function injectSystemReminders(
messages: Message[],
lastFinishedId: string | null,
step: number
): Message[] {
// Chỉ áp dụng từ step 2 trở đi
if (step <= 1 || lastFinishedId === null) {
return messages
}
return messages.map((msg) => {
// Chỉ xử lý user messages
if (msg.role !== "user") return msg
// Chỉ xử lý messages MỚI (sau lastFinished)
if (msg.id <= lastFinishedId) return msg
// Transform text parts
const newParts = msg.parts.map((part) => {
// Bỏ qua non-text, synthetic, ignored, empty
if (
part.type !== "text" ||
part.ignored ||
part.synthetic ||
!part.text?.trim()
) {
return part
}
// Wrap trong system-reminder
return {
...part,
text: [
"<system-reminder>",
"The user sent the following message:",
part.text,
"",
"Please address this message and continue with your tasks.",
"</system-reminder>",
].join("\n"),
}
})
return { ...msg, parts: newParts }
})
}
// Usage trong agent loop:
// const processedMessages = injectSystemReminders(
// rawMessages,
// lastAssistantMessage?.id ?? null,
// currentStep
// )
// // Gửi processedMessages cho LLM
`}p.text vì messages đã được clone trong build phase.
Cả hai approach đều đúng — immutable version an toàn hơn cho testing.
Tham khảo Refs
- Anthropic — Claude Code auto mode · Đề cập system reminder pattern trong production harness
- HumanLayer — Harness Engineering for Coding Agents · Context re-injection patterns
- Anthropic docs — Use XML tags · Tại sao XML tags hiệu quả với Claude
- ReAct paper · Reasoning và observation trong multi-step agent
- anomalyco/opencode — session/prompt.ts:1453-1468 · Source tham chiếu chính
- OpenAI — Prompt engineering guide · Strategies để emphasize instructions