T23 — Overflow pattern detection & retry với server headers
25+ regex pattern detect
ContextOverflowError qua mọi provider. Retry: tôn trọng retry-after-ms header. Skip retry khi overflow — trigger compaction thay vì waste calls.Tổng quan Provider
Tại sao quan trọng. Không có HTTP status code chuẩn cho "context too long" — mỗi provider trả khác nhau: Anthropic
400 "prompt is too long", OpenAI 400 "max_tokens_exceeded", Bedrock 413 no body, Gemini 400 "context_length_exceeded". Nếu không phân biệt overflow vs transient error → retry overflow = waste token + tiền + thời gian. opencode dùng 25+ regex để classify lỗi, sau đó: overflow → skip retry, trigger compaction; transient → retry với exponential backoff; rate limit → respect server retry-after.
Server-directed retry: Khi provider trả
retry-after-ms hoặc retry-after header, opencode tôn trọng giá trị đó thay vì dùng backoff tự tính. Điều này tránh thunder-herd và giảm nguy cơ bị ban IP.
Phân tích code chi tiết Anatomy
Overflow pattern list — kinh nghiệm production
provider/error.ts — OVERFLOW_PATTERNS
TS
{`
// 25+ pattern từ thực tế production với nhiều provider
const OVERFLOW_PATTERNS: RegExp[] = [
/prompt is too long/i,
/exceeds the context window/i,
/max.*context.*length/i,
/context_length_exceeded/i,
/token.*limit.*exceeded/i,
/maximum context length/i,
/input.*too long/i,
/reduce your prompt/i,
/no body.*(400|413)/i, // Bedrock silent truncation → no body
/request entity too large/i, // HTTP 413
// ... 15+ more từ Groq, Together, Ollama, Mistral...
]
export function isContextOverflow(err: unknown): boolean {
const msg = extractErrorMessage(err)
return OVERFLOW_PATTERNS.some((r) => r.test(msg))
}
function extractErrorMessage(err: unknown): string {
if (err instanceof Error) return err.message
if (typeof err === "string") return err
// Extract từ response body nếu là HTTP error
if ((err as any)?.body) return JSON.stringify((err as any).body)
return String(err)
}
`}Retry logic — server-directed + exponential backoff
session/retry.ts — withRetry
TS
{`
export async function withRetry<T>(
fn: () => Promise<T>,
maxAttempts = 5,
): Promise<T> {
let attempt = 0
while (true) {
try {
return await fn()
} catch (err) {
attempt++
// 1. Context overflow: KHÔNG retry — trigger compaction thay vì waste calls
if (isContextOverflow(err)) throw new ContextOverflowError(err)
// 2. Max attempts reached
if (attempt >= maxAttempts) throw err
// 3. Server-directed delay: tôn trọng retry-after header
const serverMs = extractRetryAfter(err)
// 4. Exponential backoff fallback: 2^(attempt-1) * 2000ms, cap 30s
const delayMs = serverMs ?? Math.min(2 ** (attempt - 1) * 2000, 30_000)
await sleep(delayMs)
}
}
}
function extractRetryAfter(err: unknown): number | null {
const headers = (err as any)?.response?.headers
if (!headers) return null
const ms = headers.get("retry-after-ms")
if (ms) return Number(ms)
const sec = headers.get("retry-after")
if (sec) return Number(sec) * 1000
return null
}
`}
API call throws error
│
▼
isContextOverflow(err)?
│
┌────┴────┐
YES NO
│ │
▼ ▼
throw attempt < maxAttempts?
ContextOverflowError │
│ ┌────────┴────────┐
▼ YES NO
trigger │ │
compaction ▼ ▼
(T8) extractRetryAfter(err) throw err
│
┌────┴─────┐
server fallback
header exponential backoff
│ 2^(n-1) * 2000ms
│ cap: 30s
└────┬───┘
▼
sleep → retry
Tương tác với kỹ thuật khác Interaction
- T7 (Overflow detection proactive): T7 detect overflow trước khi gọi API (đếm token). T23 xử lý overflow sau khi API đã reject. Hai lớp bổ sung nhau — T7 phòng ngừa, T23 xử lý fallback.
- T8 (Tail-turn compaction): Khi T23 throw
ContextOverflowError, agent loop bắt lỗi này và trigger compaction (T8) thay vì fail toàn bộ session. - T4 (Interruption-safe cleanup): Retry loop bên trong
Effect.retry(...)— Effect giữ semantics đúng khi interrupt xảy ra giữa retry cycle (không để request treo).
Failure modes Failure
1. Regex miss khi provider đổi message
Provider update error message → regex không match → overflow được xử lý như transient error → retry → waste 5 attempts trước khi fail. Cần monitor miss rate và update patterns.
2. Exponential backoff không có jitter
Multiple client cùng nhận rate limit error → cùng sleep theo formula → cùng retry tại cùng thời điểm → thunder-herd nhẹ. Thêm random jitter (±20%) để stagger.
Best practice:
delay = base * (1 + Math.random() * 0.2) — jitter 20% enough to stagger clients hiệu quả.
3. Không phân biệt 4xx vs 5xx
4xx (bad request) thường không nên retry; 5xx (server error) nên retry. Hiện tại retry logic không phân biệt rõ — chỉ dừng ở overflow. Cần thêm isRetryable(err) check.
So sánh với các harness khác Compare
| Harness | Overflow detection | Retry policy | Server-directed |
|---|---|---|---|
| opencode | 25+ regex, unified across providers | Exponential, skip on overflow | ✅ retry-after-ms |
| litellm (Aider) | Built-in per-provider | Exponential + jitter | ✅ |
| Claude Code | SDK-level detection | Retry with backoff | ✅ |
| Cline | Basic error check | Simple retry | ❌ |
| OpenHarness | SDK-level (Anthropic) | Basic retry | ⚠️ |
Implementation recipe Recipe
TS
{`
const OVERFLOW = [
/prompt is too long/i,
/context.?window/i,
/token.?limit/i,
/context_length_exceeded/i,
/request entity too large/i,
]
class ContextOverflowError extends Error {
constructor(public readonly cause: unknown) {
super("Context window exceeded")
}
}
async function retryable<T>(fn: () => Promise<T>, max = 5): Promise<T> {
for (let i = 0; i < max; i++) {
try {
return await fn()
} catch (e) {
const msg = String((e as Error).message ?? e)
// Overflow: không retry
if (OVERFLOW.some(r => r.test(msg))) throw new ContextOverflowError(e)
if (i === max - 1) throw e
// Server-directed delay
const serverMs = (e as any).headers?.["retry-after-ms"]
const base = serverMs ?? Math.min(2000 * 2 ** i, 30_000)
// Add jitter ±20%
const delay = base * (0.8 + Math.random() * 0.4)
await new Promise(r => setTimeout(r, delay))
}
}
throw new Error("unreachable")
}
`}