Skip to content

TypeScript Agent Guardrails

Attach an Amazon Bedrock Guardrail to a TypeScript Agent. A guardrail is a set of rules that Amazon Bedrock applies to the messages your agent sends to the model and to the model’s answers. Without one, a user can ask your agent about any topic, and the model can repeat personal data such as email addresses back in its answers.

The generator adds a policy file that holds your rules, the code that applies the guardrail to your agent’s model, and the infrastructure that creates the guardrail in your AWS account.

  • guardrail.json next to your agent.ts holds the rules: the topics your agent must refuse and the types of personal data (PII, personally identifiable information) it must hide or block.
  • Your CDK construct or Terraform module reads guardrail.json when you synthesize or plan. It creates the guardrail and a numbered version of it. A guardrail version is a read-only snapshot of the rules.
  • The infrastructure writes the guardrail’s ID and version number to runtime config, the AWS AppConfig application where your infrastructure stores the details of deployed resources. The entry is agentcore.guardrails.<AgentClass>, named after your agent’s class, for example agentcore.guardrails.MyAgent. Each guarded agent has its own entry.
  • guardrail.ts builds the Bedrock model that your new Agent({...}) call uses. Before each model call, the model reads the agent’s entry from runtime config and applies that guardrail.

You will need an existing TypeScript project containing an agent created with the ts#agent generator.

Run this generator@aws/nx-plugin:ts#agent#guardrails

pnpm nx g @aws/nx-plugin:ts#agent#guardrails
Build your command2

Required

Generator Options2 options
projectRequiredstring

The project containing the TypeScript Agent

agentstring

The name of the Agent component to guard. Required when the project contains more than one TypeScript Agent.

The generator adds the following files next to your agent, where <agent> is the name of your agent component (agent by default):

  • Directory<your-project>/src/<agent>/
    • agent.ts Your agent, now created with model: getGuardrailedModel()
    • guardrail.json The rules your guardrail applies
    • guardrail.ts Builds the Bedrock model with the guardrail applied

It also adds the infrastructure for your selected iac provider, where <agent-name> is the kebab-case name of your agent’s construct or module:

  • Directorypackages/common/constructs/src/app/guardrails/<agent-name>/
    • <agent-name>-guardrail.ts CDK construct containing the guardrail and its version

The generator edits agent.ts like this:

import { getGuardrailedModel } from './guardrail.js';
import { Agent, tool } from '@strands-agents/sdk';
export const getAgent = async () => {
const agent = new Agent({
model: getGuardrailedModel(),
systemPrompt: `You are a mathematical wizard.

Running the generator again on the same agent changes no file, and it keeps your edits to guardrail.json.

If your agent was generated with infra: none, the generator adds only the agent files and prints a warning. You then create the guardrail yourself and write its ID and version to runtime config at agentcore.guardrails.<AgentClass> as { guardrailId, guardrailVersion }.

If your new Agent({...}) call already sets model, the generator leaves agent.ts unchanged. It prints the file name and the lines to add. To keep your own model options, create your model with the GuardrailedBedrockModel class from guardrail.ts. It takes the same options as BedrockModel, for example:

agent.ts
import { GuardrailedBedrockModel } from './guardrail.js';
const agent = new Agent({
model: new GuardrailedBedrockModel({ modelId: 'my-model-id' }),
// ...
});

The guardrail checks two things on every model call:

  • the conversation your agent sends to the model, including the user’s latest message
  • the model’s answer, before your agent streams it back to the user

When the guardrail blocks a user’s message, the user gets the blockedInputMessaging text, and the model is not called. Strands also replaces the blocked message in the agent’s conversation history with [User input redacted.]. When it blocks an answer, the user gets the blockedOutputsMessaging text. When an ANONYMIZE rule matches, the answer contains a placeholder such as {EMAIL} instead of the value.

The guardrail does not check tool definitions, the arguments the model passes to your tools, or the results your tools return. For example, if a tool returns a customer’s email address from a database, the guardrail does not hide it from the model. Treat tool results as untrusted, as the TypeScript Agent guide describes under “Securing your Agent”.

The vended policy is an example for the generated maths agent. It refuses investment advice and hides or blocks three types of personal data:

guardrail.json
{
"blockedInputMessaging": "Sorry, I can't help with that request.",
"blockedOutputsMessaging": "Sorry, I can't share that answer.",
"deniedTopics": [
{
"name": "Investment advice",
"definition": "Recommendations about which specific stocks, funds or other investments to buy or sell.",
"examples": ["Which stocks should I buy this year?"]
}
],
"piiEntities": [
{ "type": "EMAIL", "action": "ANONYMIZE" },
{ "type": "PHONE", "action": "ANONYMIZE" },
{ "type": "US_SOCIAL_SECURITY_NUMBER", "action": "BLOCK" }
]
}

The fields are:

  • blockedInputMessaging: the reply a user gets when the guardrail blocks their message. At most 500 characters.
  • blockedOutputsMessaging: the reply a user gets when the guardrail blocks the model’s answer. At most 500 characters.
  • deniedTopics: topics your agent must refuse. Each topic has a name (at most 100 letters, digits, spaces and the characters -_!?.), a definition of at most 1000 characters that describes the topic, and optional examples of at most 100 characters each.
  • piiEntities: types of personal data to act on. type is a sensitive information type such as EMAIL, PHONE or US_SOCIAL_SECURITY_NUMBER. action is ANONYMIZE to replace the value with a placeholder, or BLOCK to block the whole message or answer.

Write a topic definition that describes the topic itself. For example, “Recommendations about which specific stocks to buy” works better than “Do not give investment advice”.

Each time you change guardrail.json and deploy, the infrastructure creates a new numbered guardrail version and writes it to runtime config. The version’s description holds the SHA-256 hash of guardrail.json, so a change to the file changes the description, and a new version replaces the old one. With the CDK construct, the hash also covers any guardrail properties you pass.

Your running agent does not need a restart. It caches what it reads from runtime config for 5 seconds, so it uses the new version within 5 seconds of the AppConfig deployment. The old version is deleted after the AppConfig deployment: CloudFormation deletes it in its cleanup step, and Terraform deletes it after the deployment module when you add the guardrail module to that module’s depends_on, as shown in Wiring the guardrail to your agent.

Your agent never uses the DRAFT version. DRAFT always holds the latest rules, so an edit made in the AWS console would reach your running agent without a deployment. guardrail.ts throws an error if the version in runtime config is not a version number.

The generator does not change your stack or your root Terraform module, in the same way that the ts#agent generator leaves deploying the agent to you. When it finishes, it prints the code below with your agent’s names filled in. Add it to create the guardrail, write its runtime-config entry and allow the agent to apply the guardrail:

packages/infra/src/stacks/application-stack.ts
import { MyAgent, MyAgentGuardrail } from '@my-scope/common-constructs';
const agent = new MyAgent(this, 'MyAgent');
new MyAgentGuardrail(this, 'MyAgentGuardrail', { agent });

The construct writes the guardrail’s ID and version to runtime config at agentcore.guardrails.MyAgent, and grants the agent permission to apply the guardrail. The agent construct already allows the agent to read runtime config.

The construct exposes:

  • guardrail (the CfnGuardrail) and version (the CfnGuardrailVersion)
  • guardrailId, guardrailArn and guardrailVersion
  • grantApply(grantee), which grants bedrock:ApplyGuardrail on the guardrail to another principal

It also adds the GuardrailId and GuardrailVersion stack outputs. Its props are agent and any CfnGuardrail properties. The CfnGuardrail properties take precedence over guardrail.json. For example, add content filters:

new MyAgentGuardrail(this, 'MyAgentGuardrail', {
agent,
contentPolicyConfig: {
filtersConfig: [
{ type: 'HATE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
],
},
});

If you forget the wiring, runtime config has no entry for your agent. The model then throws an error before it calls Bedrock, so the agent does not run without its guardrail. For MyAgent, the error is:

No guardrail for this agent in runtime config at agentcore.guardrails.MyAgent. Add the MyAgentGuardrail construct (CDK) or the guardrails/my-agent module (Terraform) to your infrastructure.

The agent starts with every protocol (http, a2a and ag-ui), and each request fails with this error.

The <agent>-dev target sets LOCAL_DEV=true. When LOCAL_DEV is true and the RUNTIME_CONFIG_APP_ID environment variable is not set, guardrail.ts does not read runtime config. It prints a warning once and the agent runs without a guardrail:

Terminal window
pnpm nx agent-dev your-project

To test your rules locally, deploy the guardrail first. Then set RUNTIME_CONFIG_APP_ID to the ID of your deployed runtime-config application, from the RuntimeConfigApplicationId stack output (CDK) or module.runtime_config_appconfig.application_id (Terraform). Your shell also needs AWS credentials that can read AppConfig and apply the guardrail:

Terminal window
RUNTIME_CONFIG_APP_ID=<application-id> pnpm nx agent-dev your-project

The agent then reads only its own entry, so several guarded agents can run locally against the same application. If the agent cannot read runtime config, or the entry is missing, it prints the warning once and runs without a guardrail.

The <agent>-serve target does not set LOCAL_DEV, so it needs RUNTIME_CONFIG_APP_ID and the deployed entry, otherwise each request fails as described above. The <agent>-chat target connects to a running agent and needs no runtime config itself.

Amazon Bedrock charges for each guardrail policy that evaluates text, on both the input and the answer. See Amazon Bedrock pricing.

Each model call also takes longer, because the guardrail checks the answer before your agent streams it to the user.

  • This generator supports agents created with ts#agent. It does not support the AgentCore Harness.
  • guardrail.json holds denied topics and PII entities only. Add other policies, such as content filters and word filters, with the construct’s props (CDK) or by editing the module (Terraform).
  • The guardrail does not check tool results, as described in What the guardrail checks.