Blog · Infraestrutura · 30 min de leitura

Claude Platform on AWS: Mão na Massa com um Agent de Padel no Slack

Por Fabio Douek

Ir para seção
Explica (TLDR) como se eu fosse...
O que é isso?

Imagine que você já tem uma pulseirinha especial que te deixa entrar em todos os brinquedos do parque, e hoje o parque adicionou uma atração novinha operada por outra empresa. Em vez de te dar uma segunda pulseirinha, eles deixam a sua atual funcionar no brinquedo novo também, e o custo aparece na mesma conta do parque no fim do dia.

Foi isso que a AWS fez com a Claude. A sua pulseirinha AWS, que os adultos chamam de IAM, agora abre a porta para todo o parquinho da Claude da Anthropic. Os robôs que respondem perguntas ainda vivem no prédio da Anthropic, não no prédio da AWS, mas você entra e paga pelo da AWS.

Trate isso como um novo caminho de procurement para um fornecedor já existente. A superfície contratual é o AWS Marketplace e a superfície de autenticação é o AWS IAM, mas o processador de dados de inferência é a Anthropic, operando fora do perímetro de segurança da AWS. Compromissos AWS existentes são abatidos contra o consumo, o que é conveniente, e o CloudTrail captura metadados de requisição, o que é auditável.

As perguntas materiais a levantar são residência de dados, Zero Data Retention e workloads regulados. ZDR é opt-in neste caminho, em vez de implícito, e a Anthropic afirma explicitamente que workloads FedRAMP High, IL4-5 e HIPAA-ready devem permanecer no Amazon Bedrock, onde a AWS é o único processador. Várias features de destaque entram como beta dentro de um serviço GA, então contratos vinculados a elas merecem uma segunda leitura.

Pense nisso como uma intervenção pontual para um sintoma: times que já rodam na AWS e querem o conjunto completo de features da Anthropic sem montar uma segunda relação com fornecedor. O mecanismo é sólido, IAM e SigV4 na frente da plataforma existente da Anthropic, e a base de evidência é forte no caminho de auth porque tanto SDKs quanto notebooks de exemplo da AWS chegaram no primeiro dia.

Efeitos colaterais a monitorar: os dados de inferência saem do perímetro AWS, então o data path é materialmente diferente do Bedrock, e várias features de destaque chegam como beta dentro de um wrapper GA. Bons candidatos são times Claude-only com compromissos AWS existentes e sem regras estritas de residência; candidatos ruins são workloads regulados que precisam que a AWS seja o único processador.

Repare no pequeno alívio que vem de não ter que onboardar um novo fornecedor. Procurement não precisa assinar um novo contrato, segurança não precisa emitir uma nova chave, finance não precisa aprender uma nova fatura. O time consegue focar no modelo e no trabalho, em vez do encanamento ao redor deles.

A tensão nova aterriza em algum lugar mais silencioso: os dados de inferência cruzam para fora do perímetro AWS que seu time de segurança desenhou com cuidado, e as features da Claude que seus engenheiros estão mais empolgados, Skills, Managed Agents, MCP, ainda estão rotuladas como beta. Vale nomear abertamente antes do time se animar com isso, para que a confiança não seja construída sobre uma história que muda em silêncio.

Trate como um músico convidado sentando na sua sessão, mas você não precisa imprimir uma nova setlist para ele. Ele já conhece suas deixas porque as deixas são suas credenciais AWS, e a divisão de royalties cai no mesmo extrato mensal que você ia receber de qualquer jeito. O instrumento que ele toca continua sendo o dele, afinado na sala dele.

Onde o conjunto fica mais rico é na lista de equipamento: Skills, code execution, Files, MCP, o Console, todos aparecem no mesmo suporte que os músicos da Claude direta usam. Onde precisa de cuidado é que vários desses pedais carregam etiquetas beta, e a sala onde o músico ensaia não é a sua sala. O tempo de integração é rápido, a checagem de som no rigor de produção é a batida mais lenta.

A história é tempo até o valor para times AWS-native. Solte um novo client SDK numa app existente, faça login com credenciais IAM que sua plataforma já emite, e a fatura é abatida contra o compromisso AWS que o time de finance negociou no trimestre passado. Sem novo contrato, sem novo cofre de chaves, sem nova fatura.

O posicionamento se escreve sozinho para lojas Claude-only na AWS: o mesmo conjunto de features da Anthropic da API direta, o mesmo Console, os mesmos lançamentos de modelo no mesmo dia, mas a superfície de procurement e auditoria que todo CFO e time de segurança já entende. A ressalva honesta no brief é a linha do perímetro de segurança, que compradores vão notar e devem ouvir de você antes de ouvirem de uma revisão de fornecedor.

Imagem de capa do Claude Platform on AWS

Visão Geral

Em 11 de maio de 2026, a AWS anunciou a disponibilidade geral do Claude Platform on AWS: acesso direto à experiência nativa da Claude Platform da Anthropic através de uma conta AWS existente. A AWS se posiciona como “o primeiro cloud provider a oferecer acesso à experiência nativa da Claude Platform.” Sem conta Anthropic separada, sem relação de billing separada, sem segundo cofre de chaves.

O enquadramento que importa: isto não é o Bedrock com uma nova camada de tinta. O Bedrock é operado pela AWS, com a AWS como único processador de dados. O Claude Platform on AWS é operado pela Anthropic. A AWS fornece a camada de autenticação (SigV4 ou API key), controle de acesso baseado em IAM, billing via AWS Marketplace, logging de auditoria do CloudTrail e PrivateLink. A Anthropic opera a inferência. A documentação da Anthropic coloca de forma direta: “Diferente do Amazon Bedrock, onde a AWS opera a stack de inferência, a Anthropic opera o Claude Platform on AWS.”

Essa distinção é o fato estrutural para tudo o que vem depois neste post. É por isso que CPoA tem Agent Skills, Files API, MCP connector, code execution, web search e web fetch, a tool Advisor, Managed Agents e pass-through de beta-headers.

Arquitetura

Três coisas se movem quando você chama Claude Platform on AWS, e elas vivem em três planos diferentes.

Sua conta AWS Sua app / SDK AnthropicAWS · boto3 chain SigV4 aws-external-anthropic gateway sts:GetWebIdentityToken Metering no Marketplace CloudTrail mgmt events por padrão, data events opt-in IAM workspace ARN aws-external-anthropic:* Perímetro de segurança AWS Plano operado pela Anthropic Claude Platform Messages API · /v1/messages Managed Agents Skills MCP Files Code Exec Web search Web fetch Advisor Prompt caching · Batch

inference_geo: us (1.1x) | global pool de capacidade separado do Bedrock + API direta

Claude Console federado via aws-external-anthropic:AssumeConsole indicador "Account managed by AWS"

O gateway do lado AWS é o que faz a história IAM funcionar. Quando seu SDK assina um request com SigV4 para o serviço aws-external-anthropic, o gateway chama sts:GetWebIdentityToken server-side, emite um JWT e encaminha o request para a Anthropic com aquele JWT como âncora de confiança. O AWS Marketplace contabiliza a chamada. A Anthropic processa a inferência. O CloudTrail registra o request do lado AWS. Os próprios inputs e outputs saem do perímetro AWS a caminho do plano da Anthropic.

Uma tabela rápida de onde cada preocupação vive:

PreocupaçãoVive do lado AWSVive do lado Anthropic
Identidade / authIAM, SigV4, STS, workspace ARNMirror de metadata do workspace
BillingAWS Marketplace, abatimento EDP, Cost Explorer(nenhum)
AuditoriaCloudTrail (management events por padrão, data events opt-in)Request IDs da Anthropic
Compute de inferência(nenhum)Runtime de managed agents, inferência do modelo
Residência de dadosRegião do workspace (apenas control plane)inference_geo: us (1.1x) ou global
Features(nenhuma)Messages API, Skills, MCP, Files, code exec, Console
Privacidade de redeAWS PrivateLink até o gatewayCaminho semipúblico depois do gateway

A divisão que surpreende as pessoas: a região AWS na qual seu workspace está vinculado controla onde o gateway vive e onde o escopo de auditoria e billing do lado AWS atua, não onde o modelo de fato roda.

⚠️ Perímetro de segurança, em português claro: O CloudTrail registra que um request aconteceu e quem fez. O prompt e o completion viajam para a infraestrutura operada pela Anthropic para processamento. Se sua postura de compliance exige que a AWS seja o único processador de dados, o caminho certo continua sendo o Amazon Bedrock. A própria documentação da Anthropic direciona workloads FedRAMP High, IL4-5 e HIPAA-ready de volta para o Bedrock.

