Architecture pattern library

AWS field guide

Separate agent reasoning from side effects

Treat the model as a planner, not as a privileged runtime. Every side effect should cross a narrow, observable tool boundary.

Use this pattern when

An agent calls tools, executes workflows, or operates across multiple AWS accounts.

Reference architecture

Responsibilities and controls, not a deployment template.

Synchronous Asynchronous

Client or event

Authenticated intent

Amazon Bedrock

Plans and selects a tool

Tool router

Schema and policy validation

AWS Lambda

One narrow capability

AWS resource

Read or side effect

Reasoning boundary: validate everything that crosses this line
Controls that span every tool invocation

Least privilege

Role per tool

Audit trail

Caller and arguments

Approval gate

High-impact writes

Idempotency

Safe retries

AWS service marks use the official Q3 2026 AWS Architecture Icons. Abstract nodes represent application responsibilities rather than AWS services.

Decisions that shape the pattern

  • Define one business capability per tool.
  • Validate tool arguments independently of the model.
  • Return structured results instead of raw service responses.

Security boundaries

  • Give each tool its own least-privilege role.
  • Treat model output as untrusted input.
  • Record the caller, tool, arguments, result, and correlation ID.

Reliability posture

  • Use idempotency keys for mutating actions.
  • Separate retryable reads from risky writes.
  • Add timeouts and explicit failure responses at every boundary.

Starter implementation

Start from deployable infrastructure

Review every permission, limit, Region, and cost assumption before production.

Download ADR template
agent-tool-boundaries.stack.ts
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as athena from 'aws-cdk-lib/aws-athena';
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
import * as budgets from 'aws-cdk-lib/aws-budgets';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as patterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as glue from 'aws-cdk-lib/aws-glue';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sources from 'aws-cdk-lib/aws-lambda-event-sources';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Construct } from 'constructs';

export class PatternStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const tool = new lambda.Function(this, 'Tool', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('tool'),
      timeout: Duration.seconds(15),
    });
    
    tool.addPermission('AllowBedrockAgent', {
      principal: new iam.ServicePrincipal('bedrock.amazonaws.com'),
      sourceAccount: Stack.of(this).account,
    });
    
    const agentRole = new iam.Role(this, 'AgentRole', {
      assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'),
    });
    agentRole.addToPolicy(new iam.PolicyStatement({
      actions: ['bedrock:InvokeModel'],
      resources: ['arn:aws:bedrock:*::foundation-model/amazon.nova-lite-v1:0'],
    }));
    
    new bedrock.CfnAgent(this, 'Agent', {
      agentName: 'guarded-tool-agent',
      agentResourceRoleArn: agentRole.roleArn,
      foundationModel: 'amazon.nova-lite-v1:0',
      instruction: 'Use the read-only tool only when required.',
      autoPrepare: true,
      actionGroups: [{
        actionGroupName: 'ReadOnlyTools',
        actionGroupExecutor: { lambda: tool.functionArn },
        functionSchema: { functions: [{
          name: 'get_workload_status',
          description: 'Read the status of one approved workload.',
          parameters: { workloadId: { type: 'string', required: true } },
        }] },
      }],
    });
  }
}

Before production

Adoption checklist

  1. 01Inventory every possible side effect.
  2. 02Create typed input schemas for tools.
  3. 03Add approval gates for high-impact actions.
  4. 04Test duplicate calls and partial failures.
  5. 05Alarm on denied or anomalous tool activity.

From the journal

Selected from service names and architecture signals used by this pattern.

Was this playbook useful?

One click helps prioritize deeper examples and updates.