feat: add Agent/Team/Workflow factories for multi-tenant AgentOS#7549
feat: add Agent/Team/Workflow factories for multi-tenant AgentOS#7549ashpreetbedi wants to merge 18 commits intov2.6.0from
Conversation
…agent construction Enable multi-tenant AgentOS deployments where the agent's tools, instructions, model, or database scope depend on who is calling. Factories are registered callables that AgentOS invokes on each request with a RequestContext, returning a freshly built Agent/Team/Workflow. Key additions: - AgentFactory, TeamFactory, WorkflowFactory classes - RequestContext with TrustedContext for JWT-claim-driven authorization - factory_input form field on all run endpoints - Async factory support via get_*_by_id_async variants - Factory discovery in list endpoints (type: "factory") - Input validation via pydantic input_schema - Exception hierarchy: FactoryError, FactoryValidationError, FactoryPermissionError, FactoryContextRequired - 22 unit tests covering invocation, validation, error handling The existing prototype deep_copy path is completely unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
AgentFactory, TeamFactory, and WorkflowFactory are near-identical (~80 lines each). The only difference is the class name and docstring references. This should be a single generic Factory[T] base class, with the three as thin subclasses (or even just type aliases). Right now any bug fix (e.g., in validate_input) needs to be applied in 3 places. |
| agent = get_agent_by_id( | ||
| agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True | ||
| ) | ||
| except FactoryContextRequired: |
There was a problem hiding this comment.
Errors here should be consistent with async -- and again should not just be factory errors
There was a problem hiding this comment.
Errors here should be consistent with async
we can added Exception too yes, but other factory errors are not needed because these other endpoints (cancel, continue, get_run, list_runs, get_config) operate on an already-running or already-existing agent. There's no factory to invoke. so that's why we only raise FactoryContextRequired here
| ) | ||
| async def get_agent(agent_id: str, request: Request) -> AgentResponse: | ||
| agent = get_agent_by_id(agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True) | ||
| # Check if it's a factory first — return factory info without requiring a RequestContext |
There was a problem hiding this comment.
Here again, the errors can be raised by the get_agent_by_id function. We cannot just raise the FactoryContextRequired error.
| agent = get_agent_by_id( | ||
| agent_id=agent_id, agents=os.agents, db=os.db, registry=os.registry, create_fresh=True | ||
| ) | ||
| except FactoryContextRequired: |
There was a problem hiding this comment.
Flagging this as well, but all the exceptions need to be reworked.
| get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams), | ||
| get_eval_router( | ||
| dbs=self.dbs, | ||
| agents=[a for a in self.agents if not isinstance(a, AgentFactory)] if self.agents else None, |
There was a problem hiding this comment.
Factory primitives can be provided to the eval router, no?
There was a problem hiding this comment.
this will need to be supported seperately, the eval runner accesses agent.model directly and there's no RequestContext available in the eval context. Supporting factories in evals would require the eval config to specify mock context values (user_id, claims, factory_input), which is new API surface. maybe for evals 2.0
| db: Optional[Union[BaseDb, AsyncBaseDb, RemoteDb]] = None | ||
|
|
||
| for agent in self.agents or []: | ||
| if isinstance(agent, AgentFactory): |
There was a problem hiding this comment.
Rather than adding these checks inside the setup tracing function, we should have already filtered these factory primitives out.
| validated_input = agent.validate_input(ctx.input) | ||
| from dataclasses import replace | ||
|
|
||
| ctx_with_input = replace(ctx, input=validated_input) |
There was a problem hiding this comment.
Business logic of how a factory should work should not be inside OS utils.
|
Summary
Add first-class factory support to AgentOS for per-request, context-driven agent/team/workflow construction. This enables multi-tenant deployments where the agent's tools, instructions, model, or database scope depend on who is calling — driven by JWT claims, request body input, or any other request-time context.
DX example:
Key changes
AgentFactory,TeamFactory,WorkflowFactory— registered callables that produce components per requestRequestContextwithTrustedContext— separates verified middleware claims from untrusted client input at the type levelfactory_inputform field on all run endpoints — JSON payload validated against optionalinput_schemaget_*_by_id_asyncvariantstype: "factory"andfactory_input_schemaFactoryError→ 500,FactoryValidationError→ 400,FactoryPermissionError→ 403Files changed
agno/agent/factory.pyagno/team/factory.pyagno/workflow/factory.pyagno/os/utils.pybuild_request_contexthelperagno/os/app.pyagno/os/routers/agents/router.pyfactory_inputparam, factory error handlingagno/os/routers/teams/router.pyagno/os/routers/workflows/router.pyagno/os/routers/agents/schema.pytype,factory_input_schemafields,from_factory()agno/os/routers/teams/schema.pytests/unit/os/test_factories.pyThe existing prototype
deep_copypath is completely unchanged. All changes are additive.Type of change
Checklist
ruff formatandruff check)Duplicate and AI-Generated PR Check
Additional Notes
specs/agno/features/agent-factories/design.mdRequestContext.trustedvs.inputsplit forces factory authors to explicitly reach into verified claims for authorization decisions — the most likely multi-tenancy bug (client-supplied field deciding tools) is visible at code review time🤖 Generated with Claude Code