Vier Tools sitzen jetzt hinter einem einzigen AgentCore Gateway, statt einzeln, eine Python-Funktion nach der anderen, in jeden Agenten verdrahtet zu werden: cloudwatch-read, logs-read und cost-read übernehmen eine Read-only-Rolle in einem Spoke-Konto und können niemals irgendetwas mutieren, und ssm-execute, das einzige mutierende Tool der Plattform, kann ebenfalls keinen Spoke direkt erreichen, es kann nur eine Step-Functions-Ausführung starten, die auf die Slack-Freigabe eines Menschen pausiert. Diese Pause ist keine UI-Nettigkeit. Sie ist der eine Ort in der gesamten Plattform, an dem ein AWS-Credential geprägt wird, das in der Lage ist, etwas in einem Spoke-Konto zu ändern, und es wird erst geprägt, nachdem eine Person auf Freigeben geklickt hat.

Teil 1 hat das Szenario gesetzt und sich für AgentCore plus Strands entschieden. Teil 2 hat die Kontogrenze gebaut: die Spoke-Rollen ops-readonly und ops-mutate, die dieser Teil als existierend voraussetzt und unverändert wiederverwendet. Teil 3 hat den ersten Agenten mit zwei direkt verdrahteten Tools ausgeliefert, cloudwatch_read und logs_read als einfache, mit @tool dekorierte Python-Funktionen, die der Triage-Agent In-Process aufgerufen hat, und unumwunden gesagt, dass Gateway Indirektion gewesen wäre, zwischen der es noch nichts zu routen gab. Dieser Teil ist der Punkt, an dem sich diese Indirektion auszahlt: Ein zweites Read-Tool (cost-read) und das erste mutierende Tool der Plattform (ssm-execute) existieren jetzt beide, und vier Tools, die jeder Agent unabhängig aufrufen könnte, sind genau die Zahl, bei der eine gemeinsame, zentral gescopte Tool-Ebene aufhört, verfrüht zu sein. Der begleitende Code liegt auf github.com/flightlesstux/agents-on-call, unter terraform/20-gateway-tools/ und agents/tools/, und jeder Ausschnitt unten stammt direkt aus diesen Dateien, nicht für den Beitrag vereinfacht.

Warum Inline-Tools bei Agent Nummer zwei aufhören zu skalieren

Teil 3s zwei Tools lebten in agents/triage/agent.py: einfache Funktionen, jede mit einem kontoübergreifenden Assume-Role-Aufruf, direkt von dem einen Agenten aufgerufen, der sie brauchte. Bei einem Agenten ist daran nichts falsch. Der Ärger beginnt beim zweiten: Teil 5s Cost-Agent braucht cloudwatch-read und cost-read, sein Runbook-Agent braucht cloudwatch-read, logs-read und irgendwann ssm-execute. In-Process verdrahtet, wie Teil 3 es getan hat, bedeutet das dieselbe Python-Funktion, eingefügt in drei Agenten-Codebasen, drei IAM-Rollen, die unabhängig voneinander dieselbe sts:AssumeRole-Berechtigung gewähren, und drei Stellen, an denen ein Bug im Aufbau der kontoübergreifenden Session zu fixen ist, ein Tippfehler in der External-ID-Behandlung einer Kopie, den niemand bemerkt, bis die Aufrufe genau dieses einen Agenten auf eine Art fehlschlagen, die die anderen beiden nicht reproduzieren.

AgentCore Gateway entfernt das Copy-Paste, nicht indem es die Tools schlauer macht, sondern indem es sie exakt einmal existieren lässt. Eine Lambda pro Tool, eine Execution Role pro Lambda, ein Gateway, das die Tool-Aufrufe jedes Agenten zum richtigen Ziel routet. Die eigene IAM-Rolle eines Agenten braucht überhaupt kein sts:AssumeRole mehr in irgendeinen Spoke (Teil 3s Triage-Runtime-Rolle hat es noch, da dieser Agent vor diesem Teil entstanden ist); ein künftiger Agent braucht nur noch die Berechtigung, den MCP-Endpunkt des Gateways aufzurufen, und jedes kontoübergreifende Read-Credential, das die Plattform prägt, kommt aus genau vier Lambda-Execution-Rollen, die dieses Modul besitzt, nicht aus so vielen Agenten, wie bis zum Ende der Serie existieren.

AgentCore Gateway als MCP-Tool-Ebene

AgentCore Gateway wandelt seit der General Availability von AgentCore am 13. Oktober 2025 APIs, Lambda-Funktionen und bestehende Services in MCP-kompatible Tools um, mit IAM-basierter Autorisierung bereits zu dieser GA verfügbar. MCP selbst, das Protokoll, das jedes Gateway-Ziel spricht, ist der offene Standard, den Anthropic im November 2024 veröffentlicht hat, um KI-Anwendungen mit Daten- und Tool-Quellen zu verbinden; der Beitrag von Gateway besteht darin, vier separate Lambda-Funktionen in einen einzigen MCP-Server zu verwandeln, aus dem die Client-Bibliothek eines Agenten Tools entdecken kann, statt dass der Code des Agenten die ARN und die Aufruf-Form jeder Lambda direkt kennen muss. Die Preisgestaltung folgt dem Rest von AgentCores Form: 0,005 $ pro 1.000 API-Aufrufen (ListTools, InvokeTool, Ping) und 0,02 $ pro 100 indizierte Tools im Monat, klein genug, dass vier Tools neben den Token-Kosten eines einzigen Modellaufrufs nichts kosten.

Die Gateway-Ressource selbst ist kurz. authorizer_type = "AWS_IAM" bedeutet, dass dieselbe IAM-Grenze, auf die sich diese ganze Serie gestützt hat, entscheidet, wer das Gateway überhaupt aufrufen darf, kein separater JWT-Issuer, den man für eine Plattform aufbauen müsste, die bereits ein Identitätssystem hat:

resource "aws_bedrockagentcore_gateway" "tools" {
  name            = "${var.platform_name}-tools"
  description     = "MCP tool plane: cloudwatch-read, logs-read, cost-read, ssm-execute. AWS_IAM authorizer, IAM does the access control both for who can call the Gateway and what each tool Lambda can touch."
  role_arn        = aws_iam_role.gateway.arn
  protocol_type   = "MCP"
  authorizer_type = "AWS_IAM"

  # Optional Cedar policy layer, see variables.tf's cedar_policy_engine_arn
  # comment for why the engine itself is a variable, not a resource, at this
  # part's date.
  dynamic "policy_engine_configuration" {
    for_each = var.cedar_policy_engine_arn == null ? [] : [var.cedar_policy_engine_arn]
    content {
      arn  = policy_engine_configuration.value
      mode = var.cedar_policy_engine_mode
    }
  }

  tags = merge(var.tags, { Component = "gateway" })
}

Dieser policy_engine_configuration-Block ist das bisher neueste Stück Terraform dieser Serie, einen Datums-Check wert. Cedar-basierte Policy-Evaluierung für Gateway landete im aws-Provider am 27. Mai 2026 als reine List-Ressource, bekam dann am 10. Juni diesen Write-Path-Block, Provider-Version 6.50.0, acht Tage vor dem eigenen Datum dieses Teils. Sicher unter der Backdating-Regel der Serie, aber nur knapp, und es gibt weiterhin keine Terraform-Ressource, um die Cedar-Policy-Engine selbst zu erstellen, nur um eine bereits existierende anzuhängen. var.cedar_policy_engine_arn ist standardmäßig null, und der Block ist in ein dynamic gewickelt, dasselbe Muster, das Teil 2 für den Bedrock-Modellzugriff genutzt hat: eine echte AWS-Fähigkeit, auf die Terraform verweisen, aber noch nicht vollständig provisionieren kann.

Eine Lambda, eine Aufgabe: das Tool-Muster

Jedes Tool folgt derselben Form: eine Lambda-Funktion, eine Execution Role, gescoped auf exakt das, was dieses eine Tool braucht, und ein Gateway-Target, das das Input-Schema des Tools beschreibt, sodass der MCP-Client eines Agenten es aufrufen kann, ohne den Lambda-Quellcode zu lesen. cloudwatch-read ist das klarste Beispiel, weil es dieselbe Logik ist, die Teil 3 schon ausgeliefert hat, nur verschoben. Die Execution Role vertraut nur dem Lambda-Service und gewährt nur zwei Dinge: ops-readonly in den konfigurierten Spokes annehmen, und die eigenen CloudWatch Logs schreiben:

data "aws_iam_policy_document" "cloudwatch_read_permissions" {
  statement {
    sid       = "AssumeOpsReadonlyInSpokes"
    effect    = "Allow"
    actions   = ["sts:AssumeRole"]
    resources = local.ops_readonly_role_arns
  }

  statement {
    sid    = "WriteOwnLogs"
    effect = "Allow"
    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]
    resources = [local.lambda_log_arn]
  }
}

resource "aws_lambda_function" "cloudwatch_read" {
  function_name    = "${var.platform_name}-cloudwatch-read"
  description      = "AgentCore Gateway MCP tool: reads a CloudWatch metric from a spoke account via ops-readonly. Read-only."
  role             = aws_iam_role.cloudwatch_read.arn
  handler          = "cloudwatch_read.handler"
  runtime          = "python3.12"
  timeout          = 30
  memory_size      = 256
  filename         = data.archive_file.cloudwatch_read_zip.output_path
  source_code_hash = data.archive_file.cloudwatch_read_zip.output_base64sha256
  ...
}

Keine Wildcard-Resource irgendwo in diesem Policy-Dokument: local.ops_readonly_role_arns baut eine ARN pro konfigurierter Spoke-Konto-ID, und alles andere, was diese Rolle berühren kann, existiert außerhalb dieser Liste plus ihrer eigenen Log-Gruppe nicht. logs-read und cost-read nutzen dieselben Trust- und Permission-Dokumente wieder; nur ihr Handler-Code und das für das Gateway sichtbare Tool-Schema unterscheiden sich. Der Handler selbst ist Teil 3s Logik, fast unverändert, liest seine Argumente aus einem vom Gateway gelieferten Event statt aus Python-Keyword-Argumenten:

def handler(event: dict, context) -> dict:
    """Lambda entrypoint for the cloudwatch-read Gateway target.
    ...
    """
    spoke_account_id = event["spoke_account_id"]
    namespace = event["namespace"]
    metric_name = event["metric_name"]
    dimensions = event.get("dimensions", {})
    lookback_minutes = int(event.get("lookback_minutes", 60))
    stat = event.get("stat", "Average")
    period_seconds = int(event.get("period_seconds", 60))

    session = assume_role(spoke_account_id, OPS_READONLY_ROLE_NAME, "cloudwatch-read")
    cloudwatch = session.client("cloudwatch", config=_boto_config)

Das Gateway-Target ist das, was diese Lambda in etwas verwandelt, das der MCP-Client eines Agenten entdecken kann: einen Namen, eine Beschreibung und einen im JSON-Schema-Format gehaltenen Input-Vertrag. Der vollständige Block umfasst sieben Parameter; die Form unten ist für jedes Tool dieselbe:

resource "aws_bedrockagentcore_gateway_target" "cloudwatch_read" {
  gateway_identifier = aws_bedrockagentcore_gateway.tools.gateway_id
  name               = "cloudwatch-read"
  description        = "Reads a CloudWatch metric's recent datapoints from a spoke account."

  credential_provider_configuration {
    gateway_iam_role {}
  }

  target_configuration {
    mcp {
      lambda {
        lambda_arn = aws_lambda_function.cloudwatch_read.arn

        tool_schema {
          inline_payload {
            name        = "cloudwatch_read"
            description = "Read a CloudWatch metric's recent datapoints from a spoke account. An empty datapoint list is a real finding, not an error."

            input_schema {
              type = "object"

              property {
                name        = "spoke_account_id"
                type        = "string"
                description = "12-digit account ID of the spoke to query."
                required    = true
              }
              ...
            }
          }
        }
      }
    }
  }
}

credential_provider_configuration { gateway_iam_role {} } ist das Detail, bei dem sich ein Innehalten lohnt: Es sagt dem Gateway, die Ziel-Lambda mit der eigenen Execution Role des Gateways über einfaches IAM aufzurufen, nicht über einen OAuth-Flow oder einen aus einem Credential Provider gezogenen API-Key. Das ist die richtige Wahl für eine Lambda im selben Konto, auf derselben Plattform; der role_arn des Gateways bekommt lambda:InvokeFunction auf genau diese vier Lambda-ARNs und sonst nichts, dieselbe Ein-Zweck-eine-Berechtigung-Disziplin wie jede Rolle in dieser Serie bisher.

