sam@upra:~$
← All writing

Prompt Versioning in Amazon Bedrock Without Breaking Production

Written in a personal capacity. Views are the author’s own.

A prompt change is a software release. It can alter correctness, latency, token consumption, safety behavior, tool selection, and the shape of downstream data without changing one line of application code. Production therefore needs a release boundary stronger than “someone clicked Save.”

Amazon Bedrock Prompt Management provides that boundary, but only if teams distinguish its mutable working draft from its immutable numbered versions. Bedrock documentation describes the draft as the place to iterate and a version as a point-in-time snapshot intended for deployment.1 The operational rule follows directly: engineers may test drafts; production must invoke a numbered version ARN.

The resource model: one mutable head, many immutable releases

CreatePrompt creates version DRAFT. UpdatePrompt modifies that draft, not an already published version. It is also replacement-oriented rather than patch-oriented: AWS explicitly says to include fields you want to retain as well as fields you want to replace.2 A pipeline should therefore send a complete desired state and reject console edits as drift.

CreatePromptVersion snapshots the current draft and returns an incrementing numeric version, beginning at 1.3 GetPrompt without promptVersion reads the draft; with a numeric promptVersion, it reads that frozen version.4 The identifiers make the boundary visible:

Prompt ID:       ABCDE12345
        Draft ARN:       arn:aws:bedrock:us-east-1:111122223333:prompt/ABCDE12345
        Version 7 ARN:   arn:aws:bedrock:us-east-1:111122223333:prompt/ABCDE12345:7
        

The deployable unit is more than prompt text. A PromptVariant includes a name, TEXT or CHAT template type, template configuration, and optionally a model or inference-profile ID, inference configuration, model-specific request fields, tools, and metadata.5 Treat all of these as one artifact. Changing temperature or a model ID is as material as changing an instruction.

Variants are named candidate configurations; defaultVariant identifies the selected one. They are not weighted production routes. The console can compare up to three candidates, but saving from compare mode keeps the chosen draft and deletes the other comparison candidates.6 Use variants to experiment, then publish an explicit winner. Run traffic experiments in an application or Bedrock Flow where assignment, exposure, and metrics are controlled.

A safe boto3 release path

Prompt control-plane operations use the bedrock-agent client; inference uses bedrock-runtime. The following example creates or fully updates a draft, creates an idempotent immutable candidate, reads it back, and invokes that exact ARN. BEDROCK_MODEL_ID should be a model ID or inference-profile ARN approved for the target Region.

import hashlib
        import json
        import os
        import boto3

        region = os.environ["AWS_REGION"]
        model_id = os.environ["BEDROCK_MODEL_ID"]
        git_sha = os.environ["GIT_SHA"]
        prompt_id = os.getenv("BEDROCK_PROMPT_ID")  # absent on first deployment

        control = boto3.client("bedrock-agent", region_name=region)
        runtime = boto3.client("bedrock-runtime", region_name=region)

        variant = {
            "name": "release",
            "templateType": "TEXT",
            "modelId": model_id,
            "templateConfiguration": {
                "text": {
                    "text": (
                        "Summarize this incident for an on-call engineer. "
                        "Return impact, likely cause, and next action.\n\n"
                        "Incident: {{incident}}"
                    ),
                    "inputVariables": [{"name": "incident"}],
                }
            },
            "inferenceConfiguration": {
                "text": {"temperature": 0.0, "maxTokens": 500}
            },
        }

        desired = {
            "name": "incident_summary",
            "description": "Operational incident summary",
            "defaultVariant": "release",
            "variants": [variant],
        }

        canonical = json.dumps(desired, sort_keys=True, separators=(",", ":"))
        spec_digest = hashlib.sha256(canonical.encode()).hexdigest()

        if prompt_id:
            # UpdatePrompt is not a patch: send the full desired state.
            control.update_prompt(promptIdentifier=prompt_id, **desired)
        else:
            created = control.create_prompt(
                **desired,
                tags={"ManagedBy": "prompt-cicd", "SpecSha256": spec_digest},
            )
            prompt_id = created["id"]

        candidate = control.create_prompt_version(
            promptIdentifier=prompt_id,
            description=f"git={git_sha[:12]} spec={spec_digest[:16]}",
            clientToken=hashlib.sha256(
                f"{prompt_id}:{spec_digest}".encode()
            ).hexdigest(),
            tags={"GitSha": git_sha, "SpecSha256": spec_digest},
        )

        frozen = control.get_prompt(
            promptIdentifier=prompt_id,
            promptVersion=candidate["version"],
        )
        assert frozen["arn"] == candidate["arn"]
        assert frozen["defaultVariant"] == desired["defaultVariant"]
        assert frozen["variants"] == desired["variants"]

        response = runtime.converse(
            modelId=candidate["arn"],
            promptVariables={
                "incident": {"text": "Checkout errors rose after the 14:05 deploy."}
            },
            requestMetadata={
                "environment": "staging",
                "git_sha": git_sha[:12],
                "prompt_version": candidate["version"],
            },
        )
        print(response["output"]["message"]["content"][0]["text"])
        print(response["usage"], response["metrics"])
        