Setup

Vou passar pelo caminho que espero que a maioria das pessoas vai realmente tomar: o SDK Python contra uma API key de curta duração para development, com notas sobre o caminho SigV4 de produção.

Pré-requisitos

  • Uma conta AWS com permissão para assinar no AWS Marketplace
  • Permissão IAM para habilitar outbound web identity federation na conta (one-time)
  • Um workspace Slack onde você consegue instalar apps e adicionar canais (você vai precisar de permissões de admin do workspace ou equivalente para a demo)
  • Python 3.10+ com o novo extra anthropic[aws] instalado

Passo 1: Habilitar outbound web identity federation

O gateway chama sts:GetWebIdentityToken server-side para emitir o JWT que ele encaminha para a Anthropic. Essa capacidade STS vem desabilitada por padrão em toda conta AWS, então habilite uma vez, one-time por conta AWS:

aws iam enable-outbound-web-identity-federation

Passo 2: Encontrar Claude Platform on AWS no Console e clicar em Get Started

Faça login no Console AWS e busque por Claude Platform on AWS na navegação superior. A página do serviço tem um único botão Get Started. Clique para iniciar o fluxo de assinatura do AWS Marketplace.

Página de landing do Claude Platform on AWS no Console AWS com o botão Get Started

Passo 3: Assinar e criar um workspace

O botão Get Started te leva para a assinatura do AWS Marketplace, e depois redireciona para platform.claude.com/partner-signup. O sign-up provisiona uma organização Anthropic novinha vinculada à conta AWS. Se sua empresa já tem uma organização Claude Enterprise, ela não é carregada para cá: API keys, workspaces e configurações do Console começam do zero.

A Anthropic envia um email para o contato da conta AWS para iniciar o setup da organização.

Email da Anthropic convidando o contato da conta AWS para configurar a nova organização

Uma tela de confirmação te coloca no fluxo de partner-signup.

Página de confirmação de email após clicar no link do convite

Nomeie a nova organização Anthropic. Ela é separada de qualquer organização Claude Enterprise existente.

Formulário de setup da organização Anthropic

Uma vez que a organização existe, crie seu primeiro workspace. Um por região AWS.

Tela de organização criada com o call to action para criar o workspace

O workspace ID se parece com wrkspc_01AbCdEf23GhIj e é seu ARN de recurso IAM:

arn:aws:aws-external-anthropic:us-east-1:123456789012:workspace/wrkspc_01AbCdEf23GhIj

Passo 4: Política IAM

A Anthropic publica quatro managed policies (AnthropicFullAccess, AnthropicReadOnlyAccess, AnthropicInferenceAccess, AnthropicLimitedAccess) e as actions vivem no namespace aws-external-anthropic:*. Uma política mínima só para inferência fica assim:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "aws-external-anthropic:CreateInference",
        "aws-external-anthropic:CountTokens",
        "aws-external-anthropic:GetModel",
        "aws-external-anthropic:ListModels"
      ],
      "Resource": "arn:aws:aws-external-anthropic:us-east-1:123456789012:workspace/wrkspc_01AbCdEf23GhIj"
    }
  ]
}

Passo 5: Instalar o SDK e configurar variáveis de ambiente

O client Python chegou no dia do GA na anthropic v0.101.0:

pip install "anthropic[aws]>=0.101.0"

O client TypeScript é um package separado, @anthropic-ai/aws-sdk v0.3.0. Os dois estão marcados como beta na documentação.

Para exploração, crie uma API key no Claude Console (acessado pelo Console AWS) e configure:

export ANTHROPIC_AWS_API_KEY=aws-external-anthropic-api-key-...
export ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_01AbCdEf23GhIj
export AWS_REGION=us-east-1

A Demo: um agent de reserva de padel no Slack

Eu vinha gastando tempo demais organizando partidas de padel e checando disponibilidade no site do meu clube. A interface web não é grande coisa. Em um fim de semana recente eu construí uma Skill para isso, e o Claude Code virou o lugar de onde eu checava disponibilidade. Claude Platform on AWS chega num bom momento: dá para portar a Skill, plugar no Slack e usar pelo celular.

