MÓDULO 5.1

📐 Arquitetando Seu Agente

Do escopo à estrutura de código: como planejar e organizar os componentes fundamentais de um agente de IA usando Python e Ollama.

6
Tópicos
30
Minutos
Intermediário
Nível
Prática
Tipo
1

🎯 Definindo o Escopo

Antes de escrever uma linha de código, você precisa definir exatamente o que seu agente faz. Agente de código? Assistente de pesquisa? Automação de deploy? Cada tipo exige ferramentas diferentes, níveis de autonomia distintos e modelos com capacidades específicas. Sem escopo claro, você constrói um Frankenstein que faz tudo mal.

Checklist de Escopo do Agente

Tipo: agente de código (lê/escreve arquivos), assistente (responde perguntas com contexto) ou automação (executa pipelines)?

Ferramentas: quais ações concretas o agente precisa executar? Liste de 3 a 5 para começar

Autonomia: o agente pode agir sozinho ou precisa de confirmação humana para cada ação?

Modelo: qual LLM será usado? Local (Ollama) ou API? Qual tamanho (8B, 70B)?

💡 Comece Pequeno

Não tente construir o Claude Code de primeira. Comece com um agente que faz UMA coisa bem: ler arquivos e responder perguntas sobre o código. Depois adicione escrita, depois execução. O Claude Code levou meses para chegar em 40+ ferramentas — seu MVP precisa de 3.

2

⚡ Stack Tecnológica

Nosso agente será construído com Python 3.11+ e Ollama como provedor de LLM local. O Ollama roda modelos open-source diretamente na sua máquina — sem custos de API, sem latência de rede, sem dependências externas. A biblioteca ollama para Python oferece uma interface limpa e compatível com o padrão OpenAI.

Setup Inicial — Python + Ollama

# Terminal: instalar dependências
pip install ollama

# Python: testar conexão
from ollama import chat

response = chat(
    model='qwen3:8b',
    messages=[
        {'role': 'user', 'content': 'Hello, are you working?'}
    ]
)
print(response.message.content)

# Pré-requisito: Ollama rodando em localhost:11434
# Instale: https://ollama.ai → ollama pull qwen3:8b

Python 3.11+: tipagem moderna, asyncio maduro, f-strings avançadas

Ollama: servidor local de LLMs — roda Qwen, Llama, Mistral e outros modelos open-source

qwen3:8b: modelo recomendado para agentes — bom suporte a tool calling, roda em 8GB VRAM

📊 Por Que Python + Ollama?

Sem custo de API: modelos rodam localmente, sem tokens cobrados — ideal para desenvolvimento e testes

Privacidade total: código e dados nunca saem da sua máquina — importante para projetos proprietários

Latência baixa: sem round-trip de rede — respostas em milissegundos para modelos pequenos

Ecossistema Python: subprocess, pathlib, asyncio, json — tudo que um agente precisa já vem built-in

3

🧩 Componentes Essenciais

Todo agente precisa de 5 componentes fundamentais: o loop principal (AgentLoop), o executor de ferramentas (ToolExecutor), o gerenciador de contexto (ContextManager), o armazenamento de memória (MemoryStore) e o controlador de permissões (PermissionController). Cada um mapeia diretamente em um subsistema do Claude Code.

Diagrama dos subsistemas do agente
Os subsistemas do Claude Code — nosso agente implementará versões simplificadas de cada um.

Mapeamento: Claude Code → Python

Componente Claude Code Python
AgentLoop queryHaiku / queryLoop while True + ollama.chat()
ToolExecutor 40+ tools com schemas TOOL_MAP + dispatch
ContextManager System prompt + CLAUDE.md trim_messages()
MemoryStore 4 tipos + AutoDream JSON file persistence
PermissionController 5 camadas allow/deny/ask JSON config + check()

💡 Arquitetura Modular

Cada componente deve ser uma classe independente com interface clara. Isso permite trocar o LLM (de Ollama para OpenAI API) sem mexer nas ferramentas, ou adicionar novas ferramentas sem alterar o loop. O Claude Code segue exatamente esse padrão — é o que permite a ele suportar múltiplos modelos e dezenas de ferramentas.

4

📦 Padrão: Tool Registry

O Tool Registry é o padrão que conecta o LLM às ferramentas. Consiste em duas estruturas: TOOL_MAP (dicionário nome → função Python) e TOOL_SCHEMAS (lista de schemas JSON que o LLM entende). Quando o LLM decide usar uma ferramenta, o registry localiza a função correta e a executa.

Implementação do Tool Registry

# tool_registry.py
from pathlib import Path
import subprocess

# 1. Funções das ferramentas
def read_file(path: str) -> str:
    return Path(path).read_text(encoding='utf-8')

def run_shell(command: str) -> str:
    result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60)
    return result.stdout + result.stderr

# 2. Mapa nome → função
TOOL_MAP = {
    'read_file': read_file,
    'run_shell': run_shell,
}

# 3. Schemas para o LLM
TOOL_SCHEMAS = [
    {
        'type': 'function',
        'function': {
            'name': 'read_file',
            'description': 'Read contents of a file',
            'parameters': {
                'type': 'object',
                'properties': {
                    'path': {'type': 'string', 'description': 'File path to read'}
                },
                'required': ['path']
            }
        }
    },
    {
        'type': 'function',
        'function': {
            'name': 'run_shell',
            'description': 'Run a shell command',
            'parameters': {
                'type': 'object',
                'properties': {
                    'command': {'type': 'string', 'description': 'Shell command to execute'}
                },
                'required': ['command']
            }
        }
    }
]

# 4. Dispatch: executar ferramenta por nome
def execute_tool(name: str, args: dict) -> str:
    if name not in TOOL_MAP:
        return f"Error: unknown tool '{name}'"
    try:
        return TOOL_MAP[name](**args)
    except Exception as e:
        return f"Error: {e}"

💡 Descrições São Tudo

O LLM decide qual ferramenta usar baseado na description do schema. Uma descrição vaga = ferramenta mal utilizada. Seja específico: "Read the full contents of a file given its path" é melhor que "Read file". O Claude Code investe dezenas de linhas descrevendo cada ferramenta.

5

📦 Padrão: Context Manager

A context window é o recurso mais precioso do agente. Cada mensagem, cada resultado de ferramenta consome tokens. Sem gerenciamento, o agente atinge o limite e para de funcionar. O Context Manager monitora o uso de tokens e comprime mensagens antigas quando necessário — exatamente como o AutoDream do Claude Code.

Implementação do Context Manager

# context_manager.py
import json

def estimate_tokens(text: str) -> int:
    """Estimativa simples: ~4 chars por token."""
    return len(text) // 4

def trim_messages(messages: list, max_tokens: int = 30000) -> list:
    """Mantém system prompt + mensagens mais recentes dentro do budget."""
    # Sempre preservar system messages
    system = [m for m in messages if m['role'] == 'system']
    non_system = [m for m in messages if m['role'] != 'system']

    # Calcular tokens do system prompt
    system_tokens = sum(estimate_tokens(json.dumps(m)) for m in system)
    budget = max_tokens - system_tokens

    # Adicionar mensagens de trás pra frente (mais recentes primeiro)
    kept = []
    used = 0
    for msg in reversed(non_system):
        msg_tokens = estimate_tokens(json.dumps(msg))
        if used + msg_tokens > budget:
            break
        kept.insert(0, msg)
        used += msg_tokens

    return system + kept

class ContextManager:
    def __init__(self, max_tokens: int = 30000):
        self.max_tokens = max_tokens
        self.messages: list = []

    def add(self, message: dict):
        self.messages.append(message)
        self.messages = trim_messages(self.messages, self.max_tokens)

    def get_messages(self) -> list:
        return self.messages.copy()

    def token_usage(self) -> int:
        return sum(estimate_tokens(json.dumps(m)) for m in self.messages)

📊 Estratégias de Compressão

Truncar antigas: mais simples — remove mensagens do início (o que fizemos acima)

Resumir com LLM: pedir ao modelo para resumir mensagens antigas antes de descartar (AutoDream)

Truncar tool results: saídas de ferramentas longas podem ser cortadas para os primeiros N caracteres

Sliding window: manter sempre as últimas N mensagens — previsível e simples

6

📦 Padrão: Permission Controller

Um agente sem controle de permissões é uma bomba-relógio. O Permission Controller decide, para cada ferramenta, se ela pode rodar livremente (allow), precisa de confirmação (ask) ou está bloqueada (deny). Usa um arquivo JSON de configuração para que regras possam ser ajustadas sem alterar código.

Implementação do Permission Controller

# permissions.json
{
    "read_file": "allow",
    "write_file": "ask",
    "run_shell": "ask",
    "search_in_files": "allow",
    "delete_file": "deny"
}

# permission_controller.py
import json
from pathlib import Path

class PermissionController:
    def __init__(self, config_path: str = 'permissions.json'):
        self.rules = json.loads(Path(config_path).read_text())

    def check(self, tool_name: str) -> str:
        """Retorna 'allow', 'ask' ou 'deny'."""
        return self.rules.get(tool_name, 'ask')

    def request_permission(self, tool_name: str, args: dict) -> bool:
        """Pede confirmação ao usuário para ferramentas 'ask'."""
        permission = self.check(tool_name)
        if permission == 'allow':
            return True
        if permission == 'deny':
            print(f"BLOCKED: {tool_name} is denied by policy")
            return False
        # permission == 'ask'
        print(f"\n⚠️  Tool: {tool_name}")
        print(f"   Args: {json.dumps(args, indent=2)}")
        resp = input("   Allow? [y/N]: ").strip().lower()
        return resp == 'y'

Fazer

  • Usar "deny" por padrão para ferramentas desconhecidas
  • Logar cada execução de ferramenta com timestamp e args
  • Separar permissões por ambiente (dev vs prod)
  • Validar argumentos antes de executar a ferramenta

Evitar

  • Dar "allow" para run_shell sem validação de comandos
  • Hardcodar permissões no código — use JSON configurável
  • Ignorar o retorno do permission check antes de executar
  • Confiar no LLM para se auto-limitar — ele não vai

📋 Resumo do Módulo

Definir escopo antes de codar — tipo de agente, ferramentas, autonomia e modelo
Python 3.11+ e Ollama como stack — local, gratuito e privado
5 componentes essenciais: AgentLoop, ToolExecutor, ContextManager, MemoryStore, PermissionController
Tool Registry conecta LLM às ferramentas via TOOL_MAP + TOOL_SCHEMAS
Context Manager monitora e comprime tokens para manter o agente funcional
Permission Controller garante segurança com allow/ask/deny configurável

Próximo Módulo:

5.2 - 🔧 Construindo o Sistema de Ferramentas