Команда AI for Devs подготовила перевод статьи о том, как на самом деле устроены AI-агенты для программирования. Автор шаг за шагом показывает, что за Claude Code не стоит магия: это последовательный агентный цикл, инструменты, контроль разрешений и работа с контекстом.
Что делает Claude Code мощным, на удивление просто: это цикл, который позволяет ИИ читать файлы, запускать команды и итеративно работать, пока задача не будет выполнена.
Сложность начинается там, где нужно разрулить пограничные случаи, сделать хороший UX и встроиться в реальные процессы разработки.
В этом посте я начну с нуля и шаг за шагом подведу вас к архитектуре Claude Code, показывая, как вы могли бы «изобрести» её сами — от первых принципов, имея лишь терминал, API LLM и желание сделать ИИ действительно полезным.
Для начала зафиксируем проблему, которую мы пытаемся решить.
Когда вы пользуетесь ChatGPT или Claude в браузере, вы делаете массу ручной работы:
Копируете и вставляете код из чата в файлы
Запускаете команды сами, затем копируете ошибки обратно
Даете контекст, загружая файлы или вставляя содержимое
Вручную проходите цикл «исправил — проверил — отладил» снова и снова
По сути, вы выступаете руками ИИ. ИИ думает — вы исполняете.
Представьте, что вы говорите ИИ: «Исправь баг в auth.py» — и уходите. Возвращаетесь, а баг уже исправлен. ИИ прочитал файл, понял, что происходит, попробовал исправление, запустил тесты, увидел падение, попробовал другой подход — и в итоге добился успеха.
Вот что делает агент. Это ИИ, который умеет:
Совершать действия в реальном мире (читать файлы, запускать команды)
Наблюдать результаты
Решать, что делать дальше
Повторять, пока задача не будет завершена
Давайте соберем такого с нуля.
Начнём с абсолютного минимума: ИИ, который умеет выполнить одну bash-команду.
#!/bin/bash # agent-v0.sh - The simplest possible agent PROMPT="$1" # Ask Claude what command to run RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "content-type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-opus-4-5-20251101", "max_tokens": 1024, "messages": [{"role": "user", "content": "'"$PROMPT"'\n\nRespond with ONLY a bash command. No markdown, no explanation, no code blocks."}] }') # Extract the command from response COMMAND=$(echo "$RESPONSE" | jq -r '.content[0].text') echo "AI suggests: $COMMAND" read -r -p "Run this command? (y/n) " CONFIRM if [ "$CONFIRM" = "y" ]; then eval "$COMMAND" fi
Использование
bash agent-v0.sh "list all Python files in this directory" # AI suggests: ls *.py # Run this command? (y/n)
Это… не слишком полезно. ИИ может предложить одну команду, а дальше вы снова делаете всё вручную.
Но вот ключевая мысль: а что если завернуть это в цикл?
Ключевая идея, лежащая в основе всех ИИ-агентов, — это цикл агента:
while (task not complete): 1. AI decides what to do next 2. Execute that action 3. Show AI the result 4. Go back to step 1
Давайте реализуем ровно это. ИИ должен сообщать нам:
Какое действие выполнить
Завершена ли задача
Используем простой JSON-формат:
#!/bin/bash # agent-v1.sh - Agent with a loop SYSTEM_PROMPT='You are a helpful assistant that can run bash commands. When the user gives you a task, respond with JSON in this exact format: {"action": "bash", "command": "your command here"} When the task is complete, respond with: {"action": "done", "message": "explanation of what was accomplished"} Only respond with JSON. No other text.' # We'll build messages as a JSON array (using jq for proper escaping) MESSAGES="[]" run_agent() { local USER_MSG="$1" # Add initial user message using jq to handle escaping MESSAGES=$(echo "$MESSAGES" | jq --arg msg "$USER_MSG" '. + [{"role": "user", "content": $msg}]') while true; do # Build the request body properly with jq REQUEST_BODY=$(jq -n \ --arg model "claude-opus-4-5-20251101" \ --arg system "$SYSTEM_PROMPT" \ --argjson messages "$MESSAGES" \ '{model: $model, max_tokens: 1024, system: $system, messages: $messages}') # Call the API RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "content-type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d "$REQUEST_BODY") # Echo the response for debugging AI_TEXT=$(echo "$RESPONSE" | jq -r '.content[0].text') # Add assistant message to history MESSAGES=$(echo "$MESSAGES" | jq --arg msg "$AI_TEXT" '. + [{"role": "assistant", "content": $msg}]') # Parse the action from the JSON response ACTION=$(echo "$AI_TEXT" | jq -r '.action // empty') if [ -z "$ACTION" ]; then echo "❌ Could not parse response: $AI_TEXT" break elif [ "$ACTION" = "done" ]; then echo "✅ $(echo "$AI_TEXT" | jq -r '.message')" break elif [ "$ACTION" = "bash" ]; then COMMAND=$(echo "$AI_TEXT" | jq -r '.command') echo "🔧 Running: $COMMAND" # Execute and capture output OUTPUT=$(eval "$COMMAND" 2>&1) echo "$OUTPUT" # Feed result back to AI MESSAGES=$(echo "$MESSAGES" | jq --arg msg "Command output: $OUTPUT" '. + [{"role": "user", "content": $msg}]') else echo "❌ Unknown action: $ACTION" break fi done } run_agent "$1"
Теперь у нас есть штука, которая действительно умеет итеративно работать:
bash agent-v1.sh "Create a file called hello.py that prints hello world, then run it" # 🔧 Running: echo 'print("hello world")' > hello.py # 🔧 Running: python hello.py # hello world # ✅ Created hello.py and executed it successfully. It prints "hello world".
ИИ выполнил две команды и затем сообщил, что задача завершена. Мы собрали цикл агента!
Но погодите. Мы выполняем произвольные команды без каких-либо проверок безопасности. ИИ может предложить rm -rf /, и мы послушно это исполним.
Давайте добавим человека в контур для опасных операций. Сначала определим функцию, которая оборачивает выполнение команды проверкой безопасности:
# Add this function BEFORE run_agent() in your script execute_with_permission() { local COMMAND="$1" # Check if command seems dangerous if echo "$COMMAND" | grep -qE 'rm |sudo |chmod |curl.*\|.*sh'; then # Use >&2 to print to stderr, so prompts display immediately # (stdout gets captured by the $(...) in the agent loop) echo "⚠️ Potentially dangerous command: $COMMAND" >&2 echo "Allow? (y/n)" >&2 read CONFIRM if [ "$CONFIRM" != "y" ]; then echo "DENIED BY USER" return 1 fi fi eval "$COMMAND" 2>&1 }
Затем внутри цикла агента заменяем прямой вызов eval на нашу новую функцию:
# BEFORE: OUTPUT=$(eval "$COMMAND" 2>&1) # AFTER (with permission check): OUTPUT=$(execute_with_permission "$COMMAND")
Вот и всё. Функция вклинивается между запросом ИИ и реальным выполнением, давая вам возможность заблокировать опасные команды. Если вы запретили выполнение, можно отдать это обратно ИИ, чтобы он попробовал другой подход.
Попробуйте:
# Create a test file echo 'print("hello world")' > hello.py # Ask the agent to delete it bash agent-v1.sh "delete the file hello.py" # 🔧 Running: rm hello.py # ⚠️ Potentially dangerous command: rm hello.py # Allow? (y/n)
Нажмите y, чтобы разрешить удаление, или n, чтобы заблокировать.
Это начало системы разрешений. Claude Code развивает эту идею гораздо дальше:
Разрешения по типам инструментов (правки файлов vs. bash-команды)
Аллоулисты по шаблонам (Bash(npm test:*) разрешает любую команду npm test)
Режимы «принять всё» на уровне сессии, когда вы доверяете ИИ
Ключевая мысль: человек должен контролировать, что именно ИИ может делать, но с такой детализацией, чтобы это не бесило.
Запуск bash-команд — это мощно, но это также:
Опасно: неограниченный доступ к системе
Неэффективно: чтобы прочитать файл, не стоит поднимать отдельный процесс
Неточно: парсинг вывода хрупкий
Что если вместо этого дать ИИ структурированные инструменты?
Дальше перейдём на Python, потому что там проще и чище работать с JSON и API-вызовами:
# agent-v2.py - Agent with structured tools import anthropic import json import os client = anthropic.Anthropic() TOOLS = [ { "name": "read_file", "description": "Read the contents of a file", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Path to the file"} }, "required": ["path"] } }, { "name": "write_file", "description": "Write content to a file", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Path to the file"}, "content": {"type": "string", "description": "Content to write"} }, "required": ["path", "content"] } }, { "name": "run_bash", "description": "Run a bash command", "input_schema": { "type": "object", "properties": { "command": {"type": "string", "description": "The command to run"} }, "required": ["command"] } } ] def execute_tool(name, input): """Execute a tool and return the result.""" if name == "read_file": try: with open(input["path"], "r") as f: return f.read() except Exception as e: return f"Error: {e}" elif name == "write_file": try: with open(input["path"], "w") as f: f.write(input["content"]) return f"Successfully wrote to {input['path']}" except Exception as e: return f"Error: {e}" elif name == "run_bash": import subprocess result = subprocess.run( input["command"], shell=True, capture_output=True, text=True ) return result.stdout + result.stderr def run_agent(task): """Main agent loop.""" messages = [{"role": "user", "content": task}] while True: response = client.messages.create( model="claude-opus-4-5-20251101", max_tokens=4096, tools=TOOLS, messages=messages ) # Check if we're done if response.stop_reason == "end_turn": # Extract final text response for block in response.content: if hasattr(block, "text"): print(f"✅ {block.text}") break # Process tool uses if response.stop_reason == "tool_use": # Add assistant's response to history messages.append({"role": "assistant", "content": response.content}) tool_results = [] for block in response.content: if block.type == "tool_use": print(f"🔧 {block.name}: {json.dumps(block.input)}") result = execute_tool(block.name, block.input) print(f" → {result[:200]}...") # Truncate for display tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # Add results to conversation messages.append({"role": "user", "content": tool_results}) if __name__ == "__main__": import sys run_agent(sys.argv[1])
Теперь мы используем нативный API tool use от Anthropic. Это гораздо лучше, потому что:
Типобезопасность: ИИ точно знает, какие параметры принимает каждый инструмент
Явные действия: чтение файла — это вызов read_file, а не cat
Контролируемая поверхность: мы сами решаем, какие инструменты вообще существуют
Попробуйте:
# Create a test file for the agent to work with cat > main.py << 'EOF' def calculate(x, y): return x + y def greet(name): print(f"Hello, {name}!") EOF # Run the agent uv run --with anthropic python agent-v2.py "Read main.py and add a docstring to the first function" # 🔧 read_file: {"path": "main.py"} # → def calculate(x, y):... # 🔧 write_file: {"path": "main.py", "content": "def calculate(x, y):\n \"\"\"Calculate..."} # → Successfully wrote to main.py # ✅ I've added a docstring to the calculate function explaining its purpose.
У нашего инструмента write_file есть проблема: он перезаписывает файл целиком. Если ИИ делает небольшую правку в файле на 1000 строк, ему приходится выводить все 1000 строк. Это:
Дорого: больше токенов на вывод — выше стоимость
Рискованно: ИИ может случайно «уронить» строки
Медленно: генерация такого объёма текста занимает время
А что если сделать инструмент для хирургически точных правок?
{ "name": "edit_file", "description": "Make a precise edit to a file by replacing a unique string", "input_schema": { "type": "object", "properties": { "path": {"type": "string"}, "old_str": {"type": "string", "description": "Exact string to find (must be unique in file)"}, "new_str": {"type": "string", "description": "String to replace it with"} }, "required": ["path", "old_str", "new_str"] } }
Реализация:
def edit_file(path, old_str, new_str): with open(path, "r") as f: content = f.read() # Ensure the string is unique count = content.count(old_str) if count == 0: return f"Error: '{old_str}' not found in file" if count > 1: return f"Error: '{old_str}' found {count} times. Must be unique." new_content = content.replace(old_str, new_str) with open(path, "w") as f: f.write(new_content) return f"Successfully replaced text in {path}"
Это ровно то, как работает инструмент str_replace в Claude Code. Требование уникальности может показаться раздражающим, но на деле это фича:
Заставляет ИИ добавлять достаточно контекста, чтобы правка была однозначной
Создаёт естественный «diff», который человеку легко проверить
Предотвращает случайные массовые замены
Пока что наш агент умеет читать только те файлы, о которых он уже знает. Но что делать с задачей вроде «найди, где баг в аутентификации»?
ИИ нужно уметь искать по кодовой базе. Давайте добавим для этого инструменты.
SEARCH_TOOLS = [ { "name": "glob", "description": "Find files matching a pattern", "input_schema": { "type": "object", "properties": { "pattern": {"type": "string", "description": "Glob pattern (e.g., '**/*.py')"} }, "required": ["pattern"] } }, { "name": "grep", "description": "Search for a pattern in files", "input_schema": { "type": "object", "properties": { "pattern": {"type": "string", "description": "Regex pattern to search for"}, "path": {"type": "string", "description": "Directory or file to search in"} }, "required": ["pattern"] } } ]
Теперь ИИ может:
glob("**/*.py") → найти все Python-файлы
grep("def authenticate", "src/") → найти код, связанный с аутентификацией
read_file("src/auth.py") → прочитать нужный файл
edit_file(...) → исправить баг
Вот и весь паттерн: дайте ИИ инструменты для разведки, и он сможет ориентироваться в кодовой базе, которую видит впервые.
Вот с какой проблемой вы быстро столкнётесь: окна контекста конечны.
Если вы работаете с большой кодовой базой, диалог может выглядеть так:
Пользователь: «Исправь баг в аутентификации»
ИИ: читает 10 файлов, запускает 20 команд, пробует 3 подхода
...диалог разрастается до 100 000 токенов
ИИ: упирается в лимит контекста и начинает забывать ранние детали
Как с этим справляться?
Когда контекст становится слишком длинным, можно сжать историю, суммировав произошедшее:
def compact_conversation(messages): """Summarize the conversation to free up context.""" summary_prompt = """Summarize this conversation concisely, preserving: - The original task - Key findings and decisions - Current state of the work - What still needs to be done""" summary = client.messages.create( model="claude-opus-4-5-20251101", max_tokens=2000, messages=[ {"role": "user", "content": f"{messages}\n\n{summary_prompt}"} ] ) return [{"role": "user", "content": f"Previous work summary:\n{summary}"}]
Для сложных задач можно запускать подагента со своим отдельным контекстом:
def delegate_to_subagent(task, tools_allowed): """Spawn a sub-agent for a focused task.""" result = run_agent( task=task, tools=tools_allowed, max_turns=10 # Prevent infinite loops ) # Only return the result, not the full conversation return result.final_answer
Поэтому в Claude Code есть концепция подагентов: специализированные агенты решают узкие подзадачи в собственном контексте и возвращают только итог.
Мы до этого замалчивали одну важную вещь: откуда ИИ вообще знает, как себя вести?
System prompt — это место, где вы кодируете:
Идентичность ИИ и его возможности
Правила использования инструментов
Проектный контекст
Поведенческие ограничения
Вот упрощённая версия того, что делает Claude Code эффективным:
SYSTEM_PROMPT = """You are an AI assistant that helps with software development tasks. You have access to the following tools: - read_file: Read file contents - write_file: Create or overwrite files - edit_file: Make precise edits to existing files - glob: Find files by pattern - grep: Search for patterns in files - bash: Run shell commands ## Guidelines ### Before making changes: 1. Understand the task fully before acting 2. Read relevant files to understand context 3. Plan your approach ### When editing code: 1. Use edit_file for small changes (preferred) 2. Use write_file only for new files or complete rewrites 3. Run tests after changes when possible 4. If tests fail, analyze the error and iterate ### General principles: - Be concise but thorough - Explain your reasoning briefly - Ask for clarification if the task is ambiguous - If you're stuck, say so instead of guessing ## Current Directory You are working in: {current_directory} """
Но тут возникает проблема: а если у проекта есть специфические соглашения? Что если команда использует конкретный тестовый фреймворк или у репозитория нестандартная структура директорий?
laude Code решает это через CLAUDE.md — файл в корне проекта, который автоматически добавляется в контекст:
# CLAUDE.md ## Project Overview This is a FastAPI application for user authentication. ## Key Commands - `make test`: Run all tests - `make lint`: Run linting - `make dev`: Start development server ## Architecture - `src/api/`: API routes - `src/models/`: Database models - `src/services/`: Business logic - `tests/`: Test files (mirror src/ structure) ## Conventions - All functions must have type hints - Use pydantic for request/response models - Write tests before implementing features (TDD) ## Known Issues - The /auth/refresh endpoint has a race condition (see issue #142)
Теперь ИИ знает:
Как запускать тесты в этом проекте
Где что лежит
Какие соглашения нужно соблюдать
Какие подводные камни уже известны
Это одна из самых сильных возможностей Claude Code: проектные знания, которые путешествуют вместе с кодом.
Посмотрим, что у нас получилось. Ядро любого агентского инструмента для программирования — это цикл:
1. Подготовка (выполняется один раз)
Загрузить system prompt с описаниями инструментов, поведенческими правилами и проектным контекстом (CLAUDE.md)
Инициализировать пустую историю диалога
2. Цикл агента (повторяется до завершения)
Отправить историю диалога в LLM
LLM решает: вызвать инструмент или ответить пользователю
Если нужен вызов инструмента:
1. Check permissions (prompt user if dangerous) 2.Execute the tool (read_file, edit_file, bash, glob, grep, etc.) 3. Add the result to conversation history 4. Loop back to step 2
Если это финальный ответ:
1. Display response to user 2. Done
Вот и всё. Любой ИИ-агент для разработки — от нашего bash-скрипта на 50 строк до Claude Code — следует этому паттерну.
А теперь давайте соберём полноценный, рабочий mini-Claude Code, которым действительно можно пользоваться. Он объединяет всё, что мы изучили: цикл агента, структурированные инструменты, проверки разрешений и интерактивный REPL:
#!/usr/bin/env python3 # mini-claude-code.py - A minimal Claude Code clone import anthropic import subprocess import os import json client = anthropic.Anthropic() TOOLS = [ { "name": "read_file", "description": "Read the contents of a file", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Path to the file"} }, "required": ["path"] } }, { "name": "write_file", "description": "Write content to a file (creates or overwrites)", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Path to the file"}, "content": {"type": "string", "description": "Content to write"} }, "required": ["path", "content"] } }, { "name": "list_files", "description": "List files in a directory", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Directory path (default: current directory)"} } } }, { "name": "run_command", "description": "Run a shell command", "input_schema": { "type": "object", "properties": { "command": {"type": "string", "description": "The command to run"} }, "required": ["command"] } } ] DANGEROUS_PATTERNS = ["rm ", "sudo ", "chmod ", "mv ", "cp ", "> ", ">>"] def check_permission(tool_name, tool_input): """Check if an action requires user permission.""" if tool_name == "run_command": cmd = tool_input.get("command", "") if any(p in cmd for p in DANGEROUS_PATTERNS): print(f"\n⚠️ Potentially dangerous command: {cmd}") response = input("Allow? (y/n): ").strip().lower() return response == "y" elif tool_name == "write_file": path = tool_input.get("path", "") print(f"\n📝 Will write to: {path}") response = input("Allow? (y/n): ").strip().lower() return response == "y" return True def execute_tool(name, tool_input): """Execute a tool and return the result.""" if name == "read_file": path = tool_input["path"] try: with open(path, "r") as f: content = f.read() return f"Contents of {path}:\n{content}" except Exception as e: return f"Error reading file: {e}" elif name == "write_file": path = tool_input["path"] content = tool_input["content"] try: with open(path, "w") as f: f.write(content) return f"✅ Successfully wrote to {path}" except Exception as e: return f"Error writing file: {e}" elif name == "list_files": path = tool_input.get("path", ".") try: files = os.listdir(path) return f"Files in {path}:\n" + "\n".join(f" {f}" for f in sorted(files)) except Exception as e: return f"Error listing files: {e}" elif name == "run_command": cmd = tool_input["command"] try: result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) output = result.stdout + result.stderr return f"$ {cmd}\n{output}" if output else f"$ {cmd}\n(no output)" except subprocess.TimeoutExpired: return f"Command timed out after 30 seconds" except Exception as e: return f"Error running command: {e}" return f"Unknown tool: {name}" def agent_loop(user_message, conversation_history): """Run the agent loop until the task is complete.""" conversation_history.append({"role": "user", "content": user_message}) while True: # Call Claude response = client.messages.create( model="claude-opus-4-5-20251101", max_tokens=4096, system=f"You are a helpful coding assistant. Working directory: {os.getcwd()}", tools=TOOLS, messages=conversation_history ) # Add assistant response to history conversation_history.append({"role": "assistant", "content": response.content}) # Check if we're done (no tool use) if response.stop_reason == "end_turn": # Print the final text response for block in response.content: if hasattr(block, "text"): print(f"\n🤖 {block.text}") break # Process tool calls tool_results = [] for block in response.content: if block.type == "tool_use": tool_name = block.name tool_input = block.input print(f"\n🔧 {tool_name}: {json.dumps(tool_input)}") # Check permissions if not check_permission(tool_name, tool_input): result = "Permission denied by user" print(f" 🚫 {result}") else: result = execute_tool(tool_name, tool_input) # Truncate long output for display display = result[:200] + "..." if len(result) > 200 else result print(f" → {display}") tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # Add tool results to conversation conversation_history.append({"role": "user", "content": tool_results}) return conversation_history def main(): print("Mini Claude Code") print(" Type your requests, or 'quit' to exit.\n") conversation_history = [] while True: try: user_input = input("You: ").strip() except (EOFError, KeyboardInterrupt): print("\nGoodbye!") break if not user_input: continue if user_input.lower() in ["quit", "exit", "q"]: print("Goodbye!") break conversation_history = agent_loop(user_input, conversation_history) if __name__ == "__main__": main()
Сохраните это как mini-claude-code.py и запустите:
uv run --with anthropic python mini-claude-code.py
Вот как выглядит сессия:
Mini Claude Code Type your requests, or 'quit' to exit. You: create a python file that prints the fibonacci sequence up to n 🔧 write_file: {"path": "fibonacci.py", "content": "def fibonacci(n):\n ..."} 📝 Will write to: fibonacci.py Allow? (y/n): y → ✅ Successfully wrote to fibonacci.py 🤖 I've created fibonacci.py with a function that prints the Fibonacci sequence. Would you like me to run it to test it? You: yes, run it with n=10 🔧 run_command: {"command": "python fibonacci.py 10"} → $ python fibonacci.py 10 0 1 1 2 3 5 8 13 21 34 🤖 The script works correctly! It printed the first 10 Fibonacci numbers. You: quit Goodbye!
Это рабочий мини-клон Claude Code примерно на 150 строк. В нём есть:
Интерактивный REPL: сохраняет контекст диалога между запросами
Несколько инструментов: чтение, запись, листинг файлов, запуск команд
Проверки разрешений: спрашивает перед записью файлов или выполнением опасных команд
Память диалога: каждый следующий запрос опирается на предыдущий контекст
По сути, это и есть то, что делает Claude Code, плюс:
Отполированный терминальный UI
Продвинутая система разрешений
Уплотнение контекста, когда диалоги становятся длинными
Делегирование подагентам для сложных задач
Хуки для кастомной автоматизации
Интеграция с git и другими инструментами разработки
Если вы хотите развивать эту основу, не изобретая всё заново, Anthropic предлагает Claude Agent SDK. Это тот же движок, на котором работает Claude Code, но в виде библиотеки.
Вот как выглядит наш простой агент с использованием SDK:
import { query } from "@anthropic-ai/claude-agent-sdk"; for await (const message of query({ prompt: "Fix the bug in auth.py", options: { model: "claude-opus-4-5-20251101", allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], maxTurns: 50 } })) { if (message.type === "assistant") { for (const block of message.message.content) { if ("text" in block) { console.log(block.text); } else if ("name" in block) { console.log(`Using tool: ${block.name}`); } } } }
SDK берёт на себя:
Цикл агента (вам не нужно реализовывать его вручную)
Все встроенные инструменты (Read, Write, Edit, Bash, Glob, Grep и т. д.)
Управление разрешениями
Отслеживание контекста
Координацию подагентов
Начиная с простого bash-скрипта, мы пришли к следующим выводам:
Цикл агента: ИИ решает → выполняет → наблюдает → повторяет
Структурированные инструменты: лучше, чем чистый bash, с точки зрения безопасности и точности
Точечные правки: str_replace лучше, чем полная перезапись файлов
Инструменты поиска: позволяют ИИ исследовать кодовые базы
Управление контекстом: уплотнение и делегирование решают проблему длинных задач
Проектные знания: CLAUDE.md даёт проектно-специфичный контекст
Каждый из этих шагов появился из практической боли:
«Как заставить ИИ делать больше одной вещи?» → цикл агента
«Как не дать ему угробить систему?» → система разрешений
«Как делать правки эффективно?» → инструмент str_replace
«Как ему находить код, о котором он ничего не знает?» → инструменты поиска
«Что делать, когда заканчивается контекст?» → уплотнение
«Откуда ему знать соглашения моего проекта?» → CLAUDE.md
Вот так вы и могли бы изобрести Claude Code. Базовые идеи на удивление просты.
Как и прежде, сложность появляется при работе с пограничными случаями, создании хорошего UX и интеграции с реальными процессами разработки.
Если вы хотите писать собственных агентов:
Начинайте с простого: базовый цикл агента и 2–3 инструмента
Добавляйте инструменты постепенно: каждая новая возможность должна решать реальную проблему
Аккуратно обрабатывайте ошибки: инструменты ломаются, агент должен уметь восстанавливаться
Тестируйте на реальных задачах: именно пограничные случаи покажут, чего не хватает
Рассмотрите Claude Agent SDK: зачем изобретать велосипед?
Будущее разработки — за агентами, которые действительно умеют что-то делать. Теперь мы понимаем, как они устроены.
Ресурсы
Claude Agent SDK Documentation
Claude Code Documentation
Anthropic API Reference
Друзья! Эту статью подготовила команда ТГК «AI for Devs» — канала, где мы рассказываем про AI-ассистентов, плагины для IDE, делимся практическими кейсами и свежими новостями из мира ИИ. Подписывайтесь, чтобы быть в курсе и ничего не упустить!
Источник


