awslabs / awslabs/loom

Output infrastructure as code

Open
#27 0 comments 0 reactions 1 assignee Claimed by @heeki View on GitHub
enhancement
Dominant language
Python
Stars
185
Forks
41
Avg merge
8h 6m
Merged PRs (30d)
2

Description

## Overview

Loom currently deploys agents by calling the AgentCore control-plane API directly from the backend (`create_agent_runtime`/`update_agent_runtime` in `backend/app/services/deployment.py`). Some teams can't or don't want Loom's backend to hold IAM permissions to create/update runtimes directly in their account — they want Loom to produce infrastructure-as-code artifacts (CloudFormation, Terraform) that their own CI/CD or platform team applies through existing change-management processes instead.

This issue adds an "infrastructure as code" mode: a global toggle to disable Loom's automatic API-driven deployment, paired with pre-generated (not on-the-fly) CloudFormation and Terraform templates that describe an agent runtime, parameterized per-deployment via a separate config file — following the same pattern Loom already uses for the Strands agent harness: a static, checked-in artifact configured at deploy time via environment/config injection, not code generated per request.

## Context

### Current State

**How the Strands harness pattern works today (the model to follow)**
- The Strands agent artifact lives at `agents/strands_agent/` (`src/handler.py`, `src/agent.py`, `src/config.py`) and is a **static, checked-in template** — per `agents/strands_agent/README.md` L7, it's "configured entirely via environment variables and/or a JSON configuration file — no user-authored code is generated." Feature flags toggle integrations at deploy time via config, not by generating new code per agent.
- `build_agent_artifact()` (`backend/app/services/deployment.py` L93–170) copies this static source tree, installs dependencies, zips it, and uploads it to S3 at a timestamped prefix: `loom-artifacts/strands_agent/{timestamp}/agent.zip` (L163). The artifact itself never changes structurally between deployments — only the injected config (`AGENT_CONFIG_JSON` env var) changes.
- This is the pattern R2/R3 should mirror: pre-built, checked-in CloudFormation/Terraform templates, with per-deployment values supplied through a separate parameter/config file rather than templated/generated Python string-building at request time.

**Current API-driven deployment flow (what IaC mode needs to replace)**
- `backend/app/services/deployment.py`: `create_runtime()` (L173–250) and `update_runtime()` (L358–427) are the two entry points that call `create_agent_runtime`/`update_agent_runtime` directly via boto3. Full parameter surface an IaC template needs to cover: `name`, `description`, `role_arn`, `env_vars`, `network_mode` (`PUBLIC`/`VPC`) + `vpc_subnet_ids`/`vpc_security_group_ids`, `protocol` (`HTTP`/`MCP`), `lifecycle_config`, `authorizer_config`, `artifact_bucket`/`artifact_prefix` (S3 location of the built artifact), and `tags`
- `agentRuntimeArtifact.codeConfiguration` (L218–229) hardcodes `runtime: PYTHON_3_13` and `entryPoint: ["opentelemetry-instrument", "src/handler.py"]` — these are constants an IaC template can bake in directly rather than parameterize
- `_merge_tags()` (L25–44) combines global tag policies with per-deployment overrides — an IaC template's `Tags:`/`tags = {}` block should reproduce the same merged result so tagging behavior doesn't silently diverge between the two deployment modes
- `backend/app/routers/agents.py` orchestrates the full create/update flow: builds the artifact, resolves/creates the IAM execution role, then calls `create_runtime`/`update_runtime` (L1460, L1818, L3468, L4035) and persists the result to the `Agent` model

**Existing toggle pattern to follow**
- `backend/app/models/site_setting.py` is a generic key/value settings table (`SiteSetting.key`/`.value`), accessed via `get_site_setting(db, key)` and updated via `PUT /site/{key}` (`backend/app/routers/settings.py` L282–319). Existing keys include `cpu_io_wait_discount`, `loom_registry_id`, `enabled_model_ids` — this is the established mechanism for global backend behavior toggles and is the natural home for a new `iac_deployment_mode` (or similarly named) setting, rather than adding a new column to `Agent`, since this is a deployment-pipeline-wide decision, not a per-agent one
- No existing per-agent boolean disables API deployment today — `Agent.source` (`register`/`deploy`/`harness`) is a discriminator set once at creation, not a runtime toggle

**Existing CloudFormation house style (`backend/iac/*.yaml`, `shared/iac/*.yaml`)**
- SAM templates: `AWSTemplateFormatVersion: "2010-09-09"` + `Transform: AWS::Serverless-2016-10-31` header
- Parameter naming: `p`-prefixed (`pAgentName`, `pRoleSuffix`), each with a `Description` noting where the value comes from; Outputs are `o`-prefixed (`oEcsSecurityGroupId`)
- Boolean-like parameters are modeled as `Type: String` with `AllowedValues: ["true","false"]` plus a `Default`, and a `Conditions:` block derives real conditionals from them (e.g. `shared/iac/role.yaml` L28–34, L45–47) — not native CFN `Boolean` type
- Consistent `loom:application`/`loom:group`/`loom:owner` tags sourced from `pTagApplication`/`pTagGroup`/`pTagOwner` parameters (`shared/iac/role.yaml` L35–43, L54–60)
- A new agent-runtime CloudFormation template should match this style so it looks native to the rest of `iac/`, even though it will live in a new location (see Key Files)

**No existing Terraform in the repo**
- `find . -iname "*.tf"` and a grep for `terraform` return nothing anywhere in the repo — Terraform support is greenfield, with no existing convention to mirror. R3 should establish its own house style (e.g. `variables.tf`/`main.tf`/`outputs.tf` split, `.tfvars.example` for per-deployment values) since there's nothing else to be consistent with

### Key Files

