Skip to content

fix(bb-agent): AgentCoreRuntime as registered Compute (E2E config fix for #368) - #486

Closed
soberm wants to merge 22 commits into
mainfrom
soberm/pr368-agentcore-runtime-config-fix
Closed

soberm wants to merge 22 commits into
mainfrom
soberm/pr368-agentcore-runtime-config-fix

Conversation

@soberm

@soberm soberm commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What

Fixes the E2E regression on #368 (Aurora / DSQL / Realtime — "not configured" / "Missing env" / "BLOCKS_RT_WS_URL not set"). Opened as a draft to run the full PR Checks workflow (incl. E2E Sandbox/Production) on #368's content + this fix.

Root cause

#368 moved the Agent's Aurora/DSQL/Realtime tool executions into a new AgentCoreRuntime container, which was a Scope — not a Compute. A rebase then pulled in #391 ("Retarget config registry to shared role + per-stack compute registry"), which changed finalizeConfigRegistry to stamp BLOCKS_CONFIG_BUCKET/BLOCKS_CONFIG_KEY only onto registered Computes. Being a Scope, the container fell outside that distribution and self-injected coordinates that drifted from the authoritative config location, so at runtime it loaded empty config and every config-backed block it hosts threw. KnowledgeBase passed because it's served by the registered Lambda compute.

(#368 was green pre-rebase — run 33088638803, commit 4f87436 — and went red only after the rebase, run 33651536435.)

Fix

AgentCoreRuntime now extends Compute (auto-registers via the base ctor) and implements setEnv, writing into a live container-env object the bedrock-agentcore Runtime L2 renders lazily by reference. finalizeConfigRegistry then stamps the container with the same authoritative bucket+key it uploads the app config to — the exact path the Lambda compute already uses. No parallel self-injection to drift.

Verification

  • npm run build + cdk synth green; all 7 AWS::BedrockAgentCore::Runtime resources carry BLOCKS_CONFIG_BUCKET/BLOCKS_CONFIG_KEY matching BlocksConfigDeployment; core + bb-agent unit tests pass.
  • Manually deployed the comprehensive sandbox and ran E2E — the previously-failing Database/DSQL/Realtime families execute against real resources and pass.

This PR is for CI validation; the fix commit can be cherry-picked onto #368.

pjkroker and others added 21 commits September 2, 2026 17:53
…ization

If initialize() throws (most often because loadConfigToProcessEnv() can't read
blocks-config.json during the brief post-deploy window before it's readable), the
memoized rejected initPromise poisoned the container for its whole lifetime — every
later request re-awaited the same rejection and 500'd. Now a failed init resets the
promise so the next request retries; config.ts also stops caching a not-found result.
The handler self-heals once config is available.
Covers the init-retry + config-404 no-cache fix: a first-request init failure
must be retried on the next request (not cached as a poisoned rejection), and a
not-found config result must not be cached (later fetch picks up the real config).
Both fail on the pre-fix behavior.
…t to the Agent BB

Per review: core shouldn't trust a specific compute by default. Remove the bedrock-agentcore
assume-role statement from BlocksRole; the Agent BB adds it from its own CDK construct (where the
AgentCore Runtime that runs AS the role is provisioned). The role keeps its lambda CompositePrincipal
so a compute-BB can add its own trust principal. Test now asserts core trusts only lambda by default.
…JSDoc

In common/index.ts the class is named BlocksStack; BaseBlocksStack is only the alias the CDK layer
imports it under. Reference the contract as named here to avoid confusing a reader of this file.
Split the config bucket's creation into an eager, memoized getConfigLocation(scope) that both
finalizeConfigRegistry (handler path, unchanged) and other compute running as the shared role can use.
This lets the Agent BB's AgentCore Runtime inject BLOCKS_CONFIG_BUCKET/KEY at construction so its
loadConfigToProcessEnv() loads the same app config as the handler (fixes tools using config-backed BBs
failing in the runtime). No IAM change — the config-read grant sits on the shared role.
…ation

README: replace stale AsyncJob/SQS references with the AgentCore Runtime (stream() prose,
the provisions table + a runtime note, consumption-based cost line with the AgentCore pricing
link, and a See Also link); drop the now-redundant timeout bullet.
DESIGN: reflect the trimmed Realtime grant (Query + BatchWriteItem), the injected env vars
(no session-bucket name — re-derived in-process), and keep the Architecture section compute-neutral.
… transport in README

- Mark AgentTurnPayload / invokeTurn / dispatchTurn @internal — the turn-dispatch seam is not
  customer API (customers use stream()/resume()). The type stays exported only so api-extractor can
  resolve the protected dispatchTurn signature; it's no longer part of the public surface. API.md regenerated.
- README: the Realtime BB provisions an API Gateway WebSocket, not AppSync Events — correct the cost
  bullet to match DESIGN and the provisions table.
The mock now runs the loop in-process via the base dispatchTurn (fire-and-forget invokeTurn);
there is no AsyncJob to propagate the error through. Update the comment to match.
…CallbackUrl()

Follows the core + bb-realtime review changes:
- The shared role's bedrock-agentcore assume-role trust is now added here (in AgentCoreRuntime's
  once-per-stack guard), not in core — so a Realtime-only app never trusts AgentCore. Scoped by
  aws:SourceAccount/aws:SourceArn. Narrows executionRole to concrete iam.Role (fail loud otherwise).
- Drops the redundant Realtime grant: the loop runs AS the shared role, which already holds the
  publish permissions via the Realtime child's handler wiring. Gets the callback URL to inject from
  realtime.publishCallbackUrl() instead of grantPublish's return value.
- Test asserts the shared role gains the scoped bedrock-agentcore trust when an Agent is synthesized;
  comments/DESIGN updated to reflect inherited Realtime publish + construct-owned trust.
…itions)

Pre-existing lint error (lint/style/useImportType) surfaced by the full-file lint: z is used only as
z.infer<...> in agent.ts, so the import must be 'import type'.
… error surfaces

- Add agentcore-bundle.test.ts: runs bundleAgentCoreAsset() over a trivial fixture and asserts the
  asset shape (main.js + CJS banner, {"type":"commonjs"}, non-empty _deps/) — the co-bundle path
  was previously only exercised by a sandbox e2e (CDK tests pass a pre-built agentcoreAssetPath).
  Wired into the package test script.
- Document that stream()/resume() surface errors on two paths: loop failures as an 'error' channel
  chunk once dispatched, vs a dispatch failure (e.g. AWS runtime can't be invoked) rejecting the
  awaited call itself.
The runtime?: 'agentcore' AgentConfig field had a single value and no reader — settable with no
observable effect. Drop it (unreleased) rather than ship speculative public surface; a real runtime
option can be re-added non-breakingly when a second value exists. Removes the field + its DESIGN
paragraph + changeset bullet; API.md regenerated.
The internal BBs are created on the Agent scope (this), not a parent scope, in both the runtime and
CDK layers. Reword the note to the real invariant: same child ids across layers → same fullId → same
derived physical names, so the deployed loop resolves the CDK-provisioned resources.
…review)

Inject BLOCKS_CONFIG_BUCKET/BLOCKS_CONFIG_KEY (via core's getConfigLocation) so the container's
loadConfigToProcessEnv() loads the same app config as the handler. Without it the container ran with
empty config, so tools using config-backed Building Blocks (secrets/flags/endpoints via registerConfig)
failed in the runtime. IAM to read the blob is inherited via the shared execution role.

This subsumes the callback-URL handoff: BLOCKS_RT_CALLBACK_URL is in that config blob (registered by the
Realtime BB), so the explicit injection and the Realtime publishCallbackUrl() dependency are removed
(the realtime prop is dropped from AgentCoreRuntime). Retires PR #367 entirely.
…ent-source.ts

- index.cdk.test.ts's file-level docstring still described the abandoned 'dedicated/separate role'
  design; rewrite it to the shipped model (runtime runs AS the shared BlocksRole) — matching the
  assertions in the file.
- Delete job-event-source.ts: the AsyncJob removal left INTERACTIVE_JOB_EVENT_SOURCE with no importers
  (dead code, not published in files, no deps).
The Agent no longer runs on an internal AsyncJob, so it no longer registers jobQueueUrl; the umbrella
overload still declared it (a field that resolved to undefined). Remove it + regenerate API.md; add a
@aws-blocks/blocks changeset.
…ME (Pranav review)

The AgentCore Runtime set BLOCKS_STACK_NAME to cdk.Stack.of(this).stackName, but the handler and the
Lambda compute use backendStackName (the owning BlocksStack/BlocksBackend canonical root id). They
coincide for a top-level BlocksStack but diverge for a BlocksBackend embedded in a customer stack
(backend fullId vs customer stack name) — so the container would rebuild resource names from the wrong
namespace and miss its own tables/bucket/config. Use this.backendStackName to match.
… wire boundary (John review)

Locally the loop runs in-process, so a tool received the live context object; on AWS context is
JSON-serialized into the InvokeAgentRuntime payload and re-parsed (z.unknown passthrough), so a tool
gets a JSON-mangled context (Date→string, Set→{}, undefined dropped). Round-trip context in the mock
dispatchTurn so a serialization bug fails in local dev instead of only after deploy. Adds a regression
test pinning the boundary.
…ves config coordinates

The AgentCore container hosts the Aurora/DSQL/Realtime tool executions but
was a Scope, not a Compute. After #391 retargeted the config registry to
stamp BLOCKS_CONFIG_BUCKET/KEY only onto registered computes, the container
fell outside that distribution and self-injected coordinates that drifted
from the authoritative location — so at runtime it loaded empty config and
those blocks threw 'not configured' / 'Missing env' / 'BLOCKS_RT_WS_URL not
set'. KnowledgeBase passed because it is served by the registered Lambda
compute.

AgentCoreRuntime now extends Compute (auto-registers) and exposes setEnv,
writing into a live container env object the bedrock-agentcore Runtime L2
renders lazily by reference — so finalizeConfigRegistry stamps the same
authoritative bucket+key it uploads the app config to, via the same path
the Lambda compute uses. The isolated CDK test now finalizes config through
the production path.

Verified: build + cdk synth green; all 7 Runtime resources carry
BLOCKS_CONFIG_BUCKET/KEY matching BlocksConfigDeployment; core + bb-agent
unit tests pass.
@changeset-bot

changeset-bot Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 59f243b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@aws-blocks/bb-agent Minor
@aws-blocks/blocks Patch
@aws-blocks/core Minor
@aws-blocks/bb-logger Patch
@aws-blocks/bb-kv-store Patch
@aws-blocks/bb-distributed-table Patch
@aws-blocks/auth-common Patch
@aws-blocks/bb-app-setting Patch
@aws-blocks/bb-data Patch
@aws-blocks/bb-distributed-data Patch
@aws-blocks/bb-auth-basic Patch
@aws-blocks/bb-auth-cognito Patch
@aws-blocks/bb-auth-oidc Patch
@aws-blocks/bb-realtime Patch
@aws-blocks/bb-async-job Patch
@aws-blocks/bb-dashboard Patch
@aws-blocks/bb-cron-job Patch
@aws-blocks/bb-file-bucket Patch
@aws-blocks/bb-knowledge-base Patch
@aws-blocks/bb-email-client Patch
@aws-blocks/bb-tracer Patch
@aws-blocks/bb-metrics Patch
@aws-blocks/bb-lambda-compute Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@soberm soberm closed this Sep 3, 2026
@soberm soberm reopened this Sep 3, 2026
@soberm
soberm marked this pull request as ready for review September 3, 2026 12:50
@soberm
soberm requested a review from a team as a code owner September 3, 2026 12:50
@soberm
soberm changed the base branch from agentcore-rt/1-core to main September 3, 2026 12:53
@soberm soberm closed this Sep 3, 2026
@soberm soberm reopened this Sep 3, 2026
The handler's post-deploy self-heal never fired on the common case. On a
404 for blocks-config.json (the transient window before the BucketDeployment
is readable), config.ts returns {} WITHOUT caching — but loadConfigToProcessEnv()
then returns normally, so initialize() SUCCEEDS, handler is assigned, and the
'if (!handler)' request guard is false forever. The container serves 500
'not configured' for its whole life; the self-heal only fired on the rare
non-404 throw branch.

Fix: config.ts exposes isConfigResolved() (configCache !== null — uniquely true
after a real success or a legitimately-empty/no-bucket load, false only after a
transient-404 miss which declines to cache). initialize() now throws a typed
TransientConfigError when the config coordinates are set but the load didn't
resolve, so the existing catch resets initPromise and the next request re-runs
initialize() and re-fetches — self-healing once the blob is present. A
config-less app (no bucket) or a genuinely-empty {} config both cache their
result, so they never throw or spin.

Adds tests: transient-empty recovers on next request (backend not imported
against empty config); config-less and genuinely-empty apps do not re-init or
re-fetch. core: 745 tests pass.
@soberm soberm reopened this Sep 3, 2026
@soberm soberm closed this Sep 4, 2026

This branch was previously deployed

1 inactive deployment
publish — 59f243bf Deployed Sep 3, 2026 by soberm via bench / summary #1284
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants