Skip to content

Feature Proposal: Multi-Target Operations #1187

Description

@notgitika

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.tsxno --target flag; uses resolveAgent() → first target
  • src/cli/commands/logs/action.ts — logs business logic
  • src/cli/commands/traces/command.tsxno --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:

CommandHas --target?DefaultNotes
deployYes"default" (literal)Fails if no target named "default"
statusYesFirst deployed targetMost sensible default
invokeYes"default" (literal)Falls back to first deployed target
logsNoFirst target via resolveAgent()Gap
tracesNoFirst target via resolveAgent()Gap
fetch accessNoN/AGap

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

DimensionExisting (single-target)New (multi-target)
Triggered byNo flag (implicit)--target <name> or --all-targets
Configurationaws-targets.json entry 0Explicit name or all entries
Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
State trackingdeployed-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:

  1. handleDeploy() receives options.target = "prod"
  2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
  3. Validates "prod" exists in targets — error with list of valid names if not
  4. Deploys CDK stack for prod target only
  5. Writes deployed-state.json at targets.prod

What we build:

  • No new code for deploy — --target already exists; behavior is correct

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:

  1. handleDeploy() receives options.allTargets = true
  2. Reads all entries from aws-targets.json
  3. Iterates sequentially (or in parallel if --parallel)
  4. Calls existing single-target deploy logic per entry
  5. Aggregates results; prints per-target status
  6. Exits non-zero if any target fails; reports which targets failed

What we build:

  • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
  • --parallel flag (companion to --all-targets)
  • runForAllTargets() utility in src/cli/operations/multi-target.ts
  • Per-target result aggregation and exit code logic

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:

  1. logs command receives options.targetName = "prod"
  2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
  3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
  4. CloudWatch logs fetched for the resolved runtime in the prod region

What we build:

  • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
  • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
  • resolveAgent() accepts optional targetName param; uses it when provided

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:

  1. No --target or --all-targets flag provided
  2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
  3. 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

CommandChange TypeDescriptionBackwards Compatible
agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
agentcore statusnew_flagAdd --all-targets and --parallelYes
agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

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:

ScenarioMessage
--target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
--all-targets with empty aws-targets.jsonNo 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

FilePurpose
src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

Modified Files

FileChange
src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
src/cli/commands/status/command.tsxAdd --all-targets, --parallel
src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
src/cli/commands/logs/action.tsPass targetName to resolveAgent()
src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
src/cli/commands/traces/action.tsPass targetName to resolveAgent()
src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
src/cli/commands/fetch-access/action.tsThread 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

#DecisionChoiceRationale
1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

Implementation Phases

Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

  • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
  • When targetName provided: use it to look up state; error clearly if not found
  • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
  • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
  • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
  • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

Phase 2: Add --target to logs, traces, fetch access (independently shippable)

  • Add --target <name> to logs command (command.tsx + pass to action.ts)
  • Add --target <name> to traces command (command.tsx + pass to action.ts)
  • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
  • Thread targetName through to resolveAgent() in all three action files
  • Unit tests: logs with explicit target, traces with explicit target, invalid target error
  • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

Phase 3: Add --all-targets and --parallel (independently shippable)

  • Write runForAllTargets() in src/cli/operations/multi-target.ts
    • Reads resolveAWSDeploymentTargets() to get ordered target list
    • Accepts action function, options, and parallel: boolean
    • Sequential: for-await loop; Parallel: Promise.allSettled
    • Returns per-target results with success/failure and output
  • Add --all-targets and --parallel flags to all six commands
  • Implement mutual exclusivity validation (--target + --all-targets → error)
  • Implement --parallel without --all-targets → error
  • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
  • Aggregate exit code: non-zero if any target fails
  • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
  • Ship note: After this lands, all multi-target workflows are available.

Phase 4: Polish (independently shippable)

  • Update help text for all six commands to document new flags
  • Update docs/commands.md and docs/configuration.md
  • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
  • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
  • Ship note: Documentation and E2E coverage for the complete feature.

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

ScenarioNo 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/AN/A
Invalid --target nameN/AError + listN/AN/A
--target + --all-targetsN/AErrorN/AN/A
--parallel without --all-targetsN/AErrorN/AN/A
Partial failure (one target fails)N/AN/ANon-zero exitNon-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

#QuestionForContext
1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
2For --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.
3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
4Does 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.tsconsttargetName=targetNames[0];// always first

After:

// src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

runForAllTargets() Sketch

