Skip to content

Commit 716db40

Browse files
committed
feat: add resource tagging support
Add `agentcore tag` command for managing AWS resource tags on agents, memories, and gateways. Supports project-level default tags (inherited by all resources) and per-resource tag overrides. - Add TagsSchema to project, agent, memory, and gateway schemas - Add `tag list|add|remove|set-defaults|remove-defaults` subcommands - Auto-tag new projects with `agentcore:created-by` and `agentcore:project-name` - Add unit tests for schema validation and tag actions - Add integration tests for tag command round-trip
1 parent 686dbee commit 716db40

File tree

21 files changed

+768
-6
lines changed

21 files changed

+768
-6
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Note: CDK L3 constructs are in a separate package `@aws/agentcore-cdk`.
3535
- `pause online-eval` - Pause (disable) a deployed online eval config
3636
- `resume online-eval` - Resume (enable) a paused online eval config
3737
- `package` - Package agent artifacts without deploying (zip for CodeZip, container image build for Container)
38+
- `tag` - Manage resource tags (list, add, remove, set-defaults, remove-defaults)
3839
- `validate` - Validate configuration files
3940
- `update` - Check for CLI updates
4041
- `help` - Display help information

docs/commands.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,46 @@ agentcore remove all --dry-run # Preview
380380

381381
---
382382

383+
## Tagging
384+
385+
### tag
386+
387+
Manage AWS resource tags on your AgentCore project. Tags are applied to deployed CloudFormation resources (agents,
388+
memories, gateways). Credentials are not taggable since they're deployed via the AgentCore Identity API.
389+
390+
```bash
391+
# List all tags (project defaults + per-resource)
392+
agentcore tag list
393+
agentcore tag list --json
394+
agentcore tag list --resource agent:MyAgent
395+
396+
# Add a tag to a specific resource
397+
agentcore tag add --resource agent:MyAgent --key environment --value prod
398+
399+
# Remove a tag from a resource
400+
agentcore tag remove --resource agent:MyAgent --key environment
401+
402+
# Set a project-level default tag (inherited by all resources)
403+
agentcore tag set-defaults --key team --value platform
404+
405+
# Remove a project-level default tag
406+
agentcore tag remove-defaults --key team
407+
```
408+
409+
Resource references use `type:name` format. Taggable types: `agent`, `memory`, `gateway`.
410+
411+
Per-resource tags override project-level defaults when keys conflict. Projects created with the CLI include
412+
`agentcore:created-by` and `agentcore:project-name` as defaults.
413+
414+
| Flag | Description |
415+
| ------------------ | ------------------------------ |
416+
| `--resource <ref>` | Resource reference (type:name) |
417+
| `--key <key>` | Tag key (max 128 chars) |
418+
| `--value <value>` | Tag value (max 256 chars) |
419+
| `--json` | JSON output |
420+
421+
---
422+
383423
## Development
384424

385425
### dev

