DEV Community

Размислите на един лаик за 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)