Já que estava nisso, eu queria colocar cada primitivo do CPoA no palco em uma demo só: Managed Agents Sessions, Skills, Environments, code execution, MCP connector e o credential vault. O cenário: um bot Slack que responde perguntas sobre quadras de padel no #padel-bot chamando a API (não oficial) de booking do meu clube. O clube roda em Gladstone, uma plataforma de centro de lazer com portal do membro mas sem API pública.

Construí em duas passadas:

  • Cenário A: a forma puramente CPoA, uma session Managed Agents long-lived que faz polling do Slack via MCP.
  • Cenário B: troca o polling pelo Slack Events API batendo numa pequena Lambda que encaminha para a mesma session. UX sub-segundo, mesmo agent, mesma Skill, mesmo vault.

Cenário A: uma session long-lived que faz polling do Slack via MCP

O fluxo de dados:

Slack #padel-bot canal C01AB23CD45 Servidor Slack MCP mcp.slack.com/mcp OAuth renovado pelo vault API Slack (read / post) tick user.message (sessions.events.send / EventBridge) Session Managed Agents (long-lived, agent v3) Vault Slack mcp_oauth + bloco de refresh Skill: pslc-padel SKILL.md + scripts/ CLIs read-only Environment pip: requests, cryptography net limited: gladstonego.cloud MCP connector toolset slack política always_allow Sandbox de code execution (bash → python3 scripts/…) read history / chat.postMessage <club>.gladstonego.cloud HTTPS

A session é acordada num heartbeat (user.message com texto tick). Em cada tick o agent lê o histórico recente do #padel-bot via Slack MCP, encontra a mensagem humana mais nova sem uma resposta do bot depois, roda o script de Skill certo em bash e responde via Slack MCP.

1. Criar o vault, adicionar a credencial do Slack

from anthropic import AnthropicAWS
c = AnthropicAWS(aws_region='us-east-1', workspace_id='wrkspc_…')
v = c.beta.vaults.create(display_name='pslc-padel-bot')
# v.id -> vlt_01AbCdEf23GhIj

A credencial vai pelo Claude Console: escolha Slack na lista de servidores MCP e percorra o fluxo OAuth. O vault armazena com um bloco de refresh, então a Anthropic renova automaticamente o access token na expiração. É isso que faz um vault ser mais do que um simples cofre de secrets.

Tela de consentimento OAuth do Slack aceitando a integração com a Claude

Slack confirma a instalação no workspace

Credencial criada no vault no Claude Console com metadata mcp_oauth + refresh

Leia de volta para confirmar (sem vazamento de token; os campos de secret são write-only por design):

for cred in c.beta.vaults.credentials.list(vault_id=v.id).data:
    print(cred.id, cred.display_name, cred.auth.type, cred.auth.mcp_server_url)
# vcrd_…  padel-bot-mcp  mcp_oauth  https://mcp.slack.com/mcp

Tem uma pegadinha: esse vault guarda credenciais de servidores MCP, indexadas por mcp_server_url. Não é um cofre de secrets genérico. O usuário/senha do Gladstone que vamos precisar a seguir não pode viver aqui.

2. Fazer upload da Skill

skills.create recebe uma lista de tuplas (path, bytes, mime). Todos os arquivos têm que compartilhar um diretório top-level com SKILL.md na raiz.

from pathlib import Path
skill_dir = Path('<repo>/.claude/skills/pslc-padel')
top = 'pslc-padel'
rels = [
    'SKILL.md',
    'scripts/pslc_client.py',
    'scripts/check_availability.py',
    'scripts/list_bookings.py',
]
files = [(f'{top}/{r}', (skill_dir / r).read_bytes(),
          'text/plain' if r.endswith('.md') else 'text/x-python')
         for r in rels]
skill = c.beta.skills.create(display_title='pslc-padel', files=files)
# skill.id -> skill_01AbCdEf23GhIj

3. Fazer upload das credenciais do Gladstone como arquivo

import io
content = (Path.home() / '.config' / 'pslc-padel' / '.env').read_bytes()  # pslc_user= … / pslc_password= …
f = c.beta.files.upload(file=('pslc-padel.env', io.BytesIO(content), 'application/x-envfile'))
# f.id -> file_01AbCdEf23GhIj

Aqui é onde o escopo estreito do vault aparece. BetaCloudConfigParams não expõe um campo env hoje, e o vault tem escopo para credenciais de servidor MCP, então o caminho mais direto para secrets não-MCP é a Files API mais um mount via sessions.resources.add.

4. Criar o Environment

env = c.beta.environments.create(
    name='pslc-padel-env',
    description='Sandbox for the pslc-padel Skill.',
    config={
        'type': 'cloud',
        'packages': {
            'type': 'packages',
            'pip': ['requests', 'cryptography'],
        },
        'networking': {
            'type': 'limited',
            'allowed_hosts': ['<club>.gladstonego.cloud'],
            'allow_mcp_servers': True,      # opens egress to attached MCP servers
            'allow_package_managers': True, # lets pip install at session start
        },
    },
)
# env.id -> env_01AbCdEf23GhIj

O modo de rede limited é a postura de produção. Se a chamada anterior do post sobre “fora do perímetro de segurança AWS” vai bater como verdade, o Environment não pode ser unrestricted. A allowlist é exatamente o que a Skill e o Slack MCP precisam, nada mais.

5. Criar o Agent

SYSTEM = """You are PadelBot, a Slack-based read-only assistant for a padel club I'm a member of padel courts.

Your home channel is `#padel-bot`. Treat every human message there as addressed to you.

Tools:
- Skill pslc-padel: scripts/check_availability.py --date YYYY-MM-DD --duration {30|60|90}
                    scripts/list_bookings.py [--history]
- MCP server slack: read history, post replies.

First-run bootstrap (idempotent): copy /mnt/session/uploads/root/.config/pslc-padel/.env
to ~/.config/pslc-padel/.env before running any Skill script.

Working loop on each tick:
1. Read recent #padel-bot history via Slack MCP.
2. If newest human message has no bot reply after it, answer it.
3. Run the right Skill, post a tight one- or two-line reply, stop.

Read-only only. Booking is out of scope. Never reveal credentials."""

agent = c.beta.agents.create(
    model='claude-sonnet-4-6',
    name='padel-slack-agent',
    description='Read-only padel agent driven by Slack.',
    system=SYSTEM,
    skills=[{'type': 'custom', 'skill_id': skill.id}],
    mcp_servers=[{'type': 'url', 'name': 'slack', 'url': 'https://mcp.slack.com/mcp'}],
    tools=[
        {'type': 'agent_toolset_20260401',
         'default_config': {'enabled': True, 'permission_policy': {'type': 'always_allow'}}},
        {'type': 'mcp_toolset', 'mcp_server_name': 'slack',
         'default_config': {'enabled': True, 'permission_policy': {'type': 'always_allow'}}},
    ],
)
# agent.id -> agent_01AbCdEf23GhIj

Duas coisas que vale apontar:

  • A tool “bash” do agent não é uma tool code_execution: é o bundle agent_toolset_20260401 (bash + read/write/edit/glob/grep + web_fetch/web_search) entregue pelo SDK. Esse bundle é o que dispara para o sandbox do Environment para rodar os scripts Python da Skill.
  • A permission policy padrão para mcp_toolset é always_ask, que faria um bot autônomo travar esperando aprovação humana em cada chamada Slack. Para este agent eu virei para always_allow. Para workflows sensíveis onde você prefere manter humano no loop, o inverso é a postura mais cuidadosa: deixar em always_ask e plugar o webhook session.requires_action da Anthropic numa UX de aprovação.

6. Criar a Session, montar as credenciais

session = c.beta.sessions.create(
    agent='agent_01AbCdEf23GhIj',
    environment_id=env.id,
    vault_ids=[v.id],
    title='Padel Slack bot — long-lived session',
    resources=[{
        'type': 'file',
        'file_id': f.id,
        'mount_path': '/root/.config/pslc-padel/.env',
    }],
)
# session.id -> sesn_01AbCdEf23GhIj

O runtime do agent no GA montou os recursos sob /mnt/session/uploads/..., então o meu arquivo aterrissou em /mnt/session/uploads/root/.config/pslc-padel/.env em vez do path que pedi. O _find_env da Skill lê ~/.config/pslc-padel/.env; o bootstrap de primeira execução do system prompt copia entre os dois no primeiro tick. Vale saber porque é o tipo de coisa que falha silenciosamente como “Login rejected” em vez de “file not found.”

7. Disparar o tick

c.beta.sessions.events.send(
    session_id=session.id,
    events=[{'type': 'user.message', 'content': [{'type': 'text', 'text': 'tick'}]}],
)

