Кантэкст і памяць Claude Code знутры, перабудаваныя ў агента LangGraph
Як Claude Code фармуе выклікі API, запускае субагентаў і ўшчыльняе доўгія сесіі — і тыя ж ідэі, перабудаваныя ў LangGraph з чэкпойнтэрамі і файламі памяці.
Даклэдзіны з выцярпанага коду Claude Code v2.1.87 паказвае, як адказчык караце сюжэт і памяць пад дзяўніяя кансэльвання коду. Ён рашае звычныя проблэмы дзяўная працюючых агентаў: сюжэт, які перасягае меры модэля, выход адказчыка, які ніхто не можа прыгадаць, вярненне памяці з пакананых сесыяў у правы момант і захаванне інструкцыйяў пра праект пасля ўкладнення. У наступным чысле апісваецца механізм і пасля таго ствараецца спрасаваная версія ў LangGraph з яго вбудованымі апаратамі для чэкпоінтаў.
Як Claude Code караце сюжэт
Запрос API складаецца з трох частэй. Две з іх — прамова сістэмы (ідэнтыфікатар плюс кантэкст git) і масэва інструментаў (шэмы JSON для Bash, Read, Edit, Skill, Agent і MCP) — застаюцца параду жоўтых паведамленняў, і процэс складчання ніколі не зачыняецца на іх. Рэшта пракладаецца ў масэву паведамленняў як элементы ролі карыстальніка, абгорнутыя ў тагі <system-reminder>: файлы з інструкцыямі на самым верху, ход паведамленняў і рэзультаты работы інструментаў у серэдзіне, а таксама элементы прыкрэпленняў (спамяці, спісы навыкаў і агентаў, інструкцыі MCP, выходныя даны хуків) дадаюцца пасля кожнага цыклу выканання інструментаў. Калі вікна запоўніцца, процэс складчання стыскае кожнае паведамленне і перзапускае важлівыя прыкрэплення з іх первісных джэрэл. Пасля гэтага следуе цыкл запытанняў, падагенты і процэс складчання.
Цыкл запытанняў
Запіс паведамлення пачынае цыкл, у яком кожная ітэрацыя — гэта адзін вызов API, і ён продовжваецца да тэперы, калі мадэль дае адказ без жадных вызоў інструментаў.
Перад пачаткам цыклу фонавы вызов Sonnet выбірае да пяці актуальных файлаў памяці, выкарыстоўваючы толькі тэкст запытання корыстніка, адтак як адправка всіх пераказоў коштаў бы занадта дорага. Рэзультаты не вікорыстоўваюцца негайна. Яны з’являюцца пасля выканання інструмента, таму першы вызов API ў кожным раундзе ніколі не ўключае іх.
Кожная ітэрацыя прыбліжае масэвы массів паведамленняў у памяці першым, чым ён падае да API. Спачатку ён скорачае спіс да таго, што знаходзіцца пасля последней межы компактавання, пры чым цэлы файл JSONL на дыску не змінюецца. Потым ён запісвае будзь-які занадта вялікі рэзультат інструмента у свой сабскладны файл і заменяе яго 2-кб прэвью ўнутры тэксту. Калі пасля гэтага колькасць токенав все ўсё застаецца вышэй за апошнія 93% вікна кантэксту, запускаецца працэс компактавання.
Адказ можа быць або звычным тэкстам, які завершае цыкл, або аднам або калькам вызоў інструментаў, якія выкананыя і зноў практыкуюцы цыкл. Інструменты дзеляцца на групы: загальныя, такія як Bash, Read, Edit і Grep; інструмент Skill, які запоўнюе розмову цэлым файлам SKILL.md; інструмент Agent, які запускае ізольаванага падагента, які вяртае адзін рэзультат; і ToolSearch, які запоўнюе схему інструментаў для наступнай ітэрацыі. Калі заканчываецца кожны раунд інструментаў, дадаюцца датчыкі змян для спісаў навыкаў, спісаў агентаў, інструкцый MCP і будзь-якіх завершаных прадзеяў памяці, і выдаецца толькі тыя даны, якія змяніліся з пярэдньага разу.
Падагенты
Званне на інструмент Agent выклікае у Claude Code стварэнне ізольаванага кантэкста ў тым жа процэсе. Падагент пачынае працу з копіі дыялогу родніка, отрымае свае сабе інструменты і сістэмны запрос, працюе з выключаным механізамам размышленняя, ўскораючы такім чынам вітраты токенав, і самастойна выконвае той самы код ціклу запітаў. Агенты тыпу Explore і Plan таксама втрачаюць дакумент CLAUDE.md і інформацыю про статус git з той копіі, што дапамагае заўсёды заошчадзіць токены.
Лячыць толькі паведамленне з фінальным рэзультатам, якое вяртаецца да родніка. Выклікі інструментав, працэсы разумовага аналізу і будзь-яя компактнаеобработка ўнутрь падагента збіраюцца ў файле-транскрыпцыі subagents/agent-{id}.jsonl, які роднік ніколі не ачынае. Такім чынам вітраты застаюць ізольаванымі: дзесяткі внутраніх выклікаў інструментав можу скліквацца ў аднае паведамленне ў кантэксце родніка.
Компактнаеобработка
У кодзе існуе мікрокомпактны слой, які служыць для скарычвання старых рэзультатаў інструментаў прычыму цэлага компактавання, але ён выключаны і не ўключаны ў дзейнасць у гэтым разе. Сама процедура компактавання працюе за двума стратэгіямі, прычыму другая выступае як альтэрнатыва, калі першая недоступна. Першая стратэгія негайна заменяе ўжо падготававаны файл памяці сесіі, таму компактаванню не трэба выканання власных запытоў API; фонавы агент поступова складае гэты файл праз аднарозовыя запыты ў паказваныя раней моменты, а шлях да гэтага файла наразе заблокаваны за дапамогою функцыйных блакатоў. Другая стратэгія, якая зараз выкарыстоўваецца, — цэлыснае компактаванне LLM. Агент, які працюе на адной лініі, прыме наявныя паведамленні і структураваны запыт на компактаванне, і скорачае кантакт да адпаведна пяці тысяч токенав. Найновейшыя паведамленні залішаюцца без змян праз тое самае пад рэзюме, каб модэль все ўсё мела свой негайны рабочы кантэкст, а ўсё старэе заменяецца рэзюме.
Далей перадаюцца ўсе заўязкі, які знаходзіліся ў messages[] і былі стыснуты, і якія паўтараюцься. Спіс агентаў, назвы інструментаў, якія былі відкладзены, і інструкцыі MCP знову паводзяцца цэлкам, а таксама паўтараюцца функціі, якія запускаюцца пад час пачатку сесіі. Контэнт навыкаў, якія былі выкліканы, вяртаецца з кэшу ў памяці, які лімітаваны прыблізна 25 000 токенамі, а пяцьце файлаў, з якімі было апэнсавана найбольш няўяло часу, чытаюцца з дыска занова. Сістэмны запрос, tools[], файл Claude.md і кантэкст git ніколі не знаходзіліся ў messages[], таму стысненне не можа якіх-небудзь паўніста паўтарыць; іх кэшы проста чысцяцца, ўнаследке чаго наступны раз будуць выкарыстоўваны свежыя значэнні.
Ёсць адзін пункт, які заняты ўмышленна: каталог навыкаў не перабудовваецца, што дапамагае заўсёды захаваць або чырвоны 4 000 токенав. Негатыўны наследк – пасля стыснення мадэль не можа знайсці навыкі, якія яна ўжо не выкарыстоўвала, як толькі Claude.md іх не перыядыруе. Стыснення таксама ведае працююць дэталей, адтолькі ўсе поперадніе падсумкі адбываюцца разам з усім іншым, і пасля трэцьяго працэса первісныя факты стаюць у тры разы стыснутымі. Нічога насправды не знікае, адтолькі файл JSONL з можлівасцю толькі дадавання дакументаў на дыску зберагае весь ход задач, а маркер межы стыснення (parentUuid: null) паказвае запускачу, дзе пачынаць продажу.
Спрасцаваная версія LangGraph
За дапамою LangGraph можна стварыць меньшую версію той самай архітэктуры. Ён ужо з’ядруеіць інструменты для зберагчэння дадзэнняў у віглядзе чэкпоінтаў і функцыю RemoveMessage для выдалення паведамленняў. Памяць між сэсіямі працюе за тым жа прынцыпам, як у Claude Code — кожны раз чытаюцца файлы у фармаце markdown з дыска. Логіку стыснення дадзэнняў трэба напісаць самостоятэльна.
У дыяграме супарадавання кожны стрэлка з’ѐеднае канцэпцію з Claude Code слева з ўяўным еквівалентам у LangGraph справа. Края, пазначаныя як моменты паліпшэння, паказуюць, што LangGraph можа зберагчыць інструкцыі за межамі messages[], таму стыснення ніколі не паўтараецца над імі.
У графе ёсць пяць вузлаў. Лініі з кропкамі, які выходзяць з call_model, ёсць умовнымі: яны перадаюць запыткі да інструментаў праз tools, а таксама тэкстовыя адпаведзі на post_turn. У load_context файлы памяці (.agent/memory/*.md) чытаюцца з дыска, а пасля — уключаюцца ў SystemMessage у call_model. Чырвонта гэтыя файлы не патрапляюць у messages[], таму яны застаюцца недарабленымі пасля працэса стыснення.
Стварэнне графа
Вузлы выконваюцца па спрабе, і пасля call_model умовны ўзлам або цыклізуе праз інструменты, або пераходзіць да post_turn. Checkpointer у SQLite зберагае весь стан праз пачатакі працэса.
def build_graph():
builder = StateGraph(AgentState)
builder.add_node("load_context", load_context)
builder.add_node("prune_and_compact", prune_and_compact)
builder.add_node("call_model", call_model)
builder.add_node("tools", ToolNode(TOOLS))
builder.add_node("post_turn", post_turn)
builder.add_edge(START, "load_context")
builder.add_edge("load_context", "prune_and_compact")
builder.add_edge("prune_and_compact", "call_model")
builder.add_conditional_edges(
"call_model", route_after_model,
{"tools": "tools", "post_turn": "post_turn"},
)
builder.add_edge("tools", "call_model")
builder.add_edge("post_turn", END)
checkpointer = SqliteSaver(sqlite3.connect("checkpoints.sqlite"))
return builder.compile(checkpointer=checkpointer)
# Subagent: separate StateGraph with own call_model + tools loop
subagent_builder = StateGraph(SubagentState)
subagent_builder.add_node("call_model", subagent_call_model)
subagent_builder.add_node("tools", ToolNode([bash, read_file, write_file]))
subagent_builder.add_edge(START, "call_model")
subagent_builder.add_conditional_edges("call_model", subagent_route,
{"tools": "tools", "__end__": END})
subagent_builder.add_edge("tools", "call_model")
subagent_graph = subagent_builder.compile()
Сувязь між двума графікамі знаходзіцца ў інструмэне run_subagent:
@tool
def run_subagent(task: str) -> str:
"""Run an isolated subagent for a specific task."""
result = subagent_graph.invoke({"messages": [HumanMessage(content=task)]})
return result["messages"][-1].content # only final result
Калі мадэль вызывае run_subagent, галоўны вузол tools выкананае функцыю, якая запускае граф сабагента, які самастойна выкананае своі цыклы call_model і tools. Лячыцца толькі фінальны адказ у вачынку, таму галоўны граф бачыць звычны рэзультат інструмента. Сабагент не мае інструкцый, файлаў памяці чы правіка стану, таму яго кантэкст ў лёгкай форме, і кожны вызов ёсць самастоятэльным. У прыкладзе ёсць адны тып сабагента. Claude Code на час выканання выбірае з калькілятара (Explore для пошуку, Plan для планавання, універсальны тып з усіма інструментамі, а таксама плагін-агенты на кшталт vercel:ai-architect), кожны з якіх мае своі інструменты, правыя і сістэмны запрос.
Запросы
Гэта спрасцаваныя версіі, якія вікорыстоўваюцца ў рэплікацыі LangGraph, а рэальныя запросы Claude Code ўсё бол дэталізаваныя.
Сам прынцып Воблак Кода ў сваёй складзенай версіі ўжо большы, ён складаецца з дзевяці раздэлаў, зоны для запісаў <analysis>, прадмовы «CRITICAL: Respond with TEXT ONLY» і блока з прыкладам фармата. У прыбліжанай версіі, паказанай ніжэй, залічваныя елементы:
Your task is to create a detailed summary of the conversation so far,
paying close attention to the user's explicit requests and your previous actions.
This summary should capture technical details, code patterns, and decisions
that would be essential for continuing the work without losing context.
Before writing the summary, analyze the conversation chronologically in <analysis> tags.
Then provide the summary in <summary> tags with these sections:
1. Primary Request and Intent: The user's explicit requests in detail.
2. Key Technical Concepts: Important technologies, frameworks, and patterns discussed.
3. Files and Code Sections: Specific files examined, modified, or created, with paths
and why each was important.
4. Errors and Fixes: Errors encountered, how they were fixed, and any user corrections
or feedback (pay special attention to "don't do X" or "actually I meant Y").
5. Problem Solving: Problems solved and any ongoing troubleshooting.
6. Pending Tasks: Tasks explicitly requested but not yet completed.
7. Current Work: What was being worked on immediately before this summary,
with specific file names and details from the most recent messages.
У кодзе спачатку адзінакоўваецца супрачына-адказ, пры чым trim_messages выбірае няўялікі канец, які трэба застаўіць. Толькі старэйшая частка надаецца модэлю з такім запітам, і гэты модэль бачыць усё (паведамленні корыстніка, адпаведзі асістэнта, рэзультаты інструментаў) і стыскае іх. Полная рэалізацыя знаходзіцца ў раздзеле з стысненнем ніжэй.
Запіт на апдэйт памяці сесіі ў Claude Code выкарыстоўвае шаблон з дзесяцью раздзеламі, з лімітамі колькасці токенав у кожны раздзел, замену {{variableName}} і строгія правіла ўтрымання заголовкаў раздзелаў. Апроставаная версія практычна відображае основную ідэю:
Based on the conversation above, update the session notes.
Update each section below with specific, actionable details from the conversation.
Write info-dense content: file paths, function names, error messages, exact commands.
Skip a section if there are no new insights to add. Do not add filler.
Keep each section under 500 words. If approaching the limit, cycle out less important
details while preserving the most critical information.
Always update Current State to reflect the most recent work.
Sections:
# Session Title
# Current State
# Task Specification
# Files and Functions
# Errors and Corrections
# Key Results
# Next Steps
Шаблон стварэння дадзеяў кладзіны для робочага прайсупутніка, дакументацыі кодавой базы, вывучання, ключоўых рэзультатаў і журналу работ, лімітуе кожную кладзіну до 2 000 токенаў, а весь файл — до 12 000 токенаў, і выкарыстоўвае агента для рэдагавання кладзінаў за дапамогою FileEdit, а не для перапісву файлу.
Стан: інструкцыі паza паведамленнямі
Пры кожным вызове API інструкцыі і памяць чытаюцца з дыска і размешчаюцца ў SystemMessage, як гэта рабіць prependUserContext() у Claude Code. Яны не зберагліваюцца ў messages[], таму компактаванне іх ігнаруе. Контэнт вызваных навыкаў і памяць сесіі знаходзяцца ў полях стану, якія застаюцца пасля компактавання:
class AgentState(MessagesState):
summary: str # compaction summary
instructions: str # CLAUDE.md equivalent (never compacted)
invoked_skills: dict[str, str] # skill name → content (survives compaction)
session_memory: str | None # background summary for instant compaction
# Cross-session memory: .agent/memory/*.md files read from disk every turn.
# Survives compaction (re-read from disk, never in messages[]).
# Agent creates memories via write_file tool. For production at scale,
# use a lightweight LLM call to select relevant files (Claude Code's approach).
Кампактаванне: ствараеіцца аптакт пра старыя, застаюцься няўясныя
Дызайн адпавядае прынцыпам Claude Code. Няўясныя паведамленні застаюцься недыягнутымі, адтолькі што модэль яшчо працавала з імі, а толькі старыя аптактуюцца.
def compact(state: AgentState) -> dict:
messages = state["messages"]
# Keep recent messages (Claude Code uses calculateMessagesToKeepIndex)
kept = trim_messages(
messages, strategy="last",
token_counter=count_tokens_approximately,
max_tokens=COMPACT_THRESHOLD // 5,
start_on="human", end_on=("human", "tool"),
)
kept_ids = {id(m) for m in kept}
# Summarise only the OLDER messages
to_summarise = [m for m in messages if id(m) not in kept_ids]
if not to_summarise:
return {}
response = model.invoke(
to_summarise + [HumanMessage(content=COMPACT_PROMPT)]
)
# Delete old, keep recent, inject summary + re-inject skills
delete = [RemoveMessage(id=m.id) for m in to_summarise]
summary = HumanMessage(content=f"[Summary]\n{response.content}")
skill_msgs = [
HumanMessage(content=f"[Skill: {n}]\n{c[:5000]}")
for n, c in state.get("invoked_skills", {}).items()
]
return {"messages": [*delete, summary, *skill_msgs], "summary": response.content}
Рэзультат — гэта аптаванне, пасля чаго прыходзяць захаваныя няўзабавныя паведамленні, а потым зноў даданыя навыкі; гэта не аптаванне, якое заменяе ўсё. Выклік RemoveMessage стварае новы пункт контролю без старых паведамленняў, тады як пакульшыя пункты контролю застаюцца ў базе дадзеных для вярнення, так сама як JSONL у Claude Code захоўвае запісы да ўтрамбоўвання.
Памяць: на базе файлоў, чытана ў кожны раз
У прыкладзе паказана тое, як на самай працэ ў Claude Code. Файлы памяці знаходзяцца на дыску пад падметам .agent/memory/*.md, call_model чытае іх у кожны раз, і яны ніколі не патрапляюць у массів messages[]. Паколькі іх чытаюць з дыска, а не перзбудовваюць з размовы, ўтрамбоўванне не можа іх выключыць.
# In call_model — read memory files from disk every turn
MEMORY_DIR = Path(".agent/memory")
if MEMORY_DIR.exists():
for mem_file in MEMORY_DIR.glob("*.md"):
content = mem_file.read_text()[:4096]
system_parts.append(f"Memory ({mem_file.stem}):\n{content}")
# Agent creates memories by writing files:
# write_file(".agent/memory/user_prefs.md", "User prefers concise responses.")
Claude Code кожны раз пры выконанні календара Sonnet выдзеляе адзін файл памяці, які ўзімкае, да 5 з можлівых мнага: швыдкі мадэль прымае запітаў і спіс назв і апісаў файлаў, а потым вяртае найболей прыдатныя рэзультаты. У прыкладзе завантажаюцца всі файлы, што прыемна, але не дазволяе масштабавання. У працоўных наладках з велікай колькасцю памяці трэба следаваць первіснам падходу: лёгкі вызов LLM з запітам і індэксам памяці вяршыць выбір таго, што трэба завантажыць.
Навыкі: інструмент з поступовым адкрываннем інфармацыі
@tool
def load_skill(skill_name: str) -> str:
"""Load a specialised skill prompt by name."""
return SKILL_REGISTRY.get(skill_name, f"Unknown skill: {skill_name}")
У файлах інструкцыйяў указайце крытычныя навыкі, каб яны засталіся пасля працэсу стыснення. Каталог навыкаў ўжоць ці прыемнасць, а не обавязак.
Што зберагаецца пасля сесіі
Уявіце сесію, калі корыстнік задае калькі пытанняў, запускае працэс стыснення, а пасля вяртаецца знову. Даныя агента, якія зберагліся, будуць выглядаць так:
.agent-data/
checkpoints.sqlite ~5 MB (all state snapshots, grows with session)
tool-results/
bash-84ffc64a.txt 15 KB (large bash output persisted with preview)
.agent/
memory/
project_goal.md 206 B (cross-session, survives compaction)
user_prefs.md 125 B (read from disk every turn)
Для той самай працы Claude Code зберагае большэйшы об’ём дадзейнаў, адтолькі ён выкарыстоўвае толькі дапісвены фармат JSONL для зберагання транскрыпцыі, які ніколі не вычысляецца, а таксама зберагае околачыя файлы для кожнага падагента і компактныя рэзервныя копіі:
~/.claude/projects/{cwd}/
{sessionId}.jsonl 22 MB (full transcript, never deleted)
{sessionId}/
tool-results/ 12 files
subagents/ 96 files (subagent transcripts + compact backups)
session-memory/ (disabled)
history.jsonl 13k lines (prompt recall, UI only)
memory/ 4 files (auto-memory)
У LangGraph чэкпойнтар зберагае кадры стану заместо лога з’явэнняў, таму розмер сістэмы меньшы. Большыя чэкпойнтары застаюцца і можна іх адзірнуць за дапамою graph.get_state_history(config) пад час дэбаггінгу або вяснавання проблем.
Што павінна крыць рэпліка
Рэпліка LangGraph адпрацоўвае галоўную частку задачы: чытанне інструкцый з дыска, стысненне старэйшых паведамленняў, калі няўялікія застаюцца, збераганне рэзультатаў большых інструментоў у файлы, а таксама перачытанне памяці між сесіямі ў кожны раз. У продакшн-сістэме додаецца выбіранне памяці пад кераваннем LLM за дапамою швырага вызову Sonnet, дадаткі delta, якія павінны пакрыць навыкі і агентаў, ізольаваныя сабагенты, хукі з плагінамі, а таксама кэш запытанняў. З такім падходам да праектавання гэта ўжоць цінны апавядомленні для тых, хто стварае сабе агента.