- `backend/app/services/deployment.py` — `create_runtime`, `update_runtime`, `_merge_tags`, `build_agent_artifact` (the full parameter surface to templatize)
- `backend/app/routers/agents.py` — deploy/redeploy orchestration (L1460, L1818, L3468, L4035) that IaC mode needs to bypass
- `agents/strands_agent/` — the static-artifact pattern to mirror for how templates are structured, checked in, and configured
- `backend/app/models/site_setting.py`, `backend/app/routers/settings.py` — existing global-toggle mechanism
- `backend/iac/*.yaml`, `shared/iac/*.yaml` — existing CloudFormation house style to match
- New: a dedicated directory for generated templates, e.g. `iac-templates/cloudformation/` and `iac-templates/terraform/` (naming TBD during implementation, kept separate from `backend/iac/`/`shared/iac/` since those describe Loom's own infrastructure, not the agent runtimes Loom manages)

## Requirements

### R1: Disable automatic API-driven deployment and produce infrastructure-as-code artifacts instead

- Add a new global `SiteSetting` key (e.g. `iac_deployment_mode`, values `"api"`/`"cloudformation"`/`"terraform"`, default `"api"`) following the existing pattern in `backend/app/models/site_setting.py`/`backend/app/routers/settings.py`, rather than a per-agent column, since this changes how the whole deployment pipeline behaves
- When set to a non-`"api"` value, the deploy/redeploy code paths in `backend/app/routers/agents.py` (L1460, L1818, L3468, L4035) must skip the `create_runtime`/`update_runtime` boto3 calls entirely — no partial API call followed by template generation, and no IAM permissions required for `bedrock-agentcore-control:CreateAgentRuntime`/`UpdateAgentRuntime` in this mode
- Instead, the backend still runs `build_agent_artifact()` (unchanged — the artifact still needs to land in S3 for IaC to reference) and then produces a per-deployment parameter/config file (see R2/R3) containing every value that would otherwise have gone into the `create_agent_runtime`/`update_agent_runtime` API call: role ARN, env vars, network config, protocol, lifecycle config, authorizer config, artifact bucket/prefix, tags
- Surface this mode in the UI: when IaC mode is active, the deploy button/flow should present a download (or link to generated files) instead of showing live deployment progress, since there is no API call to poll status for
- Document that agents deployed via this path won't have `Agent.status`/`endpoint_status` populated from AgentCore polling until the user applies the generated IaC and Loom is pointed at the resulting runtime ARN (exact reconciliation flow — e.g. a "register existing runtime" step — is an implementation detail to work out, not fully specified here)

### R2: Support CloudFormation out of the box

- Add a pre-generated, checked-in CloudFormation template (e.g. `iac-templates/cloudformation/agent-runtime.yaml`) that declares an `AWS::BedrockAgentCore::Runtime` (and endpoint) resource, covering the same parameter surface as `create_runtime`/`update_runtime` in `deployment.py` (role ARN, artifact S3 location, network mode/subnets/security groups, protocol, lifecycle config, authorizer config, env vars, tags)
- Follow the existing house style from `backend/iac/*.yaml`/`shared/iac/*.yaml`: `p`-prefixed parameters with descriptions, `o`-prefixed outputs, string-typed booleans with `Conditions:`, and `loom:*` tags — so this template feels native alongside Loom's other CloudFormation
- The template itself must be static and checked into the repo — R1's "produce IaC" step writes a **parameter values file** (e.g. a CloudFormation parameters JSON, `cfn-params.json`) per deployment, not a freshly rendered template; the template referenced/shipped with Loom does not change per agent
- Include a short README alongside the template documenting how to deploy it (`aws cloudformation deploy --template-file ... --parameter-overrides file://cfn-params.json`)

### R3: Support Terraform out of the box

- Add a pre-generated, checked-in Terraform configuration (e.g. `iac-templates/terraform/agent-runtime/{main,variables,outputs}.tf`) covering the same parameter surface as R2/R1, using the `aws_bedrockagentcore_runtime` (and endpoint) resource(s)
- Since there's no existing Terraform in this repo to match, establish a simple, conventional structure: `variables.tf` for inputs, `main.tf` for the resource block(s), `outputs.tf` for the resulting runtime ARN/endpoint ARN
- R1's "produce IaC" step writes a per-deployment `.tfvars` file (e.g. `agent-.auto.tfvars` or similar), not a regenerated `.tf` file — the module itself is static and checked in, matching the CloudFormation approach in R2 and the Strands-harness precedent of static-artifact-plus-injected-config
- Include a short README alongside the module documenting how to apply it (`terraform init && terraform apply -var-file=...`)

## Testing

- Run backend tests: `cd backend && make test`
- Add unit tests for the new `iac_deployment_mode` setting: verify deploy/redeploy endpoints skip `create_runtime`/`update_runtime` when the mode is non-`"api"`, and that a parameter/config file is produced with the expected fields
- Validate the CloudFormation template with `aws cloudformation validate-template` (or `sam validate`, matching existing `backend/iac`/`shared/iac` tooling) as part of CI
- Validate the Terraform module with `terraform validate` and `terraform fmt -check`
- Manually deploy an agent in `"api"` mode to confirm no regression to the existing flow, then switch to `"cloudformation"` mode, generate a parameter file for a test agent, and confirm `aws cloudformation deploy` against that template produces a working runtime; repeat for `"terraform"` mode with `terraform apply`

## Out of Scope

- Generating CloudFormation/Terraform for Loom's *own* infrastructure (ECS, RDS, etc.) — that's already covered by `backend/iac/`/`shared/iac/`; this issue is only about the agent-runtime resources Loom currently creates via API on behalf of users
- A full drift-detection or state-reconciliation system between Loom's database and infrastructure applied outside of Loom (e.g. detecting that someone manually changed a CloudFormation-deployed runtime) — Loom only needs to produce the artifacts and parameter files; keeping Loom's `Agent` records in sync with externally-applied changes is a separate concern
- Supporting IaC tools beyond CloudFormation and Terraform (e.g. CDK, Pulumi) — not requested here
- Per-agent IaC-mode overrides — this issue scopes the toggle globally per R1; a future issue could revisit per-agent granularity if needed

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.