Галоўная / Артыкулы

Інжынерыя кантэксту на практыцы: даследчы агент на Claude Agent SDK

Збіраем на Python даследчага агента на Claude Agent SDK з патэрнамі запісу, адбору, сціскання і ізаляцыі кантэксту і параўноўваем яго з наіўнай базавай версіяй на вымераных запусках.

Выдавец: REACTAPP.TOP20 хв чытання4633 слоў

Крыніцы: Context Engineering in Practice: Building a Production AI Agent with the Claude Agent SDK

Агент для даследавання рэдкасць знаходзіць адну складную задачу. Большае болючасць — купа інструкцыяў. За час адной толькі расследавання кантэкст заполняецца інструкцыямі ад корыстувальніка, планамі, запітамі да пошуку, завантажанымі старонкамі, схемамі інструментаў, метаданнымі выканаю, незавершанымі твэрамі, атрыбутамі ад падрабнікаў і попераднімі рашэннямі. Якшто ўсё гэта застаецца актываўным, хорашыя доказы вынужданы конкуруваць за увагу агента з самымі старымі задачамі.

У такім случае канструкцыя «Запісаць, Выбраць, Компрэсаваць і Ізаліраваць» становіцца рабочай архітэктурай на Python у рамках Claude Agent SDK. Агент павышае свою эфектыўнасць шляхом аднаго рашэння за раз, і кожны момент ставіць тую ж запытанне: што мусi бачыць модэль у даны момент, а што можа застацца паўтароўна? Мета — агент для даследавання, які можна запусціць, а не каталог функцый SDK.

Стварэнне агента адбываецца за дзесяць крокаў:

  1. Апісаць, што будзе стваранаць.
  2. Установка простага базовага варыянта.
  3. Запіс: вынесць стан працы за межы размовы.
  4. Выбір: скласць обмежаны набор дадзейнаў.
  5. Стысненне: зробіць дзяўныя сесіі восстанавлімымі.
  6. Ізоляцыя: парадаваць спецыялізаваныя розследаванні.
  7. Ізоляцыя выходаў важкіх інструментоў у срэдовышчы.
  8. Перанесць кантэкст між сесіямі.
  9. Скласць агента, створанага на адной з кантэкстных тэхнікаў.
  10. Параўняць две архітэктуры.

Што ствараецца

Пры наяўнасці запіта агент плануе сваё даследжэнне, шукае первынныя крыніцы, парадуе вузкія розследаванні дапаможнім элементам, фіксуе доказы і ў канцы крануе атрыбутаваныя справаздачы. Прамерны заведамчык: параўняць основныя падходы да далгастраўнай памяці, якія вікорыстоўваюцца ў прымэнных агентах AI, і паспяшыць, калікі коштуе кожны з іх. Для гэтага патрэбны статыі, документы аплявення, доказы з хранілішча і перакрыцця цітатаў, таму гэта дастатнька шырока задача, каб стала справжнім тэстам.

Галоўны агент прытрымвае план і пішчытае заканчыцковы сінтэз. Адбіраны стан работы зберагаецца ў файлах, а механізм выкарыстоўвання паказвае кантэнт знанняў, якія прыходзяць; падагенты рашаюць маленькія запытанні ў своіх сабеіндывідуальных кантэкстах. Нічога з гэтага не ўключана ў SDK. Ён сапраўдзіць модэль і цыкл інструментаў, а прыкладнэ програма выбірае, дзе будзе зберагвацца стан і скількі інформацыі пераходзіць праз кожны рубік.

Першыя зображэнні паказваюць пачатковую сістэму: аднаго галоўнага агента, які з’ўязаны з інструментамі, знаннямі, тэмпарарыем і падагентамі. Рэшта элементаў структуры змінюе спосаб пераходу інформацыі праз гэтыя з’ўязкі.

Спачатку створыце срэду і установіце паказаную версію SDK, перш чым запускаць будзь-які модуль.

# terminal
python3 -m venv .venv
source .venv/bin/activate
python -m pip install claude-agent-sdk==0.2.139
export ANTHROPIC_API_KEY="your-api-key"

Такая структура выдэляе настройкі агента з аператывамі рабочага прастору, спецыяльнымі інструментамі, хукамі і метракцыяй. Гэта раздзеленне важна, таму што кожная операцыя ў конкрэтным кантэксце будзе зменіць толькі адну частку, не вымагаючы стварэння цэлага новага прыемніка.

# code/
research_agent/
  config.py       # naive and engineered ClaudeAgentOptions
  hooks.py        # pre-compaction checkpoint
  metrics.py      # message-stream and context measurements
  runner.py       # repeated runs and comparison
  tools.py        # in-process MCP tools
  workspace.py    # scratchpad and bounded retrieval
knowledge/
  memory_approaches.json
tests/
CLAUDE.md

Інжыніярство кантэксту пачынаецца з назначэнняя адпаведальнасці. Размова зберагае чынныя рашэнні, тады як файлы, інструменты, сістэмы адзыскання і падагенты зберагаюць усё, што на даны момент не патрэбна ў актыўным стане.

Простая базовая мадэль

Найменшы програма на падставе Agent SDK перадае цікл керування функцыі query(). Claude прыме запіт, выявіць, чы робіцца патрэба ў інструменце, спостарожыць за рэзультатам і будзе павтораць гэты працэс, пакуль не отрымае адпаведны ўтварэння чы не дасягне заданага ліміту. Паколькі SDK забезпечвае такі цікл, прыкладнаму програме не трэба самай пісаць механізмы парасінгу і распадзелу вызоваў інструментаў.

# research_agent/minimal.py
import asyncio
from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query

QUESTION = "Compare approaches for long-term memory in production AI agents."

async def main() -> None:
    options = ClaudeAgentOptions(
        model="sonnet",
        allowed_tools=["WebSearch", "WebFetch"],
        permission_mode="dontAsk",
        max_turns=20,
    )
    async for message in query(prompt=QUESTION, options=options):
        if isinstance(message, ResultMessage):
            print(message.result or "")

asyncio.run(main())

Эта короткая програма скрывае ў сабе штось: query() запускае цыкл, але прылада все равно выбірае, што будзе ўвайшаць у яго. Базовы код, які можна запусціць у research_agent/config.py, чытальна опісвае гэтыя варыянты.

# research_agent/config.py
def naive_options(*, run_root, server, model, max_budget_usd):
    tools = ["Read", "Glob", "Grep", "WebSearch", "WebFetch"]
    return ClaudeAgentOptions(
        cwd=run_root,
        model=model,
        tools=tools,
        allowed_tools=[*tools, "mcp__research__*"],
        permission_mode="dontAsk",
        mcp_servers={"research": server},
        strict_mcp_config=True,
        setting_sources=[],
        system_prompt={
            "type": "preset",
            "preset": "claude_code",
            "append": NAIVE_PROMPT,
        },
        env={"ENABLE_TOOL_SEARCH": "false"},
        max_turns=20,
        max_budget_usd=max_budget_usd,
    )

У базовай версіі няма інструмента Write, няма інструмента Agent, няма настройкі agents, няма хука і няма файла з інструкцыямі для праекта. Рэспондэнт павінен завершыць усё расследаванне ў рамках адной размовы. Настройкі ENABLE_TOOL_SEARCH=false таксама запобегаюць адкладанню обработкі схем MCP. Сістэмны запрос завершае правіла, прабудзячы агента вызваць інструмент whole-corpus, захаваць рэзультаты размовы і не запісваць ніякіх прымечанняў у рабочай зоне, а таксама не дэлегаваць задачы.

Інструмент whole-corpus ўмышлена мінімалізаваны, таму што проблема крыўцяе ў тым, што ён вяртае, а не у спосабе яго рэалізацыі.

# research_agent/tools.py
@tool(
    "load_knowledge_corpus",
    "Return the entire local memory-research collection. Intended only for the naive baseline.",
    {},
    annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
)
async def load_knowledge(_: dict[str, Any]) -> dict[str, Any]:
    return _text_result(load_corpus(corpus_path))

Калі Воблак выклікае яго, SDK выдае рэзультат у віглядзе UserMessage, які мае ToolResultBlock. „Пользоватэль“ у гэтым значэнні — гэта роль протаколу, а не іншая людская паведамленне. Гэты блок прыўлікаецца да історыі, якая перадаецца кожнаму наступнаму кроку модэлі, тады адна пошукавая запытка, тры завантажаныя сторункі і весь корпус дадзеных ствараюць такую павтараючуюся серыю.

UserMessage(question)
AssistantMessage(ToolUseBlock: load_knowledge_corpus)
UserMessage(ToolResultBlock: entire corpus)
AssistantMessage(ToolUseBlock: WebSearch + WebFetch)
UserMessage(ToolResultBlock: raw search and page content)
AssistantMessage(final report)

Сама парадыгма не ў сабе мае нічога паследньага. Яна ўпростоўленая, таму што прыкладнэе рашчыненне ніколі не ператварае первісных рэзультатаў у адпаведны зовнішні стан і ніколі не адмахвае іх з актыўнай траекторыі.

На другой дыяграме паказана упростоўленая структура кантэксту, якая збірае інструкцыі, адзначэнні інструментаў, первісныя сторункі, нараджэнні і історыю размовы ў адной вікнарэйцы. Рост вырабляецца за рахунак правіл прыкладнага рашчынення, а не за рахунак якога-лібо дыфекту ў цыкле SDK.

Пошук інструментаў ўвімкнуты за замовчанням для большасці налаштаванняў Agent SDK, за выключэнням задокументаваных адзінак. Таму справядлівая база павінна спецыяльна яго выключыць, калі эксперымент мае на меце прадзялованне всіх інструментаў. Як элемент контролю, упростоўленыя налаштаванні ёсць корыстнымі, таму што яны адключаюць усі рашчыненні, зв’язаныя з кантэкстам, якія будуць перапытваны ў наступных кроках.

Перш чым вітрацаць часы і токены на параболік, была адрысавана пробная запытка, якая перадала адно обмежанае запыткі праз тую ж самую настройку. Рэзультаты яе аналізу паказалі, што аутантыкацыя, маршрутызацыя і адгукаванне модэля працавалі нормальна.

# Captured output: python scripts/smoke_test.py
subtype=success
result=OPENROUTER_OK
models=claude-sonnet-5

Потым была адаптаваная версія выконана тры разы, а рэзультаты яе былі практычна паляглі ля адаптаваных тэстаў у заканчыцковай параване. Першы тэст дапамагае укрэпіць гэтыя рэзультаты.

# Captured output: python scripts/show_naive_trial.py ../measurements/comparison.json
Naive trial 1
SDK result: success
Assistant steps: 4
Tool calls: 6
  WebFetch: 3
  WebSearch: 1
  mcp__research__load_knowledge_corpus: 1
  mcp__research__search_knowledge: 1
Final active context: 16,478 tokens
Final tool-result payload: 6,851 tokens
Cumulative tree input: 138,182 tokens
Estimated cost: $0.754

