Repository navigation
feat(experimental.teams): add teams of a lead over live members - #67
Open
laithalsaadoon wants to merge 1 commit into
Open
laithalsaadoon wants to merge 1 commit into
laithalsaadoon wants to merge 1 commit into
Conversation
Adds ai_functions.experimental.teams: a lead AI function holds a few live members as typed tools, and hooks add everything else. - core: Team spawns the members as children of the lead's thread, runs the lead with each member as a tool, walks the hooks over the answer, and retires everybody on every path. Accept and Revise verdicts drive a bounded answer loop that sends a revised answer back through every reviewer before it ships, and a mandatory reviewer that runs out of revisions blocks the result. The transcript records every member call and revision as a typed record. Team(ask_timeout_s=) bounds each member ask, and Team.cancel stops a live run. - members: FunctionMember wraps an AIFunction and MethodMember one @ai_method of a MethodAgent. Both spawn without list_threads and send_message, so the lead's tool is the only way to reach a member. - contracts: the Member, TeamHook, Equippable and Described protocols and the Workspace every hook method receives. - runtime: Budget and Cost bound work in tokens, dollars or both, with reservations and nested budgets. PRICES and PriceTable price token usage by model, and InMemoryCoordinator(prices=) carries the table. TokenUsageEvent gains the id of the model that spent it, which AIThread fills in. Team.run(budget=) charges the run's measured spend and reserves each member ask at the member's estimate. Adds docs/teams.md, a README section, and examples/teams/status_desk.py.
This branch has not been deployed
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.teams, which puts one lead AI function over a few live members. The lead holds each member as a typed tool and decides whom to ask and what to ask them. Each member keeps its own conversation across the lead's asks. Hooks add everything else: extra tools, a rewritten request, a check before every ask, and a review of the answer.run.answeris the lead's final answer, andrun.transcriptlists every member call and revision as a typed record, written by the code that carried it rather than reported by the model.run.costis the run's measured spend in tokens and dollars. Every thread the run spawned is retired by the timerunreturns, on every path out.To bound that spend, the PR also adds
Budget,Costand a static price table toai_functions.runtime, and names the model on each token usage event so the table can price it. The package buildsMethodMemberonai_functions.experimental.methodsand otherwise uses only public core API.What's in it
core:Team(lead, members, hooks=(), *, ask_timeout_s=None)andTeam.run(request, coordinator=None, *, parent_id=None, run_id=None, budget=None) -> TeamRun. A run spawns the members as children of the lead's thread, runs the lead with each member as a tool, walks the hooks over each answer, and retires everybody in afinally. With no coordinator, it stands up a privateInMemoryCoordinatorand closes it when the run ends. One instance runs one run at a time, so a second concurrentrunraisesWiringErrorbefore anything spawns.on_answerreturnsAccept()orRevise(feedback=..., cap=2, mandatory=False). A revised answer goes back through every reviewer before it ships, each reviewer's revisions are capped, and a mandatory reviewer that runs out of revisions marks the runblocked.Team.cancel(reason)stops a live run, and abefore_askhook can raiseStopRun(reason)to stop it before an ask. Either way the lead stops at the tool boundary, the members retire, the teardown hooks run, andrunreturnsTeamRun(cancelled=True).ask_timeout_sbounds each member ask, and a member past its deadline is retired and isn't asked again in that run.members:FunctionMemberwraps anAIFunction, andMethodMemberwraps one@ai_methodof aMethodAgent. Both spawn withoutlist_threadsandsend_message, so the lead's tool is the only way to reach a member.contracts: theMember,TeamHook,EquippableandDescribedprotocols, and theWorkspacethat every hook method receives. Any object with anameandspawn,askandretireis a member.types: the transcript recordsMemberCall,Revision,RevisionCapandNote, theAcceptandReviseverdicts, andTeamError.runtime.budget:Costin tokens, dollars or both, andBudgetwith reservations, nested children, and anon_unknownpolicy for work that brings no estimate. UnderTeam.run(budget=...)the run holds one charge for its measured spend, and each member ask reserves the member's estimate before it runs. A spent budget refuses the next ask, and the lead reads the refusal aserror:text, so it can finish with what it has.runtime.prices:ModelPrice,PriceTableandPRICES, list prices per million tokens for current Claude models as published on 2026-10-21, with the Bedrock regional premium on geographic inference profiles and the higher rates Claude Haiku 5.5 charges for a prompt over 100,000 tokens.InMemoryCoordinator(prices=)carries the table. A model the table doesn't know has no price rather than a zero one, and a team refuses further asks under a dollar budget once it has spent unpriced usage.TokenUsageEventgainsmodel, the id of the model that spent the tokens, whichAIThreadfills in from the Strands model's config.InMemoryCoordinatortakesprices=and exposes the table asprices.The docstrings list the invariants: C1 to C14 for
core, M1 to M6 formembers, T1 to T9 for the package, BU1 to BU5 for budgets, and PR1 to PR5 for prices.Docs and examples
docs/teams.mdcovers a lead, its members and a run, writing your own member, hooks,AcceptandRevise, the transcript, budgets and deadlines, and cancelling a run, and ends with an errors table.README.mdgains a "Teams" section that links to it.examples/README.mdgains a Teams section with a row for the example.examples/teams/status_desk.pyhas an incident desk write a customer status update for a payment outage. The lead asks an analyst'stimelinemethod on a live thread and a skeptic function that checks one root-cause claim. AHouseStylehook sends the draft back until it ends with the restore time, and the run prints its transcript and cost under a $0.50 budget. The lead usesexample_helpers.models.mediumand the members usemodels.small.Testing
hatch run lint,hatch run check-specandhatch run testpass on Python 3.12 (995 passed, 2 skipped). The PR adds 256 tests, and they run offline: scripted models drive the lead and the members, and the assertions read what each model received, the transcript, and the coordinator's event log.tests/teams/test_teams_ai_thread_contract.pypins the runtime behaviors the package relies on (oneconfig_hookper cycle that replaces the tools, coordinator tools appended after it,parent_idon spawned threads, and a teardown that drops the thread's record at once), so a change to any of them fails there first.tests/teams/test_teams_cancel.pycovers a stop at a tool boundary: after aStopRun, the lead's model isn't called again with the stopped ask's missing result.On the final code, the example ran twice against Bedrock in us-east-1, with the lead on the Strands default model (
global.anthropic.claude-sonnet-4-6in strands-agents 1.62.0) and the members onglobal.anthropic.claude-haiku-4-5-20251001-v1:0. In both runs the lead asked the analyst and the skeptic once each,HouseStylesent the first draft back for the restore time, and the second draft passed. The runs spent 9,003 tokens ($0.0269) and 9,087 tokens ($0.0272) against the $0.50 budget. The skeptic called the root-cause claim unsupported in the first run and supported in the second, and each update followed it: the first named only "a software change", and the second named the retry limit set to zero.ruff format --checkreports one file,src/ai_functions/memory/base.py, which is already unformatted onmain.Follow-ups
Two PRs build on this one. The first adds the review and worklog hooks and
Rounds; the second adds subagents and nested teams, so a lead can create members during a run (create_subagent,create_subagent_inline,create_subteam,delegate,dismiss) under a headcount cap and the run's budget.