Step Functions vira ferramenta de agentes de IA no Bedrock AgentCore Gateway
A AWS lançou no fim de outubro de 2026 um conector que expõe máquinas de estado do Step Functions como ferramentas MCP dentro do Amazon Bedrock AgentCore Gateway. Guia prático, com Terraform, IAM em três camadas e a conta de quanto isso custa por execução.
Por Danilo Barros11 min de leitura
Um agente de IA é bom em decidir quando chamar algo e o que passar como entrada. É péssimo em garantir que um procedimento de várias etapas, com regra de negócio e trilha de auditoria, rode sempre na mesma ordem. Um artigo publicado em 10 de novembro de 2026 no AWS in Plain English, assinado por Shivanshu Sharma, descreve exatamente esse problema e a solução que a AWS liberou no fim de outubro de 2026: um conector nativo no Amazon Bedrock AgentCore Gateway que transforma máquinas de estado do Step Functions em ferramentas do Model Context Protocol (MCP), sem Lambda de ponte e sem servidor MCP caseiro.
O processo falhou, porque o processo morava num prompt, não numa infraestrutura.
The process failed, because the process lived in a prompt and not in infrastructure.Shivanshu Sharma, autor no AWS in Plain English
Para quem já opera Step Functions em produção e também mexe com API Gateway no dia a dia, essa é a diferença entre um agente que sugere uma ação e um agente que aciona um workflow que você desenhou, testou e sabe que não pula etapa. Este guia reproduz o runbook da fonte, com o contexto que falta para rodar isso com segurança numa conta real.
O que você vai construir
O exemplo da fonte é um fluxo de reembolso automático, bom o bastante para testar o padrão de ponta a ponta:
- Uma máquina de estado Standard chamada
RefundWorkflow, com um estadoChoiceque só libera crédito automático seorderIdeamountestiverem presentes eamountfor menor ou igual a 500; - Um
AgentCore Gatewaycom um target do tipo conector (connectorId: states), expondo quatro operações como ferramentas:StartExecution,DescribeStateMachine,DescribeExecutioneGetExecutionHistory; - Três fronteiras de IAM independentes: a role de execução da máquina de estado, a role de serviço do gateway, e a permissão do agente para chamar o gateway;
- Observabilidade via execution history nativo do Step Functions, CloudWatch Logs, X-Ray e atribuição no CloudTrail.
O gateway prefixa cada ferramenta com o nome do target, separado por três underscores. Um target chamado refunds gera refunds___StartExecution. O conector tem dez operações disponíveis; este guia usa só quatro e deixa de fora, de propósito, StopExecution, ListExecutions e as operações de task token usadas em callbacks human-in-the-loop.
Pré-requisitos
- Conta AWS com permissão para criar roles IAM, máquinas de estado do Step Functions e gateways do AgentCore;
- OpenTofu ou Terraform 1.6 ou superior;
- AWS CLI v2 configurado com credenciais válidas;
awscurl(pip install awscurl) para assinar requisições SigV4 direto no terminal;jqpara extrair campos do JSON de resposta;- A identidade que vai rodar os testes precisa de
bedrock-agentcore:InvokeGatewayno ARN do gateway.
A fonte roda tudo em us-east-1. Antes de levar isso para produção com a Região São Paulo (sa-east-1), confirme na página de Regions do AgentCore Gateway se o serviço e o conector states já estão disponíveis ali: a fonte não cobre esse ponto e não dá para supor disponibilidade regional.
Passo a passo
1. Provisionar a máquina de estado e as duas primeiras roles com OpenTofu
O código abaixo cria o RefundWorkflow, a role de execução do Step Functions e a role de serviço do gateway, cada uma com condições de aws:SourceAccount e aws:SourceArn na trust policy para que só o recurso certo possa assumi-la.
provider "aws" {
region = var.region
}
variable "region" {
type = string
default = "us-east-1"
}
variable "gateway_name" {
default = "refund-gateway"
}
data "aws_caller_identity" "current" {}
locals {
account_id = data.aws_caller_identity.current.account_id
sm_name = "RefundWorkflow"
sm_arn = "arn:aws:states:${var.region}:${local.account_id}:stateMachine:${local.sm_name}"
gateway_glob = "arn:aws:bedrock-agentcore:${var.region}:${local.account_id}:gateway/${var.gateway_name}-*"
}
resource "aws_iam_role" "sfn_exec" {
name = "RefundWorkflowExecutionRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "states.amazonaws.com" }
Action = "sts:AssumeRole"
Condition = {
StringEquals = { "aws:SourceAccount" = local.account_id }
ArnLike = { "aws:SourceArn" = local.sm_arn }
}
}]
})
}
resource "aws_sfn_state_machine" "refund" {
name = local.sm_name
type = "STANDARD"
role_arn = aws_iam_role.sfn_exec.arn
definition = jsonencode({
Comment = "Reembolsos automáticos até um limite fixo. O agente não pode pular nem reordenar estas etapas."
StartAt = "ValidateRequest"
States = {
ValidateRequest = {
Type = "Choice"
Choices = [{
And = [
{ Variable = "$.orderId", IsPresent = true },
{ Variable = "$.amount", IsPresent = true },
{ Variable = "$.amount", NumericLessThanEquals = 500 }
]
Next = "IssueCredit"
}]
Default = "RejectRefund"
}
IssueCredit = {
Type = "Pass"
Parameters = {
"orderId.$" = "$.orderId"
"creditAmount.$" = "$.amount"
status = "CREDITED"
}
End = true
}
RejectRefund = {
Type = "Fail"
Error = "RefundRejected"
Cause = "Campo obrigatório ausente ou valor acima do limite de reembolso automático"
}
}
})
}
resource "aws_iam_role" "gateway" {
name = "AgentCoreGatewayToRefundWorkflow"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "bedrock-agentcore.amazonaws.com" }
Action = "sts:AssumeRole"
Condition = { ArnLike = { "aws:SourceArn" = local.gateway_glob } }
}]
})
}
resource "aws_iam_role_policy" "gateway" {
name = "RefundWorkflowConnectorAccess"
role = aws_iam_role.gateway.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "StateMachineOperations"
Effect = "Allow"
Action = ["states:StartExecution", "states:DescribeStateMachine"]
Resource = aws_sfn_state_machine.refund.arn
},
{
Sid = "ExecutionOperations"
Effect = "Allow"
Action = ["states:DescribeExecution", "states:GetExecutionHistory"]
Resource = "arn:aws:states:${var.region}:${local.account_id}:execution:${local.sm_name}:*"
}
]
})
}
output "state_machine_arn" { value = aws_sfn_state_machine.refund.arn }
output "gateway_role_arn" { value = aws_iam_role.gateway.arn }Repare na separação de recursos na policy do gateway: StartExecution e DescribeStateMachine atuam sobre o ARN da máquina de estado; DescribeExecution e GetExecutionHistory atuam sobre execution:RefundWorkflow:*, um tipo de ARN diferente. É essa segunda linha que vira a fronteira real, porque DescribeExecution recebe o executionArn do agente, não um valor fixado pelo operador.
Aplique com:
tofu init
tofu plan -out=plan.tfplan
tofu apply plan.tfplanSe o passo seguinte falhar com erro de assume-role, espere um minuto: mudança de IAM é eventualmente consistente.
2. Criar o gateway
export REGION=us-east-1
GATEWAY_ROLE_ARN=$(tofu output -raw gateway_role_arn)
SM_ARN=$(tofu output -raw state_machine_arn)
GATEWAY_JSON=$(aws bedrock-agentcore-control create-gateway \
--name "refund-gateway" \
--protocol-type MCP \
--authorizer-type AWS_IAM \
--role-arn "$GATEWAY_ROLE_ARN" \
--region "$REGION")
GATEWAY_ID=$(echo "$GATEWAY_JSON" | jq -r '.gatewayId')
GATEWAY_URL=$(echo "$GATEWAY_JSON" | jq -r '.gatewayUrl')
aws bedrock-agentcore-control get-gateway \
--gateway-identifier "$GATEWAY_ID" \
--query status --output text --region "$REGION"O nome refund-gateway precisa bater com a variável gateway_name do Terraform, porque a trust policy da role do gateway e a futura permissão do agente usam esse nome como prefixo num ArnLike. Espere o status virar READY antes de seguir; pode levar alguns minutos.
3. Adicionar o target do conector Step Functions
aws bedrock-agentcore-control create-gateway-target \
--name "refunds" \
--gateway-identifier "$GATEWAY_ID" \
--target-configuration '{
"mcp": {
"connector": {
"source": { "connectorId": "states" },
"configurations": [
{ "name": "StartExecution", "parameterValues": { "stateMachineArn": "'"$SM_ARN"'" } },
{ "name": "DescribeStateMachine", "parameterValues": { "stateMachineArn": "'"$SM_ARN"'" } },
{ "name": "DescribeExecution" },
{ "name": "GetExecutionHistory" }
]
}
}
}' \
--credential-provider-configurations '[{"credentialProviderType": "GATEWAY_IAM_ROLE"}]' \
--region "$REGION"stateMachineArn vem fixado (pinned) no StartExecution e no DescribeStateMachine. Isso significa que, quando o agente pedir tools/list, a ferramenta refunds___StartExecution vai aparecer com só um campo no schema: input. O agente nunca vê nem escolhe qual máquina de estado está rodando. Guarde o targetId retornado e aguarde o status READY com get-gateway-target antes do próximo passo.
4. Conceder ao agente a permissão de chamar o gateway
Essa é a terceira fronteira, e o Terraform do passo 1 não cobre: ela fica na role que o seu agente (runtime, Lambda, container) já usa para rodar.
{
"Effect": "Allow",
"Action": "bedrock-agentcore:InvokeGateway",
"Resource": "arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/refund-gateway-*"
}Use o padrão com * enquanto testa e troque pelo ARN exato assim que o gateway existir. O agente não precisa de nenhuma permissão states:* própria: o Step Functions só enxerga a role do gateway.
Quanto isso custa
A fonte não cita valores em dólar (eles variam por Região e mudam com o tempo), mas dá a fórmula que importa. O custo de uma execução iniciada por um agente tem dois componentes: transições de estado do Step Functions e chamadas de ferramenta no Gateway.
cost_per_run = (S × price_per_transition) + ((1 + P + H) × price_per_gateway_tool_call)Onde S é o número de transições no caminho percorrido, P é quantas vezes o agente chama DescribeExecution até o estado sair de RUNNING, e H é quantas vezes ele lê GetExecutionHistory para explicar o resultado. O termo que mais pega gestor desprevenido é P: um agente que faz polling a cada segundo num workflow de um minuto gera dezenas de chamadas faturáveis por execução só de ficar perguntando "já terminou?".
Execuções rejeitadas e falhas ainda custam transições de estado e chamadas de ferramenta. Um canal de entrada vulnerável a prompt injection pode gerar uma sequência de execuções em estado Fail que custam dinheiro sem gerar valor de negócio nenhum.
Rejected and failed runs still cost state transitions and tool calls. An injection-prone intake channel can generate a stream of Fail-state executions that cost money while producing no business value.Shivanshu Sharma, autor no AWS in Plain English
As duas métricas que valem monitorar são custo por resultado de negócio bem-sucedido (por reembolso creditado, no exemplo) e chamadas de gateway por execução. Se a segunda sobe sem motivo, o agente está fazendo polling demais, e isso dá pra corrigir com instrução de backoff no system prompt ou na descrição da ferramenta.
Testando
A fonte testa exatamente como um agente chamaria, assinando as requisições MCP com awscurl e o serviço bedrock-agentcore:
mcp() {
awscurl --service bedrock-agentcore --region "$REGION" \
-X POST "$GATEWAY_URL" -H "Content-Type: application/json" -d "$1"
}
start_refund() {
mcp "$(jq -n --arg input "$1" \
'{jsonrpc:"2.0",id:2,method:"tools/call",params:{name:"refunds___StartExecution",arguments:{input:$input}}}')"
}
describe() {
mcp "$(jq -n --arg arn "$1" \
'{jsonrpc:"2.0",id:3,method:"tools/call",params:{name:"refunds___DescribeExecution",arguments:{executionArn:$arn,includedData:"ALL_DATA"}}}')"
}
# Teste 1: listar as ferramentas, exatamente como o agente veria
mcp '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Teste 2: reembolso dentro do limite, deve seguir para IssueCredit
ARN=$(start_refund '{"orderId":"A-1001","amount":120}' \
| jq -r '.result.content[0].text | fromjson | .executionArn')
describe "$ARN"
# Teste 3: reembolso acima do limite, deve cair em RejectRefund
ARN=$(start_refund '{"orderId":"A-1002","amount":900}' \
| jq -r '.result.content[0].text | fromjson | .executionArn')
describe "$ARN"
# Teste 4: campo obrigatório faltando, deve cair em RejectRefund
ARN=$(start_refund '{"orderId":"A-1003"}' \
| jq -r '.result.content[0].text | fromjson | .executionArn')
describe "$ARN"O teste 4 é o que prova a regra de negócio: ele confirma que as condições IsPresent do Choice cortam o caminho antes mesmo de chegar na comparação numérica, então um pedido incompleto nunca chega perto de IssueCredit. Rode describe em cada executionArn e confira o campo status: SUCCEEDED para o teste 2, FAILED com error: RefundRejected para os testes 3 e 4.
Limpando os recursos
Apague na ordem inversa da criação, porque o target depende do gateway e o gateway depende da role:
- Delete o target do conector:
aws bedrock-agentcore-control delete-gateway-target --gateway-identifier "$GATEWAY_ID" --target-id "$TARGET_ID" --region "$REGION"; - Delete o gateway:
aws bedrock-agentcore-control delete-gateway --gateway-identifier "$GATEWAY_ID" --region "$REGION"; - Rode
tofu destroypara remover a máquina de estado, a role de execução e a role do gateway; - Se você ligou CloudWatch Logs ou X-Ray (seção opcional da fonte), apague manualmente o log group criado para o Step Functions, porque ele não é gerenciado pelo Terraform deste exemplo.
Nenhum desses recursos tem cobrança por hora parada: Step Functions Standard cobra por transição de estado executada e o AgentCore Gateway cobra por chamada de ferramenta. Sem execuções e sem chamadas, não sobra fatura rodando sozinha. O risco real de custo esquecido aqui não é o recurso ocioso, é um agente de teste que ficou com a permissão InvokeGateway e continua fazendo polling.
Próximos passos
- Troque o estado
IssueCredit, hoje umPassde demonstração, por umTaskreal chamando seu sistema de billing, e dê à role de execução só a ação e o recurso daquela integração específica; - Adicione uma chave de idempotência (por exemplo
orderIdmais um ID de requisição) dentro doinput, porque o conector expõeStartExecutionsem o parâmetro de nome de execução: um agente que repete a chamada por timeout pode disparar um reembolso duplicado se o primeiro passo do workflow não rejeitar isso por escrita condicional; - Se precisar de mais de um workflow exposto a agentes (reembolsos e runbooks de infraestrutura, por exemplo), crie um gateway por workflow: todos os targets de um mesmo gateway compartilham a mesma role de serviço, e compartilhar role é compartilhar raio de explosão;
- Antes de trocar Standard por Express para ganhar volume, confira a documentação do conector:
ListExecutions,DescribeExecutioneGetExecutionHistorynão funcionam com execuções Express, e um agente que inicia e depois faz polling de status precisa da semântica Standard; - Ligue CloudWatch Logs em nível
ERRORe X-Ray na máquina de estado antes de ir para produção; a fonte deixou essa parte fora do Terraform só para manter o exemplo curto, não porque seja dispensável; - Trate os task tokens (
SendTaskSuccess,SendTaskFailure,SendTaskHeartbeat) como segredo se algum dia habilitar callbacks human-in-the-loop: IAM não consegue restringir essas ações a um recurso específico, então o próprio token vira o único controle de quem completa qual tarefa.
Fonte: AWS in Plain English
Serviços neste textoStep FunctionsAPI Gateway
Este artigo foi escrito por Danilo Barros. Conteúdo produzido pela redação de agentes de IA do AWS Hub, a partir de fontes oficiais citadas no texto. Saiba como produzimos no expediente.