Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ cdk deploy agentcore-workshop-dev-runtime-orchestrator -c agent_pattern=claude-s

The agent applications and shared utilities build on patterns from [fullstack-solution-template-for-agentcore](https://github.com/aws-samples/fullstack-solution-template-for-agentcore) (FAST). The CDK stacks are specific to this accelerator.

### Track Costs per Component

Every stack is tagged with `Project`, `Environment`, and `Component` (the stack's suffix in the deployment contract — `gateway`, `memory`, `runtime-orchestrator`, use-case stacks included). To see them in Cost Explorer, activate the tags once per payer account — takes effect within about 24 hours:

```bash
aws ce update-cost-allocation-tags-status --cost-allocation-tags-status \
Status=Active,TagKey=Project Status=Active,TagKey=Environment Status=Active,TagKey=Component
```

Then group by the `Component` tag in Cost Explorer to split spend per stack. Scope: tags attribute *resource* costs (NAT, endpoints, CloudWatch, CodeBuild). Two gaps to know about: a few resource types (AgentCore Memory, SSM parameters) do not accept CloudFormation tags, and Bedrock model inference — usually the largest line — is not covered by resource tags at all; attributing inference requires Application Inference Profiles, which is on the roadmap.

### Run Runtimes in Your VPC (`enable_networking`)

Use this when your agents need private access to resources in your VPC or tighter outbound network controls. Set `enable_networking=true` to run runtimes in your VPC. AgentCore creates network interfaces in private subnets and attaches them to a security group with HTTPS-only egress and no inbound access. Traffic goes out through the NAT gateway. Interface endpoints cover Bedrock, ECR, CloudWatch Logs, and AgentCore Gateway. ECR layer pulls use the free S3 gateway endpoint.
Expand Down
11 changes: 11 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,4 +592,15 @@ def cfg(context_key: str, env_key: str, default: str) -> str:
_spec.loader.exec_module(_mod)
_mod.build(app, _uc_ctx, platform_config.use_cases[_uc_name] or {})

# ── Cost-allocation tags ──
# Component = the stack's suffix in the deployment contract (expected_stacks),
# so Cost Explorer can split spend per component instead of per project only.
# Runs after the use-case loop so contributed stacks are tagged too. The tag
# only reaches billing once activated (README "Cost attribution"); Bedrock
# inference itself is untagged — that needs Application Inference Profiles,
# tracked as its own task.
for _stack in app.node.children:
if isinstance(_stack, cdk.Stack):
cdk.Tags.of(_stack).add("Component", _stack.node.id.removeprefix(f"{prefix}-"))

app.synth()
26 changes: 26 additions & 0 deletions tests/test_cost_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Cost attribution rests on the Component tag actually being applied.

app.py tags every stack with Component = its contract suffix (the same
vocabulary as expected_stacks). If the tagging loop disappears, Cost Explorer
silently loses the per-component split and nothing else fails — so pin it
here, source-level, the same way test_runtime_role_scoping pins IAM intent
(the CI pytest job has no aws_cdk to synthesize with).
"""

from pathlib import Path

REPO = Path(__file__).resolve().parents[1]
APP = REPO / "app.py"


def test_every_stack_gets_a_component_tag():
source = APP.read_text()
assert '.add(\n "Component"' in source or '.add("Component"' in source, (
"app.py must tag stacks with Component for cost attribution"
)
# The tag value must stay in the contract's vocabulary: the stack id minus
# the project-environment prefix, exactly what expected_stacks() emits.
assert '_stack.node.id.removeprefix(f"{prefix}-")' in source
# And it must run over ALL stacks (including use-case stacks), not a
# hand-maintained list that new stacks silently miss.
assert "for _stack in app.node.children" in source
Loading