main
102 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
695e6af5b3 |
docs(evaluate): add HR Assistant architecture diagram (#2003)
Add hr_agent_arch.png showing the HR Assistant's skills, tools, Bedrock model call, and the ADOT -> CloudWatch -> AgentCore Evaluations flow, and point the Agent Architecture section at it instead of the generic agent_architecture.png. Co-authored-by: Visakh Madathil <visakhm@amazon.com> |
||
|
|
33c0e568a9 | chore(memory): cleanup old usage of namespaces (#2005) | ||
|
|
aa87fa0097 |
fix(mcp-hosting): make the MCP hosting samples deployable and stop them reporting false success (#2004)
* fix(mcp-hosting): pin the MCP SDK below 2.0 so the samples can deploy
Both samples pinned `mcp>=1.10.0` with no upper bound. mcp 2.0.0 renamed
FastMCP to MCPServer and removed mcp.server.fastmcp with no compatibility
alias, so a fresh install resolves to 2.x and the import in mcp_server.py
raises ModuleNotFoundError.
This breaks the samples twice over. Locally, the first Quick Start command
fails. Once deployed it is worse: AgentCore does not execute the entry point
at CreateAgentRuntime, so the runtime reports READY and only returns
"Runtime initialization time exceeded" when it is invoked.
Split the server's own dependency out of requirements.txt so the pin has a
single home and only what the server imports gets vendored into the zip.
boto3 is used by deploy.py/invoke.py/cleanup.py on the developer's machine
and never by mcp_server.py, so leaving it out takes the artifact from
24.8 MB to 8.8 MB. The file is named requirements-server.txt to stay inside
the `**/requirements*.txt` glob that ash-security-scan.yml uses to pick up
dependency manifests.
The sibling 03-mcp-ec2-capacity-provider already carries this pin and the
reason for it; this brings the other two samples in line.
* fix(mcp-hosting): report deploy and cleanup failures instead of success
The samples reported success whether or not they worked, which is what made
the dependency bug in the previous commit invisible.
deploy.py never called the thing it deployed. AgentCore does not run the
entry point at CreateAgentRuntime, so a server that crashes on import still
reaches READY and the script printed "Deployment complete". It now ends with
a tools/list smoke test and exits non-zero with the runtime's own error and
the log group holding the traceback.
invoke.py read `result.get("result", {})`. A JSON-RPC error arrives with
HTTP 200, so boto3 does not raise and every failure rendered as an empty
success with exit 0 — including the "Runtime initialization time exceeded"
that a crashed server returns for every call. It now raises on the error
envelope, checks isError separately (a failed tool returns a *successful*
response), and exits non-zero.
cleanup.py printed "Cleanup complete" while leaving a live runtime. Deleting
the service-managed DEFAULT endpoint raises ConflictException, which aborted
the loop and skipped the wait, so delete_agent_runtime then failed on a
still-DELETING endpoint. It now skips DEFAULT, waits for any customer
endpoint to actually disappear, waits for the runtime itself, and on failure
reports what survived, keeps runtime_config.json and exits non-zero.
Also in deploy.py: write runtime_config.json as soon as the runtime exists,
so a later failure is still cleanable; guard a missing region, which
previously surfaced as a LocationConstraint ParamValidationError after the
IAM role had been created; handle ConflictException on re-run rather than
raising a traceback; bound the status poll; drop the redundant endpoint,
since DEFAULT is provisioned with the runtime and is what a qualifier-less
invoke reaches; and grant the X-Ray and cloudwatch:PutMetricData permissions
from the documented execution role — with logging alone the runtime serves
traffic but emits no traces and no metrics while still creating an empty
spans stream.
In mcp_server.py, keep tool docstrings to one line: FastMCP publishes the
whole docstring as the tool description, so Args:/Returns: blocks were sent
to every client on every tools/list. Constrain the closed-set arguments so
the published schema states what is valid instead of silently accepting
anything, and declare the JSON mime type on the resources in 02.
* docs(mcp-hosting): remove the app.mcp_app pattern and fix the invoke examples
Step 1 of the basics README and the parent README both instructed
`app = BedrockAgentCoreApp(); app.mcp_app = mcp; app.run()`, described as the
key difference from hosting an agent. That attribute does not exist in any
released version of bedrock-agentcore. Assigning it succeeds silently, so the
documented program runs and quietly does the wrong thing: it serves
/invocations on 127.0.0.1:8080 and has no /mcp route at all, which is why the
shipped mcp_server.py never used it.
Both READMEs now show the real file and say plainly that an MCP server is not
a BedrockAgentCoreApp — it is a FastMCP server run with the streamable-HTTP
transport, and bedrock-agentcore is not a dependency of these samples. A table
separates the four things the runtime actually requires (port 8000, host
0.0.0.0, stateless_http, path /mcp) from json_response, which is the sample's
own choice and carries a trade-off worth stating.
Other corrections, each checked against a real deployment:
- The Quick Start curl returned 406; it was missing the Accept header that
streamable HTTP requires. It is also split across two terminals now, since
mcp_server.py blocks and every later line in the block was unreachable.
- The Step 3 invoke snippets omitted contentType and accept, which fails 406
at the API, and none of them checked for the error envelope.
- agentRuntimeName was hyphenated. The service rejects that against
[a-zA-Z][a-zA-Z0-9_]{0,47}.
- The Files table listed a bedrock-agentcore dependency that is neither
present nor needed.
- The parent README's stateless-transport note implied Mcp-Session-Id gives a
stateful server process affinity, which it does not.
Document the pieces that were missing: a Prerequisites section, the fact that
AgentCore has no default region, that no endpoint needs creating because
DEFAULT is provisioned with the runtime, that a READY runtime is not a working
one, and the undocumented 33-character minimum on runtimeSessionId.
02's README claimed to demonstrate sampling. Its server never implemented it,
and it cannot while json_response=True drops server-initiated requests. The
claim is replaced with the reason.
* style(mcp-hosting): silence BLE001 on the deliberate broad excepts
The python-lint workflow runs ruff over the files a PR changes, so BLE001
fires on these even though the same pattern is widespread in files no PR has
touched.
The broad catch is the point in both places. cleanup.py has to attempt every
teardown step and report whatever failed rather than abort on the first error
or claim success — narrowing these would turn an unexpected failure into a
traceback instead of the "Cleanup INCOMPLETE" summary the previous commit
added. deploy.py's smoke test retries transient errors and reports whatever
is left after the last attempt.
The repo already ignores E722 for this reason ("acceptable in demo/sample
scripts where broad exception handling improves readability for tutorial
purposes"); BLE001 is the same judgement, so suppress it at the call sites
rather than change behaviour to satisfy the linter.
|
||
|
|
f5f1a1c2a7 |
feat(skills-evaluation): add Strands SkillInvoked evaluator and inner-loop script (#1995)
* feat(skills-evaluation): add deterministic Strands.SkillInvoked evaluator and CLI docs Adds a third skill evaluator alongside the two AgentCore built-ins: the deterministic Strands.SkillInvoked check from the strands-agents-evals SDK. evaluate.py rebuilds a Strands Evals trajectory from the session's CloudWatch spans and runs SkillInvoked client-side for every session, including the no-skill control that the LLM built-ins skip. - evaluate.py: reconstruct trajectory from spans, run SkillInvoked per session, validate expected scores, fail-fast if strands_evals is missing - requirements.txt: add strands-agents-evals (imported as strands_evals) - README.md: document the third evaluator and mirror the parent AgentCore CLI evals section * style(skills-evaluation): satisfy ruff lint and format * feat(skills-evaluation): add inner-loop Strands Evals script Add inner_loop_eval.py: reruns the skill-equipped HR Assistant in process, captures the trajectory with TracedHandler, and scores it with the Strands-native SkillSelectionAccuracyEvaluator, SkillInstructionFollowingEvaluator, and the deterministic SkillInvoked — no deployed runtime, only Bedrock model access. This is the development/CI counterpart to evaluate.py, which evaluates a deployed runtime from CloudWatch spans. - includes a --validate-extraction preflight (parse_available_skills / extract_selected_skills) to confirm the trace format is recognized - README: document inner-loop vs outer-loop and note the default judge model * docs(evaluate): update evaluation architecture diagram Remove Nova Lite from the AgentCore Runtime box and update the Evaluators box to reflect 15 built-in evaluators (matching the 02-evaluate README). * docs(evaluate): rename Starter Toolkit to AgentCore CLI in interfaces diagram Update the interfaces pyramid's top tier from 'AgentCore Starter Toolkit' to 'AgentCore CLI' to match current tooling naming. --------- Co-authored-by: Visakh Madathil <visakhm@amazon.com> |
||
|
|
19bba0494f |
feat(memory): add json payload types (#1994)
* feat(memory): add json payload types * update readme * feat(memory): add payload types * fix(memory): satisfy ruff C408 and secret scan in STM samples Rewrite the two `dict()` kwargs builders as literals (ruff C408) and mark the sample base64 PDF blob with the repo's `# pragma: allowlist secret` so detect-secrets stops flagging it as a Base64 high-entropy string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Signed-off-by: Akarsha Sehwag <akshseh@amazon.de> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
067c65be52 |
feat(memory): update checkpointer usage (#2001)
* feat(memory): update checkpointers * feat(memory): update docs * update docs * update reqs * fix ruff * ruff fix |
||
|
|
cff4a1e334 |
docs(policy): fix guardrails docstring drift and temporal test-table row (#1986)
* docs(policy): fix guardrails docstring drift and temporal test-table row - 02-guardrails-in-policy/deploy.py: the ApplicationTool free-text field is 'message' in the actual tool schema and all four guardrail policies scan context.input.message, but the module docstring, tool description, and a comment referred to a nonexistent 'customer_notes' field. Also the SSN policy is named 'block_ssn' in code but the docstring listed 'block_pii'. Aligned the docs with the code (no runtime change). - 03-temporal-policies/bankingassistant/README.md: Policy 1 test-table row 3 was self-contradictory (prompt said 'to ACC-2003', the to_account column said 'ACC-9999', and the looked-up account was ACC-2002). Made the prompt and column agree on ACC-9999 and named the looked-up account in the DENY reason so the walkthrough is coherent. * style(policy): fix pre-existing ruff findings in guardrails deploy.py The python-lint CI gate runs ruff on the full changed file, so editing deploy.py surfaced 4 pre-existing findings unrelated to the docstring fix: - RUF013 implicit Optional on get_aws_context(region, profile) -> str | None - S110/BLE001 blind except Exception: pass in add_lambda_gateway_permission -> narrowed to except ClientError with an explanatory comment (idempotent permission removal; the statement legitimately may not exist on first run). No behavior change. --------- Co-authored-by: rbelson@amazon.com <rbelson@amazon.com> |
||
|
|
4c4e2da457 |
fix(gateway/fgac): filter semantic search results by scope + add search tests (#1987)
* fix(gateway/fgac): filter semantic search results by scope + add search tests
The FGAC tutorial documents three access-control patterns, including
pattern 2: 'Semantic search with FGAC (RESPONSE interceptor) - filter
search results so users only see tools they have access to'. But the
RESPONSE interceptor only filtered tools/list shapes (result.tools /
result.structuredContent.tools), and the demo (invoke.py) plus the
README Test Cases table exercised only tools/call and tools/list - no
semantic-search coverage, despite the README promising it.
Changes:
- RESPONSE interceptor Lambda (fgac-interceptors-stack.yaml): also filter
the semantic-search response, whichever shape the gateway emits -
result.tools, result.structuredContent.tools, and a JSON string in
result.content[*].text carrying {"tools": [...]}. Non-JSON text content
passes through untouched. So x_amz_bedrock_agentcore_search results are
now scope-filtered like tools/list.
- invoke.py: add Test 8 (search with getOrder scope -> only getOrder) and
Test 9 (search with full scope -> multiple tools), plus an
extract_search_tools() helper tolerant of all three response shapes.
- README: add rows 8 and 9 to the Test Cases table so the documented tests
match the demo and the promised pattern 2.
* test(gateway/fgac): add offline unit test for RESPONSE interceptor filtering
Extracts the inline RESPONSE-interceptor Lambda from the CloudFormation
template and exercises lambda_handler against synthetic gateway events —
no AWS, no live gateway, no network. Asserts scope-based filtering across
all three response shapes (result.tools, structuredContent.tools, and the
semantic-search content[*].text JSON payload): a limited scope keeps only
the authorized tool, a full scope keeps all, non-JSON text is untouched,
and a missing token fails safe. 5/5 pass.
* style(fgac): satisfy ruff in the interceptor unit test
python-lint runs ruff on the full changed files. Two findings in the new
test_response_interceptor.py:
- SIM115: wrap the CFN-template read in a context manager
- S102 (use of exec): intentional and required — the test execs the
interceptor Lambda source extracted from our own CloudFormation template
into a throwaway module to unit-test the handler offline. Annotated with
noqa + explanation (repo-controlled input, not user data). 5/5 tests still pass.
* style(fgac): apply ruff format to interceptor unit test
The python-lint gate has two steps (ruff check + ruff format --check); the
prior commit satisfied the linter but not the formatter. Ran ruff format so
the multi-line print()/check() calls match ruff's style. No behavior change;
5/5 tests still pass.
* test(fgac): load interceptor via importlib instead of exec
Addresses review feedback (S102): replace exec(compile(...)) with
importlib loading the extracted interceptor source from a temp module
file. Removes the use-of-exec finding at the source rather than
suppressing it with noqa. SIM115 already fixed. 5/5 tests still pass;
ruff check + ruff format --check both clean.
---------
Co-authored-by: rbelson@amazon.com <rbelson@amazon.com>
|
||
|
|
b4d30e5bc7 | fix(policy): resolve deploy issues in 01-tool-access-with-policy (#1992) | ||
|
|
9912ac0f2d | docs(payments): document Coinbase Quick Create CLI flow and marketplace subscription (#1991) | ||
|
|
1568e63774 |
fix(memory): wait for eventual consistency of directly-written records, add missing skip-extraction sdk surface (#1849)
* fix(memory): wait for eventual consistency of directly-written records
Two samples that write records directly with BatchCreateMemoryRecords read them
back too early and report a result that contradicts the lesson they teach. Both
exit 0 while doing it, so nothing flags the failure.
07-batch-apis/batch-create-update-delete.py
Creates 3 records, deletes 1, then prints "Remaining (0)" when 2 should
remain. Reproduces on both surfaces.
The file already handles eventual consistency for BatchUpdate/BatchDelete
via _retry_until_propagated, which retries ResourceNotFoundException. That
helper cannot cover the final ListMemoryRecords: an unpropagated List does
not raise, it succeeds and returns an empty page, so there is no exception
to retry on.
Measured against a live account, List also lags the furthest behind. The
update and delete both succeeded on their first attempt while the surviving
records took ~87s to become listable. The module docstring had this the
other way round, warning that List leads updatability; it is corrected to
describe the two distinct behaviours and which helper covers each.
Fix: poll List until the expected record count appears. Waiting for the
count rather than for any non-empty page also covers the reverse race,
where the deleted record is still visible.
06-record-metadata/structured-metadata.py
The EU metadata filter returned 0 records on one run and 2 on the next with
no code change. Both surfaces slept a fixed 35s before filtering, with a
comment putting propagation at ~30s.
Measured over three runs, records became searchable at 75s, 86s and 97s
after BatchCreate. The 35s sleep was short every time; the run that passed
did so only because parallel load had slowed it into the window. Filtered
and unfiltered retrieval became available in the same poll, so this is
record searchability, not a separate metadata-index lag.
Fix: poll until both EU records are searchable. The count matters here too.
Breaking on the first hit returned 1 of the 2 EU records in testing, which
reads as the filter having dropped one.
Both files now print an explicit message when the budget expires short, so a
slow run says so instead of presenting a partial result as the lesson. The
polling budget is 180s in both; fast runs return as soon as the records land
and are quicker than the sleep they replace.
Verified live in us-east-1 on boto3 1.43.58 / bedrock-agentcore 1.19.0: 8 runs
(2 trials x 2 surfaces x 2 scripts) in parallel, all returning the expected 2
records with correct content -- the update reflected, the deleted record absent,
and no US-region record leaking through the EU filter. Before these changes the
same commands printed "Remaining (0)" and "EU-only results (0)".
Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
* fix(memory): add the missing sdk surface to skip-extraction.py
08-manage-extraction/README.md documents two surfaces:
python skip-extraction.py boto3 # direct service calls
python skip-extraction.py sdk # AgentCore MemoryClient helpers
The script only defined run_with_boto3, and its entrypoint read nothing but
--cleanup, so `sdk` silently ran the boto3 path. It exits 0 and prints correct
records, which is why it looks like a pass: the only tells are [boto3] labels
in an sdk run, and two "different" surfaces returning different record counts
because they are the same code path twice.
This is the only such mismatch in the area. All 16 README-documented sdk
invocations under 00-getting-started, 01-short-term-memory and
02-long-term-memory were checked against the surfaces their scripts define;
the other 15 have a real run_with_sdk.
Adds the sdk surface rather than removing the documented command, so the
folder keeps the two-surface structure every other sample in the tree has.
The sdk surface uses MemoryClient.create_event(extraction_mode=...) rather
than the session API. MemorySession/MemorySessionManager.add_turns() is the
usual SDK way to write turns, but it does not expose extraction_mode, and
choosing extraction per event is the entire lesson of this sample. Verified
against bedrock-agentcore 1.19.0 by inspecting both signatures before
picking the shape.
Also in this file:
- main() with surface dispatch, matching every sibling sample. An unknown
surface now exits 1 with "Unknown surface 'x'. Use boto3 | sdk." instead
of silently running boto3.
- The two turn lists and the strategy dict move to module scope so both
surfaces share one definition instead of duplicating twelve messages.
- The hardcoded "all 12 stored" string is now derived from the turn lists,
so it cannot drift if a turn is added.
- Docstring documents both surfaces, the --cleanup flag, and why this
sample stays on MemoryClient. Prerequisites now install
bedrock-agentcore, which the sdk surface needs.
Verified live in us-east-1 on boto3 1.43.58 / bedrock-agentcore 1.19.0, both
surfaces in parallel. Each stored all 12 events in short-term memory and
extracted only from the 8 non-skipped ones -- no lottery, bank-account or SSN
content reached long-term memory on either surface, which is the behaviour the
sample teaches. Unknown-surface dispatch confirmed to exit 1 without making
any AWS call.
Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
* fix(memory): tighten the structured-metadata polling budget to 100s
SEARCHABLE_WAIT_SECONDS was 180, which was an arbitrary ceiling rather than a
measured one. Directly-written records became searchable at 75s, 86s and 97s
across three measured trials, so 100s covers every one of them with margin
while no longer idling for three minutes when propagation has clearly stalled.
The loop breaks as soon as the expected record count appears, so this only
changes what happens on a slow run: a fast run returns at 75s under any
ceiling. When the budget does expire the sample prints an explicit message
instead of presenting a partial result as the lesson, which is the behaviour
this PR adds.
This matches the 100s budget used by the six samples in #1848, so the two
PRs no longer disagree about how long to wait.
Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
* fix(memory): trim the propagation polling budget to 120s
Measured ~87s for records to become listable after the delete, so 180s was
far more headroom than needed. 120s keeps a comfortable margin while matching
the tighter budgets requested in #1848.
|
||
|
|
76e2327349 |
docs(gateway): append /mcp to gateway URL capture commands and fix typo (#1955)
The AgentCore gateway MCP endpoint requires the /mcp path suffix, but all README URL capture commands omit it: export GATEWAY_URL=$(aws ... --query 'gatewayUrl' --output text) This gives users a URL that fails with UnknownOperationException. Append /mcp so the captured URL works immediately. Also fixes: - Rename `sensative-data-masking` → `sensitive-data-masking` (typo) - Update README link to match the corrected directory name Co-authored-by: Workshop User <workshop@workshop.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
911643a3ce |
fix(gateway): prevent cleanup scripts from destroying shared resources (#1954)
Two cleanup script bugs: 1. **waf/cleanup.py** calls `admin.delete_gateway(gateway_id)` which deletes ALL targets on the gateway and the gateway itself. But the WAF tutorial only creates one target on an existing shared gateway. Fix: only delete the specific target (via TARGET_ID from .env). 2. **semantic-search/cleanup.py** calls `admin.delete_gateway()` which internally deletes targets then immediately tries to delete the gateway. Target deletion is asynchronous — the gateway delete fails with "Gateway has targets associated". Fix: explicitly delete targets, poll until they're gone, then delete the gateway. Co-authored-by: Workshop User <workshop@workshop.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
86b4cae3e1 |
fix(gateway): fix semantic search invoke.py response parsing (#1953)
The semantic search tool returns results in `result.content[0].text` (JSON string containing a tools array), not in `result.structuredContent.tools`. The old parsing path silently returned empty results. Also adds missing `import json` needed for `json.loads()`. Co-authored-by: Workshop User <workshop@workshop.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
979692ae77 |
fix(gateway): auto-append /mcp to gateway URLs in MCP client scripts (#1952)
* fix(gateway): auto-append /mcp to gateway URLs in MCP client scripts The AgentCore CLI creates gateways whose URLs do not include the /mcp path suffix, but the MCP protocol requires requests to be sent to the /mcp endpoint. This causes UnknownOperationException errors when scripts use the raw gateway URL from the API. Add auto-append logic to gateway_mcp_client.py, streamable_http_sigv4.py, and scripts/lambda-oauth/mcp-invoke.py so they work correctly regardless of whether the URL already includes /mcp. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: modernize type annotations in gateway_mcp_client.py Replace deprecated typing imports (Dict, List, Optional, Callable, Iterator) with modern equivalents (dict, list, X | None, collections.abc.Callable/Iterator) to satisfy ruff UP035/UP006/UP045. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix ruff lint errors in streamable_http_sigv4.py and mcp-invoke.py - UP035: import Generator from collections.abc instead of typing - I001: sort imports - SIM117: combine nested async with statements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply ruff format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Workshop User <workshop@workshop.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
967e919416 |
Add Agent Skills evaluation sample (#1971)
* feat: add Agent Skills evaluation sample * fix: address Agent Skills evaluation review feedback * docs: update skills evaluation architecture diagram --------- Signed-off-by: Bharathi Srinivasan <bhrsrini@amazon.com> Co-authored-by: Shruthi <srajoli@amazon.com> Co-authored-by: Bharathi Srinivasan <bhrsrini@amazon.com> |
||
|
|
9001866298 |
feat(memory): add directIngestion sample (#1980)
* feat(memory): add directIngestion sample * update readme * update ruff |
||
|
|
e51ae5c9a9 |
feat(memory): add sample for flexible namespaces feature (#1979)
* feat(memory): add sample for flexible namespaces feature * fix(ruff): fix minor issue with ruff * fix(ruff): format file |
||
|
|
db7c3b8830 |
Add third-party (DeepEval/AutoEval) evaluators sample (#1978)
Add a 3p-evals sample under llm-as-a-judge-evaluation showing how to run DeepEval and AutoEval metrics with AgentCore Evaluations, both as managed evaluators (evaluatorType=ThirdParty) and as a custom-derived evaluator that runs a base metric on a Bedrock model of your choice. The sample reuses the shared HR Assistant agent and covers discovery via ListEvaluators, on-demand evaluation mixing managed 3p / derived / built-in metrics, and an online config using reference-free 3p metrics. Link it from the parent README's Next Steps. Co-authored-by: Visakh Madathil <visakhm@amazon.com> |
||
|
|
a647f7fa36 |
fix(memory): make the AWS CLI walkthroughs runnable (#1977)
* fix(memory): make the AWS CLI walkthroughs runnable
The `## AWS CLI walkthrough` blocks in the memory samples could not be
copy-pasted into a shell. Three separate bugs, all reproduced against the
live service:
1. `--name "FooCli-$(date +%s)"` is rejected. CreateMemory constrains name to
`[a-zA-Z][a-zA-Z0-9_]{0,47}`, which has no hyphen. botocore does not
enforce it client-side, so the service returns ValidationException and no
resource is created. The walkthrough dies on its first command.
2. The blocks go straight from create-memory to create-event. A new memory is
CREATING for ~2 minutes, and the data plane rejects writes until it is
ACTIVE: "Memory status is not active, unable to process CreateEvent
request".
3. `export MEMORY_ID=<id>` is not a placeholder to bash, it is a redirect.
Pasting it gives "syntax error near unexpected token `newline'". Same for
`<acct>`, `<role>`, `<region>` and `<name>` in other assignments and flags.
Fixes: underscores in names; capture ids from responses with
`--query`/`--output text` instead of placeholders; and poll
`while [ "$status" = CREATING ]` before touching the data plane. The native
`wait memory-created` waiter is not used because its ceiling is 120s
(delay 2 x maxAttempts 60) while creation measured 114-150s here, so it
reports a false "Max attempts exceeded".
Docs only. No Python changed.
* fix(memory): same three bugs in 5 more files, incl. a broken script
Found by widening the sweep: the first pass only shell-parsed files it had
already touched, and only globbed README.md, so these were missed.
- 00-getting-started/03-quickstart-cli.md: `--event-id <event-id-from-list-events>`
broke the quickstart itself. Now captures the id from ListEvents.
- healthcare-assistant-using-episodic: unquoted `<MEMORY_ID>` / `<DATASTORE_ID>`
in the manual-cleanup block.
- cross-region-replication: unquoted `<region>`/`<account>` in --data-stream-arn.
- scripts/create_memories.py: built names as f"{args.name}-source", passed
straight to CreateMemory, so the script ALWAYS failed regardless of --name:
"Value at 'name' failed to satisfy constraint". Reproduced against the
service. Now uses _source/_target, and validates --name via argparse
type= so a bad value is rejected up front instead of deep in an API call.
All 127 shell blocks in the memory tree now pass `bash -n`.
* docs(memory): add CLI walkthrough demo recording
Screen recording of demo.sh: reproduces all three walkthrough bugs against the
live service, then proves each fix. 10/10 checks, exit 0.
* docs(memory): use a GIF for the demo recording
Replaces the 12 MB .mov with a 4.0 MB GIF that autoplays inline in the PR and
renders in-repo, matching the convention used by the other samples. Cropped to
the used 760x1024 region (no downscaling, so the terminal text stays legible),
2x speed, 6 fps, 64-colour palette with diff_mode=rectangle.
|
||
|
|
8f929e6491 |
feat(payments): add Tutorial 09 — pay per use with the x402 upto sc… (#1967)
|
||
|
|
f96b2f982f | feat: Add MPP tutorial (#1969) | ||
|
|
bb1e5155ee |
Quick Create - Marketplace Subscription documentation (#1965)
* Update requirements.txt Signed-off-by: mvangara10 <mvangara@amazon.com> * Multi Agent, Multi Framework, Multi Runtime * python-lint * docs: Update links and Agentcore CLI notice * docs:(migration complete) * docs:AgentCore payments GA * docs: Quick Create Marketplace Subscription --------- Signed-off-by: mvangara10 <mvangara@amazon.com> |
||
|
|
967575fba6 | feat(gateway): verify Coinbase Bazaar curation (#1949) | ||
|
|
7879051081 | AgentCore payments is GA (#1964) | ||
|
|
1053076984 |
fix(memory): await async state transitions so samples stop reporting empty results as success (#1848)
* fix(memory): wait for UpdateMemory to settle in both getting-started quickstarts
Both quickstarts fail at teardown when run exactly as 00-getting-started/README.md
documents:
botocore.errorfactory.ValidationException: An error occurred (ValidationException)
when calling the DeleteMemory operation: Validation failed during DeleteMemory:
Memory is in transitional state UPDATING. Cannot delete memory.
04-quickstart-boto3.py exits 1 after ~214s. Both leave a billable memory resource
behind, since the delete never succeeds.
Root cause: one missing wait, three symptoms.
Both scripts add a semantic strategy via UpdateMemory and then continue immediately.
UpdateMemory is asynchronous. A post-mortem of the leaked resource shows the update
had not been applied at all:
{ "id": "QuickstartMemory-49xcFY89hV", "status": "ACTIVE", "strategies": null }
strategies is null even though the script explicitly added one. From that single
omission:
1. The following sleep waits for an extraction that cannot start, because the
strategy is not active yet.
2. RetrieveMemoryRecords returns an empty list, so the final print loop outputs
nothing and the quickstart's headline lesson silently produces no result.
3. DeleteMemory fires while the resource is still UPDATING, raises, and leaks.
Symptom 3 is the loud one. Symptom 2 is worse for a first-time reader: the
getting-started sample appears to run, prints no retrieved memory, and gives no
indication why.
Fixes, one per surface:
04-quickstart-boto3.py — poll GetMemory until ACTIVE after UpdateMemory. This is
the same loop the file already uses after CreateMemory (lines 76-85), just applied
to the second state transition as well. The subsequent extraction sleep is also
raised 60s -> 90s to match measured latency (see the companion commit).
05-quickstart-agentcore-sdk.py — switch update_memory_strategies() to
update_memory_strategies_and_wait(). The SDK already ships this variant with an
identical signature plus max_wait/poll_interval, so no custom polling is needed;
it is a one-word change.
Verified against a live account (us-east-1, bedrock-agentcore 1.19.0). Both scripts
now run to completion with no traceback and delete their own resources. Leaked
resource count across a full run of the area went from 2 to 0.
The failure was also hit independently by a second tester a day before this
investigation, on an unmodified checkout.
* fix(memory): poll for extraction instead of a fixed sleep in long-term memory samples
Five long-term-memory samples retrieved zero records and still exited 0. Each writes
events, sleeps a hard-coded number of seconds, retrieves exactly once, prints whatever
came back, and tears down. When extraction has not finished, the result is an empty
list reported as success.
Two of the five do not even print a count, so the failure is invisible:
[boto3] Preferences in /users/user-alex/preferences/: <- nothing follows
[boto3] Summary records in /sessions/.../summary/: <- nothing follows
04-namespaces prints its counts, which is how the problem was first spotted:
[boto3] Exact — /facts/user1/ (0):
[boto3] Tenant — /facts/tenantA/* (0):
[boto3] All — /facts/* (0):
Three empty result sets, exit code 0, no error. The lesson being taught — that records
route to the right namespace — produces no evidence either way, and an automated
run-through scores it as a pass.
The waits were already suspect in the source. Each file defines two constants and
gives the shorter one to the boto3 surface, which is the surface the README documents
as the default:
EXTRACTION_WAIT_SECONDS = 60
SESSION_EXTRACTION_WAIT_SECONDS = 90 # semantic extraction surfaces ~60-90s; extra margin
The comment states real latency is ~60-90s, then assigns the bottom of that range to
one surface and the margin to the other. Across a full parallel run the correlation was
total: every 60s boto3 run returned nothing, every 90s sdk run on the same script
returned records.
Raising 60 -> 90 is not sufficient. Measured against a live account by polling
RetrieveMemoryRecords every 10s, user-preference extraction produced its first record
at 93 seconds — three seconds past that fix. Any fixed sleep is a guess that
eventually loses, so this replaces the mechanism rather than the number:
- retrieve in a loop with a 10s interval, breaking as soon as records appear
- EXTRACTION_WAIT_SECONDS becomes a polling budget (180s) rather than a blind sleep
- print an explicit message when the budget expires with nothing found, so an empty
result can no longer be mistaken for a successful demonstration
Fast runs get faster (they break out as soon as records land); slow runs still succeed.
Files, all on the boto3 surface:
02-long-term-memory/standard-usage.py
02-long-term-memory/01-built-in-strategies/user-preference.py
02-long-term-memory/01-built-in-strategies/summary.py
02-long-term-memory/04-namespaces/namespaces-and-organization.py
02-long-term-memory/05-retrieval/retrieve-records-and-citations.py
The last two already passed with a fixed 90s wait but are converted as well, so they
cannot regress silently the next time extraction runs slow.
Verified against a live account (us-east-1, boto3 1.43.58, bedrock-agentcore 1.19.0),
all five run in parallel, before and after:
standard-usage 0 -> 2 records
user-preference 0 -> 1 preference record
summary 0 -> 1 summary record
namespaces-and-organization 0/0/0 -> 1/4/7 across the three query scopes
retrieve-records-and-citations 0 -> 3 records + ListMemoryRecords + GetMemoryRecord
namespaces-and-organization is the clearest result: it now demonstrates the actual
lesson, showing records resolved under /facts/user1/, /facts/tenantA/user1/ and
/facts/tenantA/user2/ instead of three zeros.
* fix(memory): poll for extraction in 02-strategy-overrides, both surfaces
strategies-with-overrides.py retrieved zero records on both the boto3 and sdk
surfaces and exited 0 in each case:
[boto3] Medical facts (0):
[boto3] The Godfather mention should NOT appear — override suppresses it.
rc=0
[sdk] Medical facts (0):
rc=0
Same root cause as the five samples in the previous commit — a single blind
time.sleep() before one retrieval attempt — but with a sharper consequence, because
this lesson asserts a fact is *absent*.
The sample deliberately feeds in one non-medical line ("my favourite movie is The
Godfather") and teaches that the semanticOverride prompt suppresses it. An empty
result set satisfies that claim vacuously: the reader sees the "should NOT appear"
message printed directly beneath an empty list, and cannot tell whether the override
worked or whether nothing was extracted at all. A failed run is indistinguishable
from a successful demonstration.
The margin here was the thinnest in the folder. Measured against a live account by
polling RetrieveMemoryRecords every 10s, override extraction produced its first
record at 73 seconds:
0s: 0 records
31s: 0 records
62s: 0 records
73s: FIRST RECORD (2 records)
The script waited 75 — two seconds of headroom. Under parallel load that is
reliably not enough, which is why both surfaces came back empty. This is also the
most model-dependent path in the tree: overrides invoke a caller-specified Bedrock
model for both extraction and consolidation, so its latency moves with the chosen
model, and no fixed sleep can be correct for every MODEL_ID.
Changes, applied to both surfaces:
- retrieve in a loop with a 10s interval, breaking as soon as records appear
- EXTRACTION_WAIT_SECONDS becomes a 180s polling budget rather than a blind sleep
- the "should NOT appear" line now prints only when records were actually
retrieved; an empty result prints an explicit note that suppression cannot be
demonstrated from that run
That last point is the substantive difference from the previous commit. Elsewhere an
empty retrieval is merely uninformative; here it would actively assert something the
run did not show.
Verified against a live account (us-east-1), both surfaces run in parallel:
boto3 0 -> 2 records ("mother had breast cancer at 52", "has type 2 diabetes")
sdk 0 -> 1 record ("takes metformin twice daily for type 2 diabetes")
The Godfather line is correctly absent from both, so the override is now
demonstrably doing its job rather than being credited for an empty result.
MODEL_ID is deliberately untouched here; the retired-model-id fix for this file is
in a separate change.
* fix(memory): tighten the summary.py polling budget to 100s
The polling budget in summary.py was 180s, which was an arbitrary ceiling
rather than a measured one, and its comment cited a ~93s latency that was
measured on user-preference.py, not on this sample. Summary uses
summaryMemoryStrategy and does consolidation rather than extraction, so the
borrowed figure did not describe it.
Measured directly against a live account, three parallel trials, timing from
the last CreateEvent to the first retrievable summary record:
trial A: 87s
trial B: 104s
trial C: 75s
The comment now cites that 75-104s range instead of the borrowed number.
Because the loop breaks as soon as records appear, the budget only affects
slow runs: a fast run returns at 75s under any ceiling. 100s covers the
median case and keeps the sample from idling for three minutes when
consolidation has clearly stalled.
Note for reviewers: trial B exceeded this budget at 104s. On a run that
slow the script prints "No records after 100s - consolidation may still be
running" and shows no summary, rather than hanging. That message exists
because of this PR's other changes, so a slow run now says so explicitly
instead of printing an empty result as if it were the answer. If the
preference is that no run should ever come up short, 120s covers all three
measured trials.
Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
* fix(memory): tighten the remaining polling budgets to 100s
Applies the same change already made to summary.py across the other five
samples this PR touches, so the whole set uses one budget:
02-long-term-memory/standard-usage.py
02-long-term-memory/01-built-in-strategies/user-preference.py
02-long-term-memory/02-strategy-overrides/strategies-with-overrides.py
02-long-term-memory/04-namespaces/namespaces-and-organization.py
02-long-term-memory/05-retrieval/retrieve-records-and-citations.py
The previous 180s was an arbitrary ceiling. Because each loop breaks as soon
as records appear, the budget only decides what happens on a slow run, so a
lower ceiling costs nothing on a normal one and stops the sample idling for
three minutes when extraction has clearly stalled.
Verified live in us-east-1, all five run against a live account with the new
budget in place:
standard-usage.py 4 records
04-namespaces 2 / 6 / 10 across the three scopes
01-built-in-strategies/user-preference 3 preference records
05-retrieval 4 records + ListMemoryRecords
02-strategy-overrides 1 medical fact, Godfather line absent
Every one returned records inside the 100s budget, and none printed the
"may still be running" message this PR added.
Note for reviewers: the measured latencies these budgets cover are 73s for
override extraction and ~93s for semantic and preference extraction, so 100s
is a margin of roughly 7s over the slowest measurement. Separate measurement
of summary consolidation, done when its budget was set in the previous
commit, produced 75s / 87s / 104s across three trials, so a run at the slow
end of that spread can exceed 100s. When that happens the sample now prints
an explicit message rather than presenting an empty result as the answer,
which is the behaviour this PR exists to add. If reviewers prefer that no run
ever comes up short, 120s covers every latency measured here.
Passes ruff check and ruff format --check per .github/workflows/python-lint.yml.
|
||
|
|
c36db47314 | feat: add OpenClaw Agent with AgentCore Payments (#1797) | ||
|
|
ded45844e7 |
feat: add Okta Cross App Access (XAA/ID-JAG) sample with AgentCore Runtime (#1956)
* Fix broken example links in obo-training OBO Reference Guide The Runnable Examples section had relative links to examples/ which was renamed to 3-examples/ before the initial merge. Also generalize the walkthrough paragraph so it covers both Entra and Okta claim conventions (the merged version was Entra-specific). - Relative links now correctly point at 3-examples/* - Marks UC2 Okta complete (was 'Okta planned') - Generalizes the walkthrough: actor claim (azp/appid vs cid), identity claim (oid vs sub+uid), and audience behavior (rotates on Entra vs constant on Okta) * feat: add Okta Cross App Access (XAA/ID-JAG) sample with AgentCore Runtime Adds 06-okta-xaa under 01-features/05-authenticate-and-authorize: a Strands agent on Amazon Bedrock AgentCore Runtime that calls a downstream todo API on behalf of a signed-in user, brokered by Okta Cross App Access (Identity Assertion JWT Authorization Grant). The agent (an Okta AI Agent / workload principal) performs the two-leg ID-JAG exchange with a single private_key_jwt key; the resource API validates the Okta custom-AS-issued token. Includes local (test_xaa_flow.py) and deployed (agentcore CLI) paths, Okta setup automation, and a cleanup script. * fix(okta-xaa): address security scan findings - resource-app: bind uvicorn to 127.0.0.1 by default (HOST-overridable) [Bandit B104] - okta_setup.py: stop printing the created client secret; direct user to the Okta Admin Console instead [CodeQL clear-text logging] - cleanup.py: keep the secret name out of log strings and drop raw HTTP response bodies from logs [CodeQL clear-text logging] - annotate OAuth grant/token-type URI constants and empty client_secret with # nosec (Bandit B105/B106 false positives) * fix(okta-xaa): resolve ruff lint/format and remaining CodeQL finding - ruff: sort imports (I001), str.removeprefix (FURB188), drop quoted type annotation (UP037), remove unused noqa directives (RUF100), and apply ruff format (line-length 120) across the sample - deploy/patch_agentcore_json.py: catch ImportError (not blind Exception) for the optional dotenv import (BLE001) - okta_setup.py: read the login client id from the app id rather than the credentials block, so CodeQL no longer flags it as clear-text logging --------- Co-authored-by: Satveer Khurpa <khurpas@amazon.com> |
||
|
|
79c70d2484 |
Temporal new (#1935)
* feat: add temporal policies module with banking assistant sample and React web app
Adds 03-temporal-policies/ under 02-policy/ with:
- Module README introducing temporal policies (why, what, key concepts)
- docs/ with 7 pages: Dogwood language, sessions, predicates, operators,
patterns catalog, limits, and FAQ
- bankingassistant/ hands-on lab:
- Two Lambda MCP targets (banking-tools, portfolio-tools)
- setup.py: gateway, policy engine, base permits (idempotent)
- cleanup.py: full teardown script
- deploy_lambda.py: Lambda deployment with code-update on re-run
- README with 6 steps: Cognito, Lambdas, setup, web app, banking
policies (5), portfolio policies (6)
- React + Node web app (client/): dual-protocol MCP client
(2025-11-25 via official SDK, 2026-07-28 stateless), Bedrock
Converse streaming tool loop, named sessions with policy session
ID management, mock mode for offline testing
* feat: convert to AgentCore Runtime MCP server, fix temporal policy issues
- Replace Lambda targets with a single FastMCP MCP server deployed to
AgentCore Runtime (app/banking_assistant_tools/main.py, 14 tools)
- Add agentcore/ CLI project for Runtime deployment
- Update setup.py: MCP server target with OAuth credential provider,
remove Lambda-specific code, add IAM statements for token vault
- Update cleanup.py: add waits between async deletions, remove Lambda cleanup
- Fix temporal policies: use input.account_id (not output.accountId) since
the gateway policy engine doesn't register output fields from tools/list;
use lowercase "buy"/"sell" to match what Bedrock Converse sends
- Fix SSE streaming: Accept application/json only (both protocol paths)
to get closed JSON responses instead of open SSE streams that hang
- Add tool_start streaming event: UI shows tool args immediately while
waiting for the result (pending state with dashed border)
- Add ToolList panel with refresh button
- Add tool info (name + description) to session DTO
- Fix session sidebar ordering (update in place, don't re-sort)
- Simplify system prompt (no agent-side rules, gateway is sole enforcer)
- Remove Lambda handlers and tool-schema.json files (replaced by MCP server)
* lib
* Add Entra ID gateway inbound auth sample and update .gitignore
* Update Entra ID gateway auth demo: rename gif, add kiro demo
|
||
|
|
3eb2a56f23 | Add pay-for-x402-secure-data use case (feature) (#1782) | ||
|
|
0082e848be |
fix(gateway): Use Workshop Studio S3 URLs for Cognito Launch Stack button (#1925)
The CloudFormation console rejects raw GitHub URLs with "TemplateURL must be a supported URL." Replace the non-functional Launch Stack buttons (pointing to a private staging repo) with verified Workshop Studio S3 URLs that CloudFormation accepts. Tested: stack deploys successfully via these URLs in both us-east-1 and us-west-2, producing all expected outputs (DiscoveryUrl, GatewayClientId, MCPClientId, TokenEndpoint, UserPoolId, etc.) required by downstream gateway tutorials. |
||
|
|
3c53886827 |
fix(gateway): Pin fastmcp, fix Databricks model ID, remove duplicated text (#1926)
- Pin fastmcp == 3.2.4 in labelicitation, labsession, labstateful, and labstream pyproject.toml files. The >= 3.2.4 constraint floats to 3.4.6 which performs a PyPI version check at startup and exceeds the gateway's 40-second target sync timeout, causing SYNCHRONIZE_UNSUCCESSFUL. - Fix databricks_currency_agent app.yaml MODEL_ID from 'claude-sonnet-4' to 'databricks-claude-sonnet-4' to match the app.py default — the Databricks endpoint name includes the 'databricks-' prefix. - Remove duplicated paragraph in 01-attach-targets/http/README.md where the same description text appeared three times. |
||
|
|
c115114122 |
feat: add temporal policies module with banking assistant sample (#1924)
* feat: add temporal policies module with banking assistant sample and React web app
Adds 03-temporal-policies/ under 02-policy/ with:
- Module README introducing temporal policies (why, what, key concepts)
- docs/ with 7 pages: Dogwood language, sessions, predicates, operators,
patterns catalog, limits, and FAQ
- bankingassistant/ hands-on lab:
- Two Lambda MCP targets (banking-tools, portfolio-tools)
- setup.py: gateway, policy engine, base permits (idempotent)
- cleanup.py: full teardown script
- deploy_lambda.py: Lambda deployment with code-update on re-run
- README with 6 steps: Cognito, Lambdas, setup, web app, banking
policies (5), portfolio policies (6)
- React + Node web app (client/): dual-protocol MCP client
(2025-11-25 via official SDK, 2026-07-28 stateless), Bedrock
Converse streaming tool loop, named sessions with policy session
ID management, mock mode for offline testing
* feat: convert to AgentCore Runtime MCP server, fix temporal policy issues
- Replace Lambda targets with a single FastMCP MCP server deployed to
AgentCore Runtime (app/banking_assistant_tools/main.py, 14 tools)
- Add agentcore/ CLI project for Runtime deployment
- Update setup.py: MCP server target with OAuth credential provider,
remove Lambda-specific code, add IAM statements for token vault
- Update cleanup.py: add waits between async deletions, remove Lambda cleanup
- Fix temporal policies: use input.account_id (not output.accountId) since
the gateway policy engine doesn't register output fields from tools/list;
use lowercase "buy"/"sell" to match what Bedrock Converse sends
- Fix SSE streaming: Accept application/json only (both protocol paths)
to get closed JSON responses instead of open SSE streams that hang
- Add tool_start streaming event: UI shows tool args immediately while
waiting for the result (pending state with dashed border)
- Add ToolList panel with refresh button
- Add tool info (name + description) to session DTO
- Fix session sidebar ordering (update in place, don't re-sort)
- Simplify system prompt (no agent-side rules, gateway is sole enforcer)
- Remove Lambda handlers and tool-schema.json files (replaced by MCP server)
|
||
|
|
b2425e8e2e |
Run the Agent Registry migration Glue jobs on Glue 5.0 glueetl, and drop GA terminology (#1931)
* Add the AWS Agent Registry preview-to-GA migration tool Migrates registries and registry records from the `bedrock-agentcore` namespace to `agent-registry`, handling the namespace change and the GA schema change. Extract and load are separate, idempotent steps, so a run can be inspected before anything is written and re-run safely after a failure. Two ways to run it: locally (or in CloudShell) with no infrastructure, or as a deployed CDK stack of two AWS Glue jobs plus an S3 staging bucket when incremental loads or unattended runs are needed. Both control-plane service models come from boto3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: satisfy the pinned-free ruff and ASH the samples CI installs The python-lint job installs ruff unpinned, so it now runs 0.16.1 and reported 155 findings against this folder. 153 are fixed here; the remaining two rules are ignored in a folder-scoped ruff.toml with the reason recorded, because complying would change behaviour rather than style: TRY004 the staged-record and configuration readers raise ValueError on a malformed document on purpose -- bad data, not a caller passing the wrong type -- and both the CLI exit-code mapping and the tests assert that. SIM115 the staged-record reader returns an iterator its caller consumes, so the handle must outlive the function that opened it. ruff.toml also records line-length = 120, which is the width the code and its comment blocks are already written to. Fixed: RUF100, RUF012 (ClassVar on mutable test fixtures), RUF059, ISC004, EXE001, F402 (loop variables shadowing dataclasses.field), I001, UP028, UP035/UP037, F401, SIM102, SIM117, B010, FLY002, FURB192, PIE810, TRY401, plus `ruff format` over the folder. The parenthesized multi-context `with` statements SIM117 produces were checked against CPython 3.9, which is what the Glue Python shell runs. Folded in the ASH findings: the hardcoded /tmp path now goes through tempfile.gettempdir(), and two documented non-secrets carry `# pragma: allowlist secret`. Verified: ruff check and ruff format --check clean over every file the CI job sees, the 484-test suite, npm run verify:lib, and python3.9 compileall. * Run the Glue jobs on Glue 5.0 glueetl so the SDK carries the new service model Both control-plane clients are modeled boto3 operations and neither service model lives in this repository, so a worker has to install a botocore that ships `agent-registry-control`. No Glue image does: the Python shell runtime is boto3 1.21 and the Glue 5.0 Spark runtime is boto3 1.34. Installing it is not possible on a Python shell job. `agent-registry-control` first shipped in botocore 1.43.66; every botocore from 1.43.0 onward requires Python >= 3.10; the last release that allowed 3.9 was 1.42.97, which predates the model. A Glue Python shell job is Python 3.9.10 at GlueVersion 3.0, 4.0 and 5.0 alike, and CreateJob rejects any `command.pythonVersion` outside ^([2-3]|3[.]9)$ -- so there is no pin, and no Glue version, that puts the model on a Python shell worker. A `glueetl` command on Glue 5.0 is Python 3.11, which is the only Glue runtime `boto3==1.43.66` installs on. Verified end to end against real registries: extract and load ran on the deployed jobs, the worker log shows `python3.11 -m venv` followed by `pip install boto3==1.43.66 botocore==1.43.66`, and the staged output matches a local run byte for byte apart from the run id and its timestamp. The jobs remain single-threaded boto3 scripts and never create a SparkContext -- the Spark command is how the interpreter is obtained, not a change in design. - Pin the SDK exactly (GLUE_SDK_MODULES) rather than floor it, so two runs of one cutover cannot stage and load with different SDKs. - Size at the AWS minimum for a batch Spark job, G.1X x 2 (G.025X is streaming only). glueMaxCapacity is gone: MaxCapacity and WorkerType/NumberOfWorkers are mutually exclusive, so config.ts now rejects the removed key by name instead of ignoring it, and the fractional-DPU cap on loadConcurrency with it. - Disable Glue bookmarks explicitly. An INCREMENTAL run resumes from the watermark in the staging bucket, which an operator can read and override with --changed-after; an opaque bookmark could only disagree with it. - Suppress AwsSolutions-GL1/GL3, which apply to Spark jobs only: Glue logs are run metadata, never record content, and no bookmark data exists to encrypt. - Report a missing model as a pre-flight failure (check_sdk_models) rather than as UnknownServiceError from the first client build, and probe the local interpreter for both models by capability, not by version, so a side-loaded model still works. - Rename the dual-writes migration pattern to active-active, per review feedback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Replace GA terminology with target/new-version wording The service is not generally available, so the sample must not describe it that way. Every "GA" reference is gone from documentation, source, tests and CLI output: * Identifiers and settings: GaRegistryClient -> TargetRegistryClient, validate_ga_request -> validate_target_request, iam.gaWriteActions -> iam.targetWriteActions, approval.gaStatusCounts -> approval.targetStatusCounts, api.ga -> api.target in the adapter contract. * Placeholders and paths: <ga-registry-id> -> <new-registry-id>, ga-registry-payloads/ -> new-registry-payloads/. * Prose: the destination is the "target registry" (the vocabulary the code already used for the source/target pair); the service itself is "the new version of AWS Agent Registry". * README opening no longer announces a general-availability launch date. * The AWS Glue runtime rationale is no longer spelled out in the docs. Renaming the adapter's section key changes the replay fingerprint; all three computations (checkout, package, deployed) agree on the new value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Reword the target API error labels Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: reword the migration guide link title Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Create the target registry during init instead of asking the user to init derived each target registry's settings and then handed the job back: it printed a CLI command and a prompt for the resulting id, which left anyone without a recent AWS CLI with no way forward. The registry control operations do exist in the SDK the tool already pins, so init now calls CreateRegistry itself, polls GetRegistry until the registry is READY, and writes the generated id into the configuration. The derived payload is still shown first, and creation is confirmed, since discoveryConfiguration decides who may read the registry. Answering no still prints the command to run by hand. target-config --create does the same for a mapping added later, with no prompt. The create is idempotent: clientToken is derived from the mapping and the payload, so a retry after an interrupted create returns the first registry rather than minting a second. An id is recorded before the wait, so a registry that exists is never left unnamed. check now warns when a model under ~/.aws/models/agent-registry-control shadows the SDK's own, which is what makes CreateRegistry look absent. No adapter change, so the replay fingerprint is unmoved and no redeploy is needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Keep the registry ids of a create that failed for another mapping init and target-config work per mapping, and the engine already reported one mapping's failure without abandoning the rest -- but the CLI discarded the whole result on the exit code, so one unreadable source registry meant none of the others were created. For --create the same discard was worse than unhelpful: a registry that was created has its generated id only in that output, so dropping it left a real registry named nowhere in the configuration and a next run that would create a second one. runEngineJson now takes `partial`, and both target-config calls pass it. Also says plainly in the init prompt that leaving the id empty means the new-version registry gets created, rather than "I will help you create it". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Your Name <you@example.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7a3ca0899f |
capacity provider samples (#1927)
* capacity provider samples * capacity provider samples |
||
|
|
4d5a786dd4 |
Add the AWS Agent Registry preview-to-GA migration tool (#1918)
* Add the AWS Agent Registry preview-to-GA migration tool Migrates registries and registry records from the `bedrock-agentcore` namespace to `agent-registry`, handling the namespace change and the GA schema change. Extract and load are separate, idempotent steps, so a run can be inspected before anything is written and re-run safely after a failure. Two ways to run it: locally (or in CloudShell) with no infrastructure, or as a deployed CDK stack of two AWS Glue jobs plus an S3 staging bucket when incremental loads or unattended runs are needed. Both control-plane service models come from boto3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: satisfy the pinned-free ruff and ASH the samples CI installs The python-lint job installs ruff unpinned, so it now runs 0.16.1 and reported 155 findings against this folder. 153 are fixed here; the remaining two rules are ignored in a folder-scoped ruff.toml with the reason recorded, because complying would change behaviour rather than style: TRY004 the staged-record and configuration readers raise ValueError on a malformed document on purpose -- bad data, not a caller passing the wrong type -- and both the CLI exit-code mapping and the tests assert that. SIM115 the staged-record reader returns an iterator its caller consumes, so the handle must outlive the function that opened it. ruff.toml also records line-length = 120, which is the width the code and its comment blocks are already written to. Fixed: RUF100, RUF012 (ClassVar on mutable test fixtures), RUF059, ISC004, EXE001, F402 (loop variables shadowing dataclasses.field), I001, UP028, UP035/UP037, F401, SIM102, SIM117, B010, FLY002, FURB192, PIE810, TRY401, plus `ruff format` over the folder. The parenthesized multi-context `with` statements SIM117 produces were checked against CPython 3.9, which is what the Glue Python shell runs. Folded in the ASH findings: the hardcoded /tmp path now goes through tempfile.gettempdir(), and two documented non-secrets carry `# pragma: allowlist secret`. Verified: ruff check and ruff format --check clean over every file the CI job sees, the 484-test suite, npm run verify:lib, and python3.9 compileall. --------- Co-authored-by: Your Name <you@example.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bdeaf383e1 |
Migrate 03-registry samples from preview to GA API (#1889)
* Migrate 03-registry samples from preview to GA API * Fix lint errors: RUF100, SIM102, TRY401 * Update CFN trust policy and IAM actions to agent-registry namespace * Migrate admin-approval-workflow CFN to agent-registry namespace * Fix I001: remove extra blank line in import block * Apply ruff format to all registry Python files * Migrate IAM actions and ARNs in markdown docs to agent-registry namespace * Clean up registry-synchronize-mcpserver: rename rg_client, remove endpoint_url, fix lint * Update admin-approval-workflow CFN and deploy script |
||
|
|
0ad31d4686 |
feat: add Codex on AgentCore Runtime with EFS sample (#1838)
* feat: add Codex on AgentCore Runtime with EFS sample
Adds 03-codex-with-efs under 06-workshops/01-AgentCore-runtime/12-coding-agents,
a sibling to 02-claude-code-with-efs that deploys the OpenAI Codex SDK as an HTTP
agent on AgentCore Runtime with an EFS file system mounted at /mnt/efs.
The sample mirrors the structure and scripts of 02-claude-code-with-efs so the two
can be read side by side, and demonstrates what is specific to Codex on Bedrock:
- Codex reads its provider from $CODEX_HOME/config.toml, which server.js writes on
first boot with model_provider = "amazon-bedrock". There is no equivalent of
CLAUDE_CODE_USE_BEDROCK.
- CODEX_HOME itself lives on EFS, so a Codex thread is persistent state rather than
session state. invoke.py exposes --thread alongside --session to resume a
conversation from a brand new runtime session.
- The GPT-5.6 family is served through the bedrock-mantle endpoint, so the execution
role grants bedrock-mantle:CreateInference and bedrock-mantle:CallWithBearerToken
in addition to the classic bedrock:InvokeModel actions.
- The workspace is git-initialized on first boot because Codex refuses to run outside
a repository unless skipGitRepoCheck is set.
Credentials come from the runtime execution role only; server.js strips
OPENAI_API_KEY, CODEX_API_KEY, AWS_BEARER_TOKEN_BEDROCK and AWS_PROFILE from the
environment handed to Codex. No OpenAI API key is used.
The root .gitignore ignores **/Dockerfile, so this adds a negation for the new
sample's Dockerfile, following the existing exception for the claims agent.
* fix: make the shared-skills demo work and harden the Codex EFS sample
Validated by deploying the sample end to end on AgentCore Runtime in us-east-2
with EFS mounted, then exercising every claim the README makes.
The demo was broken. Step 3 asks Codex to write a skill onto the shared file
system, but sandboxMode "workspace-write" makes only workingDirectory writable
and approvalPolicy "never" leaves Codex no way to ask for more, so the write was
denied with no escalation path. server.js now passes additionalDirectories:
[SKILLS_DIR], which the SDK forwards as `codex exec --add-dir`, and the skill
target moves to $CODEX_HOME/skills - Codex's native skills directory, so a skill
written by one session is advertised to every later session rather than being an
inert file on a shared disk. Confirmed live: session A created
python-code-review/SKILL.md on EFS, an independent session B listed and applied
it, and /mnt/efs outside the workspace and skills directory stayed read-only.
config.toml was written only when missing. CODEX_HOME lives on EFS and outlives
the container, so the first deployment pinned the model and region forever and
every later `CODEX_MODEL=... python deploy.py` was silently ignored. It is now
rewritten on every boot, preserving anything Codex appends below the managed
block (for example [projects."<ws>"] trust_level).
Other correctness and robustness fixes:
- initPersistentState() failures exited 0 with nothing listening, because the
uncaughtException handler only logs. They now exit 1.
- deploy.py granted bedrock:InvokeModel on arn:aws:bedrock:<region>:<acct>:*,
which covers far more than inference; narrowed to inference-profile/*. The
EFS grant is scoped to this sample's access point instead of every access
point in the account.
- deploy.py fails with an actionable message when boto3 is too old to know
filesystemConfigurations, instead of a raw ParamValidationError traceback.
- cleanup.py deletes the ECR repository setup.sh creates, and keeps the local
config files when the stack delete does not finish, so a half-deleted stack
that is still billing can be retried. Its waiter allowed 10 minutes, which is
not enough for a NAT Gateway plus AgentCore's asynchronous release of the
network interfaces it attached to the private subnets; raised to 30 with the
cause documented here and in the README.
- Prompts are no longer echoed into CloudWatch, which contradicted the otel
log_user_prompt = false already set in config.toml.
- Malformed JSON bodies and non-string threadId return 400 rather than 500.
- invoke.py and exec_cmd.py raised IndexError on a trailing --session/--thread.
- setup.sh cds to its own directory so its relative paths resolve from anywhere,
and records the ECR repo name for cleanup.py.
- cfn-vpc.yaml derives the four subnet CIDRs from VpcCidr with !Cidr rather than
hardcoding 10.0.x.0/24, so overriding the parameter no longer produces subnets
outside the VPC. VpcCidr gained an AllowedPattern enforcing the /16 the
layout assumes. Verified the defaults are byte-identical to the old values.
- package.json pins @openai/codex-sdk exactly; a caret range on a fast-moving
0.x SDK made image builds non-reproducible.
* fix: resolve ruff lint findings in codex-with-efs sample
- cleanup.py: catch (BotoCoreError, ClientError) instead of bare Exception
so best-effort cleanup still continues past AWS failures without
swallowing programming errors (BLE001)
- deploy.py, update.py: build runtime params as dict literals (C408)
- invoke.py: explicit `str | None` on optional parameters (RUF013)
* refactor: move codex-with-efs sample to 01-features coding-agents
The canonical location for runtime coding-agent samples is
01-features/02-host-your-agent/01-runtime/04-coding-agents; the
06-workshops copy is an older duplicate that is not indexed.
- move 03-codex-with-efs -> 04-coding-agents/06-codex-with-efs
(03 through 05 are already taken)
- add the sample to the 04-coding-agents README index
- update the root .gitignore Dockerfile allowlist to the new path
* fix: address ASH security scan findings
- Dockerfile: add HEALTHCHECK (CKV_DOCKER_2). server.js already answers any
GET with {"status":"healthy"} and curl is installed, so this is a real
probe rather than a suppression. Generous start-period covers the first
boot, which mounts EFS and git-inits the workspace.
- cfn-vpc.yaml: add a cdk_nag rules_to_suppress entry for AwsSolutions-VPC7
alongside the existing cfn_nag W60 suppression, matching the pattern used
elsewhere in the repo. Flow Logs bill continuously and the README already
lists enabling them under production notes.
---------
Co-authored-by: Erez Weinstein <erweinst@amazon.com>
|
||
|
|
fa72a1ed57 | Add 04-migrate-to-new-namespace directory for registry namespace migration sample (#1886) | ||
|
|
9018c5e3c7 |
fix: self-managed strategy sample - remove invalid namespaces, fix IA… (#1858)
* fix: self-managed strategy sample - remove invalid namespaces, fix IAM and CLI docs (#1856) * fix: rename namespaces to namespaceTemplates per reviewer feedback |
||
|
|
ee31c1790e |
feat(harness/14-s3-filesystem): optional script to provision the S3 Files prerequisites (#1881)
Both scripts in this sample mount an existing S3 Files access point, and nothing in the folder creates one — so on a fresh account --access-point-arn, --subnet-ids and --security-group-ids have nothing to point at and the sample cannot be run at all. Adds an optional provision_s3_filesystem.py that creates the prerequisites (versioned bucket, service role, file system, access point, a mount target per subnet, and an NFS security group) and prints the exact command line to paste into either script. The sample scripts themselves are unchanged. Networking is bring-your-own by default: the script discovers private subnets that already have NAT-gateway egress rather than creating a VPC, because a NAT gateway bills hourly. --create-vpc is opt-in. Every resource is recorded in provision_state.json as it is created, and --teardown deletes only what is recorded there. |
||
|
|
f67f2c041f |
fix(harness/14-s3-filesystem): missing IAM permission, leaked execution role, and false-pass command guards (#1880)
The sample could not create a harness at all: the execution-role policy was missing s3files:ListMountTargets, which the runtime validates at create time, so CreateHarness was accepted and then landed in CREATE_FAILED. Also fixes an execution-role leak (the shared HarnessExecutionRole and its inline policies survived every run, including successful ones, breaking the next sample in the folder), several guards that reported success on a failed or never-executed VM command, and a mid-stream read timeout that discarded an answer already in hand. README: document the bucket-versioning requirement, the real IAM scope, the 2-3 minute VPC-mode create time, and the flags that were undocumented. |
||
|
|
2ac3ce2b35 |
fix(harness/13-aws-skills): handle transport and throttling stream errors (#1879)
`stream_response` caught only `EventStreamError`, so a mid-stream transport failure (`ReadTimeoutError`, `ConnectionClosedError` — both `BotoCoreError`) or a `ThrottlingException` (a plain `ClientError`) escaped as an unhandled traceback that named nothing about the cause, aborting the demo even when the agent's complete answer had already been printed. Catch both base classes, report the cause, and re-raise only when no content arrived. Also fix `--raw-events` discarding good responses: raw mode skipped text accumulation, so `full_text` stayed empty and the "did we receive anything?" guard re-raised even a cosmetic close error. Remove the dead `REGION` assignment (never read; the region actually resolves in `utils/client.py`, which honours `AWS_DEFAULT_REGION` or `AWS_REGION`) along with the now-unused `import os`, and the entirely unused `import boto3`. Correct a comment that described a `tools`/`allowedTools` argument the call does not pass. README: add a Prerequisites section including the boto3 >= 1.43.32 floor that `awsSkills` requires, document `--role-arn`/`--raw-events`/`--skip-cleanup` and the ~2-3 minute wait for READY, and state the tagged-union and wildcard semantics of `skills` entries. |
||
|
|
34e039f36a |
fix(06-async-step-function): report failures instead of SUCCEEDED, and fix the sample test path (#1878)
The Step Functions workflow routed a caught harness error to a Pass/End state, so an execution whose InvokeHarness failed with AccessDeniedException finished SUCCEEDED with nothing written to DynamoDB. The missing memory/event permissions that caused that failure are also fixed, along with the JSON extraction (which crashed on bare JSON and silently truncated nested objects), the retry policy (which billed caller-fault errors four times), the --use-samples loop (which aborted on the first sample because of word-splitting), both poll loops (which treated ABORTED/TIMED_OUT as success), and three unreachable error handlers. All fixes reproduced against upstream main and re-verified live in a real AWS account over 3 deploy/teardown cycles. |
||
|
|
a9b2c0c920 |
fix(01-harness): stop 07-oauth reporting success for a failed invoke, and leaking on failure (#1877)
InvokeHarness over HTTPS returns HTTP 200 and reports agent-side failures as
{"message": ...} frames inside the event stream. The parser read only
obj["delta"], discarded those frames, printed "(No text deltas found in
stream)", and then printed the full success narrative claiming all three auth
hops had worked. Separately, the script had no try/finally, so a failure
anywhere between Step 1a and Step 4 left two Cognito pools, a credential
provider, a Lambda, a gateway + target, a harness and three IAM roles alive.
Correctness:
- Raise on non-200, collect and raise on stream error frames, and raise when
there are neither deltas nor an error frame.
- Grant the 8 memory/event actions the harness needs at runtime; CreateMemory
and ListMemories take no memory ID, so they must be scoped to "*".
- Replace three fall-through polls with deadline loops that raise on terminal
status and on timeout.
- Wrap provisioning in try/finally so cleanup always runs.
- _wait_gone no longer reports success on timeout.
- Do not print "Cleanup complete!" when resources were skipped.
- _ensure_policy publishes a new default version instead of swallowing
EntityAlreadyExists, pruning the oldest non-default at the 5-version limit.
- Paginate the Cognito pool lookup and every AgentCore list call
(ListHarnesses, ListGateways, ListGatewayTargets) through one helper.
- Match the gateway and target conflict fallbacks on name; the target fallback
took the first target it saw, which returns the wrong id on a gateway that
carries any other target.
- Drop the N+1 get_gateway in the gateway fallback; ListGateways returns name.
- _del_role can now delete a policy that has non-default versions.
- Convert six `except Exception: pass` handlers into reported skips.
Docs and lint:
- README: document the IAM permissions the walkthrough needs, and drop the
`pip install requests` step -- requests is already in ../../requirements.txt.
- utils/lambda_function_code.py: fix a latent import-order violation and a
docstring that named the wrong tools.
|
||
|
|
9b6ec3f736 |
fix(05-agent-skills): stop reporting success the sample never checked (#1876)
The demo prints four green ticks after commands whose exit codes are never read, and states a sheet count for a file nothing ever opened. - Replace two hand-rolled `for i in range(12)` polls with the shared `poll_harness_status`. Both had no `else` and no `raise`, so on timeout they fell through silently and `update_harness` ran against a CREATING harness (`ConflictException`). Real create time is ~95-133s; the ceiling was 60s. - Wrap the lifecycle in `try/finally`. The file had zero `Try` nodes, so the crash above leaked a billable harness and the shared execution role. - Raise on a failed skill install instead of printing the tick regardless, and gate on the verification command's answer instead of discarding it. - Add `run_command`, returning stdout, stderr and `contentStop.exitCode`, so a missing file is distinguishable from an unreadable one. - Validate downloads: `.xlsx` is a zip, so `zipfile.is_zipfile` rejects non-workbooks and the sheet count is read from the file rather than hardcoded. - README: document the three command outputs, the LibreOffice install the agent performs on `node:slim`, and the new diagnostics. |
||
|
|
d35540c0c5 |
fix(01-harness): stop 04-mcp-integration reporting success for a report it never retrieved (#1874)
Part 5 read the research report with `cat ... 2>/dev/null || echo '{}'`. When the
agent answered in prose without writing the file, cat's error was discarded and
the fallback emitted `{}` — valid JSON — so json.loads succeeded, the
JSONDecodeError branch never ran, and the sample printed "Research Report
Generated:" followed by an empty object. Ask for the file plainly and keep both
halves of the answer: stderr says why it is missing, the exit code says whether
to trust stdout. A truncated file now reads differently from an absent one.
stream_invoke iterated the event stream bare. internalServerException,
validationException and runtimeClientError are modelled `exception: true`, so
botocore sets a 400 and raises EventStreamError out of the iterator rather than
yielding a dict — the `elif "internalServerException" in event` branch could
never run, and any stream failure escaped into `finally`, ending the demo at
Part 1. Catch (ClientError, BotoCoreError): both are needed and neither covers
the other, since EventStreamError subclasses ClientError while ReadTimeoutError
subclasses BotoCoreError.
That makes Part 4's `Errors encountered:` reachable for the first time. It used
to sit above the except clause every real run took, and could only have printed
0 anyway, because the sole thing appending to that list was the unreachable
branch above. It now prints the service's own message, with an explicit else so
a bad URL that starts succeeding shows up instead of passing silently.
Part 3 is titled "MCP with Authentication" and printed "configured with the API
key", but the only line that would use the key was commented out — so it
demonstrated a config with no credential in it. `headers` is a real member of
remoteMcp (map<string,string>). Enabling it means the config dump below carries
a secret, so print a deep copy with every header value masked, honouring the
comment already there forbidding echoing the key. Masking by iteration, not by
header name, so switching to x-api-key needs no change here.
README: the `curl` used to test an MCP server failed against a healthy one —
missing the `Accept` header (406) and not a complete JSON-RPC request (-32700).
It appeared twice, once as the first troubleshooting step. The
`internalServerException in the stream` entry described an event that never
arrives; replaced with one keyed on the EventStreamError that does. Documented
`headers` properly, and added an entry for Part 5's new missing-report message
so it is not mistaken for a bug.
|
||
|
|
ceacf51e09 |
fix(01-harness): correct the gateway docs, harden the pollers, stop leaking the role (#1870)
The gateway-integration sample runs correctly end to end, but its docs describe a different sample than the one that ships, its gateway pollers cannot report the failures the service actually returns, and cleanup leaves the IAM role behind. Documentation - The README walked the reader through a "Create routing rule" step. A gateway routes to its targets as soon as they report READY -- no rule is needed to run this sample. Replaced with a note explaining that rules are a separate, optional feature for shaping traffic across multiple targets. - The Key Concepts heading read "IAM auth (`NONE` type)", which contradicts itself: NONE means no inbound authorizer, and AWS_IAM is a distinct value the sample does not use. Replaced with a "Two independent authorization sides" section and a table separating inbound (authorizerType on create_gateway) from outbound (credentialProviderConfigurations on create_gateway_target), so it no longer conflicts with 07-oauth, which configures both. - The README flow and the module docstring were numbered 1-7 while the script prints Step 0-4 plus cleanup. Renumbered both to match the real output. - Both described Step 3 as creating a harness "wired to the Gateway". No such binding exists: create_harness takes no gateway parameter, and the gateway ARN travels per-invoke in the `tools` argument to invoke_harness. Corrected, and stated explicitly, because the false coupling is what made the Clean Up section's ordering advice look justified. - The Clean Up snippet listed the deletes in an order that contradicted its own "targets before gateway" note, omitted the role deletion entirely, and implied the harness must go first because it references the gateway. Only the target-before-gateway constraint is real. - Added troubleshooting entries for HTTP 429 (the default Exa endpoint is keyless on a shared, rate-limited free tier, so a busy account can exhaust it; shows how to attach your own key as an outbound API_KEY credential provider), for the UPDATE_UNSUCCESSFUL / SYNCHRONIZE_UNSUCCESSFUL statuses, and for a target stuck in CREATE_PENDING_AUTH. Code - GATEWAY_POLL_TIMEOUT was 120s while the README told the reader to expect "2-3 minutes" -- the same contradiction between a local constant and the documented wait that #1863 fixed for the harness poller. Raised to 300s, matching 07-oauth's budget for these two operations. Measured: both the gateway and the target reach READY in about 5s, so the old ceiling only ever mattered on the slow runs it was least able to survive. - The pollers checked for the wrong terminal statuses. A gateway reports UPDATE_UNSUCCESSFUL, not FAILED, when an update fails, and a target adds SYNCHRONIZE_UNSUCCESSFUL; neither was treated as terminal, so a failed resource was polled until the timeout and the service's statusReasons was never shown. The target poller also tested for DELETE_FAILED, which is not in the target status enum at all -- a dead branch. Both now use the real enums, and the three *_PENDING_AUTH states fail fast with an explanation instead of spinning on a state that cannot clear by itself. - The stream handler surfaced only internalServerException. validationException and runtimeClientError are modelled stream events too, and falling through them printed nothing, so a rejected invoke looked like an agent with no answer. - REGION was read from AWS_DEFAULT_REGION alone, while utils/client.py deliberately honours AWS_REGION as well and treats an empty string as unset. A shell exporting AWS_DEFAULT_REGION="" therefore built a working harness_control and a gw_control that died on "Invalid endpoint: https://bedrock-agentcore-control..amazonaws.com". Now imports the shared REGION so both clients resolve identically. - _cleanup now deletes the execution role, guarded by created_role so that a role supplied via --role-arn is never touched. The role name is shared by every sample in this folder, so deleting a caller's role would destroy something the script does not own. Without this the role outlived every run. - Annotated the create_gateway and create_gateway_target call sites with which authorization side each parameter controls, dropped the now-unused os import, and moved the utils.iam import back into alphabetical order. Every API-shape claim added here was read off the botocore service model rather than inferred: the authorizerType and credentialProviderType enums, the apiKeyCredentialProvider members, the credentialLocation enum, the gateway and target status enums, the InvokeHarness stream event members, and the absence of any gateway parameter on CreateHarness. Verified live in us-west-2, five end-to-end runs, the last on the exact bytes committed here: gateway and target reach READY with no routing rule, the agent calls exa-search___web_search_exa through the gateway and returns real results, and teardown removes the harness, target, gateway, inline policy and role, leaving the account clean. Unit-checked against stubbed clients: both pollers for the happy, every terminal, every pending-auth, transient-passthrough and timeout paths; the stream handler for all three error events, tool-use, raw mode and the tools payload shape; and _cleanup for created_role=True, created_role=False, the default argument, and failure before any resource exists. The empty-region regression was reproduced before the fix and confirmed resolved after. Ruff check and format both pass; findings on the file go from 3 to 0. |
||
|
|
cce2ac62a6 |
fix(01-harness): stop 01-custom-containers prompting for tools its images lack (#1868)
* fix(01-harness): stop 01-custom-containers prompting for tools its images lack
The custom-container sample asks the agent to run three tools that are not
present in the images it attaches, and documents the resulting output as the
expected behaviour. Live runs of all three presets:
- python preset told the agent to `curl localhost:3000`, but
python:3.12-slim ships neither curl nor wget. Now asks for urllib.
- go Part 5 asked for the architecture "using 'file' command", but
golang:1.24 has no `file`. The agent burned a turn on the failure and
recovered with readelf ("Let me try with readelf as an alternative"),
so the demo passed while doing something other than what it documents.
Now asks for `ls -lh` + `readelf -h` directly: 3 shell calls -> 2.
- README's Architecture block advertised "use curl" for every image and
described the microVM as Node-only. It also documented no python prompt
at all, though --language python is a supported preset.
Also retries the first invoke_harness on RuntimeClientError (3 attempts,
15s apart). A harness reporting READY does not guarantee the container's
microVM answers health checks yet: one python run died with "Runtime
health check failed or timed out" while that runtime's own CloudWatch log
group recorded a clean startup and zero errors. Only the call is retried,
never a stream that already emitted output, so no text can print twice.
Verified live in us-west-2, all three presets deployed, invoked and torn
down (exit 0):
- go readelf on the first try, no fallback turn; ELF64 x86-64
- python agent used urllib, HTTP 200, no curl attempt
- node unchanged default path, node v26.5.1 / npm 11.17.0, chalk ok
Harness naming follows --language so Go/Python runs no longer create
resources labelled NodeContainer_*, which made leaked resources and their
auto-named managed memory indistinguishable in the console.
* style(01-harness): satisfy ruff format on the retry message
The `ruff format --check` step of the python-lint workflow rejected the
multi-line print() added for the RuntimeClientError retry: it used hanging
indent continuation where the formatter wants the parenthesized form.
Formatting only — the AST is identical before and after, so behaviour is
unchanged and the rendered message is byte-for-byte the same.
|
||
|
|
ec078649fa |
fix(os-actions): remove trailing slash from base_url to prevent doubl… (#1867)
* fix(os-actions): remove trailing slash from base_url to prevent double-slash 404s * fix(os-actions): suppress ruff BLE001/S110 on best-effort cleanup handlers |