Read-only ist kein Slogan, es sind drei separate IAM-Fakten

"Read-only per Default" bedeutet nur dann etwas, wenn es den Kontakt mit einem Bug übersteht. Diese Verdrahtung übersteht drei spezifische. Blast Radius: Die drei Read-Rollen halten keine Berechtigung, die Zustand in einem Spoke ändern kann, nur sts:AssumeRole in ops-readonly, dessen Policy (Teil 2) ein explizites Deny über ihre Read-only-Managed-Policies legt, sodass eine Prompt Injection, die einen Agenten überzeugt, ein Read-Tool zum Löschen von etwas aufzufordern, nirgendwohin führt. Audit: Jeder Read überquert weiterhin eine Kontogrenze via sts:AssumeRole, sodass CloudTrail im Security-Konto genau sieht, welche Tool-Lambda welchen Spoke wann berührt hat. Vertrauen mit dem menschlichen Team: Ein On-Call-Engineer, der ssm-execute hinter einem Approval-Gate sieht und drei read-benannte Tools, deren Rollen nirgendwo eine mutierende Aktion halten, hat einen Grund, der Behauptung "es liest nur" zu glauben, statt sie im Vertrauen hinzunehmen, in fünf Minuten mit aws iam get-role-policy überprüfbar, kein Satz in einem System-Prompt, dem jemand vertrauen muss, dass das Modell ihn befolgt hat.

Das eine mutierende Tool, und das Gate dahinter

ssm-execute ist das einzige Tool auf der Plattform, dessen IAM-Rolle überhaupt keine sts:AssumeRole-Berechtigung in einen Spoke enthält. Ihr gesamtes Berechtigungsset ist states:StartExecution, gescoped auf exakt eine State-Machine-ARN:

data "aws_iam_policy_document" "ssm_execute_permissions" {
  statement {
    sid       = "StartApprovalStateMachineOnly"
    effect    = "Allow"
    actions   = ["states:StartExecution"]
    resources = [aws_sfn_state_machine.mutation_approval.arn]
  }
  ...
}

ssm-execute aufzurufen führt nichts aus, es schlägt vor, etwas auszuführen. Der Handler prüft das vorgeschlagene Dokument gegen eine lokale Allowlist, noch bevor er überhaupt eine Step-Functions-Ausführung erstellt, ein redundanter Fail-Fast-Check obendrauf auf den eigentlichen, ops-mutates eigene IAM-Policy aus Teil 2, die tatsächlich hält, falls der Code dieser Lambda einen Bug hat:

if ssm_document_arn not in ALLOWED_SSM_DOCUMENT_ARNS:
    return {
        "status": "rejected",
        "reason": f"{ssm_document_arn} is not on the allowlist; no approval request was created.",
    }

execution = _sfn.start_execution(
    stateMachineArn=APPROVAL_STATE_MACHINE_ARN,
    input=json.dumps({
        "spoke_account_id": spoke_account_id,
        "ssm_document_arn": ssm_document_arn,
        "ssm_parameters": ssm_parameters,
        "diagnosis": diagnosis,
        "blast_radius": blast_radius,
    }),
)

Die State Machine ist der Ort, an dem .waitForTaskToken seinen Namen verdient. Das eigene Tutorial von Step Functions zu diesem Muster ist explizit, dass ein Task-State unbegrenzt pausieren, einen Token generieren und erst fortsetzen kann, wenn etwas außerhalb der State Machine SendTaskSuccess oder SendTaskFailure mit genau diesem Token aufruft, und dass sowohl der Wait-State als auch die gesamte Ausführung ihr eigenes TimeoutSeconds brauchen, sonst endet eine hängengebliebene Ausführung nie. NotifySlackAndWaitForApproval ruft slack-post mit dem Task-Token in seinem Payload auf, dann pausiert Step Functions selbst, nicht die Lambda:

definition = jsonencode({
  Comment        = "Human approval gate for ops-mutate SSM Automation execution."
  StartAt        = "NotifySlackAndWaitForApproval"
  TimeoutSeconds = var.state_machine_timeout_seconds
  States = {
    NotifySlackAndWaitForApproval = {
      Type     = "Task"
      Resource = "arn:aws:states:::lambda:invoke.waitForTaskToken"
      Parameters = {
        FunctionName = aws_lambda_function.slack_post.arn
        Payload = {
          "TaskToken.$"      = "$$.Task.Token"
          "Diagnosis.$"      = "$.diagnosis"
          "BlastRadius.$"    = "$.blast_radius"
          "SsmDocument.$"    = "$.ssm_document_arn"
          "SsmParameters.$"  = "$.ssm_parameters"
          "SpokeAccountId.$" = "$.spoke_account_id"
        }
      }
      TimeoutSeconds = var.approval_wait_timeout_seconds
      Next           = "RunApprovedAutomation"
      Catch = [
        {
          ErrorEquals = ["States.ALL"]
          Next        = "ApprovalDeniedOrTimedOut"
        }
      ]
    }
    ...
  }
})

Zwei States folgen, oben nicht gezeigt: RunApprovedAutomation ruft executor.py mit dem eigenen Output des Wait-States als Payload auf, und ApprovalDeniedOrTimedOut ist ein einfacher Fail-State, in den der obige Catch routet. slack-post postet eine Karte mit der Diagnose, dem Blast Radius, dem exakten SSM-Dokument und den Parametern, plus Approve/Deny-Links, die den Task-Token tragen, jeder zeigt auf eine API-Gateway-Route (/succeed, /fail), die von approval_callback.py bedient wird. Die gesamte Aufgabe dieser Lambda ist ein einziger API-Aufruf, send_task_success oder send_task_failure gegen den Token des geklickten Links, und ihre IAM-Policy gewährt beides auf Resource = "*" aus einem echten Grund: Keine der beiden APIs adressiert eine bestimmte Ausführung per ARN, nur den opaken Token, dieselbe Form, auf die Teil 2s InspectAndStopOwnExecutions-Statement für SSMs eigene Inspektions-APIs gestoßen ist. Es lohnt sich, es klar zu benennen: Diesen Token in den Query-String eines GET-Links zu kodieren ist eine echte Schwäche, ein weitergeleiteter oder geloggter Link ist eine weitergeleitete oder geloggte Freigabe, dokumentiert in slack_post.py statt versteckt, zu ersetzen durch einen signierten, einmal verwendbaren, datastore-gestützten Token, bevor dies gegen irgendetwas läuft, das zählt. Diese Routen tragen überhaupt keinen Authorizer: Token-Besitz ist die gesamte Zugriffskontrolle, sodass jeder, der den Link erhält, nicht nur der beabsichtigte Slack-Approver, ihn auslösen kann.

Erst bei Freigabe wird überhaupt etwas mit der Berechtigung erstellt, ein Spoke-Konto zu ändern. executor.py ist die einzige Lambda, deren Execution Role ops-mutate annehmen kann: Teil 2s Trust Policy nennt exakt die Rollen-ARN dieser Lambda als ihren einzigen Principal, und ihre eigene Credential-Lebensdauer ist absichtlich kurz:

session = assume_role(
    spoke_account_id,
    OPS_MUTATE_ROLE_NAME,
    "post-approval-executor",
    duration_seconds=300,
)
ssm = session.client("ssm", config=_boto_config)

response = ssm.start_automation_execution(
    DocumentName=ssm_document_arn,
    Parameters={k: [v] if not isinstance(v, list) else v for k, v in ssm_parameters.items()},
)

Fünf Minuten, kürzer als der Fünfzehn-Minuten-Default der Read-Tools: Dieses Credential existiert, um exakt einen API-Aufruf zu machen, nicht um wiederverwendbar für einen Follow-up herumzuliegen, den die Funktion nie beabsichtigt hat. Zwei Lambdas, zwei IAM-Rollen, ein enger Handoff über eine State-Machine-Ausführung, das ist es, was aus "das einzige mutierende Tool kann einen Spoke nicht direkt erreichen" eine Eigenschaft der Verdrahtung macht, kein Versprechen, das dieser Code hält, indem er sich entscheidet, boto3s STS-Client nicht zu importieren.

Was der Agent sieht, wenn ein Tool abgelehnt wird

Drei unterschiedliche Arten von "Nein" existieren hier, es lohnt sich, sie auseinanderzuhalten. Eine Ablehnung auf Anwendungsebene: ssm-executes Allowlist-Check gibt {"status": "rejected", "reason": ...} als normales Tool-Ergebnis zurück, keine Exception, keine Ausführung erstellt; der Agent meldet es schlicht. Eine Ablehnung auf IAM-Ebene: Ein fehlendes lambda:InvokeFunction-Grant oder Assume-Role-Grant lässt den Aufruf mit einer AWS-AccessDeniedException fehlschlagen, die als Tool-Fehler auftaucht, nicht als Tool-Ergebnis, die Hard-Stop-Unterscheidung, auf die Teil 3s System-Prompt den Triage-Agenten schon trainiert hat zu achten. Und, sobald cedar_policy_engine_arn mit mode = "ENFORCE" gesetzt ist, eine Ablehnung auf Policy-Ebene: Cedar weist einen Aufruf ab, bevor er überhaupt die Lambda erreicht, nach Regeln außerhalb des eigenen Codes irgendeines Tools; in LOG_ONLY wird derselbe Aufruf geloggt, aber durchgelassen, weshalb es wichtig ist, eine neue Policy dort gegen echten Traffic zu staging, bevor man auf ENFORCE umschaltet, wo eine zu breite Regel Aufrufe scheitern lässt, die nie ein Problem waren, ohne Tool-Level-Log, das erklärt, warum.

Fehlerbilder, auf die man achten sollte

Fünf Dinge, die es wert sind zu wissen, bevor diese Pipeline gegen einen echten Vorschlag läuft. Die zwei SSM-Dokument-Allowlists, ssm-executes lokale Kopie und ops-mutates eigene IAM-Policy, leben in zwei unabhängig angewendeten Terraform-Modulen (diesem hier und 00-foundation), und nichts erzwingt, dass sie identisch bleiben; ein in der lokalen Liste fehlendes Dokument wird abgelehnt, bevor eine Approval-Anfrage existiert, ein in der IAM-Liste fehlendes Dokument bekommt eine Anfrage erstellt für etwas, das ops-mutate dann auszuführen verweigert, und der zweite Fall ist schlimmer, weil ein Mensch schon auf Freigeben geklickt hat, bevor er es entdeckt. Einen der beiden TimeoutSeconds-Werte zu vergessen, den des Wait-States oder den der Ausführung, verwandelt einen unbeantworteten Slack-Vorschlag in eine pausierte Ausführung, die für immer altert, statt laut in ApprovalDeniedOrTimedOut zu scheitern, genau das, was das Human-Approval-Tutorial von Step Functions als den häufigsten Weg markiert, wie dieses Muster bricht. Und die eigene Schwäche des Approval-Links besteht, bis er durch einen signierten, einmal verwendbaren, datastore-gestützten Token ersetzt wird: Jeden Slack-Channel, an den dies postet, so behandeln, als wäre eine weitergeleitete Nachricht gleichbedeutend mit einer weitergeleiteten Freigabe. Ein GET-Request, der Zustand mutiert, lädt außerdem zu versehentlicher Freigabe ein: Slacks eigener Link-Unfurler, unternehmenseigene Link-Security-Scanner und Browser-Prefetch können diese URL alle abrufen, bevor ein Mensch je klickt, und kein Code-Pfad hier unterscheidet diesen Abruf von einer bewussten Freigabe. Und approval_callback.py reicht das Proposal-JSON direkt aus dem Query-String des geklickten Links an send_task_success weiter, ohne es gegen das zu prüfen, was Step Functions für diesen Token tatsächlich aufgezeichnet hat, sodass ein manipulierter Link eine andere ssm_document_arn oder ssm_parameters unterschieben kann, als der Mensch in Slack gesehen hat; ops-mutates eigene IAM-Allowlist begrenzt den Schaden auf etwas bereits Erlaubtes, aber die spezifische Dokument-und-Parameter-Kombination, die ein Mensch freigegeben hat, ist nicht dieselbe, die tatsächlich verdrahtet ausgeführt wird.

Weiterlesen

Das vollständige Modul terraform/20-gateway-tools/ und agents/tools/, einschließlich der IAM-Policy-Dokumente und der drei anderen Gateway-Targets, die aus den obigen Ausschnitten gekürzt wurden, liegen im begleitenden Repository unter github.com/flightlesstux/agents-on-call. Für die Datenbank- und Infrastruktur-Seite, solche Plattformen in diesem Maßstab zu betreiben, gibt es die Field Notes auf ercan.cloud, und der Hub liegt auf ercanermis.com.