Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(aibridge): resolve bedrock inference profiles on first request - #28996

Closed
evgeniy-scherbina wants to merge 18 commits into
mainfrom
yevhenii/aigov-488-resolve-inference-profiles-on-first-request
Closed

feat(aibridge): resolve bedrock inference profiles on first request#28996
evgeniy-scherbina wants to merge 18 commits into
mainfrom
yevhenii/aigov-488-resolve-inference-profiles-on-first-request

Conversation

@evgeniy-scherbina

Copy link
Copy Markdown
Contributor

Follow-up to #28877, stacked on its branch. Review that PR first.

#28877 resolves Bedrock application inference profile (AIP) ARNs while constructing a provider. Review found two consequences of putting an AWS call on that path:

  • A transient GetInferenceProfile or STS failure drops the provider from the rebuilt snapshot. Reloads are event driven only, so the provider stays gone until a provider CRUD event or a restart, and requests get 404 route not supported rather than the 503 disabled sentinel.
  • The initial reload runs inline on coder server startup under context.Background(), so boot blocks up to 30s per AIP-configured provider on unreachable egress, and SIGINT does not interrupt it.

Both follow from resolution being a construction-time step. This moves it to the first request that needs it.

NewAnthropic now makes no network call: it keeps the configured Bedrock settings, the loaded aws.Config, and a shared InferenceProfileCache. CreateInterceptor resolves through that cache using the request context, then builds the per-request BedrockRuntime. Model() stays infallible and the shared Interceptor interface is unchanged, because resolution completes before the interceptor exists.

The cache holds successes for the process lifetime, keyed by ARN. Bedrock has no UpdateInferenceProfile, so a profile's underlying model is fixed at creation and repointing requires a new ARN, which means no invalidation logic. Failures are not cached, so a transient one is retried by the next request. singleflight collapses a burst of concurrent first requests into one AWS lookup. The cache is created once per reloader and passed into provider construction, so it survives ReplaceProviders instead of being discarded on every reload.

Net effect: boot never waits on AWS, reloads never call AWS, a deployment resolves each profile once per process, and a resolution failure costs one request instead of the provider.

The trade is that a permission misconfiguration now surfaces as a failed request rather than at startup, and provider_info{status="error"} no longer flags it.

Implementation plan

Why change the current design

The PR resolves application inference profile (AIP) ARNs during provider
construction. Review found two P1 consequences:

  • CRF-11: a transient GetInferenceProfile or STS failure drops the Bedrock
    provider from the snapshot. Reloads are event driven only, so the provider
    stays gone until a provider CRUD event or a restart, and requests get
    404 route not supported rather than the 503 disabled sentinel.
  • CRF-12: the initial reload runs inline on coder server startup with
    context.Background(), so boot blocks up to 30s per AIP provider on
    unreachable egress and SIGINT does not interrupt it.

A process cache alone fixes neither: the cache is cold at boot, and it only
helps after a first success.

Resolving on the first request removes AWS from both the boot path and the
reload path. Construction becomes pure again, so a resolution failure can only
affect the request that triggers it, and the next request retries.

Design

Resolution seam

Anthropic.CreateInterceptor(w, r, tracer) (intercept.Interceptor, error) is
per request, already returns an error, and has r.Context(). Resolution
happens there, before the interceptor is built, so:

  • interceptionBase.Model() stays infallible.
  • The shared intercept.Interceptor interface is unchanged.
  • bridge.go already maps a CreateInterceptor error to a logged 500.

Cache

  • Resolve(ctx, awsCfg, identifier) (string, error).
  • Non-AIP identifiers return unchanged with no AWS call, as today.
  • Successes are cached for the process lifetime, keyed by ARN. Sound because
    Bedrock has no UpdateInferenceProfile: an AIP's underlying model is fixed at
    creation, and repointing requires a new ARN.
  • Failures are not cached, so the next request retries.
  • singleflight collapses concurrent cold-cache resolutions of the same ARN
    into one AWS call.

The cache must outlive provider instances, since ReplaceProviders swaps them
on every reload. It is created once in poolRPCReloader and passed through
BuildProvidersFromProto and buildProvider into NewAnthropic, rather than
being a package-level global, so tests get their own.

Timeout and context

Resolution derives from r.Context(), so it is cancelable and bounded by the
request deadline, with inferenceProfileResolutionTimeout as an upper bound.
Boot no longer waits on AWS at all, so the uncancelable context.Background()
problem disappears from this path.

Trade-offs accepted

  • The first request per ARN per process pays one control-plane call.
  • A permission misconfiguration surfaces on first use instead of at startup.
    This reverses the earlier "fail at construction" decision, which review showed
    to be silent deletion plus a 404 rather than a loud failure.
  • provider_info{status="error"} no longer signals AIP problems. Resolution
    failures are logged per request; a dedicated metric is a possible follow-up.

Out of scope

  • Normalizing directly configured system inference profile ARNs.
  • Provisioned throughput and prompt router identifiers.
  • Persisting resolved models in the database.
  • Diff-aware reload that reuses unchanged provider instances.

Relates to https://linear.app/codercom/issue/AIGOV-488

Created by Coder Agents on behalf of @evgeniy-scherbina.

evgeniy-scherbina and others added 18 commits September 1, 2026 14:14
Application inference profile ARNs are opaque, so Bedrock capability
detection matched nothing and adaptive-thinking conversion shaped
requests for the wrong model. Resolve the ARN through GetInferenceProfile
and use the underlying model ID for capabilities, usage records, pricing,
and metrics, while still invoking the profile so AWS attributes spend.

Resolution only runs for application inference profile ARNs, so
deployments configured with plain model IDs need no extra permission.
…solved

Drop the empty-value fallback so the runtime is always constructed with
resolved model IDs, and name the accessors after what they return rather
than how they are used.
…dpoint

Drop the injected resolver in favor of the mock-endpoint pattern the
Bedrock credential tests already use, so the AWS client, response
decoding, and error wrapping are exercised. Building the client from the
loaded AWS config also honors custom control-plane endpoints.
…edrockCredentials

Inference profile resolution reused the credentials but reloaded the AWS
config and overwrote its credentials. Return the config that was already
loaded so the control-plane client shares one identity and one set of
environment-derived settings.
…ctly

The injected resolver existed only for tests, which now drive resolution
through a mock Bedrock endpoint. Asserting that no request reaches
Bedrock is also a stronger statement than asserting a stub was unused.
@linear-code

linear-code Bot commented Sep 4, 2026

Copy link
Copy Markdown

AIGOV-488

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

Base automatically changed from yevhenii/aigov-488-support-bedrock-application-inference-profile-arns-with to main September 8, 2026 01:01
@evgeniy-scherbina

Copy link
Copy Markdown
Contributor Author

Closing unmerged: superseded by the write-time resolution design.

Resolution is moving to coderd, at provider create/update, with the result persisted and passed to the gateway in the provider payload. That removes the AWS call from provider construction entirely, so it fixes CRF-11 and CRF-12 at the root rather than by moving the call to the request path, and it keeps resolution in one place instead of two.

Created by Coder Agents on behalf of @evgeniy-scherbina.

@github-actions github-actions Bot locked and limited conversation to collaborators Sep 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant