From 2c6d165c94144618f9fdbed375e20190f3cf276f Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Wed, 19 Aug 2026 00:45:07 +0200 Subject: [PATCH 01/13] add pricing reference file --- .../investigation-cost-guardrail/CHANGELOG.md | 4 + skills/investigation-cost-guardrail/SKILL.md | 19 ++-- .../references/pricing-reference.md | 93 +++++++++++++++++++ 3 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 skills/investigation-cost-guardrail/references/pricing-reference.md diff --git a/skills/investigation-cost-guardrail/CHANGELOG.md b/skills/investigation-cost-guardrail/CHANGELOG.md index 436277f..4734729 100644 --- a/skills/investigation-cost-guardrail/CHANGELOG.md +++ b/skills/investigation-cost-guardrail/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.1.0 + +- Add `references/pricing-reference.md` — regional rate resolution for AWS API cost estimates: session cache → Price List Query API `GetProducts` filtered on `regionCode` → baseline × uplift → halt, response parsing, `ServiceCode` resolution, lookup caps, and the endpoint-vs-priced-region distinction. + ## 2.0.0 - Expand the scope from a fixed CloudWatch/X-Ray/CloudTrail set to all AWS service with the agent's own tools; `aws-services` metadata changes to `All`. diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index 92750d8..ce86d15 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -1,14 +1,3 @@ ---- -name: investigation-cost-guardrail -description: Cost guardrail for AWS DevOps Agent that covers ALL AWS services and native agent tools. Before the agent makes any paid API call, this skill estimates cost, enforces budgets per investigation, detects expensive operations across all services (Athena queries, S3 scans, DynamoDB scans, SageMaker inference, PromQL, etc.), enforces time window requirements, monitors cumulative call volume, and cancels if thresholds are exceeded. This skill applies to ALL investigations regardless of which services are involved. -metadata: - author: tqquresh, inesttia - version: "2.0.0" - aws-devops-agent-skills.agent-types: "Incident RCA" - aws-devops-agent-skills.aws-services: "All" - aws-devops-agent-skills.technical-domains: "Cost Optimization, Operations" ---- - # Investigation Cost Guardrail Skill ## Overview @@ -27,6 +16,10 @@ Rather than listing every free/paid operation across 200+ AWS services, this ski This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user invocation. +## When to Load the Pricing Reference + +`references/pricing-reference.md` holds the authoritative per-region rates and cost formulas. The **first time** an operation is classified **PAID** by Layer 2, you MUST read it **before** estimating that operation's cost. Load it once, then reuse it for the rest of the investigation. Investigations that never invoke a paid AWS operation (metadata-only, or third-party tools only) do not need to load it. + --- ## Layer 0: Native Agent Tool Classification @@ -133,7 +126,7 @@ If an operation doesn't clearly fit Rules 1–3: ## Layer 2: Known-Paid Registry -These operations have **confirmed pricing** with estimation formulas. This list is extensible — operators can add entries. +These operations have **confirmed pricing** with estimation formulas. This list is extensible — operators can add entries. Per-region rates and usagetype tokens live in the pricing reference. ### Confirmed Paid Operations @@ -316,4 +309,4 @@ When halting or warning, ALWAYS suggest free or cheaper alternatives: | `xray:GetTraceSummaries` (broad) | Narrow time + add filter expression | 90%+ | | `s3:GetObject` (large) | `s3:SelectObjectContent` with SQL | Variable | ---- +--- \ No newline at end of file diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md new file mode 100644 index 0000000..8663cce --- /dev/null +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -0,0 +1,93 @@ +# AWS Pricing Reference for Investigation Cost Estimation + +## How to use this file + +1. **Determine workload region** from the resource ARN or `aws_region` param — never default to agent space region. +2. **Query the Pricing API** using the template table below. **Always use `aws_region=us-east-1`** — the Pricing API endpoint only exists in us-east-1 and ap-south-1. Calling it from any other region (ap-northeast-1, sa-east-1, us-west-2, etc.) will fail with a connection error or AccessDeniedException. The workload region appears only as a `regionCode` filter value, never as the API endpoint region. +3. **Cache the result** as `rate_cache[(service, operation, workload_region)]` — one lookup per service+region per investigation. +4. **Fall back to floor rate** only on API failure + +--- + +## Pricing API Query Templates + +All queries follow this structure — always `aws_region=us-east-1`: +```bash +aws pricing get-products --service-code --filters --region us-east-1 +``` + +| Service | ServiceCode | Filter field | Filter value | Floor rate | Formula | +|---|---|---|---|---|---| +| CW Logs Insights | `AmazonCloudWatch` | `usagetype` | `-DataScanned-Bytes` | $0.005/GB ² | `scan_gb × rate` | +| CW GetMetricData | `AmazonCloudWatch` | `operation` | `GetMetricData` | $0.01/1K metrics | `(metrics × periods) / 1K × rate` | +| CW Contributor Insights | `AmazonCloudWatch` | `usagetype` | `-CW:ContributorInsightEvents`| $0.020/1M | `rules × (events / 1M) × rate` | +| X-Ray GetTraceSummaries | `AWSXRay` | `operation` | `XRay-Traces-Scanned` | $0.50/1M traces | `traces / 1M × rate` | +| X-Ray BatchGetTraces | `AWSXRay` | `operation` | `XRay-Traces-Retrieved` | $0.50/1M traces | `traces / 1M × rate` | +| Athena SQL | `AmazonAthena` | `usagetype` | `-DataScannedInTB` | $5.00/TB | `scan_tb × rate`; min 10MB | +| S3 GET/SELECT (Tier2) | `AmazonS3` | `usagetype` | `-Requests-Tier2` | $0.0004/1K | `requests / 1K × rate` | +| S3 PUT/COPY/LIST (Tier1) | `AmazonS3` | `usagetype` | `-Requests-Tier1` | $0.005/1K | `requests / 1K × rate` | + + +--- + +## S3 Tier Mapping + +| Tier | usagetype | Operations | Floor | +|---|---|---|---| +| **Tier1** | `Requests-Tier1` | PUT, COPY, POST, **LIST** | $0.005/1K | +| **Tier2** | `Requests-Tier2` | **GET**, SELECT, HEAD | $0.0004/1K | + +--- + +## Cross-Region Data Transfer Rates + +> ⚠️ **Do NOT use a flat $0.02/GB for all regions.** Transfer rates vary significantly. AP → US is 4.5× higher than EU → US. + +| Source region | Destination | Rate (confirmed via Pricing API) | +|---|---|---| +| us-east-1, us-east-2, us-west-* | Any other AWS region | $0.02/GB | +| eu-* | us-east-1 / other regions | $0.02/GB | +| ap-northeast-1 (Tokyo) | us-east-1 / other regions | $0.09/GB | +| ap-southeast-1 (Singapore) | us-east-1 / other regions | $0.09/GB | +| ap-southeast-2 (Sydney) | us-east-1 / other regions | $0.09/GB | +| ap-south-1 (Mumbai) | us-east-1 / other regions | $0.086/GB | +| sa-east-1 (São Paulo) | us-east-1 / other regions | $0.138/GB | + +**Formula**: `returned_data_gb × regional_transfer_rate` + +--- + +## Region Prefix Mapping + +| Region | Prefix | Exceptions | +|---|---|---| +| us-east-1 | *(none)* | Contributor Insights: always `USE1-`; Lambda: bare `Request`; DynamoDB: bare `ReadRequestUnits` | +| us-east-2 | USE2 | | +| us-west-1 | USW1 | | +| us-west-2 | USW2 | | +| eu-west-1 | EU | X-Ray: `EUW1-` not `EU-` | +| eu-west-2 | EUW2 | | +| eu-west-3 | EUW3 | | +| eu-central-1 | EUC1 | | +| eu-north-1 | EUN1 | | +| ap-southeast-1 | APS1 | | +| ap-southeast-2 | APS2 | | +| ap-northeast-1 | APN1 | | +| ap-northeast-2 | APN2 | | +| ap-south-1 | APS3 | | +| sa-east-1 | SAE1 | | +| ca-central-1 | CAN1 | | +| me-south-1 | MES1 | | +| af-south-1 | AFS1 | | + +--- + +## Free Operations (no cost, no lookup needed) + +`logs:DescribeLogGroups`, `logs:FilterLogEvents`, `cloudtrail:LookupEvents`, `EC2/ECS/RDS Describe*`, `cloudwatch:GetMetricStatistics`, `dynamodb:DescribeTable`, `s3:HeadObject`, `lambda:GetFunction`, `lambda:GetFunctionConfiguration`, `kinesis:DescribeStream`, `kinesis:ListShards`, `kinesis:GetRecords`, `sqs:GetQueueAttributes` + +--- + +## Reference Links + +[CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [Kinesis](https://aws.amazon.com/kinesis/data-streams/pricing/) · [SQS](https://aws.amazon.com/sqs/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Resource Explorer](https://aws.amazon.com/resource-explorer/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) From 350a2c62a13d5892793fe071428de34df582ab1a Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Wed, 19 Aug 2026 08:39:26 +0200 Subject: [PATCH 02/13] update skill md file --- skills/investigation-cost-guardrail/SKILL.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index ce86d15..60fa362 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -1,3 +1,14 @@ +--- +name: investigation-cost-guardrail +description: Cost guardrail for AWS DevOps Agent that covers ALL AWS services and native agent tools. Before the agent makes any paid API call, this skill estimates cost, enforces budgets per investigation, detects expensive operations across all services (Athena queries, S3 scans, DynamoDB scans, SageMaker inference, PromQL, etc.), enforces time window requirements, monitors cumulative call volume, and cancels if thresholds are exceeded. This skill applies to ALL investigations regardless of which services are involved. +metadata: + author: tqquresh, inesttia + version: "2.1.0" + aws-devops-agent-skills.agent-types: "Incident RCA" + aws-devops-agent-skills.aws-services: "All" + aws-devops-agent-skills.technical-domains: "Cost Optimization, Operations" +--- + # Investigation Cost Guardrail Skill ## Overview @@ -15,7 +26,6 @@ Rather than listing every free/paid operation across 200+ AWS services, this ski ## Activation This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user invocation. - ## When to Load the Pricing Reference `references/pricing-reference.md` holds the authoritative per-region rates and cost formulas. The **first time** an operation is classified **PAID** by Layer 2, you MUST read it **before** estimating that operation's cost. Load it once, then reuse it for the rest of the investigation. Investigations that never invoke a paid AWS operation (metadata-only, or third-party tools only) do not need to load it. From 864c5b03579ae69be656854aa6f39f70700676cb Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Wed, 19 Aug 2026 08:56:42 +0200 Subject: [PATCH 03/13] update changelog --- skills/investigation-cost-guardrail/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skills/investigation-cost-guardrail/CHANGELOG.md b/skills/investigation-cost-guardrail/CHANGELOG.md index 4734729..3f74d48 100644 --- a/skills/investigation-cost-guardrail/CHANGELOG.md +++ b/skills/investigation-cost-guardrail/CHANGELOG.md @@ -2,7 +2,10 @@ ## 2.1.0 -- Add `references/pricing-reference.md` — regional rate resolution for AWS API cost estimates: session cache → Price List Query API `GetProducts` filtered on `regionCode` → baseline × uplift → halt, response parsing, `ServiceCode` resolution, lookup caps, and the endpoint-vs-priced-region distinction. + - Add `references/pricing-reference.md` to resolve the per-region rate for a paid operation at estimation time. + - It ships Price List Query API lookup templates for the operations the skill estimates most often (CloudWatch Logs Insights, `GetMetricData`, Contributor Insights, X-Ray, Athena, S3 requests). + - `SKILL.md` loads it on the first operation Layer 2 classifies as PAID and reuses it for the rest of the investigation; baseline rates are indicative only. + ## 2.0.0 From ecb4e9f42b3ca1d7c36d879079bcc62630224447 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Wed, 19 Aug 2026 09:11:27 +0200 Subject: [PATCH 04/13] update readme --- skills/investigation-cost-guardrail/README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/skills/investigation-cost-guardrail/README.md b/skills/investigation-cost-guardrail/README.md index c34006f..dd1bab5 100644 --- a/skills/investigation-cost-guardrail/README.md +++ b/skills/investigation-cost-guardrail/README.md @@ -110,6 +110,21 @@ Even if AWS launches a new service tomorrow, the heuristic rules will correctly ``` +### Regional Rate Resolution + +Rates vary by AWS Region. `references/pricing-reference.md` lets the skill resolve the rate for the workload's Region rather than applying the us-east-1 baselines listed in Layer 2. + +| Step | What happens | +| --- | --- | +| 1. Region | Derived from the resource ARN | +| 2. Lookup | `pricing:GetProducts`, with the workload Region passed as a `regionCode` filter value | +| 3. Cache | Keyed on `(service, operation, region)` — one lookup per service and Region per investigation | +| 4. Fallback | Published baseline rate, if the lookup is unavailable | + +The reference also maps `usagetype` Region prefixes and their exceptions, S3 Tier1/Tier2 requests, cross-Region transfer rates, and the operations that are free and need no lookup. + +It loads once, on the first operation classified as PAID, and is reused for the rest of the investigation. Investigations that touch only metadata or third-party tools do not load it. Baseline rates are indicative; the live API is authoritative. + ### Layer 3: Response Validation (Self-Learning) After execution, the skill checks response fields for metered indicators: @@ -287,6 +302,28 @@ Add the skill to your Agent Space and adjust the threshold to match your organiz **Option B:** Download the `.zip` directly from the [repository](https://github.com/aws/tools-for-devops-agent/tree/main/skills/investigation-cost-guardrail) and upload it as a skill in your Agent Space. +### Required IAM Permissions + +The skill calls the AWS Price List Query API to resolve per-Region rates. Grant the role your Agent Space assumes: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "pricing:GetProducts", + "Resource": "*" + } + ] +} +``` + +`Resource` is `*` because the Price List API returns public pricing data. The API is free and read-only. + +Your Agent Space tool policy must also permit the call. If the lookup is unavailable, the skill uses the published baseline rates in `references/pricing-reference.md` and flags the estimate. + + ## Known Limitations - **Budget is scoped to a single investigation:** each investigation starts with a fresh budget; cumulative tracking across multiple investigations at the agent space level is not currently supported. From 3e6c65f0e783fe5f6f2da6675fb02153537e735b Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 20:19:24 +0200 Subject: [PATCH 05/13] remove Pricing API Query Templates and update skill file --- skills/investigation-cost-guardrail/SKILL.md | 85 ++++++----- .../references/pricing-reference.md | 135 ++++++++---------- 2 files changed, 107 insertions(+), 113 deletions(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index 60fa362..02a4822 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -26,9 +26,22 @@ Rather than listing every free/paid operation across 200+ AWS services, this ski ## Activation This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user invocation. -## When to Load the Pricing Reference -`references/pricing-reference.md` holds the authoritative per-region rates and cost formulas. The **first time** an operation is classified **PAID** by Layer 2, you MUST read it **before** estimating that operation's cost. Load it once, then reuse it for the rest of the investigation. Investigations that never invoke a paid AWS operation (metadata-only, or third-party tools only) do not need to load it. +## Fetch Live Rate Before Estimating + +The **first time** an operation is classified **PAID** by Layer 2, fetch the live rate from the AWS Pricing API **before** estimating cost. Always call the Pricing API from `us-east-1`. + +### Standard pattern (works for all services except S3, Athena, and SageMaker) + +```bash +aws pricing get-products \ + --service-code \ + --filters '[{"Type":"TERM_MATCH","Field":"operation","Value":""}, + {"Type":"TERM_MATCH","Field":"regionCode","Value":""}]' \ + --region us-east-1 +``` + +Use the **operation name from Layer 2** directly as the filter value (e.g. `StartQuery`, `GetMetricData`, `XRay-Traces-Scanned`). Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. --- @@ -99,8 +112,6 @@ An operation is FREE if it matches ALL of these: > ⚠️ **Exception:** Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for: **S3** ($0.0004/1K GET, $0.005/1K LIST), **SQS** ($0.40/1M requests after free tier), **Lambda Invoke** ($0.20/1M). When Layer 2 has an entry, it takes precedence over Rule 1. -> ⚠️ **Tool policy can override cost classification.** Some operations classified as FREE here (e.g., `cloudtrail:LookupEvents`) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. - ### Rule 2: PAID — Data-scanning operations An operation is PAID if it matches ANY of these patterns: @@ -136,33 +147,31 @@ If an operation doesn't clearly fit Rules 1–3: ## Layer 2: Known-Paid Registry -These operations have **confirmed pricing** with estimation formulas. This list is extensible — operators can add entries. Per-region rates and usagetype tokens live in the pricing reference. +These operations have **confirmed pricing**. Always fetch the live rate via the Pricing API before estimating. If the Pricing API call fails, halt — do not estimate from memory. ### Confirmed Paid Operations -| Service | Operation | Cost Formula | Estimation Method | +| ServiceCode | Operation | Cost Formula | Estimation Method | |---|---|---|---| -| CloudWatch Logs | `StartQuery` | $0.005/GB scanned | Query `IncomingBytes` metric for time window | -| CloudWatch Logs | `StartLiveTail` | $0.01/minute | Duration-based | -| CloudWatch | `GetMetricData` | $0.01/1,000 metrics×periods | Count metrics and periods | -| CloudWatch | `get_prometheus_metrics` | $0.01/1,000 metrics×periods | Count series × (range/step) | -| X-Ray | `GetTraceSummaries` | $0.50/1M traces | Paginate or sample to estimate count | -| X-Ray | `BatchGetTraces` | $0.50/1M traces | Count trace IDs in request | -| Athena | `StartQueryExecution` | $5.00/TB scanned | Check table metadata; require `WHERE` clause | -| DynamoDB | `Scan` | RCU consumed (~$0.25/1M RCU) | Check `TableSizeBytes`; BLOCK unless user approves | -| DynamoDB | `Query` (broad) | RCU consumed | Check `ItemCount`; warn if > 10K items | -| S3 | `GetObject` | $0.0004/1,000 requests + $0.09/GB transfer | Count requests; flag if cross-region or >100MB | -| S3 | `ListObjectsV2`, `ListObjects` | $0.005/1,000 requests | Count calls; warn if paginating heavily | -| S3 | `PutObject`, `CopyObject` | $0.005/1,000 requests | Count calls | -| S3 | `SelectObjectContent` | $0.002/GB scanned + $0.0007/GB returned | Check object size | -| Resource Explorer | `Search` | $0.01/query (after 1000 free/month) | Count calls | -| SageMaker | `InvokeEndpoint` | Instance-dependent | BLOCK — require explicit approval | -| Kinesis | `GetRecords` | $0.015/1M records | Estimate from shard count × duration | -| CloudWatch | Contributor Insights | $0.02/rule/1K events | Count rules and event volume | -| SQS | All operations | $0.40/1M requests (first 1M free/month) | Count total SQS calls; usually negligible | -| Lambda | `Invoke` | $0.20/1M requests + compute ($0.0000166667/GB-sec) | BLOCK unless user explicitly requests function execution | - -> ℹ️ **Rates are baseline published figures and may vary by region.** The regional rate may be higher, so a rate-based estimate is a lower bound. Treat any estimate within 20% of the remaining budget as exceeding it. +| `AmazonCloudWatch` | `StartQuery` | `scan_gb × rate` | Query `IncomingBytes` metric for time window | +| `AmazonCloudWatch` | `StartLiveTail` | Duration-based | Duration-based | +| `AmazonCloudWatch` | `GetMetricData` | `(metrics × periods) / 1K × rate` | Count metrics and periods | +| `AmazonCloudWatch` | `get_prometheus_metrics` | `(series × datapoints) / 1K × rate` | Count series × (range/step) | +| `AmazonCloudWatch` | `GetInsightRuleReport` | `rules × (events / 1K) × rate` | Count rules and event volume | +| `AWSXRay` | `GetTraceSummaries` | `traces / 1M × rate` | Paginate or sample to estimate count | +| `AWSXRay` | `BatchGetTraces` | `traces / 1M × rate` | Count trace IDs in request | +| `AmazonAthena` | `StartQueryExecution` | `scan_tb × rate`; min 10MB | Check table metadata; require `WHERE` clause | +| `AmazonDynamoDB` | `Scan` | RCU consumed | Check `TableSizeBytes`; BLOCK unless user approves | +| `AmazonDynamoDB` | `Query` | RCU consumed | Check `ItemCount`; warn if > 10K items | +| `AmazonS3` | `GetObject` | See pricing-reference.md | Count requests; flag if cross-region or >100MB | +| `AmazonS3` | `ListObjectsV2`, `ListObjects` | See pricing-reference.md | Count calls; warn if paginating heavily | +| `AmazonS3` | `PutObject`, `CopyObject` | See pricing-reference.md | Count calls | +| `AmazonS3` | `SelectObjectContent` | `scan_gb × rate` | Check object size | +| `AWSResourceExplorer2` | `Search` | Per query | Count calls | +| `AmazonSageMaker` | `InvokeEndpoint` | — | BLOCK — require explicit user approval | +| `AmazonKinesis` | `GetRecords` | `records / 1M × rate` | Estimate from shard count × duration | +| `AmazonSQS` | All operations | Per request | Count total SQS calls; usually negligible | +| `AWSLambda` | `Invoke` | Per request + compute | BLOCK unless user explicitly requests function execution | --- @@ -176,9 +185,9 @@ After ANY operation executes, check the response for metered fields: |---|---|---| | `BytesScanned`, `DataScanned` | Data scanning charge | Record GB scanned, add to running cost | | `RecordsProcessed`, `ItemCount` | Record processing | Record count, estimate RCU/cost | -| `QueryExecutionId` + `DataScannedInBytes` | Athena scan | Add to cost at $5/TB | -| `TracesProcessedCount` | X-Ray processing | Add to cost at $0.50/1M | -| `ConsumedCapacity` | DynamoDB RCU/WCU | Add to cost at $0.25/1M RCU | +| `QueryExecutionId` + `DataScannedInBytes` | Athena scan | Add to cost at live rate | +| `TracesProcessedCount` | X-Ray processing | Add to cost at live rate | +| `ConsumedCapacity` | DynamoDB RCU/WCU | Add to cost at live rate | | `ContentLength` > 100MB | Large object fetch | Flag for transfer cost | | `NextToken` after 10+ pages | Pagination runaway | Trigger volume guardrail | | `warnings` containing "500 series" | PromQL truncation | Flag max-cost query, suggest narrowing | @@ -187,7 +196,7 @@ If a previously-unclassified operation returns metered fields: 1. Log it as a paid operation for this session 2. Add the cost to the running total -3. Warn the user: `⚠️ Discovered paid operation: : cost $X.XX` +3. Warn the user: `⚠️ Discovered paid operation: : cost $X.XX` --- @@ -215,14 +224,14 @@ else: proceed # After execution: running_cost += actual_cost (from response fields or estimation) - call_counts[service] += 1 + call_counts[servicecode] += 1 ``` **Volume guardrails:** ```text -if call_counts[any_service] > 200: ⚠️ WARN -if call_counts[any_service] > 500: 🚫 HALT +if call_counts[any_servicecode] > 200: ⚠️ WARN +if call_counts[any_servicecode] > 500: 🚫 HALT if sum(all_call_counts) > 1000: 🚫 HALT ``` @@ -232,12 +241,12 @@ if sum(all_call_counts) > 1000: 🚫 HALT ```text 📋 INVESTIGATION BUDGET STATUS -═══════════════════════════════════════════════════════════ +════════════════════════════════════════════════════════════ Budget: $10.00 Spent: $X.XX (Y paid operations) Free calls: Z operations (no cost) PromQL: X,XXX metrics×periods consumed ($X.XX) -Next op: : — estimated $X.XX +Next op: : — estimated $X.XX Projected: $X.XX (exceeds budget by $X.XX) 🚫 HALTED — would exceed $10.00 budget. @@ -273,7 +282,7 @@ For EVERY paid operation: ```text if target_region ≠ agent_space_region: estimated_return_size = estimate_return_bytes(operation_type) - transfer_cost = estimated_return_size × $0.02/GB + transfer_cost = estimated_return_size × regional_transfer_rate # from pricing-reference.md total_estimate += transfer_cost flag: "⚠️ Cross-region transfer: " ``` @@ -318,5 +327,3 @@ When halting or warning, ALWAYS suggest free or cheaper alternatives: | `athena:StartQueryExecution` (full) | Add partition filter in `WHERE` | 90%+ | | `xray:GetTraceSummaries` (broad) | Narrow time + add filter expression | 90%+ | | `s3:GetObject` (large) | `s3:SelectObjectContent` with SQL | Variable | - ---- \ No newline at end of file diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index 8663cce..df44381 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -1,90 +1,77 @@ -# AWS Pricing Reference for Investigation Cost Estimation +# AWS Pricing Reference — S3 and Cross-Region Transfer -## How to use this file - -1. **Determine workload region** from the resource ARN or `aws_region` param — never default to agent space region. -2. **Query the Pricing API** using the template table below. **Always use `aws_region=us-east-1`** — the Pricing API endpoint only exists in us-east-1 and ap-south-1. Calling it from any other region (ap-northeast-1, sa-east-1, us-west-2, etc.) will fail with a connection error or AccessDeniedException. The workload region appears only as a `regionCode` filter value, never as the API endpoint region. -3. **Cache the result** as `rate_cache[(service, operation, workload_region)]` — one lookup per service+region per investigation. -4. **Fall back to floor rate** only on API failure +This file covers the two cases that cannot use the standard `operation + regionCode` Pricing API pattern from SKILL.md: +1. **S3** — uses `usagetype` tiers, not `operation` +2. **Cross-region data transfer** — reference rates (Pricing API response is multi-destination and noisy; use table below) --- -## Pricing API Query Templates +## S3 Pricing Lookup + +S3 has no `operation` field — use `usagetype` with the region prefix and tier: -All queries follow this structure — always `aws_region=us-east-1`: ```bash -aws pricing get-products --service-code --filters --region us-east-1 +aws pricing get-products \ + --service-code AmazonS3 \ + --filters '[{"Type":"TERM_MATCH","Field":"usagetype","Value":"-Requests-"}]' \ + --region us-east-1 ``` -| Service | ServiceCode | Filter field | Filter value | Floor rate | Formula | -|---|---|---|---|---|---| -| CW Logs Insights | `AmazonCloudWatch` | `usagetype` | `-DataScanned-Bytes` | $0.005/GB ² | `scan_gb × rate` | -| CW GetMetricData | `AmazonCloudWatch` | `operation` | `GetMetricData` | $0.01/1K metrics | `(metrics × periods) / 1K × rate` | -| CW Contributor Insights | `AmazonCloudWatch` | `usagetype` | `-CW:ContributorInsightEvents`| $0.020/1M | `rules × (events / 1M) × rate` | -| X-Ray GetTraceSummaries | `AWSXRay` | `operation` | `XRay-Traces-Scanned` | $0.50/1M traces | `traces / 1M × rate` | -| X-Ray BatchGetTraces | `AWSXRay` | `operation` | `XRay-Traces-Retrieved` | $0.50/1M traces | `traces / 1M × rate` | -| Athena SQL | `AmazonAthena` | `usagetype` | `-DataScannedInTB` | $5.00/TB | `scan_tb × rate`; min 10MB | -| S3 GET/SELECT (Tier2) | `AmazonS3` | `usagetype` | `-Requests-Tier2` | $0.0004/1K | `requests / 1K × rate` | -| S3 PUT/COPY/LIST (Tier1) | `AmazonS3` | `usagetype` | `-Requests-Tier1` | $0.005/1K | `requests / 1K × rate` | - - ---- - -## S3 Tier Mapping - -| Tier | usagetype | Operations | Floor | -|---|---|---|---| -| **Tier1** | `Requests-Tier1` | PUT, COPY, POST, **LIST** | $0.005/1K | -| **Tier2** | `Requests-Tier2` | **GET**, SELECT, HEAD | $0.0004/1K | +Replace `` with `Tier1` or `Tier2` based on the operation. For us-east-1 omit the prefix entirely (e.g. `Requests-Tier1`). + +### S3 Tier Mapping + +| Tier | Operations | +|---|---| +| `Tier1` | PUT, COPY, POST, LIST | +| `Tier2` | GET, SELECT, HEAD | + +### Region Prefix Mapping + +| Region | Prefix | +|---|---| +| us-east-1 | *(omit — bare `Requests-Tier1`)* | +| us-east-2 | USE2 | +| us-west-1 | USW1 | +| us-west-2 | USW2 | +| eu-west-1 | EU | +| eu-west-2 | EUW2 | +| eu-west-3 | EUW3 | +| eu-central-1 | EUC1 | +| eu-central-2 | EUC2 | +| eu-north-1 | EUN1 | +| eu-south-1 | EUS1 | +| ap-southeast-1 | APS1 | +| ap-southeast-2 | APS2 | +| ap-southeast-3 | APS4 | +| ap-southeast-4 | APS6 | +| ap-northeast-1 | APN1 | +| ap-northeast-2 | APN2 | +| ap-northeast-3 | APN3 | +| ap-south-1 | APS3 | +| ap-east-1 | APE1 | +| sa-east-1 | SAE1 | +| ca-central-1 | CAN1 | +| me-south-1 | MES1 | +| me-central-1 | MEC1 | +| af-south-1 | AFS1 | +| il-central-1 | ILC1 | --- ## Cross-Region Data Transfer Rates -> ⚠️ **Do NOT use a flat $0.02/GB for all regions.** Transfer rates vary significantly. AP → US is 4.5× higher than EU → US. - -| Source region | Destination | Rate (confirmed via Pricing API) | -|---|---|---| -| us-east-1, us-east-2, us-west-* | Any other AWS region | $0.02/GB | -| eu-* | us-east-1 / other regions | $0.02/GB | -| ap-northeast-1 (Tokyo) | us-east-1 / other regions | $0.09/GB | -| ap-southeast-1 (Singapore) | us-east-1 / other regions | $0.09/GB | -| ap-southeast-2 (Sydney) | us-east-1 / other regions | $0.09/GB | -| ap-south-1 (Mumbai) | us-east-1 / other regions | $0.086/GB | -| sa-east-1 (São Paulo) | us-east-1 / other regions | $0.138/GB | - -**Formula**: `returned_data_gb × regional_transfer_rate` - ---- - -## Region Prefix Mapping - -| Region | Prefix | Exceptions | -|---|---|---| -| us-east-1 | *(none)* | Contributor Insights: always `USE1-`; Lambda: bare `Request`; DynamoDB: bare `ReadRequestUnits` | -| us-east-2 | USE2 | | -| us-west-1 | USW1 | | -| us-west-2 | USW2 | | -| eu-west-1 | EU | X-Ray: `EUW1-` not `EU-` | -| eu-west-2 | EUW2 | | -| eu-west-3 | EUW3 | | -| eu-central-1 | EUC1 | | -| eu-north-1 | EUN1 | | -| ap-southeast-1 | APS1 | | -| ap-southeast-2 | APS2 | | -| ap-northeast-1 | APN1 | | -| ap-northeast-2 | APN2 | | -| ap-south-1 | APS3 | | -| sa-east-1 | SAE1 | | -| ca-central-1 | CAN1 | | -| me-south-1 | MES1 | | -| af-south-1 | AFS1 | | - ---- - -## Free Operations (no cost, no lookup needed) - -`logs:DescribeLogGroups`, `logs:FilterLogEvents`, `cloudtrail:LookupEvents`, `EC2/ECS/RDS Describe*`, `cloudwatch:GetMetricStatistics`, `dynamodb:DescribeTable`, `s3:HeadObject`, `lambda:GetFunction`, `lambda:GetFunctionConfiguration`, `kinesis:DescribeStream`, `kinesis:ListShards`, `kinesis:GetRecords`, `sqs:GetQueueAttributes` +Baseline rates — verify current values via the [Data Transfer pricing page](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) if precision is required. + +| Source region | Rate | +|---|---| +| us-east-1, us-east-2, us-west-* | $0.02/GB | +| eu-* | $0.02/GB | +| ap-northeast-1 (Tokyo) | $0.09/GB | +| ap-southeast-1 (Singapore) | $0.09/GB | +| ap-southeast-2 (Sydney) | $0.09/GB | +| ap-south-1 (Mumbai) | $0.086/GB | +| sa-east-1 (São Paulo) | $0.138/GB | --- From 42f0039b4538f27cca9b486324414965bb325dff Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 20:26:41 +0200 Subject: [PATCH 06/13] revert changes --- skills/investigation-cost-guardrail/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index 02a4822..efd9baa 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -112,6 +112,8 @@ An operation is FREE if it matches ALL of these: > ⚠️ **Exception:** Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for: **S3** ($0.0004/1K GET, $0.005/1K LIST), **SQS** ($0.40/1M requests after free tier), **Lambda Invoke** ($0.20/1M). When Layer 2 has an entry, it takes precedence over Rule 1. +⚠️ Tool policy can override cost classification. Some operations classified as FREE here (e.g., cloudtrail:LookupEvents) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. + ### Rule 2: PAID — Data-scanning operations An operation is PAID if it matches ANY of these patterns: From 136720fb6c41e2a2c58e88b5afec0665fd90e680 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 21:02:27 +0200 Subject: [PATCH 07/13] remove hardcoded cost cross region --- skills/investigation-cost-guardrail/SKILL.md | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index efd9baa..c20afe9 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -43,6 +43,22 @@ aws pricing get-products \ Use the **operation name from Layer 2** directly as the filter value (e.g. `StartQuery`, `GetMetricData`, `XRay-Traces-Scanned`). Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. +### Cross-region transfer rate + +For any paid operation where `target_region ≠ agent_space_region`, fetch the live transfer rate. The rate is **source-region-based and destination-independent** — one call returns the correct rate regardless of where data is going: + +```bash +aws pricing get-products \ + --service-code AWSDataTransfer \ + --filters '[{"Type":"TERM_MATCH","Field":"transferType","Value":"InterRegion Outbound"}, + {"Type":"TERM_MATCH","Field":"fromRegionCode","Value":""}, + {"Type":"TERM_MATCH","Field":"toLocationType","Value":"AWS Region"}]' \ + --max-results 1 \ + --region us-east-1 +``` + +Read `pricePerUnit.USD` from the first result. Cache as `transfer_rate_cache[source_region]` — one lookup per source region per investigation. + --- ## Layer 0: Native Agent Tool Classification @@ -112,7 +128,7 @@ An operation is FREE if it matches ALL of these: > ⚠️ **Exception:** Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for: **S3** ($0.0004/1K GET, $0.005/1K LIST), **SQS** ($0.40/1M requests after free tier), **Lambda Invoke** ($0.20/1M). When Layer 2 has an entry, it takes precedence over Rule 1. -⚠️ Tool policy can override cost classification. Some operations classified as FREE here (e.g., cloudtrail:LookupEvents) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. +> ⚠️ **Tool policy can override cost classification.** Some operations classified as FREE here (e.g., `cloudtrail:LookupEvents`) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. ### Rule 2: PAID — Data-scanning operations @@ -283,8 +299,10 @@ For EVERY paid operation: ```text if target_region ≠ agent_space_region: + fetch transfer_rate = transfer_rate_cache[target_region] + ?? live lookup (see "Cross-region transfer rate" above) estimated_return_size = estimate_return_bytes(operation_type) - transfer_cost = estimated_return_size × regional_transfer_rate # from pricing-reference.md + transfer_cost = estimated_return_size × transfer_rate total_estimate += transfer_cost flag: "⚠️ Cross-region transfer: " ``` From 9e989e96e7b775a2b8e5ad9d2a50049a4b106839 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 21:08:23 +0200 Subject: [PATCH 08/13] remove hardcoded cost cross region --- .../references/pricing-reference.md | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index df44381..a8673f2 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -1,8 +1,6 @@ -# AWS Pricing Reference — S3 and Cross-Region Transfer +# AWS Pricing Reference — S3 -This file covers the two cases that cannot use the standard `operation + regionCode` Pricing API pattern from SKILL.md: -1. **S3** — uses `usagetype` tiers, not `operation` -2. **Cross-region data transfer** — reference rates (Pricing API response is multi-destination and noisy; use table below) +This file covers S3 request pricing, which cannot use the standard `operation + regionCode` pattern from SKILL.md. For all other services use the standard pattern, and for cross-region transfer rates use the live lookup pattern documented in SKILL.md under "Cross-region transfer rate". --- @@ -59,22 +57,6 @@ Replace `` with `Tier1` or `Tier2` based on the operation. For us-east-1 o --- -## Cross-Region Data Transfer Rates - -Baseline rates — verify current values via the [Data Transfer pricing page](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) if precision is required. - -| Source region | Rate | -|---|---| -| us-east-1, us-east-2, us-west-* | $0.02/GB | -| eu-* | $0.02/GB | -| ap-northeast-1 (Tokyo) | $0.09/GB | -| ap-southeast-1 (Singapore) | $0.09/GB | -| ap-southeast-2 (Sydney) | $0.09/GB | -| ap-south-1 (Mumbai) | $0.086/GB | -| sa-east-1 (São Paulo) | $0.138/GB | - ---- - ## Reference Links [CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [Kinesis](https://aws.amazon.com/kinesis/data-streams/pricing/) · [SQS](https://aws.amazon.com/sqs/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Resource Explorer](https://aws.amazon.com/resource-explorer/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) From 486787f5c8c7c56b85611da051860362827077b8 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 21:35:36 +0200 Subject: [PATCH 09/13] cleanup skill.md file --- skills/investigation-cost-guardrail/SKILL.md | 35 ++---------- .../references/pricing-reference.md | 54 ++++++++++++++++++- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index c20afe9..be1e0b5 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -8,7 +8,6 @@ metadata: aws-devops-agent-skills.aws-services: "All" aws-devops-agent-skills.technical-domains: "Cost Optimization, Operations" --- - # Investigation Cost Guardrail Skill ## Overview @@ -29,35 +28,11 @@ This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user ## Fetch Live Rate Before Estimating -The **first time** an operation is classified **PAID** by Layer 2, fetch the live rate from the AWS Pricing API **before** estimating cost. Always call the Pricing API from `us-east-1`. - -### Standard pattern (works for all services except S3, Athena, and SageMaker) - -```bash -aws pricing get-products \ - --service-code \ - --filters '[{"Type":"TERM_MATCH","Field":"operation","Value":""}, - {"Type":"TERM_MATCH","Field":"regionCode","Value":""}]' \ - --region us-east-1 -``` - -Use the **operation name from Layer 2** directly as the filter value (e.g. `StartQuery`, `GetMetricData`, `XRay-Traces-Scanned`). Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. - -### Cross-region transfer rate +The **first time** an operation is classified **PAID** by Layer 2, fetch the live rate **before** estimating cost. -For any paid operation where `target_region ≠ agent_space_region`, fetch the live transfer rate. The rate is **source-region-based and destination-independent** — one call returns the correct rate regardless of where data is going: - -```bash -aws pricing get-products \ - --service-code AWSDataTransfer \ - --filters '[{"Type":"TERM_MATCH","Field":"transferType","Value":"InterRegion Outbound"}, - {"Type":"TERM_MATCH","Field":"fromRegionCode","Value":""}, - {"Type":"TERM_MATCH","Field":"toLocationType","Value":"AWS Region"}]' \ - --max-results 1 \ - --region us-east-1 -``` +For **AWS operations**: read `references/pricing-reference.md` for the exact Pricing API call patterns, exceptions, caching rules, and failure handling. -Read `pricePerUnit.USD` from the first result. Cache as `transfer_rate_cache[source_region]` — one lookup per source region per investigation. +For **non-AWS tools** (Splunk, Datadog, Grafana, etc.): use the cost model from Layer 0 directly — no live lookup available. --- @@ -165,7 +140,7 @@ If an operation doesn't clearly fit Rules 1–3: ## Layer 2: Known-Paid Registry -These operations have **confirmed pricing**. Always fetch the live rate via the Pricing API before estimating. If the Pricing API call fails, halt — do not estimate from memory. +These operations have **confirmed pricing**. Always fetch the live rate via the Pricing API (see `references/pricing-reference.md`) before estimating. If the Pricing API call fails, halt — do not estimate from memory. ### Confirmed Paid Operations @@ -300,7 +275,7 @@ For EVERY paid operation: ```text if target_region ≠ agent_space_region: fetch transfer_rate = transfer_rate_cache[target_region] - ?? live lookup (see "Cross-region transfer rate" above) + ?? live lookup (see references/pricing-reference.md) estimated_return_size = estimate_return_bytes(operation_type) transfer_cost = estimated_return_size × transfer_rate total_estimate += transfer_cost diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index a8673f2..c10933d 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -1,6 +1,56 @@ -# AWS Pricing Reference — S3 +# AWS Pricing Reference -This file covers S3 request pricing, which cannot use the standard `operation + regionCode` pattern from SKILL.md. For all other services use the standard pattern, and for cross-region transfer rates use the live lookup pattern documented in SKILL.md under "Cross-region transfer rate". +This file contains all AWS-specific Pricing API call patterns. Read this file the first time any operation is classified PAID by Layer 2, before estimating cost. + +--- + +## Standard Pattern (most services) + +Always call the Pricing API from `us-east-1` regardless of workload region: + +```bash +aws pricing get-products \ + --service-code \ + --filters '[{"Type":"TERM_MATCH","Field":"operation","Value":""}, + {"Type":"TERM_MATCH","Field":"regionCode","Value":""}]' \ + --region us-east-1 +``` + +Use the **operation name from Layer 2** directly as the filter value. Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. + +--- + +## Exceptions — Non-Standard Pricing API Patterns + +Some services cannot use the standard `operation + regionCode` pattern: + +| ServiceCode | AWS Operation | Correct Pricing API Pattern | +|---|---|---| +| `AmazonAthena` | `StartQueryExecution` | `regionCode` only — no `operation` field exists | +| `AmazonDynamoDB` | `Scan`, `Query` | `operation=PayPerRequestThroughput` + `group=DDB-ReadUnits` + `regionCode` | +| `AWSLambda` | `Invoke` | `group=AWS-Lambda-Requests` + `regionCode` (no `operation` field) | +| `AmazonS3` | All request ops | `usagetype` tiers — see S3 section below | +| `AmazonSageMaker` | `InvokeEndpoint` | Not in Pricing API — instance-hour billed. BLOCK, require user approval | +| `AmazonSQS` | All operations | Not in Pricing API catalog — use hardcoded $0.40/1M requests | +| `AWSResourceExplorer2` | `Search` | Not in Pricing API catalog — use hardcoded $0.00015/search | + +--- + +## Cross-Region Data Transfer Rate + +For any paid operation where `target_region ≠ agent_space_region`, fetch the live transfer rate. The rate is **source-region-based and destination-independent**: + +```bash +aws pricing get-products \ + --service-code AWSDataTransfer \ + --filters '[{"Type":"TERM_MATCH","Field":"transferType","Value":"InterRegion Outbound"}, + {"Type":"TERM_MATCH","Field":"fromRegionCode","Value":""}, + {"Type":"TERM_MATCH","Field":"toLocationType","Value":"AWS Region"}]' \ + --max-results 1 \ + --region us-east-1 +``` + +Read `pricePerUnit.USD` from the first result. Cache as `transfer_rate_cache[source_region]` — one lookup per source region per investigation. --- From 51dc14e1a91914fbdbac7267e4fdd3decf01fc9f Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 21 Aug 2026 21:41:49 +0200 Subject: [PATCH 10/13] cleanup reference file --- .../references/pricing-reference.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index c10933d..01a18fd 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -4,7 +4,7 @@ This file contains all AWS-specific Pricing API call patterns. Read this file th --- -## Standard Pattern (most services) +## Standard Pattern Always call the Pricing API from `us-east-1` regardless of workload region: @@ -18,21 +18,6 @@ aws pricing get-products \ Use the **operation name from Layer 2** directly as the filter value. Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. ---- - -## Exceptions — Non-Standard Pricing API Patterns - -Some services cannot use the standard `operation + regionCode` pattern: - -| ServiceCode | AWS Operation | Correct Pricing API Pattern | -|---|---|---| -| `AmazonAthena` | `StartQueryExecution` | `regionCode` only — no `operation` field exists | -| `AmazonDynamoDB` | `Scan`, `Query` | `operation=PayPerRequestThroughput` + `group=DDB-ReadUnits` + `regionCode` | -| `AWSLambda` | `Invoke` | `group=AWS-Lambda-Requests` + `regionCode` (no `operation` field) | -| `AmazonS3` | All request ops | `usagetype` tiers — see S3 section below | -| `AmazonSageMaker` | `InvokeEndpoint` | Not in Pricing API — instance-hour billed. BLOCK, require user approval | -| `AmazonSQS` | All operations | Not in Pricing API catalog — use hardcoded $0.40/1M requests | -| `AWSResourceExplorer2` | `Search` | Not in Pricing API catalog — use hardcoded $0.00015/search | --- From 8baaafc0558aba47f7b0ed71c6f61de7b8443247 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Wed, 26 Aug 2026 16:34:45 +0200 Subject: [PATCH 11/13] Replace hardcoded rates with live Pricing API lookups --- skills/investigation-cost-guardrail/SKILL.md | 89 +++++++++------ .../references/pricing-reference.md | 105 +++++++++++++----- 2 files changed, 132 insertions(+), 62 deletions(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index be1e0b5..0b57e40 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -30,7 +30,7 @@ This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user The **first time** an operation is classified **PAID** by Layer 2, fetch the live rate **before** estimating cost. -For **AWS operations**: read `references/pricing-reference.md` for the exact Pricing API call patterns, exceptions, caching rules, and failure handling. +For **AWS operations**: read `references/pricing-reference.md` for the exact Pricing API call patterns, filter fields, filter values, and failure handling. The reference file specifies — for each service and operation — whether to use `Field:"operation"` or `Field:"usagetype"`, and the exact value to use. Do not derive these from the operation name. For **non-AWS tools** (Splunk, Datadog, Grafana, etc.): use the cost model from Layer 0 directly — no live lookup available. @@ -44,7 +44,7 @@ Before Layer 1 heuristics, classify the agent's own tools. These are NOT `use_aw | Tool | Classification | Cost Model | Guardrail | |---|---|---|---| -| `get_prometheus_metrics` | **PAID** | $0.01 / 1,000 metrics×periods (same billing meter as CloudWatch GetMetricData) | Track series × datapoints per call | +| `get_prometheus_metrics` | **PAID** | Billed per sample scanned — **rate from live `CW:PromQL:SamplesScanned` usagetype lookup** (`AmazonCloudWatch`, workload-region prefix) | Track samples scanned per call; HALT if rate lookup returns 0 products | | `use_aws` | **VARIABLE** | Depends on operation — apply Layers 1–3 | Full heuristic pipeline | | `use_azure` | **FREE** | Azure Reader role, no per-call billing | Track count only | | `grafana_query_prometheus` | **CAUTION** | Depends on Grafana data source billing model | Track count, warn at 50+ | @@ -62,15 +62,18 @@ Before Layer 1 heuristics, classify the agent's own tools. These are NOT `use_aw `get_prometheus_metrics` deserves special handling because: -- Cost = (number of series returned) × (number of datapoints per series) / 1000 × $0.01 -- Maximum 500 series per query — a broad query hitting the cap costs ~$0.005 per period -- Range queries with small `step` multiply cost: `7d / 60s step = 10,080 datapoints × 500 series = 5M metrics` +- Maximum 500 series per query — a broad query hitting the cap costs `500 × rate` per period. +- Range queries with small `step` multiply cost: `7d / 60s step = 10,080 datapoints × 500 series = 5M samples` **Before each PromQL call:** ```text -estimated_metrics = min(500, estimated_series) × (time_range_seconds / step_seconds) -estimated_cost = (estimated_metrics / 1000) * 0.01 +rate = live usagetype lookup (AmazonCloudWatch, usagetype=-CW:PromQL:SamplesScanned) + # bare "CW:PromQL:SamplesScanned" for us-east-1; use workload-region prefix for all others +if rate lookup returns 0 products: 🚫 HALT — do not hardcode or improvise a rate + +estimated_samples = min(500, estimated_series) × (time_range_seconds / step_seconds) +estimated_cost = estimated_samples × rate if estimated_cost > $0.50: ⚠️ WARN — suggest narrower time range, larger step, or label filters @@ -99,9 +102,7 @@ An operation is FREE if it matches ALL of these: - It returns metadata/configuration (not data content or query results) - It does NOT scan, process, or transform customer data -**Examples:** `DescribeInstances`, `ListFunctions`, `GetRole`, `LookupEvents` - -> ⚠️ **Exception:** Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for: **S3** ($0.0004/1K GET, $0.005/1K LIST), **SQS** ($0.40/1M requests after free tier), **Lambda Invoke** ($0.20/1M). When Layer 2 has an entry, it takes precedence over Rule 1. +⚠️ Exception: Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for S3, SQS, and Lambda Invoke — when Layer 2 has an entry, it takes precedence over Rule 1. > ⚠️ **Tool policy can override cost classification.** Some operations classified as FREE here (e.g., `cloudtrail:LookupEvents`) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. @@ -111,13 +112,13 @@ An operation is PAID if it matches ANY of these patterns: | Pattern | Why It Costs Money | Examples | |---|---|---| -| Verb contains `Query` or `Search` | Scans indexed data | `StartQuery`, `Search`, `StartQueryExecution` | +| Verb contains `Query` or `Search` | Scans indexed data | `StartQuery`, `StartQueryExecution` | | Verb contains `Scan` | Full table/index scan | `Scan` (DynamoDB), `StartScan` | | Verb contains `Execute` + processes data | Runs a computation | `StartQueryExecution` (Athena), `ExecuteStatement` | | Verb contains `Invoke` + runs workload | Triggers compute | `InvokeEndpoint` (SageMaker), `Invoke` (Lambda) | | Operation reads **content** (not metadata) | Data transfer | `GetObject` (S3, large), `GetLogEvents` (bulk), `BatchGetTraces` | -| Operation starts a **streaming session** | Per-time billing | `StartLiveTail`, `SubscribeToShard` | -| Operation name contains `Insights` | Analytics processing | `GetContributorInsights`, `StartQuery` | +| Operation starts a **streaming session** | Per-time billing | `StartLiveTail` | +| Operation name contains `Insights` | Analytics processing | `GetContributorInsights`, `GetInsightRuleReport` | ### Rule 3: CAUTION — High-volume free operations @@ -140,31 +141,46 @@ If an operation doesn't clearly fit Rules 1–3: ## Layer 2: Known-Paid Registry -These operations have **confirmed pricing**. Always fetch the live rate via the Pricing API (see `references/pricing-reference.md`) before estimating. If the Pricing API call fails, halt — do not estimate from memory. +These operations have **confirmed pricing**. Before estimating, fetch the live rate via the Pricing API using the exact filter field and value from the table below — see `references/pricing-reference.md` for the bash call patterns and region prefix mapping. + +> **Critical lookup rule:** `usagetype` and `operation` are different Pricing API filter fields. The correct field and value for each operation are specified explicitly below — do NOT derive them from the operation name. + +### Pricing Lookup Rules + +```text +if len(products) == 0: + 🚫 HALT — Pricing lookup returned no results for : + Reason: filter field/value or workload-region prefix may be incorrect + Do NOT proceed with the paid operation. + Do NOT improvise a rate from memory, training data, or any other source. + Options: + → Re-check pricing-reference.md for the correct filter field, value, and region prefix + → Skip this operation and use a free alternative + → Report the lookup gap to the user +``` ### Confirmed Paid Operations -| ServiceCode | Operation | Cost Formula | Estimation Method | -|---|---|---|---| -| `AmazonCloudWatch` | `StartQuery` | `scan_gb × rate` | Query `IncomingBytes` metric for time window | -| `AmazonCloudWatch` | `StartLiveTail` | Duration-based | Duration-based | -| `AmazonCloudWatch` | `GetMetricData` | `(metrics × periods) / 1K × rate` | Count metrics and periods | -| `AmazonCloudWatch` | `get_prometheus_metrics` | `(series × datapoints) / 1K × rate` | Count series × (range/step) | -| `AmazonCloudWatch` | `GetInsightRuleReport` | `rules × (events / 1K) × rate` | Count rules and event volume | -| `AWSXRay` | `GetTraceSummaries` | `traces / 1M × rate` | Paginate or sample to estimate count | -| `AWSXRay` | `BatchGetTraces` | `traces / 1M × rate` | Count trace IDs in request | -| `AmazonAthena` | `StartQueryExecution` | `scan_tb × rate`; min 10MB | Check table metadata; require `WHERE` clause | -| `AmazonDynamoDB` | `Scan` | RCU consumed | Check `TableSizeBytes`; BLOCK unless user approves | -| `AmazonDynamoDB` | `Query` | RCU consumed | Check `ItemCount`; warn if > 10K items | -| `AmazonS3` | `GetObject` | See pricing-reference.md | Count requests; flag if cross-region or >100MB | -| `AmazonS3` | `ListObjectsV2`, `ListObjects` | See pricing-reference.md | Count calls; warn if paginating heavily | -| `AmazonS3` | `PutObject`, `CopyObject` | See pricing-reference.md | Count calls | -| `AmazonS3` | `SelectObjectContent` | `scan_gb × rate` | Check object size | -| `AWSResourceExplorer2` | `Search` | Per query | Count calls | -| `AmazonSageMaker` | `InvokeEndpoint` | — | BLOCK — require explicit user approval | -| `AmazonKinesis` | `GetRecords` | `records / 1M × rate` | Estimate from shard count × duration | -| `AmazonSQS` | All operations | Per request | Count total SQS calls; usually negligible | -| `AWSLambda` | `Invoke` | Per request + compute | BLOCK unless user explicitly requests function execution | +> **Rate = `pricePerUnit.USD`** from `terms.OnDemand → priceDimensions` where `beginRange="0"`. No /1K or /1M divisors. Region scoping: see Region Scoping column. + +| ServiceCode | Layer 2 Operation | Pricing API Filter Field | Filter Value | Region scoping | Cost Formula | Estimation Method | +|---|---|---|---|---|---|---| +| `AmazonCloudWatch` | `GetMetricData` | `operation` | `GetMetricData` | + `regionCode=` | `(metrics × periods) × rate` | Count metrics and periods | +| `AmazonCloudWatch` | `StartQuery` | `operation` | `StartQuery` | + `regionCode=` | `scan_gb × rate` | Query `IncomingBytes` metric for time window | +| `AmazonCloudWatch` | `StartLiveTail` | `operation` | `StartLiveTail` | + `regionCode=` | Duration-based | Duration-based | +| `AmazonCloudWatch` | `GetInsightRuleReport` | `operation` | `GetInsightRuleReport` | + `regionCode=` | `rules × events × rate` | Count rules and event volume | +| `AmazonCloudWatch` | `get_prometheus_metrics` (native tool) | `usagetype` | `CW:PromQL:SamplesScanned` | workload-region prefix required (bare in us-east-1) | `samples_scanned × rate` | Estimate `min(500, series) × (range_seconds / step_seconds)`; HALT if lookup empty | +| `AWSXRay` | `GetTraceSummaries` | `operation` | `XRay-Traces-Scanned` | + `regionCode=` | `traces × rate` | Paginate or sample to estimate count | +| `AWSXRay` | `BatchGetTraces` | `operation` | `XRay-Traces-Retrieved` | + `regionCode=` | `traces × rate` | Count trace IDs in request | +| `AmazonAthena` | `StartQueryExecution` | `usagetype` | `DataScannedInTB` | workload-region prefix required (USE1- for us-east-1) | `scan_tb × rate`; min 10MB | Check table metadata; require `WHERE` clause | +| `AmazonDynamoDB` | `Scan` | `usagetype` | `ReadRequestUnits` | workload-region prefix required (**bare in us-east-1**, no USE1-) | RCU consumed × rate | Check `TableSizeBytes`; BLOCK unless user approves | +| `AmazonDynamoDB` | `Query` | `usagetype` | `ReadRequestUnits` | workload-region prefix required (**bare in us-east-1**, no USE1-) | RCU consumed × rate | Check `ItemCount`; warn if > 10K items | +| `AmazonS3` | `GetObject` | `usagetype` | `Requests-Tier2` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count requests; flag if cross-region or >100MB | +| `AmazonS3` | `ListObjectsV2`, `ListObjects` | `usagetype` | `Requests-Tier1` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count calls; warn if paginating heavily | +| `AmazonS3` | `PutObject`, `CopyObject` | `usagetype` | `Requests-Tier1` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count calls | +| `AmazonS3` | `SelectObjectContent` | `usagetype` | `Requests-Tier2` | workload-region prefix required (bare in us-east-1) | `scan_gb × rate` | Check object size | +| `AmazonSageMaker` | `InvokeEndpoint` | — | — | — | — | BLOCK — require explicit user approval | +| `AWSLambda` | `Invoke` | — | — | — | Per request + compute | BLOCK unless user explicitly requests function execution | --- @@ -238,7 +254,7 @@ if sum(all_call_counts) > 1000: 🚫 HALT Budget: $10.00 Spent: $X.XX (Y paid operations) Free calls: Z operations (no cost) -PromQL: X,XXX metrics×periods consumed ($X.XX) +PromQL: X,XXX samples scanned ($X.XX) Next op: : — estimated $X.XX Projected: $X.XX (exceeds budget by $X.XX) @@ -276,6 +292,7 @@ For EVERY paid operation: if target_region ≠ agent_space_region: fetch transfer_rate = transfer_rate_cache[target_region] ?? live lookup (see references/pricing-reference.md) + # If the live lookup returns 0 products: 🚫 HALT — do NOT improvise a rate estimated_return_size = estimate_return_bytes(operation_type) transfer_cost = estimated_return_size × transfer_rate total_estimate += transfer_cost diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index 01a18fd..455d845 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -2,68 +2,89 @@ This file contains all AWS-specific Pricing API call patterns. Read this file the first time any operation is classified PAID by Layer 2, before estimating cost. +> **Critical:** `usagetype` and `operation` are **different** Pricing API filter fields. Always use the field and value specified in the Layer 2 table — do NOT derive the filter value from the AWS API operation name. Every rate must come from an exact Pricing API lookup with the correct filter field/value and the correct region scoping. If any of these cannot be resolved exactly, **HALT** — do NOT improvise a rate. + +--- + +## Two different regions — do not conflate + +1. **Pricing API endpoint region** — always `us-east-1` (the Pricing API endpoint only exists in us-east-1 and ap-south-1). This is where you *send* the call. It has nothing to do with your workload or the agent's runtime region. +2. **Workload region** — the region of the resource being priced. For `operation`-based lookups this is passed as a `regionCode` filter. For `usagetype`-based lookups this drives the prefix (`USW2-`, `EU-`, etc.). + +> The workload region is a function of the **resource being priced only** — never the agent's runtime region, and never a hardcoded default. If the workload region is unknown, resolve it from the resource ARN before pricing. **Do NOT default to us-east-1** as the workload region. + +--- + +## Region Scoping by Filter Type + +| Filter type | How to scope to workload region | +|---|---| +| `operation` | Add `{"Type":"TERM_MATCH","Field":"regionCode","Value":""}` as a second filter. Returns exactly 1 product. Omitting it returns all regions (paginated, nondeterministic). | +| `usagetype` | Prepend the workload-region prefix to the value (e.g. `USW2-DataScannedInTB`). See Region Prefix Mapping table below. | + --- -## Standard Pattern +## Standard Patterns -Always call the Pricing API from `us-east-1` regardless of workload region: +### operation-based lookup ```bash aws pricing get-products \ --service-code \ - --filters '[{"Type":"TERM_MATCH","Field":"operation","Value":""}, + --filters '[{"Type":"TERM_MATCH","Field":"operation","Value":""}, {"Type":"TERM_MATCH","Field":"regionCode","Value":""}]' \ --region us-east-1 ``` -Use the **operation name from Layer 2** directly as the filter value. Use the **workload region** (from the resource ARN or `aws_region` param) as `regionCode` — never the agent space region. - - ---- - -## Cross-Region Data Transfer Rate - -For any paid operation where `target_region ≠ agent_space_region`, fetch the live transfer rate. The rate is **source-region-based and destination-independent**: +### usagetype-based lookup ```bash aws pricing get-products \ - --service-code AWSDataTransfer \ - --filters '[{"Type":"TERM_MATCH","Field":"transferType","Value":"InterRegion Outbound"}, - {"Type":"TERM_MATCH","Field":"fromRegionCode","Value":""}, - {"Type":"TERM_MATCH","Field":"toLocationType","Value":"AWS Region"}]' \ - --max-results 1 \ + --service-code \ + --filters '[{"Type":"TERM_MATCH","Field":"usagetype","Value":"-"}]' \ --region us-east-1 ``` -Read `pricePerUnit.USD` from the first result. Cache as `transfer_rate_cache[source_region]` — one lookup per source region per investigation. +### Empty result = lookup failure = HALT ---- +If the Pricing API returns zero products, **do not proceed**. This is a lookup failure, not a zero-cost result. + +```text +if len(products) == 0: + 🚫 HALT — Pricing lookup returned no results + Do NOT proceed with the paid operation. + Do NOT improvise a rate from memory, training data, or any other source. +``` -## S3 Pricing Lookup +> Every operation that returns empty HALTs. There are no exceptions. + +> ⚠️ **X-Ray eu-west-3 catalog gap:** `XRay-Traces-Scanned` and `XRay-Traces-Retrieved` do not exist in the AWS Pricing catalog for eu-west-3. This is expected — HALT-on-empty applies, report the gap to the user. + +--- -S3 has no `operation` field — use `usagetype` with the region prefix and tier: +## Amazon S3 — usagetype with region prefix ```bash aws pricing get-products \ --service-code AmazonS3 \ - --filters '[{"Type":"TERM_MATCH","Field":"usagetype","Value":"-Requests-"}]' \ + --filters '[{"Type":"TERM_MATCH","Field":"usagetype","Value":"-Requests-"}]' \ --region us-east-1 ``` -Replace `` with `Tier1` or `Tier2` based on the operation. For us-east-1 omit the prefix entirely (e.g. `Requests-Tier1`). +**us-east-1 uses a bare value (no prefix):** `Requests-Tier2`. All other regions carry the prefix: `USW2-Requests-Tier2`. If the lookup returns 0 products, HALT. -### S3 Tier Mapping +#### S3 Tier Mapping | Tier | Operations | |---|---| | `Tier1` | PUT, COPY, POST, LIST | | `Tier2` | GET, SELECT, HEAD | -### Region Prefix Mapping +#### Region Prefix Mapping (usagetype-based services: S3, Athena, DynamoDB, PromQL) | Region | Prefix | |---|---| -| us-east-1 | *(omit — bare `Requests-Tier1`)* | +| us-east-1 | *(S3/DynamoDB/PromQL: omit — bare value; Athena: USE1-)* | | us-east-2 | USE2 | | us-west-1 | USW1 | | us-west-2 | USW2 | @@ -74,24 +95,56 @@ Replace `` with `Tier1` or `Tier2` based on the operation. For us-east-1 o | eu-central-2 | EUC2 | | eu-north-1 | EUN1 | | eu-south-1 | EUS1 | +| eu-south-2 | EUS2 | | ap-southeast-1 | APS1 | | ap-southeast-2 | APS2 | | ap-southeast-3 | APS4 | | ap-southeast-4 | APS6 | +| ap-southeast-5 | APS7 | +| ap-southeast-6 | APS8 | +| ap-southeast-7 | APS9 | | ap-northeast-1 | APN1 | | ap-northeast-2 | APN2 | | ap-northeast-3 | APN3 | | ap-south-1 | APS3 | +| ap-south-2 | APS5 | | ap-east-1 | APE1 | +| ap-east-2 | APE2 | | sa-east-1 | SAE1 | | ca-central-1 | CAN1 | +| ca-west-1 | CAN2 | | me-south-1 | MES1 | | me-central-1 | MEC1 | +| mx-central-1 | MXC1 | | af-south-1 | AFS1 | | il-central-1 | ILC1 | --- +## Cross-Region Data Transfer Rate + +For any paid operation where the source (workload) region differs from the destination (agent space) region, fetch the live transfer rate. Supplying **both** `fromRegionCode` and `toRegionCode` (plus `transferType` and `toLocationType`) narrows the result to a single deterministic product; a `fromRegionCode`-only filter returns many products and must not be used. + +```bash +aws pricing get-products \ + --service-code AWSDataTransfer \ + --filters '[{"Type":"TERM_MATCH","Field":"transferType","Value":"InterRegion Outbound"}, + {"Type":"TERM_MATCH","Field":"fromRegionCode","Value":""}, + {"Type":"TERM_MATCH","Field":"toRegionCode","Value":""}, + {"Type":"TERM_MATCH","Field":"toLocationType","Value":"AWS Region"}]' \ + --region us-east-1 +``` + +- ``: region where data originates (the workload region) — resolve from the resource ARN; do NOT default it +- ``: the agent space region — resolve it explicitly; do NOT hardcode us-east-1 +- Select the price dimension with `beginRange: "0"` if multiple are returned +- Cache as `transfer_rate_cache[source_region → destination_region]` — one lookup per route per investigation +- **If the filtered query returns 0 results: 🚫 HALT.** Do NOT improvise a rate. Re-check the region codes and filters, or report the gap. + +> Inter-region transfer rates vary widely by geography (roughly $0.01–$0.15/GB depending on source region) — always look up the specific route; never assume a flat rate. + +--- + ## Reference Links -[CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [Kinesis](https://aws.amazon.com/kinesis/data-streams/pricing/) · [SQS](https://aws.amazon.com/sqs/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Resource Explorer](https://aws.amazon.com/resource-explorer/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) +[CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [SQS](https://aws.amazon.com/sqs/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Resource Explorer](https://aws.amazon.com/resource-explorer/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) From 0a8354b8cf1dbb9684c2ce03333d2871ae5b259a Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 28 Aug 2026 13:18:22 +0200 Subject: [PATCH 12/13] update readme and changelog --- .../investigation-cost-guardrail/CHANGELOG.md | 7 +- skills/investigation-cost-guardrail/README.md | 84 +++++++++++-------- skills/investigation-cost-guardrail/SKILL.md | 8 +- .../references/pricing-reference.md | 16 +++- 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/skills/investigation-cost-guardrail/CHANGELOG.md b/skills/investigation-cost-guardrail/CHANGELOG.md index 3f74d48..8e1c45b 100644 --- a/skills/investigation-cost-guardrail/CHANGELOG.md +++ b/skills/investigation-cost-guardrail/CHANGELOG.md @@ -2,9 +2,10 @@ ## 2.1.0 - - Add `references/pricing-reference.md` to resolve the per-region rate for a paid operation at estimation time. - - It ships Price List Query API lookup templates for the operations the skill estimates most often (CloudWatch Logs Insights, `GetMetricData`, Contributor Insights, X-Ray, Athena, S3 requests). - - `SKILL.md` loads it on the first operation Layer 2 classifies as PAID and reuses it for the rest of the investigation; baseline rates are indicative only. + - Replace every hardcoded rate with a live AWS Price List Query API lookup, resolved for the workload region at estimation time. The skill no longer carries baseline rates of any kind. + - Add `references/pricing-reference.md` holding the exact call patterns: which filter field each operation uses (`operation` vs `usagetype`), the exact filter value, region scoping, the `usagetype` region-prefix map and its us-east-1 exceptions, S3 request tiers and the three meters S3 Select bills on, and the filtered cross-region data transfer lookup. + - `SKILL.md` loads the reference on the first operation Layer 2 classifies as PAID and reuses it for the rest of the investigation. + - Layer 2 now records filter fields and formulas instead of rates, covering CloudWatch Logs Insights, `GetMetricData`, `GetInsightRuleReport`, Live Tail, PromQL samples scanned, X-Ray, Athena, DynamoDB, and S3 requests and Select. ## 2.0.0 diff --git a/skills/investigation-cost-guardrail/README.md b/skills/investigation-cost-guardrail/README.md index dd1bab5..49922e6 100644 --- a/skills/investigation-cost-guardrail/README.md +++ b/skills/investigation-cost-guardrail/README.md @@ -10,7 +10,7 @@ The agent decides whether to load a skill based on description matching. To guar 1. **Classify:** Determine if the operation is free or paid -2. **Estimate:** Calculate expected cost using known formulas or heuristics +2. **Estimate:** Resolve the live per-Region rate, then apply the operation's formula or heuristic 3. **Check budget:** Compare estimated cost against per-investigation budget @@ -47,7 +47,7 @@ Rather than hardcoding every free/paid operation across 200+ AWS services, this │ • Covers ANY current or future AWS service │ ├─────────────────────────────────────────────────┤ │ Layer 2: Known-Paid Registry │ -│ • Exact pricing formulas for high-cost ops │ +│ • Live per-Region rate lookup + formulas │ │ • Extensible by operator configuration │ │ • Athena, DynamoDB, S3, X-Ray, SageMaker... │ ├─────────────────────────────────────────────────┤ @@ -65,7 +65,7 @@ Even if AWS launches a new service tomorrow, the heuristic rules will correctly | Tool | Classification | Cost Model | Guardrail | | --- | --- | --- | --- | -| get_prometheus_metrics | **PAID** | $0.01 / 1,000 metrics×periods (same billing meter as CloudWatch GetMetricData) | Track series × datapoints per call | +| get_prometheus_metrics | **PAID** | Billed per sample scanned — rate resolved from a live `CW:PromQL:SamplesScanned` usagetype lookup | Track samples scanned per call | | use_aws | **VARIABLE** | Depends on operation — apply Layers 1–3 | Full heuristic pipeline | | use_azure | **FREE** | Azure Reader role, no per-call billing | Track count only | | grafana_query_prometheus | **CAUTION** | Depends on Grafana data source billing model | Track count, warn at 50+ | @@ -83,25 +83,30 @@ Even if AWS launches a new service tomorrow, the heuristic rules will correctly | Classification | Rule | | --- | --- | -| **FREE** | Verb is Describe, List, Get, Lookup, Check, Validate, Tag: returns metadata only | +| **FREE** | Verb is Describe, List, Get, Lookup, Check, Validate, Tag, Untag: returns metadata only | | **PAID** | Verb contains Query, Scan, Execute, Invoke, Insights: processes or scans data | | **CAUTION** | Paginated List/Describe with broad scope | ### Layer 2: Known-Paid Registry -| Service | Operation | Cost Formula | -| --- | --- | --- | -| CloudWatch Logs | StartQuery | $0.005/GB scanned | -| CloudWatch Logs | StartLiveTail | $0.01/minute | -| CloudWatch | GetMetricData / PromQL | $0.01/1,000 metrics×periods | -| X-Ray | GetTraceSummaries, BatchGetTraces | $0.50/1M traces | -| Athena | StartQueryExecution | $5.00/TB scanned | -| DynamoDB | Scan | ~$0.25/1M RCU. **BLOCKED unless approved** | -| S3 | GetObject | $0.0004/1K requests + $0.09/GB transfer | -| S3 | SelectObjectContent | $0.002/GB scanned + $0.0007/GB returned | -| SageMaker | InvokeEndpoint | **BLOCKED: requires explicit approval** | -| Lambda | Invoke | **BLOCKED unless user explicitly requests** | -| Kinesis | GetRecords | $0.015/1M records | +The registry holds no rates of its own. It records, per operation, which Pricing API filter field to query (`operation` or `usagetype`), the exact filter value, and the formula the resolved rate feeds into. `usagetype` and `operation` are different filter fields, and the value is never derived from the AWS API operation name — both are stated explicitly per operation. + +| Service | Operation | Rate resolved via | Cost Formula | +| --- | --- | --- | --- | +| CloudWatch Logs | StartQuery | `operation` | `scan_gb × rate` | +| CloudWatch Logs | StartLiveTail | `operation` | Duration-based | +| CloudWatch | GetMetricData | `operation` | `(metrics × periods) × rate` | +| CloudWatch | GetInsightRuleReport | `usagetype` | `metrics_requested × rate` | +| CloudWatch | PromQL (`get_prometheus_metrics`) | `usagetype` | `samples_scanned × rate` | +| X-Ray | GetTraceSummaries, BatchGetTraces | `operation` | `traces × rate` | +| Athena | StartQueryExecution | `usagetype` | `scan_tb × rate`, min 10MB | +| DynamoDB | Scan | `usagetype` | `RCU × rate`. **BLOCKED unless approved** | +| DynamoDB | Query | `usagetype` | `RCU × rate` | +| S3 | GetObject | `usagetype` (Tier2) | Per request | +| S3 | ListObjects, PutObject, CopyObject | `usagetype` (Tier1) | Per request | +| S3 | SelectObjectContent | `usagetype` (3 meters) | Bytes scanned + bytes returned + request | +| SageMaker | InvokeEndpoint | — | **BLOCKED: requires explicit approval** | +| Lambda | Invoke | — | **BLOCKED unless user explicitly requests** | **Operators can extend this registry:** @@ -112,18 +117,18 @@ Even if AWS launches a new service tomorrow, the heuristic rules will correctly ### Regional Rate Resolution -Rates vary by AWS Region. `references/pricing-reference.md` lets the skill resolve the rate for the workload's Region rather than applying the us-east-1 baselines listed in Layer 2. +Rates vary by AWS Region, so every rate is resolved live for the workload's Region. `references/pricing-reference.md` holds the call patterns that do it. | Step | What happens | | --- | --- | -| 1. Region | Derived from the resource ARN | -| 2. Lookup | `pricing:GetProducts`, with the workload Region passed as a `regionCode` filter value | +| 1. Region | Derived from the resource ARN — never the agent's runtime Region, never a default | +| 2. Lookup | `pricing:GetProducts`. `operation` lookups pass the workload Region as a `regionCode` filter; `usagetype` lookups prepend the workload-Region prefix | | 3. Cache | Keyed on `(service, operation, region)` — one lookup per service and Region per investigation | -| 4. Fallback | Published baseline rate, if the lookup is unavailable | -The reference also maps `usagetype` Region prefixes and their exceptions, S3 Tier1/Tier2 requests, cross-Region transfer rates, and the operations that are free and need no lookup. -It loads once, on the first operation classified as PAID, and is reused for the rest of the investigation. Investigations that touch only metadata or third-party tools do not load it. Baseline rates are indicative; the live API is authoritative. +There is no fallback rate. If the lookup cannot be resolved exactly, the skill halts rather than estimating: it will not improvise a rate from memory or training data. The operator is offered the choice to re-check the filter field, value, and Region prefix, use a free alternative, or have the lookup gap reported. + +It loads once, on the first operation classified as PAID, and is reused for the rest of the investigation. Investigations that touch only metadata or third-party tools do not load it. ### Layer 3: Response Validation (Self-Learning) @@ -138,11 +143,10 @@ After execution, the skill checks response fields for metered indicators: | ContentLength > 100MB | Large object transfer | | NextToken after 10+ pages | Pagination runaway | -If a previously-unclassified operation returns metered fields, it is reclassified as paid for the remainder of the investigation. ## Budget Enforcement -The skill maintains a **running cost accumulator** throughout each investigation using the scratchpad: +The skill maintains a running cost accumulator throughout each investigation using the scratchpad: ``` 📋 INVESTIGATION BUDGET STATUS @@ -188,11 +192,7 @@ When the next operation would exceed the budget: ## Cross-Region Detection -When the target region differs from the Agent Space region, the skill adds estimated data transfer cost ($0.02/GB) based on return size: - -- Aggregation queries: ~KB (negligible) -- Raw log/trace fetches: up to 100% of matched bytes -- Unknown: 15% of scan volume as upper bound (flagged ⚠️) +When the target region differs from the Agent Space region, the skill adds a data transfer cost. The rate is looked up live for that specific source → destination route and cached per route, because inter-Region rates vary widely by geography (roughly $0.01–$0.15/GB depending on source Region) — there is no flat rate to assume. ## Cost Reduction Suggestions @@ -206,10 +206,13 @@ When halting, the skill always suggests cost-efficient alternatives: | dynamodb:Scan | dynamodb:Query with key condition | ~100% | | athena:StartQueryExecution (full) | Add partition filter in WHERE | 90%+ | | xray:GetTraceSummaries (broad) | Narrow time + add filter expression | 90%+ | +| s3:GetObject (large) | s3:SelectObjectContent with SQL filter | Variable | | Broad time window | Narrow to ±30 min around incident | 90%+ | ## Scenarios +The dollar figures below are illustrative output. Every one of them is computed from a rate the skill resolved live for the workload's Region at estimation time; none is a rate published by this skill. + **Scenario A: Scoped investigation, within budget** ``` @@ -256,19 +259,24 @@ Agent attempts: athena:StartQueryExecution (full scan, no WHERE clause) ``` -**Scenario D: PromQL broad query warned** +**Scenario D: PromQL broad query flagged** ``` Agent attempts: get_prometheus_metrics (no label filter, 7d range, 60s step) - ⚠️ WARN: PromQL query would fetch ~5M metrics×periods - 💰 Estimated cost: $50.00 + ⚠️ FLAGGED: query hit the 500-series cap + ~5.04M samples scanned (500 series × 10,080 datapoints at 60s step) + 💰 ~$0.05 at the live CW:PromQL:SamplesScanned rate + + The series cap means this query cost the maximum it could for + this range and step, and the returned data is truncated — so the + result is both incomplete and needlessly broad. 💡 Suggestions: → Add label filters to reduce series count → Use topk(10, ...) to cap series - → Increase step to 300s - → Narrow time range to 1h + → Increase step to 300s (5× cheaper) + → Narrow time range to 1h (168× cheaper) ``` @@ -321,11 +329,13 @@ The skill calls the AWS Price List Query API to resolve per-Region rates. Grant `Resource` is `*` because the Price List API returns public pricing data. The API is free and read-only. -Your Agent Space tool policy must also permit the call. If the lookup is unavailable, the skill uses the published baseline rates in `references/pricing-reference.md` and flags the estimate. +The call is always sent to `us-east-1`, so a Region-scoped tool policy needs to allow `us-east-1`. + +Your Agent Space tool policy must also permit the call. This permission is required, not optional: the skill carries no baseline rates to fall back on, so if the lookup is unavailable it halts before the paid operation instead of estimating. ## Known Limitations - **Budget is scoped to a single investigation:** each investigation starts with a fresh budget; cumulative tracking across multiple investigations at the agent space level is not currently supported. - **Skill-halted investigations show "Completed" status:** halt reason is only visible in the investigation output. - +- **A paid operation cannot be estimated without a successful rate lookup:** there is no fallback rate, so a missing `pricing:GetProducts` permission halts that operation. \ No newline at end of file diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index 0b57e40..0608574 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -102,7 +102,7 @@ An operation is FREE if it matches ALL of these: - It returns metadata/configuration (not data content or query results) - It does NOT scan, process, or transform customer data -⚠️ Exception: Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for S3, SQS, and Lambda Invoke — when Layer 2 has an entry, it takes precedence over Rule 1. +⚠️ Exception: Some services charge per-request even for Get/List operations. Layer 2 overrides this heuristic for S3 and Lambda Invoke — when Layer 2 has an entry, it takes precedence over Rule 1. > ⚠️ **Tool policy can override cost classification.** Some operations classified as FREE here (e.g., `cloudtrail:LookupEvents`) may be blocked by tool policy in certain environments. If an operation is denied, it costs $0.00 (never executed) — proceed with alternatives. @@ -112,7 +112,7 @@ An operation is PAID if it matches ANY of these patterns: | Pattern | Why It Costs Money | Examples | |---|---|---| -| Verb contains `Query` or `Search` | Scans indexed data | `StartQuery`, `StartQueryExecution` | +| Verb contains `Query` | Scans indexed data | `StartQuery`, `StartQueryExecution` | | Verb contains `Scan` | Full table/index scan | `Scan` (DynamoDB), `StartScan` | | Verb contains `Execute` + processes data | Runs a computation | `StartQueryExecution` (Athena), `ExecuteStatement` | | Verb contains `Invoke` + runs workload | Triggers compute | `InvokeEndpoint` (SageMaker), `Invoke` (Lambda) | @@ -168,7 +168,7 @@ if len(products) == 0: | `AmazonCloudWatch` | `GetMetricData` | `operation` | `GetMetricData` | + `regionCode=` | `(metrics × periods) × rate` | Count metrics and periods | | `AmazonCloudWatch` | `StartQuery` | `operation` | `StartQuery` | + `regionCode=` | `scan_gb × rate` | Query `IncomingBytes` metric for time window | | `AmazonCloudWatch` | `StartLiveTail` | `operation` | `StartLiveTail` | + `regionCode=` | Duration-based | Duration-based | -| `AmazonCloudWatch` | `GetInsightRuleReport` | `operation` | `GetInsightRuleReport` | + `regionCode=` | `rules × events × rate` | Count rules and event volume | +| `AmazonCloudWatch` | `GetInsightRuleReport` | `usagetype` | `CW:GIRR-Metrics` | workload-region prefix required (bare in us-east-1) | metrics_requested × rate| Count metrics requested in the report call | | `AmazonCloudWatch` | `get_prometheus_metrics` (native tool) | `usagetype` | `CW:PromQL:SamplesScanned` | workload-region prefix required (bare in us-east-1) | `samples_scanned × rate` | Estimate `min(500, series) × (range_seconds / step_seconds)`; HALT if lookup empty | | `AWSXRay` | `GetTraceSummaries` | `operation` | `XRay-Traces-Scanned` | + `regionCode=` | `traces × rate` | Paginate or sample to estimate count | | `AWSXRay` | `BatchGetTraces` | `operation` | `XRay-Traces-Retrieved` | + `regionCode=` | `traces × rate` | Count trace IDs in request | @@ -178,7 +178,7 @@ if len(products) == 0: | `AmazonS3` | `GetObject` | `usagetype` | `Requests-Tier2` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count requests; flag if cross-region or >100MB | | `AmazonS3` | `ListObjectsV2`, `ListObjects` | `usagetype` | `Requests-Tier1` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count calls; warn if paginating heavily | | `AmazonS3` | `PutObject`, `CopyObject` | `usagetype` | `Requests-Tier1` | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Count calls | -| `AmazonS3` | `SelectObjectContent` | `usagetype` | `Requests-Tier2` | workload-region prefix required (bare in us-east-1) | `scan_gb × rate` | Check object size | +| `AmazonS3` | `SelectObjectContent` | `usagetype` | Bills on 3 meters — see pricing-reference.md | workload-region prefix required (bare in us-east-1) | See pricing-reference.md | Check object size | | `AmazonSageMaker` | `InvokeEndpoint` | — | — | — | — | BLOCK — require explicit user approval | | `AWSLambda` | `Invoke` | — | — | — | Per request + compute | BLOCK unless user explicitly requests function execution | diff --git a/skills/investigation-cost-guardrail/references/pricing-reference.md b/skills/investigation-cost-guardrail/references/pricing-reference.md index 455d845..a576509 100644 --- a/skills/investigation-cost-guardrail/references/pricing-reference.md +++ b/skills/investigation-cost-guardrail/references/pricing-reference.md @@ -8,7 +8,7 @@ This file contains all AWS-specific Pricing API call patterns. Read this file th ## Two different regions — do not conflate -1. **Pricing API endpoint region** — always `us-east-1` (the Pricing API endpoint only exists in us-east-1 and ap-south-1). This is where you *send* the call. It has nothing to do with your workload or the agent's runtime region. +1. **Pricing API endpoint region** — always `us-east-1`. 2. **Workload region** — the region of the resource being priced. For `operation`-based lookups this is passed as a `regionCode` filter. For `usagetype`-based lookups this drives the prefix (`USW2-`, `EU-`, etc.). > The workload region is a function of the **resource being priced only** — never the agent's runtime region, and never a hardcoded default. If the workload region is unknown, resolve it from the resource ARN before pricing. **Do NOT default to us-east-1** as the workload region. @@ -80,6 +80,18 @@ aws pricing get-products \ | `Tier1` | PUT, COPY, POST, LIST | | `Tier2` | GET, SELECT, HEAD | +#### S3 Select (`SelectObjectContent`) — three meters + +The request tier prices the **call**, not the bytes. Select needs all three lookups below; `usagetype` is the only viable filter, as these carry an empty `operation` field. + +| Component | usagetype | Unit | Formula | +|---|---|---|---| +| Bytes scanned | `-Select-Scanned-Bytes` | GB | `scan_gb × rate` | +| Bytes returned | `-Select-Returned-Bytes` | GB | `returned_gb × rate` | +| Request | `-Requests-Tier2` | Requests | `requests × rate` | + +`total = scanned + returned + request`. Bare values in us-east-1, prefixed elsewhere. If any lookup returns 0 products, HALT. + #### Region Prefix Mapping (usagetype-based services: S3, Athena, DynamoDB, PromQL) | Region | Prefix | @@ -147,4 +159,4 @@ aws pricing get-products \ ## Reference Links -[CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [SQS](https://aws.amazon.com/sqs/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Resource Explorer](https://aws.amazon.com/resource-explorer/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) +[CloudWatch](https://aws.amazon.com/cloudwatch/pricing/) · [X-Ray](https://aws.amazon.com/xray/pricing/) · [Athena](https://aws.amazon.com/athena/pricing/) · [DynamoDB](https://aws.amazon.com/dynamodb/pricing/on-demand/) · [S3](https://aws.amazon.com/s3/pricing/) · [Lambda](https://aws.amazon.com/lambda/pricing/) · [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) From af78d2f1ff6b30deaae3d59671d5448ce2872633 Mon Sep 17 00:00:00 2001 From: Ines Attia Date: Fri, 28 Aug 2026 18:54:33 +0200 Subject: [PATCH 13/13] update readme and changelog --- skills/investigation-cost-guardrail/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/investigation-cost-guardrail/SKILL.md b/skills/investigation-cost-guardrail/SKILL.md index 0608574..7d5b880 100644 --- a/skills/investigation-cost-guardrail/SKILL.md +++ b/skills/investigation-cost-guardrail/SKILL.md @@ -169,7 +169,7 @@ if len(products) == 0: | `AmazonCloudWatch` | `StartQuery` | `operation` | `StartQuery` | + `regionCode=` | `scan_gb × rate` | Query `IncomingBytes` metric for time window | | `AmazonCloudWatch` | `StartLiveTail` | `operation` | `StartLiveTail` | + `regionCode=` | Duration-based | Duration-based | | `AmazonCloudWatch` | `GetInsightRuleReport` | `usagetype` | `CW:GIRR-Metrics` | workload-region prefix required (bare in us-east-1) | metrics_requested × rate| Count metrics requested in the report call | -| `AmazonCloudWatch` | `get_prometheus_metrics` (native tool) | `usagetype` | `CW:PromQL:SamplesScanned` | workload-region prefix required (bare in us-east-1) | `samples_scanned × rate` | Estimate `min(500, series) × (range_seconds / step_seconds)`; HALT if lookup empty | +| `AmazonCloudWatch` | `get_prometheus_metrics` (native tool) | `usagetype` | `CW:PromQL:SamplesScanned` | workload-region prefix required (bare in us-east-1) | `samples_scanned × rate` | Estimate `min(500, series) × (range_seconds / step_seconds)`| | `AWSXRay` | `GetTraceSummaries` | `operation` | `XRay-Traces-Scanned` | + `regionCode=` | `traces × rate` | Paginate or sample to estimate count | | `AWSXRay` | `BatchGetTraces` | `operation` | `XRay-Traces-Retrieved` | + `regionCode=` | `traces × rate` | Count trace IDs in request | | `AmazonAthena` | `StartQueryExecution` | `usagetype` | `DataScannedInTB` | workload-region prefix required (USE1- for us-east-1) | `scan_tb × rate`; min 10MB | Check table metadata; require `WHERE` clause |