Skip to content
Open
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Note: CDK L3 constructs are in a separate package `@aws/agentcore-cdk`.
- `pause online-eval` - Pause (disable) a deployed online eval config
- `resume online-eval` - Resume (enable) a paused online eval config
- `package` - Package agent artifacts without deploying (zip for CodeZip, container image build for Container)
- `tag` - Manage resource tags (list, add, remove, set-defaults, remove-defaults)
- `validate` - Validate configuration files
- `update` - Check for CLI updates
- `help` - Display help information
Expand Down
40 changes: 40 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,46 @@ agentcore remove all --dry-run # Preview

---

## Tagging

### tag

Manage AWS resource tags on your AgentCore project. Tags are applied to deployed CloudFormation resources (agents,
memories, gateways). Credentials are not taggable since they're deployed via the AgentCore Identity API.

```bash
# List all tags (project defaults + per-resource)
agentcore tag list
agentcore tag list --json
agentcore tag list --resource agent:MyAgent

# Add a tag to a specific resource
agentcore tag add --resource agent:MyAgent --key environment --value prod

# Remove a tag from a resource
agentcore tag remove --resource agent:MyAgent --key environment

# Set a project-level default tag (inherited by all resources)
agentcore tag set-defaults --key team --value platform

# Remove a project-level default tag
agentcore tag remove-defaults --key team
```

Resource references use `type:name` format. Taggable types: `agent`, `memory`, `gateway`.

Per-resource tags override project-level defaults when keys conflict. Projects created with the CLI include
`agentcore:created-by` and `agentcore:project-name` as defaults.

| Flag | Description |
| ------------------ | ------------------------------ |
| `--resource <ref>` | Resource reference (type:name) |
| `--key <key>` | Tag key (max 128 chars) |
| `--value <value>` | Tag value (max 256 chars) |
| `--json` | JSON output |

---

## Development

### dev
Expand Down
21 changes: 15 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Main project configuration using a **flat resource model**. Agents, memories, an
{
"name": "MyProject",
"version": 1,
"tags": {
"agentcore:created-by": "agentcore-cli",
"agentcore:project-name": "MyProject",
"environment": "dev"
},
"agents": [
{
"type": "AgentCoreRuntime",
Expand Down Expand Up @@ -75,6 +80,7 @@ Main project configuration using a **flat resource model**. Agents, memories, an
| ------------------- | -------- | ----------------------------------------------------------- |
| `name` | Yes | Project name (1-23 chars, alphanumeric, starts with letter) |
| `version` | Yes | Schema version (integer, currently `1`) |
| `tags` | No | Project-level default tags (inherited by all resources) |
| `agents` | Yes | Array of agent specifications |
| `memories` | Yes | Array of memory resources |
| `credentials` | Yes | Array of credential providers (API key or OAuth) |
Expand Down Expand Up @@ -114,6 +120,7 @@ Main project configuration using a **flat resource model**. Agents, memories, an
| `networkMode` | No | `"PUBLIC"` (default) or `"PRIVATE"` |
| `envVars` | No | Custom environment variables |
| `instrumentation` | No | OpenTelemetry settings |
| `tags` | No | Per-agent tags (override project defaults) |

### Runtime Versions

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

| Field | Required | Description |
| --------------------- | -------- | --------------------------------------- |
| `type` | Yes | Always `"AgentCoreMemory"` |
| `name` | Yes | Memory name (1-48 chars) |
| `eventExpiryDuration` | Yes | Days until events expire (7-365) |
| `strategies` | Yes | Array of memory strategies (at least 1) |
| Field | Required | Description |
| --------------------- | -------- | ------------------------------------------- |
| `type` | Yes | Always `"AgentCoreMemory"` |
| `name` | Yes | Memory name (1-48 chars) |
| `eventExpiryDuration` | Yes | Days until events expire (7-365) |
| `strategies` | Yes | Array of memory strategies (at least 1) |
| `tags` | No | Per-memory tags (override project defaults) |

### Memory Strategies

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

### CUSTOM_JWT Authorizer Configuration

Expand Down
97 changes: 97 additions & 0 deletions integ-tests/tag.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { createTestProject, runCLI } from '../src/test-utils/index.js';
import type { TestProject } from '../src/test-utils/index.js';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

describe('integration: tag command', () => {
let project: TestProject;

beforeAll(async () => {
project = await createTestProject({
language: 'Python',
framework: 'Strands',
modelProvider: 'Bedrock',
memory: 'none',
});
});

afterAll(async () => {
await project.cleanup();
});

it('creates project with auto-tags in agentcore.json', async () => {
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
expect(spec.tags).toEqual({
'agentcore:created-by': 'agentcore-cli',
'agentcore:project-name': expect.any(String),
});
});

it('set-defaults adds a project-level tag', async () => {
const result = await runCLI(
['tag', 'set-defaults', '--key', 'environment', '--value', 'dev', '--json'],
project.projectPath
);
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
const output = JSON.parse(result.stdout);
expect(output.success).toBe(true);

// Verify in agentcore.json
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
expect(spec.tags.environment).toBe('dev');
});

it('tag add sets a per-resource tag', async () => {
// Get the agent name from spec
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
const agentName = spec.agents[0]?.name;
if (!agentName) return; // Skip if no agent

const result = await runCLI(
['tag', 'add', '--resource', `agent:${agentName}`, '--key', 'cost-center', '--value', '12345', '--json'],
project.projectPath
);
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);

// Verify in agentcore.json
const updatedSpec = JSON.parse(await readFile(specPath, 'utf-8'));
expect(updatedSpec.agents[0].tags).toEqual({ 'cost-center': '12345' });
});

it('tag list returns JSON output', async () => {
const result = await runCLI(['tag', 'list', '--json'], project.projectPath);
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
const output = JSON.parse(result.stdout);
expect(output.projectDefaults).toBeDefined();
expect(output.resources).toBeInstanceOf(Array);
});

it('tag remove removes a per-resource tag', async () => {
const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
const agentName = spec.agents[0]?.name;
if (!agentName || !spec.agents[0].tags?.['cost-center']) return;

const result = await runCLI(
['tag', 'remove', '--resource', `agent:${agentName}`, '--key', 'cost-center', '--json'],
project.projectPath
);
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);

const updatedSpec = JSON.parse(await readFile(specPath, 'utf-8'));
expect(updatedSpec.agents[0].tags).toBeUndefined();
});

it('remove-defaults removes a project-level tag', async () => {
const result = await runCLI(['tag', 'remove-defaults', '--key', 'environment', '--json'], project.projectPath);
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);

const specPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const spec = JSON.parse(await readFile(specPath, 'utf-8'));
expect(spec.tags.environment).toBeUndefined();
});
});
2 changes: 2 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { registerRemove } from './commands/remove';
import { registerResume } from './commands/resume';
import { registerRun } from './commands/run';
import { registerStatus } from './commands/status';
import { registerTag } from './commands/tag';
import { registerTraces } from './commands/traces';
import { registerUpdate } from './commands/update';
import { registerValidate } from './commands/validate';
Expand Down Expand Up @@ -144,6 +145,7 @@ export function registerCommands(program: Command) {
registerResume(program);
registerRun(program);
registerStatus(program);
registerTag(program);
registerTraces(program);
registerUpdate(program);
registerValidate(program);
Expand Down
4 changes: 4 additions & 0 deletions src/cli/commands/create/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ function createDefaultProjectSpec(projectName: string): AgentCoreProjectSpec {
return {
name: projectName,
version: 1,
tags: {
'agentcore:created-by': 'agentcore-cli',
'agentcore:project-name': projectName,
},
agents: [],
memories: [],
credentials: [],
Expand Down
Loading
Loading