diff --git a/README-ru.md b/README-ru.md new file mode 100644 index 000000000..95a5ee29b --- /dev/null +++ b/README-ru.md @@ -0,0 +1,377 @@ +[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) | [Русский](./README-ru.md) +# Learn Claude Code -- Harness Engineering для настоящих агентов + +## Model -- это и есть Agent + +Прежде чем говорить о коде, давайте раз и навсегда расставим всё по своим местам. + +**Agent -- это model. Не фреймворк. Не цепочка промптов. Не drag-and-drop рабочий процесс.** + +### Что такое Agent + +Agent -- это нейронная сеть: Transformer, RNN, обученная функция, которая через миллиарды шагов градиентного спуска на данных о последовательностях действий научилась воспринимать окружающую среду, рассуждать о целях и совершать действия для их достижения. Слово «agent» в сфере ИИ всегда означало именно это. Всегда. + +Человек -- это agent. Биологическая нейронная сеть, сформированная миллионами лет эволюционного обучения, воспринимающая мир через органы чувств, рассуждающая с помощью мозга, действующая посредством тела. Когда DeepMind, OpenAI или Anthropic говорят «agent», они имеют в виду то же самое, что это слово означало с самого начала существования области: **model, которая научилась действовать.** + +Доказательство написано историей: + +- **2013 -- DeepMind DQN играет в Atari.** Одна нейронная сеть, получая только сырые пиксели и игровые счета, научилась играть в 7 игр для Atari 2600 -- превзойдя все предыдущие алгоритмы и победив экспертов-людей в 3 из них. К 2015 году та же архитектура масштабировалась до [49 игр и сравнялась с профессиональными тестировщиками](https://www.nature.com/articles/nature14236), публикация в *Nature*. Никаких игровых правил, никаких деревьев решений. Одна model, обучающаяся на опыте. Эта model и была agent. + +- **2019 -- OpenAI Five покоряет Dota 2.** Пять нейронных сетей, сыгравших [45 000 лет Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/) против самих себя за 10 месяцев, победили **OG** -- действующих чемпионов мира TI8 -- со счётом 2:0 в прямом эфире из Сан-Франциско. В последующем публичном турнире ИИ выиграл 99,4% из 42 729 партий против всех желающих. Никаких заскриптованных стратегий. Никакой заранее запрограммированной командной координации. Model сами научились командной работе, тактике и адаптации в реальном времени исключительно через самоигру. + +- **2019 -- DeepMind AlphaStar осваивает StarCraft II.** AlphaStar [победил профессиональных игроков со счётом 10:1](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/) в закрытом матче, а позднее достиг [статуса Grandmaster](https://www.nature.com/articles/d41586-019-03298-6) на европейских серверах -- войдя в топ 0,15% из 90 000 игроков. Игра с неполной информацией, решениями в реальном времени и комбинаторным пространством действий, многократно превосходящим шахматы и го. Agent? Model. Обученная. Не заскриптованная. + +- **2019 -- Tencent Jueyu доминирует в Honor of Kings.** «Jueyu» от Tencent AI Lab [победила профессиональных игроков KPL](https://www.jiemian.com/article/3371171.html) в полноценном матче 5v5 на World Champion Cup. В режиме 1v1 профессионалы выиграли лишь [1 из 15 игр и ни разу не пережили 8 минут](https://developer.aliyun.com/article/851058). Интенсивность обучения: один день равнялся 440 человеческим годам. К 2021 году Jueyu превзошла профессионалов KPL по всему пулу героев. Никаких ручных таблиц матчапов. Никаких заскриптованных составов. Model, выучившая всю игру с нуля через самоигру. + +- **2024-2025 -- LLM agents меняют облик разработки ПО.** Claude, GPT, Gemini -- большие языковые model, обученные на всём массиве человеческого кода и рассуждений, -- разворачиваются как coding agents. Они читают кодовые базы, пишут реализации, отлаживают ошибки, координируют работу в командах. Архитектура идентична всем предыдущим agents: обученная model, помещённая в среду и снабжённая tools для восприятия и действия. Единственное отличие -- масштаб усвоенного и универсальность решаемых задач. + +Каждое из этих достижений подтверждает одно: **«agent» -- это никогда не окружающий код. Agent -- это всегда model.** + +### Чем Agent не является + +Слово «agent» было присвоено целой индустрией по прокладке промптов. + +Drag-and-drop конструкторы рабочих процессов. No-code платформы «AI agents». Библиотеки оркестрации цепочек промптов. Все они разделяют одно заблуждение: что соединение вызовов LLM API через ветки if-else, графы узлов и жёстко прошитую логику маршрутизации -- это «создание agent». + +Это не так. То, что они строят, -- машина Рубе Голдберга: чрезмерно сложный, хрупкий конвейер из процедурных правил, куда LLM вставлен как раздутая нода завершения текста. Это не agent. Это shell-скрипт с манией величия. + +**«Agents» из промпт-водопроводов -- это фантазия программистов, которые не обучают model.** Они пытаются брутфорсом получить интеллект, нагромождая процедурную логику -- огромные деревья правил, графы узлов, каскады цепочек промптов -- и молясь, что достаточное количество связующего кода каким-то образом породит автономное поведение. Не породит. Нельзя сконструировать агентность. Агентность -- это результат обучения, а не программирования. + +Такие системы мертворождённые: хрупкие, немасштабируемые, принципиально неспособные к обобщению. Это современное воскрешение GOFAI (Good Old-Fashioned AI) -- символических систем правил, от которых область отказалась десятилетия назад, теперь покрашенных в цвет LLM. Другая упаковка, тот же тупик. + +### Смена мышления: от «разработки agents» к разработке Harness + +Когда кто-то говорит «я разрабатываю agent», это может означать лишь одно из двух: + +**1. Обучение model.** Корректировка весов через reinforcement learning, дообучение, RLHF или другие методы на основе градиентного спуска. Сбор данных о процессе выполнения задач -- реальных последовательностей восприятия, рассуждения и действия в реальных областях -- и использование их для формирования поведения model. Именно этим занимаются DeepMind, OpenAI, Tencent AI Lab и Anthropic. Это разработка agent в истинном смысле слова. + +**2. Создание harness.** Написание кода, дающего model среду для работы. Это то, чем занимаемся большинство из нас, и это -- тема данного репозитория. + +Harness -- это всё, что нужно agent для функционирования в конкретной области: + +``` +Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions + + Tools: file I/O, shell, network, database, browser + Knowledge: product docs, domain references, API specs, style guides + Observation: git diff, error logs, browser state, sensor data + Action: CLI commands, API calls, UI interactions + Permissions: sandboxing, approval workflows, trust boundaries +``` + +Model принимает решения. Harness их исполняет. Model рассуждает. Harness предоставляет context. Model -- водитель. Harness -- транспортное средство. + +**Harness coding agent -- это его IDE, терминал и доступ к файловой системе.** Harness фермерского agent -- это массив датчиков, управление орошением и потоки погодных данных. Harness гостиничного agent -- это система бронирования, каналы общения с гостями и API управления объектом. Agent -- интеллект, принимающий решения -- всегда model. Harness меняется в зависимости от области. Agent обобщается на все области. + +Этот репозиторий учит создавать транспортные средства. Транспортные средства для написания кода. Но паттерны проектирования применимы к любой области: управление фермой, гостиничный бизнес, производство, логистика, здравоохранение, образование, научные исследования. Везде, где задачу нужно воспринять, осмыслить и выполнить -- agent нужен harness. + +### Что на самом деле делают Harness Engineers + +Если вы читаете этот репозиторий, скорее всего, вы harness engineer -- и это очень сильная позиция. Вот ваша настоящая работа: + +- **Реализуйте tools.** Дайте agent руки. Чтение/запись файлов, выполнение shell-команд, вызовы API, управление браузером, запросы к базам данных. Каждый tool -- это действие, которое agent может совершить в своей среде. Проектируйте их атомарными, компонуемыми и хорошо описанными. + +- **Курируйте knowledge.** Дайте agent экспертизу в области. Документация продукта, записи об архитектурных решениях, руководства по стилю, нормативные требования. Загружайте по требованию (s05), а не заранее. Agent должен знать, что доступно, и брать то, что нужно. + +- **Управляйте context.** Дайте agent чистую память. Изоляция subagent (s04) предотвращает утечку шума. Сжатие context (s06) предотвращает переполнение историей. Системы задач (s07) сохраняют цели за пределами одного разговора. + +- **Контролируйте permissions.** Задайте agent границы. Ограничьте доступ к файлам через sandbox. Требуйте подтверждения для деструктивных операций. Соблюдайте границы доверия между agent и внешними системами. Здесь безопасность встречается с harness engineering. + +- **Собирайте данные о процессе выполнения задач.** Каждая последовательность действий, которую agent выполняет в вашем harness, -- это обучающий сигнал. Трассировки восприятия-рассуждения-действия из реальных развёртываний -- это сырой материал для дообучения следующего поколения model agents. Ваш harness не просто обслуживает agent -- он может помочь его улучшить. + +Вы пишете не интеллект. Вы строите мир, в котором этот интеллект живёт. Качество этого мира -- насколько чётко agent воспринимает, насколько точно действует, насколько богаты его доступные знания -- напрямую определяет, насколько эффективно интеллект может себя проявить. + +**Стройте отличные harnesses. Agent сделает остальное.** + +### Почему Claude Code -- мастер-класс по Harness Engineering + +Почему этот репозиторий препарирует именно Claude Code? + +Потому что Claude Code -- это самый элегантный и полноценно реализованный harness для agent, который нам доводилось видеть. Не из-за какого-то одного умного трюка, а из-за того, чего он *не делает*: он не пытается быть agent. Он не навязывает жёстких рабочих процессов. Он не перепроверяет model сложными деревьями решений. Он снабжает model tools, knowledge, управлением context и границами permissions -- а затем уходит с дороги. + +Посмотрите, что такое Claude Code в своей сути: + +``` +Claude Code = один agent loop + + tools (bash, read, write, edit, glob, grep, browser...) + + on-demand skill loading + + context compression + + subagent spawning + + task system with dependency graph + + team coordination with async mailboxes + + worktree isolation for parallel execution + + permission governance +``` + +Вот и всё. Вся архитектура целиком. Каждый компонент -- это механизм harness: кусочек мира, построенный для agent. Сам agent? Это Claude. Model. Обученная Anthropic на всём массиве человеческого мышления и кода. Harness не делает Claude умным. Claude уже умён. Harness даёт Claude руки, глаза и рабочее пространство. + +Вот почему Claude Code -- идеальный предмет для изучения: **он демонстрирует, что происходит, когда вы доверяете model и сосредотачиваете свои инженерные усилия на harness.** Каждая сессия в этом репозитории (s01-s12) реконструирует один механизм harness из архитектуры Claude Code. По завершении вы понимаете не только то, как работает Claude Code, но и универсальные принципы harness engineering, применимые к любому agent в любой области. + +Урок не в том, чтобы «скопировать Claude Code». Урок таков: **лучшие продукты на основе agents создают инженеры, понимающие, что их работа -- это harness, а не интеллект.** + +--- + +## Видение: наполнить вселенную настоящими Agents + +Это не только про coding agents. + +В каждой области, где люди выполняют сложную, многоэтапную работу, требующую суждений, могут работать agents -- при наличии правильного harness. Паттерны этого репозитория универсальны: + +``` +Estate management agent = model + property sensors + maintenance tools + tenant comms +Agricultural agent = model + soil/weather data + irrigation controls + crop knowledge +Hotel operations agent = model + booking system + guest channels + facility APIs +Medical research agent = model + literature search + lab instruments + protocol docs +Manufacturing agent = model + production line sensors + quality controls + logistics +Education agent = model + curriculum knowledge + student progress + assessment tools +``` + +Loop всегда один и тот же. Tools меняются. Knowledge меняется. Permissions меняются. Agent -- model -- обобщается на всё. + +Каждый harness engineer, читающий этот репозиторий, осваивает паттерны, применимые далеко за пределами разработки ПО. Вы учитесь строить инфраструктуру для интеллектуального автоматизированного будущего. Каждый хорошо спроектированный harness, развёрнутый в реальной области, -- это ещё одно место, где agent может воспринимать, рассуждать и действовать. + +Сначала мы заполняем мастерские. Затем фермы, больницы, заводы. Потом города. Потом планету. + +**Bash is all you need. Real agents are all the universe needs.** + +--- + +``` + THE AGENT PATTERN + ================= + + User --> messages[] --> LLM --> response + | + stop_reason == "tool_use"? + / \ + yes no + | | + execute tools return text + append results + loop back -----------------> messages[] + + + That's the minimal loop. Every AI agent needs this loop. + The MODEL decides when to call tools and when to stop. + The CODE just executes what the model asks for. + This repo teaches you to build what surrounds this loop -- + the harness that makes the agent effective in a specific domain. +``` + +**12 последовательных сессий: от простого loop до изолированного автономного выполнения.** +**Каждая сессия добавляет один механизм harness. У каждого механизма -- своё motto.** + +> **s01**   *"Один loop и Bash -- это всё, что нужно"* — один tool + один loop = agent +> +> **s02**   *"Добавить tool -- значит добавить один обработчик"* — loop остаётся прежним; новые tools регистрируются в таблице диспетчеризации +> +> **s03**   *"Agent без плана блуждает"* — сначала составь список шагов, потом выполняй; завершаемость удваивается +> +> **s04**   *"Дели большие задачи; каждая подзадача получает чистый context"* — subagents используют независимые messages[], сохраняя главный разговор чистым +> +> **s05**   *"Загружай knowledge тогда, когда это нужно, а не заранее"* — передавай через tool_result, а не через system prompt +> +> **s06**   *"Context заполняется; нужен способ освободить место"* — трёхуровневая стратегия сжатия для бесконечных сессий +> +> **s07**   *"Дели большие цели на малые задачи, упорядочивай их, сохраняй на диск"* — файловый граф задач с зависимостями, закладывающий основу для многоагентного сотрудничества +> +> **s08**   *"Запускай медленные операции в фоне; agent продолжает думать"* — daemon threads выполняют команды и отправляют уведомления по завершении +> +> **s09**   *"Когда задача слишком велика для одного -- делегируй товарищам по команде"* — постоянные teammates + async mailboxes +> +> **s10**   *"Teammates нужны общие правила коммуникации"* — один паттерн запрос-ответ управляет всеми переговорами +> +> **s11**   *"Teammates сами просматривают доску и берут задачи"* — лидеру не нужно назначать каждую задачу вручную +> +> **s12**   *"Каждый работает в своей директории, без помех"* — tasks управляют целями, worktrees управляют директориями, связанными по ID + +--- + +## Основной паттерн + +```python +def agent_loop(messages): + while True: + response = client.messages.create( + model=MODEL, system=SYSTEM, + messages=messages, tools=TOOLS, + ) + messages.append({"role": "assistant", + "content": response.content}) + + if response.stop_reason != "tool_use": + return + + results = [] + for block in response.content: + if block.type == "tool_use": + output = TOOL_HANDLERS[block.name](**block.input) + results.append({ + "type": "tool_result", + "tool_use_id": block.id, + "content": output, + }) + messages.append({"role": "user", "content": results}) +``` + +Каждая сессия добавляет один механизм harness поверх этого loop -- не изменяя сам loop. Loop принадлежит agent. Механизмы принадлежат harness. + +## Область охвата (важно) + +Этот репозиторий -- учебный проект уровня 0->1 по harness engineering: построению среды, окружающей model-agent. +Намеренно упрощены или опущены некоторые производственные механизмы: + +- Полные шины событий/хуков (например, PreToolUse, SessionStart/End, ConfigChange). + s12 включает только минимальный append-only поток событий жизненного цикла в учебных целях. +- Управление permissions на основе правил и рабочие процессы доверия +- Управление жизненным циклом сессии (resume/fork) и расширенное управление жизненным циклом worktree +- Полные детали MCP runtime (transport/OAuth/resource subscribe/polling) + +Рассматривайте протокол team JSONL mailbox в этом репозитории как учебную реализацию, а не как утверждение о каких-либо конкретных производственных внутренностях. + +## Быстрый старт + +```sh +git clone https://github.com/shareAI-lab/learn-claude-code +cd learn-claude-code +pip install -r requirements.txt +cp .env.example .env # Edit .env with your ANTHROPIC_API_KEY + +python agents/s01_agent_loop.py # Start here +python agents/s12_worktree_task_isolation.py # Full progression endpoint +python agents/s_full.py # Capstone: all mechanisms combined +``` + +### Веб-платформа + +Интерактивные визуализации, пошаговые диаграммы, просмотр исходного кода и документация. + +```sh +cd web && npm install && npm run dev # http://localhost:3000 +``` + +## Путь обучения + +``` +Phase 1: THE LOOP Phase 2: PLANNING & KNOWLEDGE +================== ============================== +s01 The Agent Loop [1] s03 TodoWrite [5] + while + stop_reason TodoManager + nag reminder + | | + +-> s02 Tool Use [4] s04 Subagents [5] + dispatch map: name->handler fresh messages[] per child + | + s05 Skills [5] + SKILL.md via tool_result + | + s06 Context Compact [5] + 3-layer compression + +Phase 3: PERSISTENCE Phase 4: TEAMS +================== ===================== +s07 Tasks [8] s09 Agent Teams [9] + file-based CRUD + deps graph teammates + JSONL mailboxes + | | +s08 Background Tasks [6] s10 Team Protocols [12] + daemon threads + notify queue shutdown + plan approval FSM + | + s11 Autonomous Agents [14] + idle cycle + auto-claim + | + s12 Worktree Isolation [16] + task coordination + optional isolated execution lanes + + [N] = number of tools +``` + +## Архитектура + +``` +learn-claude-code/ +| +|-- agents/ # Python reference implementations (s01-s12 + s_full capstone) +|-- docs/{en,zh,ja}/ # Mental-model-first documentation (3 languages) +|-- web/ # Interactive learning platform (Next.js) +|-- skills/ # Skill files for s05 ++-- .github/workflows/ci.yml # CI: typecheck + build +``` + +## Документация + +Подход «сначала ментальная модель»: проблема, решение, ASCII-диаграмма, минимальный код. +Доступно на [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/) | [Русский](./docs/ru/). + +| Сессия | Тема | Motto | +|---------|-------|-------| +| [s01](./docs/ru/s01-the-agent-loop.md) | The Agent Loop | *Один loop и Bash -- это всё, что нужно* | +| [s02](./docs/ru/s02-tool-use.md) | Tool Use | *Добавить tool -- значит добавить один обработчик* | +| [s03](./docs/ru/s03-todo-write.md) | TodoWrite | *Agent без плана блуждает* | +| [s04](./docs/ru/s04-subagent.md) | Subagents | *Дели большие задачи; каждая подзадача получает чистый context* | +| [s05](./docs/ru/s05-skill-loading.md) | Skills | *Загружай knowledge тогда, когда это нужно, а не заранее* | +| [s06](./docs/ru/s06-context-compact.md) | Context Compact | *Context заполняется; нужен способ освободить место* | +| [s07](./docs/ru/s07-task-system.md) | Tasks | *Дели большие цели на малые задачи, упорядочивай их, сохраняй на диск* | +| [s08](./docs/ru/s08-background-tasks.md) | Background Tasks | *Запускай медленные операции в фоне; agent продолжает думать* | +| [s09](./docs/ru/s09-agent-teams.md) | Agent Teams | *Когда задача слишком велика для одного -- делегируй teammates* | +| [s10](./docs/ru/s10-team-protocols.md) | Team Protocols | *Teammates нужны общие правила коммуникации* | +| [s11](./docs/ru/s11-autonomous-agents.md) | Autonomous Agents | *Teammates сами просматривают доску и берут задачи* | +| [s12](./docs/ru/s12-worktree-task-isolation.md) | Worktree + Task Isolation | *Каждый работает в своей директории, без помех* | + +## Что дальше -- от понимания к созданию продуктов + +После 12 сессий вы понимаете harness engineering изнутри и снаружи. Два способа применить эти знания: + +### Kode Agent CLI -- CLI coding agent с открытым исходным кодом + +> `npm i -g @shareai-lab/kode` + +Поддержка Skill и LSP, готовность к Windows, совместимость с GLM / MiniMax / DeepSeek и другими открытыми model. Устанавливайте и работайте. + +GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)** + +### Kode Agent SDK -- встраивайте возможности Agent в своё приложение + +Официальный Claude Code Agent SDK взаимодействует с полноценным CLI-процессом под капотом -- каждый одновременный пользователь означает отдельный процесс терминала. Kode SDK -- это автономная библиотека без накладных расходов на процесс для каждого пользователя, встраиваемая в бэкенды, расширения браузера, встраиваемые устройства или любую среду выполнения. + +GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)** + +--- + +## Родственный репозиторий: от *сессий по запросу* до *постоянно работающего ассистента* + +Harness, которому учит этот репозиторий, -- **использовать и выбрасывать**: открыл терминал, дал agent задачу, закрыл по завершении, следующая сессия начинается с чистого листа. Такова модель Claude Code. + +[OpenClaw](https://github.com/openclaw/openclaw) доказал другую возможность: поверх того же ядра agent два механизма harness превращают agent из «ткни, чтобы он двинулся» в «просыпается каждые 30 секунд, чтобы проверить, есть ли работа»: + +- **Heartbeat** -- каждые 30 секунд harness отправляет agent сообщение: есть ли что-то, что нужно сделать? Нет? Обратно спать. Есть? Действовать немедленно. +- **Cron** -- agent может планировать свои будущие задачи, которые автоматически выполняются в нужное время. + +Добавьте многоканальную IM-маршрутизацию (WhatsApp / Telegram / Slack / Discord, 13+ платформ), постоянную память context и систему личности Soul -- и agent превратится из одноразового инструмента в постоянно работающего персонального ИИ-ассистента. + +**[claw0](https://github.com/shareAI-lab/claw0)** -- наш сопутствующий учебный репозиторий, деконструирующий эти механизмы harness с нуля: + +``` +claw agent = agent core + heartbeat + cron + IM chat + memory + soul +``` + +``` +learn-claude-code claw0 +(agent harness core: (proactive always-on harness: + loop, tools, planning, heartbeat, cron, IM channels, + teams, worktree isolation) memory, soul personality) +``` + +## О нас +
+ +Сканируйте WeChat, чтобы подписаться, +или следите в X: [shareAI-Lab](https://x.com/baicai003) + +## Лицензия + +MIT + +--- + +**Model -- это agent. Код -- это harness. Стройте отличные harnesses. Agent сделает остальное.** + +**Bash is all you need. Real agents are all the universe needs.** diff --git a/README.md b/README.md index 02561fef1..4f14b82a0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) +[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) | [Русский](./README-ru.md) # Learn Claude Code -- Harness Engineering for Real Agents ## The Model IS the Agent @@ -299,7 +299,7 @@ learn-claude-code/ ## Documentation Mental-model-first: problem, solution, ASCII diagram, minimal code. -Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/). +Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/) | [Русский](./docs/ru/). | Session | Topic | Motto | |---------|-------|-------| diff --git a/docs/ru/s01-the-agent-loop.md b/docs/ru/s01-the-agent-loop.md new file mode 100644 index 000000000..b38c37fd1 --- /dev/null +++ b/docs/ru/s01-the-agent-loop.md @@ -0,0 +1,116 @@ +# s01: The Agent Loop + +`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Один loop и Bash — это всё, что нужно"* -- один tool + один loop = agent. +> +> **Harness layer**: The loop -- первое соединение model с реальным миром. + +## Проблема + +Языковая model умеет рассуждать о коде, но не может *взаимодействовать* с реальным миром — читать файлы, запускать тесты или проверять ошибки. Без loop каждый вызов tool требует вручную копировать и вставлять результаты обратно. Вы сами становитесь loop. + +## Решение + +``` ++--------+ +-------+ +---------+ +| User | ---> | LLM | ---> | Tool | +| prompt | | | | execute | ++--------+ +---+---+ +----+----+ + ^ | + | tool_result | + +----------------+ + (loop until stop_reason != "tool_use") +``` + +Одно условие выхода управляет всем потоком. Loop работает до тех пор, пока model не прекращает вызывать tools. + +## Как это работает + +1. Запрос пользователя становится первым сообщением. + +```python +messages.append({"role": "user", "content": query}) +``` + +2. Отправляем messages и определения tools в LLM. + +```python +response = client.messages.create( + model=MODEL, system=SYSTEM, messages=messages, + tools=TOOLS, max_tokens=8000, +) +``` + +3. Добавляем ответ ассистента. Проверяем `stop_reason` — если model не вызвала tool, работа завершена. + +```python +messages.append({"role": "assistant", "content": response.content}) +if response.stop_reason != "tool_use": + return +``` + +4. Выполняем каждый вызов tool, собираем результаты, добавляем как сообщение пользователя. Возвращаемся к шагу 2. + +```python +results = [] +for block in response.content: + if block.type == "tool_use": + output = run_bash(block.input["command"]) + results.append({ + "type": "tool_result", + "tool_use_id": block.id, + "content": output, + }) +messages.append({"role": "user", "content": results}) +``` + +Собранное в одну функцию: + +```python +def agent_loop(query): + messages = [{"role": "user", "content": query}] + while True: + response = client.messages.create( + model=MODEL, system=SYSTEM, messages=messages, + tools=TOOLS, max_tokens=8000, + ) + messages.append({"role": "assistant", "content": response.content}) + + if response.stop_reason != "tool_use": + return + + results = [] + for block in response.content: + if block.type == "tool_use": + output = run_bash(block.input["command"]) + results.append({ + "type": "tool_result", + "tool_use_id": block.id, + "content": output, + }) + messages.append({"role": "user", "content": results}) +``` + +Это весь agent — менее 30 строк. Всё остальное в этом курсе надстраивается сверху, не изменяя loop. + +## Что изменилось + +| Компонент | До | После | +|---------------|------------|--------------------------------| +| Agent loop | (нет) | `while True` + stop_reason | +| Tools | (нет) | `bash` (один tool) | +| Messages | (нет) | Накапливаемый список | +| Control flow | (нет) | `stop_reason != "tool_use"` | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s01_agent_loop.py +``` + +1. `Create a file called hello.py that prints "Hello, World!"` +2. `List all Python files in this directory` +3. `What is the current git branch?` +4. `Create a directory called test_output and write 3 files in it` diff --git a/docs/ru/s02-tool-use.md b/docs/ru/s02-tool-use.md new file mode 100644 index 000000000..9174ba570 --- /dev/null +++ b/docs/ru/s02-tool-use.md @@ -0,0 +1,99 @@ +# s02: Tool Use + +`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Добавить tool — значит добавить один handler"* -- loop остаётся прежним; новые tools регистрируются в dispatch map. +> +> **Harness layer**: Tool dispatch -- расширяем возможности model. + +## Проблема + +Имея только `bash`, agent использует оболочку для всего. `cat` обрезает вывод непредсказуемо, `sed` ломается на спецсимволах, а каждый bash-вызов — это неограниченная поверхность для атак. Специализированные tools вроде `read_file` и `write_file` позволяют применять ограничение путей на уровне tool. + +Ключевая идея: добавление tools не требует изменения loop. + +## Решение + +``` ++--------+ +-------+ +------------------+ +| User | ---> | LLM | ---> | Tool Dispatch | +| prompt | | | | { | ++--------+ +---+---+ | bash: run_bash | + ^ | read: run_read | + | | write: run_wr | + +-----------+ edit: run_edit | + tool_result | } | + +------------------+ + +The dispatch map — это словарь: {tool_name: handler_function}. +Один поиск заменяет любую цепочку if/elif. +``` + +## Как это работает + +1. Каждый tool получает функцию-handler. Ограничение путей предотвращает выход за пределы рабочего пространства. + +```python +def safe_path(p: str) -> Path: + path = (WORKDIR / p).resolve() + if not path.is_relative_to(WORKDIR): + raise ValueError(f"Path escapes workspace: {p}") + return path + +def run_read(path: str, limit: int = None) -> str: + text = safe_path(path).read_text() + lines = text.splitlines() + if limit and limit < len(lines): + lines = lines[:limit] + return "\n".join(lines)[:50000] +``` + +2. Dispatch map связывает имена tools с handlers. + +```python +TOOL_HANDLERS = { + "bash": lambda **kw: run_bash(kw["command"]), + "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")), + "write_file": lambda **kw: run_write(kw["path"], kw["content"]), + "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], + kw["new_text"]), +} +``` + +3. В loop ищем handler по имени. Тело loop осталось неизменным по сравнению с s01. + +```python +for block in response.content: + if block.type == "tool_use": + handler = TOOL_HANDLERS.get(block.name) + output = handler(**block.input) if handler \ + else f"Unknown tool: {block.name}" + results.append({ + "type": "tool_result", + "tool_use_id": block.id, + "content": output, + }) +``` + +Добавить tool = добавить handler + добавить запись в схему. Loop никогда не меняется. + +## Что изменилось по сравнению с s01 + +| Компонент | До (s01) | После (s02) | +|----------------|-----------------------|----------------------------| +| Tools | 1 (только bash) | 4 (bash, read, write, edit)| +| Dispatch | Жёстко закодированный bash | `TOOL_HANDLERS` dict | +| Безопасность путей | Нет | Sandbox `safe_path()` | +| Agent loop | Без изменений | Без изменений | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s02_tool_use.py +``` + +1. `Read the file requirements.txt` +2. `Create a file called greet.py with a greet(name) function` +3. `Edit greet.py to add a docstring to the function` +4. `Read greet.py to verify the edit worked` diff --git a/docs/ru/s03-todo-write.md b/docs/ru/s03-todo-write.md new file mode 100644 index 000000000..b1c4b7e37 --- /dev/null +++ b/docs/ru/s03-todo-write.md @@ -0,0 +1,96 @@ +# s03: TodoWrite + +`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Agent без плана блуждает"* -- сначала перечисли шаги, затем выполняй. +> +> **Harness layer**: Планирование -- удерживаем model на курсе, не прописывая маршрут. + +## Проблема + +На многоходовых задачах model теряет нить. Она повторяет уже сделанное, пропускает шаги или уходит в сторону. Длинные диалоги усугубляют ситуацию — системный промпт тускнеет по мере того, как tool results заполняют context. Рефакторинг в 10 шагов может завершить шаги 1–3, а затем model начинает импровизировать, потому что забыла шаги 4–10. + +## Решение + +``` ++--------+ +-------+ +---------+ +| User | ---> | LLM | ---> | Tools | +| prompt | | | | + todo | ++--------+ +---+---+ +----+----+ + ^ | + | tool_result | + +----------------+ + | + +-----------+-----------+ + | TodoManager state | + | [ ] task A | + | [>] task B <- doing | + | [x] task C | + +-----------------------+ + | + if rounds_since_todo >= 3: + inject into tool_result +``` + +## Как это работает + +1. TodoManager хранит элементы со статусами. Одновременно только один элемент может быть в состоянии `in_progress`. + +```python +class TodoManager: + def update(self, items: list) -> str: + validated, in_progress_count = [], 0 + for item in items: + status = item.get("status", "pending") + if status == "in_progress": + in_progress_count += 1 + validated.append({"id": item["id"], "text": item["text"], + "status": status}) + if in_progress_count > 1: + raise ValueError("Only one task can be in_progress") + self.items = validated + return self.render() +``` + +2. Tool `todo` добавляется в dispatch map как любой другой tool. + +```python +TOOL_HANDLERS = { + # ...базовые tools... + "todo": lambda **kw: TODO.update(kw["items"]), +} +``` + +3. Напоминание встраивает подсказку, если model не вызывала `todo` 3 и более раундов подряд. + +```python +if rounds_since_todo >= 3 and messages: + last = messages[-1] + if last["role"] == "user" and isinstance(last.get("content"), list): + last["content"].insert(0, { + "type": "text", + "text": "Update your todos.", + }) +``` + +Ограничение «только один `in_progress` за раз» принуждает к последовательной концентрации. Напоминание создаёт механизм подотчётности. + +## Что изменилось по сравнению с s02 + +| Компонент | До (s02) | После (s03) | +|----------------|------------------|----------------------------| +| Tools | 4 | 5 (+todo) | +| Планирование | Нет | TodoManager со статусами | +| Напоминание | Нет | `` после 3 раундов| +| Agent loop | Простой dispatch | + счётчик rounds_since_todo| + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s03_todo_write.py +``` + +1. `Refactor the file hello.py: add type hints, docstrings, and a main guard` +2. `Create a Python package with __init__.py, utils.py, and tests/test_utils.py` +3. `Review all Python files and fix any style issues` diff --git a/docs/ru/s04-subagent.md b/docs/ru/s04-subagent.md new file mode 100644 index 000000000..75e2c7e3c --- /dev/null +++ b/docs/ru/s04-subagent.md @@ -0,0 +1,94 @@ +# s04: Subagents + +`s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Раздели большие задачи; каждая подзадача получает чистый context"* -- subagents используют независимые messages[], сохраняя основной диалог чистым. +> +> **Harness layer**: Context isolation -- защищаем ясность мышления model. + +## Проблема + +По мере работы agent его массив messages растёт. Каждое прочитанное содержимое файла, каждый вывод bash остаётся в context навсегда. На вопрос «Какой тестовый фреймворк использует этот проект?» может потребоваться прочитать 5 файлов — но родительскому agent нужен только ответ: «pytest». + +## Решение + +``` +Parent agent Subagent ++------------------+ +------------------+ +| messages=[...] | | messages=[] | <-- fresh +| | dispatch | | +| tool: task | ----------> | while tool_use: | +| prompt="..." | | call tools | +| | summary | append results | +| result = "..." | <---------- | return last text | ++------------------+ +------------------+ + +Context родителя остаётся чистым. Context subagent отбрасывается. +``` + +## Как это работает + +1. Родитель получает tool `task`. Дочерний agent получает все базовые tools, кроме `task` (без рекурсивного порождения). + +```python +PARENT_TOOLS = CHILD_TOOLS + [ + {"name": "task", + "description": "Spawn a subagent with fresh context.", + "input_schema": { + "type": "object", + "properties": {"prompt": {"type": "string"}}, + "required": ["prompt"], + }}, +] +``` + +2. Subagent стартует с `messages=[]` и запускает собственный loop. Родителю возвращается только финальный текст. + +```python +def run_subagent(prompt: str) -> str: + sub_messages = [{"role": "user", "content": prompt}] + for _ in range(30): # safety limit + response = client.messages.create( + model=MODEL, system=SUBAGENT_SYSTEM, + messages=sub_messages, + tools=CHILD_TOOLS, max_tokens=8000, + ) + sub_messages.append({"role": "assistant", + "content": response.content}) + if response.stop_reason != "tool_use": + break + results = [] + for block in response.content: + if block.type == "tool_use": + handler = TOOL_HANDLERS.get(block.name) + output = handler(**block.input) + results.append({"type": "tool_result", + "tool_use_id": block.id, + "content": str(output)[:50000]}) + sub_messages.append({"role": "user", "content": results}) + return "".join( + b.text for b in response.content if hasattr(b, "text") + ) or "(no summary)" +``` + +Вся история сообщений дочернего agent (возможно, 30+ вызовов tools) отбрасывается. Родитель получает краткое резюме в один абзац как обычный `tool_result`. + +## Что изменилось по сравнению с s03 + +| Компонент | До (s03) | После (s04) | +|----------------|-------------------|---------------------------| +| Tools | 5 | 5 (базовые) + task (родитель) | +| Context | Единый общий | Изоляция родителя и дочернего | +| Subagent | Нет | Функция `run_subagent()` | +| Возвращаемое значение | Нет | Только текст резюме | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s04_subagent.py +``` + +1. `Use a subtask to find what testing framework this project uses` +2. `Delegate: read all .py files and summarize what each one does` +3. `Use a task to create a new module, then verify it from here` diff --git a/docs/ru/s05-skill-loading.md b/docs/ru/s05-skill-loading.md new file mode 100644 index 000000000..9caa91c45 --- /dev/null +++ b/docs/ru/s05-skill-loading.md @@ -0,0 +1,108 @@ +# s05: Skills + +`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Загружай знания тогда, когда они нужны, а не заранее"* -- внедряй через tool_result, а не через системный промпт. +> +> **Harness layer**: Знания по запросу -- экспертиза предметной области, загружаемая по мере необходимости model. + +## Проблема + +Вы хотите, чтобы agent следовал рабочим процессам конкретной предметной области: соглашениям git, паттернам тестирования, чек-листам code review. Размещение всего в системном промпте расходует токены на неиспользуемые skill. 10 skill по 2000 токенов каждый = 20 000 токенов, большинство из которых не имеют отношения к конкретной задаче. + +## Решение + +``` +System prompt (Layer 1 -- always present): ++--------------------------------------+ +| You are a coding agent. | +| Skills available: | +| - git: Git workflow helpers | ~100 tokens/skill +| - test: Testing best practices | ++--------------------------------------+ + +When model calls load_skill("git"): ++--------------------------------------+ +| tool_result (Layer 2 -- on demand): | +| | +| Full git workflow instructions... | ~2000 tokens +| Step 1: ... | +| | ++--------------------------------------+ +``` + +Layer 1: *названия* skill в системном промпте (дёшево). Layer 2: полное *содержимое* через tool_result (по запросу). + +## Как это работает + +1. Каждый skill — это директория с файлом `SKILL.md`, содержащим YAML frontmatter. + +``` +skills/ + pdf/ + SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ... + code-review/ + SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ... +``` + +2. SkillLoader сканирует файлы `SKILL.md`, используя имя директории как идентификатор skill. + +```python +class SkillLoader: + def __init__(self, skills_dir: Path): + self.skills = {} + for f in sorted(skills_dir.rglob("SKILL.md")): + text = f.read_text() + meta, body = self._parse_frontmatter(text) + name = meta.get("name", f.parent.name) + self.skills[name] = {"meta": meta, "body": body} + + def get_descriptions(self) -> str: + lines = [] + for name, skill in self.skills.items(): + desc = skill["meta"].get("description", "") + lines.append(f" - {name}: {desc}") + return "\n".join(lines) + + def get_content(self, name: str) -> str: + skill = self.skills.get(name) + if not skill: + return f"Error: Unknown skill '{name}'." + return f"\n{skill['body']}\n" +``` + +3. Layer 1 попадает в системный промпт. Layer 2 — это ещё один tool handler. + +```python +SYSTEM = f"""You are a coding agent at {WORKDIR}. +Skills available: +{SKILL_LOADER.get_descriptions()}""" + +TOOL_HANDLERS = { + # ...base tools... + "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]), +} +``` + +Model узнаёт, какие skill существуют (дёшево), и загружает их при необходимости (дорого). + +## Что изменилось по сравнению с s04 + +| Компонент | До (s04) | После (s05) | +|----------------|------------------|----------------------------| +| Tools | 5 (base + task) | 5 (base + load_skill) | +| System prompt | Статическая строка | + описания skill | +| Знания | Отсутствуют | файлы skills/\*/SKILL.md | +| Внедрение | Отсутствует | Двухуровневое (system + result)| + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s05_skill_loading.py +``` + +1. `What skills are available?` +2. `Load the agent-builder skill and follow its instructions` +3. `I need to do a code review -- load the relevant skill first` +4. `Build an MCP server using the mcp-builder skill` diff --git a/docs/ru/s06-context-compact.md b/docs/ru/s06-context-compact.md new file mode 100644 index 000000000..fb39f60ee --- /dev/null +++ b/docs/ru/s06-context-compact.md @@ -0,0 +1,124 @@ +# s06: Context Compact + +`s01 > s02 > s03 > s04 > s05 > [ s06 ] | s07 > s08 > s09 > s10 > s11 > s12` + +> *"Context заполнится; нужен способ освободить место"* -- трёхуровневая стратегия сжатия для бесконечных сессий. +> +> **Harness layer**: Сжатие -- чистая память для бесконечных сессий. + +## Проблема + +Окно context конечно. Один вызов `read_file` на файл из 1000 строк стоит ~4000 токенов. После чтения 30 файлов и выполнения 20 bash-команд накапливается 100 000+ токенов. Без сжатия agent не может работать с большими кодовыми базами. + +## Решение + +Три уровня, с нарастающей агрессивностью: + +``` +Every turn: ++------------------+ +| Tool call result | ++------------------+ + | + v +[Layer 1: micro_compact] (silent, every turn) + Replace tool_result > 3 turns old + with "[Previous: used {tool_name}]" + | + v +[Check: tokens > 50000?] + | | + no yes + | | + v v +continue [Layer 2: auto_compact] + Save transcript to .transcripts/ + LLM summarizes conversation. + Replace all messages with [summary]. + | + v + [Layer 3: compact tool] + Model calls compact explicitly. + Same summarization as auto_compact. +``` + +## Как это работает + +1. **Layer 1 -- micro_compact**: Перед каждым вызовом LLM заменяет старые результаты tool на заглушки. + +```python +def micro_compact(messages: list) -> list: + tool_results = [] + for i, msg in enumerate(messages): + if msg["role"] == "user" and isinstance(msg.get("content"), list): + for j, part in enumerate(msg["content"]): + if isinstance(part, dict) and part.get("type") == "tool_result": + tool_results.append((i, j, part)) + if len(tool_results) <= KEEP_RECENT: + return messages + for _, _, part in tool_results[:-KEEP_RECENT]: + if len(part.get("content", "")) > 100: + part["content"] = f"[Previous: used {tool_name}]" + return messages +``` + +2. **Layer 2 -- auto_compact**: Когда количество токенов превышает порог, сохраняет полный transcript на диск, затем просит LLM создать резюме. + +```python +def auto_compact(messages: list) -> list: + # Save transcript for recovery + transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl" + with open(transcript_path, "w") as f: + for msg in messages: + f.write(json.dumps(msg, default=str) + "\n") + # LLM summarizes + response = client.messages.create( + model=MODEL, + messages=[{"role": "user", "content": + "Summarize this conversation for continuity..." + + json.dumps(messages, default=str)[:80000]}], + max_tokens=2000, + ) + return [ + {"role": "user", "content": f"[Compressed]\n\n{response.content[0].text}"}, + ] +``` + +3. **Layer 3 -- ручное сжатие**: tool `compact` запускает то же самое суммирование по требованию. + +4. loop объединяет все три уровня: + +```python +def agent_loop(messages: list): + while True: + micro_compact(messages) # Layer 1 + if estimate_tokens(messages) > THRESHOLD: + messages[:] = auto_compact(messages) # Layer 2 + response = client.messages.create(...) + # ... tool execution ... + if manual_compact: + messages[:] = auto_compact(messages) # Layer 3 +``` + +Transcript сохраняют полную историю на диске. Ничто не теряется по-настоящему -- данные просто перемещаются за пределы активного context. + +## Что изменилось по сравнению с s05 + +| Компонент | До (s05) | После (s06) | +|----------------|------------------|----------------------------| +| Tools | 5 | 5 (base + compact) | +| Управление context | Отсутствует | Трёхуровневое сжатие | +| Micro-compact | Отсутствует | Старые результаты -> заглушки| +| Auto-compact | Отсутствует | Срабатывание по порогу токенов| +| Transcript | Отсутствуют | Сохраняются в .transcripts/| + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s06_context_compact.py +``` + +1. `Read every Python file in the agents/ directory one by one` (наблюдай, как micro-compact заменяет старые результаты) +2. `Keep reading files until compression triggers automatically` +3. `Use the compact tool to manually compress the conversation` diff --git a/docs/ru/s07-task-system.md b/docs/ru/s07-task-system.md new file mode 100644 index 000000000..ecdff65ed --- /dev/null +++ b/docs/ru/s07-task-system.md @@ -0,0 +1,131 @@ +# s07: Task System + +`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12` + +> *"Разбивай большие цели на маленькие task, упорядочивай их, сохраняй на диск"* -- файловый граф task с зависимостями, закладывающий основу для многоагентного взаимодействия. +> +> **Harness layer**: Постоянные task -- цели, которые переживают любой отдельный разговор. + +## Проблема + +TodoManager из s03 — это плоский чек-лист в памяти: без упорядочивания, без зависимостей, без статусов помимо «сделано или нет». Реальные цели имеют структуру -- task B зависит от task A, task C и D можно выполнять параллельно, task E ждёт завершения обеих: C и D. + +Без явных связей agent не может определить, что готово к выполнению, что заблокировано и что можно запустить одновременно. А поскольку список живёт только в памяти, сжатие context (s06) полностью его уничтожает. + +## Решение + +Превратить чек-лист в **граф task**, сохраняемый на диск. Каждый task — это JSON-файл со статусом и зависимостями (`blockedBy`). Граф в любой момент отвечает на три вопроса: + +- **Что готово?** -- task со статусом `pending` и пустым `blockedBy`. +- **Что заблокировано?** -- task, ожидающие завершения зависимостей. +- **Что сделано?** -- task со статусом `completed`, завершение которых автоматически разблокирует зависимые task. + +``` +.tasks/ + task_1.json {"id":1, "status":"completed"} + task_2.json {"id":2, "blockedBy":[1], "status":"pending"} + task_3.json {"id":3, "blockedBy":[1], "status":"pending"} + task_4.json {"id":4, "blockedBy":[2,3], "status":"pending"} + +Task graph (DAG): + +----------+ + +--> | task 2 | --+ + | | pending | | ++----------+ +----------+ +--> +----------+ +| task 1 | | task 4 | +| completed| --> +----------+ +--> | blocked | ++----------+ | task 3 | --+ +----------+ + | pending | + +----------+ + +Ordering: task 1 must finish before 2 and 3 +Parallelism: tasks 2 and 3 can run at the same time +Dependencies: task 4 waits for both 2 and 3 +Status: pending -> in_progress -> completed +``` + +Этот граф task становится основой координации для всего, что идёт после s07: фоновое выполнение (s08), многоагентные команды (s09+) и изоляция через worktree (s12) -- всё это читает из этой же структуры и пишет в неё. + +## Как это работает + +1. **TaskManager**: один JSON-файл на task, CRUD с графом зависимостей. + +```python +class TaskManager: + def __init__(self, tasks_dir: Path): + self.dir = tasks_dir + self.dir.mkdir(exist_ok=True) + self._next_id = self._max_id() + 1 + + def create(self, subject, description=""): + task = {"id": self._next_id, "subject": subject, + "status": "pending", "blockedBy": [], + "owner": ""} + self._save(task) + self._next_id += 1 + return json.dumps(task, indent=2) +``` + +2. **Разрешение зависимостей**: завершение task удаляет его ID из списка `blockedBy` всех остальных task, автоматически разблокируя зависимые. + +```python +def _clear_dependency(self, completed_id): + for f in self.dir.glob("task_*.json"): + task = json.loads(f.read_text()) + if completed_id in task.get("blockedBy", []): + task["blockedBy"].remove(completed_id) + self._save(task) +``` + +3. **Статус и управление зависимостями**: `update` обрабатывает переходы состояний и рёбра зависимостей. + +```python +def update(self, task_id, status=None, + add_blocked_by=None, remove_blocked_by=None): + task = self._load(task_id) + if status: + task["status"] = status + if status == "completed": + self._clear_dependency(task_id) + if add_blocked_by: + task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) + if remove_blocked_by: + task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] + self._save(task) +``` + +4. Четыре tool для task добавляются в таблицу обработчиков. + +```python +TOOL_HANDLERS = { + # ...base tools... + "task_create": lambda **kw: TASKS.create(kw["subject"]), + "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status")), + "task_list": lambda **kw: TASKS.list_all(), + "task_get": lambda **kw: TASKS.get(kw["task_id"]), +} +``` + +Начиная с s07, граф task используется по умолчанию для многошаговых работ. Todo из s03 остаётся для быстрых чек-листов в рамках одной сессии. + +## Что изменилось по сравнению с s06 + +| Компонент | До (s06) | После (s07) | +|---|---|---| +| Tools | 5 | 8 (`task_create/update/list/get`) | +| Модель планирования | Плоский чек-лист (в памяти) | Граф task с зависимостями (на диске) | +| Связи | Отсутствуют | Рёбра `blockedBy` | +| Отслеживание статусов | Сделано или нет | `pending` -> `in_progress` -> `completed` | +| Сохранение | Теряется при сжатии | Переживает сжатие и перезапуски | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s07_task_system.py +``` + +1. `Create 3 tasks: "Setup project", "Write code", "Write tests". Make them depend on each other in order.` +2. `List all tasks and show the dependency graph` +3. `Complete task 1 and then list tasks to see task 2 unblocked` +4. `Create a task board for refactoring: parse -> transform -> emit -> test, where transform and emit can run in parallel after parse` diff --git a/docs/ru/s08-background-tasks.md b/docs/ru/s08-background-tasks.md new file mode 100644 index 000000000..dbcf375b6 --- /dev/null +++ b/docs/ru/s08-background-tasks.md @@ -0,0 +1,107 @@ +# s08: Background Tasks + +`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12` + +> *"Запускай медленные операции в background; agent продолжает думать"* -- потоки-демоны выполняют команды и уведомляют о завершении. +> +> **Harness layer**: Фоновое выполнение -- model думает, пока harness ждёт. + +## Проблема + +Некоторые команды выполняются минутами: `npm install`, `pytest`, `docker build`. При блокирующем loop model простаивает в ожидании. Если пользователь просит «установи зависимости и пока они устанавливаются, создай конфигурационный файл», agent выполняет задачи последовательно, а не параллельно. + +## Решение + +``` +Main thread Background thread ++-----------------+ +-----------------+ +| agent loop | | subprocess runs | +| ... | | ... | +| [LLM call] <---+------- | enqueue(result) | +| ^drain queue | +-----------------+ ++-----------------+ + +Timeline: +Agent --[spawn A]--[spawn B]--[other work]---- + | | + v v + [A runs] [B runs] (parallel) + | | + +-- results injected before next LLM call --+ +``` + +## Как это работает + +1. BackgroundManager отслеживает task с потокобезопасной очередью уведомлений. + +```python +class BackgroundManager: + def __init__(self): + self.tasks = {} + self._notification_queue = [] + self._lock = threading.Lock() +``` + +2. `run()` запускает поток-демон и немедленно возвращает управление. + +```python +def run(self, command: str) -> str: + task_id = str(uuid.uuid4())[:8] + self.tasks[task_id] = {"status": "running", "command": command} + thread = threading.Thread( + target=self._execute, args=(task_id, command), daemon=True) + thread.start() + return f"Background task {task_id} started" +``` + +3. Когда подпроцесс завершается, его результат помещается в очередь уведомлений. + +```python +def _execute(self, task_id, command): + try: + r = subprocess.run(command, shell=True, cwd=WORKDIR, + capture_output=True, text=True, timeout=300) + output = (r.stdout + r.stderr).strip()[:50000] + except subprocess.TimeoutExpired: + output = "Error: Timeout (300s)" + with self._lock: + self._notification_queue.append({ + "task_id": task_id, "result": output[:500]}) +``` + +4. agent loop опустошает очередь уведомлений перед каждым вызовом LLM. + +```python +def agent_loop(messages: list): + while True: + notifs = BG.drain_notifications() + if notifs: + notif_text = "\n".join( + f"[bg:{n['task_id']}] {n['result']}" for n in notifs) + messages.append({"role": "user", + "content": f"\n{notif_text}\n" + f""}) + response = client.messages.create(...) +``` + +loop остаётся однопоточным. Параллелизируется только ввод-вывод подпроцессов. + +## Что изменилось по сравнению с s07 + +| Компонент | До (s07) | После (s08) | +|----------------|------------------|----------------------------| +| Tools | 8 | 6 (base + background_run + check)| +| Выполнение | Только блокирующее | Блокирующее + background-потоки| +| Уведомления | Отсутствуют | Очередь, опустошаемая за loop| +| Параллелизм | Отсутствует | Потоки-демоны | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s08_background_tasks.py +``` + +1. `Run "sleep 5 && echo done" in the background, then create a file while it runs` +2. `Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.` +3. `Run pytest in the background and keep working on other things` diff --git a/docs/ru/s09-agent-teams.md b/docs/ru/s09-agent-teams.md new file mode 100644 index 000000000..95590d8d6 --- /dev/null +++ b/docs/ru/s09-agent-teams.md @@ -0,0 +1,125 @@ +# s09: Команды агентов + +`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12` + +> *"Когда задача слишком велика для одного — делегируй коллегам"* -- постоянные коллеги + асинхронные mailbox-ы. +> +> **Harness layer**: Team mailbox-ы -- несколько model-ей, скоординированных через файлы. + +## Проблема + +Subagent-ы (s04) одноразовые: запустить, поработать, вернуть резюме, завершить. Нет идентичности, нет памяти между вызовами. Фоновые задачи (s08) выполняют команды оболочки, но не могут принимать решения под управлением LLM. + +Настоящая командная работа требует: (1) постоянных агентов, которые живут дольше одного промпта, (2) управления идентичностью и жизненным циклом, (3) канала коммуникации между агентами. + +## Решение + +``` +Жизненный цикл коллеги: + spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN + +Коммуникация: + .team/ + config.json <- состав команды + статусы + inbox/ + alice.jsonl <- только дозапись, очищается при чтении + bob.jsonl + lead.jsonl + + +--------+ send("alice","bob","...") +--------+ + | alice | -----------------------------> | bob | + | loop | bob.jsonl << {json_line} | loop | + +--------+ +--------+ + ^ | + | BUS.read_inbox("alice") | + +---- alice.jsonl -> read + drain ---------+ +``` + +## Как это работает + +1. TeammateManager поддерживает config.json со списком команды. + +```python +class TeammateManager: + def __init__(self, team_dir: Path): + self.dir = team_dir + self.dir.mkdir(exist_ok=True) + self.config_path = self.dir / "config.json" + self.config = self._load_config() + self.threads = {} +``` + +2. `spawn()` создаёт коллегу и запускает его agent loop в отдельном потоке. + +```python +def spawn(self, name: str, role: str, prompt: str) -> str: + member = {"name": name, "role": role, "status": "working"} + self.config["members"].append(member) + self._save_config() + thread = threading.Thread( + target=self._teammate_loop, + args=(name, role, prompt), daemon=True) + thread.start() + return f"Spawned teammate '{name}' (role: {role})" +``` + +3. MessageBus: JSONL inbox-ы с дозаписью. `send()` добавляет строку JSON; `read_inbox()` читает всё и очищает. + +```python +class MessageBus: + def send(self, sender, to, content, msg_type="message", extra=None): + msg = {"type": msg_type, "from": sender, + "content": content, "timestamp": time.time()} + if extra: + msg.update(extra) + with open(self.dir / f"{to}.jsonl", "a") as f: + f.write(json.dumps(msg) + "\n") + + def read_inbox(self, name): + path = self.dir / f"{name}.jsonl" + if not path.exists(): return "[]" + msgs = [json.loads(l) for l in path.read_text().strip().splitlines() if l] + path.write_text("") # drain + return json.dumps(msgs, indent=2) +``` + +4. Каждый коллега проверяет свой inbox перед каждым вызовом LLM, добавляя полученные сообщения в context. + +```python +def _teammate_loop(self, name, role, prompt): + messages = [{"role": "user", "content": prompt}] + for _ in range(50): + inbox = BUS.read_inbox(name) + if inbox != "[]": + messages.append({"role": "user", + "content": f"{inbox}"}) + response = client.messages.create(...) + if response.stop_reason != "tool_use": + break + # выполнить tool-ы, добавить результаты... + self._find_member(name)["status"] = "idle" +``` + +## Что изменилось по сравнению с s08 + +| Компонент | До (s08) | После (s09) | +|----------------|------------------|----------------------------| +| Tool-ы | 6 | 9 (+spawn/send/read_inbox) | +| Агенты | Один | Lead + N коллег | +| Персистентность| Нет | config.json + JSONL inbox-ы| +| Потоки | Фоновые команды | Полный agent loop в потоке | +| Жизненный цикл | Запустить-забыть | idle -> working -> idle | +| Коммуникация | Нет | message + broadcast | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s09_agent_teams.py +``` + +1. `Spawn alice (coder) and bob (tester). Have alice send bob a message.` +2. `Broadcast "status update: phase 1 complete" to all teammates` +3. `Check the lead inbox for any messages` +4. Введи `/team`, чтобы увидеть список команды со статусами +5. Введи `/inbox`, чтобы вручную проверить inbox лида diff --git a/docs/ru/s10-team-protocols.md b/docs/ru/s10-team-protocols.md new file mode 100644 index 000000000..99b90500b --- /dev/null +++ b/docs/ru/s10-team-protocols.md @@ -0,0 +1,106 @@ +# s10: Team Protocols + +`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12` + +> *"Коллегам нужны общие правила коммуникации"* -- один паттерн запрос-ответ управляет всеми переговорами. +> +> **Harness layer**: Protocol-ы -- структурированные handshake-и между model-ями. + +## Проблема + +В s09 коллеги работают и общаются, но им не хватает структурированной координации: + +**Завершение работы**: Принудительное завершение потока оставляет файлы недозаписанными, а config.json устаревшим. Нужен handshake: лид запрашивает, коллега соглашается (завершить и выйти) или отказывает (продолжать работу). + +**Согласование плана**: Когда лид говорит «отрефакторь модуль аутентификации», коллега сразу приступает. Для высокорисковых изменений лид должен сначала проверить план. + +Оба случая имеют одинаковую структуру: одна сторона отправляет запрос с уникальным ID, другая отвечает, ссылаясь на этот же ID. + +## Решение + +``` +Shutdown Protocol Plan Approval Protocol +================== ====================== + +Lead Teammate Teammate Lead + | | | | + |--shutdown_req-->| |--plan_req------>| + | {req_id:"abc"} | | {req_id:"xyz"} | + | | | | + |<--shutdown_resp-| |<--plan_resp-----| + | {req_id:"abc", | | {req_id:"xyz", | + | approve:true} | | approve:true} | + +Общий FSM: + [pending] --approve--> [approved] + [pending] --reject---> [rejected] + +Трекеры: + shutdown_requests = {req_id: {target, status}} + plan_requests = {req_id: {from, plan, status}} +``` + +## Как это работает + +1. Лид инициирует завершение работы, генерируя request_id и отправляя его через inbox. + +```python +shutdown_requests = {} + +def handle_shutdown_request(teammate: str) -> str: + req_id = str(uuid.uuid4())[:8] + shutdown_requests[req_id] = {"target": teammate, "status": "pending"} + BUS.send("lead", teammate, "Please shut down gracefully.", + "shutdown_request", {"request_id": req_id}) + return f"Shutdown request {req_id} sent (status: pending)" +``` + +2. Коллега получает запрос и отвечает — approve или reject. + +```python +if tool_name == "shutdown_response": + req_id = args["request_id"] + approve = args["approve"] + shutdown_requests[req_id]["status"] = "approved" if approve else "rejected" + BUS.send(sender, "lead", args.get("reason", ""), + "shutdown_response", + {"request_id": req_id, "approve": approve}) +``` + +3. Согласование плана следует идентичному паттерну. Коллега отправляет план (генерируя request_id), лид проверяет (ссылаясь на тот же request_id). + +```python +plan_requests = {} + +def handle_plan_review(request_id, approve, feedback=""): + req = plan_requests[request_id] + req["status"] = "approved" if approve else "rejected" + BUS.send("lead", req["from"], feedback, + "plan_approval_response", + {"request_id": request_id, "approve": approve}) +``` + +Один FSM, два применения. Одна и та же машина состояний `pending -> approved | rejected` обслуживает любой protocol запрос-ответ. + +## Что изменилось по сравнению с s09 + +| Компонент | До (s09) | После (s10) | +|----------------|------------------|------------------------------| +| Tool-ы | 9 | 12 (+shutdown_req/resp +plan)| +| Завершение | Естественный выход| Handshake запрос-ответ | +| Контроль плана | Нет | Отправка/проверка с согласованием| +| Корреляция | Нет | request_id для каждого запроса| +| FSM | Нет | pending -> approved/rejected | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s10_team_protocols.py +``` + +1. `Spawn alice as a coder. Then request her shutdown.` +2. `List teammates to see alice's status after shutdown approval` +3. `Spawn bob with a risky refactoring task. Review and reject his plan.` +4. `Spawn charlie, have him submit a plan, then approve it.` +5. Введи `/team`, чтобы следить за статусами diff --git a/docs/ru/s11-autonomous-agents.md b/docs/ru/s11-autonomous-agents.md new file mode 100644 index 000000000..3566705b7 --- /dev/null +++ b/docs/ru/s11-autonomous-agents.md @@ -0,0 +1,142 @@ +# s11: Автономные агенты + +`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12` + +> *"Коллеги сами просматривают доску и берут задачи"* -- лиду не нужно назначать каждую вручную. +> +> **Harness layer**: Автономность -- model-и, которые находят работу без указаний. + +## Проблема + +В s09-s10 коллеги работают только тогда, когда им явно говорят. Лид должен запустить каждого с конкретным промптом. Десять невзятых задач на доске? Лид назначает каждую вручную. Не масштабируется. + +Настоящая автономность: коллеги сами просматривают доску задач, берут свободные задачи, работают над ними, затем ищут следующие. + +Один нюанс: после сжатия context-а (s06) агент может забыть, кто он. Повторная инъекция идентичности решает эту проблему. + +## Решение + +``` +Жизненный цикл коллеги с циклом ожидания: + ++-------+ +| spawn | ++---+---+ + | + v ++-------+ tool_use +-------+ +| WORK | <------------- | LLM | ++---+---+ +-------+ + | + | stop_reason != tool_use (or idle tool called) + v ++--------+ +| IDLE | опрос каждые 5с, до 60с ++---+----+ + | + +---> проверить inbox --> сообщение? -------> WORK + | + +---> сканировать .tasks/ --> свободная? ---> взять -> WORK + | + +---> таймаут 60с --------------------------> SHUTDOWN + +Повторная инъекция идентичности после сжатия: + if len(messages) <= 3: + messages.insert(0, identity_block) +``` + +## Как это работает + +1. Loop коллеги имеет две фазы: WORK и IDLE. Когда LLM перестаёт вызывать tool-ы (или вызывает `idle`), коллега переходит в IDLE. + +```python +def _loop(self, name, role, prompt): + while True: + # -- ФАЗА WORK -- + messages = [{"role": "user", "content": prompt}] + for _ in range(50): + response = client.messages.create(...) + if response.stop_reason != "tool_use": + break + # выполнить tool-ы... + if idle_requested: + break + + # -- ФАЗА IDLE -- + self._set_status(name, "idle") + resume = self._idle_poll(name, messages) + if not resume: + self._set_status(name, "shutdown") + return + self._set_status(name, "working") +``` + +2. Фаза IDLE опрашивает inbox и доску задач в цикле. + +```python +def _idle_poll(self, name, messages): + for _ in range(IDLE_TIMEOUT // POLL_INTERVAL): # 60s / 5s = 12 + time.sleep(POLL_INTERVAL) + inbox = BUS.read_inbox(name) + if inbox: + messages.append({"role": "user", + "content": f"{inbox}"}) + return True + unclaimed = scan_unclaimed_tasks() + if unclaimed: + claim_task(unclaimed[0]["id"], name) + messages.append({"role": "user", + "content": f"Task #{unclaimed[0]['id']}: " + f"{unclaimed[0]['subject']}"}) + return True + return False # таймаут -> завершение +``` + +3. Сканирование доски задач: найти задачи в статусе pending, без владельца, без блокировок. + +```python +def scan_unclaimed_tasks() -> list: + unclaimed = [] + for f in sorted(TASKS_DIR.glob("task_*.json")): + task = json.loads(f.read_text()) + if (task.get("status") == "pending" + and not task.get("owner") + and not task.get("blockedBy")): + unclaimed.append(task) + return unclaimed +``` + +4. Повторная инъекция идентичности: когда context слишком короткий (произошло сжатие), вставить блок идентичности. + +```python +if len(messages) <= 3: + messages.insert(0, {"role": "user", + "content": f"You are '{name}', role: {role}, " + f"team: {team_name}. Continue your work."}) + messages.insert(1, {"role": "assistant", + "content": f"I am {name}. Continuing."}) +``` + +## Что изменилось по сравнению с s10 + +| Компонент | До (s10) | После (s11) | +|----------------|------------------|----------------------------| +| Tool-ы | 12 | 14 (+idle, +claim_task) | +| Автономность | Управляется лидом| Самоорганизующаяся | +| Фаза IDLE | Нет | Опрос inbox + доски задач | +| Взятие задач | Только вручную | Авто-взятие свободных задач| +| Идентичность | System prompt | + повторная инъекция после сжатия| +| Таймаут | Нет | 60с IDLE -> авто-завершение| + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s11_autonomous_agents.py +``` + +1. `Create 3 tasks on the board, then spawn alice and bob. Watch them auto-claim.` +2. `Spawn a coder teammate and let it find work from the task board itself` +3. `Create tasks with dependencies. Watch teammates respect the blocked order.` +4. Введи `/tasks`, чтобы увидеть доску задач с владельцами +5. Введи `/team`, чтобы следить за тем, кто работает, а кто в IDLE diff --git a/docs/ru/s12-worktree-task-isolation.md b/docs/ru/s12-worktree-task-isolation.md new file mode 100644 index 000000000..537e86f03 --- /dev/null +++ b/docs/ru/s12-worktree-task-isolation.md @@ -0,0 +1,121 @@ +# s12: Worktree + Изоляция задач + +`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s12 ]` + +> *"Каждый работает в своей директории — никаких конфликтов"* -- task-и управляют целями, worktree-ы управляют директориями, связанными по ID. +> +> **Harness layer**: Изоляция директорий -- параллельные полосы выполнения, которые никогда не пересекаются. + +## Проблема + +К s11 агенты могут самостоятельно брать и выполнять task-и. Но каждая task выполняется в одной общей директории. Два агента, рефакторящих разные модули одновременно, столкнутся: агент A редактирует `config.py`, агент B редактирует `config.py`, нестейджированные изменения смешиваются, и ни один не может откатиться чисто. + +Доска задач отслеживает *что делать*, но не имеет мнения о *где делать*. Решение: дать каждой task свой git worktree. Task-и управляют целями, worktree-ы управляют контекстом выполнения. Связать их по task ID. + +## Решение + +``` +Плоскость управления (.tasks/) Плоскость выполнения (.worktrees/) ++------------------+ +------------------------+ +| task_1.json | | auth-refactor/ | +| status: in_progress <------> branch: wt/auth-refactor +| worktree: "auth-refactor" | task_id: 1 | ++------------------+ +------------------------+ +| task_2.json | | ui-login/ | +| status: pending <------> branch: wt/ui-login +| worktree: "ui-login" | task_id: 2 | ++------------------+ +------------------------+ + | + index.json (реестр worktree-ов) + events.jsonl (лог жизненного цикла) + +Машины состояний: + Task: pending -> in_progress -> completed + Worktree: absent -> active -> removed | kept +``` + +## Как это работает + +1. **Создать task.** Сначала сохранить цель. + +```python +TASKS.create("Implement auth refactor") +# -> .tasks/task_1.json status=pending worktree="" +``` + +2. **Создать worktree и привязать к task.** Передача `task_id` автоматически переводит task в `in_progress`. + +```python +WORKTREES.create("auth-refactor", task_id=1) +# -> git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD +# -> index.json получает новую запись, task_1.json получает worktree="auth-refactor" +``` + +Привязка записывает состояние на обе стороны: + +```python +def bind_worktree(self, task_id, worktree): + task = self._load(task_id) + task["worktree"] = worktree + if task["status"] == "pending": + task["status"] = "in_progress" + self._save(task) +``` + +3. **Выполнять команды в worktree.** `cwd` указывает на изолированную директорию. + +```python +subprocess.run(command, shell=True, cwd=worktree_path, + capture_output=True, text=True, timeout=300) +``` + +4. **Завершить работу.** Два варианта: + - `worktree_keep(name)` -- сохранить директорию для дальнейшего использования. + - `worktree_remove(name, complete_task=True)` -- удалить директорию, завершить привязанную task, отправить event. Один вызов обрабатывает и teardown, и завершение. + +```python +def remove(self, name, force=False, complete_task=False): + self._run_git(["worktree", "remove", wt["path"]]) + if complete_task and wt.get("task_id") is not None: + self.tasks.update(wt["task_id"], status="completed") + self.tasks.unbind_worktree(wt["task_id"]) + self.events.emit("task.completed", ...) +``` + +5. **Поток event-ов.** Каждый шаг жизненного цикла отправляет запись в `.worktrees/events.jsonl`: + +```json +{ + "event": "worktree.remove.after", + "task": {"id": 1, "status": "completed"}, + "worktree": {"name": "auth-refactor", "status": "removed"}, + "ts": 1730000000 +} +``` + +Отправляемые event-ы: `worktree.create.before/after/failed`, `worktree.remove.before/after/failed`, `worktree.keep`, `task.completed`. + +После сбоя состояние восстанавливается из `.tasks/` + `.worktrees/index.json` на диске. Память в разговоре энергозависима; состояние в файлах — нет. + +## Что изменилось по сравнению с s11 + +| Компонент | До (s11) | После (s12) | +|-----------------------|----------------------------|----------------------------------------------| +| Координация | Доска задач (owner/status) | Доска задач + явная привязка worktree | +| Область выполнения | Общая директория | Изолированная директория на task | +| Восстанавливаемость | Только статус task | Статус task + индекс worktree | +| Завершение работы | Завершение task | Завершение task + явный keep/remove | +| Видимость жизн. цикла | Неявно в логах | Явные event-ы в `.worktrees/events.jsonl` | + +## Попробуй сам + +```sh +cd learn-claude-code +python agents/s12_worktree_task_isolation.py +``` + +1. `Create tasks for backend auth and frontend login page, then list tasks.` +2. `Create worktree "auth-refactor" for task 1, then bind task 2 to a new worktree "ui-login".` +3. `Run "git status --short" in worktree "auth-refactor".` +4. `Keep worktree "ui-login", then list worktrees and inspect events.` +5. `Remove worktree "auth-refactor" with complete_task=true, then list tasks/worktrees/events.`