// src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
      Skip to content

      Feature Proposal: Multi-Target Operations #1187

      Description

      @notgitika

      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.tsxno --target flag; uses resolveAgent() → first target
      • src/cli/commands/logs/action.ts — logs business logic
      • src/cli/commands/traces/command.tsxno --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:

      CommandHas --target?DefaultNotes
      deployYes"default" (literal)Fails if no target named "default"
      statusYesFirst deployed targetMost sensible default
      invokeYes"default" (literal)Falls back to first deployed target
      logsNoFirst target via resolveAgent()Gap
      tracesNoFirst target via resolveAgent()Gap
      fetch accessNoN/AGap

      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

      DimensionExisting (single-target)New (multi-target)
      Triggered byNo flag (implicit)--target <name> or --all-targets
      Configurationaws-targets.json entry 0Explicit name or all entries
      Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
      State trackingdeployed-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:

      1. handleDeploy() receives options.target = "prod"
      2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
      3. Validates "prod" exists in targets — error with list of valid names if not
      4. Deploys CDK stack for prod target only
      5. Writes deployed-state.json at targets.prod

      What we build:

      • No new code for deploy — --target already exists; behavior is correct

      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:

      1. handleDeploy() receives options.allTargets = true
      2. Reads all entries from aws-targets.json
      3. Iterates sequentially (or in parallel if --parallel)
      4. Calls existing single-target deploy logic per entry
      5. Aggregates results; prints per-target status
      6. Exits non-zero if any target fails; reports which targets failed

      What we build:

      • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
      • --parallel flag (companion to --all-targets)
      • runForAllTargets() utility in src/cli/operations/multi-target.ts
      • Per-target result aggregation and exit code logic

      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:

      1. logs command receives options.targetName = "prod"
      2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
      3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
      4. CloudWatch logs fetched for the resolved runtime in the prod region

      What we build:

      • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
      • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
      • resolveAgent() accepts optional targetName param; uses it when provided

      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:

      1. No --target or --all-targets flag provided
      2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
      3. 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

      CommandChange TypeDescriptionBackwards Compatible
      agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
      agentcore statusnew_flagAdd --all-targets and --parallelYes
      agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
      agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
      agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
      agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

      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:

      ScenarioMessage
      --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
      --all-targets with empty aws-targets.jsonNo 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

      FilePurpose
      src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

      Modified Files

      FileChange
      src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
      src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
      src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
      src/cli/commands/status/command.tsxAdd --all-targets, --parallel
      src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
      src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
      src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
      src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
      src/cli/commands/logs/action.tsPass targetName to resolveAgent()
      src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
      src/cli/commands/traces/action.tsPass targetName to resolveAgent()
      src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
      src/cli/commands/fetch-access/action.tsThread 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

      #DecisionChoiceRationale
      1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
      2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
      3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
      4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
      5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
      6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

      Implementation Phases

      Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

      • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
      • When targetName provided: use it to look up state; error clearly if not found
      • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
      • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
      • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
      • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

      Phase 2: Add --target to logs, traces, fetch access (independently shippable)

      • Add --target <name> to logs command (command.tsx + pass to action.ts)
      • Add --target <name> to traces command (command.tsx + pass to action.ts)
      • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
      • Thread targetName through to resolveAgent() in all three action files
      • Unit tests: logs with explicit target, traces with explicit target, invalid target error
      • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

      Phase 3: Add --all-targets and --parallel (independently shippable)

      • Write runForAllTargets() in src/cli/operations/multi-target.ts
        • Reads resolveAWSDeploymentTargets() to get ordered target list
        • Accepts action function, options, and parallel: boolean
        • Sequential: for-await loop; Parallel: Promise.allSettled
        • Returns per-target results with success/failure and output
      • Add --all-targets and --parallel flags to all six commands
      • Implement mutual exclusivity validation (--target + --all-targets → error)
      • Implement --parallel without --all-targets → error
      • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
      • Aggregate exit code: non-zero if any target fails
      • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
      • Ship note: After this lands, all multi-target workflows are available.

      Phase 4: Polish (independently shippable)

      • Update help text for all six commands to document new flags
      • Update docs/commands.md and docs/configuration.md
      • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
      • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
      • Ship note: Documentation and E2E coverage for the complete feature.

      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

      ScenarioNo 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/AN/A
      Invalid --target nameN/AError + listN/AN/A
      --target + --all-targetsN/AErrorN/AN/A
      --parallel without --all-targetsN/AErrorN/AN/A
      Partial failure (one target fails)N/AN/ANon-zero exitNon-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

      #QuestionForContext
      1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
      2For --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.
      3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
      4Does 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.tsconsttargetName=targetNames[0];// always first

      After:

      // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

      runForAllTargets() Sketch

      // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        enhancementNew feature or request

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
          Skip to content

          Feature Proposal: Multi-Target Operations #1187

          Description

          @notgitika

          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.tsxno --target flag; uses resolveAgent() → first target
          • src/cli/commands/logs/action.ts — logs business logic
          • src/cli/commands/traces/command.tsxno --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:

          CommandHas --target?DefaultNotes
          deployYes"default" (literal)Fails if no target named "default"
          statusYesFirst deployed targetMost sensible default
          invokeYes"default" (literal)Falls back to first deployed target
          logsNoFirst target via resolveAgent()Gap
          tracesNoFirst target via resolveAgent()Gap
          fetch accessNoN/AGap

          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

          DimensionExisting (single-target)New (multi-target)
          Triggered byNo flag (implicit)--target <name> or --all-targets
          Configurationaws-targets.json entry 0Explicit name or all entries
          Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
          State trackingdeployed-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:

          1. handleDeploy() receives options.target = "prod"
          2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
          3. Validates "prod" exists in targets — error with list of valid names if not
          4. Deploys CDK stack for prod target only
          5. Writes deployed-state.json at targets.prod

          What we build:

          • No new code for deploy — --target already exists; behavior is correct

          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:

          1. handleDeploy() receives options.allTargets = true
          2. Reads all entries from aws-targets.json
          3. Iterates sequentially (or in parallel if --parallel)
          4. Calls existing single-target deploy logic per entry
          5. Aggregates results; prints per-target status
          6. Exits non-zero if any target fails; reports which targets failed

          What we build:

          • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
          • --parallel flag (companion to --all-targets)
          • runForAllTargets() utility in src/cli/operations/multi-target.ts
          • Per-target result aggregation and exit code logic

          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:

          1. logs command receives options.targetName = "prod"
          2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
          3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
          4. CloudWatch logs fetched for the resolved runtime in the prod region

          What we build:

          • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
          • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
          • resolveAgent() accepts optional targetName param; uses it when provided

          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:

          1. No --target or --all-targets flag provided
          2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
          3. 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

          CommandChange TypeDescriptionBackwards Compatible
          agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
          agentcore statusnew_flagAdd --all-targets and --parallelYes
          agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
          agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
          agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
          agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

          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:

          ScenarioMessage
          --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
          --all-targets with empty aws-targets.jsonNo 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

          FilePurpose
          src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

          Modified Files

          FileChange
          src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
          src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
          src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
          src/cli/commands/status/command.tsxAdd --all-targets, --parallel
          src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
          src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
          src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
          src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
          src/cli/commands/logs/action.tsPass targetName to resolveAgent()
          src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
          src/cli/commands/traces/action.tsPass targetName to resolveAgent()
          src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
          src/cli/commands/fetch-access/action.tsThread 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

          #DecisionChoiceRationale
          1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
          2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
          3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
          4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
          5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
          6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

          Implementation Phases

          Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

          • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
          • When targetName provided: use it to look up state; error clearly if not found
          • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
          • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
          • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
          • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

          Phase 2: Add --target to logs, traces, fetch access (independently shippable)

          • Add --target <name> to logs command (command.tsx + pass to action.ts)
          • Add --target <name> to traces command (command.tsx + pass to action.ts)
          • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
          • Thread targetName through to resolveAgent() in all three action files
          • Unit tests: logs with explicit target, traces with explicit target, invalid target error
          • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

          Phase 3: Add --all-targets and --parallel (independently shippable)

          • Write runForAllTargets() in src/cli/operations/multi-target.ts
            • Reads resolveAWSDeploymentTargets() to get ordered target list
            • Accepts action function, options, and parallel: boolean
            • Sequential: for-await loop; Parallel: Promise.allSettled
            • Returns per-target results with success/failure and output
          • Add --all-targets and --parallel flags to all six commands
          • Implement mutual exclusivity validation (--target + --all-targets → error)
          • Implement --parallel without --all-targets → error
          • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
          • Aggregate exit code: non-zero if any target fails
          • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
          • Ship note: After this lands, all multi-target workflows are available.

          Phase 4: Polish (independently shippable)

          • Update help text for all six commands to document new flags
          • Update docs/commands.md and docs/configuration.md
          • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
          • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
          • Ship note: Documentation and E2E coverage for the complete feature.

          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

          ScenarioNo 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/AN/A
          Invalid --target nameN/AError + listN/AN/A
          --target + --all-targetsN/AErrorN/AN/A
          --parallel without --all-targetsN/AErrorN/AN/A
          Partial failure (one target fails)N/AN/ANon-zero exitNon-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

          #QuestionForContext
          1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
          2For --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.
          3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
          4Does 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.tsconsttargetName=targetNames[0];// always first

          After:

          // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

          runForAllTargets() Sketch

          // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            enhancementNew feature or request

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
              Skip to content

              Feature Proposal: Multi-Target Operations #1187

              Description

              @notgitika

              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.tsxno --target flag; uses resolveAgent() → first target
              • src/cli/commands/logs/action.ts — logs business logic
              • src/cli/commands/traces/command.tsxno --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:

              CommandHas --target?DefaultNotes
              deployYes"default" (literal)Fails if no target named "default"
              statusYesFirst deployed targetMost sensible default
              invokeYes"default" (literal)Falls back to first deployed target
              logsNoFirst target via resolveAgent()Gap
              tracesNoFirst target via resolveAgent()Gap
              fetch accessNoN/AGap

              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

              DimensionExisting (single-target)New (multi-target)
              Triggered byNo flag (implicit)--target <name> or --all-targets
              Configurationaws-targets.json entry 0Explicit name or all entries
              Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
              State trackingdeployed-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:

              1. handleDeploy() receives options.target = "prod"
              2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
              3. Validates "prod" exists in targets — error with list of valid names if not
              4. Deploys CDK stack for prod target only
              5. Writes deployed-state.json at targets.prod

              What we build:

              • No new code for deploy — --target already exists; behavior is correct

              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:

              1. handleDeploy() receives options.allTargets = true
              2. Reads all entries from aws-targets.json
              3. Iterates sequentially (or in parallel if --parallel)
              4. Calls existing single-target deploy logic per entry
              5. Aggregates results; prints per-target status
              6. Exits non-zero if any target fails; reports which targets failed

              What we build:

              • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
              • --parallel flag (companion to --all-targets)
              • runForAllTargets() utility in src/cli/operations/multi-target.ts
              • Per-target result aggregation and exit code logic

              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:

              1. logs command receives options.targetName = "prod"
              2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
              3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
              4. CloudWatch logs fetched for the resolved runtime in the prod region

              What we build:

              • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
              • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
              • resolveAgent() accepts optional targetName param; uses it when provided

              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:

              1. No --target or --all-targets flag provided
              2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
              3. 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

              CommandChange TypeDescriptionBackwards Compatible
              agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
              agentcore statusnew_flagAdd --all-targets and --parallelYes
              agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
              agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
              agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
              agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

              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:

              ScenarioMessage
              --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
              --all-targets with empty aws-targets.jsonNo 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

              FilePurpose
              src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

              Modified Files

              FileChange
              src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
              src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
              src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
              src/cli/commands/status/command.tsxAdd --all-targets, --parallel
              src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
              src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
              src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
              src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
              src/cli/commands/logs/action.tsPass targetName to resolveAgent()
              src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
              src/cli/commands/traces/action.tsPass targetName to resolveAgent()
              src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
              src/cli/commands/fetch-access/action.tsThread 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

              #DecisionChoiceRationale
              1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
              2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
              3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
              4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
              5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
              6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

              Implementation Phases

              Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

              • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
              • When targetName provided: use it to look up state; error clearly if not found
              • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
              • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
              • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
              • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

              Phase 2: Add --target to logs, traces, fetch access (independently shippable)

              • Add --target <name> to logs command (command.tsx + pass to action.ts)
              • Add --target <name> to traces command (command.tsx + pass to action.ts)
              • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
              • Thread targetName through to resolveAgent() in all three action files
              • Unit tests: logs with explicit target, traces with explicit target, invalid target error
              • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

              Phase 3: Add --all-targets and --parallel (independently shippable)

              • Write runForAllTargets() in src/cli/operations/multi-target.ts
                • Reads resolveAWSDeploymentTargets() to get ordered target list
                • Accepts action function, options, and parallel: boolean
                • Sequential: for-await loop; Parallel: Promise.allSettled
                • Returns per-target results with success/failure and output
              • Add --all-targets and --parallel flags to all six commands
              • Implement mutual exclusivity validation (--target + --all-targets → error)
              • Implement --parallel without --all-targets → error
              • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
              • Aggregate exit code: non-zero if any target fails
              • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
              • Ship note: After this lands, all multi-target workflows are available.

              Phase 4: Polish (independently shippable)

              • Update help text for all six commands to document new flags
              • Update docs/commands.md and docs/configuration.md
              • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
              • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
              • Ship note: Documentation and E2E coverage for the complete feature.

              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

              ScenarioNo 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/AN/A
              Invalid --target nameN/AError + listN/AN/A
              --target + --all-targetsN/AErrorN/AN/A
              --parallel without --all-targetsN/AErrorN/AN/A
              Partial failure (one target fails)N/AN/ANon-zero exitNon-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

              #QuestionForContext
              1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
              2For --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.
              3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
              4Does 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.tsconsttargetName=targetNames[0];// always first

              After:

              // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

              runForAllTargets() Sketch

              // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                enhancementNew feature or request

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
                  Skip to content

                  Feature Proposal: Multi-Target Operations #1187

                  Description

                  @notgitika

                  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.tsxno --target flag; uses resolveAgent() → first target
                  • src/cli/commands/logs/action.ts — logs business logic
                  • src/cli/commands/traces/command.tsxno --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:

                  CommandHas --target?DefaultNotes
                  deployYes"default" (literal)Fails if no target named "default"
                  statusYesFirst deployed targetMost sensible default
                  invokeYes"default" (literal)Falls back to first deployed target
                  logsNoFirst target via resolveAgent()Gap
                  tracesNoFirst target via resolveAgent()Gap
                  fetch accessNoN/AGap

                  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

                  DimensionExisting (single-target)New (multi-target)
                  Triggered byNo flag (implicit)--target <name> or --all-targets
                  Configurationaws-targets.json entry 0Explicit name or all entries
                  Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
                  State trackingdeployed-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:

                  1. handleDeploy() receives options.target = "prod"
                  2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
                  3. Validates "prod" exists in targets — error with list of valid names if not
                  4. Deploys CDK stack for prod target only
                  5. Writes deployed-state.json at targets.prod

                  What we build:

                  • No new code for deploy — --target already exists; behavior is correct

                  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:

                  1. handleDeploy() receives options.allTargets = true
                  2. Reads all entries from aws-targets.json
                  3. Iterates sequentially (or in parallel if --parallel)
                  4. Calls existing single-target deploy logic per entry
                  5. Aggregates results; prints per-target status
                  6. Exits non-zero if any target fails; reports which targets failed

                  What we build:

                  • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
                  • --parallel flag (companion to --all-targets)
                  • runForAllTargets() utility in src/cli/operations/multi-target.ts
                  • Per-target result aggregation and exit code logic

                  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:

                  1. logs command receives options.targetName = "prod"
                  2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
                  3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
                  4. CloudWatch logs fetched for the resolved runtime in the prod region

                  What we build:

                  • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
                  • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
                  • resolveAgent() accepts optional targetName param; uses it when provided

                  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:

                  1. No --target or --all-targets flag provided
                  2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
                  3. 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

                  CommandChange TypeDescriptionBackwards Compatible
                  agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                  agentcore statusnew_flagAdd --all-targets and --parallelYes
                  agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                  agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
                  agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
                  agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

                  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:

                  ScenarioMessage
                  --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
                  --all-targets with empty aws-targets.jsonNo 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

                  FilePurpose
                  src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

                  Modified Files

                  FileChange
                  src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
                  src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
                  src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
                  src/cli/commands/status/command.tsxAdd --all-targets, --parallel
                  src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
                  src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
                  src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
                  src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
                  src/cli/commands/logs/action.tsPass targetName to resolveAgent()
                  src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
                  src/cli/commands/traces/action.tsPass targetName to resolveAgent()
                  src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
                  src/cli/commands/fetch-access/action.tsThread 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

                  #DecisionChoiceRationale
                  1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
                  2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
                  3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
                  4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
                  5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
                  6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

                  Implementation Phases

                  Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

                  • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
                  • When targetName provided: use it to look up state; error clearly if not found
                  • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
                  • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
                  • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
                  • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

                  Phase 2: Add --target to logs, traces, fetch access (independently shippable)

                  • Add --target <name> to logs command (command.tsx + pass to action.ts)
                  • Add --target <name> to traces command (command.tsx + pass to action.ts)
                  • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
                  • Thread targetName through to resolveAgent() in all three action files
                  • Unit tests: logs with explicit target, traces with explicit target, invalid target error
                  • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

                  Phase 3: Add --all-targets and --parallel (independently shippable)

                  • Write runForAllTargets() in src/cli/operations/multi-target.ts
                    • Reads resolveAWSDeploymentTargets() to get ordered target list
                    • Accepts action function, options, and parallel: boolean
                    • Sequential: for-await loop; Parallel: Promise.allSettled
                    • Returns per-target results with success/failure and output
                  • Add --all-targets and --parallel flags to all six commands
                  • Implement mutual exclusivity validation (--target + --all-targets → error)
                  • Implement --parallel without --all-targets → error
                  • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
                  • Aggregate exit code: non-zero if any target fails
                  • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
                  • Ship note: After this lands, all multi-target workflows are available.

                  Phase 4: Polish (independently shippable)

                  • Update help text for all six commands to document new flags
                  • Update docs/commands.md and docs/configuration.md
                  • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
                  • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
                  • Ship note: Documentation and E2E coverage for the complete feature.

                  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

                  ScenarioNo 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/AN/A
                  Invalid --target nameN/AError + listN/AN/A
                  --target + --all-targetsN/AErrorN/AN/A
                  --parallel without --all-targetsN/AErrorN/AN/A
                  Partial failure (one target fails)N/AN/ANon-zero exitNon-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

                  #QuestionForContext
                  1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
                  2For --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.
                  3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
                  4Does 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.tsconsttargetName=targetNames[0];// always first

                  After:

                  // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

                  runForAllTargets() Sketch

                  // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    enhancementNew feature or request

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
                      Skip to content

                      Feature Proposal: Multi-Target Operations #1187

                      Description

                      @notgitika

                      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.tsxno --target flag; uses resolveAgent() → first target
                      • src/cli/commands/logs/action.ts — logs business logic
                      • src/cli/commands/traces/command.tsxno --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:

                      CommandHas --target?DefaultNotes
                      deployYes"default" (literal)Fails if no target named "default"
                      statusYesFirst deployed targetMost sensible default
                      invokeYes"default" (literal)Falls back to first deployed target
                      logsNoFirst target via resolveAgent()Gap
                      tracesNoFirst target via resolveAgent()Gap
                      fetch accessNoN/AGap

                      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

                      DimensionExisting (single-target)New (multi-target)
                      Triggered byNo flag (implicit)--target <name> or --all-targets
                      Configurationaws-targets.json entry 0Explicit name or all entries
                      Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
                      State trackingdeployed-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:

                      1. handleDeploy() receives options.target = "prod"
                      2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
                      3. Validates "prod" exists in targets — error with list of valid names if not
                      4. Deploys CDK stack for prod target only
                      5. Writes deployed-state.json at targets.prod

                      What we build:

                      • No new code for deploy — --target already exists; behavior is correct

                      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:

                      1. handleDeploy() receives options.allTargets = true
                      2. Reads all entries from aws-targets.json
                      3. Iterates sequentially (or in parallel if --parallel)
                      4. Calls existing single-target deploy logic per entry
                      5. Aggregates results; prints per-target status
                      6. Exits non-zero if any target fails; reports which targets failed

                      What we build:

                      • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
                      • --parallel flag (companion to --all-targets)
                      • runForAllTargets() utility in src/cli/operations/multi-target.ts
                      • Per-target result aggregation and exit code logic

                      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:

                      1. logs command receives options.targetName = "prod"
                      2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
                      3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
                      4. CloudWatch logs fetched for the resolved runtime in the prod region

                      What we build:

                      • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
                      • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
                      • resolveAgent() accepts optional targetName param; uses it when provided

                      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:

                      1. No --target or --all-targets flag provided
                      2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
                      3. 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

                      CommandChange TypeDescriptionBackwards Compatible
                      agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                      agentcore statusnew_flagAdd --all-targets and --parallelYes
                      agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                      agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
                      agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
                      agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

                      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:

                      ScenarioMessage
                      --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
                      --all-targets with empty aws-targets.jsonNo 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

                      FilePurpose
                      src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

                      Modified Files

                      FileChange
                      src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
                      src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
                      src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
                      src/cli/commands/status/command.tsxAdd --all-targets, --parallel
                      src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
                      src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
                      src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
                      src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
                      src/cli/commands/logs/action.tsPass targetName to resolveAgent()
                      src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
                      src/cli/commands/traces/action.tsPass targetName to resolveAgent()
                      src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
                      src/cli/commands/fetch-access/action.tsThread 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

                      #DecisionChoiceRationale
                      1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
                      2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
                      3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
                      4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
                      5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
                      6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

                      Implementation Phases

                      Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

                      • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
                      • When targetName provided: use it to look up state; error clearly if not found
                      • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
                      • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
                      • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
                      • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

                      Phase 2: Add --target to logs, traces, fetch access (independently shippable)

                      • Add --target <name> to logs command (command.tsx + pass to action.ts)
                      • Add --target <name> to traces command (command.tsx + pass to action.ts)
                      • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
                      • Thread targetName through to resolveAgent() in all three action files
                      • Unit tests: logs with explicit target, traces with explicit target, invalid target error
                      • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

                      Phase 3: Add --all-targets and --parallel (independently shippable)

                      • Write runForAllTargets() in src/cli/operations/multi-target.ts
                        • Reads resolveAWSDeploymentTargets() to get ordered target list
                        • Accepts action function, options, and parallel: boolean
                        • Sequential: for-await loop; Parallel: Promise.allSettled
                        • Returns per-target results with success/failure and output
                      • Add --all-targets and --parallel flags to all six commands
                      • Implement mutual exclusivity validation (--target + --all-targets → error)
                      • Implement --parallel without --all-targets → error
                      • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
                      • Aggregate exit code: non-zero if any target fails
                      • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
                      • Ship note: After this lands, all multi-target workflows are available.

                      Phase 4: Polish (independently shippable)

                      • Update help text for all six commands to document new flags
                      • Update docs/commands.md and docs/configuration.md
                      • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
                      • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
                      • Ship note: Documentation and E2E coverage for the complete feature.

                      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

                      ScenarioNo 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/AN/A
                      Invalid --target nameN/AError + listN/AN/A
                      --target + --all-targetsN/AErrorN/AN/A
                      --parallel without --all-targetsN/AErrorN/AN/A
                      Partial failure (one target fails)N/AN/ANon-zero exitNon-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

                      #QuestionForContext
                      1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
                      2For --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.
                      3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
                      4Does 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.tsconsttargetName=targetNames[0];// always first

                      After:

                      // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

                      runForAllTargets() Sketch

                      // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        enhancementNew feature or request

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
                          Skip to content

                          Feature Proposal: Multi-Target Operations #1187

                          Description

                          @notgitika

                          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.tsxno --target flag; uses resolveAgent() → first target
                          • src/cli/commands/logs/action.ts — logs business logic
                          • src/cli/commands/traces/command.tsxno --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:

                          CommandHas --target?DefaultNotes
                          deployYes"default" (literal)Fails if no target named "default"
                          statusYesFirst deployed targetMost sensible default
                          invokeYes"default" (literal)Falls back to first deployed target
                          logsNoFirst target via resolveAgent()Gap
                          tracesNoFirst target via resolveAgent()Gap
                          fetch accessNoN/AGap

                          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

                          DimensionExisting (single-target)New (multi-target)
                          Triggered byNo flag (implicit)--target <name> or --all-targets
                          Configurationaws-targets.json entry 0Explicit name or all entries
                          Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
                          State trackingdeployed-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:

                          1. handleDeploy() receives options.target = "prod"
                          2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
                          3. Validates "prod" exists in targets — error with list of valid names if not
                          4. Deploys CDK stack for prod target only
                          5. Writes deployed-state.json at targets.prod

                          What we build:

                          • No new code for deploy — --target already exists; behavior is correct

                          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:

                          1. handleDeploy() receives options.allTargets = true
                          2. Reads all entries from aws-targets.json
                          3. Iterates sequentially (or in parallel if --parallel)
                          4. Calls existing single-target deploy logic per entry
                          5. Aggregates results; prints per-target status
                          6. Exits non-zero if any target fails; reports which targets failed

                          What we build:

                          • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
                          • --parallel flag (companion to --all-targets)
                          • runForAllTargets() utility in src/cli/operations/multi-target.ts
                          • Per-target result aggregation and exit code logic

                          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:

                          1. logs command receives options.targetName = "prod"
                          2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
                          3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
                          4. CloudWatch logs fetched for the resolved runtime in the prod region

                          What we build:

                          • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
                          • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
                          • resolveAgent() accepts optional targetName param; uses it when provided

                          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:

                          1. No --target or --all-targets flag provided
                          2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
                          3. 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

                          CommandChange TypeDescriptionBackwards Compatible
                          agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                          agentcore statusnew_flagAdd --all-targets and --parallelYes
                          agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                          agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
                          agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
                          agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

                          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:

                          ScenarioMessage
                          --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
                          --all-targets with empty aws-targets.jsonNo 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

                          FilePurpose
                          src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

                          Modified Files

                          FileChange
                          src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
                          src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
                          src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
                          src/cli/commands/status/command.tsxAdd --all-targets, --parallel
                          src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
                          src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
                          src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
                          src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
                          src/cli/commands/logs/action.tsPass targetName to resolveAgent()
                          src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
                          src/cli/commands/traces/action.tsPass targetName to resolveAgent()
                          src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
                          src/cli/commands/fetch-access/action.tsThread 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

                          #DecisionChoiceRationale
                          1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
                          2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
                          3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
                          4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
                          5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
                          6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

                          Implementation Phases

                          Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

                          • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
                          • When targetName provided: use it to look up state; error clearly if not found
                          • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
                          • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
                          • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
                          • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

                          Phase 2: Add --target to logs, traces, fetch access (independently shippable)

                          • Add --target <name> to logs command (command.tsx + pass to action.ts)
                          • Add --target <name> to traces command (command.tsx + pass to action.ts)
                          • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
                          • Thread targetName through to resolveAgent() in all three action files
                          • Unit tests: logs with explicit target, traces with explicit target, invalid target error
                          • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

                          Phase 3: Add --all-targets and --parallel (independently shippable)

                          • Write runForAllTargets() in src/cli/operations/multi-target.ts
                            • Reads resolveAWSDeploymentTargets() to get ordered target list
                            • Accepts action function, options, and parallel: boolean
                            • Sequential: for-await loop; Parallel: Promise.allSettled
                            • Returns per-target results with success/failure and output
                          • Add --all-targets and --parallel flags to all six commands
                          • Implement mutual exclusivity validation (--target + --all-targets → error)
                          • Implement --parallel without --all-targets → error
                          • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
                          • Aggregate exit code: non-zero if any target fails
                          • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
                          • Ship note: After this lands, all multi-target workflows are available.

                          Phase 4: Polish (independently shippable)

                          • Update help text for all six commands to document new flags
                          • Update docs/commands.md and docs/configuration.md
                          • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
                          • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
                          • Ship note: Documentation and E2E coverage for the complete feature.

                          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

                          ScenarioNo 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/AN/A
                          Invalid --target nameN/AError + listN/AN/A
                          --target + --all-targetsN/AErrorN/AN/A
                          --parallel without --all-targetsN/AErrorN/AN/A
                          Partial failure (one target fails)N/AN/ANon-zero exitNon-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

                          #QuestionForContext
                          1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
                          2For --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.
                          3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
                          4Does 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.tsconsttargetName=targetNames[0];// always first

                          After:

                          // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

                          runForAllTargets() Sketch

                          // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            enhancementNew feature or request

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Feature Proposal: Multi-Target Operations · Issue #1187 · aws/agentcore-cli · GitHub
                              Skip to content

                              Feature Proposal: Multi-Target Operations #1187

                              Description

                              @notgitika

                              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.tsxno --target flag; uses resolveAgent() → first target
                              • src/cli/commands/logs/action.ts — logs business logic
                              • src/cli/commands/traces/command.tsxno --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:

                              CommandHas --target?DefaultNotes
                              deployYes"default" (literal)Fails if no target named "default"
                              statusYesFirst deployed targetMost sensible default
                              invokeYes"default" (literal)Falls back to first deployed target
                              logsNoFirst target via resolveAgent()Gap
                              tracesNoFirst target via resolveAgent()Gap
                              fetch accessNoN/AGap

                              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

                              DimensionExisting (single-target)New (multi-target)
                              Triggered byNo flag (implicit)--target <name> or --all-targets
                              Configurationaws-targets.json entry 0Explicit name or all entries
                              Deploy mechanismCDK/imperative (unchanged)Same — dispatches per-target
                              State trackingdeployed-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:

                              1. handleDeploy() receives options.target = "prod"
                              2. resolveAWSDeploymentTargets() reads aws-targets.json — finds 3 entries
                              3. Validates "prod" exists in targets — error with list of valid names if not
                              4. Deploys CDK stack for prod target only
                              5. Writes deployed-state.json at targets.prod

                              What we build:

                              • No new code for deploy — --target already exists; behavior is correct

                              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:

                              1. handleDeploy() receives options.allTargets = true
                              2. Reads all entries from aws-targets.json
                              3. Iterates sequentially (or in parallel if --parallel)
                              4. Calls existing single-target deploy logic per entry
                              5. Aggregates results; prints per-target status
                              6. Exits non-zero if any target fails; reports which targets failed

                              What we build:

                              • --all-targets flag on deploy, status, invoke, logs, traces, fetch access
                              • --parallel flag (companion to --all-targets)
                              • runForAllTargets() utility in src/cli/operations/multi-target.ts
                              • Per-target result aggregation and exit code logic

                              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:

                              1. logs command receives options.targetName = "prod"
                              2. Passes targetName to resolveAgent() instead of relying on targetNames[0]
                              3. resolveAgent() uses the explicit targetName to look up state in deployed-state.json
                              4. CloudWatch logs fetched for the resolved runtime in the prod region

                              What we build:

                              • --target <name> flag on logs command (src/cli/commands/logs/command.tsx)
                              • --target <name> flag on traces command (src/cli/commands/traces/command.tsx)
                              • resolveAgent() accepts optional targetName param; uses it when provided

                              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:

                              1. No --target or --all-targets flag provided
                              2. Code reads aws-targets.json[0] as the target (same as today for status; fixes deploy/invoke default bug)
                              3. 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

                              CommandChange TypeDescriptionBackwards Compatible
                              agentcore deploynew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                              agentcore statusnew_flagAdd --all-targets and --parallelYes
                              agentcore invokenew_flagAdd --all-targets and --parallel; normalize default to first target (not "default")Yes
                              agentcore logsnew_flagAdd --target <name>, --all-targets, --parallelYes
                              agentcore tracesnew_flagAdd --target <name>, --all-targets, --parallelYes
                              agentcore fetch accessnew_flagAdd --target <name>, --all-targetsYes

                              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:

                              ScenarioMessage
                              --target name not in aws-targets.jsonTarget "prod" not found. Available targets: dev, staging (from aws-targets.json)
                              --all-targets with empty aws-targets.jsonNo 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

                              FilePurpose
                              src/cli/operations/multi-target.tsrunForAllTargets() utility — sequential/parallel loop with result aggregation

                              Modified Files

                              FileChange
                              src/cli/operations/resolve-agent.tsAccept optional targetName?: string param; use it instead of targetNames[0] when provided
                              src/cli/commands/deploy/command.tsxNormalize default: awsTargets[0].name instead of "default"; add --all-targets, --parallel
                              src/cli/commands/deploy/actions.tsThread target through multi-target loop when --all-targets
                              src/cli/commands/status/command.tsxAdd --all-targets, --parallel
                              src/cli/commands/status/action.tsThread target through multi-target loop when --all-targets
                              src/cli/commands/invoke/command.tsxNormalize default; add --all-targets, --parallel
                              src/cli/commands/invoke/action.tsThread target through multi-target loop; pass targetName to resolveAgent()
                              src/cli/commands/logs/command.tsxAdd --target <name>, --all-targets, --parallel
                              src/cli/commands/logs/action.tsPass targetName to resolveAgent()
                              src/cli/commands/traces/command.tsxAdd --target <name>, --all-targets, --parallel
                              src/cli/commands/traces/action.tsPass targetName to resolveAgent()
                              src/cli/commands/fetch-access/command.tsxAdd --target <name>, --all-targets
                              src/cli/commands/fetch-access/action.tsThread 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

                              #DecisionChoiceRationale
                              1Where to fix resolveAgent()Add optional targetName paramBackwards compatible — existing callers pass nothing, get current behavior
                              2Default target resolutionawsTargets[0].name (not "default")Matches status command's already-correct behavior; fixes latent bug in deploy/invoke
                              3--all-targets execution modelSequential default, --parallel opt-inSequential is safer for deploy (CDK limits, IAM propagation); parallel useful for status/logs
                              4Multi-target utility locationsrc/cli/operations/multi-target.tsConsistent with existing operations directory; keeps command files thin
                              5--target + --all-targets mutual exclusivityError at flag parse timeUnambiguous intent; avoids silent override of one by the other
                              6Schema changesNonedeployed-state.json and aws-targets.json already model multi-target correctly

                              Implementation Phases

                              Phase 1: Fix resolveAgent() and normalize defaults (independently shippable)

                              • Add targetName?: string param to resolveAgent() in src/cli/operations/resolve-agent.ts
                              • When targetName provided: use it to look up state; error clearly if not found
                              • When targetName absent: preserve current behavior (targetNames[0]) — no behavior change
                              • Normalize deploy and invoke default: replace "default" literal with awsTargets[0].name
                              • Unit tests: resolveAgent with explicit target, resolveAgent with absent target, resolveAgent default
                              • Ship note: After this lands, zero behavior change for single-target projects. Bug fix for projects whose first target isn't named "default".

                              Phase 2: Add --target to logs, traces, fetch access (independently shippable)

                              • Add --target <name> to logs command (command.tsx + pass to action.ts)
                              • Add --target <name> to traces command (command.tsx + pass to action.ts)
                              • Add --target <name> to fetch access command (command.tsx + pass to action.ts)
                              • Thread targetName through to resolveAgent() in all three action files
                              • Unit tests: logs with explicit target, traces with explicit target, invalid target error
                              • Ship note: After this lands, logs, traces, and fetch access behave consistently with deploy, status, invoke.

                              Phase 3: Add --all-targets and --parallel (independently shippable)

                              • Write runForAllTargets() in src/cli/operations/multi-target.ts
                                • Reads resolveAWSDeploymentTargets() to get ordered target list
                                • Accepts action function, options, and parallel: boolean
                                • Sequential: for-await loop; Parallel: Promise.allSettled
                                • Returns per-target results with success/failure and output
                              • Add --all-targets and --parallel flags to all six commands
                              • Implement mutual exclusivity validation (--target + --all-targets → error)
                              • Implement --parallel without --all-targets → error
                              • Per-target progress output: [1/3] Deploying to dev... ✓ (12s)
                              • Aggregate exit code: non-zero if any target fails
                              • Unit tests: sequential loop, parallel loop, partial failure, mutual exclusivity errors
                              • Ship note: After this lands, all multi-target workflows are available.

                              Phase 4: Polish (independently shippable)

                              • Update help text for all six commands to document new flags
                              • Update docs/commands.md and docs/configuration.md
                              • Add E2E test: deploy to two targets in sequence; verify both appear in deployed-state.json
                              • Edge cases: --all-targets with one target (degenerate case — should work identically to --target first)
                              • Ship note: Documentation and E2E coverage for the complete feature.

                              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

                              ScenarioNo 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/AN/A
                              Invalid --target nameN/AError + listN/AN/A
                              --target + --all-targetsN/AErrorN/AN/A
                              --parallel without --all-targetsN/AErrorN/AN/A
                              Partial failure (one target fails)N/AN/ANon-zero exitNon-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

                              #QuestionForContext
                              1Should --all-targets on invoke be supported? It's operationally unusual to invoke an agent on every target at once.gitikavj@ / PMIncluded in scope per inputs, but worth a deliberate decision. Power-user edge case only.
                              2For --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.
                              3Should failed targets in --all-targets be retried, or fail-fast?gitikavj@ / EngCurrent proposal: no retry, non-zero exit with which targets failed.
                              4Does 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.tsconsttargetName=targetNames[0];// always first

                              After:

                              // src/cli/operations/resolve-agent.tsfunctionresolveAgent(context: DeployedProjectConfig,options: {runtime?: string;targetName?: string}// targetName added){consttargetName=options.targetName??targetNames[0];// explicit wins// ... rest unchanged}

                              runForAllTargets() Sketch

                              // src/cli/operations/multi-target.tsexportasyncfunctionrunForAllTargets<T>(targets: AwsDeploymentTarget[],action: (target: AwsDeploymentTarget)=>Promise<T>,options: {parallel?: boolean}): Promise<Array<{target: string;result: T|Error}>>{if(options.parallel){constresults=awaitPromise.allSettled(targets.map(action));returntargets.map((t,i)=>({target: t.name,result: results[i].status==='fulfilled' ? results[i].value : results[i].reason,}));}constresults=[];for(consttargetoftargets){try{results.push({target: target.name,result: awaitaction(target)});}catch(err){results.push({target: target.name,result: errasError});}}returnresults;}

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                enhancementNew feature or request

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions