DEV Community

Cover image for Размислите на един лаик за Amazon Bedrock AgentCore платформата

Размислите на един лаик за Amazon Bedrock AgentCore платформата

Съдържание

AgentCore платформата се базира на повече от 20-годишния опит на AWS в решаването на проблемите на магазина на Amazon и на крайните клиенти. Услугата променя много от вече изградените ни представи за compute, network, storage и database и позволява връзка между акаунти и между различни региони, без да е нужно да познаваш софтуерно дефинираните мрежи в дълбочина. Дава и възможност да интегрираш легаси системи към новите агенти. Платформата се състои от няколко функционалности, които ще разгледаме една по една: Identity, Gateway, Memory и Runtime.

AgentCore Identity: кой какво може

AgentCore Identity управлява самоличността на агента и достъпа му до външни услуги и инструменти чрез абстракция, наречена workload identity. Ключовете за външните услуги се пазят в Secrets Manager с префикс bedrock-agentcore-identity!. На практика услугата управлява потока на идентификация — кой какво иска да прави и дали е одобрено. Така гарантираш липсата на своеволия: агентът има достъп точно толкова, колкото си му дал.

Предлагат се две форми на автентикация и оторизация.

2LO (2-legged OAuth) — агентът се представя пред външната система с предварително издаден токен: нещо като пропуск от твое име, който определя какво точно му е позволено да прави. Секретите се пазят в Secrets Manager. За iGaming сектора 2LO е нещо като табу, което трябва да се избягва на всяка цена — думата PAT предизвиква силно вълнение в инфосек общността, независимо дали токенът е криптиран с KMS ключ със строга политика. Нещо като „дракарис“ при драконите от Game of Thrones.

3LO (3-legged OAuth) — В този случай имаш три партии - агент, потребител и трета система. Агентът сам по себе си няма никакви права в Jira, Confluence или GitHub. Когато за пръв път поиска да работи от твое име, системата ти показва екран за одобрение — както когато приложение те пита дали разрешаваш достъп до камерата. На екрана пише точно какво ще може агентът (например само да чете тикети) и когато натиснеш Allow, системата издава токен само с тези права, който AgentCore Identity пази за теб. Оттам нататък агентът работи с този токен и може да прави единствено одобрените действия — нищо повече. За всяка от системите (Jira, Confluence, GitHub) одобрението е отделно, със собствени права.

Пример с 2LO:

resource "aws_bedrockagentcore_api_key_credential_provider" "github" {
  name               = "github-pat-token"
  api_key_wo         = local.envs["GITHUB_PAT"]
  api_key_wo_version = var.token_version
}
Enter fullscreen mode Exit fullscreen mode

Самоличността и ключовете обаче са само половината от историята — агентът има нужда и от инструменти, до които да стига с тях. Тук идва Gateway.

AgentCore Gateway: правилният инструмент, без излишни токени

AgentCore Gateway позволява връзка към Lambda функции, Smithy модели, OpenAPI-съвместими API-та и външни MCP сървъри. Решава и един много важен проблем: когато подадеш много инструменти на агент, той често греши и избира неправилния. Това струва токени, които клиентът плаща. Затова Gateway използва семантично търсене, за да открие правилния инструмент.

Terraform кодът дефинира gateway-я и неговата цел (target). OpenAPI схемата може да се подаде inline в JSON формат; ако е в YAML, се преобразува с Terraform функции — jsonencode(yamldecode(...)). Една особеност: ако целта е AgentCore Runtime агент, gateway-ят трябва да е без зададен protocol type — Runtime target-и не могат да се добавят към gateway с protocol_type = "MCP".