docs/configuration.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Main project configuration using a **flat resource model**. Agents, memories, an
2222
{
2323
"name": "MyProject",
2424
"version": 1,
25+
"tags": {
26+
"agentcore:created-by": "agentcore-cli",
27+
"agentcore:project-name": "MyProject",
28+
"environment": "dev"
29+
},
2530
"agents": [
2631
{
2732
"type": "AgentCoreRuntime",
@@ -75,6 +80,7 @@ Main project configuration using a **flat resource model**. Agents, memories, an
7580
| ------------------- | -------- | ----------------------------------------------------------- |
7681
| `name` | Yes | Project name (1-23 chars, alphanumeric, starts with letter) |
7782
| `version` | Yes | Schema version (integer, currently `1`) |
83+
| `tags` | No | Project-level default tags (inherited by all resources) |
7884
| `agents` | Yes | Array of agent specifications |
7985
| `memories` | Yes | Array of memory resources |
8086
| `credentials` | Yes | Array of credential providers (API key or OAuth) |
@@ -114,6 +120,7 @@ Main project configuration using a **flat resource model**. Agents, memories, an
114120
| `networkMode` | No | `"PUBLIC"` (default) or `"PRIVATE"` |
115121
| `envVars` | No | Custom environment variables |
116122
| `instrumentation` | No | OpenTelemetry settings |
123+
| `tags` | No | Per-agent tags (override project defaults) |
117124

118125
### Runtime Versions
119126

@@ -137,12 +144,13 @@ Main project configuration using a **flat resource model**. Agents, memories, an
137144
}
138145
```
139146

140-
| Field | Required | Description |
141-
| --------------------- | -------- | --------------------------------------- |
142-
| `type` | Yes | Always `"AgentCoreMemory"` |
143-
| `name` | Yes | Memory name (1-48 chars) |
144-
| `eventExpiryDuration` | Yes | Days until events expire (7-365) |
145-
| `strategies` | Yes | Array of memory strategies (at least 1) |
147+
| Field | Required | Description |
148+
| --------------------- | -------- | ------------------------------------------- |
149+
| `type` | Yes | Always `"AgentCoreMemory"` |
150+
| `name` | Yes | Memory name (1-48 chars) |
151+
| `eventExpiryDuration` | Yes | Days until events expire (7-365) |
152+
| `strategies` | Yes | Array of memory strategies (at least 1) |
153+
| `tags` | No | Per-memory tags (override project defaults) |
146154

147155
### Memory Strategies
148156

@@ -329,6 +337,7 @@ Gateway and MCP tool configuration. Gateways, their targets, and standalone MCP
329337
| `targets` | Yes | Array of gateway targets |
330338
| `authorizerType` | No | `"NONE"` (default), `"AWS_IAM"`, or `"CUSTOM_JWT"` |
331339
| `authorizerConfiguration` | No | Required when `authorizerType` is `"CUSTOM_JWT"` (see below) |
340+
| `tags` | No | Per-gateway tags (override project defaults) |
332341

333342
### CUSTOM_JWT Authorizer Configuration
334343

integ-tests/tag.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { createTestProject, runCLI } from '../src/test-utils/index.js';
2+
import type { TestProject } from '../src/test-utils/index.js';
3+
import { readFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6+
7+
describe('integration: tag command', () => {
8+
let project: TestProject;
9+
10+
beforeAll(async () => {
11+
project = await createTestProject({
12+
language: 'Python',
13+
framework: 'Strands',
14+
modelProvider: 'Bedrock',
15+
memory: 'none',
16+
});
17+
});
18+
19+
afterAll(async () => {
20+
await project.cleanup();
21+
});
22+
23+
it('creates project with auto-tags in agentcore.json', async () => {
24+
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
25+
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
26+
expect(spec.tags).toEqual({
27+
'agentcore:created-by': 'agentcore-cli',
28+
'agentcore:project-name': expect.any(String),
29+
});
30+
});
31+
32+
it('set-defaults adds a project-level tag', async () => {
33+
const result = await runCLI(
34+
['tag', 'set-defaults', '--key', 'environment', '--value', 'dev', '--json'],
35+
project.projectPath
36+
);
37+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
38+
const output = JSON.parse(result.stdout);
39+
expect(output.success).toBe(true);
40+
41+
// Verify in agentcore.json
42+
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
43+
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
44+
expect(spec.tags.environment).toBe('dev');
45+
});
46+
47+
it('tag add sets a per-resource tag', async () => {
48+
// Get the agent name from spec
49+
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
50+
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
51+
const agentName = spec.agents[0]?.name;
52+
if (!agentName) return; // Skip if no agent
53+
54+
const result = await runCLI(
55+
['tag', 'add', '--resource', `agent:${agentName}`, '--key', 'cost-center', '--value', '12345', '--json'],
56+
project.projectPath
57+
);
58+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
59+
60+
// Verify in agentcore.json
61+
const updatedSpec = JSON.parse(await readFile(specPath, 'utf-8'));
62+
expect(updatedSpec.agents[0].tags).toEqual({ 'cost-center': '12345' });
63+
});
64+
65+
it('tag list returns JSON output', async () => {
66+
const result = await runCLI(['tag', 'list', '--json'], project.projectPath);
67+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
68+
const output = JSON.parse(result.stdout);
69+
expect(output.projectDefaults).toBeDefined();
70+
expect(output.resources).toBeInstanceOf(Array);
71+
});
72+
73+
it('tag remove removes a per-resource tag', async () => {
74+
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
75+
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
76+
const agentName = spec.agents[0]?.name;
77+
if (!agentName || !spec.agents[0].tags?.['cost-center']) return;
78+
79+
const result = await runCLI(
80+
['tag', 'remove', '--resource', `agent:${agentName}`, '--key', 'cost-center', '--json'],
81+
project.projectPath
82+
);
83+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
84+
85+
const updatedSpec = JSON.parse(await readFile(specPath, 'utf-8'));
86+
expect(updatedSpec.agents[0].tags).toBeUndefined();
87+
});
88+
89+
it('remove-defaults removes a project-level tag', async () => {
90+
const result = await runCLI(['tag', 'remove-defaults', '--key', 'environment', '--json'], project.projectPath);
91+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
92+
93+
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
94+
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
95+
expect(spec.tags.environment).toBeUndefined();
96+
});
97+
});

src/cli/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { registerRemove } from './commands/remove';
1212
import { registerResume } from './commands/resume';
1313
import { registerRun } from './commands/run';
1414
import { registerStatus } from './commands/status';
15+
import { registerTag } from './commands/tag';
1516
import { registerTraces } from './commands/traces';
1617
import { registerUpdate } from './commands/update';
1718
import { registerValidate } from './commands/validate';
@@ -144,6 +145,7 @@ export function registerCommands(program: Command) {
144145
registerResume(program);
145146
registerRun(program);
146147
registerStatus(program);
148+
registerTag(program);
147149
registerTraces(program);
148150
registerUpdate(program);
149151
registerValidate(program);

src/cli/commands/create/action.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ function createDefaultProjectSpec(projectName: string): AgentCoreProjectSpec {
2727
return {
2828
name: projectName,
2929
version: 1,
30+
tags: {
31+
'agentcore:created-by': 'agentcore-cli',
32+
'agentcore:project-name': projectName,
33+
},
3034
agents: [],
3135
memories: [],
3236
credentials: [],

0 commit comments

Comments
 (0)