Пяцьце пасляпэўных значэнняў меруюць разныя рычыны. Чысла 16,478 — гэта кантэкст актываўных лід-значэнняў, пры чым 6,851 з гэтых токенаў паходзяць з рэзультатаў інструментаў. Чысла 138,182 — гэта сумарны ввод за ўсе чатыры крокі асістэнта, уключаючы стварэнне і чытанне кешу, таму гэта не значыць, што агент у певны момант меў вікна з 138,182 токанамі. Разлік стае важны, калі да паўтарнага аналізу прыўяжуцца падагенты.

Наўіны агент можа служыць справядлівым контролем толькі тады, калі такія функцыі, як пошук інструментаў, ўсунутыя явна. У іншым случае стандартныя настройкі SDK можу таямніча зробіць базовы варыант розумней, чым планавалося.

Запіс: файлы scratchpad

Першая змянэнне дадае можлівасць выкорыстоўваць «scratchpad», які падтрымваны файлавой сістэмай. У файле plan.md зафіксаваны пытанні, аспекты даследжэння, тое, што ужо зроблена, і тое, што застаецца. У файле research_notes.md сакаваны тэзы, доказы і працоўкі, у файле sources.json — метаданы цытатаў, а у файле open_questions.md паказаны нераскрытыя пытанні. Вялікі матэрыял выкарыстоўваецца ў папке з артыфактамі.

# research_agent/workspace.py
def initialize_workspace(root: Path, question: str) -> Path:
    workspace = root / "workspace"
    workspace.mkdir(parents=True, exist_ok=True)
    (workspace / "artifacts").mkdir(exist_ok=True)
    (workspace / "checkpoints").mkdir(exist_ok=True)

    for name, template in WORKSPACE_FILES.items():
        path = workspace / name
        if not path.exists():
            path.write_text(template.format(question=question), encoding="utf-8")
    return workspace

Паколькі гэтыя файлы ўстойчывы, агент можа апдэйтаваць іх пасля кожнага крока доследжэння і чытаць толькі той, які ў яго ёсць пасля гэтага. Збой чы справа з компактаваннем не пазначаюцца на плане даследжэння і доказах, і чалавек можа без проблемачакоў прачытаць стан, не прабуючы склеіваць інфармацыю з транскрыпцыі.

Трэція дыяграма падана з афлайн-скрыпты, якая ініцыязуе рабочую зону і выдрукуе точныя файлы, якія вікорыстоўвае агент як робочую плошчу.

Сесія служыць іншай мэты. У дакументах сесіі яе опісваюць як места зберагчыка запітоў, вызоваў інструментаў, рэзультатаў іх работы і адпаведных адказаў, неабходных для продажу размовы. Робочая плошча ж зберагчыкае сэрвіруемы агентам стан даследжэння. Адновленне сесіі вяртае пацёлк паводзакоў, тады калі чытанне файла plan.md вяртае намерна падтрымваны відблеск выпакоўвання задачы.

Гэтыя адміністрацыйныя меры запобегаюць двум частым адказам. Калі транскрыпцыя ўсё, што існуе ў базе дадзеных, восстановленне залежыць ад все дужэй працоўнай історыі. Калі рабочая папка стане проста копіяю транскрыпцый, тая ж проблема кантэксту з’яўляецца на дыску. Таму кожны файл мае сяродзімую структуру і зберагае вывары, доказы і незаканчаную работу, а не копіі размов. Історыя сесій фіксуе, як агент дасяг ў тыято становішча, а рабочая папка зберагае неабходны мінімум інформацыі, кароткія даныя, якія памагаюць вырашыць, што будзе далей.

Выбіранне: абмежаны працоўны набор

Знання з вантажу можа перавырашыць тое, што адна-едзіная вызова модэлю можа прыняць. Выбіранне рашае гэтыя проблемы праз завантажэнне інформацыі па фазам: планаванне чытае plan.md, сінтэза — research_notes.md, а стварэнне цітатаў — sources.json. Інструменты і знання працуюць па там самым прынцыпе, адколі нават іх метаданы можу зайняць значны частаку запіту.

Што стосуецца інструментаў, спецыяльна разрабатаная супэрвізія дазволяе выконваць пошук інструментаў. У дасяглібнай інструкцыі Anthropic зазначаецца, што каталог можа мястыць аж 10 000 інструментаў, пры чым за адны пошук вяртаецца да пяці актуальных, калі толькі не будзе змянена настройка. Цяная інструкцыя охопляе як стандартныя MCP-інструменты, так і індывідуальныя MCP-інструменты SDK; такім чынам велікі каталог можа заставацца пахаваным у спадчыні, пакуль Claude не запатрабавае конкрэтную структуру. У дасяглібнай інструкцыі таксама паведамляецца, што пры аб’ёме дзесяці інструментаў або менш часта шырэе ёсць прышвартаванне ўсіх інструментаў з самага пачатку, таму адкладзены прышвартавань інструментаў выкорыстоўваецца радыя для расшырэння магчымасцей, а не як гарантаваная перадача.

Што стосуецца знанняў, індывідуальны інструмент search_knowledge работае на базе дадзеных SQLite FTS5. Кэшэр прымае запіт і значэнь top_k, а потым вяртае толькі цытаваныя фрагменты, а не всю колекцыю. Індывідуальныя інструменты ў SDK праходзяць праз внутрашній MCP-сервер, таму прыклад застаецца самодостатнім, але водночас перакрывае тыя ж межы, што і званчайны сервіс.

# research_agent/tools.py
@tool(
    "search_knowledge",
    "Search the local memory-research collection and return only the most relevant cited passages.",
    {
        "type": "object",
        "properties": {
            "query": {"type": "string", "minLength": 1},
            "top_k": {"type": "integer", "minimum": 1, "maximum": 10},
        },
        "required": ["query", "top_k"],
        "additionalProperties": False,
    },
    annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
)
async def search_knowledge(args: dict[str, Any]) -> dict[str, Any]:
    try:
        return _text_result(
            search_corpus(corpus_path, args["query"], args["top_k"])
        )
    except (KeyError, TypeError, ValueError, sqlite3.Error) as error:
        return _error_result(error)

У запіце задаецца top_k=5, хоць схема дазваляе значэння ад одного да дзесяці. Такім чынам правілы і жорсткія ліміты заставляюцься адзін ад другога: якосьасць выкарыстоўвання інфармацыі можна налаштаваць пазней, не змянюючы умовы працы інструменту, а некарэктны запіт на тыячы запісаў зазнае невядомага рэзультата, перш чым зможа запалаваць кантэкст.

Чэтвёртыя рысунакі паказваюць чытанне файлаў, пошук у інструменце і адзысканне ведамасцей як трохі спосабаў выбору разных відоў кантэксту, якія ўсе заканчываюцца аднаковым рэзультатам: обмежаным наборам дадзеных для тэкушчага крока.

Афлайн-тэст запускае рэальны код абшуку і пераканаецца, што пяць ёсць максімальным лімітом, што кожны знайдзены элемент застаецца з своім вучальным URL, і што некарэктныя значэння top_k адхоўваюцца. Ён нічога не кажа пра якасць сэмантычнага абшуку, таму што прылад для тэставання ёсць дужа маленькім. У рэальных умовах такая ацэнка прадаўаецца самам абшукавачу, які спынаецца на ацэнёваных запітах і паказначыках прызначальнасці, а не на спадчыннай ацэнцы за выгляд готовага тэксту. Выкарыстанне дадзеных паза межамі размовы звалівае прастор, але сама выборка вярнайша, чы робат все ўсё можа знайсці пяць правільных элементаў, калі яны стануць рэлевантнымі.

Складзенне: вялікія сесіі, якія можна вярнуць

Выбараныя правілы адначасова кантролююць, што будзе прыймацца, але довгі сесія продовжвае накапляць старыя перадачы даных. Калі прага кантэксту падходзіць да канца, Agent SDK сама складвае історыю: старэйшыя паведамленні ператвараюцца на статыстыку, а няўзначныя розмовы і важлівы стан застаюцца. У строе з’яўляецца SystemMessage з падтыпам compact_boundary, які дазволяе коду для зьвязку післяльгаваць кожны раз, калі гэта выканана.

Звычайная статыстыка можа адмахнуцца саме тых фактов, на якіх базуецца даследжэнне. Таму ў праекце включаюцься правілы зберагчэння даных, прызначанныя для конкрэтных даследжэнняў, у файл CLAUDE.md: трэба захаваць первісны запіт, падтверджаныя нарадкі, нерашаныя пытанні, суперасункі, URL-адрэсы джэраў, важлівыя рашэнні і чырговы план. Гэты файл керуе тым, што будзе включанае ў статыстыку, і не трэба, каб усі дадзеныя з карцы для нотатак перавозіліся з кожным вызывам.

Перад будзь-якай стыпаннёю, яе автаматычной чыра ручной, хук PreCompact зберагае точку контролю. Калі становіцца доступны транскрыпт SDK, яго копіююць, а метаданы фіксуюць прычыну запуску, сесію, катэгорыю працы і месца, з якого павяліўся транскрыпт.

# research_agent/hooks.py

def build_precompact_hook(workspace: Path):
    async def archive_before_compaction(
        input_data: dict[str, Any],
        tool_use_id: str | None,
        context: Any,
    ) -> dict[str, Any]:
        del tool_use_id, context

        checkpoint_dir = workspace / "checkpoints"
        checkpoint_dir.mkdir(parents=True, exist_ok=True)
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
        session_id = input_data["session_id"]
        trigger = input_data["trigger"]
        stem = f"{timestamp}-{session_id}-{trigger}"
        transcript = Path(input_data["transcript_path"])

        metadata = {
            "session_id": session_id,
            "trigger": trigger,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "source_transcript": str(transcript),
            "custom_instructions": input_data.get("custom_instructions"),
            "archived": transcript.is_file(),
        }
        if transcript.is_file():
            shutil.copy2(transcript, checkpoint_dir / f"{stem}.jsonl")
        (checkpoint_dir / f"{stem}.json").write_text(
            json.dumps(metadata, indent=2) + "\n", encoding="utf-8"
        )
        return {}

    return archive_before_compaction

Пятыя рысунак паказвае, як процэс складання скарматаваных кантактаў у статыс, які адпавядае правілама зберагчэння, тады як пункт перапаказу і ручны блок застаюцца незалежнымі запісамі ў асяроддзі.

Ручны блок і процэс складання задавалі разныя патрэбы. Ручны блок — гэта стан, які агент намеравана запісвае па ходу, організаваны для пазнейшага чытання. Складанне — гэта процэс скарматавання вялікай колькасці кантактаў, які выкалываецца тады, калі актыўная історыя стае занадта вялікай. Калі і адны, і другі ўжо ёсць, скомпактаваная сесыя можа апісацца на sources.json або research_notes.md, калі статыс чагось згубіў.

У тэсце без падзейчання на хук быў задаўся тымчасовы транскрыпт, і было паўнастацана трохі рэчаў: з’явілася точка контролю, транскрыпт быў скопіяваны, а метаданы запуску засталіся. У шасці рэальных працоўках па порэванню compact_boundary ніколі не з’яўляўся, адтуды што самы большы кантэкст, які быў зафіксаваны, складаў 34 309 токенав протыяў 200 000 токенав, якія былі зазначаны. Такім чынам эксперымент паўнастацаняе, што хук працуе, але нічога не сказвае пра тое, насколькі добра функцыонуе рэальная кампактнасць. Апісанне должна мястыць рашэнні і нераспрацаваныя заведамы, а падмагчыцельны блок — доказы у чытальнай форме.

Ізолювацыя: спецыялізаваныя падагенты

Незалежныя лініі розследавання лёгкей караць, калі яны не выкарыстоўваюць адну і тую ж, постаўляючуюся ў часе траекторыю. Пасля таго, як главны агент практычна складзея план, троўцы агенты займаюцца аналізам дакументаў, пераглядам падтверджэнняў у базе даных і перакрыччуванням цітатаў. Кожна з гэтых задач абмежвае спосабы выпання, інструменты і фармат перадачы матэрыялаў.

# research_agent/config.py
"paper-researcher": AgentDefinition(
    description="Analyzes primary research papers for memory mechanisms and trade-offs.",
    prompt=(
        "Investigate only the assigned paper question. Use primary sources. "
        "Return at most five findings, each with a URL and an explicit limitation."
    ),
    tools=["WebSearch", "WebFetch", "mcp__research__search_knowledge"],
    model=model,
),

У кодзе сабагенты адзявляюцца за дапамою AgentDefinition. За дакументацыяю Anthropic пра сабагентаў кожны з іх пачынае новую розмову з сае кантэкстам, зберагчы там своі проміжныя вызовы інструментаў і рэзультаты, а празацоўкавае паведамленне адправляе толькі наўрадзе галоўнаму агенту. Такі перадачы ўмоўлівае справжню межу кантэксту, а не проста правілу ў існуючым вікні галоўнага агента.

Шосты рысунак паказвае, як кожны спецыяліст атрымлеў спецыфічную задачу і новае вікно. Галоўны агент бачыць стыслую інформацыю пра перадачу, але не бачыць ні пошукаў, ні завантажаных стораніц, ні крокаў выкарыстоўванняя для ўтварэння гэтай інформацыі.

Ізоляцыя караецца пэўнымі затратамі. Тры падагенты можаць зноў аднарадзіць тыя ж джэрэла, выкарыстоўваць больш токенав і прыйняць суперсучасную тэрміналогію. Ёй запобiec трэба за дапамогою патронав, якія выказваюць неабходнасць указвання первічных URL-адрэс, чыткага апісання мерыябоў і не болейш чамузнацца нараджэнняў. Пераканальнік бачыць толькі спарчаныя тверджэння, а не весь змест, што дазволяе контролаваць яго работу і зменшвае рызык таго, што ён таямна перапісае сінтэзу.

Родычы должны магчыма было оцаніць тое, што ўтрымліваецца. Пяць рэчэй без ідэнтыфікатораў джэрэлае ёсць стыслымі, але непрыдатнымі, а полны транскрыпт дзецячага дыялогу стырае граніцы. Контракт тут вяртае нараджэнняў, цітаты, абмежэнні версій і мерыябоў — тые полькі, якія неабходны керуючаму для пораўнання твэрджэнняў. Лішнея сховання дзецячага дыялогу сама па сабе недастатня, ўжо таму, што ізоляцыя працюе толькі тады, калі рэзультат містыць доказы і нявескласці.

Ізолювацыя выходаў складных інструментаў

Дзеяны навантажэнні павінны пераходзіць межу інструментаў толькі як апавяснення. Функцыя materialize_source запісвае выбраны документ у катэгорыю workspace/artifacts/ і вяртае яго шлях, размах у байтах і короткі прэвю. Пазней Claude можа адкрыць вялікі фрагмент документа за дапамою інструментаў для керування файламі, тады весь документ ніколі не застаецца ў рэзультатах аднаўскага інструмента.

# research_agent/workspace.py (full function; use the Gist when publishing)
def materialize_source(corpus_path: Path, workspace: Path, source_id: str) -> dict:
    document = next(
        (item for item in load_corpus(corpus_path) if item["source_id"] == source_id),
        None,
    )
    if document is None:
        raise ValueError(f"unknown source_id: {source_id}")

    path = safe_artifact_path(workspace, f"{source_id}.txt")
    content = (
        f"Title: {document['title']}\n"
        f"URL: {document['url']}\n"
        f"Published: {document['published']}\n\n"
        f"{document['text']}\n"
    )
    path.write_text(content, encoding="utf-8")
    return {
        "path": str(path),
        "characters": len(content),
        "preview": content[:240],
    }

SDK таксама мае адпрацоўку для забезпечэння безпекі рэзультатаў MCP. Дакументацыя Anthropic да MCP пазначае выхідны тэкст дужы большы за 10 000 токенаў і задае 25 000 токенаў як стандартны ліміт; занадта большы тэкст можа быць запісаны на дыск і выкарыстаны ў кантэксце як звязак з файлам. Гэты стандартны ліміт служыць апатронам. Працягванне вярнення шляхоў і метадаў намеравана дае прыкладнаму програмнаму забезпечэнню стабільныя правілы ўжо задаўна, перш чым будзе досягнуты экстрэнны ліміт.

Сьведчыння з сьпява гэтага пункту апавядання падае з намерованага тэсту на абыякавасць, які прабуюць выйсці з каталогу артыфактав за дапамою ../outside.txt. Рабочая зона адхіляе такі шлях прытаманна нічога не было запісана.

