A deployment of awslabs/loom you drive from the
repo. Six components, three tiers (light / production / production-ha),
generated CI, and a naming scheme that lets many Loom instances coexist in one or
many AWS accounts without collision. Pinned to Loom v1.6.0. Built with
chant, though you run loomster by its verbs, not by
knowing chant.
Clone it and your agent already knows how to operate it:
git clone https://github.com/INTENTIUS/loomster
cd loomster && npm install
The checkout ships skills/loomster/SKILL.md, the capability map an agent reads on
its own — every lifecycle verb, the golden paths, and the guardrails that keep a
deploy from locking itself out. chant also serves an MCP server (chant serve mcp,
stdio) for inspecting and building the graph. From the checkout, ask your agent to
"stand up Loom locally" and it knows what to run.
Docs: Tutorial · Overview · Adoption · Run it locally · Tiers & targets · Screens · Naming
- The full stack builds Loom's real images and runs on Floci,
a local emulator, browsable at
localhost:8080, no AWS account. - The light tier is deployed end to end to a real AWS account. Loom served on a real ALB, backed by real RDS and Cognito.
productionis validated end to end on a real AWS account — 7/7 stacksCREATE_COMPLETE: RDS Proxy, PrivateLink, ACM on a custom domain, autoscaling, and the agents wave (the Strands assistant's code-config Runtime reachesREADYon Bedrock AgentCore, app served over HTTPS).production-hais validated end to end on a real account too — 7/7 stacks, adding Multi-AZ RDS + a live credential rotation, and it ran as a second instance alongsideproduction(multi-deployment).
just local-up # browsable, authenticated Loom (needs Docker, no AWS account)
just local-down # tear it down
Floci provides the managed pieces (RDS, Cognito, S3, ECR); the app tier runs from
a chant-generated docker-compose, browsable at http://localhost:8080. The web
app runs for real, and agents now deploy locally too — the Floci image emulates
Bedrock AgentCore's control plane — though invoking one returns a canned stub, not
real reasoning. See the local guide
and local caveats.
package.json consumes published @intentius/chant and its lexicons from npm. No sibling checkout, no codegen step. A fresh clone installs and builds on its
own, which is what CI does.
just install # npm install
just build # typecheck (helper, composites, project-local lint rules)
just lint # chant lint . — core rules + .chant/rules/ project-local rules
just test # vitest run
just check # all of the above
Six stacks, deployed in dependency order (chant graph --components):
| Component | Depends on | What it is |
|---|---|---|
shared-foundation |
— | ALB, ECS cluster, ECR, KMS, S3 artifact bucket, DNS, agent IAM role |
loom-cognito |
— | Cognito user pool, hosted-UI domain, resource server, clients |
loom-db |
shared-foundation |
RDS Postgres, Secrets Manager; production adds RDS Proxy + rotation |
loom-frontend |
shared-foundation |
The frontend ECS Fargate service |
loom-backend |
shared-foundation, loom-db, loom-cognito |
The backend ECS Fargate service |
loom-agents |
shared-foundation, loom-cognito, loom-backend |
The Bedrock AgentCore agents — the Strands assistant (code-config Runtime, every tier). No-code harnesses are created on demand via Loom's app; a BYO container agent can be pre-provisioned with LOOM_HARNESS_AGENT_IMAGE_URI |
loom-backend / loom-frontend each run build, publish, apply, verify:
docker-build, then publish-image promoted by digest, then cfn-deploy, then
(against real AWS only) wait-steady-state and health-gate, with a
rollback-previous compensation phase. The runtime Verify checks are skipped
against Floci, which takes every stack to CREATE_COMPLETE but does not run the
app workload. Cross-stack inputs (cluster ARN, security group, target group, the
DB secret, the Cognito pool, and more) resolve via stackOutput(...).
downstream-stub in the graph is a verification-only stack, not part of Loom. It
consumes shared-foundation's outputs to prove they resolve for a real consumer.
Deployment is via GitHub Actions (.github/workflows/deploy.yml), gated
so it stays inert until you opt in:
- Repo variable
DEPLOY=true. - A GitHub environment named
productionholding the AWS credentials the job assumes.
The job vendors Loom's source, configures AWS credentials + ECR login, and runs
chant run --components all --env "$LOOM_ENV", the dependency-ordered
orchestrator. It derives the wave order from each component's dependsOn, builds
and publishes the images by digest, and applies each stack. See the
Tutorial for the
tier-by-tier walkthrough.
loom-backend / loom-frontend build Loom's actual images, but Loom's source
isn't vendored into this repo. It lives upstream at
awslabs/loom, pinned v1.6.0. Fetch it
before a real chant run deploy or a manual docker build:
npm run vendor # or: just vendor
scripts/vendor-loom.sh resolves refs/tags/v1.6.0, checks it against a sha
pinned in the script (a tag is mutable, so this fails loudly if it's ever
force-moved), then sparse-checks out just backend/, frontend/, and agents/
into vendor/loom (gitignored).
Not required for typecheck/test/synth, which never touch vendor/loom. Run it
right before a deploy, not before chant lint .. Unlike tsc, a whole-project
chant lint/build walks every .ts under the path with no gitignore
awareness, so a present vendor/loom gets linted as project code. CI never
vendors, so the gating lint job is unaffected; locally, rm -rf vendor/loom
before just check if you've vendored.
The build contexts, for reference:
| Component | context |
dockerfile |
|---|---|---|
loom-backend |
vendor/loom |
backend/Dockerfile |
loom-frontend |
vendor/loom/frontend |
Dockerfile (default) |
loom-backend's context is the vendor root (not vendor/loom/backend) because
its Dockerfile COPYs the Strands agent's source from outside backend/, the same context/Dockerfile pair Loom's own shared/makefile uses.
loom-agents has no docker-build phase: agents/strands_agent/ ships no
Dockerfile upstream. Loom builds that agent as a Python zip and deploys it to
Bedrock AgentCore Runtime, never as a container image.
Beyond the one-shot component deploys, ops/ holds the durable, gated concerns
for a running Loom deployment (upgrade, credential rotation, teardown), plus a
scheduled observe/reconcile pair. These run as
chant Ops on
Temporal where they need an approval gate and saga
rollback, or on the local executor where they don't.
| Op | What it does | Gated? |
|---|---|---|
loom-watch |
Drift detection — chant lifecycle diff --live across every stack, on a 15-min cron. Every tier. |
No |
loom-reconcile |
On drift, opens a cloud-to-code PR (owned-only, never mutates the cloud). production / production-ha. |
No |
loom-upgrade-light |
Snapshot RDS, migrate, promote-by-digest. | No — local executor |
loom-upgrade-production[-ha] |
Same, plus an approval gate and an RDS-restore rollback. | Yes |
loom-rotate-production[-ha] |
Rotate the Cognito M2M client, the RDS credential, and (custom-domain tiers) the ALB's ACM cert. | Yes |
loom-backup |
Labelled RDS snapshot, plus a cross-region DR copy when LOOM_DR_REGION is set. Additive. |
No — local executor |
loom-restore-drill |
Restore the latest backup to a throwaway instance, assert it comes up healthy, delete it. Proves the backup restores. | No — local executor |
loom-cognito-export |
Export the Cognito pool's users, groups, and memberships (to stdout, or S3 with LOOM_BACKUP_BUCKET). Read-only. |
No — local executor |
loom-restore |
Restore the DB (snapshot or PITR) to a new instance, then cut the backend over to it (repoint secret + redeploy). | Yes — cutover is destructive |
loom-teardown |
Gated, owned-only, marker-scoped stack deletes. No foreign deletes. | Yes |
loom-dns-setup |
Create + delegate a custom-domain Route53 zone, wait for the delegation to resolve. Human-in-the-loop, local. | No |
chant build ops # compile to dist/ops/<name>/
chant run loom-upgrade-light # local executor, no Temporal needed
chant run loom-upgrade-production --temporal # pauses at "Approve"
chant run signal loom-upgrade-production approve-loom-upgrade-production
npm run watch # one-shot loom-watch
npm run reconcile # one-shot loom-reconcile
Migrations run against the backend's own task-definition family with an
overridden command. No rebuild. The migration entrypoint depends on Loom's own
tooling (known once vendor/loom is checked out); override it via
LOOM_MIGRATION_COMMAND (comma-separated argv).
chant build --components --generate gitlab synthesizes the component pipeline
from the same component graph chant graph --components reads. One stage per
parallel-safe wave, one job per component, needs: mirroring dependsOn,
cross-stack outputs threaded as job artifacts. It's written to
.gitlab/components.yml; the root .gitlab-ci.yml includes it and adds a gated
deploy job and the scheduled lifecycle jobs (the component pipeline is skipped
on schedule pipelines, so a scheduled run never triggers a deploy).
npm run generate:gitlab # writes .gitlab/components.yml
just gitlab-validate # regenerate + diff against the committed copy (fails on drift)
GitHub and Forgejo get the same treatment: npm run generate:github /
generate:forgejo (committed .github/workflows/components.yml and
.forgejo/workflows/components.yml), just github-validate / forgejo-validate,
and just github-runtime-e2e / forgejo-runtime-e2e via act. All three also
carry a gated deploy and scheduled lifecycle workflows. See
CI providers for how GitHub, GitLab,
and Forgejo compare.
Every generated job is a thin trigger (chant run --components <name>) that
assumes chant, its deps, and dist/*.template.json already exist. Neither is
true in a bare checkout. Wire it once, project-wide, through
generateComponentPipeline's beforeScript/image/extraScript options: a
custom runner image with chant + awscli preinstalled, or beforeScript: ["npm ci", "npm run synth"] plus an awscli install on a stock image.
just gitlab-runtime-e2e proves the generated pipeline actually executes. It
runs the generated component pipeline in Docker (via gitlab-ci-local) against
Floci, deploying the light tier's infrastructure components end to end including
the cross-stack output handoff between waves. Needs Docker; not part of gating CI.
The GitHub equivalent is just github-runtime-e2e (via act).
The stateless lifecycle concerns also run on plain GitHub Actions cron, a second trigger host alongside each Op's own Temporal schedule. The durable/gated Ops (upgrade, rotation, teardown) are never scheduled here; they need a durable gate a cron can't give. Each workflow stays inert until a team opts in.
| Workflow | Runs | Cron | Opt-in |
|---|---|---|---|
watch.yml |
loom-watch |
every 15 min | vars.SCHEDULED_WATCH == 'true' |
reconcile.yml |
loom-reconcile |
hourly | vars.SCHEDULED_RECONCILE == 'true' and vars.LOOM_TIER is production/production-ha |
cost-report.yml |
npm run synth && npm run estimate-cost |
weekly | vars.SCHEDULED_COST_REPORT == 'true' |
audit.yml |
loom-audit |
daily | vars.SCHEDULED_AUDIT == 'true' |
backup.yml |
loom-backup |
daily | vars.SCHEDULED_BACKUP == 'true' |
All four also declare workflow_dispatch for an on-demand run.
watch.yml/reconcile.yml additionally need the production environment (they
run a live chant lifecycle diff); cost-report.yml/audit.yml never touch AWS.
reconcile.yml opens PRs via the default GITHUB_TOKEN (with contents: write
pull-requests: write, already declared).cost-report.yml's real numbers needinfracostinstalled plus anINFRACOST_CLI_AUTHENTICATION_TOKENsecret; without them it prints a per-component skip and never fails.
A third adoption on-ramp beyond "run chant" and "adopt this repo": consume the
output, skip the tool. Grab the pre-synthesized CloudFormation templates +
generated CI and deploy with plain aws cloudformation deploy. chant need not be
installed at deploy time.
npm run export-bundle
writes dist/bundle/loom-v1.6.0/{light,production,production-ha}/. Each tier a
self-contained directory: a synthesized CloudFormation template per component
(templates/), the generated GitHub Actions + GitLab CI pipelines (ci/), and
the tier's own manifest.json. The bundle's generated README.md has the deploy
order and the full parameter reference, regenerated from each run's own templates.
Every composite derives its physical resource names and cost tags from one shared
parameter source, loomNaming(...) in src/lib/naming.ts, keyed
{project}-{env}-{instance}-{component}-{resource}. A project-local lint rule
(.chant/rules/no-hardcoded-name.ts) flags any hardcoded physical name. Every
taggable resource carries the same five keys (component / tier / env /
owner / instance) straight from loomNaming(...).tags(). See
Naming & tagging for the full
convention.
Every composite exposes a provision | reference-existing | omit choice where
meaningful. Bring your own VPC, KMS key, ACM cert, Route53 zone, ECR repos, agent
IAM role, Postgres endpoint, or Cognito pool (including one pool shared across
instances), all through parameters, no composite forked. src/examples/byo/
deploys against pre-existing everything with zero composite edits. See
Adoption for the full matrix.
npm run estimate-cost shells out to Infracost
against the synthesized templates, one estimate per component. chant carries no
pricing data. This is plumbing. Without infracost installed and authenticated
it prints a per-component skip and exits 0, never failing the build. Loom's own
per-invocation LLM token cost is application runtime, out of scope for this hook.