feat(python): implement agents and session#5541
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces Genkit agents, including runtime, registration, and bidirectional connection APIs, along with a new Artifacts middleware. The review feedback highlights several critical issues: potential memory and task leaks in _reflection_v2.py and _core/_action.py due to uncleaned connections and uncancelled background tasks; runtime crashes such as a NameError in _ai/_generate.py from a missing copy import, a TypeError in _artifacts.py on empty message content, and a JSON serialization failure in _ai/_agent.py from raw exception objects; and logical bugs like raw dictionaries being ignored in _tool_request_parts and redundant snapshot writes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…art calls in tests
…elete obsolete test
| "packages/genkit/src/genkit/_core/_typing.py" = ["E501"] | ||
| # Base model uses **kwargs: Any to match pydantic signatures | ||
| "packages/genkit/src/genkit/_core/_base.py" = ["ANN401"] | ||
| "packages/genkit/src/genkit/_ai/_json_patch.py" = ["ANN401"] |
There was a problem hiding this comment.
leave a code comment if this is needed
…and interfaces
Decomposed the massive '_base.py' and '_helpers.py' monolothic files into modular, cleanly segregated sub-components without any circular dependencies or type-checking blocks:
- Created '_runtime.py' to house all prompt orchestration helpers, 'SessionRunner', and 'AgentRuntime' execution logic.
- Created '_types.py' to house common agent configuration, transform, and results models.
- Co-located prompt tagging helpers inside '_base.py' and execution helpers inside '_runtime.py'.
- Deleted the obsolete '_helpers.py'.
Cleaned up session store implementation details and interfaces:
- Converted '_session_stores' into an implicit namespace package by deleting the facade '__init__.py'.
- Renamed all private instance variables inside '_session.py' to clean public properties (e.g. 'self._lock' -> 'self.lock', 'self._state' -> 'self.session_state' to avoid method naming conflicts).
- Completely deleted the redundant early-draft 'InMemorySessionStore' class from '_session.py'.
- Replaced all raw string literals with the proper 'StatusCodes' enum values across all files.
- Removed strict RFC-4122 UUID format validation on 'session_id' to allow custom session ID patterns and match JS behavior.
- Cleaned up queue-related type alias indirections ('IntakeQueueItem', 'BidiInQueueItem', 'StreamQueueItem') to use concrete types directly.
All 26 unit tests pass 100% successfully.
…ion docstrings Removed all 'Reference: go/core/action.go ...' comments from docstrings in '_core/_action.py' to clean up python documentation and maintain a Python-first codebase.
Made 'in_queue' unbounded in BidiAction's one-shot adapter and stream_bidi() to avoid blocking the transport connection. Turn-level backpressure is managed separately at the agent runtime intake layer (via 'self.turn_inputs = CloseableQueue(maxsize=1)').
…on class Imported 'StatusCodes' from 'genkit._core._error' and replaced all raw string status code literals (like 'INVALID_ARGUMENT' and 'FAILED_PRECONDITION') with the proper 'StatusCodes' enum values inside '_core/_action.py'.
Renamed the private instance variable '_bidi_fn' to 'bidi_fn' inside '_core/_action.py' to keep the API clean, public, and free of unnecessary leading underscores.
Completely deleted the obsolete 'QueueSentinel' class, 'QUEUE_SENTINEL' constant, and '_SENTINEL' alias from '_core/_action.py'. These were early design remnants that are entirely obsolete now that 'CloseableQueue' natively manages end-of-stream signaling via its async iterator and 'QueueShutDown' exception.
…annel defaults
Simplified the generic type variables for 'BidiConnection' ('StreamInT', 'StreamOutT_co', 'BidiOutT_co') by removing unnecessary PEP 696 'default=Any' parameters, keeping their declarations simple and standard.
Retained the 'default=Any' and 'default=Never' parameters on the core 'Action' and 'Channel' classes to preserve backward compatibility across the SDK (e.g. allowing 'Action[Input, Output]' or 'Channel[Value]' without errors in existing code).
…iance design Added a comprehensive, detailed comment block inside '_core/_action.py' above the 'BidiConnection.__init__' constructor. The comment documents the type theory, covariance, and interior type-erasure choices: 1. Explains that output types are covariant to allow safe subclass polymorphism. 2. Explains that the constructor accepts 'CloseableQueue[Any]' because mutable queues are invariant in Python, and passing covariant type parameters directly would violate type theory and trigger compiler errors. 3. Highlights that 100% type safety remains publicly enforced on the 'send()' and 'receive()' methods.
…riance Simplified and refined the inline typing comment above 'BidiConnection.__init__' to concisely explain covariance and mutable queue invariance using a concrete Dog/Animal subclass polymorphism example.
Rewrote the inline typing comment above 'BidiConnection.__init__' to use simple, plain-English explanations instead of academic type theory jargon (like 'subclass polymorphism' or 'invariance'), making it instantly readable for any developer.
Renamed BidiConnection's internal fields to clean, public properties and simplified the close() docstring: - 'self._in_queue' -> 'self.in_queue' - 'self._out_queue' -> 'self.out_queue' - 'self._result' -> 'self.result' - 'self._closed' -> 'self.closed' This keeps instance variables consistently clean and free of unnecessary leading underscores.
Re-applied and finalized the leading underscore removals on 'BidiConnection' instance variables inside '_core/_action.py', ensuring the editor buffer overwrite is fully resolved. All 26 tests are passing.
Improved the error message in 'BidiConnection.send' when trying to send on a closed connection to be highly descriptive and clear: 'Cannot send input: the BidiConnection is already closed.'
Improved the error message in 'BidiConnection.send' to use clear, helpful, full-sentence prose matching the agents style: 'Cannot send input on BidiConnection because the connection has already been closed. No further inputs can be sent after close() is called.' Also shortened the 'send()' method docstring to a single line to match the user's editor state.
…tructor
Fully parameterized 'in_queue' and 'out_queue' with their generic type parameters ('StreamInT' and 'StreamOutT_co') in the 'BidiConnection' constructor signature.
Since Python's type system allows covariant type variables in '__init__' parameters (which are exempt from variance checking during object instantiation), we do not need to use the 'CloseableQueue[Any]' interior type erasure workaround. This completely removes the 'Any' smell and guarantees 100% strong typing across both internal fields and public boundaries.
Renamed the parameter of 'BidiConnection.send' from 'input' to 'item'. This avoids shadowing the Python built-in 'input()' function and allows us to completely remove the '# noqa: A002' linter suppression comment, keeping the API clean, idiomatic, and 100% compliant with standard Python linting rules.
Removed the redundant 'hasattr(self.in_queue, "close")' check in 'BidiConnection.close()'. Since 'self.in_queue' is strongly typed as 'CloseableQueue', it is guaranteed to have a 'close()' method at both compile-time and runtime. This removes a leftover code smell and simplifies the implementation.
…undant code Completely refactored BidiAction to be extremely concise and idiomatic: 1. Simplified the '_as_streaming_fn' wrapper by leveraging task result/exception propagation directly, eliminating 'result_holder', 'err_holder', and the 'done' asyncio.Event. 2. Removed the redundant 'hasattr(out_queue, "close")' check in 'stream_bidi()' since 'out_queue' is guaranteed to be a 'CloseableQueue'. 3. Simplified the instantiation of 'result_future' to use 'asyncio.Future()'. All 26 tests are passing successfully.
…ream_bidi Inlined the execution of the bidirectional function in 'BidiAction.stream_bidi()' using a lambda expression: 'execute=lambda: self.bidi_fn(input, in_queue, out_queue)' This completely removes the nested '_execute_bidi' helper function, saving 3 lines of boilerplate and keeping the telemetry block clean and elegant. All 26 tests are passing.
Removed leading underscores from all local helper functions defined inside 'BidiAction' methods: - '_as_streaming_fn' -> 'as_streaming_fn' - '_run' -> 'run' (inside 'as_streaming_fn' and 'stream_bidi()') - '_on_trace_start' -> 'trace_start_cb' (renamed to avoid shadowing parameter) Since these functions are scoped locally to their parent methods, they are private by definition and do not need a leading underscore prefix.
Renamed the local helper function from 'as_streaming_fn' to 'as_action_fn'. This name is much more accurate as it indicates that the helper wraps the specialized 'bidi_fn' into a standard, one-shot 'ActionFn' contract expected by the base 'Action' class.
Added an explanatory comment in the 'BidiAction' constructor for the 'bidi: True' metadata flag: - Explains that it is used by the Genkit Dev UI and Reflection API to identify this as a bidirectional action and render the interactive chat interface.
…nsport Completely refactored 'HttpAgentTransport' and 'AgentTransport' to eliminate code smells and linter suppressions: 1. Replaced 'asyncio.Queue' with 'CloseableQueue' and removed the '_SENTINEL' hack, simplifying the stream generator to a clean 'async for' loop. 2. Renamed the 'input' parameter to 'agent_input' in 'AgentTransport.run_turn' and 'HttpAgentTransport.run_turn' to avoid shadowing the Python built-in 'input()' function and remove the '# noqa: A002' comment.
… variables Refactored 'HttpAgentTransport.run_turn' to be highly self-readable: 1. Renamed local background task 'execute_request' -> 'fetch_stream' to clearly reflect its purpose. 2. Renamed the generic 'item' variable to 'chunk' in 'stream_generator' to align with the streaming domain terminology. 3. Simplified future instantiation to use 'asyncio.Future()' directly.
…nsport Completely refactored 'InProcessTransport' inside '_inprocess.py' to align with 'HttpAgentTransport' and eliminate code smells: 1. Replaced 'asyncio.Queue' with 'CloseableQueue' and removed the 'None' sentinel hack, simplifying the stream generator to a clean 'async for' loop. 2. Renamed the 'input' parameter to 'agent_input' in 'run_turn()' to avoid shadowing the Python built-in 'input()' function and remove the '# noqa: A002' comment.
…emove copy hook Refactored 'Agent' to avoid magic copy hooks and improve explicit object lifecycles: 1. Replaced 'copy.copy(self._transport)' with direct instantiation: 'InProcessTransport(self, self.store)' in 'chat()' and 'load_chat()'. 2. Removed the 'copy' module import from '_base.py' and deleted the obsolete '__copy__' method from 'InProcessTransport'. 3. Saved 'store' on the 'Agent' instance as a public 'self.store' property and renamed 'self._transport' to 'self.transport' to align with public clean property standards.
…nsport instances Removed the 'self.transport' property from 'Agent.__init__'. Instead, we now construct the 'InProcessTransport' instance completely on-the-fly and transiently whenever we need it inside: - 'chat()' - 'load_chat()' - 'get_snapshot()' - 'abort()' This keeps the 'Agent' class completely stateless and clean, holding only its static configuration 'self.store' property, with all transport logic fully encapsulated and short-lived.
This PR adds core Agent and Session capabilities, the Middleware plugin framework, schema-to-typing generator updates, and the Python agents sample.