Como adicionar Agent de IA em suas aplicações

Do chatbot que só conversa ao agente que executa: o que um agent realmente é, quando vale construir o loop você mesmo, e como embutir o Cursor SDK em scripts, APIs e produtos.

Como adicionar Agent de IA em suas aplicações

Um chatbot responde. Um agent age.

Essa diferença parece sutil até você tentar colocar inteligência artificial de verdade dentro de um produto. O modelo precisa ler arquivos, chamar APIs internas, abrir um PR, consultar o Linear, esperar um job de CI e voltar com um resultado — não só um parágrafo de texto. Se a sua aplicação ainda trata o LLM como um endpoint de “pergunte e receba”, você está deixando a parte difícil (e a parte útil) do lado de fora.

Este post cobre o que um agent de fato é, as duas formas de adicioná-lo a uma aplicação, e um caminho concreto com o Cursor SDK — o mesmo runtime que já roda no IDE, no CLI e na web do Cursor.

O que é um agent (e o que não é)

Três peças formam qualquer coding agent:

  1. Modelo — o cérebro. Escolhe o próximo passo, não só a próxima frase.
  2. Ferramentas — o corpo. Ler arquivo, grep, shell, HTTP, MCP, funções da sua aplicação.
  3. Harness — o sistema nervoso. O loop que chama o modelo, executa as tools, injeta o resultado de volta no contexto, gerencia sessão, sandbox, cancelamento e falha.

O harness é o que a maioria das equipes subestima. Sem ele, você tem um fetch para um provedor de modelos e um prompt longo. Com ele, o modelo trabalha em ciclo até concluir a tarefa — ou até você cancelar.

usuário → prompt
            ↓
        [ modelo ]
            ↓
     precisa de tool? ──não──→ resposta final
            ↓ sim
     executa ferramenta
            ↓
     resultado volta no contexto ──→ [ modelo ]

Um chatbot para no primeiro “não”. Um agent continua.

Duas formas de adicionar isso à sua aplicação

1. Montar o loop você mesmo

Útil para entender o mecanismo. Você escolhe um modelo, descreve tools em JSON Schema, chama tool_use quando o modelo pedir, e reenvia o resultado. Em Python isso cabe em poucas dezenas de linhas. Em produção, vira um produto paralelo: sandbox, persistência de sessão, streaming, retry, limites de token, observabilidade, e um novo ciclo de adaptação a cada modelo que sai.

Faça isso se o objetivo é aprender. Não faça isso se o objetivo é entregar um agent confiável contra o seu código e os seus dados.

2. Usar um runtime pronto

O Cursor SDK expõe o mesmo agent do editor como biblioteca. Você não reimplementa o harness: cria um Agent, manda um prompt, observa o Run. TypeScript (@cursor/sdk) e Python (cursor-sdk) compartilham o mesmo modelo mental. Para outras linguagens, existe o SDK Bridge.

O resto deste post segue esse caminho. É o que você usa quando o agent precisa viver dentro da aplicação — CI, bot interno, painel, produto para o usuário final — e não só no IDE do desenvolvedor.

Conceitos que você vai usar o tempo todo

Conceito O que é
Agent Container durável: conversa, workspace, modelo, settings. Sobrevive a vários prompts.
Run Uma submissão. Tem stream, status, resultado e cancelamento próprios.
SDKMessage Evento normalizado do stream. Mesmo formato em local e cloud.

Três padrões de invocação cobrem quase toda integração. Não misture os três no mesmo fluxo.

One-shot: Agent.prompt()

Script, GitHub Action, job que manda um prompt, espera, sai. O SDK cria, executa e descarta o agent por você.

import { Agent } from "@cursor/sdk";

const result = await Agent.prompt("Refatore src/utils.ts para ficar mais legível", {
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  local: { cwd: process.cwd() },
});

console.log(result.status, result.result);

Se imediatamente depois você for “continuar a conversa”, este não era o padrão certo. Use o próximo.

Conversacional: Agent.create() + agent.send()

Streaming, follow-up, cancelamento. É o formato da maioria das aplicações reais — API, bot, painel interno.

import { Agent } from "@cursor/sdk";

await using agent = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  local: { cwd: process.cwd() },
});

const run = await agent.send("Encontre o bug em src/auth.ts");

for await (const event of run.stream()) {
  if (event.type === "assistant") {
    for (const block of event.message.content) {
      if (block.type === "text") process.stdout.write(block.text);
    }
  }
}

await run.wait();

const run2 = await agent.send("Agora escreva um teste de regressão");
await run2.wait();

O segundo send mantém o contexto da conversa. stream() observa; wait() é o que fecha o ciclo. Você pode pular o stream. Quase nunca deve pular o wait().

Retomar depois: Agent.resume()

Cron que continua o cleanup de ontem. Webhook que estende o agent de um usuário. CLI que recarrega estado entre processos.

await using agent = await Agent.resume(previousAgentId, {
  apiKey: process.env.CURSOR_API_KEY!,
});

const run = await agent.send("Atualize também o changelog");
await run.wait();

IDs com prefixo bc- são cloud; o resto é local. Servidores MCP passados inline não sobrevivem ao resume — eles costumam carregar secrets e existem só em memória. Passe de novo no resume.

Local ou cloud: escolha explícita

O SDK seleciona local se você não passar nem local nem cloud. O erro clássico: você queria um agent na VM do Cursor, esqueceu o campo cloud, e passou uma hora debugando um executor local silencioso.

  • Local — o loop corre na máquina de quem chamou, contra cwd. Bom para scripts de dev e CI que já têm o repositório checado. “Local” descreve onde o loop e o filesystem rodam, não onde o modelo roda. A inferência continua nos modelos hospedados do Cursor.
  • Cloud — VM isolada, repo clonado, o job sobrevive se o caller desconectar. Bom para paralelismo, PRs automáticos (autoCreatePR: true) e qualquer coisa que não possa depender do laptop do engenheiro.
const local = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  local: { cwd: "/path/to/repo" },
});

const cloud = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  cloud: {
    repos: [{ url: "https://github.com/sua-org/seu-repo", startingRef: "main" }],
    autoCreatePR: true,
  },
});

Passe sempre um dos dois. O custo é uma linha.

Colocando o agent numa API

O caso mais comum em produto: o usuário manda uma tarefa, o backend dispara um run, o frontend acompanha o stream. Em Python, servidores e orquestração concorrente devem usar o client async — não misture sync e async no mesmo caminho.

import os
from cursor_sdk import AsyncClient, LocalAgentOptions

async def handle_task(prompt: str) -> str:
    async with await AsyncClient.launch_bridge(workspace=os.getcwd()) as client:
        async with await client.agents.create(
            model="composer-2.5",
            api_key=os.environ["CURSOR_API_KEY"],
            local=LocalAgentOptions(cwd=os.getcwd()),
        ) as agent:
            run = await agent.send(prompt)
            return await run.text()

Em TypeScript, um Route Handler que faz stream para o browser pode iterar run.stream() e escrever chunks SSE. Guarde agent.agentId e run.id antes de começar a streamar. Se o stream travar, esses IDs são o que você consulta no dashboard ou via Agent.getRun().

const run = await agent.send(prompt);
console.log({ agentId: agent.agentId, runId: run.id });

const encoder = new TextEncoder();
const stream = new ReadableStream({
  async start(controller) {
    try {
      for await (const event of run.stream()) {
        if (event.type === "assistant") {
          for (const block of event.message.content) {
            if (block.type === "text") {
              controller.enqueue(encoder.encode(`data: ${JSON.stringify(block.text)}\n\n`));
            }
          }
        }
      }
      await run.wait();
      controller.close();
    } catch (err) {
      controller.error(err);
    }
  },
});

return new Response(stream, {
  headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
});

Para um one-shot em CI, Agent.prompt() continua sendo a opção mais difícil de vazar recurso: ele faz dispose sozinho.

Ligando o agent ao resto do seu produto

Um agent sem tools da aplicação é um assistente genérico. Você quer que ele fale com o seu mundo.

MCP servers

Passe servidores inline em Agent.create() ou agent.send(). HTTP (com headers estáticos ou OAuth) ou stdio (command / args / env). Override por send substitui os servers da criação — não faz merge.

const agent = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  local: { cwd: process.cwd() },
  mcpServers: {
    linear: {
      type: "http",
      url: "https://mcp.linear.app/sse",
      headers: {
        Authorization: `Bearer ${process.env.LINEAR_API_KEY!}`,
      },
    },
  },
});

Sem local.settingSources, só entram servers inline. Isso é o que você quer em serviço: a config vem do código, não do ~/.cursor de quem está rodando o processo.

Custom tools (só local)

Quando a “ferramenta” é uma função da sua aplicação — status de deploy, consulta no banco, endpoint interno — não precisa levantar um MCP separado. Passe em local.customTools. O SDK registra como um server MCP chamado custom-user-tools.

const agent = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  local: {
    cwd: process.cwd(),
    customTools: {
      get_deployment_status: {
        description: "Consulta o status atual do deploy de um serviço.",
        inputSchema: {
          type: "object",
          properties: {
            service: { type: "string", description: "Nome do serviço" },
          },
          required: ["service"],
        },
        async execute({ service }) {
          const res = await fetch(`https://deploys.internal/api/${service}`);
          return await res.text();
        },
      },
    },
  },
});

Custom tools não existem em cloud agents. Para cloud, exponha a mesma capacidade via MCP HTTP.

Restringir o que o modelo pode fazer

Em local, tools é allowlist e disallowedTools é denylist. Útil para um agent só-leitura no seu produto, ou para um bot de CI que não deve abrir shell.

const reader = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2.5" },
  tools: ["read", "grep", "glob", "ls"],
  local: { cwd: process.cwd() },
});

Essas restrições não persistem no agent: passe de novo no resume.

Autenticação, modelo e o que cobram

export CURSOR_API_KEY="cursor_..."

A chave vem do Dashboard → API Keys (usuário) ou de uma service account do time. Chaves de Team Admin ainda não são suportadas.

Em código de infraestrutura compartilhada, passe apiKey / api_key explícito. Depender da env var em um processo multi-tenant é o tipo de atalho que mistura contas.

O modelo é obrigatório em local. Passe sempre, inclusive em cloud, para o comportamento não mudar sem você perceber. composer-2.5 é o default razoável para a maior parte das integrações de código. { id: "auto" } deixa o servidor escolher. Cursor.models.list() devolve os IDs válidos para a conta que está autenticada — não hardcode IDs exóticos sem conferir acesso.

Runs do SDK entram no mesmo pool de pricing e Privacy Mode do IDE. No dashboard de usage eles aparecem com a tag SDK.

O que quebra em produção (e como evitar)

Dois tipos de falha, um instinto de misturá-las. CursorAgentError lançado significa que o run nem começou (auth, config, rede). result.status === "error" significa que começou e falhou no meio. Exit code 1 para o primeiro, 2 para o segundo, 0 só para finished.

import { Agent, CursorAgentError } from "@cursor/sdk";

try {
  const run = await agent.send(prompt);
  const result = await run.wait();
  if (result.status === "error") {
    console.error("run failed:", result.id);
    process.exit(2);
  }
} catch (err) {
  if (err instanceof CursorAgentError) {
    console.error("startup failed:", err.message, "retryable=", err.isRetryable);
    process.exit(1);
  }
  throw err;
}

Respeite isRetryable. Retry cego em cloud cria runs duplicados.

Dispose não é opcional. O SDK segura executors locais, stores de run e clients HTTP. TypeScript: await using agent = .... Python: with Agent.create(...) as agent:. Agent.prompt() já faz isso por você.

Não carregue settings ambiente sem querer. O default é config inline only. settingSources: "all" puxa project/user/team/MDM de quem está no processo — quase nunca o que um serviço deveria fazer. Em cloud isso não se aplica: cloud sempre honra team/project/plugins.

Em CI com cloud, skipReviewerRequest: true a menos que um humano precise ser notificado. Evita spam de reviewer request em PRs gerados pelo agent.

Nem toda operação existe em todo runtime. Runs destacados ou rehidratados (Agent.getRun depois que o event store local fechou) podem não suportar stream, cancel ou conversation. Antes de chamar, run.supports("cancel").

O que as pessoas estão de fato construindo

Os exemplos oficiais no Cursor Cookbook deixam o mapa claro:

  • CI que se conserta — o pipeline falha, um agent resume o log, abre PR com o fix.
  • Kanban com agent — arrastar um card dispara o trabalho, o PR volta como anexo.
  • CLI interno — o time dispara agents do terminal sem abrir o IDE.
  • Produto com agent embutido — o usuário final conversa com um agent sem sair da sua UI. Notion publicou exatamente esse padrão: o SDK como o agent de dentro do produto, não como um chat ao lado.

O padrão se repete: a aplicação decide quando e com qual contexto o agent roda. O SDK decide como ele pensa e age.

Checklist para o primeiro agent na sua app

  1. Instale @cursor/sdk (Node 22.13+) com pnpm, ou cursor-sdk (Python 3.10+).
  2. Exporte CURSOR_API_KEY. Em serviço, passe a chave no create.
  3. Escolha o padrão: prompt (one-shot), create + send (conversa), resume (entre processos).
  4. Passe local ou cloud de forma explícita.
  5. Logue agentId e run.id antes do stream. Sempre chame wait().
  6. Trate CursorAgentError separado de result.status === "error".
  7. Ligue MCP ou customTools para o agent enxergar o seu domínio.
  8. Dispose com await using / with. Pronto.

A partir daí, o trabalho deixa de ser “como faço um agent” e passa a ser “qual tarefa da minha aplicação merece um”. Essa é a pergunta certa.

Referências

Quer colocar sua ideia no ar?

Conte o que você precisa e receba uma proposta em até 24 horas.

Falar conosco