The request shape above follows the current boto3 Prompt Management client.8 When a managed prompt ARN is supplied as modelId, promptVariables fills its variables. Do not also send system, inferenceConfig, toolConfig, or additionalModelRequestFields; those must come from Prompt Management. Additional messages, if supplied, are appended after the stored messages.7

Promote source identity, not AWS version numbers

Separate development, staging, and production accounts are a useful blast-radius boundary, but their prompt IDs and numeric versions are independent. An ARN embeds Region, account, prompt ID, and version. Consequently, “promote version 7” is ambiguous: staging version 7 and production version 7 need not contain the same artifact.

Promote a Git commit and canonical content digest instead. Recreate the same desired state in each environment, cut a local Bedrock version, read it back, and record the resulting ARN. A practical repository layout is:

prompts/incident-summary/
        ├── prompt.yaml                 # type, variables, model, inference settings
        ├── template.txt
        ├── evals/cases.jsonl
        ├── evals/thresholds.yaml
        └── releases/2026-08-25.yaml    # reviewed deployment record
        
logical_prompt: incident-summary
        source_git_sha: 9f83c8e1a2d4
        spec_sha256: 4a7f...c219
        eval_suite_sha256: 82b1...9d0e
        environments:
          staging:
            region: us-east-1
            prompt_id: A1B2C3D4E5
            version: "12"
            version_arn: arn:aws:bedrock:us-east-1:111122223333:prompt/A1B2C3D4E5:12
          production:
            region: us-east-1
            prompt_id: F6G7H8J9K0
            version: "9"
            version_arn: arn:aws:bedrock:us-east-1:444455556666:prompt/F6G7H8J9K0:9
        

This release file is evidence, not the source of prompt content. Generate it in CI after AWS read-back verification and require code review for production changes.

Evaluation is the promotion gate

Create the immutable candidate before evaluation, then test that exact ARN. A rejected candidate can remain unpublished; immutability is more valuable than gap-free numbering. The suite should combine deterministic checks—required JSON schema, prohibited content, tool contract, maximum length—with scored checks for task quality, safety, latency, input/output tokens, and estimated cost. Compare against the current production version on the same cases and model configuration. Use repeated samples where temperature or model nondeterminism makes a single run misleading.

Bedrock Evaluation jobs accept a model or inference profile in inferenceConfig; they are not a release mechanism for a managed-prompt version ARN.11 Bedrock’s custom evaluation datasets are JSONL in S3 and support up to 1,000 prompts for an automatic evaluation job.12 Those facilities are useful for model-level benchmarking. For the actual prompt release gate, a small harness that calls Converse with the candidate version ARN preserves the artifact under test and can implement domain-specific judges and thresholds.

Rollback and observability are pointer operations

Keep the production application’s selected version ARN in deployment configuration. Promotion changes that pointer only after evaluation and approval. Rollback changes it back to the last known-good ARN; it does not edit the draft and does not create another prompt version. Preserve several known-good versions and smoke-test rollback during normal releases. If a version is deleted with DeletePrompt(promptVersion=...), its ARN is no longer a rollback target.