Ізоляцыя асяроддзя таксама стварае межу безпекі. Назва артыфакта, кантролюемага модэлем, ніколі не павінна стаць довільным шляхам у файловай сістэме. safe_artifact_path прымае адну консерватыўную назву файла і пераканальваецца, што выкарыстоўваны пункт прызначэння застаецца ў каталозе workspace/artifacts/. Фактычны бяг нешта такі.

# Captured output: python scripts/demonstrate_failure.py
Blocked artifact path: artifact name must use only letters, numbers, dots, underscores, or hyphens

У гэтым чэке няма жадных заяваў пра сандбоксы аператывайных сістэм чы ізоляцію калькантаў. Ён паўтарыць адну вузкую правілу: аргумент інструмента не можа выкарыстоўваць пераметранне пашы для запісу за межы каталогу артыфактаў. У рэальных развёраннях яшчэ трэба правыя на роўні працэсаў і адзеленыя прасторы працы для кожнага карыстальніка чы запуску. А замена большога пэйлоаду на просто паш захоўвае кантэкст толькі тады, калі паш ёсць обмежаны і агент можа пазней запрацаваць толькі частку даных.

Зберагчы кантэкст між сесіямі

Дзейнікі з работай трываюць дыяўольш чым працэсы і вікна кантэксту. SDK праставляе тры варыянты: продакцэюваць пасляпэўныя дзеянні ў межах апошняй розмовы, вярнуцца да конкрэтной сесіі па ўпынку, чы расколасці сесію на новую галузь. Функцыя «Продакцэюваць» падходзіць для продажнай лініі работ, «Вярнуцца» — для явнага восстанавлення, а «Раскол» — для далейшых дзеянняў, якія должны успадкуваць пакананы кантэкст без змены будучых дзеянняў асалідной сесіі.

# examples/session_modes.py
def session_options(session_id: str) -> dict[str, ClaudeAgentOptions]:
    return {
        "continue": ClaudeAgentOptions(continue_conversation=True),
        "resume": ClaudeAgentOptions(resume=session_id),
        "fork": ClaudeAgentOptions(resume=session_id, fork_session=True),
    }

Кожны варыянт зберагае стан спілкування: запіты, вызовы інструментаў, рэзультаты і адпаведныя адказы. Для форкі выдаецца новы ідэнтыфікатор сесіі, тады як первоначальны застаецца без змян. Гэта падходзіць для запітоў у духу "Цяпер жорсткая прыроўнайце гэтыя нараджанні спецыяльна для агентаў кодавання", адколі новая галузь можа будавацца на вялікай меры на аснове ўжо існуючага направлення доследжэння.

У восьмым рысунке прадстаўлены разлікі между рэжымамі: "Продаваць" прарабатоўвае над чынным направленням, "Адноваць" вяртае выбранае, а "Форкаваць" запускае новую галузь з таго ж пункта контролю.

Файлы на дыске трэбуець аднаэчнае адаробка. Галузі, створаныя праз fork, дзеліцца адной катэгорыяю працы, як толькі програма не задае іншую, таму обе могу перазапісаць plan.md або final_report.md. Програма ухілваецца ад зіштыкненняў, ствараючы унікальны корант выканання на адной базе UTC-часовага пазначэння і адным случайным суфіксам, а форк для практычнага выкарыстоўвання павінен скопіюваць або стварыць версію катэгорыяю працы ў новы корант выканання перад тым, як будзе выканана пісанне ў будзь-якай з галузей. Памятаце, што ID сесіі называе історыю дыялогу. Гэта не ID транзакцыі для зовнішніх сістэм, з якімі взаімаўся агент, таму базы дадзеных, хранілішчы об’ектаў, індэксы пошуку і працоўскія прасторы кожны павінен маты сваю сопственную політыку прыменнення версій і паралельнай працы. Структура файлаў робіць гэта відным і не хавае яго за прыменненням функцыі продажу. Fork аддзеляе толькі дыялог, тады як зовнішні стан застаецца спільным, пакуль працоўскі прастор не будзе версіяваны або роздзелены.

Складанне агента, створанага на адміністрацыі кантэксту

Готавыя настройкі аб’еднваюць усе чатыры операцыі і не даджаюць жадных дапаможных канструкцый для оркестрацыі. Функцыя Write – гэта структураваная рабочая прастора. Функцыя Select – гэта чытанне файлоў па фазам, адкладзеныя схемы інструментаў і выкарыстанне абмежаных запісаў знанняў. Функцыя Compress – гэта складзенне парамоў SDK разам з інструкцыямі пра зберагачэнне дадзеных і чэкпоінтом, які бераецца адразу перад цым. Функцыя Isolate – гэта стварэнне саб-агентаў у павярзаных межах і зберагачэнне выходных дадзеных інструментаў у файлах.

# research_agent/config.py
tools = ["Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch", "Agent"]
return ClaudeAgentOptions(
    cwd=run_root,
    model=model,
    tools=tools,
    allowed_tools=[*tools, "mcp__research__*"],
    permission_mode="dontAsk",
    mcp_servers={"research": server},
    strict_mcp_config=True,
    setting_sources=["project"],
    system_prompt={"type": "preset", "preset": "claude_code", "append": ENGINEERED_PROMPT},
    env={"ENABLE_TOOL_SEARCH": "true"},
    hooks={"PreCompact": [HookMatcher(hooks=[build_precompact_hook(workspace)])]},
    agents=research_subagents(model),
    max_turns=30,
    max_budget_usd=max_budget_usd,
)

