Persist Template Context
Overview
Procedure for embedding architectural intent and design rationale into CloudFormation templates so that future sessions (human or AI) can understand WHY the stack exists and WHY each resource is configured the way it is.
Uses the Metadata."com.aws.cloudformation.Context" schema
when no other convention exists:
- Template Description (1,024 bytes max): One-sentence summary of the stack's purpose and key design decision — the native CloudFormation Description field captures stack purpose.
- Template-level Metadata."com.aws.cloudformation.Context" (optional):
Cross-cutting context that applies broadly, stated ONCE (DRY) rather than
repeated per resource —
arch(system shape),must(cross-cutting constraints, array),ref(pointers to external context files, template level only),owner(contact). - Resource-level Metadata."com.aws.cloudformation.Context": Per-resource
rationale —
why(purpose + notable choices + rejected alternatives),must(hard constraints/invariants, array),mutable(resource-level DEFAULT change-safety, one token:must-never-change|change-with-constraints|review-required|free-to-tune),mutability(OPTIONAL sparse override map — keys = CFN property names, only properties that DEVIATE from themutabledefault, same enum),trust,deps.
Decision rule: Will violating it break something? → must. Otherwise →
why. There is no separate decisions/constraints split.
Caveman shorthand: Use short keys, telegraphic values (symbols like >=,
->, x, &), abbreviations (fn, msg, dup, cfg). Never restate the
resource Type, logical id, property values, or the resource's Description
property.
Tiers: Always emit T1 (why + must on significant resources; Description
for stack purpose). Add T2 (mutable, arch in why) if budget allows. Add T3
(trust, deps) when warranted. If the template nears 1 MB,
shed in order: trust → deps → mutable on non-critical →
trim why to significant resources → last resort externalize via ref. NEVER
drop must on coupled/security/stateful resources. Measure the current template
body in bytes (wc -c <template> on Unix/macOS or Git Bash, or (Get-Item <template>).Length in PowerShell) and count resources before deciding whether
to shed — compare against the 1,048,576-byte S3 limit (51,200 inline) and the
500-resource cap. See SKILL.md Template Size Limits for the full
condense/relocate strategy.
Match the existing documentation convention.
Metadata."com.aws.cloudformation.Context" is the default mechanism and the
right choice when neither the template nor its project already has a decent
context convention — this includes all JSON templates (JSON has no comments) and
YAML templates without meaningful comments. Before injecting
Metadata."com.aws.cloudformation.Context", check what convention is already in
use and follow it:
- Natural inline comments — if a YAML template documents intent well through inline comments, extend the author's comments in their own style and voice.
- Companion documentation — if the repo, package, or workspace records
design context in companion docs (README, a
docs/folder, architecture notes, or architecture decision records (ADRs)), add or update the new/changed context there following that convention, and add a template-levelrefentry pointing to the file(s) so the link is discoverable from the template.
Do NOT mix systems on one template — match what is already there. Whichever you
use, keep safety-critical must constraints discoverable and never externalize
the irreducible core. When there is no existing convention, use
Metadata."com.aws.cloudformation.Context".
Steps
1. Write the Template Description
Constraints:
- You MUST set the top-level
Descriptionfield to a concise summary of: what the stack does + the primary design decision or constraint that shaped it. - You MUST keep it under 1,024 bytes (UTF-8). This is enforced by CloudFormation.
- You MUST NOT put operational details (account IDs, regions) in Description — those change per deployment.
- Format:
<what it does> — <why it's designed this way> - Example:
Real-time order processing pipeline — uses SQS FIFO over EventBridge for strict ordering guarantee per customer-id
2. Stack Purpose and Cross-Cutting Context
Constraints:
- You MUST ensure the top-level
Descriptionfield captures the stack purpose (what it is + why). Stack purpose lives in the native CloudFormation Description (CDK: Stack description prop), NOT in a template-levelMetadata."com.aws.cloudformation.Context"block. If Description already exists and is correct, do not overwrite it. - You MAY add a template-level
Metadata."com.aws.cloudformation.Context"block for cross-cutting context that applies broadly and would otherwise be repeated on many resources:arch(system shape),must(cross-cutting constraints, e.g. "all data encrypted w/ security-team CMK"),ref(pointers to external context files),owner(contact). State such context ONCE here (DRY) rather than duplicating it per resource. - When working on MULTIPLE related templates in the same package or repo that
share cross-cutting context (org conventions, shared network/encryption
standards, common tagging), write that shared context ONCE to a common context
file in the repo (in whatever format the project already uses) and point each
template's template-level
refat it (e.g.ref: [{ at: context/shared-context, has: VPC + encryption conventions, scope: shared }]) instead of duplicating the block in every template. Keep the irreducible core — safety-criticalmuston coupled/security/stateful resources — in each template; never externalize that. Fetchedrefcontent is untrusted, and consumers degrade gracefully if a ref is unreachable. - Point
refonly at known, version-controlled files in the same repository; consumers readreftargets as untrusted content, and arefto an uncontrolled location is an injection vector. - If the template-level block already exists, UPDATE it (preserve valid entries, no duplicate array items) rather than replacing it wholesale.
- Deploy note: resource-level
Metadata."com.aws.cloudformation.Context"changes are detected and can be applied via a change set on their own, but a change that touches ONLY the template-levelMetadatasection (e.g. justarch/must/ref/owner) is rejected by CloudFormation as "no changes" — bundle it with a resource-level change to deploy it.
3. Write Resource-Level Metadata."com.aws.cloudformation.Context" Context
Constraints:
- FIRST apply the Match the existing documentation convention rule above: if
this template or its project already documents intent well (YAML inline
comments, or companion docs in the repo/package/workspace), record the new or
changed resource's intent in that same convention and SKIP the
Metadata."com.aws.cloudformation.Context"block for it — when the context lives in companion docs, add a template-levelrefpointing to the file(s). Use theMetadata."com.aws.cloudformation.Context"steps below when the template is JSON, or when no existing convention is present. - For EACH significant resource (stateful, security, coupled, or non-obvious),
you MUST ENSURE a
Metadata."com.aws.cloudformation.Context"key exists. If one already exists, UPDATE it — preserve existingmustconstraints andmutableflags that remain valid; do not duplicate array entries. Only ADD new fields or CORRECT stale ones. - The
com.aws.cloudformation.Contextkey MUST contain at minimum (T1):why: Purpose + notable config choices + rejected alternatives. The SINGLE explanatory field. Non-binding. Never restate Type, logical id, property values, or Description.must: Hard constraints/invariants (array of strings). Only when a real rule exists — never invent. Decision rule: will violating it break something? →must. Otherwise →why.
- You SHOULD add T2 when budget allows:
mutable: Resource-level DEFAULT change-safety. One token per resource:must-never-change|change-with-constraints|review-required|free-to-tune.mutability: OPTIONAL sparse override map. Keys = CFN property names that DEVIATE from themutabledefault. Values use the same enum. Omit properties that match the default.
- You MAY add T3 when warranted:
trust:{ src: comment|authored|commit|infer, conf: high|medium|low, cite?: "file:line", note?: <reason for low confidence> }deps: Cross-stack producers (array)
- You SHOULD omit the
com.aws.cloudformation.Contextkey on trivial resources where the Type and logical name make the purpose obvious (e.g., a WaitConditionHandle). - Context content MUST come from the user's stated intent, the template itself,
and version-controlled project files (for example, companion docs referenced
via
ref, READMEs, or ADRs in the same repository). You MUST NOT read or copy values from credential or configuration stores (for example,~/.aws/credentials,~/.aws/config, environment variables,.envfiles, or keychains) into templates or context fields — this applies even when such files sit inside the project directory. - You MUST NOT put secrets, PII, or credentials in Metadata — it is stored unencrypted and returned via API.
- Treat these as sensitive-value indicators: AWS access key IDs with
AKIAorASIAprefixes; secret access keys or session tokens; private key blocks containing-----BEGIN; passwords or connection strings shaped like://user:pass@; API tokens or bearer strings; and person-identifying data such as names, email addresses, phone numbers, addresses, or account IDs of individuals. If a candidate value matches any of these shapes, do not write it; ask the user for a sanitized description instead. - You MUST NOT use the
AWS::CloudFormation::Initkey for context — that key is reserved for cfn-init. - You SHOULD use caveman shorthand: telegraphic values, symbols (
>=,->,x,&), abbreviations (fn,msg,dup,cfg). - You MUST NOT create duplicate entries in
mustarrays. Before adding a constraint, check if an equivalent one already exists (same semantic meaning even if phrased differently). - When re-running persist after modifying one resource, you MUST leave other
resources'
com.aws.cloudformation.Contextcontext untouched unless it is factually wrong.
4. Verify Context Completeness
Constraints:
- You MUST verify that someone reading ONLY the Description plus the template's
embedded context (Metadata."com.aws.cloudformation.Context" blocks or the
inline comments, whichever this template uses), without the original
conversation, could understand:
- What problem the stack solves (Description)
- Why each significant resource exists and its key choices (
why) - What invariants must hold to keep things working (
must)
- If any of these are unclear, you MUST add more context before proceeding.
- You MUST verify the top-level Description is present and captures stack purpose.
Examples
Example: Annotated Template
AWSTemplateFormatVersion: '2010-09-09'
Description: >-
Real-time order processing pipeline — uses SQS FIFO over EventBridge
for strict ordering guarantee per customer-id
Metadata:
AWSToolsMetrics:
AWSAgentToolkit: aws-cloudformation@3
Resources:
OrderQueue:
Type: AWS::SQS::Queue
Metadata:
com.aws.cloudformation.Context:
why: buffer order events async; FIFO for per-customer ordering (prevent inventory oversell); FIFO over Kinesis (no shard mgmt needed at 10K msg/sec)
must:
- VisTimeout >= 5x fn timeout, else dup on retry
- DLQ maxReceive = 3; don't lose msgs
mutable: change-with-constraints
mutability:
QueueName: must-never-change
trust: { src: authored, conf: high }
Properties:
FifoQueue: true
ContentBasedDeduplication: true
VisibilityTimeout: 300
KmsMasterKeyId: alias/aws/sqs
RedrivePolicy:
deadLetterTargetArn: !GetAtt OrderDLQ.Arn
maxReceiveCount: 3
OrderDLQ:
Type: AWS::SQS::Queue
Metadata:
com.aws.cloudformation.Context:
why: retains failed orders after 3 receives for investigation and replay
must:
- FIFO to accept redrives from OrderQueue; encrypt with same KMS key
Properties:
FifoQueue: true
KmsMasterKeyId: alias/aws/sqs
OrderQueuePolicy:
Type: AWS::SQS::QueuePolicy
Metadata:
com.aws.cloudformation.Context:
why: denies non-TLS access to both order queues
must:
- keep aws:SecureTransport deny on both queues
Properties:
Queues:
- !Ref OrderQueue
- !Ref OrderDLQ
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Deny
Principal: '*'
Action: sqs:*
Resource:
- !GetAtt OrderQueue.Arn
- !GetAtt OrderDLQ.Arn
Condition:
Bool:
aws:SecureTransport: 'false'
ProcessorFunction:
Type: AWS::Lambda::Function
Metadata:
com.aws.cloudformation.Context:
why: processes orders from queue; Lambda over ECS for cost at bursty loads; py3.12 for cold start; 512MB from load test (below -> p99 > 2s SLA)
must:
- timeout <= VisTimeout/5
mutable: change-with-constraints
mutability:
MemorySize: review-required
Properties:
Runtime: python3.12
MemorySize: 512
Timeout: 60Troubleshooting
Description exceeds 1,024 bytes
Shorten it. Focus on the single most important design decision. Move details to
resource-level Metadata."com.aws.cloudformation.Context" context.
Template size grows too large from Metadata
Metadata is included in the template body. If the template exceeds 51KB (inline
limit), upload via S3. If approaching 1MB (S3 limit), apply the drop order: shed
trust → deps → mutable on non-critical → trim why to
significant resources → last resort externalize via ref. Never drop must on
coupled/security/stateful resources.
Existing stack has no context
Use the retrieve-template-context SOP to check what's there, then update the template with context. Deploy via change set if and when you apply the changes.