Prompt Caching: Por que eu mato minhas threads de IA sem dó
AIPare de tratar a IA da sua IDE como colega de trabalho com memória infinita. Neste artigo eu falo sobre como eu atuo e qual a melhor forma pragmática de manter o código saudável (e barato).
Em 2003, eu vi um gerente de TI entrar em pânico porque um script de backup em Perl tinha 400 linhas. Ele achava que a máquina ia "esquecer" o que estava fazendo. Vinte e poucos anos depois, eu vejo desenvolvedores seniores cometendo o exato oposto desse erro: eles tratam o chat da LLM como se fosse um colega de trabalho com memória fotográfica infinita e infinita paciência.
Eu venho observando o caos silencioso que a adoção desordenada de LLMs nas IDEs está causando. O mercado adotou a IA como um estagiário incansável, mas esqueceu de um detalhe básico: o estagiário sofre de amnésia anterógrada severa a cada nova pergunta.
Hoje em dia, meu editor principal é o Zed, amarrado na API da Anthropic via Claude Code. E eu desenvolvi um método de trabalho que choca quem está acostumado a ficar horas rolando a barra de um mesmo chat, tentando fazer a máquina lembrar o que foi decidido há quinze interações.
A verdade é que eu não tenho pena de thread de IA. Eu evito ao máximo que a thread atinja 150%, 250% de contexto consumido. Eu mato a aba. Corto o mal pela raiz e começo de novo. E não, isso não foi sorte ou preguiça. Foi um desenho arquitetural que faço propositalmente para proteger o código, a sanidade do projeto e, não menos importante, a fatura do cartão de crédito no fim do mês.
Se você está dividindo uma base de código minimamente decente e usando IA, pare de tratar o histórico do chat como o seu repositório de verdades.
A ilusão da memória contínua
Existe um erro de compreensão fundamental sobre como os modelos de linguagem funcionam nas nossas ferramentas de desenvolvimento. Quando você está usando o Cursor, o Zed ou qualquer outra extensão ligada a um LLM, a interface faz parecer que você está em um chat do WhatsApp. Você digita, o bot responde, o histórico fica ali, bonitinho, subindo a tela.
A realidade no backend é outra. A cada nova interação, cada vez que você dá um "Enter", o modelo não "lembra" da conversa anterior. A sua IDE pega todo o histórico daquela aba, empacota com o seu novo pedido, e manda o bloco inteiro como um novo input para a API.
Se a sua thread está consumindo 200% de uma janela de contexto teórica (que muitas vezes é apenas um limite de segurança da ferramenta para evitar faturas astronômicas), você está re-enviando código velho, refatorações descartadas, pedidos que deram errado e alucinações que o modelo cometeu quatro horas atrás.
Você está forçando a máquina a ler lixo. E, pior, a atenção do modelo (o tal do mecanismo de attention dos Transformers) começa a se diluir no meio desse ruído. De repente, a IA sugere mudar uma função baseada em um requisito que vocês já haviam descartado há vinte mensagens. É o equivalente digital a tentar ter uma reunião técnica numa sala com trinta pessoas gritando ao mesmo tempo.
E ainda tem alguns problemas em algumas IDEs (inclusive o Zed) que mostra o tamanho da janela de contexto, você consegue reparar que durante a interação, o contexto está em 91% por exemplo e de repente muda pra 14% e você mal repara e acha que está sob controle. Mas você está errado.
Eu não quero o modelo lendo o que deu errado. Eu quero ele focado no que é real agora.
Arquitetura como estado, chat como ferramenta temporária
Como eu fujo disso? Terceirizando o estado mental do sistema para arquivos de texto, e não para o histórico da ferramenta.
Eu nunca abro o Zed e digo "crie um CRUD de usuários" ou "Faça um sistema de pagamentos". Eu construí um ecossistema de arquivos em Markdown que servem como o cérebro persistente do projeto.
Tudo começa com o product_specs.md, onde descrevo cada feature, regra de negócio e dependência. Eu não deixo a IA inferir o que o sistema faz; ela lê isso.
Para o backend, tenho o architecture.md . Onde estão as decisões de banco de dados, padrões de repositório e regras de acesso. Para a interface, o system_design.md dita os tokens de CSS, componentes e padrões do frontend.
Quando vou iniciar uma tarefa, eu não uso um modelo rápido. Eu chamo o Claude Opus. Eu dou o contexto desses arquivos e digo a ele para atuar como o planejador. Ele não escreve código; ele elabora o plano. Ele entende a especificação, as restrições da arquitetura e, o mais importante, escreve o plano em um arquivo phases.md. Esse arquivo quebra as features em fases, documentando o que já foi, o que está em andamento e o próximo passo.
É nesse ponto que eu mato a thread.
O plano está feito? Está no phases.md? Excelente. Fecho a aba do Opus. O trabalho dele ali terminou.
Eu abro uma aba completamente nova, limpa. Nela, eu chamo o Claude Sonnet. Ele não precisa saber das horas de discussão arquitetural que tive com o Opus. Eu aponto ele para o phases.md , digito "estamos na Fase 2, passo 1. O contexto técnico está no product_specs.md e no architecture.md .Execute."
O chat da IA é efêmero. O estado consolidado vive no repositório. E claro, que isso só funciona bem, por conta do meu agents.md que está em .zed/agents.md e do claude.md que está em .claude/claude.md. Pelo menos no Zed esses 2 arquivos são lidos SEMPRE no começo de todo prompt, independente do que eu fizer. E eu faço cada um específico para cada projeto, claro. Vou pegar um de exemplo que estou usando em um projeto particular abaixo (eu ofusquei termos e nomes específicos do meu projeto):
claude.md
# Project Context & AI Instructions: *project_name*
This file serves as the master context for Claude (or others) operating on this repository. Always read this file before suggesting architectural changes or writing new features.
## Project Overview
**url_project** is XXXXXXX platform connecting users ... data security.
## Tech Stack
- **Backend:** Python 3.13, FastAPI, SQLAlchemy/Alembic.
- **Frontend:** Next.js (App Router), React, TypeScript, Tailwind CSS.
- **Database:** PostgreSQL (Neon Tech) - Relational, Serverless.
- **Storage:** Cloudflare R2 (S3-compatible) - for images, videos, and media lifecycle management.
- **Payments:** Stripe.
- **Auth / Identity:** SSO (Google/Microsoft), eKYC (Identity and Facial validation via 3rd party SDK).
## Directory Structure (Standard)
```text
project/
├── backend/ # FastAPI application
│ ├── api/ # Routes and endpoints
│ ├── core/ # Config, security, DB setup
│ ├── models/ # SQLAlchemy ORM models
│ ├── schemas/ # Pydantic models (DTOs)
│ └── services/ # Business logic
├── frontend/ # Next.js application
│ ├── src/app/ # Next.js App Router pages
│ ├── src/components/# Reusable UI components
│ └── src/lib/ # Utilities, API clients (BFF)
├── docs/ # architecture.md, prompt_history.md
└── .github/ # CI/CD workflows
```
## Domain Specific Rules
1. **High Concurrency:** The platform handles millions of users. Write highly optimized, non-blocking code.
2. **Data Privacy (PII):** User data is highly sensitive. Passwords, exact locations, and payment details must NEVER be logged or inadvertently exposed in API responses. Use Pydantic `exclude` and strict DTOs.
3. **Media Delivery:** Do not serve media through the Python backend. Always generate pre-signed URLs or point directly to the CDN (Cloudflare R2).
4. **Premium UI/UX:** The frontend must feel luxurious, fast, and native-like on mobile devices. Animations should be smooth and loading states must always be handled gracefully.
## Security & WAF Protocol (Zero Trust)
1. **Edge-First Mentality:** Always assume the application is running behind a strict Cloudflare WAF. Ensure the backend correctly identifies client IPs using Cloudflare headers (e.g., `CF-Connecting-IP`) rather than standard socket IPs.
2. **Strict RBAC & Admin Isolation:** The administrative backoffice is a highly privileged, segregated zone. All admin endpoints must enforce strict Role-Based Access Control. The admin panel must be on an obfuscated route and explicitly block indexing (`X-Robots-Tag: noindex`).
3. **Secure Defaults & DTOs:** All API inputs must be strictly validated. You must enforce Strict DTOs (Data Transfer Objects) to ensure PII (real name, email, CPF) is NEVER leaked in public API responses.
4. **Session Security:** Identity and session management are fully delegated to Clerk (product_specs.md §10) — the backend never issues, stores, or rotates a credential or refresh token of its own. The Next.js frontend manages Clerk's session cookie; FastAPI is a pure Resource Server that only ever verifies a Bearer JWT against Clerk's JWKS (see `docs/architecture.md` §4). Never store the Clerk session token in LocalStorage — the frontend forwards it to the backend via the `Authorization` header, sourced from Clerk's own cookie-backed session.
## AI Behavior Overrides
- Always strictly follow the rules defined in `agents.md`.
- If a request violates the separation of concerns (e.g., mixing business logic directly into a Next.js Client Component instead of the BFF/Backend), push back and suggest the correct architectural pattern.
## UI & Design System Protocol
1. **Figma Translation:** The primary method for UI development will be via Figma Dev Mode specs provided by the user.
2. **Strict Tailwind Constraints:** NEVER invent arbitrary hex colors, font sizes, or spacing values (e.g., `w-[32px]`, `text-[#123456]`). Tailwind v4 — there is no `tailwind.config.ts`; all customization lives in `frontend/src/app/globals.css` via `@theme inline` (see `docs/systemdesign.md`). You MUST strictly use the semantic classes defined there (e.g., `text-primary`, `p-4`, `rounded-lg`).
3. **Component Modularity:** Break down complex Figma screens into atomic, reusable React components before implementing the page logic. Always separate presentation from business logic (BFF).agents.md
# Global AI Persona & Directives: Project_Name
You are an elite, pragmatic Staff Full Stack Engineer specialized in Python (FastAPI), Next.js (React), Database Architecture (PostgreSQL/Neon), and Cloud Infrastructure (Render, Cloudflare). Your role is to assist another Senior Engineer in developing, debugging, and architecting a secure, a XXXXX platform focused on XXXXXXXX. You must strictly adhere to the business rules, architecture, and scope defined in the `docs/product_specs.md` file located at the root of the project directory.
## 1. Communication Style
- **Zero fluff:** No pleasantries, no apologies, no moralizing, and no generic AI disclaimers.
- **High signal-to-noise ratio:** Be extremely concise. Output code immediately when asked, followed by a brief, high-level explanation ONLY if the underlying logic is complex or non-obvious.
- **Assume expertise:** Do not explain basic Python, TypeScript, or React concepts. Speak to me as a peer.
## 2. Stack Philosophies
### A. Python / FastAPI (Backend)
- **Async-First:** Write asynchronous code (`async def`) for all I/O bound operations (DB, external APIs).
- **Typing:** Always use modern type hinting for function signatures, return types, and complex variables.
- **Pydantic & Validation:** Strictly enforce data validation and serialization at the API boundary using Pydantic models.
- **Dependency Injection:** Utilize FastAPI's `Depends` for database sessions, current user injection, and service classes.
### B. Next.js / React (Frontend & BFF)
- **TypeScript Strict:** All frontend code must be strictly typed. No `any`.
- **App Router & RSC:** Prefer React Server Components (RSC) for data fetching. Use Client Components (`"use client"`) only when interactivity or hooks are required.
- **BFF Pattern:** Mask complex backend logic and tokens. The Next.js API routes must act as a Backend-For-Frontend (BFF), communicating with the Python backend securely.
### C. Design System
- **Pixel-Perfect Translation:** When provided with Figma specs or design descriptions, map them meticulously to Tailwind CSS (or your chosen UI library).
- **Modular Components:** Build reusable, accessible (a11y) components. Separate logic from presentation.
- **Mobile-First:** Ensure all UI code defaults to mobile views and scales up via Tailwind breakpoints (`md:`, `lg:`).
- **Shadcn UI Strict Adherence:** The design system is strictly based on Shadcn UI. You MUST read and strictly follow the design tokens and component structures specified in `docs/systemdesign.md`. Do not hallucinate custom CSS or third-party UI libraries.
- **Lightweight File Handling:** The frontend must handle PDF and XML parsing, viewing, uploads, and downloads efficiently. Never buffer large files in the Next.js API/BFF memory. Always utilize Cloudflare R2 via secure Presigned URLs for direct client-to-bucket operations.
## 3. Core Engineering Principles
- **Immutability:** Always create new objects/arrays in TypeScript and Python; never mutate existing ones.
- **Security-First (STOP Rule):** If a security vulnerability (e.g., exposing PII, improper token handling, SQL injection risk) is identified, STOP immediately. Prioritize fixing critical issues. NEVER hardcode secrets.
- **Repository Pattern (Backend):** Encapsulate all database access behind standard interfaces. Business logic must depend on the abstract interface, not the ORM.
- **Data Deletion:** Treat user data safely. Implement soft deletes for critical data and strict TTL/Lifecycle rules for temporary media.
## 4. Strict Security & Boundary Rules
- **CRITICAL BOUNDARY:** You must NEVER read, analyze, suggest modifications to, or parse any file or directory that is outside the "project_name" project folder.
* **Database Environments (Prod vs. Dev):** The system has to uses two database environments. You must ALWAYS instruct the creation or usage of a Development Database for testing, migrations (`alembic upgrade`), and feature validation.
* **Production Database (Read-Only):** The `.env` file's production `DATABASE_URL` is strictly for READ-ONLY operations. It must only be queried to investigate bugs or understand existing live data schema. NEVER run `INSERT`, `UPDATE`, `DELETE`, or schema migrations against the production database unless I authorize it via a prompt or acceptance during execution (in which case, the request for this access must be clearly highlighted).
* **External Actions Delegation:** You cannot directly mutate external infrastructure. Whenever an action is required in Git, Neon, Render, Cloudflare, Resend, or external APIs.
## 5. Documentation & State Synchronization
As a Staff Engineer, you are strictly responsible for keeping the project's mental model and history up to date.
* **Git State Awareness:** Ask the user to provide the latest `git diff` before starting to code to understand manual commits, unless instructed otherwise.
* **Standard Workflow:** Plan (break down tasks) -> TDD (Red, Green, Refactor) -> Review (Security/Architecture self-check).
* **Architecture Updates:** Whenever a new feature, module, or infrastructure change is validated, you MUST proactively generate the markdown updates for `docs/architecture.md` (or create it) reflecting the new state.
* **Phases and Plans Updates:** Whenever you identify or I demand an improvement, technical debt, or pending items, update the `docs/phases.md` and `docs/system_design.md` files, which is used to describe the tasks broken down from `docs/product_specs.md`. At the end of each session, update the `docs/phases.md` file to keep it up to date and ensure the roadmap remains aligned.
* **End-of-Task Summary (MANDATORY):** At the conclusion of EVERY task, you must output a structured summary exactly in this format:
**[RESUMO DA CONCLUSÃO DA TAREFA]**
* **O que foi feito:** (Brief summary of implemented logic/code).
* **O que ficou de fora:** (Any edge cases, deferred features, or technical debt left for later).
* **Riscos Identificados:** (Potential security, performance, or multi-tenant isolation risks).
* **Status do architecture.md:** (Confirmation if the file needs updates or was already updated).
* **Ações Manuais Necessárias:** (Explicit list of actions the user MUST take.
## 6. Standard Execution Workflow
1. **Plan:** Identif y dependencies, constraints, security boundaries, and break down tasks.
2. **TDD / Testing:** Write failing tests first (pytest for backend, Jest/Vitest for frontend), implement minimal logic, then refactor.
3. **Review:** Self-correct against security and architecture patterns before outputting code.
### @Backend (FastAPI) Security Mandate
- You are the absolute gatekeeper of user PII. Implement strict separation between public profile data and sensitive user data in the PostgreSQL database.
- Complement Edge security with application-level rate limiting on sensitive routes (e.g., `/login`, `/register`, `/upload`, eKYC webhooks) to prevent brute-force and application-layer DDoS.
- All WebSockets must implement authentication checks upon connection and handle connection dropping gracefully.
### @Frontend (Next.js) Security Mandate
- Ensure the Admin UI is completely decoupled from the main public application routing where possible, or strictly protected via middleware.
- View-Once media in the chat MUST be obfuscated. Disable right-click, prevent caching, and use ephemeral blob URLs to hinder unauthorized saving or downloading.
Mas e o consumo de tokens? Não fica caro?
Quando eu explico isso, a primeira pergunta que surge é: "Mas Guilherme, se você abre uma aba nova e envia todos esses `.md` do zero a cada tarefa, você não gasta uma fortuna em tokens de entrada?"
E aqui entra a beleza da arquitetura das LLMs modernas que muita gente ignora: Prompt Caching.
O meu claude.md introduz o cache de prompts na API do Claude. Como os meus arquivos (agents.md, architecture.mde system_design.md) formam um bloco de contexto grande e estático, e eu os envio sistematicamente no início de cada nova thread, a infraestrutura deles faz o cache desse bloco.
Eu não pago o valor cheio por milhares de tokens toda vez. Ler do cache custa uma fração minúscula (até 90% a menos) do custo de processamento de tokens novos. Eu pago barato pelo contexto estático e só gasto tokens "caros" nas poucas linhas da instrução nova e no código gerado.
Se eu mantivesse a thread antiga viva e gigante, além de pagar para re-enviar todo o lixo do histórico a cada requisição, eu ainda estaria bagunçando o cache com conteúdo dinâmico e variável. Cortar o histórico e reiniciar apenas com os arquivos consolidados é matematicamente, economicamente e logicamente superior.
Eu fecho o ciclo com um prompthitory.md . Depois que o Sonnet gera o código e eu reviso e aprovo, faço com que o registro resumido do porquê aquela decisão foi tomada vá para esse arquivo de log de prompts. É um registro otimizado, sem o falatório do chat.
É claro, é preciso ter rigor na gestão. Se o prompthistory.md ou o architecture.md crescerem descontroladamente, o benefício do cache diminui e o modelo volta a se afogar em texto. Eu mantenho esses documentos magros e diretos. Ah, e tem um detalhe crucial: Se você começa a digitar e enquanto o Opus está fazendo o planejamento ou o Sonnet está executando e vai fazer outras coisas, lembre-se que o Prompt Caching da Anthropic (e de todos os outros LLMs) tem um prazo para consumir o cache com valor menor. Então se a IA te entrega uma pergunta ou solicita algo e você deixa ela 1 ou 2 horas esperando, saiba que quando voltar pagará o preço normal dos tokens. Como comentei, ter duas décadas de software e produto te ensina a separar o que é desnecessário do que é essencial.
Por que a UI da IDE não me ajuda nisso?
Se a estratégia é tão boa, por que eu ainda tenho que mudar o seletor do Opus para o Sonnet manualmente no Zed ou abrir uma nova thread após finalizar o plano?
A resposta reside na maturidade das ferramentas atuais. O Zed, integrado ao Claude Code via external-agent ou mesmo via api, foca em ser um editor rápido e leve, não um orquestrador complexo de IAs. O botão de "Concordar e aceitar edições" atua apenas no buffer de diff local. Ele não emite um hook avisando a configuração do agente: "ei, terminamos o planejamento, troque as chaves da requisição e ative o executor".
Não me entenda mal: não acho que seja um defeito fatal. Automatizar essa transição exigiria um script que monitorasse eventos do file system e alterasse parâmetros do agente via linha de comando. Na prática, gastar dois segundos para clicar e mudar o modelo me mantém consciente do fluxo. A fricção leve, às vezes, é um lembrete físico de que o contexto mudou. O dia que a IDE fizer isso sozinha, vai ter júnior rodando Opus pra consertar tag HTML porque esqueceu qual fase estava.
Nós gostamos de reclamar que as ferramentas não são inteligentes o suficiente. Eu prefiro que a ferramenta seja burra, previsível e me obedeça. Além disso, este processo evita um dos problemas modernos que vejo muitos desenvolvedores reclamarem nas redes sociais que é o de "não saber o código que está sendo gerado". Toda vez que você para para ler, responder, adicionar e interagir com a LLM você está entendendo e tendo mais contato com o código, e portanto irá se manter sempre atualizado. E o Zed cumpre isso magnificamente.
A minha cabeça é dividida: Engenheiro e Produto
Esse modo de trabalho não surgiu num estalo. Ele é o resultado direto de ser alguém que atua como engenheiro de software mas que passou quase duas décadas olhando também para o desenvolvimento de produtos.
Minha mente de engenheiro quer código eficiente, bancos normalizados e infraestrutura que escale. Minha mente de produto sabe que o código mais perfeito do mundo é inútil se ele se perder no meio de um refatoramento indesejado causado por uma IA confusa, atrasando a entrega da funcionalidade que o usuário precisa amanhã.
Usar o Opus para planejar é a visão de produto: definir o escopo, entender as limitações e quebrar em fases sem tocar em código. Mudar para o Sonnet, limpar a mesa e forçá-lo a olhar apenas para os arquivos `.md` é a visão de engenharia pragmática: foco na execução, restrição de variáveis e redução de custo operacional.
Delegar não significa entregar a chave do carro para o LLM. Significa montar a estrada, as proteções laterais e os pedágios, para só então deixar o carro andar.
O fim da dependência da memória
A grande falácia do uso atual de inteligência artificial na programação é a ideia de que a máquina deve entender o seu projeto apenas conversando com você. Isso não é engenharia; isso é terapia. E terapia de código sai muito cara.
A inteligência artificial é um compilador de intenções avançado. E, assim como um compilador tradicional não lembra da execução anterior, o LLM não deve depender da memória de conversas passadas para ser eficiente. O estado do sistema pertence ao sistema — aos seus arquivos de arquitetura, aos seus testes, à sua documentação.
Matar threads que passaram dos 100% de uso não é um ato de desespero. Eu faço isso para manter a higiene do contexto. É garantir que a ferramenta esteja sempre operando no auge da sua atenção, utilizando as capacidades nativas de cache da API, e mantendo o seu repositório como a única fonte de verdade.
Você tem terceirizado o entendimento da sua arquitetura para o histórico de um chat que será perdido assim que fechar a IDE? Ou você tem a disciplina de formalizar o estado do projeto para que a máquina apenas o execute? Da próxima vez que o ícone de uso do contexto bater 150%, não hesite. Feche a aba. Seu código (e seu bolso) agradecem.
Comments