Every invocation should emit the version ARN, logical release ID, request ID, latency, token usage, stop reason, evaluator or business outcome, and application trace ID. Converse returns normalized usage and metrics; its requestMetadata map can add release dimensions to invocation logs.7 Bedrock model invocation logging is disabled by default and, when enabled, can capture full requests, responses, and metadata to CloudWatch Logs or S3.13 That is valuable but sensitive: apply retention, encryption, access controls, and redaction appropriate to the data. Request metadata appears in those logs only when invocation logging is enabled.14

CloudTrail provides a separate audit plane. Prompt-management control calls are management events; RenderPrompt is a data event and requires an advanced event selector for resource type AWS::Bedrock::Prompt.15 Use both: CloudTrail answers who changed or rendered a prompt; invocation telemetry answers whether a release behaved correctly.

IAM and CI/CD: make unsafe paths impossible

Separate three roles. Authors may create and update drafts and run non-production tests. Release automation may read drafts, create versions, and tag them.

Runtime roles may invoke approved models or inference profiles and need bedrock:RenderPrompt for managed prompts, but do not need CreatePrompt, UpdatePrompt, or CreatePromptVersion.1617

The Bedrock authorization reference supports prompt and prompt-version resource ARNs and resource-tag conditions, enabling tighter policies than Resource: "*" where the action supports them.18

If using a customer-managed KMS key, include the Bedrock service and deployment roles in the key policy and constrain the Bedrock prompt encryption context.16

CloudFormation now exposes both AWS::Bedrock::Prompt for the mutable prompt and AWS::Bedrock::PromptVersion for the snapshot.910 The version resource takes an unversioned PromptArn; all of its properties require replacement. Ensure each release changes a replacement property—put the Git SHA and digest in its description or tags—or generate a new logical resource per release. Add DeletionPolicy: Retain and UpdateReplacePolicy: Retain if the stack must preserve rollback versions.

A production pipeline should therefore: validate the manifest and variables; deploy the complete draft; create a candidate version with an idempotency token; read back and hash it; run regression, safety, schema, latency, and cost gates; require approval; create and verify the environment-local production version; update the application pointer; smoke-test; then watch release-scoped telemetry. AWS Prescriptive Guidance similarly recommends treating prompts as version-controlled assets, using golden tests, approvals, environment separation, and automated rollback.1920

Failure modes to block in policy

Several shortcuts defeat the version model. Do not let production invoke an unversioned prompt ARN: that couples live behavior to the next draft save. Do not evaluate a draft and then create the version later; another writer can change the draft between those operations. Snapshot first, then evaluate the returned version ARN. Do not treat the template file alone as the release artifact while model, tools, or sampling parameters live in a console. Do not equate numeric versions across accounts. Do not overwrite a release record when rolling forward; append a new record so the old ARN and evidence remain available. Finally, do not declare success from a CreatePromptVersion response alone. Read the version back, compare normalized desired and actual configurations, invoke it with a smoke case, and verify that release metadata reached the logging destination.

These controls are deliberately mechanical. They convert race conditions, incomplete deployments, and unverifiable rollbacks into pipeline failures rather than production incidents.

The essential design is simple: drafts are workspaces, versions are artifacts, and ARNs are release coordinates. Once those concepts are enforced in IAM and CI/CD, prompt iteration can remain fast without making production mutable.

Sources


  1. https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management-deploy.html 

  2. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_UpdatePrompt.html 

  3. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_CreatePromptVersion.html 

  4. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_GetPrompt.html 

  5. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_PromptVariant.html 

  6. https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management-create.html 

  7. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html 

  8. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/create_prompt.html 

  9. https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-bedrock-prompt.html 

  10. https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-bedrock-promptversion.html 

  11. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_CreateEvaluationJob.html 

  12. https://docs.aws.amazon.com/bedrock/latest/userguide/model-evaluation-prompt-datasets.html 

  13. https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html 

  14. https://docs.aws.amazon.com/bedrock/latest/userguide/cost-mgmt-request-metadata.html 

  15. https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html 

  16. https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management-prereq.html 

  17. https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html 

  18. https://docs.aws.amazon.com/service-authorization/latest/reference/list_bedrock.html 

  19. https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-serverless/prompt-agent-and-model.html 

  20. https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-serverless/cicd-and-automation.html