fix(aws-strands): forward plugins per-thread and warn on agent-bound params - #2612
Merged
ranst91 merged 2 commits intoSep 3, 2026
Merged
Conversation
…params The adapter rebuilds a fresh strands.Agent per thread_id by reading the template's constructor params back off the instance. Strands consumes `plugins` during init: it runs each plugin's init_agent against the agent that received it, registers that plugin's hooks and tools into that agent's registries, and keeps only a _PluginRegistry holding a weakref back to the agent. _references_agent sees that weakref and classifies `plugins` as template-owned, and template-owned params were deliberately excluded from the uncarried-param warning. A caller who set plugins on the template therefore got neither the plugins nor a word about losing them. The template never serves a request, so a plugin whose behaviour lives in init_agent silently did nothing. Report both kinds of uncarried param, each with its own message and both under the existing once-per-param bookkeeping. They stay separate because they point at different fixes: an unreadable param is an adapter gap a later release may close, while a param the SDK wired to one agent will never be carryable. Add the `plugins=` kwarg Python was missing, matching StrandsAgentOptions.plugins on the TypeScript side. It forwards into every per-thread StrandsAgentCore next to `hooks`, under the same falsy-omission rule, and "plugins" joins _AGUI_EXPLICIT_PARAMS so it is not also probed off the template. Since that exclusion hides it from the generic probe, a dedicated read keeps the template case reportable, filtering out the plugins Strands registers on every Agent itself so the warning does not reach callers who set none. Precedence is unchanged: thread_agent_kwargs wins at the merge site, then the explicit kwarg, and the template never carries. The warning stays quiet for a thread whose kwargs supplied the param either way. Also drops a duplicated _report_uncarried_params call at the build site that was a no-op given the once-per-param bookkeeping. The forwarding half was proposed in #2141 by @hiepcs against an older tree. Co-authored-by: HiepBP <hiepcs@users.noreply.github.com>
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen install the packages you need: # Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1788433359' --index testpypi
# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1788433359' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1788433359' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1788433359' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1788433359' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1788433359' --index testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1788433359
Commit: cf37618 |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/claude-managed-agents
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/vercel-ai-sdk
@ag-ui/watsonx
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
The plugins kwarg added in the previous commit reached a constructor that has no such parameter on strands-agents below 1.28.0, the release that added plugins to Agent. This package declares a 1.15.0 floor and runs a CI lane against it. There, Strands raised a bare TypeError from inside per-thread construction, and because that happens inside the run generator it escaped to the caller rather than becoming a run error: the first request died with an SDK traceback pointing at neither the argument nor the version that could not take it. Probe the capability off the Agent constructor signature and refuse at wrap time instead. A static misconfiguration is knowable the moment the wrapper is built, so it is answered there, with a message naming the installed version and what to do about it. Not raised for a multi-agent orchestrator, which builds no per-thread agent and so ignores plugins on every release; reporting a version problem there would describe something the newer releases do not do either. Tests previously gated the whole plugin block on the SDK having plugins, which left twelve behaviours unasserted at the declared floor. Only one of them needs the real plugin system: the one proving init_agent runs once per thread against a real Agent. The rest exercise the adapter's own reading, reporting and forwarding, so they now use a synthetic plugin registry and plain sentinels, the way the resolver tests in that file already do, and run on every supported release. The three that drive the kwarg against a stub core declare the capability explicitly rather than skipping, which is what the stub already assumes. Floor skips drop from twelve to one. Adds direct coverage for the SDK-plugin name filter and for the new refusal, and a README section documenting the per-thread rebuild, the hooks and plugins routes, and the version boundary.
AlemTuzlak
approved these changes
Sep 3, 2026
ranst91
deleted the
fix/aws-strands-python-plugins-and-agent-bound-warning
branch
September 3, 2026 13:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the still-open half of #2129.
The bug
StrandsAgentbuilds a freshstrands.Agentperthread_idfrom the template Agent, recovering the template's constructor params through_extract_agent_kwargs. On strands-agents 1.35.0,Agent.__init__acceptspluginsand consumes the list immediately: it calls each plugin'sinit_agentagainst the agent that received it, registers that plugin's hooks and tools into that agent's registries, and keeps only a_PluginRegistryholding aweakrefback to the agent._references_agentsees that weakref and returns_AGENT_BOUND, sopluginslands in_template_owned_params._report_uncarried_paramswarns only about_unreadable_params, and template-owned params were excluded on purpose, on the reasoning that an agent-bound param is a structural property rather than a surprise.The result is the worst of the two failure modes. A caller who sets
pluginson the template gets no plugins on the agents that actually serve requests, and no warning that anything was lost. The template never serves a request, so a plugin whose whole behaviour lives ininit_agentsilently does nothing.Reproduced before the fix:
What changed
1. An agent-bound param that will not carry now says so.
_report_uncarried_paramsreports both kinds, each with its own message and both under the existing once-per-param bookkeeping, so neither can nag. They are kept separate because they ask for different reading: an unreadable param is a gap in this adapter that a later release may close, while a param the SDK wired to the agent that received it will never be carryable, so the per-thread route is the whole answer rather than a stopgap.2. Python gets the
plugins=kwarg TypeScript already has.StrandsAgent(plugins=[...])now forwards to every per-threadStrandsAgentCore, next to the existinghooksforwarding and under the same falsy-omission rule: an empty list is never passed, becauseplugins=[]is a value a future SDK could read as "register none of the defaults either"."plugins"is added to_AGUI_EXPLICIT_PARAMSso it is not also probed off the template.Because that exclusion also takes
pluginsout of the generic probe, a small dedicated read (_template_plugin_names) keeps the template case reportable, mirroring thesession_managerfootgun detector already in the constructor. It filters out plugins Strands registers itself: every Agent is built with at least one (strands:model), and counting those would warn every caller about a setting nobody made. Plugins a caller opts into are not namespaced that way, including vended ones likeAgentSkills, whose name isagent_skills.Precedence
Three sources can name
pluginsat once, and all three can be present together:thread_agent_kwargswins. It goes on last at the merge site, which is the existing rule for every param and is unchanged here.plugins=kwarg is next.The warning stays quiet whenever either of the first two supplied the param for that thread, so acting on the message makes it stop. It is still said per param and per thread, so one thread supplying everything does not buy silence for the next thread that supplies nothing.
Version floor
pluginsarrived on the StrandsAgentconstructor in strands-agents 1.28.0. This package declares a 1.15.0 floor and runs a CI lane against it, so the new kwarg can be handed a release with nowhere to put it. Left alone it reached that constructor and Strands raised a bareTypeErrorfrom inside per-thread construction, which escapes the run generator rather than becoming a run error: the caller's first request died with an SDK traceback naming neither the argument they passed nor the version that could not take it.Raising the floor would drop support for 1.15 through 1.27, so instead the capability is probed off the constructor signature and a caller who supplies
plugins=on a release without it is refused while the wrapper is being built. That is where the mistake is knowable, and it is said once rather than on every request. The message names the installed version and what to do.This follows the pattern the adapter already uses for session reconciliation, which probes for the exact repository methods it needs, fails in a defined way when they are absent, and documents the boundary in the README support table.
Not raised for a multi-agent orchestrator. An orchestrator builds no per-thread agent and so ignores both
hooksandpluginson every release; reporting a version problem there would describe something the newer releases do not do either.Credit
PR #2141 by @hiepcs proposed exactly the shape of part 2, including the
_AGUI_EXPLICIT_PARAMSentry and the falsy-omission rule, and diagnosed theAgentSkillssymptom. That PR was written against a much older tree and no longer applies, so this is written fresh, but the design of the forwarding half is theirs.Tests
Extended the two suites that already own this behaviour.
tests/test_template_agent_propagation.py:plugins=kwarg supplies themNoneand[])strands.Agent, a forwarded plugin'sinit_agentruns once per thread and against that thread's own agenttests/test_thread_agent_kwargs.py:Only the real-
Agentinit_agenttest needs the SDK's plugin system, so it is the only one skipped at the floor. The rest exercise the adapter's own reading, reporting and forwarding, and use a synthetic plugin registry and plain sentinels, which is how the resolver tests in that file already build their cases. The three that drive the kwarg against a stub core declare the capability explicitly rather than skipping, since a stub takes any kwarg and that is what it already assumes.test_no_constructor_param_is_dropped_silentlynow also asserts that template-owned params are named in a warning, which is the general form of the defect above.Each behaviour was checked against a mutated source to confirm the tests fail without it: removing the forwarding, the template detection, the SDK-plugin filter, or the wrap-time refusal each turns the relevant tests red, and moving the kwarg after the hook merge breaks the precedence test.
Verification
plugins-forwarded.test.ts.Also in this diff
The per-thread build site called
_report_uncarried_paramstwice, once unconditionally and again whenthread_agent_kwargswasNone. The second call was a no-op given the once-per-param bookkeeping. Removed.A README section now documents the per-thread rebuild, the
hooksandpluginsroutes, and the version boundary.