
Skill
investigation-cost-guardrail
enforce cost guardrails for AWS operations
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.
SKILL.md
Investigation Cost Guardrail Skill
Overview
This skill provides cost guardrails for ANY AWS service and ALL native agent tools — not just a hardcoded list. It uses heuristic classification to determine whether an API operation is free or paid, estimates cost before execution, and enforces per-investigation budgets.
Design Principle
Rather than listing every free/paid operation across 200+ AWS services, this skill uses three layers:
- Heuristic rules — classify any operation based on naming patterns and behavior
- Known-paid registry — explicit overrides for high-cost operations with pricing formulas
- Response validation — detect metered usage from API response fields after execution
Activation
This skill MUST be ALWAYS ACTIVE during investigations. It does NOT require user invocation.
Layer 0: Native Agent Tool Classification
Before Layer 1 heuristics, classify the agent's own tools. These are NOT use_aws calls but have distinct billing implications:
Tool Cost Matrix
| 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 |
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+ |
use_datadog | CAUTION | Datadog API rate limits (no per-call $ cost, but may throttle) | Track count, warn at 100+ |
use_splunk | PAID | Splunk search license (per GB ingested/searched) | Treat like CW Logs StartQuery |
use_pagerduty | FREE | PagerDuty API (rate limited, not per-call billed) | Track count only |
shell | CAUTION | May invoke aws, az, kubectl — untracked by Layers 1–3 | Log commands, warn if aws/az detected |
subagent | PAID | Counts toward agent-seconds billing ($0.0083/sec) | Track spawns, enforce total time |
fs_read, fs_write, fs_tree | FREE | Local file I/O | No guardrail needed |
datetime | FREE | Internal state ops | No guardrail needed |
write_scratchpad, read_scratchpad | FREE | Internal state (may not be available in all environments) | No guardrail needed |
read_memories | FREE | Internal memory recall | No guardrail needed |
PromQL-Specific Controls
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
stepmultiply cost:7d / 60s step = 10,080 datapoints × 500 series = 5M metrics
Before each PromQL call:
estimated_metrics = min(500, estimated_series) × (time_range_seconds / step_seconds)
estimated_cost = (estimated_metrics / 1000) * 0.01
if estimated_cost > $0.50:
⚠️ WARN — suggest narrower time range, larger step, or label filters
if estimated_cost > $2.00:
🚫 HALT — require approval or suggest aggregation (sum, topk, avg)
Cost reduction for PromQL:
- Use
sum by (label)to reduce series count - Use
topk(N, ...)to cap returned series - Increase
step(300s instead of 60s = 5× cheaper) - Narrow time range (1h instead of 7d = 168× cheaper)
Layer 1: Heuristic Classification
Before making ANY use_aws call, classify the operation using these rules IN ORDER:
Rule 1: FREE by default — Metadata operations
An operation is FREE if it matches ALL of these:
- Verb is:
Describe,List,Get,Lookup,Check,Validate,Tag,Untag - 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.
⚠️ 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:
| Pattern | Why It Costs Money | Examples |
|---|---|---|
Verb contains Query or Search | Scans indexed data | StartQuery, Search, 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 |
Rule 3: CAUTION — High-volume free operations
An operation is FREE but CAUTION if:
- It's a paginated List/Describe that could return thousands of results
- It has no built-in limit and the scope is broad (e.g., all resources in a region)
Examples: ListObjectsV2 (large bucket), ListMetrics (unfiltered), DescribeTasks (large cluster)
Rule 4: UNKNOWN — Cannot classify
If an operation doesn't clearly fit Rules 1–3:
- Treat as CAUTION (proceed but track)
- After execution, check response for metered fields (see Layer 3)
- If metered: add to the known-paid list for this session
Layer 2: Known-Paid Registry
These operations have confirmed pricing with estimation formulas. This list is extensible — operators can add entries.
Confirmed Paid Operations
| Service | 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.
Layer 3: Response Validation
After ANY operation executes, check the response for metered fields:
Metered Response Fields (indicates cost was incurred)
| Field Pattern | Meaning | Action |
|---|---|---|
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 |
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 |
If a previously-unclassified operation returns metered fields:
- Log it as a paid operation for this session
- Add the cost to the running total
- Warn the user:
⚠️ Discovered paid operation: <service>:<operation> cost $X.XX
Budget Enforcement
Per-Investigation Budget
The agent MUST mentally track a running cost estimate throughout the investigation. Since write_scratchpad/read_scratchpad are not available in all environments, budget enforcement is behavioral — the agent maintains the accumulator in its context window.
At investigation start:
Budget: $10.00
Running cost: $0.00
Call counts: {}
Before each PAID operation:
estimated_cost = estimate(operation)
if running_cost + estimated_cost > budget:
🚫 HALT — show budget display
else:
proceed
# After execution:
running_cost += actual_cost (from response fields or estimation)
call_counts[service] += 1
Volume guardrails:
if call_counts[any_service] > 200: ⚠️ WARN
if call_counts[any_service] > 500: 🚫 HALT
if sum(all_call_counts) > 1000: 🚫 HALT
ℹ️ If
write_scratchpadbecomes available in your environment, use it for persistent state across subagent boundaries. Check with:search_user_tools("scratchpad"). If found, store{budget, running_cost, call_counts}as JSON.
Budget Display (on halt)
📋 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: <service>:<operation> — estimated $X.XX
Projected: $X.XX (exceeds budget by $X.XX)
🚫 HALTED — would exceed $10.00 budget.
💡 Options:
→ Approve additional $X.XX to continue
→ Narrow the time window to reduce scan volume
→ Skip this operation and continue with free alternatives
→ End investigation with findings so far
Time Window Enforcement
For ANY operation classified as PAID that scans data over a time range:
| Scenario | Action |
|---|---|
| User provided time window | ✅ Use it — estimate cost for that window |
| No time window, operation scans data | 🚫 CANCEL — show worst-case cost, ask for window |
| No time window, operation is bounded (single resource lookup) | ✅ Proceed — no scan involved |
Key distinction: "Get me the config of Lambda X" (bounded, free) vs. "Search logs for errors" (unbounded scan, needs window).
PromQL-specific: Range queries without explicit start/end default to "now" which is safe. But broad label selectors ({} with just metric name) can hit 500 series cap — always prefer specific labels.
Cross-Region Detection
For EVERY paid operation:
if target_region ≠ agent_space_region:
estimated_return_size = estimate_return_bytes(operation_type)
transfer_cost = estimated_return_size × $0.02/GB
total_estimate += transfer_cost
flag: "⚠️ Cross-region transfer: <target> → <agent_space>"
Return size heuristics:
- Aggregation queries (stats, count, group-by): ~KB (negligible)
- PromQL with aggregation (sum, topk): ~KB (negligible)
- PromQL range query (500 series × 10K points): ~50MB (flag ⚠️)
- Raw log/trace fetches: up to 100% of matched bytes
- Describe/List results: ~KB (negligible)
- Unknown: use 15% of scan volume as upper bound, flag ⚠️
Cost Reduction Suggestions
When halting or warning, ALWAYS suggest free or cheaper alternatives:
Generic Alternatives (apply to any service)
| Pattern | Free/Cheaper Alternative |
|---|---|
| Broad time window scan | Narrow to ±30 min around the incident |
| Multiple resource query | Target specific resource ID |
| Full scan (DynamoDB, Athena) | Add filter/WHERE/key condition |
| Analytics query for known string | Use free filter API (FilterLogEvents) — note: LookupEvents may be tool-policy-blocked in some environments |
| Cross-region operation | Suggest user run from workload region |
| Large object fetch | Use SelectObjectContent with SQL filter |
| Pagination explosion | Add limit, filter, or narrower scope |
| Broad PromQL (no label filters) | Add specific label matchers or use aggregation |
| PromQL small step (60s over 7d) | Increase to 300s+ or reduce time range |
Service-Specific Alternatives
| Instead of... | Use... | Savings |
|---|---|---|
logs:StartQuery | logs:FilterLogEvents (if searching for known string) | 100% |
cloudwatch:GetMetricData (many) | cloudwatch:GetMetricStatistics (single) | ~100% |
get_prometheus_metrics (broad) | Add sum by (label) or topk(5, ...) | 90%+ |
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 | Variable |
More skills from the tools-for-devops-agent repository
View all 15 skillsaws-health-events
analyze AWS Health events for incidents
Aug 20AWSDebuggingIncident ResponseMonitoringaws-vpc-dns-investigation
investigate VPC DNS resolution issues
Aug 20AWSDebuggingDNSNetworkingcrm-production-investigation-guidelines
investigate CRM production incidents
Aug 20AWSCRMDebuggingIncident Response +1database-migration-service-expertise
troubleshoot AWS Database Migration Service
Aug 20AWSDatabaseDebuggingMigration +1database-rds-devops
diagnose Aurora MySQL and PostgreSQL databases
Aug 20AWSDatabaseDebuggingMySQL +1eks-operation-review
audit Amazon EKS cluster operations
Aug 20AuditAWSKubernetesOperations
More from AWS (Amazon)
View publisheragents-build
add capabilities to existing agent projects
agent-toolkit-for-aws
Aug 20AgentsAutomationAWSEngineeringagents-connect
connect agents to external services
agent-toolkit-for-aws
Jul 16AgentsAPI DevelopmentAuthenticationAWSagents-debug
debug agent and environment issues
agent-toolkit-for-aws
Jul 16AgentsAWSDebuggingObservabilityagents-deploy
deploy AI agents to AWS
agent-toolkit-for-aws
Jul 12AgentsAWSCI/CDDeploymentagents-get-started
scaffold and deploy new agent projects
agent-toolkit-for-aws
Jul 12AgentsAWSDeploymentagents-harden
harden agents for production
agent-toolkit-for-aws
Jul 16AgentsAWSBest PracticesSecurity