Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a5d8f41
feat(cdk): switch the Bedrock inference profile to the global geo
isadeks Aug 26, 2026
8e39535
fix(cli): strip every geo prefix in the doctor Bedrock check
isadeks Aug 27, 2026
c629e1e
fix(cli): check the inference profile doctor will invoke, and reject …
isadeks Aug 27, 2026
a2550be
fix(agent): inject both model env vars from the resolved geography
isadeks Aug 27, 2026
b51712c
fix(cdk): clear a dropped model override, widen the drift guard, corr…
isadeks Aug 27, 2026
6759649
fix(cdk): wire the third substrate, guard the headline fix, revert a …
isadeks Aug 27, 2026
bc66ce1
Merge main, converging the geo helper and delivering the main model t…
isadeks Aug 28, 2026
6cd8f08
fix(bedrock): drop the profile-less granted model, reject wildcard gr…
isadeks Aug 28, 2026
c941454
docs(onboard-repo): correct the model-grant command, which cannot wor…
isadeks Aug 28, 2026
b016d3e
fix(microvm): deliver the main model to the MicroVM guest, not just t…
isadeks Aug 31, 2026
e449c7e
test(cdk): fail the build when the template approaches CloudFormation…
isadeks Aug 31, 2026
e714ee8
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Aug 31, 2026
7d86c11
test(cdk): state the template budget's real justification
isadeks Aug 31, 2026
c0ed2ee
fix: address review — unbundle the CDK from runtime Lambdas, stop adv…
isadeks Aug 31, 2026
feec350
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Sep 1, 2026
c2d6dc0
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Sep 1, 2026
15c97c2
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Sep 1, 2026
cb9d459
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Sep 2, 2026
e6464b0
fix: correct the geography guidance, measure the substrate that deplo…
isadeks Sep 2, 2026
bed9f2c
Merge branch 'main' into fix/804-805-doctor-and-model-validation
isadeks Sep 3, 2026
f6abd06
fix: restore six regression tests I deleted, and guard the platform d…
isadeks Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ The `run.sh` script overrides the container's default CMD to run `python /app/sr
| `AWS_SECRET_ACCESS_KEY` | Conditional† | | Explicit keys, if you are not using CLI-based resolution |
| `AWS_SESSION_TOKEN` | No | | For temporary credentials |
| `AWS_PROFILE` | No | | Profile for `aws configure export-credentials` in `run.sh`, or default profile when using the `~/.aws` mount fallback |
| `ANTHROPIC_MODEL` | No | `us.anthropic.claude-opus-5` | Bedrock **inference profile** ID for `InvokeModel` (see [inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-use.html)). Must be the `us.`-prefixed profile ID, not a bare foundation-model ID — see [Model configuration](../docs/guides/DEVELOPER_GUIDE.md#model-configuration) |
| `ANTHROPIC_MODEL` | No | `global.anthropic.claude-opus-5` | Bedrock **inference profile** ID for `InvokeModel` (see [inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-use.html)). Must be a geo-prefixed profile ID matching the deployment's `bedrockGeoRegion`, not a bare foundation-model ID — see [Model configuration](../docs/guides/DEVELOPER_GUIDE.md#model-configuration) |
| `MAX_TURNS` | No | `100` | Max agent turns before stopping |
| `MAX_BUDGET_USD` | No | | **Local batch only** (shell env when running `entrypoint.py` directly). Range 0.01–100; agent stops when the budget is reached. For deployed AgentCore **server** mode and production tasks, set **`max_budget_usd`** on task creation (REST API, CLI `--max-budget`, or Blueprint default); the orchestrator sends it in the `/invocations` JSON body — server mode does not read `MAX_BUDGET_USD` from the environment. |
| `DRY_RUN` | No | | Set to `1` to validate config and print the prompt without running the agent |
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | No | `us.anthropic.claude-haiku-4-5-20251001-v1:0` | Bedrock **inference profile** ID for the small/fast auxiliary model — the pre-flight safety check and WebFetch summarization (see below). Set by the CDK stack (`cdk/src/stacks/agent.ts` (the runtime environment block)); the `us.` prefix is required |
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | No | `global.anthropic.claude-haiku-4-5-20251001-v1:0` | Bedrock **inference profile** ID for the small/fast auxiliary model — the pre-flight safety check and WebFetch summarization (see below). Set by the CDK stack (`cdk/src/stacks/agent.ts` (the runtime environment block)), which derives the prefix from `bedrockGeoRegion` — a geo prefix is required |
| `NUDGES_TABLE_NAME` | No | | **Phase 2.** DynamoDB table for mid-task user nudges (`<user_nudge>` XML blocks injected between turns). If unset, the agent runs without nudge support — `nudge_reader.read_pending()` returns `[]` and logs a WARN once. Set automatically by the CDK stack on both AgentCore runtimes. |
| `JIRA_APP_ACTOR_PROXY_URL` | No | | Resolved per-task from the Jira tenant secret. Forge v2 web-trigger URL used for app-authored Jira comments and transitions. |
| `JIRA_APP_ACTOR_SHARED_SECRET` | No | | Resolved per-task from the Jira tenant secret. HMAC key for the Forge proxy; redacted from agent diagnostics. |
Expand All @@ -133,7 +133,7 @@ The `run.sh` script overrides the container's default CMD to run `python /app/sr
including non-Jira tasks, so a warm AgentCore process cannot expose one
tenant's OAuth or Forge credential to the next task.

**Bedrock model access (main model):** Configuring `ANTHROPIC_MODEL` and IAM credentials is not enough. Your AWS account must be able to **invoke** that model in Amazon Bedrock: follow [Request access to models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) (Marketplace permissions on first use, Anthropic first-time use where required, valid payment method for Marketplace-backed models). Always use an inference profile ID such as `us.anthropic.claude-opus-5`: a bare foundation-model ID cannot be invoked with on-demand throughput and Bedrock rejects it with `ValidationException`. IAM must also grant the model — see [Model configuration](../docs/guides/DEVELOPER_GUIDE.md#model-configuration) for the full layering. If the CLI stops with a message that the model is not available on your Bedrock deployment, fix model access in the console or switch `ANTHROPIC_MODEL` to an entitled profile, then retry.
**Bedrock model access (main model):** Configuring `ANTHROPIC_MODEL` and IAM credentials is not enough. Your AWS account must be able to **invoke** that model in Amazon Bedrock: follow [Request access to models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) (Marketplace permissions on first use, Anthropic first-time use where required, valid payment method for Marketplace-backed models). Always use an inference profile ID such as `global.anthropic.claude-opus-5`: a bare foundation-model ID cannot be invoked with on-demand throughput and Bedrock rejects it with `ValidationException`. IAM must also grant the model — see [Model configuration](../docs/guides/DEVELOPER_GUIDE.md#model-configuration) for the full layering. If the CLI stops with a message that the model is not available on your Bedrock deployment, fix model access in the console or switch `ANTHROPIC_MODEL` to an entitled profile, then retry.

**Pre-flight check model**: Claude Code runs a quick safety verification using a small Haiku model before executing each tool command. On Bedrock, the default Haiku model ID may not be enabled in your account, causing the check to time out with *"Pre-flight check is taking longer than expected"* warnings. The agent sets `ANTHROPIC_DEFAULT_HAIKU_MODEL` to a known-available Bedrock Haiku model ID to avoid this. If you see pre-flight timeout warnings, verify that this model is enabled in your Bedrock model access settings.

Expand All @@ -145,8 +145,8 @@ tenant's OAuth or Forge credential to the next task.
# Dry run — validate config, fetch issue, print assembled prompt, then exit
DRY_RUN=1 ./agent/run.sh "owner/repo" 42

# Run with a specific model (overrides the us.anthropic.claude-opus-5 default).
# Must be a `us.`-prefixed inference profile that IAM grants — see Model configuration.
# Run with a specific model (overrides the global.anthropic.claude-opus-5 default).
# Must be a geo-prefixed inference profile that IAM grants — see Model configuration.
ANTHROPIC_MODEL="us.anthropic.claude-sonnet-4-6" ./agent/run.sh "owner/repo" 42

# Limit agent to 50 turns
Expand Down Expand Up @@ -431,7 +431,7 @@ docker images bgagent-local --format "{{.Size}}"

```
agent/
├── Dockerfile Python 3.13 + Node.js 20 + Claude Code CLI + git + gh + mise (default platform linux/arm64)
├── Dockerfile Python 3.13 + Node.js 24 + Claude Code CLI + git + gh + mise (default platform linux/arm64)
├── .dockerignore
├── pyproject.toml App dependencies (claude-agent-sdk, FastAPI, boto3, OpenTelemetry distro, MCP, cedarpy, …)
├── uv.lock Locked deps for reproducible `uv sync` in the image
Expand Down
42 changes: 36 additions & 6 deletions agent/scripts/diagnostics/test_sdk_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,24 @@ async def smoke_test():
# Ensure required env vars
os.environ.setdefault("CLAUDE_CODE_USE_BEDROCK", "1")
region = os.environ.get("AWS_REGION", "")
model = os.environ.get("ANTHROPIC_MODEL", "us.anthropic.claude-sonnet-4-6")
# No hardcoded fallback: this file drifted to a model and geography the platform
# no longer defaults to, so the diagnostic silently tested something other than
# what runs. Require the caller to state it — a diagnostic that quietly probes the
# wrong model is worse than one that refuses.
model = os.environ.get("ANTHROPIC_MODEL", "")

if not region:
print("ERROR: AWS_REGION not set", file=sys.stderr)
sys.exit(1)

if not model:
print(
"ERROR: ANTHROPIC_MODEL not set. Pass the geo-prefixed inference-profile "
"id the deployment uses, e.g. ANTHROPIC_MODEL=global.anthropic.claude-opus-5",
file=sys.stderr,
)
sys.exit(1)

print(f"Region: {region}")
print(f"Model: {model}")
print(f"Python: {sys.version}")
Expand Down Expand Up @@ -108,17 +120,19 @@ def on_stderr(line: str):
print(f"Duration: {elapsed:.1f}s")
print(f"Counts: {counts}")

if counts["assistant"] > 0 and counts["result"] > 0:
print("\nPASS — SDK yields messages. Issue is specific to the")
print(" server threading context, not SDK/CLI/Bedrock.")
ok = counts["assistant"] > 0 and counts["result"] > 0
if ok:
print("\nPASS — SDK yields messages for this model in this Region.")
print(" Rules OUT the SDK/CLI/Bedrock path. It does not identify the")
print(" cause of any other failure — threading is one candidate, not a")
print(" conclusion this test can reach.")
elif counts["system"] > 0 and counts["assistant"] == 0:
print("\nFAIL — Got init but zero AssistantMessages.")
print(" Same symptom as production. Issue is SDK/CLI level,")
print(" NOT threading. Check:")
print(" 1. CLI stderr output above for errors")
print(" 2. Bedrock model availability / permissions")
print(" 3. SDK ↔ CLI version compatibility")
print(" SDK: claude-agent-sdk==0.1.43")
try:
import importlib.metadata

Expand All @@ -129,9 +143,25 @@ def on_stderr(line: str):
elif counts["system"] == 0:
print("\nFAIL — Zero messages at all. CLI subprocess may not start.")
print(" Check: is claude-code installed? Run: claude --version")
else:
# Reachable: assistant>0 but result==0 (the model spoke, the run never
# terminated cleanly). Without this branch the script printed NO verdict at
# all and still exited 1 — a diagnostic that fails silently is the thing this
# file exists to rule out.
print("\nINDETERMINATE — Got AssistantMessages but no ResultMessage.")
print(" The model responded, so the SDK/CLI/Bedrock path works, but the")
print(" run did not terminate normally. Treat as a FAILURE of this")
print(" diagnostic, not evidence about the model. Check the CLI stderr")
print(" above for a mid-stream error, and whether the run was killed")
print(" (timeout, OOM, cancelled) before it could emit its result.")
if errors:
print(f"\nErrors: {errors}")

# Exit code must match the verdict. It printed FAIL and exited 0, so any caller
# that checked the status — CI, a script, a person using `&&` — read a failure as
# success. A diagnostic that lies in its exit code is worse than no diagnostic.
return ok


if __name__ == "__main__":
asyncio.run(smoke_test())
sys.exit(0 if asyncio.run(smoke_test()) else 1)
11 changes: 8 additions & 3 deletions agent/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,13 +560,18 @@ def build_config(
resolved_github_token = github_token or resolve_github_token()
resolved_aws_region = aws_region or os.environ.get("AWS_REGION", "")
resolved_anthropic_model = anthropic_model or os.environ.get(
"ANTHROPIC_MODEL", "us.anthropic.claude-opus-5"
"ANTHROPIC_MODEL", "global.anthropic.claude-opus-5"
)
# Small/fast auxiliary model (WebFetch summarization etc.). Falls back to the
# deployed ANTHROPIC_DEFAULT_HAIKU_MODEL env, then the platform default. Must
# be an inference-profile id (us.*), not a bare model id (see runner).
# be a geo-prefixed inference-profile id, not a bare model id (see runner).
#
# This fallback is a SEPARATE value from the env var the stack injects, which
# derives its prefix from the bedrockGeoRegion context key. Only a run with no
# env set at all reaches this literal, so the two must be moved together or a
# local run silently uses a different geography than a deployed one.
resolved_haiku_model = haiku_model or os.environ.get(
"ANTHROPIC_DEFAULT_HAIKU_MODEL", "us.anthropic.claude-haiku-4-5-20251001-v1:0"
"ANTHROPIC_DEFAULT_HAIKU_MODEL", "global.anthropic.claude-haiku-4-5-20251001-v1:0"
)

# Resolve the workflow id (the create-task boundary already pinned it; local
Expand Down
11 changes: 8 additions & 3 deletions agent/src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,17 @@ class TaskConfig(BaseModel):
task_description: str = ""
github_token: str = ""
aws_region: str
anthropic_model: str = "us.anthropic.claude-opus-5"
anthropic_model: str = "global.anthropic.claude-opus-5"
# The "small/fast" model Claude Code uses for auxiliary work (e.g. WebFetch
# page summarization). Must be a cross-region INFERENCE-PROFILE id (``us.``
# page summarization). Must be a cross-region INFERENCE-PROFILE id (geo
# prefix), not a bare foundation-model id — Claude 4.x cannot be invoked
# on-demand by bare id on Bedrock. Threaded to ANTHROPIC_DEFAULT_HAIKU_MODEL.
haiku_model: str = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
#
# A deployed task never reaches this default: the stack injects both model env
# vars from its resolved bedrockGeoRegion. It applies to direct construction
# (tests, local runs), so it must name the same geography as the deployment or
# those paths silently exercise a different one.
haiku_model: str = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
dry_run: bool = False
max_turns: int = 10
max_budget_usd: float | None = None
Expand Down
8 changes: 5 additions & 3 deletions agent/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ def test_default_workflow_when_omitted(self):
assert config.is_pr_workflow is False

def test_haiku_model_defaults_to_inference_profile(self, monkeypatch):
# No override, no env → platform default, which must be a us.* inference
# profile (Claude 4.x can't be invoked on-demand by bare model id).
# No override, no env → platform default, which must be a GEO-PREFIXED
# inference profile (Claude 4.x can't be invoked on-demand by bare model
# id). The geography must match the deployment's bedrockGeoRegion, or a run
# with no env set calls a profile the IAM grant does not cover.
monkeypatch.delenv("ANTHROPIC_DEFAULT_HAIKU_MODEL", raising=False)
config = build_config(
repo_url="owner/repo",
task_description="fix bug",
github_token="ghp_test123",
aws_region="us-east-1",
)
assert config.haiku_model == "us.anthropic.claude-haiku-4-5-20251001-v1:0"
assert config.haiku_model == "global.anthropic.claude-haiku-4-5-20251001-v1:0"

def test_haiku_model_resolves_from_env(self, monkeypatch):
# The deployed ANTHROPIC_DEFAULT_HAIKU_MODEL (set by agent.ts) flows
Expand Down
11 changes: 8 additions & 3 deletions agent/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,14 @@ def test_haiku_model_env_is_set_from_config(self, monkeypatch):
)

def test_config_default_haiku_model_is_an_inference_profile(self):
# The platform default (no override) must be a us.* profile, never a bare
# foundation-model id — the whole point of the fix.
assert _config().haiku_model.startswith("us.")
# The platform default (no override) must be a GEO-PREFIXED profile, never a
# bare foundation-model id — Claude 4.x cannot be invoked on-demand by bare
# id. Asserted as "has a geo prefix" rather than "starts with us.": the
# geography is a deploy-time choice (bedrockGeoRegion), so pinning one here
# would fail the moment the platform default moves, which tells us nothing
# about the property that matters.
geos = ("global.", "us.", "us-gov.", "eu.", "apac.", "jp.", "au.")
assert _config().haiku_model.startswith(geos)


class TestClaudeCliVersionProbe:
Expand Down
7 changes: 7 additions & 0 deletions agent/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,13 @@ def test_wire_contract_is_exactly_the_documented_key_set(self):
"agent_session_role_arn": "AGENT_SESSION_ROLE_ARN",
"aws_sdk_ua_app_id": "AWS_SDK_UA_APP_ID",
"anthropic_default_haiku_model": "ANTHROPIC_DEFAULT_HAIKU_MODEL",
# The MAIN model. Absent until now, and silently: the AgentCore runtime
# and the ECS task definitions inject it into their own env, so only
# MicroVM depends on this transport — and its fallback in config.py is a
# ``global.``-prefixed literal, so a missing value was not an error but a
# wrong model that the IAM grant does not cover on any non-``global``
# deployment, surfacing as AccessDenied at turn 0.
"anthropic_model": "ANTHROPIC_MODEL",
}

def test_required_subset_is_exactly_the_four_run_blocking_keys(self):
Expand Down
3 changes: 3 additions & 0 deletions cdk/cdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@
"yarn.lock",
"node_modules"
]
},
"context": {
"bedrockGeoRegion": "global"
}
}
Loading
Loading