Multi-Target Operations: CLI DevEx Proposal
Author: gitikavj@
Date: 2026-05-08
Status: Draft
Archetype: scope_widening
What is Multi-Target Operations?
Today, aws-targets.json supports an array of named deployment targets (e.g., dev, staging, prod), but the CLI hardcodes resolution to the first entry for most operational commands. Developers who need to work across multiple environments must either manually swap entries or maintain separate project directories — neither is acceptable at scale.
Multi-Target Operations makes the target selection surface first-class across all operational commands. Every command that reads or writes to a deployed environment gains --target <name> and --all-targets flags. The default behavior (no flag) uses the first configured target, preserving full backwards compatibility for single-target projects.
The core gap is narrow and surgical: resolveAgent() in src/cli/operations/resolve-agent.ts hardcodes targetNames[0]. That one callsite, plus consistent flag exposure across logs and traces (the two commands currently missing --target), is the bulk of the work. The multi-target state model in deployed-state.json already supports this — it has been per-target since day one.
Scope
In scope:
- Adding
--target <name> and --all-targets flags to logs and traces (the two commands that currently lack them)
- Normalizing
--target default behavior across deploy, status, and invoke (currently inconsistent)
- Adding
--all-targets flag to deploy, status, invoke, logs, traces
- Adding
--parallel execution flag for --all-targets operations (power-user opt-in)
- Adding
--target and --all-targets to fetch access command
- Fixing
resolveAgent() to accept an explicit target name instead of hardcoding targetNames[0]
- Schema normalization in
aws-targets.json (already supports named targets — no structural change needed)
Out of scope:
- Console/UI experience
- Service internals or control plane changes
- Changes to
aws-targets.json format (already correct)
- Changes to
deployed-state.json structure (already per-target)
- Target creation/management commands (
add target, remove target) — covered by direct aws-targets.json editing today
Current State
aws-targets.json is an array of AwsDeploymentTarget objects (defined in src/schema/schemas/aws-targets.ts), each with name, account, region, and optional description. The structure has always supported multiple targets. deployed-state.json (defined in src/schema/schemas/deployed-state.ts) stores state as Record<DeploymentTargetName, TargetDeployedState> — also per-target since inception.
The gap is entirely in the command layer. resolveAgent() (src/cli/operations/resolve-agent.ts) always uses targetNames[0] from deployed state. Of the six operational commands, three have --target flags but with inconsistent defaults, and two (logs, traces) have no --target flag at all.
Key files today:
src/cli/operations/resolve-agent.ts — shared agent resolution; hardcodes first target
src/cli/commands/deploy/command.tsx — has --target, defaults to literal "default"
src/cli/commands/deploy/actions.ts — deploy business logic
src/cli/commands/status/command.tsx — has --target, defaults to first deployed target
src/cli/commands/status/action.ts — status business logic
src/cli/commands/invoke/command.tsx — has --target, defaults to literal "default"
src/cli/commands/invoke/action.ts — invoke business logic
src/cli/commands/logs/command.tsx — no --target flag; uses resolveAgent() → first target
src/cli/commands/logs/action.ts — logs business logic
src/cli/commands/traces/command.tsx — no --target flag; uses resolveAgent() → first target
src/cli/commands/traces/action.ts — traces business logic
src/schema/schemas/aws-targets.ts — AwsDeploymentTarget Zod schema
src/schema/schemas/deployed-state.ts — DeployedState Zod schema (already per-target)
src/lib/schemas/io/config-io.ts — resolveAWSDeploymentTargets() with region fallback logic
Current developer experience:
# Single target (works fine):
agentcore deploy # deploys to "default" target
# Multi-target (broken — user must edit aws-targets.json):
agentcore deploy --target prod # works on deploy only
agentcore logs # always shows first target's logs — no flag to change this
agentcore traces # always shows first target's traces — no flag to change this
Current --target flag audit:
| Command |
Has --target? |
Default |
Notes |
deploy |
Yes |
"default" (literal) |
Fails if no target named "default" |
status |
Yes |
First deployed target |
Most sensible default |
invoke |
Yes |
"default" (literal) |
Falls back to first deployed target |
logs |
No |
First target via resolveAgent() |
Gap |
traces |
No |
First target via resolveAgent() |
Gap |
fetch access |
No |
N/A |
Gap |
Target State
Every operational command that touches a deployed environment exposes --target <name> and --all-targets. The default with no flag is the first entry in aws-targets.json (not the literal string "default"). With --all-targets, the command runs against every configured target sequentially, with --parallel available for concurrent execution.
Target developer experience:
# Single target (explicit):
agentcore deploy --target prod
agentcore status --target staging
agentcore invoke --target dev "hello world"
agentcore logs --target prod
agentcore traces --target prod
agentcore fetch access --target prod
# All targets (sequential by default):
agentcore deploy --all-targets
agentcore status --all-targets
agentcore invoke --all-targets "hello world"
# All targets (parallel — power users):
agentcore deploy --all-targets --parallel
agentcore status --all-targets --parallel
# Default (unchanged behavior — uses first target):
agentcore deploy
agentcore status
agentcore invoke "hello world"
agentcore logs
agentcore traces
Coexistence Model
Model: user_chooses_one
Backwards compatible: Yes
| Dimension |
Existing (single-target) |
New (multi-target) |
| Triggered by |
No flag (implicit) |
--target <name> or --all-targets |
| Configuration |
aws-targets.json entry 0 |
Explicit name or all entries |
| Deploy mechanism |
CDK/imperative (unchanged) |
Same — dispatches per-target |
| State tracking |
deployed-state.json targets[first] |
deployed-state.json targets[name] |
Projects with a single entry in aws-targets.json are unaffected. --target defaults to the first configured target. No migration required. No config field changes.
Callout: The default-to-first-entry rule replaces the current default-to-literal-"default" behavior in deploy and invoke. This is a bug fix, not a breaking change — any project whose first target is named "default" (the common case) sees identical behavior.
Developer Journeys
1/ Developer with multiple targets runs deploy to prod
User story: A developer has aws-targets.json with [dev, staging, prod] entries. They want to ship to prod only.
CLI experience:
agentcore deploy --target prod
Under the hood:
handleDeploy() receives options.target = "prod"
resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
- Validates
"prod" exists in targets — error with list of valid names if not
- Deploys CDK stack for
prod target only
- Writes
deployed-state.json at targets.prod
What we build:
2/ Developer deploys to all targets in sequence
User story: Same developer wants to promote a release to all three environments in one command.
CLI experience:
agentcore deploy --all-targets
# Output:
# [1/3] Deploying to dev... ✓ (12s)
# [2/3] Deploying to staging... ✓ (14s)
# [3/3] Deploying to prod... ✓ (11s)
Under the hood:
handleDeploy() receives options.allTargets = true
- Reads all entries from
aws-targets.json
- Iterates sequentially (or in parallel if
--parallel)
- Calls existing single-target deploy logic per entry
- Aggregates results; prints per-target status
- Exits non-zero if any target fails; reports which targets failed
What we build:
3/ Developer checks logs for a specific target
User story: Developer wants to tail logs for the prod deployment of their agent.
CLI experience:
agentcore logs --target prod
agentcore logs --target prod --runtime my-agent
Under the hood:
logs command receives options.targetName = "prod"
- Passes
targetName to resolveAgent() instead of relying on targetNames[0]
resolveAgent() uses the explicit targetName to look up state in deployed-state.json
- CloudWatch logs fetched for the resolved runtime in the prod region
What we build:
4/ Existing single-target project — unchanged (proves backwards compat)
User story: Developer has one entry in aws-targets.json named "default". They never use --target.
CLI experience:
# Everything works exactly as before:
agentcore deploy # uses first (and only) target: "default"
agentcore status # uses first deployed target: "default"
agentcore invoke "hello" # uses first deployed target: "default"
agentcore logs # uses first deployed target: "default"
agentcore traces # uses first deployed target: "default"
Under the hood:
- No
--target or --all-targets flag provided
- Code reads
aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
- All existing code paths invoked identically
Callout: This is the regression test. If this journey breaks after the change ships, it's a blocker.
Affected Commands
| Command |
Change Type |
Description |
Backwards Compatible |
agentcore deploy |
new_flag |
Add --all-targets and --parallel; normalize default to first target (not "default") |
Yes |
agentcore status |
new_flag |
Add --all-targets and --parallel |
Yes |
agentcore invoke |
new_flag |
Add --all-targets and --parallel; normalize default to first target (not "default") |
Yes |
agentcore logs |
new_flag |
Add --target <name>, --all-targets, --parallel |
Yes |
agentcore traces |
new_flag |
Add --target <name>, --all-targets, --parallel |
Yes |
agentcore fetch access |
new_flag |
Add --target <name>, --all-targets |
Yes |
Schema Changes
No changes to agentcore.json or deployed-state.json schema. Both already support multiple targets. No new fields.
Callout: The only "schema" change is behavioral: the unset---target default moves from the literal string "default" to awsTargets[0].name. This is enforced in command option parsing, not schema validation.
In aws-targets.json (no structural change):
[
{ "name": "dev", "account": "111122223333", "region": "us-west-2" },
{ "name": "staging", "account": "444455556666", "region": "us-west-2" },
{ "name": "prod", "account": "777788889999", "region": "us-east-1" }
]
In deployed-state.json (no structural change):
{
"targets": {
"dev": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } },
"staging": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } },
"prod": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } }
}
}
Detection & Prerequisites
No external tool dependencies. No detection logic needed.
Target validation error messages:
| Scenario |
Message |
--target name not in aws-targets.json |
Target "prod" not found. Available targets: dev, staging (from aws-targets.json) |
--all-targets with empty aws-targets.json |
No targets configured. Add entries to aws-targets.json first. |
--target + --all-targets together |
--target and --all-targets are mutually exclusive. |
--parallel without --all-targets |
--parallel requires --all-targets. |
Codebase Changes
New Files
| File |
Purpose |
src/cli/operations/multi-target.ts |
runForAllTargets() utility — sequential/parallel loop with result aggregation |
Modified Files
| File |
Change |
src/cli/operations/resolve-agent.ts |
Accept optional targetName?: string param; use it instead of targetNames[0] when provided |
src/cli/commands/deploy/command.tsx |
Normalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel |
src/cli/commands/deploy/actions.ts |
Thread target through multi-target loop when --all-targets |
src/cli/commands/status/command.tsx |
Add --all-targets, --parallel |
src/cli/commands/status/action.ts |
Thread target through multi-target loop when --all-targets |
src/cli/commands/invoke/command.tsx |
Normalize default; add --all-targets, --parallel |
src/cli/commands/invoke/action.ts |
Thread target through multi-target loop; pass targetName to resolveAgent() |
src/cli/commands/logs/command.tsx |
Add --target <name>, --all-targets, --parallel |
src/cli/commands/logs/action.ts |
Pass targetName to resolveAgent() |
src/cli/commands/traces/command.tsx |
Add --target <name>, --all-targets, --parallel |
src/cli/commands/traces/action.ts |
Pass targetName to resolveAgent() |
src/cli/commands/fetch-access/command.tsx |
Add --target <name>, --all-targets |
src/cli/commands/fetch-access/action.ts |
Thread target name through resolution |
Architecture: Where It Branches
Dispatch Point
agentcore [command] [args]
│
├─ --target <name> → resolves single named target → existing single-target path
├─ --all-targets → runForAllTargets() → iterates aws-targets.json entries
│ └─ [--parallel] → Promise.all vs sequential loop
└─ (no flag) → awsTargets[0] → existing single-target path (unchanged behavior)
Pattern: Dispatch at command option parsing — no Strategy class needed. runForAllTargets() is a thin iterator that calls the existing single-target action function per entry.
Precedent in the codebase: src/cli/commands/status/action.ts already implements the dispatch pattern — it reads deployedTargetNames, selects options.targetName ?? targetNames[0], and passes the name down. The deploy and invoke normalization fix copies this exact pattern.
Deploy Flow Change
No change to the deploy flow phases. Multi-target with --all-targets is the existing flow executed N times in sequence (or parallel). The CDK path, preflight, state write — all unchanged per invocation.
Current: New (--all-targets):
1. Preflight validation FOR EACH target in aws-targets.json:
2. Identity providers 1. Preflight validation
3. OAuth2 providers 2. Identity providers
4. CDK synth + deploy 3. OAuth2 providers
5. Parse outputs → deployed-state.json 4. CDK synth + deploy
6. Post-deploy operations 5. Parse outputs → deployed-state.json
6. Post-deploy operations
Report: 3/3 succeeded | 1/3 failed (prod)
Architectural Decisions
| # |
Decision |
Choice |
Rationale |
| 1 |
Where to fix resolveAgent() |
Add optional targetName param |
Backwards compatible — existing callers pass nothing, get current behavior |
| 2 |
Default target resolution |
awsTargets[0].name (not "default") |
Matches status command's already-correct behavior; fixes latent bug in deploy/invoke |
| 3 |
--all-targets execution model |
Sequential default, --parallel opt-in |
Sequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs |
| 4 |
Multi-target utility location |
src/cli/operations/multi-target.ts |
Consistent with existing operations directory; keeps command files thin |
| 5 |
--target + --all-targets mutual exclusivity |
Error at flag parse time |
Unambiguous intent; avoids silent override of one by the other |
| 6 |
Schema changes |
None |
deployed-state.json and aws-targets.json already model multi-target correctly |
Implementation Phases
Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)
Phase 2: Add --target to logs, traces, fetch access (independently shippable)
Phase 3: Add --all-targets and --parallel (independently shippable)
Phase 4: Polish (independently shippable)
Testing Strategy
Existing Test Preservation
All existing tests must pass without modification after Phases 1-3. The changes to resolveAgent() are purely additive (optional param). The normalize-default fix only breaks projects with a misconfigured first target named something other than "default" — which is the target of the fix. Run npm test, npm run typecheck, npm run lint on every PR.
Precedent in the codebase: src/cli/commands/status/action.ts implements target selection with options.targetName ?? targetNames[0]. Existing status tests already cover the no-flag path. We verify that pattern still passes after we extend it to the other commands.
New Test Matrix
| Scenario |
No flag (default) |
--target prod |
--all-targets |
--all-targets --parallel |
| deploy |
✓ (first target) |
✓ |
✓ |
✓ |
| status |
✓ (first target) |
✓ |
✓ |
✓ |
| invoke |
✓ (first target) |
✓ |
✓ |
✓ |
| logs |
✓ (first target) |
✓ |
✓ |
✓ |
| traces |
✓ (first target) |
✓ |
✓ |
✓ |
| fetch access |
✓ (first target) |
✓ |
N/A |
N/A |
Invalid --target name |
N/A |
Error + list |
N/A |
N/A |
--target + --all-targets |
N/A |
Error |
N/A |
N/A |
--parallel without --all-targets |
N/A |
Error |
N/A |
N/A |
| Partial failure (one target fails) |
N/A |
N/A |
Non-zero exit |
Non-zero exit |
Integration / E2E
- Against account
998846730471 in us-west-2 and us-east-1:
- Deploy two-target project; verify
deployed-state.json has both target entries
agentcore status --all-targets shows both target states
agentcore logs --target <name> streams correct CloudWatch log group
agentcore deploy --target <name> does not touch the other target's stack
Open Questions
| # |
Question |
For |
Context |
| 1 |
Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once. |
gitikavj@ / PM |
Included in scope per inputs, but worth a deliberate decision. Power-user edge case only. |
| 2 |
For --all-targets --parallel on deploy: should there be a max-concurrency cap, or unlimited? |
gitikavj@ |
Unlimited could hit CDK rate limits or IAM propagation races if many targets exist. |
| 3 |
Should failed targets in --all-targets be retried, or fail-fast? |
gitikavj@ / Eng |
Current proposal: no retry, non-zero exit with which targets failed. |
| 4 |
Does fetch access support --all-targets? The inputs include it, but access tokens are per-target by definition. |
gitikavj@ |
Included per inputs. Need confirmation that listing all-target tokens is a real use case. |
Escalation Required
- Default target string
"default" vs first-entry: Changing the default from the literal string "default" to awsTargets[0].name is my recommendation, but it technically changes behavior for projects where awsTargets[0].name !== "default". This is a latent bug fix, but the AgentCore CLI team should sign off before Phase 1 ships.
--parallel on deploy: Parallel CDK deployments to multiple targets in the same account/region may hit CloudFormation stack operation limits. Need confirmation from the CDK/infra team on whether throttling is a risk here, and whether a max-concurrency cap is needed.
Appendix
Side-by-Side: Single-Target vs Multi-Target Project
Single-target project (unchanged):
aws-targets.json:
[
{ "name": "default", "account": "998846730471", "region": "us-west-2" }
]
agentcore deploy # same as today
agentcore status # same as today
agentcore logs # same as today — resolves to "default"
Multi-target project (new capability):
aws-targets.json:
[
{ "name": "dev", "account": "111122223333", "region": "us-west-2" },
{ "name": "staging", "account": "444455556666", "region": "us-west-2" },
{ "name": "prod", "account": "777788889999", "region": "us-east-1" }
]
agentcore deploy # deploys to dev (first entry)
agentcore deploy --target prod # deploys to prod only
agentcore deploy --all-targets # deploys dev → staging → prod in sequence
agentcore status --all-targets # shows status for all three
agentcore logs --target prod # streams prod logs
resolveAgent() Change (Sketch)
Before:
// src/cli/operations/resolve-agent.ts
const targetName = targetNames[0]; // always first
After:
// src/cli/operations/resolve-agent.ts
function resolveAgent(
context: DeployedProjectConfig,
options: { runtime?: string; targetName?: string } // targetName added
) {
const targetName = options.targetName ?? targetNames[0]; // explicit wins
// ... rest unchanged
}
runForAllTargets() Sketch
// src/cli/operations/multi-target.ts
export async function runForAllTargets<T>(
targets: AwsDeploymentTarget[],
action: (target: AwsDeploymentTarget) => Promise<T>,
options: { parallel?: boolean }
): Promise<Array<{ target: string; result: T | Error }>> {
if (options.parallel) {
const results = await Promise.allSettled(targets.map(action));
return targets.map((t, i) => ({
target: t.name,
result: results[i].status === 'fulfilled' ? results[i].value : results[i].reason,
}));
}
const results = [];
for (const target of targets) {
try {
results.push({ target: target.name, result: await action(target) });
} catch (err) {
results.push({ target: target.name, result: err as Error });
}
}
return results;
}
Multi-Target Operations: CLI DevEx Proposal
Author: gitikavj@
Date: 2026-05-08
Status: Draft
Archetype: scope_widening
What is Multi-Target Operations?
Today,
aws-targets.jsonsupports an array of named deployment targets (e.g., dev, staging, prod), but the CLI hardcodes resolution to the first entry for most operational commands. Developers who need to work across multiple environments must either manually swap entries or maintain separate project directories — neither is acceptable at scale.Multi-Target Operations makes the target selection surface first-class across all operational commands. Every command that reads or writes to a deployed environment gains
--target <name>and--all-targetsflags. The default behavior (no flag) uses the first configured target, preserving full backwards compatibility for single-target projects.The core gap is narrow and surgical:
resolveAgent()insrc/cli/operations/resolve-agent.tshardcodestargetNames[0]. That one callsite, plus consistent flag exposure acrosslogsandtraces(the two commands currently missing--target), is the bulk of the work. The multi-target state model indeployed-state.jsonalready supports this — it has been per-target since day one.Scope
In scope:
--target <name>and--all-targetsflags tologsandtraces(the two commands that currently lack them)--targetdefault behavior acrossdeploy,status, andinvoke(currently inconsistent)--all-targetsflag todeploy,status,invoke,logs,traces--parallelexecution flag for--all-targetsoperations (power-user opt-in)--targetand--all-targetstofetch accesscommandresolveAgent()to accept an explicit target name instead of hardcodingtargetNames[0]aws-targets.json(already supports named targets — no structural change needed)Out of scope:
aws-targets.jsonformat (already correct)deployed-state.jsonstructure (already per-target)add target,remove target) — covered by directaws-targets.jsonediting todayCurrent State
aws-targets.jsonis an array ofAwsDeploymentTargetobjects (defined insrc/schema/schemas/aws-targets.ts), each withname,account,region, and optionaldescription. The structure has always supported multiple targets.deployed-state.json(defined insrc/schema/schemas/deployed-state.ts) stores state asRecord<DeploymentTargetName, TargetDeployedState>— also per-target since inception.The gap is entirely in the command layer.
resolveAgent()(src/cli/operations/resolve-agent.ts) always usestargetNames[0]from deployed state. Of the six operational commands, three have--targetflags but with inconsistent defaults, and two (logs,traces) have no--targetflag at all.Key files today:
src/cli/operations/resolve-agent.ts— shared agent resolution; hardcodes first targetsrc/cli/commands/deploy/command.tsx— has--target, defaults to literal"default"src/cli/commands/deploy/actions.ts— deploy business logicsrc/cli/commands/status/command.tsx— has--target, defaults to first deployed targetsrc/cli/commands/status/action.ts— status business logicsrc/cli/commands/invoke/command.tsx— has--target, defaults to literal"default"src/cli/commands/invoke/action.ts— invoke business logicsrc/cli/commands/logs/command.tsx— no--targetflag; uses resolveAgent() → first targetsrc/cli/commands/logs/action.ts— logs business logicsrc/cli/commands/traces/command.tsx— no--targetflag; uses resolveAgent() → first targetsrc/cli/commands/traces/action.ts— traces business logicsrc/schema/schemas/aws-targets.ts— AwsDeploymentTarget Zod schemasrc/schema/schemas/deployed-state.ts— DeployedState Zod schema (already per-target)src/lib/schemas/io/config-io.ts— resolveAWSDeploymentTargets() with region fallback logicCurrent developer experience:
Current --target flag audit:
--target?deploy"default"(literal)statusinvoke"default"(literal)logstracesfetch accessTarget State
Every operational command that touches a deployed environment exposes
--target <name>and--all-targets. The default with no flag is the first entry inaws-targets.json(not the literal string"default"). With--all-targets, the command runs against every configured target sequentially, with--parallelavailable for concurrent execution.Target developer experience:
Coexistence Model
Model:
user_chooses_oneBackwards compatible: Yes
--target <name>or--all-targetsProjects with a single entry in
aws-targets.jsonare unaffected.--targetdefaults to the first configured target. No migration required. No config field changes.Callout: The default-to-first-entry rule replaces the current default-to-literal-
"default"behavior indeployandinvoke. This is a bug fix, not a breaking change — any project whose first target is named"default"(the common case) sees identical behavior.Developer Journeys
1/ Developer with multiple targets runs deploy to prod
User story: A developer has
aws-targets.jsonwith[dev, staging, prod]entries. They want to ship to prod only.CLI experience:
Under the hood:
handleDeploy()receivesoptions.target = "prod"resolveAWSDeploymentTargets()readsaws-targets.json— finds 3 entries"prod"exists in targets — error with list of valid names if notprodtarget onlydeployed-state.jsonattargets.prodWhat we build:
--targetalready exists; behavior is correct2/ Developer deploys to all targets in sequence
User story: Same developer wants to promote a release to all three environments in one command.
CLI experience:
Under the hood:
handleDeploy()receivesoptions.allTargets = trueaws-targets.json--parallel)What we build:
--all-targetsflag ondeploy,status,invoke,logs,traces,fetch access--parallelflag (companion to--all-targets)runForAllTargets()utility insrc/cli/operations/multi-target.ts3/ Developer checks logs for a specific target
User story: Developer wants to tail logs for the prod deployment of their agent.
CLI experience:
Under the hood:
logscommand receivesoptions.targetName = "prod"targetNametoresolveAgent()instead of relying ontargetNames[0]resolveAgent()uses the explicittargetNameto look up state indeployed-state.jsonWhat we build:
--target <name>flag onlogscommand (src/cli/commands/logs/command.tsx)--target <name>flag ontracescommand (src/cli/commands/traces/command.tsx)resolveAgent()accepts optionaltargetNameparam; uses it when provided4/ Existing single-target project — unchanged (proves backwards compat)
User story: Developer has one entry in
aws-targets.jsonnamed"default". They never use--target.CLI experience:
Under the hood:
--targetor--all-targetsflag providedaws-targets.json[0]as the target (same as today forstatus; fixesdeploy/invokedefault bug)Callout: This is the regression test. If this journey breaks after the change ships, it's a blocker.
Affected Commands
agentcore deploynew_flag--all-targetsand--parallel; normalize default to first target (not"default")agentcore statusnew_flag--all-targetsand--parallelagentcore invokenew_flag--all-targetsand--parallel; normalize default to first target (not"default")agentcore logsnew_flag--target <name>,--all-targets,--parallelagentcore tracesnew_flag--target <name>,--all-targets,--parallelagentcore fetch accessnew_flag--target <name>,--all-targetsSchema Changes
No changes to
agentcore.jsonordeployed-state.jsonschema. Both already support multiple targets. No new fields.Callout: The only "schema" change is behavioral: the unset-
--targetdefault moves from the literal string"default"toawsTargets[0].name. This is enforced in command option parsing, not schema validation.In
aws-targets.json(no structural change):[ { "name": "dev", "account": "111122223333", "region": "us-west-2" }, { "name": "staging", "account": "444455556666", "region": "us-west-2" }, { "name": "prod", "account": "777788889999", "region": "us-east-1" } ]In
deployed-state.json(no structural change):{ "targets": { "dev": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } }, "staging": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } }, "prod": { "resources": { "runtimes": { "my-agent": { "runtimeId": "..." } } } } } }Detection & Prerequisites
No external tool dependencies. No detection logic needed.
Target validation error messages:
--targetname not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)--all-targetswith empty aws-targets.jsonNo targets configured. Add entries to aws-targets.json first.--target+--all-targetstogether--target and --all-targets are mutually exclusive.--parallelwithout--all-targets--parallel requires --all-targets.Codebase Changes
New Files
src/cli/operations/multi-target.tsrunForAllTargets()utility — sequential/parallel loop with result aggregationModified Files
src/cli/operations/resolve-agent.tstargetName?: stringparam; use it instead oftargetNames[0]when providedsrc/cli/commands/deploy/command.tsxawsTargets[0].nameinstead of"default"; add--all-targets,--parallelsrc/cli/commands/deploy/actions.ts--all-targetssrc/cli/commands/status/command.tsx--all-targets,--parallelsrc/cli/commands/status/action.ts--all-targetssrc/cli/commands/invoke/command.tsx--all-targets,--parallelsrc/cli/commands/invoke/action.tstargetNameto resolveAgent()src/cli/commands/logs/command.tsx--target <name>,--all-targets,--parallelsrc/cli/commands/logs/action.tstargetNameto resolveAgent()src/cli/commands/traces/command.tsx--target <name>,--all-targets,--parallelsrc/cli/commands/traces/action.tstargetNameto resolveAgent()src/cli/commands/fetch-access/command.tsx--target <name>,--all-targetssrc/cli/commands/fetch-access/action.tsArchitecture: Where It Branches
Dispatch Point
Pattern: Dispatch at command option parsing — no Strategy class needed.
runForAllTargets()is a thin iterator that calls the existing single-target action function per entry.Precedent in the codebase:
src/cli/commands/status/action.tsalready implements the dispatch pattern — it readsdeployedTargetNames, selectsoptions.targetName ?? targetNames[0], and passes the name down. Thedeployandinvokenormalization fix copies this exact pattern.Deploy Flow Change
No change to the deploy flow phases. Multi-target with
--all-targetsis the existing flow executed N times in sequence (or parallel). The CDK path, preflight, state write — all unchanged per invocation.Architectural Decisions
targetNameparamawsTargets[0].name(not"default")--parallelopt-insrc/cli/operations/multi-target.tsImplementation Phases
Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)
targetName?: stringparam toresolveAgent()insrc/cli/operations/resolve-agent.tstargetNameprovided: use it to look up state; error clearly if not foundtargetNameabsent: preserve current behavior (targetNames[0]) — no behavior changedeployandinvokedefault: replace"default"literal withawsTargets[0].name"default".Phase 2: Add --target to logs, traces, fetch access (independently shippable)
--target <name>tologscommand (command.tsx+ pass toaction.ts)--target <name>totracescommand (command.tsx+ pass toaction.ts)--target <name>tofetch accesscommand (command.tsx+ pass toaction.ts)targetNamethrough toresolveAgent()in all three action fileslogs,traces, andfetch accessbehave consistently withdeploy,status,invoke.Phase 3: Add --all-targets and --parallel (independently shippable)
runForAllTargets()insrc/cli/operations/multi-target.tsresolveAWSDeploymentTargets()to get ordered target listparallel: boolean--all-targetsand--parallelflags to all six commands--target+--all-targets→ error)--parallelwithout--all-targets→ error[1/3] Deploying to dev... ✓ (12s)Phase 4: Polish (independently shippable)
docs/commands.mdanddocs/configuration.md--all-targetswith one target (degenerate case — should work identically to--target first)Testing Strategy
Existing Test Preservation
All existing tests must pass without modification after Phases 1-3. The changes to
resolveAgent()are purely additive (optional param). The normalize-default fix only breaks projects with a misconfigured first target named something other than"default"— which is the target of the fix. Runnpm test,npm run typecheck,npm run linton every PR.Precedent in the codebase:
src/cli/commands/status/action.tsimplements target selection withoptions.targetName ?? targetNames[0]. Existing status tests already cover the no-flag path. We verify that pattern still passes after we extend it to the other commands.New Test Matrix
--target prod--all-targets--all-targets --parallel--targetname--target+--all-targets--parallelwithout--all-targetsIntegration / E2E
998846730471inus-west-2andus-east-1:deployed-state.jsonhas both target entriesagentcore status --all-targetsshows both target statesagentcore logs --target <name>streams correct CloudWatch log groupagentcore deploy --target <name>does not touch the other target's stackOpen Questions
--all-targetsoninvokebe supported? It's operationally unusual to invoke an agent on every target at once.--all-targets --parallelondeploy: should there be a max-concurrency cap, or unlimited?--all-targetsbe retried, or fail-fast?fetch accesssupport--all-targets? The inputs include it, but access tokens are per-target by definition.Escalation Required
"default"vs first-entry: Changing the default from the literal string"default"toawsTargets[0].nameis my recommendation, but it technically changes behavior for projects whereawsTargets[0].name !== "default". This is a latent bug fix, but the AgentCore CLI team should sign off before Phase 1 ships.--parallelondeploy: Parallel CDK deployments to multiple targets in the same account/region may hit CloudFormation stack operation limits. Need confirmation from the CDK/infra team on whether throttling is a risk here, and whether a max-concurrency cap is needed.Appendix
Side-by-Side: Single-Target vs Multi-Target Project
Single-target project (unchanged):
aws-targets.json:[ { "name": "default", "account": "998846730471", "region": "us-west-2" } ]Multi-target project (new capability):
aws-targets.json:[ { "name": "dev", "account": "111122223333", "region": "us-west-2" }, { "name": "staging", "account": "444455556666", "region": "us-west-2" }, { "name": "prod", "account": "777788889999", "region": "us-east-1" } ]resolveAgent() Change (Sketch)
Before:
After:
runForAllTargets() Sketch