다정함을 위한 전쟁 News2026. 8. 5. 14:53
'News' 카테고리의 다른 글
| 듣보잡 쓰레기의 최후 (0) | 2026.08.05 |
|---|---|
| 50넘어 알게되는 인생조언 (1) | 2026.07.25 |
| 결정사 500점 만점에 497점 받은 여자 스펙 (0) | 2026.07.25 |
| 뜻을 몰랐을 때도 예뻤고 알고 나니 더 예쁘다. (0) | 2026.07.24 |
| 말해도 되는 것과 안되는 것의 차이 (0) | 2026.07.24 |
| 듣보잡 쓰레기의 최후 (0) | 2026.08.05 |
|---|---|
| 50넘어 알게되는 인생조언 (1) | 2026.07.25 |
| 결정사 500점 만점에 497점 받은 여자 스펙 (0) | 2026.07.25 |
| 뜻을 몰랐을 때도 예뻤고 알고 나니 더 예쁘다. (0) | 2026.07.24 |
| 말해도 되는 것과 안되는 것의 차이 (0) | 2026.07.24 |
| 다정함을 위한 전쟁 (0) | 2026.08.05 |
|---|---|
| 50넘어 알게되는 인생조언 (1) | 2026.07.25 |
| 결정사 500점 만점에 497점 받은 여자 스펙 (0) | 2026.07.25 |
| 뜻을 몰랐을 때도 예뻤고 알고 나니 더 예쁘다. (0) | 2026.07.24 |
| 말해도 되는 것과 안되는 것의 차이 (0) | 2026.07.24 |
📅 발행일: 2026년 8월 2일 ・ 🏷️ 태그: #번역 #소스코드분석 #ClaudeCode #AI에이전트 #LLM #컨텍스트윈도우 #컴팩션 #메모리 ・ 📚 원문: Medium 원문 보기
📌 핵심 요약 (TL;DR)
- 컴팩션(Compaction) 8단계 캐스케이드: 긴 대화에서 컨텍스트 윈도우가 가득 차면, 하나의 도구가 아니라 비용이 싼 순서대로 8가지 메커니즘이 순차 실행됩니다.
- 메모리 3계층: 컨텍스트 내부 메모리(Tier 1) → 파일 기반 영구 메모리(Tier 2,MEMORY.md) → 지침 메모리(Tier 3,CLAUDE.md)로 나뉩니다.
- 44개 기능 플래그: 이미 배포됐지만 문서화되지 않은 것(가짜 도구, 잠행 모드, 모델 자동 다운그레이드 등)과 아직 미공개인 것(KAIROS, ULTRAPLAN 등)이 섞여 있습니다.
- 서킷 브레이커:MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3— 자동 컴팩션이 3번 연속 실패하면 세션 종료까지 비활성화됩니다. "에이전트가 조용히 멍해지는" 순간의 정체입니다.
긴 클로드 코드(Claude Code) 세션을 진행하다 보면, 작업 중이던 무언가가 조용히 개선을 멈추는 순간이 옵니다. 에이전트는 크래시하지도 않고, 경고도 하지 않습니다. 그저 자신의 컨텍스트 윈도우에서 여유 공간을 확보하는 능력을 잃고, 그 시점부터 모든 답변이 조금씩 나빠지기 시작합니다.
유출된 클로드 코드 소스에는 이 현상이 정확히 언제 발생하는지 설명하는 한 줄이 있습니다. autoCompact.ts 파일 안에 있는 이 코드입니다:
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;
그것뿐입니다. 세 번 실패하면 시스템은 포기합니다.
컴팩션(Compaction)은 컨텍스트 윈도우가 차오르기 시작할 때 에이전트가 긴 대화에서 공간을 확보하는 과정을 가리키는 용어입니다. @anthropic-ai/claude-code 버전 2.1.88이 npm에 배포될 때, 59.8MB짜리 JavaScript 소스 맵이 패키지 안에 함께 번들되었습니다. 배포 스크립트에 .map 파일에 대한 제외 규칙이 빠져 있었던 것입니다. 그 작은 실수로 약 512,000줄에 달하는 복잡한 클라이언트 소스가 세상에 공개되었습니다.
Anthropic은 몇 시간 만에 패키지를 내렸습니다. 회사는 보안 침해가 아닌 패키징 실수라고 확인했습니다. 그러나 npm 레지스트리는 관대하지 않았고, 자동 미러들이 이미 아티팩트를 복제해 두었습니다.
Anthropic이 4월 23일 품질 보고서에 대한 사후 분석(postmortem)을 공개할 무렵, 소스 맵을 가진 사람은 누구나 모든 개발자 머신에서 실제로 실행되고 있는 코드를 읽을 수 있었습니다.
Anthropic이 공개적으로 "모델을 감싸는 얇은 레이어"라고 설명한 것은, 실제로는 하나의 대화를 유지하기 위해 여덟 가지 다른 일을 하는 시스템이었습니다. 이 유출은 악의적인 음모를 드러내는 것이 아닙니다. 언어 모델을 궤도에서 벗어나지 않게 유지하는 데 얼마나 많은 작업이 필요한지를 드러냅니다.
긴 에이전트 대화는 끊임없이 토큰 한도에 부딪힙니다. 컨텍스트 윈도우는 파일 읽기, bash 출력, 에러 트레이스로 가득 찹니다. 원본 대화록을 매번 통째로 모델에 전달하는 것은 결국 크래시를 보장합니다.
이를 처리하는 순진한 방법은 대화가 너무 길어지면 전체를 요약하는 것입니다. 유출된 코드는 Anthropic이 그 접근을 첫 번째 방어선으로 명시적으로 거부했음을 보여줍니다. 요약은 값비싼 언어 모델 호출이 필요하고, 프롬프트 캐시까지 손상시킵니다. 대화 기록을 다시 쓰면 캐시된 프리픽스가 무효화되어 API 비용이 치솟고 지연 시간이 악화됩니다.
클로드 코드는 하나의 도구 대신 8가지 서로 다른 컴팩션 메커니즘을 사용합니다. 이들은 "가장 저렴한 것 우선(cheapest-first)" 원칙에 따라 엄격한 우선순위 순서로 실행됩니다. 모델 호출 없이 실행되는 모든 메커니즘은 토큰이 드는 어떤 메커니즘보다 먼저 실행됩니다. 시스템은 마지막에 스스로 요약하기 전에, 값싼 구조적 트릭으로 컨텍스트를 우아하게(gracefully) 저하시키려 시도합니다.
코드를 어떻게 자르느냐에 따라 5개 또는 7개로 셀 수 있습니다. 저는 8개로 셉니다. 캐시 기반 마이크로컴팩트와 시간 기반 마이크로컴팩트가 완전히 다른 신호와 상호 배타적인 코드 경로에서 실행되기 때문입니다.
다음은 정확히 발화 순서대로 나열한 8가지 메커니즘입니다:
prompt_too_long 오류를 반환할 때의 비상 폴백입니다.Tool Result Budget은 첫 번째 방어선입니다. 큰 터미널 출력을 공격적으로 다듬습니다. 4,000줄짜리 로그 파일을 요청하면, 파일이 기술적으로 여전히 디스크에 있더라도 나머지 대화는 짧은 미리보기만 보게 됩니다.
Snip은 두 번째 방어선입니다. 메시지가 토크나이저에 도달하기도 전에 배열에서 오래된 메시지를 버립니다. 그래서 에이전트가 터미널 창을 닫은 적이 없는데도, 같은 세션에서 앞서 논의한 특정 변수명을 갑자기 언급하지 않게 됩니다.
Cached Microcompact는 베타 cache_edits API를 사용합니다. 서버 측 프롬프트 캐시에서 오래된 도구 결과를 외과적으로 제거합니다. Read, Bash, Grep, Glob, WebSearch 같은 고정된 도구 허용 목록에 대해 매 턴 실행됩니다. 실제로 어떤 모습인지 보여드리면: 디버깅 세션의 청구 비용이 대화의 원시 토큰 수가 암시하는 것보다 훨씬 낮습니다.
Time-based Microcompact는 벽시계를 기준으로 오래된 도구 결과를 지웁니다. 무거운 텍스트를 리터럴 문자열 [Old tool result content cleared]로 교체합니다. 캐시 기반 버전과 상호 배타적입니다. 점심 먹고 돌아왔는데 세션의 오래된 도구 출력이 모두 사라져 있다면, 이 메커니즘이 발화한 것입니다.
Context Collapse는 내부 코드네임 Marble Origami로 불립니다. 비파괴적입니다. 원시 대화 배열을 영구히 다시 쓰는 대신, 축소(콜랩스)의 커밋 로그를 유지하고 매 턴 압축된 뷰를 투영합니다. UI에는 40개의 메시지 기록이 표시되지만, 에이전트는 그것들의 고수준 요약만 본 것처럼 답변합니다.
Auto-Compact는 무거운 일꾼(heavy lifter)입니다. 약 33,000토큰의 예약 버퍼를 두고 트리거됩니다. 9개의 고정 섹션으로 구조화된 요약을 생성하는 서브에이전트를 포크합니다. 그 신호는 명확합니다 — 30분쯤 지나면 에이전트가 갑자기 지금까지 한 일을 요약하고, 그 요약에서 이어서 진행합니다.
Reactive Compact는 비상 폴백입니다. API가 엄격히 prompt_too_long 오류를 던질 때만 실행됩니다. 모든 것을 공격적으로 압축합니다. 요약기 자체가 넘치면, 프롬프트가 들어갈 때까지 가장 오래된 API 라운드 그룹을 버립니다. 에이전트가 맥락을 완전히 잃지 않고 회복하기 전에, 터미널 트레이스에 "prompt too long" 오류가 잠깐 깜빡이는 것을 볼 수 있습니다.
Compaction Circuit Breaker는 이 글의 서두에 나온 상수입니다. 3회 연속 자동 컴팩션 실패 후, 세션의 나머지 동안 자동 컴팩션을 비활성화합니다. 이것이 바로 에이전트가 작동을 멈추고 다시는 회복되지 않는 상황을 만드는 정확한 메커니즘입니다.
그 순서 — pre-flight(사전), post-flight(사후), post-failure(실패 후) — 가 캐스케이드 디스패처가 실제로 인코딩하는 내용입니다:
/**
* compaction/cascade.ts
* Illustrative reconstruction of Claude Code's eight-mechanism compaction
* cascade as observed in the leaked source map. Identifiers differ from the
* leak, but this version preserves the priority order, the pre-flight /
* post-flight / post-failure split, the mutual exclusion, and the breaker.
*/
import type { ConversationState } from "../state";
import { toolResultBudget } from "./mechanisms/tool-result-budget";
import { snip } from "./mechanisms/snip";
import { cachedMicrocompact } from "./mechanisms/cached-microcompact";
import { timeBasedMicrocompact } from "./mechanisms/time-based-microcompact";
import { contextCollapse } from "./mechanisms/context-collapse";
import { autoCompact } from "./mechanisms/auto-compact";
import { reactiveCompact } from "./mechanisms/reactive-compact";
import { circuitBreaker } from "./mechanisms/circuit-breaker";
export const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;
export type MechanismName =
| "tool_result_budget"
| "snip"
| "cached_microcompact"
| "time_based_microcompact"
| "context_collapse"
| "auto_compact"
| "reactive_compact"
| "circuit_breaker";
export type CompactionResult =
| { kind: "noop"; mechanism: MechanismName }
| { kind: "applied"; mechanism: MechanismName; tokensFreed: number }
| { kind: "skipped"; mechanism: MechanismName; reason: string }
| { kind: "failed"; mechanism: MechanismName; error: Error };
export type Phase = "pre_flight" | "post_flight" | "post_failure";
export interface Mechanism {
readonly name: MechanismName;
readonly phase: Phase;
readonly priority: number;
shouldRun(state: ConversationState): boolean;
apply(state: ConversationState): Promise<CompactionResult>;
}
const MECHANISMS = [
toolResultBudget, // 1
snip, // 2
cachedMicrocompact, // 3
timeBasedMicrocompact, // 4
contextCollapse, // 5
autoCompact, // 6
reactiveCompact, // 7
circuitBreaker, // 8
] as const satisfies readonly Mechanism[];
export interface CascadeContext {
state: ConversationState;
consecutiveFailures: number;
lastUserActivityMs: number;
}
export async function runPreFlight(
ctx: CascadeContext,
): Promise<readonly CompactionResult[]> {
return runPhase("pre_flight", ctx);
}
export async function runPostFlight(
ctx: CascadeContext,
modelCallSucceeded: boolean,
): Promise<readonly CompactionResult[]> {
if (ctx.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
return [{
kind: "skipped",
mechanism: "auto_compact",
reason: "circuit_breaker_open",
}];
}
return runPhase(modelCallSucceeded ? "post_flight" : "post_failure", ctx);
}
async function runPhase(
phase: Phase,
ctx: CascadeContext,
): Promise<readonly CompactionResult[]> {
const results: CompactionResult[] = [];
const ordered = MECHANISMS
.filter((m): m is Mechanism => m.phase === phase)
.toSorted((a, b) => a.priority - b.priority);
for (const mechanism of ordered) {
if (!mechanism.shouldRun(ctx.state)) {
continue;
}
if (
mechanism.name === "cached_microcompact" &&
timeBasedAlreadyApplied(results)
) {
results.push({
kind: "skipped",
mechanism: "cached_microcompact",
reason: "cold_cache",
});
continue;
}
try {
const result = await mechanism.apply(ctx.state);
results.push(result);
// Stop the cascade once we have headroom
if (result.kind === "applied" && fitsInWindow(ctx.state)) {
return results;
}
} catch (caught) {
const error = caught instanceof Error ? caught : new Error(String(caught));
results.push({ kind: "failed", mechanism: mechanism.name, error });
if (mechanism.name === "auto_compact") {
ctx.consecutiveFailures += 1;
}
}
}
return results;
}
function timeBasedAlreadyApplied(
results: readonly CompactionResult[],
): boolean {
return results.some(r =>
r.kind === "applied" && r.mechanism === "time_based_microcompact"
);
}
function fitsInWindow(state: ConversationState): boolean {
const RESERVED_BUFFER = 33_000;
return state.estimatedTokens <= state.contextWindowMax - RESERVED_BUFFER;
}
API 표면은 아주 작습니다. runPreFlight와 runPostFlight 두 함수가 애플리케이션의 나머지가 접촉하는 모든 것을 처리합니다. 전체 오케스트레이션은 그 뒤에 숨겨져 있습니다. 캐시 기반/시간 기반 마이크로컴팩트 사이의 상호 배타성 검사는 단 두 줄입니다. 차가운 캐시(cold cache)에는 외과적 편집을 할 수 없으므로, 시간 기반 버전이 이미 발화했다면 시스템은 캐시 버전을 건너뜁니다.
맨 아래의 실패 카운팅 로직에 주목하세요. 서킷 브레이커에 집계되는 것은 오직 auto-compact 실패뿐입니다. 더 저렴한 메커니즘들은 세션을 영구히 불구로 만들지 않고 오류를 던질 수 있습니다.
auto-compact가 실제로 실행될 때, 캐시 안정성을 유지하기 위한 특정 트릭을 수행합니다. 요약을 작성하기 위해 서브에이전트(별도의 백그라운드 LLM 호출)를 포크하지만, 그 서브에이전트가 정확히 9개의 고정 섹션을 출력하도록 강제합니다. 부모 대화의 캐시 키를 서브에이전트에 직접 전달합니다. 요약기 호출은 메인 대화와 똑같은 시스템 프롬프트와 도구 정의를 보므로, 두 번째 패스에서 프리픽스 공유 컨텍스트는 거의 0토큰 비용이 듭니다.
/**
* compaction/mechanisms/auto-compact.ts
*
* Illustrative reconstruction of Claude Code's mechanism #6.
* The forked subagent emits a fixed nine-section summary.
* The fork passes through the parent conversation's cache-key parameters.
*/
import type { ConversationState } from "../../state";
import type { ModelClient } from "../../model";
export const SUMMARY_SECTIONS = [
"Primary Request and Intent",
"Key Technical Concepts",
"Files and Code Sections",
"Errors and Fixes",
"Problem Solving",
"All User Messages",
"Pending Tasks",
"Current Work",
"Optional Next Step",
] as const;
export type SummarySection = (typeof SUMMARY_SECTIONS)[number];
export type AutoCompactSummary = {
readonly [K in SummarySection]: string;
};
export interface CachePrefix {
readonly systemPrompt: string;
readonly toolDefinitionsHash: string;
readonly userContextHash: string;
}
export interface AutoCompactInput {
readonly state: ConversationState;
readonly model: ModelClient;
readonly cacheKey: CachePrefix;
}
const SUMMARIZER_INSTRUCTIONS = `You are summarizing a long conversation so it can be replaced with this
summary. Emit exactly nine sections, each beginning with the literal
section header on its own line. Do not add commentary. Do not omit a
section. If a section has no content, write "(none)" beneath the header.
Sections, in order:
${SUMMARY_SECTIONS.map((s, i) => ` ${i + 1}. ${s}`).join("\n")}`.trim();
export async function runAutoCompact(
input: AutoCompactInput,
): Promise<AutoCompactSummary> {
const { state, model, cacheKey } = input;
// The fork inherits the cache prefix verbatim.
const response = await model.complete({
cacheKey,
systemPrompt: cacheKey.systemPrompt,
messages: [
...state.messages,
{ role: "user", content: SUMMARIZER_INSTRUCTIONS },
],
maxOutputTokens: 8_000,
});
return parseSummary(response.text);
}
function parseSummary(raw: string): AutoCompactSummary {
const out = {} as Record<SummarySection, string>;
const lines = raw.split("\n");
let currentSection: SummarySection | null = null;
let buffer: string[] = [];
const flush = (): void => {
if (currentSection !== null) {
out[currentSection] = buffer.join("\n").trim();
}
};
for (const line of lines) {
const trimmed = line.trim();
const matched = SUMMARY_SECTIONS.find(
s => s.toLowerCase() === trimmed.toLowerCase(),
);
if (matched !== undefined) {
flush();
currentSection = matched;
buffer = [];
} else if (currentSection !== null) {
buffer.push(line);
}
}
flush();
// Fail closed. Any missing section is a parser error.
for (const section of SUMMARY_SECTIONS) {
if (!(section in out)) {
throw new Error(
`auto_compact: missing section "${section}" in summary output`,
);
}
}
return out as AutoCompactSummary;
}
9개 중 8개 섹션만 나오는 것은 저하된 요약이 아니라 버그이며, 파서는 그렇게 취급합니다. 파서는 실패 시 닫힘(fail closed) 방식입니다. 모델이 9개 대신 8개 섹션을 출력하면 파서는 특정 오류를 던집니다. 조용한 빈 문자열 기본값은 없습니다. 다운스트림 소비자는 9개 필드가 모두 존재한다고 가정하고, 파서는 그 계약을 엄격하게 강제합니다.
에이전트는 다음 메시지를 위해, 다음 세션을 위해, 프로젝트의 수명 동안 기억해야 합니다. 서로 다른 수명은 서로 다른 저장소를 필요로 합니다.
유출된 코드는 Anthropic이 메모리에 벡터 데이터베이스를 명시적으로 거부했음을 보여줍니다. 그들은 원시 파일, grep, 마크다운 인덱스를 선호했습니다. 그 이유는 아키텍처에 드러나 있습니다. 벡터 검색은 불투명하고 매 읽기마다 임베딩 모델이 필요하지만, 파일은 아무것도 필요로 하지 않습니다. 그리고 벡터가 조용히 최신성(recency)을 보상하는 반면, 마크다운 파일은 유지하는 한 구조를 보존합니다.
Tier 1: 컨텍스트 내 메모리(In-context memory). 활성 대화, 시스템 프롬프트, 도구 정의를 담습니다. 메모리 인덱스의 처음 200줄과 퇴거되지 않은(unevicted) 도구 결과를 담습니다. Tier 1을 아키텍처적으로 다른 두 계층과 구분 짓는 것은, 에이전트가 능동적으로 추론하는 유일한 계층이라는 점입니다. 나머지 두 계층은 이 계층으로 읽혀 들어옵니다. 영속성은 일시적이며, --continue나 --resume 플래그를 쓰지 않는 한 세션이 끝나면 사라집니다. 퇴거는 앞서 다룬 8가지 컴팩션 메커니즘에 전적으로 지배됩니다. 대화록은 로컬 .jsonl 파일로 기록되어 재개(resume)가 작동하지만, 런타임 메모리는 순수하게 프로세스 내부에만 있습니다. 작업 중간에 터미널을 닫으면 다음 세션은 방금 무엇을 하고 있었는지 전혀 모릅니다.
Tier 2: 영구 파일 메모리(Persistent file memory). MEMORY.md라는 포인터 인덱스와 debugging.md 같은 특정 주제 파일을 담습니다. 세션 대화록과 Tier 1의 도구 결과 오버플로(spillover)를 담습니다. 영속성은 세션 재시작, 머신 재시작, 명시적 클리어 명령에서도 살아남습니다. 이 계층은 자가 치유(self-healing)입니다. autoDream이라는 포크된 백그라운드 서브에이전트를 사용합니다. 이 에이전트는 삼중 관문(triple-gate)을 통과한 후에만 실행됩니다: 마지막 통합 후 최소 24시간 경과, 마지막 주기 후 최소 5개 세션, 그리고 파일 기반 조언 잠금(advisory lock) 획득. 관문이 세 개인 이유는, 활성 세션 중간에 메모리를 통합하는 것이 전혀 통합하지 않는 것보다 나쁘기 때문입니다. 최근 신호를 읽고, 통합하고, 인덱스를 정리합니다. 그래서 에이전트가 아무도 다시 언급하지 않았는데 3주 전 다른 브랜치에서 알려준 사실을 갑자기 언급하는 것입니다.
Tier 2의 파일 시스템 레이아웃은 소스에 명시적으로 이름이 붙어 있습니다:
~/.claude/projects/<project>/memory/MEMORY.md
~/.claude/projects/<project>/memory/<topic>.md
~/.claude/projects/<project>/sessions/*.jsonl
중요한 점은 MEMORY.md가 정보를 직접 저장하지 않는다는 것입니다. 정보의 위치를 저장합니다. 줄당 약 150자로 제한된 포인터 인덱스입니다. 처음 25KB는 세션 시작 시 Tier 1로 스트리밍됩니다. 매 세션 시작마다 전체 메모리 디렉터리를 로드하는 것은 컴팩션의 목적을 완전히 무너뜨리기 때문입니다.
Tier 3: 지침 메모리(Instruction memory). 이는 CLAUDE.md 계층 구조입니다. 인간이 작성한 프로젝트 규칙, 컨벤션, 아키텍처 노트를 담습니다. 매 세션 시작마다 읽힙니다. 이 계층이 불균형적으로 중요한 이유는 시스템 프롬프트의 동적 경계(dynamic boundary) 위에 있기 때문입니다. auto-compact에서 완전히 변경되지 않고 살아남는 유일한 것입니다. 그래서 에이전트가 항상 당신의 빌드 명령을 아는 것입니다.
Tier 3의 해석 체인(resolution chain)은 가장 구체적인 파일이 이기는 엄격한 우선순위 순서를 따릅니다:
/etc/claude-code/CLAUDE.md — 조직 전역 규칙용.~/.claude/CLAUDE.md — 모든 프로젝트에 걸친 사용자 규칙용.<project-root>/CLAUDE.md — 버전 관리되는 프로젝트 규칙용.<project-root>/.claude/rules/*.md — 모듈식 규칙용.<project-root>/<subdirectory>/CLAUDE.md — 디렉터리별 지침용.<project-root>/CLAUDE.local.md — 개인 gitignored 노트용.이 해석 체인이 코드로 구현된 방식은 다음과 같습니다.
/**
* memory/claude-md-resolver.ts
*
* Illustrative reconstruction of Claude Code's CLAUDE.md resolution chain.
* Six layers where most-specific wins. Results are returned in apply-order
* so callers fold them with later-overrides-earlier semantics.
*/
import { readFile, readdir, stat } from "node:fs/promises";
import { homedir } from "node:os";
import { dirname, join, resolve, sep } from "node:path";
export type ResolutionLayer =
| "global"
| "user"
| "project_root"
| "project_rules"
| "subdirectory"
| "personal";
export interface ResolvedInstruction {
readonly source: ResolutionLayer;
readonly path: string;
readonly content: string;
}
export interface ResolveOptions {
readonly projectRoot: string;
readonly currentFile?: string;
}
export async function resolveClaudeMd(
opts: ResolveOptions,
): Promise<readonly ResolvedInstruction[]> {
const root = resolve(opts.projectRoot);
const found: ResolvedInstruction[] = [];
await tryAdd(found, "global", "/etc/claude-code/CLAUDE.md");
await tryAdd(found, "user", join(homedir(), ".claude", "CLAUDE.md"));
await tryAdd(found, "project_root", join(root, "CLAUDE.md"));
await addRulesDir(found, join(root, ".claude", "rules"));
if (opts.currentFile !== undefined) {
const leaf = resolve(root, opts.currentFile);
// Confine the walk to the project root.
if (leaf === root || leaf.startsWith(root + sep)) {
const chain: string[] = [];
let dir = dirname(leaf);
while (dir.startsWith(root) && dir !== root) {
chain.unshift(join(dir, "CLAUDE.md")); // root-to-leaf order
dir = dirname(dir);
}
for (const path of chain) {
await tryAdd(found, "subdirectory", path);
}
}
}
await tryAdd(found, "personal", join(root, "CLAUDE.local.md"));
return found;
}
async function tryAdd(
out: ResolvedInstruction[],
source: ResolutionLayer,
path: string,
): Promise<void> {
try {
const s = await stat(path);
if (!s.isFile()) return;
const content = await readFile(path, "utf8");
out.push({ source, path, content });
} catch {
// Missing files are normal. Most layers are empty.
}
}
async function addRulesDir(
out: ResolvedInstruction[],
dir: string,
): Promise<void> {
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return;
}
const files = entries
.filter(e => e.isFile() && e.name.endsWith(".md"))
.map(e => e.name)
.toSorted();
for (const name of files) {
const path = join(dir, name);
const content = await readFile(path, "utf8");
out.push({ source: "project_rules", path, content });
}
}
await tryAdd 호출 순서가 곧 우선순위 체인입니다. 위에서 아래로 읽으면 6계층 목록이 나옵니다. tryAdd 함수는 디자인상 대부분의 계층이 비어 있을 것을 예상하므로 누락된 파일을 조용히 삼킵니다. 하위 디렉터리 탐색은 chain.unshift를 사용해 잎→뿌리(leaf-to-root) 탐색을 뿌리→잎(root-to-leaf) 삽입 순서로 뒤집으므로, 결과를 접는(folding) 호출자는 올바른 우선순위를 얻습니다. 이것이 auto-compact에서 살아남는 것입니다. 6번 메커니즘은 Tier 1을 지우고 요약으로 다시 쓰지만, 이 리졸버가 반환한 것은 Tier 3에 손대지 않은 채 보존됩니다.
유출된 소스에서 Tengu라는 문자열을 grep하면 1,000개가 넘는 결과가 나옵니다. Tengu는 클로드 코드의 내부 프로젝트 코드네임입니다. 그 프리픽스를 벗기면 그 아래의 다음 레이어는 Anthropic이 동작을 배포하고 게이트하기 위해 사용한 기능 플래그 집합입니다. 44개가 있습니다.
44개 플래그는 빠르게 움직이는 팀이 어떻게 배포하는지 보여줍니다. 핵심 질문은 각 플래그가 실제로 무엇을 켜는지입니다. 두 그룹으로 나뉩니다: 배포됐지만 문서화되지 않은 것과 완전히 미공개인 것.
오늘 고객 머신에서 이미 실행 중이지만, 어떤 문서 페이지에도 언급된 적 없는 플래그들입니다.
안티 증류 및 가짜 도구(Anti-distillation and fake tools). ANTI_DISTILLATION_CC 플래그가 켜져 있으면 API 요청에 anti_distillation: ['fake_tools'] 지시문이 포함됩니다. 서버는 시스템 프롬프트에 미끼(decoys) 도구 정의를 주입합니다. 목적은 클로드 코드의 동작을 복제하려고 API 트래픽을 기록하는 사람에게 잡힌 훈련 데이터를 오염시키는 것입니다. 에이전트가 문서화된 적 없고 공식 도구 목록에도 없는 도구 이름을 가끔 언급하는 것은 이 때문입니다.
좌절 감지 정규식(Frustration regex). userPromptKeywords.ts 파일에는 "wtf", "this sucks" 같은 욕설과 좌절 표현을 매칭하는 정규식이 있습니다. 매칭은 다운스트림 동작을 바꿉니다. 개발자가 좌절하며 에이전트에게 욕을 한 후, 다음 답변은 눈에 띄게 더 신중하고 사과하는 톤이 되지만, 그 한 번뿐입니다.
잠행 모드(Undercover mode). 환경 변수 CLAUDE_CODE_UNDERCOVER=1은 클로드 코드를 출력에서 Anthropic 식별자를 제거하는 모드로 전환합니다. Anthropic 엔지니어들이 AI 저자임을 드러내지 않고 오픈소스 소프트웨어에 기여할 때 사용합니다. 비대칭적입니다. 환경 변수가 모드를 켜도록 강제할 수는 있지만, 끄도록 강제할 수는 없습니다. 공개 저장소의 Anthropic 직원 커밋에는 다른 모든 클로드 코드 커밋에 붙는 Co-Authored-By: Claude 줄이 없습니다.
조용한 모델 다운그레이드(Silent model downgrade). 특정 서버 오류가 발생하면 클로드 코드는 요청의 나머지 동안 Opus에서 Sonnet으로 조용히 폴백합니다. 사용자는 저하된 응답이 아닌 성공적인 응답을 봅니다. Opus 세션이 중간쯤에서 조용히 약간 멍해지고, 오류 메시지가 없었는데도 결코 회복되지 않는 것은 이 때문입니다.
직원 전용 검증 게이트(Employee-only verification gate). 생성된 diff를 실제로 컴파일되는지 재실행해 확인하는 검증 루프를 게이트하는 플래그입니다. Anthropic 엔지니어들은 이것을 실행했습니다. 고객은 실행하지 못했습니다. Anthropic 엔지니어들의 생성 diff는 외부 사용자가 로컬에서 같은 모델을 실행할 때보다 눈에 띄게 더 신뢰할 만합니다.
그리고 아직 배포되지 않은 플래그들도 있습니다. 소스에는 존재하지만 고객 빌드에는 연결되지 않았습니다.
KAIROS. 세션을 가로질러 지속되는 항상 켜진(always-on) 백그라운드 데몬입니다. 주기적인 틱(tick) 프롬프트를 받고, GitHub 웹훅을 모니터링하며, 독립적으로 행동을 취할 수 있습니다. 소스에 150개가 넘는 참조가 있습니다.
autoDream. 앞서 다룬 Tier 2 통합 서브에이전트입니다. KAIROS의 유휴 모드 아래에 게이트되어 있습니다. 현재 배포된 빌드에서 활성인지는 불분명하지만, 게이트는 크게 제한되어 있음을 시사합니다.
ULTRAPLAN. 깊은 계획 세션을 최대 30분 동안 원격 Opus 인스턴스에 오프로드합니다. 전용 플래닝 모드 변형입니다.
COORDINATOR_MODE. 구조화된 연구, 종합, 구현 단계를 가진 멀티 에이전트 스웜입니다.
유출은 내부 모델 코드네임도 드러냈습니다. Tengu는 클로드 코드 자체입니다. Capybara는 Mythos 변형으로 보입니다. Fennec은 Opus 4.6에 대응합니다. Numbat은 아직 테스트 중인 미공개 모델입니다. 이들은 소스 코드 라우팅 로직을 통해 전파되는 이름들일 뿐입니다.
소스 맵에 무엇이 들어 있는지에는 분명한 한계가 있습니다. npm 패키지에는 클라이언트 CLI만 있으므로 모델 가중치, 파인튜닝 데이터, 강화학습 커리큘럼, 프로덕션 서버 코드가 없습니다. 고객 데이터도 자격 증명도 없습니다. 이것은 하네스(harness)이지, 모델이 아닙니다.
하지만 하네스야말로 빌더가 정확히 봐야 하는 것입니다. 8가지 컴팩션 메커니즘, 3가지 메모리 계층, 44개 플래그는 특정 모델 때문이 아니라 에이전트 문제의 형태 때문에 존재합니다. Anthropic이 내일 Opus를 차세대 모델로 교체해도 하네스는 정확히 같은 8가지를 수행할 것입니다. 같은 3계층에 걸쳐 저장할 것입니다. 대부분의 플래그를 그대로 둘 것입니다.
컴팩션은 계층화되어야 하고(layer), 메모리는 계층화되어야 하며(tier), 플래그는 계속 늘어날 것입니다. 이 유출은 Anthropic을 들여다보는 창이 아닙니다. 2027년에 당신의 스택이 무엇이 될지에 대한 미리보기입니다.
이 유출이 개발자 커뮤니티에서 크게 주목받는 이유는 단순한 호기심이 아닙니다:
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3라는 상수 하나가 그 현상을 정확히 설명합니다. 3번 연속 실패하면 남은 세션 동안 자동 컴팩션이 꺼집니다.CLAUDE.md에 잘 정리해 두는 것이 얼마나 중요한지를 설명합니다.클로드 코드의 유출 소스맵은 AI 에이전트의 "내부 장기"를 한눈에 보여준 최초의 사례입니다. 모델 자체가 아니라 모델을 둘러싼 하네스 — 컴팩션 캐스케이드, 메모리 계층, 기능 플래그 — 가 실제로 에이전트의 생존 능력을 결정합니다. 그리고 그 하네스의 모양은 곧 모든 AI 에이전트 스택의 표준이 될 것입니다.
Halfway through a long Claude Code session, the thing you’ve been working on quietly stops getting better. The agent doesn’t crash. It doesn’t warn you. But it just stops being able to free up room in its own context window, and from that point on every reply gets a little bad.
There’s a line in the leaked Claude Code source that explains exactly when this happens. It sits in the fileautoCompact.ts:
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;
That’s it. Three failures and the system gave up.
Compaction is the agent’s word for freeing up room in a long conversation when the context window starts to fill. When the npm package@anthropic-ai/claude-codeversion 2.1.88 published, it shipped with a 59.8 megabyte JavaScript source map bundled inside it. The deployment script simply lacked an exclusion rule for.mapfiles. That tiny omission resulted into roughly 512,000 lines of complicate…
Anthropic removed the package within hours. The company confirmed it was a packaging mistake rather than a security breach. But the npm registry is unforgiving, and automated mirrors had already cloned the artifact.
By the time Anthropic published their April 23 postmortem addressing recent quality reports, anyone with the source map could read exactly what was running on every developer’s machine.
What Anthropic publicly described as a thin layer around their models turns out to be a system that does eight different things just to keep one conversation alive. The leak does not reveal a malicious conspiracy. It reveals how much work is required to keep a language model on track.
A long agent conversation hits token limits constantly. The context window fills up with file reads, bash outputs, and error traces. Passing the raw transcript to the model every single time eventually guarantees a crash.
The naive way to handle this is to summarize the whole conversation when it gets too long. The leaked code shows Anthropic explicitly rejected that approach as a first line of defense. Summarization requires an expensive language model call which also compromises the prompt cache. Rewriting the conversation history invalidates the cached prefix, causing API costs to skyrocket and latency to degrade.
Claude Code useseight different compaction mechanismsinstead of one tool. They run in a strict priority order based on a cheapest-first principle. Every mechanism that runs without a model call executes before any mechanism that costs tokens. The system tries to gracefully degrade the context using cheap structural tricks before it finally stops to summarize itself.
Depending on how you cut the code, you can count five or seven mechanisms. I count eight because the cached and time-based microcompact functions run on completely different signals and mutually exclusive code paths.
Here are the eight mechanisms in the exact order they fire:
TheTool Result Budgetis the first line of defense. It aggressively trims large terminal outputs. If you ask for a 4,000-line log file, the rest of the conversation only sees a short preview, even though the file is technically still on disk.
Snipis the second defense. It drops older messages from the array before they even reach the tokenizer. This is why the agent suddenly stops referencing a specific variable name discussed earlier in the exact same session, even though the terminal window was never closed.
Cached Microcompact uses a betacache_editsAPI. It surgically removes stale tool results from the server-side prompt cache. It runs every turn for a fixed allowlist of tools like Read, Bash, Grep, Glob, and WebSearch. What this looks like in practice: the billing cost of a debugging session is much lower than the raw token count of the conversation suggests.
Time-based Microcompact wipes stale tool results based on the wall clock. It replaces the heavy text with the literal string[Old tool result content cleared]. It is mutually exclusive with the cached version. If you come back from lunch to a session where the older tool outputs have all vanished, this is what fired.
Context Collapsegoes by the internal codename Marble Origami. It is non-destructive. It keeps a commit log of collapses and projects a compacted view each turn instead of permanently rewriting the raw conversation array. The UI shows 40 messages of history, but the agent answers as if it had only seen a high-level summary of them.
Auto-Compactis the heavy lifter. It triggers with a roughly 33,000-token reserved buffer. It forks a subagent that produces a structured summary in nine fixed sections. The tell is unmistakable — thirty minutes in, the agent suddenly summarizes what you’ve done and continues from the summary.
Reactive Compactis the emergency fallback. It only runs when the API strictly throws aprompt_too_longerror. It aggressively compacts everything. If the summarizer itself overflows, it drops the oldest API-round groups until the prompt fits. You might see a prompt too long error flicker briefly in the terminal trace before the agent recovers without losing the thread completely.
The Compaction Circuit Breakeris the constant from our opening. After three consecutive auto-compact failures, it disables auto-compaction for the rest of the session. This is the exact mechanism that leaves you with an agent that stops working and never recovers.
That ordering — pre-flight, post-flight, post-failure — is what the cascade dispatcher actually encodes:
/** * compaction/cascade.ts * Illustrative reconstruction of Claude Code's eight-mechanism compaction * cascade as observed in the leaked source map. Identifiers differ from the * leak, but this version preserves the priority order, the pre-flight / * post-flight / post-failure split, the mutual exclusion, and the breaker. */import type { ConversationState } from "../state";import { toolResultBudget } from "./mechanisms/tool-result-budget";import { snip } from "./mechanisms/snip";import { cachedMicrocompact } from "./mechanisms/cached-microcompact";import { timeBasedMicrocompact } from "./mechanisms/time-based-microcompact";import { contextCollapse } from "./mechanisms/context-collapse";import { autoCompact } from "./mechanisms/auto-compact";import { reactiveCompact } from "./mechanisms/reactive-compact";import { circuitBreaker } from "./mechanisms/circuit-breaker";export const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;export type MechanismName = | "tool_result_budget" | "snip" | "cached_microcompact" | "time_based_microcompact" | "context_collapse" | "auto_compact" | "reactive_compact" | "circuit_breaker";export type CompactionResult = | { kind: "noop"; mechanism: MechanismName } | { kind: "applied"; mechanism: MechanismName; tokensFreed: number } | { kind: "skipped"; mechanism: MechanismName; reason: string } | { kind: "failed"; mechanism: MechanismName; error: Error };export type Phase = "pre_flight" | "post_flight" | "post_failure";export interface Mechanism { readonly name: MechanismName; readonly phase: Phase; readonly priority: number; shouldRun(state: ConversationState): boolean; apply(state: ConversationState): Promise<CompactionResult>;}const MECHANISMS = [ toolResultBudget, // 1 snip, // 2 cachedMicrocompact, // 3 timeBasedMicrocompact, // 4 contextCollapse, // 5 autoCompact, // 6 reactiveCompact, // 7 circuitBreaker, // 8] as const satisfies readonly Mechanism[];export interface CascadeContext { state: ConversationState; consecutiveFailures: number; lastUserActivityMs: number;}export async function runPreFlight( ctx: CascadeContext,): Promise<readonly CompactionResult[]> { return runPhase("pre_flight", ctx);}export async function runPostFlight( ctx: CascadeContext, modelCallSucceeded: boolean,): Promise<readonly CompactionResult[]> { if (ctx.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) { return [{ kind: "skipped", mechanism: "auto_compact", reason: "circuit_breaker_open", }]; } return runPhase(modelCallSucceeded ? "post_flight" : "post_failure", ctx);}async function runPhase( phase: Phase, ctx: CascadeContext,): Promise<readonly CompactionResult[]> { const results: CompactionResult[] = []; const ordered = MECHANISMS .filter((m): m is Mechanism => m.phase === phase) .toSorted((a, b) => a.priority - b.priority); for (const mechanism of ordered) { if (!mechanism.shouldRun(ctx.state)) { continue; } if ( mechanism.name === "cached_microcompact" && timeBasedAlreadyApplied(results) ) { results.push({ kind: "skipped", mechanism: "cached_microcompact", reason: "cold_cache", }); continue; } try { const result = await mechanism.apply(ctx.state); results.push(result); // Stop the cascade once we have headroom if (result.kind === "applied" && fitsInWindow(ctx.state)) { return results; } } catch (caught) { const error = caught instanceof Error ? caught : new Error(String(caught)); results.push({ kind: "failed", mechanism: mechanism.name, error }); if (mechanism.name === "auto_compact") { ctx.consecutiveFailures += 1; } } } return results;}function timeBasedAlreadyApplied( results: readonly CompactionResult[],): boolean { return results.some(r => r.kind === "applied" && r.mechanism === "time_based_microcompact" );}function fitsInWindow(state: ConversationState): boolean { const RESERVED_BUFFER = 33_000; return state.estimatedTokens <= state.contextWindowMax - RESERVED_BUFFER;}
The API surface is tiny — two functions,runPreFlightandrunPostFlight, handle everything the rest of the application touches. The entire orchestration is hidden behind them. The mutual exclusion check between cached and time-based microcompact is just two lines of code. You cannot surgically edit a cold cache, so the system skips the cached version if the time-based version already fired.
Notice the failure counting logic at the bottom. Only auto-compact failures count toward the circuit breaker. The cheaper mechanisms are allowed to throw errors without permanently crippling the session.
When auto-compact actually runs, it executes a specific trick to preserve cache stability. It forks a subagent (a separate, background LLM call) to write a summary, but it forces that subagent to output exactly nine fixed sections. It passes the parent conversation’s cache key directly to the subagent. The summarizer call sees the exact same system prompt and tool definitions as the main conversation, meaning the prefix-shared context costs almost zero tokens on the second pass.
/** * compaction/mechanisms/auto-compact.ts * * Illustrative reconstruction of Claude Code's mechanism #6. * The forked subagent emits a fixed nine-section summary. * The fork passes through the parent conversation's cache-key parameters. */import type { ConversationState } from "../../state";import type { ModelClient } from "../../model";export const SUMMARY_SECTIONS = [ "Primary Request and Intent", "Key Technical Concepts", "Files and Code Sections", "Errors and Fixes", "Problem Solving", "All User Messages", "Pending Tasks", "Current Work", "Optional Next Step",] as const;export type SummarySection = (typeof SUMMARY_SECTIONS)[number];export type AutoCompactSummary = { readonly [K in SummarySection]: string;};export interface CachePrefix { readonly systemPrompt: string; readonly toolDefinitionsHash: string; readonly userContextHash: string;}export interface AutoCompactInput { readonly state: ConversationState; readonly model: ModelClient; readonly cacheKey: CachePrefix;}const SUMMARIZER_INSTRUCTIONS = `You are summarizing a long conversation so it can be replaced with thissummary. Emit exactly nine sections, each beginning with the literalsection header on its own line. Do not add commentary. Do not omit asection. If a section has no content, write "(none)" beneath the header.Sections, in order:${SUMMARY_SECTIONS.map((s, i) => ` ${i + 1}. ${s}`).join("\n")}`.trim();export async function runAutoCompact( input: AutoCompactInput,): Promise<AutoCompactSummary> { const { state, model, cacheKey } = input; // The fork inherits the cache prefix verbatim. const response = await model.complete({ cacheKey, systemPrompt: cacheKey.systemPrompt, messages: [ ...state.messages, { role: "user", content: SUMMARIZER_INSTRUCTIONS }, ], maxOutputTokens: 8_000, }); return parseSummary(response.text);}function parseSummary(raw: string): AutoCompactSummary { const out = {} as Record<SummarySection, string>; const lines = raw.split("\n"); let currentSection: SummarySection | null = null; let buffer: string[] = []; const flush = (): void => { if (currentSection !== null) { out[currentSection] = buffer.join("\n").trim(); } }; for (const line of lines) { const trimmed = line.trim(); const matched = SUMMARY_SECTIONS.find( s => s.toLowerCase() === trimmed.toLowerCase(), ); if (matched !== undefined) { flush(); currentSection = matched; buffer = []; } else if (currentSection !== null) { buffer.push(line); } } flush(); // Fail closed. Any missing section is a parser error. for (const section of SUMMARY_SECTIONS) { if (!(section in out)) { throw new Error( `auto_compact: missing section "${section}" in summary output`, ); } } return out as AutoCompactSummary;}
Eight sections out of nine isn’t a degraded summary, it’s a bug — and the parser treats it that way. The parser fails closed. If the model emits eight sections instead of nine, the parser throws a specific error. There are no silent empty-string defaults. The downstream consumer assumes all nine fields will exist, and the parser enforces that contract strictly.
An agent must remember things for the next message, for the next session, and for the lifetime of the project. Those different lifetimes need different stores.
The leak shows that Anthropic explicitly rejected vector databases for memory. They favored raw files, grep, and a markdown index. The reasoning is visible in the architecture. Vector retrieval is opaque and needs an embedding model on every read — files don’t need anything. And where vectors quietly reward recency, a markdown file preserves structure as long as you keep it.
Tier 1 is In-context memory.It holds the active conversation, the system prompt, and the tool definitions. It holds the first 200 lines of the memory index and the unevicted tool results. What makes Tier 1 architecturally different from the other two is that it’s the only tier the agent actively reasons over; the other two get read into it. Persistence is ephemeral and is gone at session end unless the--continueor--resumeflags are used. Eviction is governed entirely by the eight compaction mechanisms we just covered. Transcripts are written to a local.jsonlfile so resuming works, but the runtime memory is purely in-process. Close the terminal mid-task and the next session has no idea what you were just doing.
Tier 2 is Persistent file memory.This holds the pointer index calledMEMORY.mdand specific topic files likedebugging.md. It holds the session transcripts and the tool result spillover from Tier 1. Persistence survives session restarts, machine restarts, and explicit clear commands. This tier is self-healing. It uses a forked background subagent calledautoDream. This agent runs after a triple-gate of at least 24 hours since the last consolidation, at least 5 sessions since the last cycle, and the acquisition of a file-based advisory lock. Three gates because consolidating memory in the middle of an active session is worse than not consolidating at all. It reads recent signals, consolidates them, and prunes the index. This explains why the agent suddenly references a fact you told it three weeks ago on a different branch without anyone re-mentioning it.
The file system layout for Tier 2 is named explicitly in the source:~/.claude/projects//memory/MEMORY.md~/.claude/projects//memory/.md~/.claude/projects//sessions/*.jsonl
It is important to understand thatMEMORY.mddoes not store information directly. It stores the locations of information. It is a pointer index limited to about 150 characters per line. The first 25 kilobytes are streamed into Tier 1 at session start. Loading the full memory directory on every session start would defeat the point of compaction entirely.
Tier 3 is Instruction memory.This is theCLAUDE.mdhierarchy. It holds project rules, conventions, and architecture notes written by humans. It is read at every single session start. This tier matters disproportionately because it sits above the dynamic boundary in the system prompt. It is the only thing that survives auto-compact completely unchanged. This is what makes the agent always know your build commands.
The resolution chain for Tier 3 follows a strict precedence order where the most specific file wins:
Here is how that resolution chain is implemented in code.
/** * memory/claude-md-resolver.ts * * Illustrative reconstruction of Claude Code's CLAUDE.md resolution chain. * Six layers where most-specific wins. Results are returned in apply-order * so callers fold them with later-overrides-earlier semantics. */import { readFile, readdir, stat } from "node:fs/promises";import { homedir } from "node:os";import { dirname, join, resolve, sep } from "node:path";export type ResolutionLayer = | "global" | "user" | "project_root" | "project_rules" | "subdirectory" | "personal"; export interface ResolvedInstruction { readonly source: ResolutionLayer; readonly path: string; readonly content: string;}export interface ResolveOptions { readonly projectRoot: string; readonly currentFile?: string;}export async function resolveClaudeMd( opts: ResolveOptions,): Promise<readonly ResolvedInstruction[]> { const root = resolve(opts.projectRoot); const found: ResolvedInstruction[] = []; await tryAdd(found, "global", "/etc/claude-code/CLAUDE.md"); await tryAdd(found, "user", join(homedir(), ".claude", "CLAUDE.md")); await tryAdd(found, "project_root", join(root, "CLAUDE.md")); await addRulesDir(found, join(root, ".claude", "rules")); if (opts.currentFile !== undefined) { const leaf = resolve(root, opts.currentFile); // Confine the walk to the project root. if (leaf === root || leaf.startsWith(root + sep)) { const chain: string[] = []; let dir = dirname(leaf); while (dir.startsWith(root) && dir !== root) { chain.unshift(join(dir, "CLAUDE.md")); // root-to-leaf order dir = dirname(dir); } for (const path of chain) { await tryAdd(found, "subdirectory", path); } } } await tryAdd(found, "personal", join(root, "CLAUDE.local.md")); return found;}async function tryAdd( out: ResolvedInstruction[], source: ResolutionLayer, path: string,): Promise<void> { try { const s = await stat(path); if (!s.isFile()) return; const content = await readFile(path, "utf8"); out.push({ source, path, content }); } catch { // Missing files are normal. Most layers are empty. }}async function addRulesDir( out: ResolvedInstruction[], dir: string,): Promise<void> { let entries; try { entries = await readdir(dir, { withFileTypes: true }); } catch { return; } const files = entries .filter(e => e.isFile() && e.name.endsWith(".md")) .map(e => e.name) .toSorted(); for (const name of files) { const path = join(dir, name); const content = await readFile(path, "utf8"); out.push({ source: "project_rules", path, content }); }}
The order ofawait tryAddcalls is the precedence chain. You read top-to-bottom and you have the six-layer list. ThetryAddfunction swallows missing files silently because the design expects most layers to be empty. The subdirectory walk useschain.unshiftto flip leaf-to-root traversal into root-to-leaf insertion order, so callers folding the results get the right precedence. This is what survives auto-compact. Mechanism number six clears Tier 1 and rewrites it as a summary, but whatever this resolver returned is preserved untouched in Tier 3.
If you grep the leaked source for the stringTengu, you get over a thousand hits. Tengu is Claude Code's internal project codename. Strip that prefix away and the next layer underneath is the feature-flag set Anthropic was using to ship and gate behavior. There are44of them.
44flags shows how a fast-moving team ships. The question is what each flag actually turns on. They fall into two groups:shipped but undocumented, and completely unreleased.
Here are the flags that are already running on customer machines today, just never mentioned in any docs page.
Anti-distillation and fake tools.When theANTI_DISTILLATION_CCflag is on, the API request includes ananti_distillation: ['fake_tools']directive. The server injects decoy tool definitions into the system prompt. The purpose is to poison training data captured by anyone recording API traffic to clone Claude Code's behavior. The agent occasionally references a tool name you have never seen documented and cannot find in the official tool list.
Frustration regex.The fileuserPromptKeywords.tscontains a regex matching profanity and frustration phrases like "wtf" and "this sucks". The match changes downstream behavior. After a developer curses at the agent in frustration, the next reply is noticeably more careful and apologetic, but only that one time.
Undercover mode.The environment variableCLAUDE_CODE_UNDERCOVER=1switches Claude Code into a mode that strips Anthropic identifiers from output. This is used by Anthropic engineers contributing to open source software without revealing AI authorship. It is asymmetric. The environment variable can force the mode on, but cannot force it off. An Anthropic employee commit on a public repository does not carry theCo-Authored-By: Claudeline every other Claude Code commit ships with.
Silent model downgrade. On certain server errors, Claude Code silently falls back from Opus to Sonnet for the rest of the request. The user sees a successful response, not a degraded one. An Opus session quietly gets slightly dumber halfway through and never recovers, even though there was no error message.
Employee-only verification gate. A flag gates a verification loop that re-runs a generated diff to check it actually compiles before returning. Anthropic engineers ran this. Customers did not. Anthropic engineers’ generated diffs feel notably more reliable than the same model running locally for an external user.
Then there are the flags that are not shipped yet. These are present in the source but not yet wired into the customer build.
KAIROS. This is an always-on background daemon that persists across sessions. It takes periodic tick prompts, monitors GitHub webhooks, and can independently take actions. There are over 150 references to it in the source.
autoDream. This is the Tier 2 consolidation subagent we covered earlier. It is gated under KAIROS’s idle mode. Whether it is currently active in shipped builds is unclear, but the gate suggests it is heavily restricted.
ULTRAPLAN. This offloads deep planning sessions to a remote Opus instance for up to 30 minutes. It is a dedicated planning-mode variant.
COORDINATOR_MODE. This is a multi-agent swarm with structured research, synthesis, and implementation phases.
The leak also surfaced the internal model codenames. Tengu is Claude Code itself. Capybara appears to be the Mythos variant. Fennec maps to Opus 4.6. Numbat is an unreleased model still in testing. These are just the names that propagate through the source code routing logic.
There is a clear limit to what is in the source map. It contains no model weights, fine-tuning data, reinforcement learning curriculum or production server code, because only the client CLI is in the npm package. It contains no customer data and no credentials. This is the harness, not the model.
But the harness is exactly what builders need to see. The 8 compaction mechanisms, the 3 memory tiers, and the 44 flags exist because of the shape of the agent problem, not because of any specific model. Anthropic could swap Opus for a next-generation model tomorrow, and the harness would still do the exact same 8 things. It would store across the same three tiers. It would leave most of the same flags in place.
Compaction has to layer. Memory has to tier. The flags will keep multiplying. The leak isn’t a window into Anthropic. It is a preview of what your stack becomes by 2027.



A couple of days ago, I was working on a product pricing plan and asked Claude Code for some ideas.
If you don’t have a Medium subscription, use this link to read the full article: Link
It gave me four directions. But after reading it, I realized that this was the same answer I could get by simply asking Google Search:
Tiered pricing based on usage (Free, Pro), pay-as-you-go billing, Token Plan, free advertising.
These answers were textbook-correct, but not the kind of insightful “You can do that? Then I suddenly understand” answer I was looking for.
Is it possible to make Claude Code’s output less boring?
I tried several things, such as modifying the Agent’s.md file, but the results were mediocre.
Until a few days ago, I suddenly found a helpful answer to this question:
I saw a post on Reddit titled “I gave Claude Code ADHD…and it thinks 2x better now”.
The author of the post mainly conducts research on medical AI safety. He used the Skill method to incorporate ADHD thinking into Claude Code and even wrote a paper on this Skill.
People with ADHD often experience a chaotic mix of information in their minds, making it difficult to decide “where to start,” and thus wasting time. This is a characteristic known as “weak executive function,” and it is completely different from a lack of effort.
If you like this topic and you want to support me:
When you ask an AI tool, “I’ve made a list of things to do today. Tell me in what order I should do them,” the AI will suggest a logical order without emotion or bias. This “outsourcing of structuring work” significantly reduces the burden on execution functions.
One thing immediately came to mind: Could this skill help me turn Claude Code into ADHD? That would be awesome.
So I spent two hours reading through the paper and the code.
After watching it, I felt that this skill is really valuable in certain specific scenarios.
Truth often lies in the hands of a minority.
The biggest problem with large models right now is not illusions or incorrect answers, but rather that the answers are too “correct”.
The reason for this overly accurate prediction is the autoregressive generation mechanism of LLMs:
It ensures that each token is sampled based on the conditional probability distribution mentioned earlier. Although there are random adjustments such as temperature, the overall distribution is still strongly constrained by the training distribution.
This means that when the large model is written up to the third sentence, the first two sentences have already anchored the direction for the rest of the model.
This is ChatGPT
Therefore, the final output of a large model is always the conventional “thinking” that conforms to the training distribution, rather than truly valuable “unique” ideas (unless your prompts are very unique, but this does not align with most conversational styles).
This works perfectly for questions with standard answers: if you ask it how to write quicksort, it will write it for you quickly and correctly.
However, most questions are open-ended, and large models can easily become “mediocre” in this context.
For example, when you’re making architectural decisions or thinking about solutions, the standard answer is often the “mediocre” one.
But truly valuable solutions are often “unconventional thinking” that is “in the hands of a few”.
And this is precisely the comfort zone of ADHD thinking.
Skipping the first three obvious answers and continuing to detour where others have stopped is what ADHD thinking excels at.
Three Designs of ADHD Skill
I summarize the core design of the ADHD skill into three points.
First, five independent agents.
Instead of having an AI think five times, it makes five completely independent LLM calls, with no shared context between each call.
The ideas in Agent A are completely invisible in Agent B. The context is physically isolated, so it’s not about using a prompt to “ignore previous ideas” or compressing the context to “reduce context”; rather, there is no such thing as “previous ideas” at all.
This is similar to the approach recommended by Anthropic in the Harness project: they believe that long tasks should have their context reset directly to avoid context compression affecting task performance.
《harness-design-long-running-apps》
Each agent is assigned a completely different “thinking role”.
For example, “You are a regulator, auditing the system’s compliance and failure modes,” or “You are a ten-year-old child who has never seen software before, rethinking this problem in the most naive way.”
The paper contains 15 such frameworks, and ADHD will select five each time it runs: the goal is not to make them each “correct”, but to make them each “different”.
This forces each agent to think from only one specific perspective, simulating ADHD-like multi-threaded cognition.
Third, the processes of generation and critique are separated.
This is the most ingenious design in my opinion:
During the divergence phase, the system clearly states, “You are a generator, not a critic. Evaluation, sorting, and hedging are prohibited.”
During the convergence phase, switching to a completely different LLM call results in the system prompting “You are now a critic, engaging in adversarial reading, scoring, and finding traps.”
These two roles use different calls, different system prompts, and different output formats.
Why is this important? Because when creativity and judgment are working together, creativity cannot be fully utilised.
Just like someone who is writing the first draft of an article while simultaneously revising it, the result is that they can’t write anything at all; the same applies to an LLM.
Therefore, through this phased design, ADHD Skills can be achieved:
During the divergent thinking phase, consider the problem from five independent perspectives using “multi-threaded thinking”.
During the convergence phase, a thread is used to “integrate thinking”, and finally, an “unconventional” answer is given.
Of course, these two cases are not rigorous enough. Is ADHD really effective enough?
The authors themselves conducted more systematic verification and even wrote a paper titled “ADHD: Parallel Divergent Ideation for Coding Agents”.
The paper used six open-ended engineering questions for evaluation: ADHD was compared with a single response of the same model, with 5 wins and 1 loss.
His evaluation results showed that ADHD improved the novelty of the entire solution by 5.17 and the breadth by 4.17.
From the perspective of traditional prompt word engineering, this idea can be understood as follows:
ADHD alters the thinking logic of AI, allowing us to directly change the structure of the model’s output when calling the model.
You might say, doesn’t CoT (Mind Chain) allow AI to think more deeply? Doesn’t ToT (Mind Tree) allow it to search more broadly?
That’s right.
CoT allows a brain to think more slowly and carefully along a single path; it remains a linear path. ToT allows the same brain to try different next steps at forks, but the branches share context, so when writing the fourth step, the context of the first three steps is still there.
Therefore, no matter how deep the big model is thought out or how wide the search is, it cannot solve the problem that “the angle itself has not changed”.
Having understood this difference, we can now consider a larger question:
Once model capabilities and frameworks become similar, what will be the next dimension of differentiation?
I believe it’s about inference-time engineering: how you organize the call, isolate the context, assign roles, and schedule the generation and evaluation at the moment the model is invoked.
One example is Claude Code’s recently launched Agent Teams mode, which enables multiple AIs to collaborate in parallel, supporting up to 20 sub-agents running simultaneously.
ADHD Skill is also a concrete implementation of this direction:
It does not change the model weights or perform fine-tuning; it simply changes the diversity and quality of the agent’s output by calling orchestration during inference.
First, add the plugin marketplace provided by the official warehouse, and then install the plugins.
claude plugin marketplace add ayghri/i-have-adhd
claude plugin install i-have-adhd@i-have-adhd
Check the installation results.
claude plugin list
This will run if an update is needed.
claude plugin marketplace update i-have-adhd
If you just want to turn off the plugin temporarily instead of removing it, you can:
claude plugin disable i-have-adhd
To completely uninstall it, do the following:
claude plugin uninstall i-have-adhd
claude plugin marketplace remove i-have-adhd
Once the installation is complete, type the following into the session where you need this output method /i-have-adhd: If the command does not appear in the autocomplete list, restart Claude Code first, as the plugin index is usually read at startup.
The installation command for Codex is slightly different from that for Claude Code.
codex plugin marketplace add ayghri/i-have-adhd --ref main
codex plugin add i-have-adhd@i-have-adhd
Check if it is installed.
codex plugin list
If you need to get a new version, you can update the market index and reinstall the plugin.
codex plugin marketplace upgrade i-have-adhd
codex plugin remove i-have-adhd
codex plugin add i-have-adhd@i-have-adhd
The uninstall command is as follows:
codex plugin remove i-have-adhd
codex plugin marketplace remove i-have-adhd
Typing “ “ into the conversation $i-have-adhdwill activate this skill. If you want to restore the normal output, you can tell the agent stop adhd modeor normal modedirectly. If you are still using the old style after switching, it is usually safer to create a new session rather than repeatedly modifying the current context.
If you need to manually enable it for each session, you can write the persistence rules to a global directive file.
Use Claude Codex ~/.claude/CLAUDE.mdand Codex ~/.codex/AGENTS.md. Add the following:
## Output style
Always follow the rules in the `i-have-adhd` skill: action-first, numbered steps, no preamble, no closers, state restated each turn.
Global activation affects all projects. A safer approach is to manually invoke it in multiple sessions to see if this rhythm fits your work style before deciding whether to write a global configuration.
For tasks requiring didactic explanations, design discussions, or lengthy reasoning, you can also temporarily ask an assistant to provide a full explanation.
It’s definitely worth trying, provided you understand the side effects. However, for people with ADHD traits, the “5 steps all at once” approach is too much. I certainly felt that way myself.
We will divide it into stages.
That’s all you need.
Either the browser version of Claude.ai or Claude Code is fine. The free version is sufficient.
“I can’t start work in the morning,” “I can’t review the tasks I wrote in Notion,” “Writing meeting minutes is a pain.” Anything is fine.
What’s important isn’t whether Claude’s answers are useful, but the experience of “putting your problem into words and receiving some kind of response.”
Here are some things Claude thought were good to remember, written one line at a time.
These are the kinds of things that are worth saving:
My own decision-making criteria and work rules
Past failures (things I don’t want to repeat)
Recurring phrases and instructions
Project-specific prerequisites
Items not to include: sensitive information such as account numbers, credit card information, addresses, authentication information, family personal information, and health information. Do not store this information in persistent memory that is readable by AI. You need to decide to manage it in a separate location.
Choose one thing you do every week. It could be anything: formatting meeting minutes, organizing tasks, writing weekly reports, etc.
Create a skill that will act when you say “Do this.” Once you’ve created one, you’ll understand how to apply it to others.
I will have my output reviewed by AI other than Claude, such as Codex, ChatGPT, and Gemini.
Claude has a strong tendency towards conformity bias. He tends to say things like, “That’s great.” To stop the cognitive tunnelling that occurs during hyperfocus from the outside, a different part of the brain is needed.
To measure the degree of dependence. For example, only handwritten notes on Sunday mornings, and only a paper notebook on Wednesday evenings.
If you find yourself unable to do anything at this point, you’ll realize you were “dependent.” It’s best to realize this sooner rather than later.
Addiction itself isn’t bad. It’s only unmeasured addiction that’s dangerous.
🧙♂️ I am an AI Generative expert! If you want to collaborate on a project, drop an inquiry here or book a 1-on-1 Consulting Call With Me.
GitHub: https://github.com/uditakhourii/adhd
Paper: https://divergent.sh/
Title: 17 Recent AI Breakthroughs Everyone Should Know About
================================================================================
Sign up
Sign in
Sign up
Sign in
No Time
Share through stories.
Member-only story
Latest AI breakthroughs 2026 | New AI tools and robotics | Innovation | Automation
From frontier models, robotics, and generative AI tools to a model trained with zero Nvidia chips
Listen
Read here forFREE
A 1.6 trillion parameter language model finished pretraining this year on roughly 50,000 Chinese-made accelerators, with no Nvidia silicon anywhere in the run. The weights are on Hugging Face under an MIT license.The FP8 build alone runs past two terabytes, so almost nobody will load it on their own machine.
The past few months also brought advances in robotics, computer vision, video generation, and AI efficiency.
Here are 17 releases worth knowing about.
1. MuSViT reads sheet music the way BERT reads text
Sheet music is the printed notation musicians read: staves, noteheads, clefs and rests encoding pitch and duration through position on the page. General purpose vision models handle…
Published inNo Time
Share through stories.
Written byPranit naik
Started as a freelancer. Building a personal directory of AI and sharing what’s worked for me.
Status
Careers
Privacy
Text to speechTitle: 17 Recent AI Breakthroughs Everyone Should Know About
================================================================================
Sign up
Sign in
Sign up
Sign in
No Time
Share through stories.
Member-only story
Latest AI breakthroughs 2026 | New AI tools and robotics | Innovation | Automation
From frontier models, robotics, and generative AI tools to a model trained with zero Nvidia chips
Listen
Read here forFREE
A 1.6 trillion parameter language model finished pretraining this year on roughly 50,000 Chinese-made accelerators, with no Nvidia silicon anywhere in the run. The weights are on Hugging Face under an MIT license.The FP8 build alone runs past two terabytes, so almost nobody will load it on their own machine.
The past few months also brought advances in robotics, computer vision, video generation, and AI efficiency.
Here are 17 releases worth knowing about.
1. MuSViT reads sheet music the way BERT reads text
Sheet music is the printed notation musicians read: staves, noteheads, clefs and rests encoding pitch and duration through position on the page. General purpose vision models handle…
Published inNo Time
Share through stories.
Written byPranit naik
Started as a freelancer. Building a personal directory of AI and sharing what’s worked for me.
Status
Careers
Privacy
Text to speech
50넘어 알게되는 인생조언
1. 가르치려 들지마라 _ 어차피 안들음.
2. 혼자를 즐겨라_ 그게 진짜 휴식
3. 자식에게 모든 걸 걸지마라._ 결국 남
4. 몸이 재산이다. _아프면 모든게 끝.
5. 착한 사람 될 필요없다. _이용만 당함.
6. 자랑하지 마라 _ 듣는 사람만 괴로움.
7. 시선 신경쓰지 마라 _ 아무도 관심 없음.
8. 돈이 전부가 아니다 _ 하지만 없으면 비참함.
9. 지나간 일에 후회 마라 _ 바뀌는 건 없음.
10. 아닌 인연은 끝내라 _ 시간 낭비임.
재미지게 살아가자.
| 다정함을 위한 전쟁 (0) | 2026.08.05 |
|---|---|
| 듣보잡 쓰레기의 최후 (0) | 2026.08.05 |
| 결정사 500점 만점에 497점 받은 여자 스펙 (0) | 2026.07.25 |
| 뜻을 몰랐을 때도 예뻤고 알고 나니 더 예쁘다. (0) | 2026.07.24 |
| 말해도 되는 것과 안되는 것의 차이 (0) | 2026.07.24 |