Enable multi-account deployments
Overview
Add the ability to deploy agents across AWS accounts with Loom operating in a shared services account. Today, Loom deploys agents exclusively into the account where the backend runs — AWS_ACCOUNT_ID is a single environment variable, boto3 clients use the default credential chain, and IAM roles are created in the deployment account only. This issue introduces a deployment target model where admins register additional accounts and users select them when deploying agents.
Cross-account deployments require an STS AssumeRole flow: Loom's backend assumes a pre-provisioned role in the target account to create IAM execution roles, upload artifacts, manage secrets, and call AgentCore APIs. This issue includes the cross-account role setup to simplify enablement for new accounts.
Requirements
R1: Account Registration and Management
Admins should be able to register additional AWS accounts as deployment targets. Each account registration includes:
- Account ID (required): The 12-digit AWS account ID.
- Display name (required): A human-readable name (e.g., "Production - Payments").
- Line of business (required): Organizational grouping (e.g., "Payments", "Risk", "Platform").
- Environment (optional): Deployment tier label (e.g., "dev", "staging", "production").
- Cross-account role ARN (required): The IAM role in the target account that Loom will assume for deployments (
arn:aws:iam::{account_id}:role/loom-deployment-role).
- External ID (required): A unique identifier for the cross-account trust relationship to prevent confused deputy attacks.
- Supported regions (required): List of AWS regions where AgentCore deployments are allowed in this account.
- Enabled flag: Whether this account is currently available as a deployment target.
Admins should be able to list, update, and deactivate registered accounts. Deactivating an account should not affect existing deployed agents — it only prevents new deployments to that account.
The Loom backend's own account should be automatically available as a deployment target (the "local" account) without requiring explicit registration.
Backend changes:
- New model:
backend/app/models/account.py — DeploymentAccount table with the fields above.
- New router:
backend/app/routers/accounts.py — CRUD endpoints for account management (POST /api/accounts, GET /api/accounts, PATCH /api/accounts/{id}, DELETE /api/accounts/{id}).
- Admin-only access: Account management endpoints should be restricted to admin users.
R2: Deployment Target Selection
Users should be able to select a target account when deploying an agent (both custom agent and managed agent harness flows). The selection should support browsing by metadata — not just raw account IDs:
- Account selector: Dropdown or searchable list showing display name, line of business, environment, and account ID.
- Filtering: Filter accounts by line of business and environment.
- Region selector: After selecting an account, show only the regions that account supports.
- Default behavior: If no target account is selected, deploy to the local account (current behavior).
The selected target account ID and region should be stored on the agent record and used throughout the deployment lifecycle (role creation, artifact upload, AgentCore API calls, invocation).
Frontend changes:
frontend/src/components/AgentRegistrationForm.tsx — Add deployment target section above existing fields for both "Custom Agent" and "Managed Agent" forms.
- New component for the account selector with metadata display and filtering.
frontend/src/api/accounts.ts — API client for account CRUD endpoints.
Backend changes:
backend/app/models/agent.py — Add target_account_id field (nullable; null means local account). Add cross_account_role_arn field to store the role used for this deployment.
backend/app/routers/agents.py — Accept target_account_id in deploy requests. Look up the DeploymentAccount record to resolve the cross-account role ARN, external ID, and validate the requested region is supported.
R3: Cross-Account Deployment Execution
When deploying to a non-local account, the backend must assume the target account's cross-account role before performing deployment operations. This affects the following operations:
- IAM role creation (
backend/app/services/iam.py): Create the agent's execution role in the target account using assumed credentials.
- Artifact upload (
backend/app/services/deployment.py): Upload the agent zip to an S3 bucket in the target account. The target account must have a designated artifact bucket (stored in DeploymentAccount or discoverable via convention, e.g., loom-artifacts-{account_id}).
- Secret management (
backend/app/services/deployment.py): Store agent secrets in the target account's Secrets Manager.
- AgentCore API calls (
backend/app/services/deployment.py, backend/app/services/agentcore.py): Call bedrock-agentcore-control and bedrock-agentcore APIs in the target account/region.
- Agent invocation (
backend/app/routers/agents.py): When invoking a cross-account agent, assume the role to call invoke_agent_runtime or invoke_harness in the target account.
Implementation approach:
- New utility:
backend/app/services/sts.py — STS AssumeRole helper that accepts a role ARN and external ID, returns a boto3 Session with temporary credentials. Handle credential caching and expiration (STS tokens are valid for up to 1 hour).
- Modify all boto3 client construction in deployment and invocation paths to accept an optional
session parameter. When a session is provided (cross-account), use it instead of the default credential chain.
- The cross-account role assumption should happen at the start of the deployment background task and the assumed session should be passed through to all downstream operations.
R4: Cross-Account Role Setup
Provide a CloudFormation template that target account admins can deploy to set up the required cross-account trust relationship. This template should create:
- Deployment role (
loom-deployment-role): The role that Loom's backend assumes.
- Trust policy: Allow
sts:AssumeRole from Loom's backend account with the external ID condition.
- Permissions: IAM role creation/management, S3 artifact bucket access, Secrets Manager access, AgentCore control plane and data plane API access, CloudWatch Logs and X-Ray for observability.
- Artifact S3 bucket (optional): A bucket for storing agent deployment artifacts, with a lifecycle policy to clean up old artifacts.
- Outputs: Export the role ARN, external ID, and bucket name for easy registration in Loom.
Files:
- New template:
iac/cross-account/template.yaml — CloudFormation template parameterized with the Loom backend account ID and external ID.
- New makefile target:
make cross-account.deploy — Deploy the cross-account setup template to a target account.
- Documentation in the template describing the permissions granted and the setup flow.
The setup flow should be:
- Admin deploys the CloudFormation template in the target account, providing Loom's account ID.
- Template outputs the role ARN and external ID.
- Admin registers the account in Loom using these values.
- Loom validates the cross-account role assumption works before marking the account as active.
R5: Agent Lifecycle Operations Across Accounts
All existing agent lifecycle operations must work correctly for cross-account agents:
- Refresh (
POST /api/agents/{id}/refresh): Assume the target account role before calling get_agent_runtime or get_harness to fetch status.
- Delete (
DELETE /api/agents/{id}): Assume the target account role before deleting the runtime/harness, endpoint, execution role, secrets, and artifacts in the target account.
- Update/Redeploy: If supported, assume the target account role before updating the runtime configuration.
The agent record already stores region and account_id (parsed from the ARN). For cross-account agents, look up the DeploymentAccount record by target_account_id to get the cross-account role ARN needed for these operations.
Handle the case where a DeploymentAccount record has been deactivated or deleted but agents still exist in that account — lifecycle operations should still work using the cross_account_role_arn stored on the agent record itself, not just the account registration.
Implementation Guidance
Database Schema
New table: deployment_accounts
id: UUID (primary key)
account_id: String (unique, indexed)
display_name: String
line_of_business: String (indexed)
environment: String (nullable)
cross_account_role_arn: String
external_id: String
supported_regions: JSON (list of strings)
artifact_bucket: String (nullable)
enabled: Boolean (default true)
created_at: DateTime
updated_at: DateTime
Agent table additions:
target_account_id: String (nullable, FK to deployment_accounts.account_id)
cross_account_role_arn: String (nullable — snapshot at deploy time for resilience)
STS Session Management
# backend/app/services/sts.py
def get_cross_account_session(role_arn: str, external_id: str, region: str) -> boto3.Session:
"""Assume a cross-account role and return a boto3 Session with temporary credentials."""
sts = boto3.client("sts")
response = sts.assume_role(
RoleArn=role_arn,
ExternalId=external_id,
RoleSessionName="loom-deployment",
DurationSeconds=3600,
)
credentials = response["Credentials"]
return boto3.Session(
aws_access_key_id=credentials["AccessKeyId"],
aws_secret_access_key=credentials["SecretAccessKey"],
aws_session_token=credentials["SessionToken"],
region_name=region,
)
IAM Permissions for Loom Backend
The Loom backend's ECS task role (backend/iac/ecs.yaml) needs sts:AssumeRole permission scoped to the cross-account deployment roles. Add a policy that allows assuming roles matching a naming convention (e.g., arn:aws:iam::*:role/loom-deployment-role) or use a resource tag condition.
Out of Scope
- Multi-region Loom backend deployment (Loom itself runs in one account/region; it deploys agents cross-account).
- Cross-account artifact sharing via S3 bucket policies (each target account has its own bucket).
- Federated identity or SSO integration for account access.
- Cost allocation or chargeback per target account.
- Automated cross-account role rotation or credential vending.
Enable multi-account deployments
Overview
Add the ability to deploy agents across AWS accounts with Loom operating in a shared services account. Today, Loom deploys agents exclusively into the account where the backend runs —
AWS_ACCOUNT_IDis a single environment variable, boto3 clients use the default credential chain, and IAM roles are created in the deployment account only. This issue introduces a deployment target model where admins register additional accounts and users select them when deploying agents.Cross-account deployments require an STS AssumeRole flow: Loom's backend assumes a pre-provisioned role in the target account to create IAM execution roles, upload artifacts, manage secrets, and call AgentCore APIs. This issue includes the cross-account role setup to simplify enablement for new accounts.
Requirements
R1: Account Registration and Management
Admins should be able to register additional AWS accounts as deployment targets. Each account registration includes:
arn:aws:iam::{account_id}:role/loom-deployment-role).Admins should be able to list, update, and deactivate registered accounts. Deactivating an account should not affect existing deployed agents — it only prevents new deployments to that account.
The Loom backend's own account should be automatically available as a deployment target (the "local" account) without requiring explicit registration.
Backend changes:
backend/app/models/account.py—DeploymentAccounttable with the fields above.backend/app/routers/accounts.py— CRUD endpoints for account management (POST /api/accounts,GET /api/accounts,PATCH /api/accounts/{id},DELETE /api/accounts/{id}).R2: Deployment Target Selection
Users should be able to select a target account when deploying an agent (both custom agent and managed agent harness flows). The selection should support browsing by metadata — not just raw account IDs:
The selected target account ID and region should be stored on the agent record and used throughout the deployment lifecycle (role creation, artifact upload, AgentCore API calls, invocation).
Frontend changes:
frontend/src/components/AgentRegistrationForm.tsx— Add deployment target section above existing fields for both "Custom Agent" and "Managed Agent" forms.frontend/src/api/accounts.ts— API client for account CRUD endpoints.Backend changes:
backend/app/models/agent.py— Addtarget_account_idfield (nullable; null means local account). Addcross_account_role_arnfield to store the role used for this deployment.backend/app/routers/agents.py— Accepttarget_account_idin deploy requests. Look up theDeploymentAccountrecord to resolve the cross-account role ARN, external ID, and validate the requested region is supported.R3: Cross-Account Deployment Execution
When deploying to a non-local account, the backend must assume the target account's cross-account role before performing deployment operations. This affects the following operations:
backend/app/services/iam.py): Create the agent's execution role in the target account using assumed credentials.backend/app/services/deployment.py): Upload the agent zip to an S3 bucket in the target account. The target account must have a designated artifact bucket (stored inDeploymentAccountor discoverable via convention, e.g.,loom-artifacts-{account_id}).backend/app/services/deployment.py): Store agent secrets in the target account's Secrets Manager.backend/app/services/deployment.py,backend/app/services/agentcore.py): Callbedrock-agentcore-controlandbedrock-agentcoreAPIs in the target account/region.backend/app/routers/agents.py): When invoking a cross-account agent, assume the role to callinvoke_agent_runtimeorinvoke_harnessin the target account.Implementation approach:
backend/app/services/sts.py— STS AssumeRole helper that accepts a role ARN and external ID, returns a boto3Sessionwith temporary credentials. Handle credential caching and expiration (STS tokens are valid for up to 1 hour).sessionparameter. When a session is provided (cross-account), use it instead of the default credential chain.R4: Cross-Account Role Setup
Provide a CloudFormation template that target account admins can deploy to set up the required cross-account trust relationship. This template should create:
loom-deployment-role): The role that Loom's backend assumes.sts:AssumeRolefrom Loom's backend account with the external ID condition.Files:
iac/cross-account/template.yaml— CloudFormation template parameterized with the Loom backend account ID and external ID.make cross-account.deploy— Deploy the cross-account setup template to a target account.The setup flow should be:
R5: Agent Lifecycle Operations Across Accounts
All existing agent lifecycle operations must work correctly for cross-account agents:
POST /api/agents/{id}/refresh): Assume the target account role before callingget_agent_runtimeorget_harnessto fetch status.DELETE /api/agents/{id}): Assume the target account role before deleting the runtime/harness, endpoint, execution role, secrets, and artifacts in the target account.The agent record already stores
regionandaccount_id(parsed from the ARN). For cross-account agents, look up theDeploymentAccountrecord bytarget_account_idto get the cross-account role ARN needed for these operations.Handle the case where a
DeploymentAccountrecord has been deactivated or deleted but agents still exist in that account — lifecycle operations should still work using thecross_account_role_arnstored on the agent record itself, not just the account registration.Implementation Guidance
Database Schema
New table:
deployment_accountsAgent table additions:
STS Session Management
IAM Permissions for Loom Backend
The Loom backend's ECS task role (
backend/iac/ecs.yaml) needssts:AssumeRolepermission scoped to the cross-account deployment roles. Add a policy that allows assuming roles matching a naming convention (e.g.,arn:aws:iam::*:role/loom-deployment-role) or use a resource tag condition.Out of Scope