ποΈ Arquitetura AWS β API Gateway β Router β AgentCore
O sample expΓ΅e um ΓΊnico endpoint HTTPS via API Gateway que recebe webhooks de Telegram, Slack, Discord e Feishu. O Lambda router normaliza o payload e invoca o AgentCore Runtime, onde o main.py do Hermes roda dentro de uma microVM Firecracker dedicada.
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Telegram β β Slack β β Discord β β Feishu β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β webhook β events β interactions β event
βββββββββββββ¬βββββββ΄βββββββββββ¬ββββββββ΄βββββββββββ¬ββββββββ
βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Amazon API Gateway (HTTP API) β
β /telegram /slack /discord /feishu β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β POST + signature
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Lambda Router (Node 20 / Python) β
β - verify signature - normalize payload β
β - load S3 state - invoke AgentCore β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β InvokeAgentRuntime
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Bedrock AgentCore Runtime β
β ββββββββββββββββββββββββββββββββββββββββ β
β β Firecracker microVM (per session) β β
β β βββΊ main.py (Hermes Agent) β β
β β βββΊ tools (shell/py) β β
β β βββΊ Bedrock LLM API β β
β ββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β persist state
βΌ
βββββββββββββββββββ
β S3 bucket β
β sessions/... β
βββββββββββββββββββ
π‘ Por que essa arquitetura
- β’Um endpoint, N canais: adicionar WhatsApp = sΓ³ implementar adapter no router.
- β’Pagamento por uso real: Lambda + AgentCore sΓ³ cobram quando hΓ‘ mensagem.
- β’IAM nativo: roles separadas por componente β router NΓO acessa o bucket todo.
- β’Observabilidade unificada: tudo em CloudWatch com trace ID cruzando os 3 serviΓ§os.
π₯ Firecracker microVMs β 1 VM por sessΓ£o
O AgentCore Runtime usa o mesmo Firecracker que roda Lambda e Fargate por trΓ‘s β mas o expΓ΅e como ambiente persistente por sessΓ£o. Cada usuΓ‘rio ganha uma microVM dedicada com isolamento de hypervisor (KVM), cold-start em ~125ms e snapshot/restore nativo.
β Modelo correto
- β1 microVM por sessΓ£o de usuΓ‘rio
- βSnapshot ao final, restore na prΓ³xima msg
- βIdle TTL configurΓ‘vel (default 15min)
- βLimite hard de CPU/MEM por VM
β Anti-padrΓ΅es
- β1 VM compartilhada entre usuΓ‘rios (vaza estado)
- βManter VM viva 24/7 β perde a vantagem de custo
- βPersistir creds dentro da VM (fica no snapshot)
- βVM sem limite β bug de loop come orΓ§amento
π‘ Dica
Use o sample como referΓͺncia de mapeamento sessΓ£oβVM, nΓ£o copie idle TTL Γ s cegas. Para chatbot conversacional, 15min Γ© alto; para agente de longa execuΓ§Γ£o (CI bot), 60min faz sentido. Ajuste por canal.
π¦ CDK em 4 fases β como o sample organiza
O sample divide a infra em 4 stacks CDK independentes. Cada um pode ser destruΓdo e recriado sem tocar nos outros β vocΓͺ itera nas integraΓ§Γ΅es sem refazer VPC.
NetworkStack
VPC, subnets privadas, NAT, VPC endpoints para Bedrock e S3.
Exporta vpcId e privateSubnetIds para os stacks seguintes.
IamStack
Roles separadas: RouterLambdaRole, AgentCoreExecutionRole, StateBucketReadWriteRole.
PrincΓpio de menor privilΓ©gio β router nΓ£o pode invocar Bedrock, sΓ³ AgentCore pode.
AgentCoreStack
Recurso AWS::BedrockAgentCore::Runtime com imagem do Hermes, S3 bucket de estado, KMS key.
Aqui mora o main.py empacotado β versione com tag git.
IntegrationsStack
API Gateway HTTP API + Lambda router + Secrets Manager (tokens Telegram/Slack/Discord/Feishu).
Γ o stack que vocΓͺ mexe ao adicionar canal novo β sem tocar os outros 3.
# Snippet CDK (TypeScript) β AgentCoreStack simplificado
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
import { CfnRuntime } from 'aws-cdk-lib/aws-bedrockagentcore';
export class AgentCoreStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const stateBucket = new Bucket(this, 'HermesState', {
encryption: BucketEncryption.KMS_MANAGED,
versioned: true,
lifecycleRules: [{ expiration: Duration.days(90) }],
});
new CfnRuntime(this, 'HermesRuntime', {
runtimeName: 'hermes-agent-prod',
containerImage: `${ecrRepo.repositoryUri}:${imageTag}`,
executionRoleArn: agentRole.roleArn,
idleTimeoutSeconds: 900,
environmentVariables: {
HERMES_STATE_BUCKET: stateBucket.bucketName,
BEDROCK_MODEL_ID: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
},
});
}
}
# deploy
$ npm ci && npm run build
$ npx cdk deploy --all --require-approval never
πΎ Estado em S3 β persistΓͺncia entre invocaΓ§Γ΅es
Como microVMs sΓ£o efΓͺmeras por design, o sample persiste tudo que precisa sobreviver entre mensagens em um bucket S3 particionado por user_id/session_id: histΓ³rico de conversa, memΓ³rias de longo prazo, arquivos gerados pelo agente.
# layout do bucket
hermes-state-prod/
βββ sessions/
β βββ telegram/
β βββ user_123456/
β βββ session_abc/
β βββ history.jsonl # mensagens
β βββ memory.json # facts persistentes
β βββ workspace/ # arquivos do agente
β βββ output.csv
β βββ analysis.png
βββ snapshots/
βββ user_123456/
βββ vm_state.bin # snapshot Firecracker
π PadrΓ£o de persistΓͺncia
- history.jsonl β append-only, lido inteiro no inΓcio da sessΓ£o
- memory.json β sobrescrito a cada turno, contΓ©m facts extraΓdos
- workspace/ β sincronizado bidirecionalmente (download inΓcio, upload fim)
- Lifecycle: sessions/* β IA apΓ³s 30d, Glacier apΓ³s 90d, expira em 365d
- KMS: bucket key encryption ativo (1 chamada KMS por sessΓ£o, nΓ£o por objeto)
πͺ Lambda Router β multi-canal serverless
O router Γ© a peΓ§a mais reutilizΓ‘vel do sample: uma Lambda Node.js que recebe webhooks dos 4 canais, valida assinatura, normaliza para um schema interno e invoca o AgentCore. Adicionar WhatsApp = ~80 linhas no router, zero mudanΓ§a no resto.
// router.ts (estrutura simplificada do sample)
import { verifyTelegram, verifySlack, verifyDiscord, verifyFeishu } from './adapters';
import { invokeAgentRuntime } from './agentcore';
export async function handler(event: APIGatewayProxyEventV2) {
const channel = event.rawPath.replace('/', ''); // telegram | slack | ...
const adapter = adapters[channel];
// 1. verifica assinatura (HMAC ou OAuth)
if (!adapter.verify(event)) return { statusCode: 401 };
// 2. normaliza payload
const msg = adapter.normalize(event);
// { channel, userId, sessionId, text, attachments }
// 3. invoca AgentCore (async se demorado)
const result = await invokeAgentRuntime({
sessionId: msg.sessionId,
input: msg.text,
metadata: { channel: msg.channel, userId: msg.userId },
});
// 4. responde de volta no canal
return adapter.reply(msg, result.output);
}
π‘ Dica de produΓ§Γ£o
Mensagens longas (treino de modelo, scraping pesado) NΓO devem responder sΓncrono no webhook. Use InvokeAsync + DLQ + responda "processando..." em <3s para evitar timeout do Telegram/Slack (que retentam apΓ³s 5-10s).
π° Custos AWS β estimativa real para 1k usuΓ‘rios/mΓͺs
Premissa de cΓ‘lculo: 1.000 usuΓ‘rios ativos mensais, 30 msgs/usuΓ‘rio/mΓͺs = 30k invocaΓ§Γ΅es, sessΓ£o mΓ©dia de 90s, regiΓ£o us-east-1, modelo Claude 3.5 Sonnet via Bedrock.
π Breakdown de custo mensal estimado
| Componente | Volume | PreΓ§o | Total |
|---|---|---|---|
| API Gateway HTTP API | 30k req | $1.00/M | $0.03 |
| Lambda Router | 30k Γ 200ms Γ 256MB | $0.20/M + GB-s | $0.18 |
| AgentCore Runtime | 30k Γ 90s Γ 1vCPU/2GB | ~$0.0001/s | $270.00 |
| Bedrock Claude 3.5 Sonnet | ~600M input + 60M output | $3 / $15 per M | $2.700,00 |
| S3 storage + requests | ~50GB + 200k req | $0.023/GB + req | $1.80 |
| CloudWatch logs | ~30GB ingestion | $0.50/GB | $15.00 |
| KMS | 30k requests | $0.03/10k | $0.09 |
| TOTAL estimado | ~$2.987 / mΓͺs | ||
O custo Γ© dominado pelo LLM (~90%). AgentCore Runtime Γ© <10% β otimize prompts antes de mexer em infra.
β οΈ Quotas AWS que vΓ£o te morder
- β’ Bedrock TPM: limite default de tokens/min Γ© apertado β peΓ§a aumento ANTES de lanΓ§ar
- β’ AgentCore concurrent sessions: default ~100; precisa raise para multi-tenant sΓ©rio
- β’ Lambda concurrent executions: 1000 por regiΓ£o por default
- β’ API Gateway burst: 5000 RPS β webhook de canal viral satura sem rate limit
π‘ Dica β teste com LocalStack antes
Antes de queimar dΓ³lares na AWS real, suba o stack inteiro localmente com LocalStack:
$ docker run -d -p 4566:4566 localstack/localstack
$ export AWS_ENDPOINT_URL=http://localhost:4566
$ npx cdk bootstrap --profile localstack
$ npx cdk deploy --all --profile localstack
LocalStack Pro emula AgentCore e Bedrock; a versΓ£o Community emula sΓ³ Lambda/S3/API GW β jΓ‘ dΓ‘ pra testar o router e o S3 layout.
β Resumo do MΓ³dulo
PrΓ³ximo mΓ³dulo:
5.3 β π‘ Observabilidade em produΓ§Γ£o: OpenTelemetry, Grafana, SLOs e alertas para enxergar dentro do Hermes em prod.