Postei list my upcoming padel bookings no #padel-bot e enviei um tick. O agent percorreu o loop (Slack MCP read_messages, bootstrap, scripts/list_bookings.py via bash, Slack MCP chat.postMessage) e respondeu com minhas quatro reservas futuras como uma tabela markdown.

Thread no Slack: humano pede bookings, agent responde com tabela markdown de 4 reservas futuras

Uma segunda troca para provar o caminho de disponibilidade:

Thread no Slack: humano pede slots de domingo 19h, agent responde "todas as 5 quadras livres a €24"

O que eu tirei do Cenário A: a ergonomia de IAM/SDK é real, code execution + Skills + MCP + vault realmente compõem, e o único atrito foi coisa operacional pequena (o prefixo do path do resource mount, o default always_ask, o 403 da primeira chamada do Gladstone que reexecutou limpo). O polling é o defeito óbvio: para ficar autônomo você adicionaria um schedule no EventBridge disparando um tick a cada 4 minutos, o que funciona, mas significa pagar CCUs para acordar e não encontrar nada em 99% dos ticks.

Cenário B: trocar polling pelo Slack Events API

A mudança arquitetural é pequena mas a mudança de UX é grande. O Slack empurra envelopes event_callback no momento em que um humano digita no #padel-bot. Uma Lambda pequena valida o signing secret do Slack, parseia o envelope, e encaminha o texto para a mesma session Managed Agents como uma user.message. O code path do working loop do agent não muda (ele ainda lê o histórico recente e responde via Slack MCP), mas o wake-up é em tempo real e não tem gasto de CCU ocioso.

Slack #padel-bot canal C01AB23CD45 Sua conta AWS API Gateway HTTP API (público) SCP-friendly Lambda valida signature do Slack encaminha como user.message POST Plano Anthropic Session Managed Agents inalterada do Cenário A Skill · Environment · Vault sandbox de code execution agent.bash → scripts pslc-padel sessions.events.send Servidor Slack MCP mcp.slack.com/mcp OAuth via credencial do vault chat.postMessage reply aterrissa no #padel-bot

B.1 Um app Slack que você possui

O OAuth Slack do Cenário A foi para o app MCP pré-registrado da Anthropic. Conveniente, mas você não pode adicionar Events API num app que você não possui. Então eu criei um app Slack separado (padel-bot-events) a partir de um manifest. Em https://api.slack.com/apps escolha Create New App → From an app manifest, escolha o workspace e cole:

display_information:
  name: padel-bot-events
  description: Slack Events API bridge for the padel-bot Managed Agent on Claude Platform on AWS.
  background_color: "#1f3a59"

features:
  bot_user:
    display_name: padel-bot-events
    always_online: true

oauth_config:
  scopes:
    bot:
      - channels:history
      - channels:read
      - chat:write
      - users:read

settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false

Sem bloco event_subscriptions no manifest, porque o Slack rejeita manifests que declaram events sem um request_url ou Socket Mode, e a gente não vai ter a URL até a Lambda ter sido deployada. Events API é adicionada pela UI depois do deploy.

Fluxo Create New App do Slack mostrando a opção From an app manifest

Confirme a tela de revisão e Create.

Tela de revisão de confirmação do Slack para o manifest do padel-bot-events

Depois Install to Workspace, aprove os scopes, e /invite @padel-bot-events no #padel-bot para que o novo bot consiga ver o histórico. (O bot não posta; as respostas continuam indo pela credencial original do vault.) Por fim, copie o Signing Secret da página Basic Information do app; a Lambda vai usar para validar cada evento.

B.2 Handler da Lambda

O handler é curto. As partes interessantes:

import hashlib, hmac, json, os, time
from anthropic import AnthropicAWS

SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"].encode()
SESSION_ID     = os.environ["CPOA_SESSION_ID"]
WORKSPACE_ID   = os.environ["CPOA_WORKSPACE_ID"]
TARGET_CHANNEL = os.environ["SLACK_CHANNEL_ID"]

_client = AnthropicAWS(aws_region=os.environ["AWS_REGION"], workspace_id=WORKSPACE_ID)

def _verify(headers, body):
    ts  = headers.get("x-slack-request-timestamp")
    sig = headers.get("x-slack-signature")
    if not ts or not sig or abs(time.time() - int(ts)) > 300:
        return False
    digest = "v0=" + hmac.new(SIGNING_SECRET, f"v0:{ts}:{body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(digest, sig)

def handler(event, _ctx):
    headers = {(k or "").lower(): v for k, v in (event.get("headers") or {}).items()}
    body    = event.get("body") or ""
    if not _verify(headers, body):
        return {"statusCode": 401, "body": "bad signature"}

    payload = json.loads(body)
    if payload.get("type") == "url_verification":
        return {"statusCode": 200, "body": json.dumps({"challenge": payload["challenge"]})}

    if payload.get("type") == "event_callback":
        ev = payload.get("event") or {}
        if (ev.get("type") == "message"
                and not ev.get("subtype") and not ev.get("bot_id")
                and ev.get("channel") == TARGET_CHANNEL
                and (ev.get("text") or "").strip()):
            _client.beta.sessions.events.send(
                session_id=SESSION_ID,
                events=[{
                    "type": "user.message",
                    "content": [{
                        "type": "text",
                        "text": f"slack-event channel={ev['channel']} user={ev.get('user','')}: {ev['text']}",
                    }],
                }],
            )
        return {"statusCode": 200, "body": "ok"}

    return {"statusCode": 200, "body": "ignored"}

O Slack espera 2xx em 3 segundos ou retenta. O agent é assíncrono, então não esperamos por ele. O texto encaminhado é formatado como slack-event channel=… user=… : <text> para que o working loop do system prompt leia como mais uma mensagem humana na session.

B.3 Empacotamento: a pegadinha do manylinux

anthropic[aws] puxa pydantic_core e cryptography, os dois com extensões C compiladas. Build a partir do macOS sem especificar a plataforma e a Lambda falha silenciosamente no import com No module named 'pydantic_core._pydantic_core'. A solução:

pip install --target build \
    --platform manylinux2014_x86_64 \
    --only-binary=:all: \
    --python-version 3.11 \
    "anthropic[aws]>=0.101.0"
find build -type d -name "__pycache__" -exec rm -rf {} +
find build -name "*.dist-info" -prune -exec rm -rf {} +
( cd build && zip -qr ../dist.zip . )

Resultado: zip de ~20 MB, bem abaixo do limite de 50 MB para upload direto da Lambda.

B.4 Role IAM para a Lambda

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CpoaSessionWrite",
      "Effect": "Allow",
      "Action": [
        "aws-external-anthropic:UpdateSession",
        "aws-external-anthropic:GetWorkspace"
      ],
      "Resource": "arn:aws:aws-external-anthropic:us-east-1:123456789012:workspace/wrkspc_…"
    },
    {
      "Sid": "CpoaTokenVending",
      "Effect": "Allow",
      "Action": [
        "sts:GetWebIdentityToken",
        "sts:TagGetWebIdentityToken"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudWatchLogs",
      "Effect": "Allow",
      "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

B.5 Endpoint público via API Gateway HTTP API

O Slack precisa de uma URL HTTPS pública para fazer POST de eventos. Coloque a Lambda atrás de um API Gateway HTTP API. Config mínima, billing por milhão de requests e mais rápido que REST API para esse tipo de pass-through:

API_ID=$(aws apigatewayv2 create-api \
  --name padel-bot-http-api \
  --protocol-type HTTP \
  --target arn:aws:lambda:us-east-1:123456789012:function:padel-bot-slack-bridge \
  --region us-east-1 --profile $PROFILE \
  --query 'ApiId' --output text)

aws lambda add-permission \
  --function-name padel-bot-slack-bridge \
  --statement-id apigw-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:$API_ID/*" \
  --region us-east-1 --profile $PROFILE

B.6 Plugar o Slack Events API

No app Slack: Event Subscriptions → enable → cole o endpoint do API Gateway como Request URL. O Slack faz POST de url_verification para ele com uma signature válida; o handler ecoa o challenge; a URL vira Verified ✓. Adicione message.channels em Subscribe to bot events, salve, e o Slack vai pedir para você reinstalar o app.

Testando a integração

Com o app reinstalado e a Lambda bridge escutando, abri o canal Slack e digitei:

show me my next bookings

Troca em tempo real no Slack: humano digita, agent responde em segundos via Lambda bridge

O agent pegou a mensagem em poucos segundos, chamou a skill MCP de padel e postou as próximas reservas de quadra direto no canal. Sem slash command, sem precisar mencionar o bot, só linguagem natural no canal. O screenshot acima captura essa troca em tempo real.

O que eu refinaria antes de produção

  • Latência de reply: o agent relê o histórico do Slack a cada tick. No Cenário B o texto do usuário já está na user.message encaminhada, então um pequeno ajuste no system prompt (“se a user.message começa com slack-event, trate o texto como autoritativo; pule o fetch do histórico”) cortaria um segundo.
  • Cold starts: o Slack espera 200 em 3 s. Um init de Lambda em cold pode chegar perto desse limite. Provisioned Concurrency = 1 torna isso determinístico.
  • Idempotência: o Slack retenta em qualquer não-2xx e na própria política de retry dele. Com cold starts isso pode significar duas entregas do mesmo client_msg_id, o que dispararia o agent duas vezes. Uma tabela DynamoDB de dedup com TTL de 5 minutos indexada por client_msg_id fecha o buraco.
  • Permission policy: configurei mcp_toolset para always_allow para o bot ser autônomo. Para um agent com MCPs destrutivos, o default certo é always_ask + um webhook (session.requires_action) → uma UX human-in-the-loop. Vale uma sidebar em designs de produção.
  • Versionamento de Skill: subi o subset read-only do pslc-padel como source: custom. Updates vão por skills.versions.create. O agent fixa por skill_id e version faz default para latest, o que tudo bem para demos de um dev, mas um deploy de produção deveria fixar uma versão específica e rolar via versões do agent.

Veredicto

Esta é uma leitura mão na massa depois de subir o agent de padel ponta a ponta (OAuth Slack, upload de Skill, Environment, Agent, Session, Lambda bridge, tudo). Não é escala de produção, mas cada primitivo da página foi de fato exercitado sob a carga que você veria de um canal de bate-papo informal.

Como ele se compara

Claude Platform on AWSAmazon Bedrock (Claude)Direto (api.anthropic.com)
Operador da inferênciaAnthropicAWSAnthropic
Superfície de APIAnthropic Messages API /v1/messagesBedrock Messages ou InvokeModelAnthropic Messages API
AuthSigV4 ou API keySigV4 (+ bearer em C#/Go/Java)API key ou WIF
BillingAWS MarketplaceNativo AWSDireto com a Anthropic
Processador de dados de inferênciaAnthropicAWSAnthropic
Agent Skills, Files API, MCP connectorSim (beta)NãoSim (beta)
Sandbox de code executionSimNãoSim
Pass-through de beta headerSimNãoSim
Lançamentos de modelo no mesmo diaSimNãoSim
Vínculo workspace + regiãoUm workspace por regiãon/an/a
FedRAMP High / IL4-5 / HIPAA-readyNãoSimLimitado via BAA
Zero Data RetentionOpt-inImplícitoOpt-in

A fonte da verdade para essa matriz é o Features overview da Anthropic; a semântica do perímetro está descrita na página do CPoA.

Quem deveria usar

  • Times Claude-only na AWS que querem cada feature da Anthropic no primeiro dia com billing AWS consolidado. É a vitória óbvia.
  • Clientes da API direta da Anthropic com um compromisso AWS a abater. Mudar para cá te dá IAM, CloudTrail e abatimento de compromisso AWS sem abrir mão do conjunto de features que você já usa.
  • Times rodando um failover three-way entre direto, AWS e Bedrock. O pool de capacidade independente é a única razão pela qual isso funciona.

Quem não deveria usar

  • Workloads regulados que precisam de FedRAMP High, IL4-5 ou HIPAA-ready. Fique no Bedrock; a própria Anthropic diz isso.
  • Lojas multi-model que querem Llama, Mistral ou Titan ao lado da Claude. O Bedrock mantém essa história.
  • Incumbentes do Bedrock já satisfeitos com Guardrails, Knowledge Bases e PrivateLink para o serviço Bedrock. Não tem razão forte para migrar hoje.

O que eu ficaria de olho a seguir

  • Números reais de latência de terceiros para CPoA versus Bedrock versus direto em workloads idênticos. Ainda não existem.
  • Uma declaração pública clara sobre o caminho de rede entre o gateway AWS e a inferência da Anthropic. PrivateLink aterrissa no gateway; o que acontece depois disso não está documentado.
  • Como a Anthropic lida com a assimetria onde a API direta oferece BAAs mas o CPoA explicitamente não oferece.

Comments