Установка setting_sources=["project"] дозволяе файлу CLAUDE.md, прызначанам для конкрэтнага запуску, быць частыней настройкаў, а параметр strict_mcp_config=True обмежвае выкарыстоўванне SDK толькі серверам, які задае гэта застосоўчы. Значэнне permission_mode стаўляеся на dontAsk толькі таму, што прыклад аднасецца да автономных, ізольаваных запускаў, і гэта не ёсць загальнай рэкамендацыяю па правілах дазволаў. Розгледнутыя тут складныя працэсы затверджэння і політыкі развяртання навмысна не ўключаюцца.

У дзевятым рысунке паказана архітэктура, якая керуе чатырама аспектамі: стойкімі запісамі, выбранымі чытаннямі, складчэннем кантакту і ізольаваным выконанням. Галоўны мадэль получае толькі сфокусаваны, актыўны кантэкст.

Пасёлковы рэнеры выкарыстоўваюць ClaudeSDKClient і не выкарыстоўваюць простыяйшы функцыянт query(). Яны раздзеляюць той самы цыкл агента, але кліент забезпечвае чыстую межу адпаведзення і функцыя get_context_usage() для заканчэння працы. Гэты выбор быў здабыты пад вплывам тэхнічных рашэнняў, і архітэктура застаецца такой, якая была.

Першая спроба рэалізацыі, здабыта выключна на адной падставе запросаў, апавярнула ўсё новую проблему. Усе тры запускі SDK завершыліся, але толькі аднаму ўдалося стварыць файл final_report.md; два галоўных агента вернуліся, калі дэлегаваная праца фактычна застала незавершанай. Таму чыстае завершэння SDK не значыць, што задача прыкладнага програма завершана. Рашэнням было зробіць вызовы падагентаў явна сінхроннымі і даць кантролеру свой саўнішы контракт завершэння: ён шукае значны аўтарапорт, які мае ў сабе прынеймна два URL-адрэсы, і дазволяе адной фокусаванай запытці на вылечэнне ў той жа сесіі.

# research_agent/runner.py
while True:
    async for message in client.receive_response():
        metrics.observe(message)

    report_path = workspace / "final_report.md"
    if mode == "naive" or _report_meets_contract(report_path):
        break
    if metrics.completion_retries >= MAX_COMPLETION_RETRIES:
        break

    metrics.completion_retries += 1
    await client.query(COMPLETION_REPAIR_PROMPT)

Рэзультат, які не быў створаны пасля толькі запіту, застаецца ў measurements/initial-prompt-only-comparison.json, адтолькі кантролер павінен пераканацца ў стойкіх рэзультатах, а не выводзіць іх з фінальнага статусу модэлю.

Сет тэстаў па з’яўе карыстаецца для адрабаткі всьго, што не трэбуе модэлю: як наборы опцый, так і стварэнне рабочай прасторы, абмежанае выкарыстоўванне дадзеных, адхіленне ад некорэктных шляхоў, контрольныя пункты для стыснення дадзеных і уточнэнне лісты паведамленняў. Гэта тое, што ён адзвярнуў у згаданай асяроддзя.

# Captured output: python -m unittest discover -s tests -q
----------------------------------------------------------------------
Ran 12 tests in 0.048s

OK

У тым моманце фрэймворк стаець прыемным для выкарыстання: кожна дзеясць прыязана да межы, якая мае свой код, адпаведнага власніка для яе стану і інварыянт, які можа пераканаліць тэст.

Порэшчанне двух архітэктур

Тверджэння пра архітэктуру павінны ператварыцца ў цыфры. RunMetrics сочыць за стрэмам паведамленняў SDK, адключае дублікаты крокаў асістэнта на верхнім роўні за дапамогою ID паведамленняў, лічыць кожны вызов інструмента і кожную межу складання, підрахоўвае канцыранты ў рэзультатах інструментаў і запрашае фінальны знімак кантэксту. Сумарная ступень выкарыстання мадэлі ў всім дрэве агента, уключаючы сабагентаў, выклікаецца з ResultMessage.model_usage, тады як total_cost_usd — гэта локальная оцэнка, а не выклік на оплату.

# research_agent/metrics.py
if isinstance(message, ResultMessage):
    self.query_results += 1
    self.session_id = message.session_id
    self.result_subtype = message.subtype
    result = message.result or ""
    self.sdk_success = (
        message.subtype == "success"
        and bool(result.strip())
        and "not logged in" not in result.lower()
    )
    self.estimated_cost_usd = (
        (self.estimated_cost_usd or 0.0) + (message.total_cost_usd or 0.0)
    )
    self.result_usage = message.usage
    self.model_usage = self._merge_model_usage(self.model_usage, message.model_usage)
    self.total_tree_input_tokens = self._tree_input_tokens(self.model_usage)

if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
    self.compactions += 1

Кожная архітэктура была запушчана трыццаць разоў, незалежна ад іншых, для адпаведнага запіту, з ідэнтычным SDK, псевданіем мадэлі, прадаўцам і настройкамі локальных дадзеных. Запісы за кожны запуск і вырахаваныя значэння знаходзяцца ў measurements/comparison.json, а невелікі скрыпт для адображэння генеруе компактную табліцу з гэтага файла.

# terminal
python scripts/show_comparison.py ../measurements/comparison.json

Дзесяты ўжоць паказвае сярэгу трых запускаў і адкрывае справжнія компромісы. Ізоляцыя зменшыла канецовы тэкст рэзультата, які бачыў лідэр, але дрэва падагентаў займалі значна больш токенаў і мелі вышэйшую ацэнку вартасці. Прыклад захаванага выходу скрыпты адображэння прыкладзены ніжчэ.

