Skip to content

feat(workers): add Amazon Bedrock AgentCore as a compute provider - #3884

Merged
rossnelson merged 18 commits into
mainfrom
agentcore-compute-provider
Sep 14, 2026
Merged

rossnelson merged 18 commits into
mainfrom
agentcore-compute-provider

Conversation

@rossnelson

@rossnelson rossnelson commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Description & motivation 💭

Adds Amazon Bedrock AgentCore as a compute provider in the serverless Worker forms.

Create Edit
05a-form-create 05-form-edit
deployment worker-instances deployment-versions
01-deployment-list 02-worker-instances 03-deployment
version-details workflows workflow-details
04-version-expanded 06-workflows-by-version 07-workflow-detail

The provider itself is already merged upstream — temporal-auto-scaled-workers#117 / #118 added it, and cli#1177 shipped the --aws-agentcore-* flags in v1.8.3. This is the UI catching up.

AgentCore is invoke-based in temporal-auto-scaled-workers, so it pairs with the no-sync scaler and takes the same assumed-role Access fields as Lambda. It differs from Lambda in exactly one payload key: endpoint_arn rather than arn.

Who decides which providers are offered

Self-hosted offers all of them, selectable. There is no per-account entitlement to express, and a Service that cannot run a provider rejects the Version with a reason — so gating the picker would only hide a choice behind a badge nobody can act on.

Restriction belongs to whichever caller has grounds for it, passed in as providers. Temporal Cloud already does this: it derives the list from the Namespace's own cloud, so an AWS Namespace offers Lambda and AgentCore while a GCP Namespace offers Cloud Run. That path is untouched here — Cloud always passes the prop, so it never used these defaults.

⚠️ This changes existing behaviour for Cloud Run. It was previously gated on serverScaledProviderCloudRun and rendered "Coming Soon" on self-hosted. Since no server has ever set that capability, Cloud Run has been permanently unselectable there. It is now selectable, and a Service that cannot run it says so on create.

That also let the local Capabilities augmentation go: nothing gates on a provider capability any more, so the UI no longer depends on a field that does not exist, and does not need the one temporalio/api#867 replaced it with.

Three judgment calls worth a look

1. The field is labeled "Agent Runtime Endpoint ARN", not "Agent Runtime ARN".

The provider parses the runtime id and endpoint name out of the ARN and requires the four-part resource runtime/<id>/runtime-endpoint/<name> — see parseAgentCoreEndpointARN in temporal-auto-scaled-workers. A bare Runtime ARN fails validation server-side, so the design mock's label would have sent people down a dead end. The schema validates that shape up front, and it is checked against an ARN AWS actually generated.

2. The CloudFormation / Terraform role helper stays Lambda-only.

Both templates grant lambda:InvokeFunction. Reusing them would hand out a role that cannot invoke an AgentCore runtime. AgentCore gets the IAM Role ARN and External ID fields with no helper accordion.

⚠️ This leaves a gap: there is no equivalent IAM setup path for AgentCore yet. Worth deciding whether that blocks enabling the provider, or lands separately.

3. buildComputeConfigFromForm replaces the per-page provider ternaries.

The three create/edit pages each carried their own data.provider === 'cloud-run' ? gcp : lambda, which a third provider would have turned into three three-way branches. Mapping a provider onto a ComputeConfig is now one function in shared.ts.

Also fixed here: the Version edit form

A Version's provider cannot be changed, so the edit form now shows only the provider in use. Previously it fell through to the shared default list, which — combined with the old capability gating — rendered the provider a Version was actively running on as selected, disabled, and badged "Coming Soon" at the same time.

lockComputeProvider also failed closed when the configured provider was disabled or hidden, producing the same fallback on the create page. It now declines only when configuration does not know the provider at all.

Neither bug was AgentCore-specific: editing a Cloud Run Version on self-hosted hit both.

Screenshots (if applicable) 📸

