Cloud
Loom + FAST: Wiring an Enterprise Agent Control Plane to a Customer-Facing App on AgentCore
AWS quietly shipped two open-source answers to two different agent problems. I deployed both, wired them together, then tried to break the architecture. Here is what holds, what doesn't, and the pattern for letting customers 'create agents' without losing governance.
· 9 MIN READ
Two open-source projects landed in the AWS agent ecosystem recently, and they answer two questions that teams keep conflating:
- Loom answers “how does my platform team operate a fleet of agents?” — an enterprise control plane for Amazon Bedrock AgentCore: agent lifecycle, IAM and credential management, MCP/A2A server governance with approval workflows, HITL policies, cost tracking, admin analytics.
- FAST (Fullstack Solution Template for AgentCore) answers “how does my product team ship a customer-facing agent app this week?” — a fork-and-customize template: React frontend on Amplify Hosting, a customer Cognito pool, an AgentCore runtime, all deployed with a couple of CDK commands. The samples gallery shows what people build on it.
I spent a day deploying both, wiring them together, and then red-teaming the result. The integration is cleaner than I expected — and the places where it breaks are instructive.
TL;DR
- FAST completes Loom; neither replaces the other — one control plane governs the fleet, one FAST fork per product ships the customer experience.
- The entire integration contract is a JWT authorizer: deploy the agent from Loom, bound to the customer identity pool.
- When customers want to "create agents": give them logical agents through a BFF — governance by catalog, isolation by key design, metering by chokepoint.
- Keep infrastructure creation in the control plane; never expose its API to customer identities.
The one-line integration contract
Loom is a control plane: its UI is for platform operators, behind its own identity pool, and you would never put customers in front of it. FAST is a data plane: its frontend calls the AgentCore runtime directly from the browser with the customer’s JWT. No backend-for-frontend in the baseline.
So how do you make a FAST app talk to a Loom-governed agent? One governance act:
Deploy the agent from Loom with a JWT authorizer bound to the customer identity pool — discovery URL pointing at the customer pool, allowed clients pinned to the customer app’s client ID.
In AgentCore terms, that’s this configuration on the runtime — set from the control plane, never touched by the product team:
{
"authorizerConfiguration": {
"customJWTAuthorizer": {
"discoveryUrl": "https://cognito-idp.<region>.amazonaws.com/<customer-pool-id>/.well-known/openid-configuration",
"allowedClients": ["<customer-app-client-id>"]
}
}
}
That’s the entire contract. The platform team decides, in the control plane, which identity population may invoke which runtime. The customer app never holds AWS credentials; AgentCore validates the customer’s JWT at the door.
Two small customizations were needed in the FAST fork (both upstreamable):
- The frontend deploy script assumes you want FAST’s own bundled runtime — an env-var override lets it point at any runtime ARN instead.
- FAST’s SSE parser expects
{"data": "..."}chunk objects; Loom’s Strands agent streams bare JSON strings. One extra case in the parser handles both.
With that, a customer signs into the FAST app, chats, and the tokens stream from a runtime the platform team fully governs — model, prompt, tools, approval policies, all changeable in Loom with zero frontend redeploys. Keep the internal (SigV4) and customer-facing (JWT) variants as two runtimes with the same brain: each can be revoked independently, and your admin tooling keeps working when you rebind the customer one.
Then I challenged it: “the customer wants to create an agent”
This is where architectures like this usually fall over, so let’s push. Your product grows, and now customers want to create their own agents. How does that work against an enterprise governance layer?
First, interrogate the premise. “Create an agent” hides two very different things:
| Logical agent | Physical agent | |
|---|---|---|
| What it is | A name, a prompt, a model choice, a tool selection — configuration | A new AgentCore runtime — container, IAM role, authorizer — infrastructure |
| Who needs it | ~Every tenant | The rare tenant with isolation/compliance requirements |
| Creation cost | A database row | Minutes, account quotas, IAM sprawl |
Almost every “create your own agent” product you know sells the first and never provisions the second per customer. That distinction decides the whole design, because there are only three ways to wire customer-driven creation, and two of them hurt:
Anti-pattern
1. Expose the control plane's API to customer identities
Dead on arrival. In Loom's case, agent:write lives in the same backend as security:write (IAM roles, credential providers, secrets). Bridging customer tokens into it turns every authorization bug into a tenant-to-platform escalation. A control plane has no tenancy model because it was never supposed to.
Anti-pattern
2. Use the control plane as a provisioning broker
The product backend calls it M2M. Tempting, and still wrong: no idempotency contract, no per-tenant quotas, shared execution roles — meaning every tenant's agent runs with the same IAM identity, and cross-tenant data access is one bug away. You'd rebuild half the product to make it multi-tenant. That's not integration, that's a fork.
Recommended
3. Logical agents on a shared, governed runtime
Tenant "agents" are rows in the product's database. One multi-tenant runtime — deployed and governed by the control plane — interprets the tenant's config per request.
Option 3 is the recommendation, and I prototyped it to make sure it isn’t just slideware.
The Tier-1 pattern, verified
The prototype: a TenantAgent DynamoDB table (partition key = the tenant’s identity from the verified JWT), and a small backend-for-frontend that is the only path to the runtime:
Everything worth claiming, I tested with real requests:
- Self-service creation — a customer created a “CostBot” persona (FinOps instructions, Haiku-class model) as a
POST /agentscall. A database row; zero infrastructure. - Governance by catalog — creating an agent on a non-approved model returns
403with the allowed list. The enterprise constrains the building blocks (model catalog, approved tool registry); customers compose freely inside the box. No human approval gate per agent — that would kill self-service. - Structural tenant isolation — tenant B listing agents gets
[]; tenant B invoking tenant A’s agent gets404. Not because of a check someone remembered to write, but because the partition key comes from the verified token, not from the request body. - Metering — every call writes tokens/duration per tenant. This is the piece the “browser calls the runtime directly” pattern can never give you, and it’s why the BFF stops being optional the moment you productize: per-tenant billing needs a chokepoint you own.
One finding I didn’t expect: the control-plane system prompt and the tenant’s persona instructions layer surprisingly well. When my test persona tried to make cost the only decision lens, the platform prompt pushed back — cost-focused answer, but reliability and security not subordinated. Guardrails outranking customization, observable in the output.
And for the tenant that genuinely needs a dedicated runtime? Product-owned provisioning inside IAM permission boundaries, mandatory tenant tags, and auto-registration into the AWS Agent Registry — so the control plane stays the fleet observatory even for runtimes it didn’t create. The registry, not the control plane’s API, is the meeting point.
The security lesson I earned
Prototype shortcut: I exposed the BFF on a public endpoint with JWT validation in the Lambda code. Within hours, automated security tooling flagged the function as world-accessible and stripped the public-access policy. It was right, and my enterprise policy agrees: authentication belongs at the edge, not in your handler. The compliant shape is API Gateway with a Cognito authorizer (the request never reaches your code unauthenticated), WAF in front, or a VPC-internal ALB if the callers are private. “Auth in code behind a public URL” is a pattern scanners exist to kill — let them.
Verdict
FAST completes Loom; neither replaces the other. One installation of the control plane governs the fleet; one FAST fork per product ships the customer experience; a JWT authorizer is the contract between them. When customers want to “create agents,” give them logical agents through a BFF and keep infrastructure creation in the control plane — governance by catalog, isolation by key design, metering by chokepoint.
The nicest property of the whole architecture: the day the platform team swaps the model, tightens a guardrail, or revokes a tool, every customer conversation picks it up on the next request — and no product team redeploys anything.
Go build
Everything in this post is open source — fork, deploy, break it yourself:
- Loom — the control plane (FastAPI + React, three-phase deployment from local SQLite to ECS Fargate)
- FAST — the customer-app template (CDK or Terraform, deploys in minutes)
- FAST samples gallery — what others have built on it
And a question for you, because I know several of you are building agent platforms right now: how are you handling customer-created agents — logical configs, dedicated runtimes, or something else entirely? The comments are open below; I read all of them.
ABOUT THE AUTHOR
ONE LETTER A MONTH · NO TRACKER · UNSUBSCRIBE ANYTIME
CONTINUE READING
Related dispatches
Comments
Sign in to leave a comment
