Repository navigation
feat(experimental.methods): add method agents - #64
Merged
AdityaGolatkar merged 2 commits intoOct 21, 2026
Merged
Conversation
Adds ai_functions.experimental.methods, the @ai_function decorator
applied to methods.
- core: @ai_method compiles a bound method to an AIFunction that keeps
its typed signature, uses its docstring as the prompt template, and
exposes its call arguments to TextGradOptimizer as parameters.
MethodAgent is an object whose methods are typed AI functions over its
own state, each one a tool another agent can call. MethodThread keeps
one method's thread alive between calls so they share history.
- gated: a method's gate runs as the method's post-conditions, so a
caller cannot skip the check. propose_k forks one thread into k
branches, takes one proposal from each, and spawns them without the
coordinator tools so the branches stay independent.
- recall: Recalled and Recall declare memory as a call argument, so
guidance is retrieved fresh on every call and reaches the optimizer
as a parameter view.
A method's tool name is {owner}_{method}, where the owner is the
agent's name attribute or its lowercased class name. The separator is
an underscore because Bedrock and the Strands tool-use check accept
only letters, digits, underscores and hyphens in a tool name.
A capability's model comes from the call, the decorator, or the
agent's model attribute, in that order; with none set it runs on the
Strands default model, as a plain @ai_function does.
docs/methods.md documents the module, the README gains a Method agents
subsection, and examples/methods has two runnable examples.
…ing match @ai_function
Core:
- Add ai_thread.PostConditionError: raised from a post-condition, it ends
the cycle with that error instead of becoming retry feedback. Both
post-condition loops let it propagate.
- _merge_config accepts an explicit agent_kwargs=, which replaces the base's
agent kwargs; loose Agent kwargs still merge over it. AIFunction.replace
and @ai_function(agent_kwargs=...) raised TypeError before.
experimental.methods:
- GateFaultError is a PostConditionError, so a broken gate ends a gated()
call after one model call instead of burning every retry.
- gated() keeps the method's own post_conditions (gate first, then the
method's, then the caller's) and refuses an async gate with WiringError.
- propose_k spawns branches without post-conditions, takes k and coordinator
positionally, refuses propose parameters named method or seed, and retires
every branch even when one retirement fails.
- prompt_fn returns the docstring Template (or the body's str/Template) and
AIFunction renders it, so prompts match @ai_function; other return types
are refused.
- The compiled AIFunction is named {owner}_{method}, so threads, forks and
runtime errors use the published name; compiled(name=...) renames it.
- prompt_fn no longer captures the module's globals, so a compiled method
pickles for remote workers.
- Only parameter annotations are resolved, so a TYPE_CHECKING-only return
annotation no longer breaks compilation.
- retire() marks the thread retired only after teardown succeeds.
- Recall.trace compiles before retrieving.
- Drop the NO_ARGS "lost its parameters" guard, which refused keyword-only
methods.
- Docstrings state behavior without references to code outside the repo or
line numbers; the README section is a top-level "Method Agents".
AdityaGolatkar
approved these changes
Oct 21, 2026
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.
Summary
Adds
ai_functions.experimental.methods, the@ai_functiondecorator applied to methods. An agent written as a class usually funnels each capability through onerequest: strparameter, which drops three things@ai_functiongives for free: a typed tool schema, the docstring as the prompt template, and call arguments the optimizer can reach. A bound method keeps all three, because Python already removesselffrom its signature.Analyst("metrics").compiled("analyze")is anAIFunctionwhose tool schema carrieswindowandmax_records, with the prompt rendered from the docstring for this instance.Analyst("metrics").spawn("analyze", coordinator)is the same capability on a live thread whoseruncalls share history.The package uses only public core API (
AIFunction,ThreadConfig,Coordinator.spawn,ThreadHandle) and imports no other experimental package.What's in it
core:@ai_method,MethodAgentandMethodThread.agents()hands an object's capabilities to another agent as typed tools named{owner}_{method}, where the owner is the agent'snameor its lowercased class name. The separator is an underscore because Bedrock and the Strands tool-use check accept only letters, digits,_and-in a tool name.MethodThreadkeeps one capability's thread alive between calls;retireis idempotent and every call after it raisesRetiredThreadError.gated:GatedProposerruns aGateas the method's post-condition, so a caller can't skip the check.propose_ktakes k proposals from one shared context by forking one thread k - 1 times, and returns the ones the gate admits. The branches are spawned without the coordinator'slist_threadsandsend_messagetools, so each proposal is an independent draw and no branch can wait on a sibling.recall:Recalleddeclares memory as a call argument andRecallfills it fresh from aMemoryBackendon every call, so the recalled values reachTextGradOptimizeras parameters instead of hiding onself.A capability's model comes from the call's
model=, then the decorator'smodel=, then the agent'smodelattribute. With none of them set, it runs on the Strands default model, as a plain@ai_functiondoes.The package docstring lists its invariants, M1 to M9.
Docs and examples
docs/methods.mdcovers@ai_methodandMethodAgent, model choice,agents(),spawn(),GatedProposerwithgated()andpropose_k, andRecall, and ends with an errors table.README.mdgains a "Method agents" subsection under Multi-Agent Workflows that links to it.examples/methods/analyst_team.pycalls a method once, hands every method to an orchestrating@ai_functionas a typed tool, and keeps one method on a live thread.examples/methods/gated_proposer.pychecks discount proposals against a cap the prompt doesn't state, retries with the gate's report throughgated(), and filters a three-branchpropose_kbeam.Both examples use
example_helpers.models.medium, so they run on the Strands default model.Testing
hatch run lint,hatch run check-specandhatch run testpass on Python 3.12 (721 passed, 2 skipped).tests/methods/test_methods_ai_functions_contract.pypins the core behaviors the package relies on (oneconfig_hookper cycle,notifystarting no cycle,ThreadKwargskeys,LocalWorker.close()idempotence,load_toolsschema building), so a change to any of them fails there first.Both examples ran against Bedrock in us-east-1 on the Strands default model (
global.anthropic.claude-sonnet-4-6in strands-agents 1.61.1). The orchestrator inanalyst_team.pycalled the analyst tools under their default names, and thepropose_kbeam ingated_proposer.pyreturned a verdict for each of its three branches; that whole example took 28 seconds.ruff format --checkreports one file,src/ai_functions/memory/base.py, which is already unformatted onmainunder ruff 0.17.