What the agent does
The agent clones the project into its own cloud sandbox and reads the stack before it adds a metric. In a TypeScript CDK app like typescript/api-cors-lambda-crud-dynamodb in aws-samples/aws-cdk-examples, index.ts defines ApiLambdaCrudDynamoDBStack: a DynamoDB Table named items with a string partition key itemId and RemovalPolicy.DESTROY, five NodejsFunctions on Runtime.NODEJS_24_X bundled with @aws-sdk/* external, each pointed at a file under lambdas/, and a RestApi named Items Service with /items and /items/{id} wired to them and a mock OPTIONS for CORS. package.json pins aws-cdk-lib 2.262.1, aws-cdk 2.1133.0, TypeScript ~7.0.2 and Jest 30 with @swc/jest; cdk.json runs the app with npx tsc && npx tsx index.ts; jest.config.js matches **/*.test.ts and loads aws-cdk-lib/testhelpers/jest-autoclean. There are no alarms and no tests.
The metrics come from the constructs already in the stack, not from strings. Each function exposes metricErrors(), metricThrottles() and metricDuration(), as typescript/lambda-cloudwatch-dashboard in the same repository already uses for a dashboard; the RestApi exposes metricServerError(), metricClientError() and metricLatency(); the Table exposes throttled-request and system-error metrics per operation. The agent adds aws-cdk-lib/aws-cloudwatch alarms on each function's errors and throttles, a p99 duration alarm against the function's configured timeout, a 5XX alarm and a latency alarm on the API, and throttle alarms on the table, each with an alarmDescription naming the resource, treatMissingData set so a quiet API does not page, and an SNS topic as the alarm action whose email subscription comes from a CDK context value so no address is committed.
Thresholds and periods are the stack owner's call: the prompt carries them, and the agent writes the numbers it was given into named constants at the top of the stack with a comment each, rather than inventing a baseline for an API it has never seen traffic on. Where the prompt gives none, it uses the construct's defaults and lists in the pull request every value a reviewer should set before the first deploy. The lambdas/ handlers return 500 on any DynamoDB error, which the errors alarm would not count, since a handled 500 is not a Lambda invocation error, so the API's 5XX alarm is the one that catches it, and the agent says so.
Then it proves the stack still synthesizes. It writes test/alarms.test.ts using Template.fromStack to assert the count of AWS::CloudWatch::Alarm resources and the ComparisonOperator, Threshold and AlarmActions of one alarm per kind, normalizing asset hashes with test-utils/normalize-template.ts as SNAPSHOT_TESTING.md describes if it adds a snapshot. It runs what scripts/build-typescript.sh runs for every example on a pull request: npm ci, npm run build, npm test and cdk synth. Nothing is deployed; the sandbox has no AWS credentials and needs none for a synthesized template.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add CloudWatch alarms to this AWS CDK stack. Read index.ts (or lib/), cdk.json, package.json and jest.config.js. Using the metric methods on the constructs already in the stack, add alarms from aws-cdk-lib/aws-cloudwatch for each Lambda function's errors, throttles and p99 duration against its timeout, the REST API's 5XX count and latency, and the DynamoDB table's throttled requests, each with an alarmDescription naming the resource and treatMissingData set so missing data does not alarm. Route them to one SNS topic whose email subscription is read from a CDK context key and skipped when absent. Use the thresholds and periods given after this prompt or in the issue that follows it; where none is given, use the construct defaults and list every value I should set in the pull request. Write test/alarms.test.ts with Template.fromStack asserting the alarm count and the threshold, comparison and actions of one alarm per kind. Run npm ci, npm run build, npm test and cdk synth as scripts/build-typescript.sh does. Deploy nothing. Open a pull request that lists each alarm with its metric, statistic, period and threshold.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request that changes index.ts, adds test/alarms.test.ts, and, if the example had none, adds the test script that scripts/build-typescript.sh runs when present; the assertions come from aws-cdk-lib/assertions, which is already a dependency. The description is a table of every alarm with its metric, statistic, period, evaluation periods, threshold and the construct it watches, followed by the list of values to set before deploying and the cdk synth and jest summaries from the sandbox.
A reviewer reads the table once for thresholds that are placeholders, confirms the SNS subscription comes from context rather than a committed address, and checks that the duration alarm's threshold is below the function's actual timeout, since the example uses the construct's default and a stack that sets a longer one needs the constant changed. The repository's build-pull-request.yml builds, tests and synthesizes the changed example on the pull request, so the checks confirm the sandbox run.
The transcript keeps the synthesized template, so a reviewer can read the AWS::CloudWatch::Alarm resources as CloudFormation rather than as CDK calls. A follow-up on the same session can add a composite alarm or a dashboard once the thresholds are agreed.
Run it on every Linear issue labelled agent
In the console, add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled, filtered to the agent label. When an engineer labels an issue such as "the items API had 5XXs for an hour and nobody was paged", the trigger puts the issue, with the thresholds it names, after the prompt and starts a run in the stack's repository; the pull request links back to the issue. Only label changes made by a person in your Rush org fire it.
The same body can be sent from your own tooling with POST /o/{org}/sessions, the repository in resources and the issue's URL beside it, read through your Linear connection. A request_id made from the issue id makes a retried call return the run it already started rather than a second pull request.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Alarms are the part of a stack that is written last and tested never, because testing them means deploying. The agent does the part that can be proven without an account: it reads the constructs, writes the alarms against their own metric methods, asserts them with Template.fromStack, and synthesizes the template in its own sandbox, keeping the output, so the pull request shows the CloudFormation that would be deployed.
The numbers stay yours. The agent writes the thresholds it was given into named constants and lists the ones it had to default, rather than guessing at an error rate for an API it has never watched, and a follow-up on the same session changes them in one place. Several stacks can be done at once, each in its own sandbox, with a table per pull request a reviewer can read in a minute.
Frequently asked questions
Does it deploy anything or need AWS credentials?
No. cdk synth writes the CloudFormation template to cdk.out without an account, and the Jest assertions run against the synthesized template. The pull request's own check in build-pull-request.yml does the same. Deploying is the stack owner's step after review.
Who picks the thresholds?
You do, in the prompt or the issue. The agent writes them into named constants with a comment each, and where it was given none it uses the construct's default and lists the value in the pull request as one to set. It does not infer a baseline from code that has no traffic history in front of it.
Why is there an API 5XX alarm when the functions already have an errors alarm?
Because the handlers in lambdas/ catch DynamoDB errors and return a 500 themselves, which is a successful invocation as far as Lambda's Errors metric is concerned. The API Gateway 5XXError metric counts those responses, so the two alarms cover different failures, and the pull request says which one would have fired for a caught error.
Where do the alarm notifications go?
To one SNS topic the stack creates, with an email subscription added only when a CDK context key such as alarmEmail is set, so no address is committed and cdk synth in CI passes without one. Wiring the topic to a chat or paging service is a follow-up on the same session with that service's subscription type.