Storybook covers the picker with no server: Workers/Compute Provider Picker → "Self-hosted (every provider selectable)" and "Locked to the provider a Version uses".

Design Considerations 🎨

  • The label change in (1) departs from the mock. It is driven by what the backend accepts.
  • The missing AgentCore IAM setup helper in (2) is an open question for Design/Docs.
  • Cloud Run becoming selectable on self-hosted is a behaviour change, called out above.
  • One unverified detail: the "Open AgentCore Console" button points at https://console.aws.amazon.com/bedrock-agentcore. Someone with console access should confirm that path.

Testing 🧪

How was this tested 👻

  • Manual testing

  • E2E tests added

  • Unit tests added

  • svelte-check: 0 errors across 885 files

  • pnpm lint: 0 errors

  • Full unit suite under TZ=UTC (what CI uses): 3261 passing, 2 skipped, no unhandled errors

Driven end to end against a real Bedrock AgentCore runtime in us-west-2, not just unit-tested — and the Version was created through this form, not by the CLI:

The form created Version run-1789000863665
The Worker inside AgentCore dialed back through the tunnel and registered "agentcore-tq"
Greet completed on the AgentCore Worker and returned:
  "hello demo from AgentCore worker on localhost"

Nothing polled from the machine running it, so only a Worker AWS started could have run that workflow.

New coverage:

  • deployments-service.test.ts — AgentCore config writes endpoint_arn (not arn), pairs with no-sync, round-trips through its decoder, and the Lambda/AgentCore decoders do not cross provider types.
  • shared.test.ts — endpoint ARN required, a bare Runtime ARN rejected, role and external id required as with Lambda.
  • compute-fields.test.ts — renders the endpoint ARN field not the Lambda one, reuses the Access fields, hides the Lambda IAM helper, and offers invoke-based scaling rather than replica controls.
  • lock-compute-provider.test.ts — 23 tests, up from 15: locks to AgentCore when a Version uses it, and keeps a configured provider selectable where gating would previously have disabled or hidden it.

One thing worth calling out, since it cost a CI round: version-compute-details.svelte calls decodeAgentCoreProviderDetails, and deployment.svelte.test.ts swaps the real deployments-service for deployments-service-client-test-double.ts. The missing export there surfaced as an unhandled rejection rather than a failed assertion — every test still reported passing while the job exited 1. If you add an export a component consumes, the test double needs it too.

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

