- PVSM.RU - https://www.pvsm.ru -
Команда AI for Devs [1] подготовила перевод статьи о том, как на самом деле устроены 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"'nnRespond 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 [2] в 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 [3]") → прочитать нужный файл
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}nn{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 [4] — файл в корне проекта, который автоматически добавляется в контекст:
# 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) [4]
Инициализировать пустую историю диалога
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 [5] и запустите:
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. [6]Это тот же движок, на котором работает 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 [7] даёт проектно-специфичный контекст
Каждый из этих шагов появился из практической боли:
«Как заставить ИИ делать больше одной вещи?» → цикл агента
«Как не дать ему угробить систему?» → система разрешений
«Как делать правки эффективно?» → инструмент str_replace
«Как ему находить код, о котором он ничего не знает?» → инструменты поиска
«Что делать, когда заканчивается контекст?» → уплотнение
«Откуда ему знать соглашения моего проекта?» → CLAUDE.md [7]
Вот так вы и могли бы изобрести Claude Code. Базовые идеи на удивление просты.
Как и прежде, сложность появляется при работе с пограничными случаями, создании хорошего UX и интеграции с реальными процессами разработки.
Если вы хотите писать собственных агентов:
Начинайте с простого: базовый цикл агента и 2–3 инструмента
Добавляйте инструменты постепенно: каждая новая возможность должна решать реальную проблему
Аккуратно обрабатывайте ошибки: инструменты ломаются, агент должен уметь восстанавливаться
Тестируйте на реальных задачах: именно пограничные случаи покажут, чего не хватает
Рассмотрите Claude Agent SDK: зачем изобретать велосипед?
Будущее разработки — за агентами, которые действительно умеют что-то делать. Теперь мы понимаем, как они устроены.
Ресурсы
Автор: python_leader
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/441778
Ссылки в тексте:
[1] AI for Devs: https://t.me/+y_cNQM87LWAyNTFi
[2] str_replace: https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool#str-replace
[3] auth.py: http://auth.py
[4] CLAUDE.md: https://claude.md/
[5] mini-claude-code.py: http://mini-claude-code.py
[6] Claude Agent SDK. : https://platform.claude.com/docs/en/agent-sdk/overview
[7] CLAUDE.md: http://CLAUDE.md
[8] Источник: https://habr.com/ru/articles/984192/?utm_campaign=984192&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.