Skip to content

feat(experimental.methods): add method agents - #64

Merged
AdityaGolatkar merged 2 commits into
strands-labs:mainfrom
laithalsaadoon:upstream/methods
Oct 21, 2026
Merged

AdityaGolatkar merged 2 commits into
strands-labs:mainfrom
laithalsaadoon:upstream/methods

Conversation

@laithalsaadoon

Copy link
Copy Markdown
Contributor

Summary

Adds ai_functions.experimental.methods, the @ai_function decorator applied to methods. An agent written as a class usually funnels each capability through one request: str parameter, which drops three things @ai_function gives 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 removes self from its signature.

class Analyst(MethodAgent):
    def __init__(self, plane: str):
        self.plane = plane

    @ai_method(Finding, description="Analyze one plane over a window")
    def analyze(self, window: str, max_records: int = 20) -> Finding:
        """Analyze the {self.plane} plane over {window}."""

Analyst("metrics").compiled("analyze") is an AIFunction whose tool schema carries window and max_records, with the prompt rendered from the docstring for this instance. Analyst("metrics").spawn("analyze", coordinator) is the same capability on a live thread whose run calls 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, MethodAgent and MethodThread. agents() hands an object's capabilities to another agent as typed tools named {owner}_{method}, where the owner is the agent's name or 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. MethodThread keeps one capability's thread alive between calls; retire is idempotent and every call after it raises RetiredThreadError.
  • gated: GatedProposer runs a Gate as the method's post-condition, so a caller can't skip the check. propose_k takes 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's list_threads and send_message tools, so each proposal is an independent draw and no branch can wait on a sibling.
  • recall: Recalled declares memory as a call argument and Recall fills it fresh from a MemoryBackend on every call, so the recalled values reach TextGradOptimizer as parameters instead of hiding on self.

A capability's model comes from the call's model=, then the decorator's model=, then the agent's model attribute. With none of them set, it runs on the Strands default model, as a plain @ai_function does.

The package docstring lists its invariants, M1 to M9.

Docs and examples

  • docs/methods.md covers @ai_method and MethodAgent, model choice, agents(), spawn(), GatedProposer with gated() and propose_k, and Recall, and ends with an errors table.
  • README.md gains a "Method agents" subsection under Multi-Agent Workflows that links to it.
  • examples/methods/analyst_team.py calls a method once, hands every method to an orchestrating @ai_function as a typed tool, and keeps one method on a live thread.
  • examples/methods/gated_proposer.py checks discount proposals against a cap the prompt doesn't state, retries with the gate's report through gated(), and filters a three-branch propose_k beam.

Both examples use example_helpers.models.medium, so they run on the Strands default model.

Testing

hatch run lint, hatch run check-spec and hatch run test pass on Python 3.12 (721 passed, 2 skipped). tests/methods/test_methods_ai_functions_contract.py pins the core behaviors the package relies on (one config_hook per cycle, notify starting no cycle, ThreadKwargs keys, LocalWorker.close() idempotence, load_tools schema 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-6 in strands-agents 1.61.1). The orchestrator in analyst_team.py called the analyst tools under their default names, and the propose_k beam in gated_proposer.py returned a verdict for each of its three branches; that whole example took 28 seconds.

ruff format --check reports one file, src/ai_functions/memory/base.py, which is already unformatted on main under ruff 0.17.

laithalsaadoon and others added 2 commits October 19, 2026 04:24
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
AdityaGolatkar merged commit 8f1485c into strands-labs:main Oct 21, 2026
1 check passed
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