Storybook needs nothing. For the real thing, this branch carries a one-command scenario (#3892, merged into this branch):

pnpm demo start agentcore-serverless-worker
agentcore

It provisions the AgentCore runtime, opens a tunnel so the Worker can dial the frontend back, drives this form with Playwright to create the Version, then waits for the Worker to register and runs a workflow on it. It needs AWS credentials that can reach bedrock-agentcore, ECR and IAM; it names the exact policies if any are missing, and prints teardown commands for everything it creates.

The form is the point. The CLI could already create an AgentCore Version before this PR existed, so a scenario that shelled out to create-version would prove nothing about the change under review. createVersion: 'cli' falls back for a machine with no browser, and headed: true shows the browser doing it.

Two things the run surfaced that are worth knowing while reviewing:

  • The Access fields are load-bearing. require_role_and_external_id: false makes the IAM role optional, not ignored — the server assumes whatever role the config carries. The CLI can omit it with --aws-agentcore-skip-role-and-external-id; this form requires both fields and always sends them, so it needs a role that genuinely assumes. The scenario provisions one. That leaves a real gap, tracked separately: a self-hosted operator who turned the requirement off can create a Version by CLI and cannot by form.
  • Each run needs its own deployment, because of the server-side issue noted below. Once a deployment's Versions all go inactive, the server stops reporting their ComputeConfig, lockComputeProvider cannot name a provider it cannot see, and this page renders an error where the form should be. That behaviour is unchanged from main and affects Lambda and Cloud Run identically — it is not introduced here — but it is easy to hit twice in a row.

Checklists

Draft Checklist

  • Confirm the AgentCore console URL
  • Decide whether an AgentCore IAM setup helper (CFN/Terraform) blocks enabling the provider
  • Confirm Cloud Run becoming selectable on self-hosted is wanted in this PR, or should be split out

Companions:

Merge Checklist

Issue(s) closed

Docs

Any docs updates needed?

Serverless worker docs will need an AgentCore section, including that the Resource field takes the Runtime Endpoint ARN.


Not in this PR

  • Saving a Version end to end needs a server whose auto-scaled-workers dependency contains 312f95f (2026-08-24), which registers the aws-agentcore provider. temporalio/temporal main still pins v0.0.0-20260811170210. The scenario on this branch bumps a fetched checkout itself, which is the only reason the end-to-end run above was possible.
  • RFC: report compute providers and release stage per Namespace api#867 proposes reporting providers and their release stage per Namespace, which would let the hardcoded defaultReleaseStage table in shared.ts go away. Not a dependency of this PR.
  • The server drops ComputeConfig from inactive Version summaries, so superseded Versions show no compute provider, and a deployment whose Versions have all gone inactive cannot be added to through this form. Server-side, unchanged from main, and affects Lambda and Cloud Run too. Tracked separately.

Adds AgentCore to the serverless Worker forms, following the sequence Cloud
Run used: the UI ships gated behind a capability the api protos do not carry
yet, so OSS shows Coming Soon until a server advertises it, and cloud-ui can
enable it earlier by mapping an account feature flag into its synthesized
systemInfo capabilities.

AgentCore is invoke-based in temporal-auto-scaled-workers, so it pairs with
the no-sync scaler and takes the same assumed-role Access fields as Lambda.
It differs in one payload key: `endpoint_arn` rather than `arn`. The value is
the Runtime *Endpoint* ARN, since the provider parses the runtime id and
endpoint name out of it and rejects a bare Runtime ARN.

- Capability gate via `serverScaledProviderAgentCore`, declared as a local
  intersection on Capabilities until the proto field lands.
- Resource field validates the four-part runtime-endpoint ARN shape.
- The CloudFormation/Terraform role helper stays Lambda-only: it grants
  lambda:InvokeFunction and would hand out a role that cannot invoke a runtime.
- buildComputeConfigFromForm replaces the per-page provider ternaries, so
  mapping a provider to a ComputeConfig is one branch rather than three.
- Adds a feature-demo scenario recording what a reviewer checks and what the
  enabled path additionally requires from api and server.
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
holocene Ready Ready Preview Sep 14, 2026 5:21pm UTC

Request Review

…double

version-compute-details.svelte calls it, and deployment.svelte.test.ts
swaps the real deployments-service for the client test double, so the
missing export surfaced as an unhandled rejection during that test rather
than a failed assertion.
Editing a Version whose provider the Service does not advertise rendered that
provider selected, disabled, and badged "Coming Soon" all at once, which says
the Version is running on something unavailable.

Capability gating exists to stop somebody choosing a provider the Service
cannot run. A Version already running on one is proof it works, so gating must
not describe it as unavailable. lockProvidersTo makes the provider in use
visible and selectable, hides the rest because a Version's provider cannot be
changed, and keeps the release stage, which stays true either way.

Two causes, both fixed:

The edit page never locked at all. lockComputeProvider was only wired to the
create page, so editing fell through to the capability-gated default list.

lockComputeProvider itself failed closed when the configured provider was
disabled or hidden, which produced the same fallback on the create page. It
now only declines when configuration does not know the provider at all.

Not AgentCore-specific: Cloud Run hits this on OSS today, since no server
advertises server_scaled_provider_cloud_run.
…ersion

The previous commit ungated the provider a Version uses, but also hid every
alternative on the reasoning that a Version's provider cannot be changed. It
can: provider.type is an accepted update path on
UpdateWorkerDeploymentVersionComputeConfig, so switching a Version to another
provider is a real choice, and hiding them removed a capability the edit form
had before.

allowProviderInUse now ungates only the provider in use and leaves the
alternatives exactly as configured, so one the Service can run stays offered
and one it cannot stays refused. lockProvidersTo keeps the hiding behaviour and
is used where the provider is inherited rather than chosen: creating a new
Version in an existing Deployment.
…in Cloud

Capability gating was the wrong mechanism, and it produced the visible
nonsense: a provider selected, disabled, and badged "Coming Soon" at once.

Self-hosted has no per-account entitlement to express, and a Service that
cannot run a provider rejects the Version with a reason, so gating the picker
only hid a choice behind a badge nobody could act on. The default list is now
every provider, selectable.

Restriction belongs to the caller that has grounds for it. cloud-ui already
passes `providers` derived from the Namespace's own cloud, so an AWS Namespace
offers Lambda and AgentCore and a GCP Namespace offers Cloud Run. That path is
untouched: it always passes the prop, so it never used these defaults.

A Version's provider cannot be changed, so the edit form shows only the
provider in use rather than alternatives that cannot be applied. The previous
commit had this backwards, reading an accepted update-mask path as product
behaviour.

This also removes the local serverScaledProviderAgentCore augmentation on
Capabilities. Nothing gates on it now, so the UI no longer depends on a
capability field that does not exist and that temporalio/api#867 no longer
proposes.
* feat(demo): model the AgentCore serverless Worker as a runnable scenario

Adds the scenario, plus three capabilities the demo tool needed to express it.

requires.serverModules
  serverCommit cannot describe a feature that reaches the server through a
  dependency bump, because the commit lives in another repository. This checks
  the go.mod of the checkout being built. Pseudo-versions order by their
  embedded commit timestamp, and a tagged release satisfies a pseudo-version
  floor outright.

requires.commands, and a preflight
  A missing tool is cheap to detect and expensive to hit halfway through a
  build, or worse after a provider has been handed an address. Stages imply
  their own tools, so the tunnel stage requires ngrok without the definition
  restating it, and a workspace build requires go and git.

A tunnel stage
  A server-scaled Worker dials the frontend back to poll, and localhost is not
  reachable from a cloud provider. The outbound leg to the provider was never
  the problem. Scenarios read the address as context.publicAddress.

Failures carry remedies. remedy.ts formats what was attempted, the tool's
verbatim output, what to try, and where to look. ngrok error codes map to
concrete fixes where we know them, and to their documentation URL where we do
not: an invented remedy is worse than none.

The scenario resolves its own CLI rather than trusting PATH. The
--aws-agentcore-* flags arrived in 1.8.3, and a package-manager CLI is easily
older than the repository pins; this machine had 1.8.2 on PATH against 1.8.3
in bin/cli, which would have failed on an unknown flag.

It does not provision AWS. An AgentCore runtime bills while it exists, and
starting a demo should not create billable cloud resources as a side effect, so
it takes an endpoint ARN and the README covers provisioning.

* fix(demo): derive the workspace Go directive, and repoint runtimes correctly

Three problems the first real run of agentcore-serverless-worker exposed.

The go.work directive was a literal `go 1.26.4`. Go refuses a workspace whose
directive is below a member module's, and temporalio/cli has since moved to
1.26.5, so every source: 'workspace' scenario was one Go bump away from
failing. It now follows the higher of the two checkouts.

update-agent-runtime replaces a runtime rather than patching it, so sending
only --environment-variables fails argument validation. The scenario now reads
the current role, artifact, and network configuration and sends them back
unchanged, instead of telling the reader to assemble the command by hand.

'tunnel' was missing from STAGES, so --skip tunnel and --only tunnel were
rejected and the stage never appeared in a definition's stage list.

Also fixes a preflight test helper that passed at runtime while failing to
typecheck, which had hidden the STAGES errors from tsc.

* fix(demo): make the tunnel prove its address before handing it out

Children append to their log, and the tunnel's public address is read back out
of that log, so a previous run's url satisfied the wait before this run's ngrok
had written anything. The stage reported a plausible address, the scenario
configured a provider with it, and the failure surfaced inside an AWS container
as a connection refused, far from its cause. A stale address is syntactically
perfect, so it read as success.

Two fixes, because the second is the one that matters:

- The log is removed before the child starts, so the address can only come from
  this run.
- The address is proved with a TCP connect before it is returned. A tunnel that
  reports a url and carries no traffic is indistinguishable from a working one
  until something remote fails to dial it, and that covers more than this bug:
  a tunnel that never established reports a url too.

Found by running the scenario twice, which its own preview notes call for.

* feat(demo): optionally provision the AgentCore runtime

The scenario can now create what it needs instead of refusing when no
endpoint ARN is given. Off by default: an AgentCore runtime bills while it
exists, and starting a demo should not create billable cloud resources by
surprise.

Provisioning is idempotent by name, so repeated runs reuse one runtime rather
than adding another, and it checks that the calling identity can do every part
before creating anything, so a missing permission does not leave an account
half provisioned.

When access is missing it names the policies to attach rather than passing the
raw AccessDenied along. That includes the trap this cost us: the ECR managed
policies are named AmazonEC2ContainerRegistry*, so searching the IAM console
for "ecr" returns nothing useful, and the guidance says to search
ContainerRegistry instead. It also offers the alternative of asking someone for
an endpoint ARN rather than granting the permissions at all.

Nothing is torn down at the end. A scenario's shutdown gets a three second
grace, which is not enough to delete a runtime, and a reviewer wants the demo
to still exist when the run finishes, so the summary lists what was created and
the commands to remove it.

Creating a runtime immediately after creating its execution role fails with an
error that blames ECR permissions rather than IAM propagation, which sends the
reader to the wrong place, so that call retries for a minute.

The Worker image source ships with the scenario, including its own README on
the two requirements that are easy to miss: AgentCore only accepts arm64, and
a Worker with UseVersioning must give every workflow a VersioningBehavior or it
panics before it polls.

* feat(demo): apply unmet module requirements instead of reporting them

requires.serverModules declared what the server needed and then handed the
work back, telling a person to go and edit a checkout this tool had fetched
into its own cache. The point of a requirement is that the tool satisfies it.

A checkout this tool fetched now gets go get and go mod tidy run on it. A
server ref would not solve this: the bump is a one-line go.mod change nobody
has pushed to temporalio/temporal, and requiring a pushed branch to demo a
feature is a heavier prerequisite than bumping a throwaway checkout.
serverModules already names the exact version, so nothing else was needed.

A checkout somebody pointed TEMPORAL_SERVER_REPO at is their working tree and
is never modified. RepoSource carries whether the tool owns it, and the
unowned case fails with advice to bump it there or to unset the variable.

Two ways this could have gone quietly wrong. The bump changes checkout
content, which the build cache is keyed on, so the key folds in the module
requirements or a pre-bump binary is reused against a bumped go.mod. And go
mod tidy can lower a requirement another module constrains, so it is
re-verified rather than assumed.

Verified with TEMPORAL_SERVER_REPO unset: the tool fetched temporalio/temporal
at main (0405f547a), bumped auto-scaled-workers, built, and ran through to a
workflow completing on the AgentCore-hosted Worker.

* feat(demo): let a scenario refuse before anything expensive runs

Running the AgentCore scenario cold with nothing configured reported the
missing endpoint ARN from the scenarios stage, which is after fetching two
repositories, bumping a module, and compiling a server. A person waited five
minutes to be told something a string comparison knew at once.

Scenario gains an optional preflight, called before any stage. The AgentCore
scenario uses it for both of its prerequisites: that it has somewhere to point,
and, when provisioning, that the AWS identity can do every part. Neither needs
a running server, so neither should cost a build. Cold with nothing set now
fails in about two seconds.

AGENTCORE_PROVISION=1 opts in for a single run, so a reviewer does not edit a
tracked file and have to remember to revert it. The source resolution is shared
between preflight and run so they cannot disagree about what the scenario
needs.

Verified from nothing: no AWS resources, no .feature-demo, no environment. The
run fetched both repositories, bumped auto-scaled-workers, built the server,
opened a tunnel, created the ECR repository, built and pushed the arm64 Worker
image, created the execution role, created the runtime, and ran a workflow to
completion on the Worker inside AgentCore. The IAM propagation retry fired,
so it was not defensive.

* fix(demo): refuse before provisioning, and default provisioning on

Three things a fresh run exposed.

Provisioning is on by default for this scenario. It cannot run without a
runtime, and a demo whose first run fails is not a demo. The option remains,
AGENTCORE_PROVISION=1 turns it on for a single run, and an explicit endpoint
ARN still wins.

The tunnel guard moved above provisioning. Running with --only scenarios
created an ECR repository, an IAM role, and an AgentCore runtime, and then
refused because the tunnel stage had not run. Nothing billable should be
created for a run that cannot finish.

Preflight now checks that the runtime an endpoint ARN names actually exists.
An ARN is a string, so having one proves nothing about whether it resolves: a
stale one from a previous run looks entirely valid, silently shadows
provisioning because an explicit ARN takes precedence, and previously failed
only after a server build and a tunnel. It now fails in about two seconds and
says to unset AGENTCORE_ENDPOINT_ARN.

* fix(demo): create the AgentCore runtime with its environment already set

create-agent-runtime returns as soon as the request is accepted, and the
service rejects updates and invokes until the runtime leaves CREATING. The
scenario then immediately pointed the new runtime at the tunnel and got a
ConflictException. Whether that happened depended on how fast the rest of the
run was, so an earlier cold run passed on timing luck.

The tunnel is already open when provisioning runs, so the address goes in at
creation instead. That removes the racing update rather than timing around it,
and saves waiting for a second rollout. A reused runtime carries whatever a
previous run left on it, so that path still updates, and provisioning now
waits for READY either way.

An update that loses a race anyway reports the runtime as busy and says to run
again, rather than listing permissions that are not the problem.

* fix(demo): build the embedded frontend assets before the ui-server

The ui-server embeds the built frontend with //go:embed all:assets, and a Go
embed of a missing directory is a compile error, not an empty filesystem. A
worktree that has never built them cannot build the binary at all:

  ui/embed.go:8:12: pattern all:assets: no matching files found

The stage now runs pnpm build:server, which writes them to server/ui/assets,
when that directory is absent. Any fresh worktree hit this, so it belongs in
the stage rather than in setup instructions.

Both failures also explain themselves now instead of forwarding a compiler
error whose text does not mention the frontend.

* fix(demo): apply the runtime environment through update, not create

create-agent-runtime accepts --environment-variables and then ignores it. The
runtime comes back with environmentVariables null, and the Worker starts with
an empty TEMPORAL_ADDRESS and task queue:

  WORKER starting addr= ns=default tq= deployment=agentcore-demo

So provisioning a runtime from scratch produced a Worker that could not dial
anything, which is worse than the ConflictException it was meant to avoid.

The previous commit conflated two changes. Waiting for READY is what fixes the
race; moving the environment into create was an unnecessary optimisation on
top, and it does not work. Only the wait is kept, and the caller now always
applies the environment through update, which is the path that has been
exercised repeatedly.

This survived because every run after that change reused an existing runtime
and took the update path. The create path was not exercised until a teardown
forced a fresh provision.
@rossnelson
rossnelson marked this pull request as ready for review September 9, 2026 23:12
@rossnelson
rossnelson requested a review from a team as a code owner September 9, 2026 23:12
The scenario shelled out to `temporal worker deployment create-version`,
which proves the server accepts an aws-agentcore compute config. That was
never in question: the CLI could do this before any of this work existed.
What is under review is the UI, and a run that never opened the form said
nothing about it.

The Version is now created by driving the real create-version form with
Playwright: select Amazon Bedrock AgentCore, fill the Agent Runtime
Endpoint ARN and the Access fields, submit. `createVersion: 'cli'` keeps
the old path for a machine with no browser, and `headed: true` shows the
browser doing it. Screenshots land per step, so a failure leaves a picture
of where it stopped.

Preflight launches a browser before any stage starts, because Playwright
downloads its browsers separately from the package and finding that out
after a server build costs minutes.

The run also records what it had to type to get there. The form requires
IAM Role ARN and External ID; this server sets
require_role_and_external_id false and discards both. The UI has no
equivalent of the CLI's --aws-agentcore-skip-role-and-external-id, so a
self-hosted operator who turned the requirement off can create a Version
by CLI but not by form. FE-675 tracks that.

Adds `uiUrl` to the scenario context, set to the UI the ui stage started
so a scenario drives the checkout's UI rather than the server's bundled
one.
Proving the UI can create an AgentCore Version took more than pointing a
browser at the form.

The Access fields are not decorative. Setting
require_role_and_external_id to false makes the role optional, not
ignored: the server assumes whatever role the compute config carries. The
CLI can omit it with --aws-agentcore-skip-role-and-external-id; the form
requires both fields and always sends them, so a placeholder does not get
discarded, it fails at sts:AssumeRole after the form has done its job.
The scenario now provisions TemporalDemoAgentCoreInvoke, trusting this
account under an sts:ExternalId condition and granting
bedrock-agentcore:InvokeAgentRuntime, and waits for it to actually assume
before going on, because IAM is eventually consistent and the server
tries seconds later. The earlier comments claiming the server discards
these fields were wrong and are corrected here.

Each run also gets its own deployment. Versions from earlier runs go
INACTIVE, the server stops reporting their ComputeConfig (FE-672), and
lockComputeProvider cannot name a provider it cannot see, so the
create-version page renders an error where the form should be. Those
Versions cannot be deleted either: AgentCore sessions from earlier runs
keep polling, and the server refuses to delete a Version with active
pollers.

The driver itself had three faults, all found by running it. locator
count() does not wait, and the page renders the form only after it
fetches the deployment, so it looked before the form existed. The page
keeps hidden [role=alert] containers and a closed confirm modal whose
button is also a form submit, so first() waited on elements never shown.
And fullPage screenshots evaluate in the page, which trips over the
__name wrappers esno leaves behind.

Verified end to end against Bedrock AgentCore in us-west-2: the form
created the Version, the Worker Controller invoked the runtime, the
Worker dialed back through the tunnel and registered agentcore-tq, and
Greet returned "hello demo from AgentCore worker on localhost".
This repository is public. The demo notes named an internal repository
and described how hosted Temporal maps an account entitlement into the
capabilities it synthesizes, and several comments cited internal tracker
ids that mean nothing to anyone reading this here. The observations they
supported are worth keeping, so they are restated without the pointers.

Also corrects a note that this branch had already made false: the
compute-provider scenario still said AgentCore must render disabled and
badged "Coming Soon" on a self-hosted server. Self-hosted now offers
every provider, and a reviewer following that note would have reported a
bug against correct behaviour.
The scenario listed the fields AgentCore renders but never sent a
reviewer to the two stories that show the actual behaviour change:
every provider selectable on a self-hosted server, and a Version locked
to the provider it already uses.

Those are the cases worth a human looking at. The first is where a
self-hosted server previously gated providers on capabilities it never
advertised, which left Cloud Run permanently unselectable. The second is
where a card used to render selected and disabled at once.
The Access section required an IAM role and offered no way to create one
for AgentCore. Its CloudFormation and Terraform helpers grant
lambda:InvokeFunction, so sharing them would hand out a role that cannot
invoke a runtime, and the helper was hidden rather than reused.

Adds the AgentCore equivalents and follows the selected provider:

- temporal-agentcore-role.yaml grants bedrock-agentcore:InvokeAgentRuntime
  and GetAgentRuntimeEndpoint. Validation calls GetAgentRuntimeEndpoint
  first, so a role without it fails when the Version is created rather
  than when a Worker is wanted.
- serverless-worker-agentcore.tf points at the aws/agentcore module and
  takes agent_runtime_arns, the same four-part Endpoint ARN the Version
  takes.
- The Launch Stack link, the download filename, and the module link now
  follow the provider rather than always naming Lambda.

The trust policy is the part that differs by deployment. Cloud assumes
the role as a service principal, so it names temporal.io. A self-hosted
server assumes it as whatever IAM identity it runs as, which is an AWS
principal rather than a service and so a different key entirely. The
template takes a TemporalPrincipal parameter and switches on it, since
this PR makes the provider selectable outside Cloud for the first time.

The policy is the one proven by the demo scenario, which provisions the
same permissions and trust shape and only works because the role really
assumes.

Note the aws/agentcore Terraform module does not exist in
temporalio/terraform-modules yet. The snippet names where it will live,
as the Lambda and Cloud Run snippets do for theirs.
Comment thread src/lib/components/deployments/version-compute-details.svelte
The card drew a square border while everything around it — buttons,
accordions, inputs — is rounded, so the compute provider picker read as
unfinished next to the rest of the form.

The optional panel beneath a selected card shares its bottom edge, so the
card drops its bottom corners when that panel is showing and the panel
takes them instead. No consumer passes that snippet today, but leaving it
unhandled would have put the sharp corner back the moment one did.
Two things in the provider picker's logo tile.

The marks lost their colour. Before the icon registry moved to Io, the
picker rendered holocene's aws.svelte, which carried fill="#FF9900". The
refactor remapped it to IconAws, the monochrome glyph, so the AWS and GCP
logos have rendered flat white on dark since 20 Aug. IconAwsColor and
IconGcpColor already existed and were used nowhere outside a Badge story.
These are vendor logos rather than UI icons, so they should keep their own
colour on either theme.

The tile was also explicitly rounded-none, predating the Io work, which
left a square inside a form where everything else is rounded.
The provider allowlist inverted. `workercontroller.compute_providers.enabled`
used to mean "restrict to these", and an unset value allowed everything;
in the auto-scaled-workers release main now pins it means "only these",
and an unset value enables nothing at all.

The scenario never set it, because it never had to. The server now
reports that it could not instantiate the provider, which reads like the
build is missing it — the provider is there and switched off, and the
metric behind that branch says so where the message does not.

Worth knowing beyond this scenario: the same upgrade silently disables
Lambda and Cloud Run for any self-hosted operator who has not set the
list.

Dynamic config values may now be string arrays. The flag already passes
JSON, so only the schema stood in the way.
The last place still rendering the monochrome glyphs. Same cause as the
provider picker: the icon registry move mapped these to IconAws and
IconGcp, so the badge has drawn a flat white AWS smile since August.

These identify a vendor rather than convey UI state, so currentColor is
the wrong default for them.
The stage badge sits beside a provider name at text-sm, and at the
default size its mono uppercase label read heavier than the name it
qualifies. #3908 added a size variant, so take it rather than override
the padding locally.

The disabled-reason badge occupies the same slot and only ever renders
instead of this one, so it takes the same size.
@rossnelson
rossnelson merged commit c71816b into main Sep 14, 2026
17 checks passed
@rossnelson
rossnelson deleted the agentcore-compute-provider branch September 14, 2026 17:47
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.

3 participants