- PVSM.RU - https://www.pvsm.ru -

Claude Code изнутри: как устроены AI-агенты для разработки

Команда AI for Devs [1] подготовила перевод статьи о том, как на самом деле устроены AI-агенты для программирования. Автор шаг за шагом показывает, что за Claude Code не стоит магия: это последовательный агентный цикл, инструменты, контроль разрешений и работа с контекстом.


Что делает Claude Code мощным, на удивление просто: это цикл, который позволяет ИИ читать файлы, запускать команды и итеративно работать, пока задача не будет выполнена.

Сложность начинается там, где нужно разрулить пограничные случаи, сделать хороший UX и встроиться в реальные процессы разработки.

В этом посте я начну с нуля и шаг за шагом подведу вас к архитектуре Claude Code, показывая, как вы могли бы «изобрести» её сами — от первых принципов, имея лишь терминал, API LLM и желание сделать ИИ действительно полезным.

Конечная цель: понять, как работают мощные агенты, чтобы вы могли собрать своего

Для начала зафиксируем проблему, которую мы пытаемся решить.

Когда вы пользуетесь ChatGPT или Claude в браузере, вы делаете массу ручной работы:

  • Копируете и вставляете код из чата в файлы

  • Запускаете команды сами, затем копируете ошибки обратно

  • Даете контекст, загружая файлы или вставляя содержимое

  • Вручную проходите цикл «исправил — проверил — отладил» снова и снова

По сути, вы выступаете руками ИИ. ИИ думает — вы исполняете.

А что если ИИ мог бы исполнять тоже?

Представьте, что вы говорите ИИ: «Исправь баг в auth.py» — и уходите. Возвращаетесь, а баг уже исправлен. ИИ прочитал файл, понял, что происходит, попробовал исправление, запустил тесты, увидел падение, попробовал другой подход — и в итоге добился успеха.

Вот что делает агент. Это ИИ, который умеет:

  1. Совершать действия в реальном мире (читать файлы, запускать команды)

  2. Наблюдать результаты

  3. Решать, что делать дальше

  4. Повторять, пока задача не будет завершена

Давайте соберем такого с нуля.

Самый простой агент

Начнём с абсолютного минимума: ИИ, который умеет выполнить одну 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 — добавить инструменты

Запуск 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. Это гораздо лучше, потому что:

  1. Типобезопасность: ИИ точно знает, какие параметры принимает каждый инструмент

  2. Явные действия: чтение файла — это вызов read_file, а не cat

  3. Контролируемая поверхность: мы сами решаем, какие инструменты вообще существуют

Попробуйте:

# 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 токенов

  • ИИ: упирается в лимит контекста и начинает забывать ранние детали

Как с этим справляться?

Вариант 1: суммаризация (уплотнение)

Когда контекст становится слишком длинным, можно сжать историю, суммировав произошедшее:

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}"}]

Вариант 2: подагенты (делегирование)

Для сложных задач можно запускать подагента со своим отдельным контекстом:

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

Мы до этого замалчивали одну важную вещь: откуда ИИ вообще знает, как себя вести?

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}
"""

Но тут возникает проблема: а если у проекта есть специфические соглашения? Что если команда использует конкретный тестовый фреймворк или у репозитория нестандартная структура директорий?

Цель: проектный контекст (CLAUDE.md)

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 и другими инструментами разработки

Claude Agent SDK

Если вы хотите развивать эту основу, не изобретая всё заново, 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