data "aws_iam_policy_document" "gtw_plcy" {
  statement {
    sid     = "GetResourceOauth2Token"
    effect  = "Allow"
    actions = ["bedrock-agentcore:GetResourceApiKey"]
    resources = ["arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:token-vault/default/apikeycredentialprovider/${aws_bedrockagentcore_api_key_credential_provider.github.name}",
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default/workload-identity/${aws_bedrockagentcore_gateway.gtw.gateway_id}",
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default",
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:token-vault/default"
    ]
  }
  statement {
    sid     = "GetWorkloadAccessToken"
    effect  = "Allow"
    actions = ["bedrock-agentcore:GetWorkloadAccessToken"]
    resources = [
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default",
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default/workload-identity/${aws_bedrockagentcore_gateway.gtw.gateway_id}"
    ]
  }
  statement {
    effect  = "Allow"
    actions = ["secretsmanager:GetSecretValue"]
    resources = [
      aws_bedrockagentcore_api_key_credential_provider.github.api_key_secret_arn[0].secret_arn
    ]
  }
}
data "aws_iam_policy_document" "assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["bedrock-agentcore.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      values   = [data.aws_caller_identity.current.account_id]
      variable = "aws:SourceAccount"
    }
  }
}
resource "aws_iam_role" "gtw_role" {
  name               = "bedrock-agentcore-gateway-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_policy" "gtw_plcy" {
  policy = data.aws_iam_policy_document.gtw_plcy.json
  name   = "bedrock-agentcore-gateway-plcy"
}

resource "aws_iam_role_policy_attachment" "gtw_role_plcy_attach" {
  policy_arn = aws_iam_policy.gtw_plcy.arn
  role       = aws_iam_role.gtw_role.name
}

resource "aws_bedrockagentcore_gateway" "gtw" {
  name            = "gateway"
  role_arn        = aws_iam_role.gtw_role.arn
  authorizer_type = "AWS_IAM"
  protocol_type   = "MCP"
  exception_level = "DEBUG"
  protocol_configuration {
    mcp {
      search_type = "SEMANTIC"
    }
  }
}

resource "aws_bedrockagentcore_gateway_target" "github_target" {
  name               = "api-target"
  gateway_identifier = aws_bedrockagentcore_gateway.gtw.gateway_id
  description        = "External API target with API key authentication"

  credential_provider_configuration {
    api_key {
      provider_arn              = aws_bedrockagentcore_api_key_credential_provider.github.credential_provider_arn
      credential_location       = "HEADER"
      credential_parameter_name = "Authorization"
      credential_prefix         = "Bearer"
    }
  }

  target_configuration {
    mcp {
      open_api_schema {
        inline_payload {
          payload = jsonencode(yamldecode(file("${path.module}/github-tools.yaml")))
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Понякога освен достъп до външни услуги искаме агентът да запомня информация за крайния потребител, за да предлага по-ценни решения на база на неговия контекст. Това става с AgentCore Memory.

AgentCore Memory: какво помни агентът

Паметта се дели според стратегията на: потребителски предпочитания (неща, които потребителят харесва), семантична (факти от контекста), обобщаваща и индивидуална (custom). Извличането на дългосрочните записи е асинхронно и отнема време. Има и една особеност при създаването: всяка стратегия блокира създаването на следващата, т.е. ако настройваш повече от една, между тях трябва да се изчака — емпирично около 190 секунди; в кода по-долу съм заложил 300 за спокойствие. В Terraform изглежда по следния начин:

resource "aws_iam_role" "bedrock_memory_role" {
  name               = "bedrock-agentcore-memory-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_role_policy_attachment" "bedrock_plcy_attach" {
  role       = aws_iam_role.bedrock_memory_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonBedrockAgentCoreMemoryBedrockModelInferenceExecutionRolePolicy"
}

resource "aws_bedrockagentcore_memory" "memory" {
  name                      = "agent_memory"
  event_expiry_duration     = 30
  memory_execution_role_arn = aws_iam_role.bedrock_memory_role.arn
}

resource "aws_bedrockagentcore_memory_strategy" "user_preference" {
  name        = "userpreference_strategy"
  memory_id   = aws_bedrockagentcore_memory.memory.id
  type        = "USER_PREFERENCE"
  description = "user preference strategy"
  namespaces  = ["/users/{actorId}/preferences/"]
}

resource "time_sleep" "wait_after_usr_pref_strategy" {
  depends_on       = [aws_bedrockagentcore_memory_strategy.user_preference]
  create_duration  = "300s"
  destroy_duration = "300s"
  triggers = {
    strategy_id = aws_bedrockagentcore_memory_strategy.user_preference.memory_strategy_id
  }
}

resource "aws_bedrockagentcore_memory_strategy" "semantic" {
  name        = "semantic_strategy"
  memory_id   = aws_bedrockagentcore_memory.memory.id
  type        = "SEMANTIC"
  description = "semantic strategy"
  namespaces  = ["/users/{actorId}/facts/"]
  depends_on  = [time_sleep.wait_after_usr_pref_strategy]
}

resource "time_sleep" "wait_after_semantic_strategy" {
  depends_on       = [aws_bedrockagentcore_memory_strategy.semantic]
  create_duration  = "300s"
  destroy_duration = "300s"
  triggers = {
    strategy_id = aws_bedrockagentcore_memory_strategy.semantic.memory_strategy_id
  }
}

resource "aws_bedrockagentcore_memory_strategy" "summarization" {
  name        = "summarization_strategy"
  memory_id   = aws_bedrockagentcore_memory.memory.id
  type        = "SUMMARIZATION"
  description = "summarization strategy"
  namespaces  = ["/summaries/{actorId}/{sessionId}/"]
  depends_on  = [time_sleep.wait_after_semantic_strategy]
}
Enter fullscreen mode Exit fullscreen mode

Записите се пазят в йерархични namespace-и, в които {actorId} и {sessionId} се заместват с реалните стойности при запис.

Ето как всичко дотук се събира в самия агент — четене от дългосрочната памет, разговор с инструментите през Gateway (MCP със SigV4 подпис) и запис на хода като събитие:

"""Repo Ops agent — AgentCore Runtime entrypoint.

Поток на всяка заявка:
  1. recall()  -> чете дългосрочна памет (LTM) за този потребител
  2. Agent()   -> LLM разговор с инструментите от Gateway (MCP през SigV4)
  3. create_event() -> записва хода като събитие (STM); стратегиите
                       извличат LTM асинхронно след ~1 минута
"""
import os

import boto3
import httpx
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from bedrock_agentcore.memory import MemoryClient
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient

REGION = os.environ.get("AWS_REGION", "eu-central-1")
GATEWAY_URL = os.environ["GATEWAY_URL"]   # .../mcp
MEMORY_ID = os.environ["MEMORY_ID"]
MODEL_ID = os.environ["MODEL_ID"]

app = BedrockAgentCoreApp()               # вдига HTTP сървър на :8080
memory = MemoryClient(region_name=REGION)


class SigV4HTTPXAuth(httpx.Auth):
    """Подписва всяка MCP заявка със SigV4.

    Gateway-ът е с authorizer_type = "AWS_IAM", тоест иска подпис
    точно както всяко AWS API извикване. Ролята на Runtime трябва да
    има bedrock-agentcore:InvokeGateway върху ARN-а на gateway-а.
    """

    requires_request_body = True

    def __init__(self, service: str, region: str):
        self._creds = boto3.Session().get_credentials()
        self._service = service
        self._region = region

    def auth_flow(self, request: httpx.Request):
        aws_req = AWSRequest(
            method=request.method,
            url=str(request.url),
            data=request.content,
            headers={"host": request.url.host},
        )
        SigV4Auth(self._creds, self._service, self._region).add_auth(aws_req)
        request.headers.update(dict(aws_req.headers))
        yield request


def recall(actor_id: str, query: str) -> str:
    """Семантично търсене в дългосрочната памет.

    Namespace-ите трябва да съвпадат ТОЧНО с тези в Terraform
    (включително наклонената черта накрая).
    """
    namespaces = [
        f"/users/{actor_id}/preferences/",
        f"/users/{actor_id}/facts/",
    ]
    chunks = []
    for ns in namespaces:
        try:
            records = memory.retrieve_memories(
                memory_id=MEMORY_ID,
                namespace=ns,
                query=query,
                top_k=3,
            )
            for rec in records:
                text = rec.get("content", {}).get("text", "")
                if text:
                    chunks.append(f"- {text}")
        except Exception as exc:
            # Паметта никога не трябва да сваля агента
            print(f"[memory] retrieve failed for {ns}: {exc}")
    return "\n".join(chunks)


def remember(actor_id: str, session_id: str, prompt: str, answer: str) -> None:
    """Записва хода като събитие в краткосрочната памет."""
    try:
        memory.create_event(
            memory_id=MEMORY_ID,
            actor_id=actor_id,
            session_id=session_id,
            messages=[(prompt, "USER"), (answer, "ASSISTANT")],
        )
    except Exception as exc:
        print(f"[memory] create_event failed: {exc}")


@app.entrypoint
def invoke(payload, context):
    prompt = payload.get("prompt", "")
    actor_id = payload.get("actor_id", "default")

    # session_id идва от --runtime-session-id при извикването
    session_id = getattr(context, "session_id", None) or "local-dev-session-0000000000000000"

    memories = recall(actor_id, prompt)

    system_prompt = (
        "Ти си Repo Ops — кратък асистент за GitHub операции. "
        "Използвай наличните инструменти за всичко свързано с "
        "хранилища, pull request-и и issue-та. Никога не измисляй "
        "данни — ако инструмент върне грешка, кажи я на потребителя. "
        "Питай за потвърждение преди да създадеш issue."
    )
    if memories:
        system_prompt += f"\n\nКакво помниш за този потребител:\n{memories}"

    gateway = MCPClient(
        lambda: streamablehttp_client(
            GATEWAY_URL,
            auth=SigV4HTTPXAuth("bedrock-agentcore", REGION),
        )
    )

    with gateway:
        tools = gateway.list_tools_sync()
        print(f"[gateway] tools: {[t.tool_name for t in tools]}")
        agent = Agent(
            model=BedrockModel(model_id=MODEL_ID, region_name=REGION),
            tools=tools,
            system_prompt=system_prompt,
        )
        answer = str(agent(prompt))

    remember(actor_id, session_id, prompt, answer)
    return {"result": answer}


if __name__ == "__main__":
    app.run()
Enter fullscreen mode Exit fullscreen mode

Този код обаче трябва да живее някъде. Време е за частта, на която винаги блокирам на интервю.

AgentCore Runtime: Firecracker, MicroVM и контейнери

Винаги съм се чудил как да разкажа какво е AgentCore Runtime. Въпросите за MicroVM, виртуални машини, контейнери и процеси винаги са трудни за разказване. Може би сам не го разбирам докрай, може би заешката дупка е толкова дълбока, че е трудно да се обясни просто. Затова реших да започна отдалеч — може би и защото на интервю винаги блокирам точно на тези въпроси.

За да развенчая мита, започвам от MicroVM.

Две от опциите за деплойване на приложение (включително AI приложение) са виртуална машина и контейнер. Разликата: контейнерите пакетират всички зависимости и библиотеки на приложението и споделят общо ядро и операционна система с хоста, докато всяка виртуална машина има собствено ядро, собствена операционна система и заделена част от ресурсите. Ядрото управлява хардуера чрез драйвери, т.е. има неограничен достъп до процесорно време и памет; приложенията, от друга страна, нямат.

Често се възприема, че виртуалната машина е по-сигурна от контейнерите. Защо? Може би защото контейнерите споделят ядро с хоста.

Дали това прави контейнерите несигурни спрямо виртуалната машина, е спорен въпрос. При виртуалната машина имаш пачване, управление на драйвери и по-бавно стартиране. Има и вариантът distroless контейнер — без шел и пакетни мениджъри, само с необходимото за крайното приложение.

MicroVM се опитва да реши предизвикателствата с бавното стартиране, като освен това предлага по-голяма изолация, характерна за виртуалните машини: всяка машина идва със собствено виртуализирано ядро. Firecracker — MicroVM технологията зад AWS Lambda — позволява да стартираш до 150 MicroVM в секунда, като всяка от тях се вдига за около 125 ms.

Firecracker

Каква е връзката на виртуалните машини и контейнерите с AgentCore Runtime? Firecracker стои зад AgentCore Runtime: ти деплойваш контейнер, а той се изпълнява върху Firecracker виртуална машина. Така получаваш максимална изолация — една Firecracker машина не може да използва ресурси на друга, това е забранено на ниво хипервайзор — и в същото време максимално лесно деплойване на всяко AI приложение под формата на контейнер с всички зависимости. Около самия Firecracker процес на хоста върви и програма на име jailer — втори слой защита (пясъчник), който обвива виртуалната машина, в случай че тя бъде компрометирана.

Firecracker design

Технологията гарантира сигурност и изолация на данните, паметта и процесите на всеки клиент. AgentCore Runtime стъпва на опита на Amazon с Lambda, контейнерите и инстанциите.

Runtime дава възможност да деплойнеш всеки AI агент или MCP сървър, независимо от SDK или framework, стига да изложи HTTP на порт 8080 (за MCP сървъри портът е 8000). AWS споделят, че идеята е услугата да е framework agnostic — бъдещият сигурен дом за твоя агент, който пази най-ценното за една фирма: данните. Заедно с паметта, управлението на достъпи, връзката с други услуги и оценката на агенти това оформя цялостна AI платформа.

Подготовката на приложението включва три стъпки.

  • Създаване на регистър за контейнерни изображения, в който ще живее приложението:
resource "aws_ecr_repository" "agent_repo" {
  name                 = "agent-repo"
  image_tag_mutability = "IMMUTABLE"
  image_scanning_configuration {
    scan_on_push = false
  }
  force_delete = true
}
Enter fullscreen mode Exit fullscreen mode
  • Docker файл с всички зависимости на приложението:
# AgentCore Runtime работи САМО на Graviton (linux/arm64).
# Затова build-ът задължително минава през buildx с --platform linux/arm64.
FROM public.ecr.aws/docker/library/python:3.13-slim

WORKDIR /app

# Първо зависимостите — така Docker кешира слоя и следващите build-ове са бързи
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

# Контрактът на Runtime: HTTP сървър, който слуша на 8080
EXPOSE 8080

CMD ["python", "app.py"]
Enter fullscreen mode Exit fullscreen mode
  • Build и push на изображението:
resource "random_id" "agent_rand_tag" {
  keepers = {
    agent_img_tag = local.agent_code_hash
  }
  byte_length = 2
}

resource "null_resource" "build_and_push_agent" {
  triggers = {
    dockerfile_hash = local.agent_code_hash
  }

  provisioner "local-exec" {
    command = <<-EOT
      aws ecr get-login-password --region ${data.aws_region.current.region} | docker login --username AWS --password-stdin ${aws_ecr_repository.agent_repo.repository_url}
      docker build -t ${aws_ecr_repository.agent_repo.repository_url}:${random_id.agent_rand_tag.hex} ${path.module}/
      docker push ${aws_ecr_repository.agent_repo.repository_url}:${random_id.agent_rand_tag.hex}
    EOT
  }
}
Enter fullscreen mode Exit fullscreen mode

Terraform кодът се сглобява от съставните елементи: за да вдигнеш AgentCore Runtime, подаваш всичко останало и го създаваш последен — като Волтрон, ако сте го гледали, това е главата — с необходимата роля:

data "aws_iam_policy_document" "runtime_permissions" {
  statement {
    actions   = ["ecr:GetAuthorizationToken"]
    effect    = "Allow"
    resources = ["*"]
  }

  statement {
    actions = [
      "ecr:BatchGetImage",
      "ecr:GetDownloadUrlForLayer",
    ]
    effect    = "Allow"
    resources = [aws_ecr_repository.agent_repo.arn]
  }
  statement {
    actions = [
      "logs:DescribeLogStreams",
      "logs:CreateLogGroup",

    ]
    effect    = "Allow"
    resources = ["arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"]
  }
  statement {
    actions = [
      "logs:DescribeLogGroups"

    ]
    effect    = "Allow"
    resources = ["arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:*"]
  }
  statement {
    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents"

    ]
    effect    = "Allow"
    resources = ["arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"]
  }
  statement {
    actions = [
      "cloudwatch:PutMetricData"
    ]
    resources = ["*"]
    effect    = "Allow"
    condition {
      test     = "StringEquals"
      values   = ["bedrock-agentcore"]
      variable = "cloudwatch:namespace"
    }
  }
  statement {
    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream"
    ]
    resources = ["arn:aws:bedrock:*::foundation-model/*",
    "arn:aws:bedrock:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:*"]
    effect = "Allow"
  }
  statement {
    sid     = "GetWorkloadAccessToken"
    effect  = "Allow"
    actions = ["bedrock-agentcore:GetWorkloadAccessToken"]
    resources = [
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default",
      "arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:workload-identity-directory/default/workload-identity/${aws_bedrockagentcore_gateway.gtw.gateway_id}"
    ]
  }
  statement {
    effect  = "Allow"
    actions = ["bedrock-agentcore:InvokeGateway"]
    resources = [
      aws_bedrockagentcore_gateway.gtw.gateway_arn
    ]
  }
  statement {
    effect = "Allow"
    actions = ["bedrock-agentcore:ListEvents",
      "bedrock-agentcore:CreateEvent",
    "bedrock-agentcore:RetrieveMemoryRecords"]
    resources = [
      aws_bedrockagentcore_memory.memory.arn
    ]
  }
}

resource "aws_iam_role" "rntime_role" {
  name               = "bedrock-agentcore-rntime-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_policy" "rntime_plcy" {
  policy = data.aws_iam_policy_document.runtime_permissions.json
  name   = "bedrock-agentcore-rntime-plcy"
}

resource "aws_iam_role_policy_attachment" "rntime_role_plcy_attach" {
  policy_arn = aws_iam_policy.rntime_plcy.arn
  role       = aws_iam_role.rntime_role.name
}

resource "aws_bedrockagentcore_agent_runtime" "example" {
  agent_runtime_name = "gitops_agent_runtime"
  role_arn           = aws_iam_role.rntime_role.arn

  agent_runtime_artifact {
    container_configuration {
      container_uri = "${aws_ecr_repository.agent_repo.repository_url}:${random_id.agent_rand_tag.hex}"
    }
  }

  network_configuration {
    network_mode = "PUBLIC"
  }
  environment_variables = {
    GATEWAY_URL = aws_bedrockagentcore_gateway.gtw.gateway_url
    MEMORY_ID   = aws_bedrockagentcore_memory.memory.id
    MODEL_ID    = "eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
  }
  depends_on = [null_resource.build_and_push_agent]
}
Enter fullscreen mode Exit fullscreen mode

Бонус: cross-account и cross-region без мрежова магия

Освен всичко останало, горните услуги — и в частност Gateway — решават проблем, който обикновено налага по-сложни мрежови конструкции: извикванията между акаунти и между региони. Два примера от реалния живот. И в двата сценарият е един и същ: gateway-ят живее в платформен акаунт А в eu-west-1, а MCP сървърът (mcp-atlassian) и Jira DC — в друг акаунт Б и друг регион, eu-west-2. Разликата е в цената, която плащаш.

Пример 1: Runtime target — мрежата я няма, защото не ти трябва

AgentCore Runtime

Gateway без protocol type подава Okta JWT-то нататък и с едно InvokeAgentRuntime по AWS backbone мрежата стига до Runtime в другия акаунт и регион — никакъв peering, никакъв PrivateLink, това е API, а мрежата остава скрита. Runtime-ът върти форкнатия mcp-atlassian (arm64, stateless, MCP на :8000) и излиза през egress ENI-та право към Jira DC с потребителския токен. Цената: Identity трябва да живее в акаунта на Runtime-а — секретите се местят при агента — а губиш и агрегацията, и семантичното търсене, тъй като gateway е просто прокси

Пример 2: MCP target — семантично търсене, но мрежата си я създаваш сам

Lattice

Тук gateway-ят е с класическият MCP протокол тъй като целта не е аgentcore runtime: MCP, CUSTOM_JWT, агрегация и семантично търсене, а Identity сменя JWT-то срещу потребителския токен — 3LO vault-ът остава при платформата. Цената е мрежата, и тя е цяла верига: Lattice resource gateway (само в региона на gateway-я) → cross-region interface endpoint → PrivateLink → endpoint service в акаунт Б → вътрешен NLB с TLS :443 и публичен ACM сертификат → EC2 с mcp-atlassian на :8000 → Jira DC. Всяко звено е managed — но всяко звено е твое.

Кое кога

Ако ти трябва просто да стигнеш до агент или MCP сървър в друг акаунт и регион — Runtime target: нула мрежова работа, но vault-ът се мести при runtime-а и губиш агрегацията. Ако строиш платформа — един endpoint, семантично търсене, токените и съгласията при теб — MCP target: получаваш всичко това, но Lattice-ът, cross-region PrivateLink-ът и сертификатите са твоя грижа.

Колко струва

AgentCore няма месечен абонамент — всяка функционалност е с отделен брояч и плащаш само каквото ползваш. Цените към юли 2026:

  • Runtime — $0.0895 на vCPU-час и $0.00945 на GB-час, на секунда. CPU се таксува само докато агентът реално обработва: I/O чакането е безплатно, а агентите прекарват 30–70% от сесията в чакане на модела и инструментите.
  • Gateway — $0.005 на 1000 извиквания (ListTools, InvokeTool), $0.025 на 1000 семантични търсения и $0.02 на 100 индексирани инструмента месечно.
  • Memory — $0.25 на 1000 събития (краткосрочна памет); дългосрочната е $0.75 на 1000 записа месечно с вградените стратегии (моделът е включен в цената) и $0.50 на 1000 извличания. Три стратегии значат три отделни извличания от същите събития.
  • Identity — $0.010 на 1000 заявки за токен… но безплатна, когато минава през Runtime или Gateway. В нашата архитектура: нула.
  • Observability — по тарифите на CloudWatch (около $0.35/GB за span-ове).

Отстрани стоят ECR за образа, Secrets Manager за PAT-а и — най-голямото перо — токените към модела, които често са 50–70% от цялата сметка. Точно затова семантичното търсене на Gateway не е лукс, а FinOps: грешно избраният инструмент струва токени, а токените струват най-много.

Вместо заключение

AgentCore не е просто хостинг за агенти. Identity решава кой какво може, Gateway — кой инструмент е правилният, Memory — какво си струва да се помни, Runtime — къде всичко това живее изолирано. А когато се наложи да прекосиш акаунт и регион, вече имаш избор: плащаш или с функционалност, или с мрежова връзка. Пример 1 изпълнява обещанието от увода буквално — никаква мрежа. Пример 2 показва, че дори когато мрежата ти трябва, тя се сглобява от managed блокчета като lego без нужда от промени по рутингтаблици.

Top comments (0)