# Captured output: python scripts/show_comparison.py ../measurements/comparison.json
Measured comparison - 3 runs per architecture
Metric                               Naive      Engineered
Artifact success                       3/3             3/3
SDK success                            3/3             1/3
Mean tree input                    102,210         737,036
Mean peak context                   17,184          32,668
Final tool-result tokens             7,351           6,202
Mean subagents                           0               3
Mean estimated cost                 $0.663          $3.270
Compactions                              0               0

Успех бывае двух відаў. Успех артыфакта означае, што быў выпанаваны контракт выходных дадзеных архітэктуры: для простага агента — гэта звёрнуты справаздача, у яком указаны прынеймна два розныя URL-адрэсы, а для праектаванага агента — стойкі файл final_report.md дужоўжынаю 500 і болей сімваў, у яком таксама ёсць два URL-адрэсы. Усе шысць працоў выканалі гэта заведама.

Для успеху SDK неабяжна тое, каб фінальны падтып ResultMessage быў success. У простых запусках гэта выканалася тры разы з трох, у запусках з інжынерным кераванням — адна раз з трох. Два запуска з інжынерным кераванням стварылі неабяжныя атрыбуты, але завершыліся з кодам error_max_budget_usd; адны з іх выкорыстаў едынственны запіт на рэмонт ад кантролера. У прыкладзе з бюджэтом ад Anthropic пояснюецца, што вітраты пераказваюцца пасля вызову API, таму запуск можа завершыць стварэнне свайго рэзультата і ўсё ж працаваць у межах заданага ліміту, прычаму ўсё ешчэ чакаецца фінальны рэзультат. Спрыяйце ліміту як захоўнай бар’еру, а не як сігналу, што прылада завершыла свою працу.

Ціфры:

  • Лід отрымаў меншае колічэство інфармацыі. Серэговая колькасць сімваў рэзультата з адзначэнням ліда складала 28,572 для базовага стану і 23,428 пасля ізоляцыі, што ўскладнілася на 18,0%; частка рэзультата з адзначэнням ліда у фінальным знімку кантэксту зменшылася з 7,351 да 6,202 токенав (на 15,6% меней). У кожным експерыменце выконваліся ровна тры падагенты, і ўсе іх проміжныя пошукі ніколі не прабраўляліся ў траекторыю ліда.
  • Аб’ёмныя вітраты роўнасабліва зрослі. Аб’ёмны вхідныя даны ў цэлым дрэве складалі 102,210 токенав спачатку і 737,036 пасля, што ўскладнілася на 621,1%. Максымальны аб’ёмны вхідныя даны ліда-кантэксту складалі 17,184 токенав спачатку і 32,668 пасля, што ўскладнілася на 90,1%; прычому ацэнка вітатаў SDK зменілася з $0,663 на $3,270, што ўскладнілася на 393,4%.
  • Актыўнасць інструменту таксама выросла: апошнія 30,3 вызову ў всій структуры, протыраць шасця ў базовай сіткі. Для такага обмежанага заведама, тры спецыялісты, ўсе іх схемы і абмены межы між ямі коштаюць больш, чым кантэкст, які ўсё-такі залішаўся для керуючага.
  • Компактаванне ніколі не было выкарыстоўвана, адтолькі кожны запуск паказваў нуль меж. Гэта не адначасова значыць, што компактаванне з урахоўваннем захавання даных ёсць неэфектывае. Гэта значыць, што гэты заведама ніколі не падыходзіў да рубежа у 200 000 токенав. Эксперымент таксама не аддзельвае значэнне пошуку інструмента, адтолькі каталог MCP быў маленькі, і відкладанне адкрыць мала мало можлівасцей компенсаваць свае додатковыя вітраты.

    Што гавораць, а што не гавораць рэзультаты

    Няма нічага, што могла бы стаць падставай для абсалютнага адраджэння інжынерыі кантэксту. Даныя паказваюць, чаму чатыры операцыі трэба выкорыстоўваць выбарча. Запіс файлаў і обмежаныя запыты да дадзеных ствараюць стойкі стан, які можна пераглядзець. Ізоляцыя ёсць корыстной, калі траекторыя дзеці інакш забрудняла бы вядомы кантэкст, які і так ўжо дужа дзейна, і гэта ніколі не ўсунагаеся без каштоўкаў. Працюючы з такым размерам задання, адзін вядомы кантэкст з допаможным просторам можа стаць лепшым рашэнням для практычнае выкарыстоўвання, як толькі павышэнне якосці завдзяк спецыялістам не пакрывае дадзеных каштоўкаў.

    Што застаецца – гэта рабочы артыфакт з архітэктурай кантэкста, локальна проблема з бягам і чыткія межы ацэнкі. Розумны наступны вопыт стосуецца якасці: чы рэальна матэрыял, якай запісваецца, выбіраецца, стыскаецца і ізольуецца, ёсць самэй правільным матэрыялам? Чытанне на гэты вопыт вымагае ацэнкі на рывень задання ў чысці пакрыцча доказаў, адмахвання протыярысоў, правядзімасці цытатаў і вяснавання пасля стыснення. У гэтым случае ізоляція задання зменшыла об’ём даных, якія перадаецца з інструментам, але падвоіла канцэнтрацыі і вітраты для всей структуры, таму інжынерыя кантэкста павінна ацэнювалася як цэлая сістэма, а не прыймалася за оптымаізацыю.

    Спадневаная літэратура

  • Стварэнне захішчанага кампайляра кантэкста для AI-агентаў у .NET 10 — Кампайляванне кантэкста модэля ў .NET 10 з атрыбутамі доверлівасці, перакананням у свежасці, механізмамі маскавання, карантынаў для інжэкцый, дэтэрміністычнымі лімітамі токэнаў і маніфестам SHA-256, які можна працаваць.