1
0
mirror of synced 2026-09-01 00:05:26 +00:00

102 Commits

Author SHA1 Message Date
Visakh Madathil 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>
2026-08-31 13:40:48 -04:00
Akarsha Sehwag 33c0e568a9 chore(memory): cleanup old usage of namespaces (#2005) 2026-08-31 13:28:43 -04:00
Rui Cardoso 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.
2026-08-31 09:33:21 -04:00
Visakh Madathil 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>
2026-08-28 13:24:44 -07:00
Akarsha Sehwag 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>
2026-08-28 11:07:10 -04:00
Akarsha Sehwag 067c65be52 feat(memory): update checkpointer usage (#2001)
* feat(memory): update checkpointers

* feat(memory): update docs

* update docs

* update reqs

* fix ruff

* ruff fix
2026-08-27 17:50:01 -04:00
Robert Belson 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>
2026-08-26 10:40:31 -04:00
Robert Belson 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>
2026-08-26 10:39:37 -04:00
Eashan Kaushik b4d30e5bc7 fix(policy): resolve deploy issues in 01-tool-access-with-policy (#1992) 2026-08-25 22:25:56 -04:00
Fahad Farrukh 9912ac0f2d docs(payments): document Coinbase Quick Create CLI flow and marketplace subscription (#1991) 2026-08-25 19:23:50 -04:00
Diego Brasil 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.
2026-08-25 11:22:30 -04:00
jyotsnamas 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>
2026-08-24 12:25:33 -04:00
jyotsnamas 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>
2026-08-24 12:24:53 -04:00
jyotsnamas 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>
2026-08-24 12:24:32 -04:00
jyotsnamas 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>
2026-08-24 12:24:17 -04:00
rajolishruthi 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>
2026-08-21 17:01:41 -07:00
Akarsha Sehwag 9001866298 feat(memory): add directIngestion sample (#1980)
* feat(memory): add directIngestion sample

* update readme

* update ruff
2026-08-21 18:06:26 -04:00
Akarsha Sehwag 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
2026-08-21 15:00:21 -07:00
Visakh Madathil 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>
2026-08-21 14:59:03 -07:00
Diego Brasil 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.
2026-08-21 16:20:23 -04:00
Chris Wajule 8f929e6491 feat(payments): add Tutorial 09 — pay per use with the x402 upto sc… (#1967) 2026-08-19 21:46:50 -04:00
praven80 f96b2f982f feat: Add MPP tutorial (#1969) 2026-08-19 19:29:56 -04:00
mvangara10 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>
2026-08-18 17:01:15 -07:00
Ratnopam 967575fba6 feat(gateway): verify Coinbase Bazaar curation (#1949) 2026-08-18 16:53:47 -04:00
mvangara10 7879051081 AgentCore payments is GA (#1964) 2026-08-18 15:37:27 -04:00
Diego Brasil 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.
2026-08-17 16:17:52 -04:00
wirjo c36db47314 feat: add OpenClaw Agent with AgentCore Payments (#1797) 2026-08-17 12:32:25 -04:00
satveerkhurpa 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>
2026-08-14 10:42:48 -05:00
Eashan Kaushik 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
2026-08-13 16:01:47 -05:00
Chris Wajule 3eb2a56f23 Add pay-for-x402-secure-data use case (feature) (#1782) 2026-08-11 19:55:57 -04:00
gunaven 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.
2026-08-09 14:06:42 -04:00
gunaven 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.
2026-08-09 14:06:29 -04:00
Eashan Kaushik 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)
2026-08-07 12:50:49 -04:00
sg-nitd 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>
2026-08-07 08:33:48 -07:00
Evandro Franco 7a3ca0899f capacity provider samples (#1927)
* capacity provider samples

* capacity provider samples
2026-08-07 11:25:27 -03:00
sg-nitd 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>
2026-08-06 13:23:27 -07:00
Achintya Pinninti 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
2026-08-06 13:21:41 -07:00
Erez Weinstein 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>
2026-08-06 16:08:39 -04:00
mchaitra fa72a1ed57 Add 04-migrate-to-new-namespace directory for registry namespace migration sample (#1886) 2026-08-03 17:03:48 -07:00
Guruprasad 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
2026-08-03 16:37:47 -04:00
Rui Cardoso 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.
2026-08-03 09:18:21 -03:00
Rui Cardoso 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.
2026-08-03 09:17:50 -03:00
Rui Cardoso 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.
2026-08-03 09:17:04 -03:00
Rui Cardoso 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.
2026-08-03 09:16:40 -03:00
Rui Cardoso 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.
2026-08-03 09:16:06 -03:00
Rui Cardoso 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.
2026-08-03 09:15:35 -03:00
Rui Cardoso 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.
2026-08-03 09:14:53 -03:00
Rui Cardoso 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.
2026-08-03 09:14:25 -03:00
Rui Cardoso 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.
2026-08-03 09:13:50 -03:00
Bharathi Srinivasan 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
2026-07-31 16:14:29 -07:00