`RemoteA2aAgent` could not authenticate its calls; a caller had to bake a static
token into a custom `httpx_client`. Every other ADK component with a remote
endpoint takes an `auth_scheme`/`auth_credential` pair.
Accept that pair and an optional `credential_key`. `CredentialManager` resolves
the credential once per invocation, and the headers go on the card fetch and the
message send; with nothing to send, the agent emits `adk_request_credential`.
The interceptors and the derived key are per agent, so one agent's token cannot
reach another agent's host.
`build_auth_headers` also stops sending `Bearer None` for a tokenless OAuth2
credential. The interactive round trip needs an `LlmAgent` parent; the
`AgentRegistry` path resolves server-side.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968025847
`_construct_message_parts_from_session` rebuilds the outgoing message from
the session history. It drops `function_response` parts that carry
credential material, but not `function_call` parts. An
`adk_request_credential` call carries a serialized `AuthConfig` in its
arguments, including `raw_auth_credential` (an OAuth2 client secret or a
service account key), and `BaseLlmFlow` appends such an event to the session
whenever a toolset asks the client for a credential. A `RemoteA2aAgent` in
that session then replays the secret to the remote peer.
Drop credential-bearing `function_call` parts too. The scrub runs before
`_present_other_agent_message`, which renders a `function_call` as text with
its arguments inlined, so a later scrub would be too late.
A call counts as credential-bearing by name, or by shape when the name is not
one we know. Only `adk_request_credential` counts by name; the mock auth call
is left alone, because its args hold the peer's own prompt and it stands in for
the peer's last text part. The shape check reads the AuthConfig out of the
`authConfig` field of the AuthToolArguments envelope. A response carries the
AuthConfig flat, so the response-side check reads the top level. Reading the
top level of a request would match nothing, and would drop any ordinary call
that takes an `auth_scheme` argument.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967478046
Gemini models reject requests that combine built-in search tools (such as
GoogleSearchTool or VertexAiSearchTool) with function declarations (such as
transfer_to_agent) with "400 INVALID_ARGUMENT: Tool use with function calling
is unsupported".
This change addresses this in two ways:
1. When built-in search tools (GoogleSearchTool / VertexAiSearchTool) have
`bypass_multi_tools_limit=True` in an agent hierarchy, `multiple_tools`
now accounts for transfer targets so the tool is converted to its
function-tool equivalent (GoogleSearchAgentTool / DiscoveryEngineSearchTool),
allowing it to cleanly coexist with `transfer_to_agent`.
2. When built-in search tools are used without bypass in an agent hierarchy,
`_AgentTransferLlmRequestProcessor` skips injecting `transfer_to_agent`
and transfer instructions, ensuring only the built-in search tool is sent
to the model without conflict.
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 967423502
Merge https://github.com/google/adk-python/pull/6646
Reject yaml and ruamel unsafe/full loaders in config agent to prevent RCE.
Note: ruamel is a transitive dev/test dependency, but is blocked to prevent RCE if present in the environment.
PiperOrigin-RevId: 967340296
When one agent hands off to another, `_present_other_agent_message` replays the
first agent's turn to the second as a `role="user"` message -- the same channel
the real user speaks on -- interpolating the text straight into
`[agent] said: ...`. Nothing marks where the quoted transcript ends, so a
payload the first agent was talked into emitting reads to the second agent as a
fresh directive. Anyone who can chat to a low-privilege front-end agent can
therefore aim instructions at whatever tools the agent it transfers to holds.
Every relayed payload -- text, thoughts, tool arguments, tool results -- is now
quoted between explicit markers, and the leading part of the message states
that what sits between them is data to read and not instructions to follow.
Markers occurring inside a payload are elided first, so quoted content cannot
close its own block and carry on speaking as the framework.
The markers, the preamble and the quoting helpers live in
`flows/llm_flows/_fencing.py`. The unit tests and the conformance harness both
have to spell the expected framing, so it sits in a module of its own rather
than inside `contents.py`, where they would have to reach for private names.
This raises the bar rather than closing the class: a model can still be talked
round by text it was told to distrust. What it removes is the structural
ambiguity that made a relayed payload indistinguishable from a user turn.
Relayed turns now cost the preamble plus two marker lines per part, and
anything matching on the old `For context: [x] said: y` shape needs updating.
The conformance replay harness is one such matcher, and now reduces a relayed
turn to the payload it carries before comparing, so recordings cut before the
fencing still replay.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 966694665
- Support mode="task" in RemoteA2aAgent to natively execute task-oriented tools.
- Ensure RemoteA2aAgent in task mode registers the finish_task tool definition.
- Fix task scope aggregation during delegation using a two-pass active task scope search in the runner.
- Correctly map terminal states and status values inside A2aAgentExecutor.
PiperOrigin-RevId: 964898667
RemoteA2aAgent now rewrites human-input pause responses (adk_request_input,
adk_request_confirmation, adk_request_credential, and the mock input/auth calls)
to text before forwarding them to a remote agent on resume, matched by the
function call name. This stops Runner._validate_new_message from rejecting a
resumed message that mixes function responses with text. Credential (AuthConfig)
payloads are dropped instead of forwarded, and real long-running tool responses
are preserved so the peer can resume them by id.
PiperOrigin-RevId: 963124368
The denylist for YAML code references named dangerous standard library modules
one by one, so anything it missed stayed reachable: it had `profile` but not
`cProfile`, `pdb` but not `bdb`, `trace`, `timeit` or `pydoc`. Several of those
execute a string you hand them and need no constructor `args`, so naming one as
a tool or callback slipped past both existing mitigations and ran arbitrary
code.
Block the standard library outright via `sys.stdlib_module_names`. Configs only
ever name the agent's own package, `google.adk`, or a third-party integration,
so nothing legitimate breaks and the list stops needing a revisit every Python
release. The explicit denylist stays for names that are no longer reported as
standard library but remain importable, such as `distutils` and CPython's
`test` packages.
A denylist still cannot cover third-party packages, which the loader resolves
by name, so this narrows the surface rather than closing it.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 961151524
Not annotations-only. This is one component's slice of a repo-wide typing
cleanup, and the wider change was found to contain behavior changes that have
not all been individually triaged, so please review it as a functional change.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 958623646
Previously, HTTP trace debugging was only captured during MCP tool execution (in
`McpTool.run_async`) and stored in `ToolContext.custom_metadata`. This left other
MCP HTTP calls, such as session initialization and tool listing (`list_tools`),
untraced, making it difficult to debug failures in these phases.
This CL extends the tracing capability:
- Exposes `custom_metadata` on `ReadonlyContext` as a read-only property.
- Keeps `custom_metadata` on `Context` as a mutable property.
- Wraps `McpToolset._execute_with_session` with `_http_debug_var` to capture HTTP
traces during session creation and toolset operations (e.g., `get_tools`,
`read_resource`).
- Appends captured traces directly to the underlying `_invocation_context._custom_metadata`
using protected access, ensuring that traces are populated even when a
`ReadonlyContext` is passed (like during tool listing), while still keeping the
`ReadonlyContext` public API strictly read-only.
- Adds unit tests to verify that HTTP traces are captured for both mutable and
read-only contexts.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 956759438
Merge https://github.com/google/adk-python/pull/5852
## Link to Issue or Description of Change
### Problem
When a `RemoteA2aAgent` is used as a static node in a `Workflow` graph that feeds into a `JoinNode`, the joined output contains `None` for every `RemoteA2aAgent` predecessor.
Reproducer (simplified from a real coordinator graph):
```python
parallel_investigation_join = JoinNode(name="parallel_investigation_join")
Workflow(
edges=[
("START", account_context_agent, parallel_investigation_join), # RemoteA2aAgent
("START", ticket_history_agent, parallel_investigation_join), # RemoteA2aAgent
("START", diagnostics_agent, parallel_investigation_join), # RemoteA2aAgent
...
]
)
```
Observed `JoinNode` input:
```yaml
parallel_investigation_join:
account_context_agent: null
ticket_history_agent: null
diagnostics_agent: null
```
Root cause: `RemoteA2aAgent` inherits the default `BaseAgent._run_impl`, which iterates `run_async` and yields events without ever setting `event.output` or `event.node_info.message_as_output`. As a result, `NodeRunner._track_event_in_context` leaves `ctx.output` as `None`, and `Workflow._handle_completion` never records an entry in `loop_state.node_outputs` for that predecessor. `JoinNode` then sees `None` for it.
`LlmAgent` already solves the equivalent problem by overriding `_run_impl` and promoting the model's text reply to `event.output` (via `process_llm_agent_output` in `_llm_agent_wrapper.py`). `RemoteA2aAgent` had no equivalent hook.
### Solution
Add a workflow-only override of `_run_impl` on `RemoteA2aAgent` that mirrors `LlmAgent`'s behavior. For each event yielded by `BaseAgent._run_impl`, a new `_promote_response_to_output` helper joins the text of all parts that are **not** thoughts, function calls, or function responses, assigns it to `event.output`, and sets `event.node_info.message_as_output = True` (consistent with `LlmAgent`, prevents `NodeRunner._flush_output_and_deltas` from emitting a duplicate trailing output event).
The helper skips:
- partial events (streaming chunks)
- events not authored by this agent
- events whose `event.output` is already set
- events whose content carries only thoughts (streaming `working` / `submitted` task statuses that the legacy `_handle_a2a_response` marks `thought=True`)
- events whose content carries only function calls (the `input_required` / `auth_required` mock function call inserted by `_create_mock_function_call_for_required_user_input` — those should remain interrupts, not outputs)
- events whose A2A task state is non-final (`submitted`, `working`, `input-required`, `auth-required`, `unknown`). The v2 integration path (`_handle_a2a_response_v2`) delegates to converters that do **not** mark streaming `working` text as `thought=True`, so the thought filter alone is not enough. Without this guard, a `working` text event and the subsequent `completed` text event would each try to set `event.output`, causing `NodeRunner` to raise `ValueError: Output already set` on the second event and aborting the run before the real final answer ever surfaced. The state is read from `event.custom_metadata['a2a:response']['status']['state']`, which `_run_async_impl` already stamps before yield. Plain `A2AMessage` responses (no status field) and terminal task states (`completed`, `failed`, `canceled`, `rejected`) still promote.
In addition, `_run_impl` short-circuits after the first successful promotion. This protects against the case where a server emits multiple terminal-state events for one run (e.g. a `completed` status update followed by trailing artifact updates on the same already-completed task) — only the first terminal event becomes the node's output, subsequent ones pass through untouched.
Scope is intentionally narrow: only the agent boundary is touched. `to_adk_event.py` and the workflow scheduler are unchanged, since the same workaround (promoting content → output at the agent layer) is what `LlmAgent` does and what keeps the fix local.
## Testing Plan
### Unit Tests
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
Added `TestRemoteA2aAgentWorkflowOutput` in `tests/unittests/agents/test_remote_a2a_agent.py` (20 cases).
### Manual End-to-End (E2E) Tests
I ran the failing workflow described in the **Problem** section against [a real ADK app](https://github.com/gdsc-osaka/customer-support-agent-example/blob/2b2882ed0bd0e918aeaaa38b2e420f129db47dc1/agents/coordinator/agent.py#L27): a `Workflow` graph whose `START` fans out into multiple `RemoteA2aAgent` nodes that all feed into a single `JoinNode`. Each remote specialist runs as its own A2A server; the coordinator runs the workflow and forwards the joined dict to a downstream synthesis step.
Before the fix:
<img width="1624" height="1060" alt="Screenshot 2026-05-26 at 15 32 06" src="https://github.com/user-attachments/assets/22ddb04b-4c1d-4c75-a2ec-d4f1e87bc825" />
After the fix:
<img width="1580" height="1016" alt="Screenshot 2026-05-26 at 15 30 25" src="https://github.com/user-attachments/assets/0848a663-504f-4e60-99ff-81aa70daf164" />
### Checklist
- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.
### Additional context
The fix mirrors the pattern `LlmAgent` already uses (`process_llm_agent_output` in `src/google/adk/workflow/_llm_agent_wrapper.py`), keeping output-promotion at the agent boundary rather than touching the workflow scheduler or the A2A converters. This minimizes blast radius and avoids regressions in non-workflow usages of `RemoteA2aAgent`, where the `_run_impl` path is not exercised.
Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5852 from Harineko0:fix/remote-a2a-agent-workflow-output 0f262921e5ccbe24fbb33427dd378fad857cd8da
PiperOrigin-RevId: 955426327
Serialize the fingerprint with sorted JSON keys, exclude_none, and a
stable tool order so reordered tools or SDK field drift no longer change
the hash. Existing sessions take a one-time cache miss while the stored
fingerprint is recomputed.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 955368831
The agent card fetch could only be authenticated via a pre-built httpx
client with credentials fixed at construction time; request_interceptors
only wrap send_message, so the card GET stayed unauthenticated.
Add card_request_interceptors on A2aRemoteAgentConfig (symmetric with
request_interceptors). Each CardRequestInterceptor.before_request is an
async hook returning a typed A2aCardRequestConfig whose headers are injected
into the card request.
PiperOrigin-RevId: 955226237
An agent-level `generate_content_config.http_options.base_url` is copied into
every LlmRequest and overrides the client transport, so the configured API key
and the full prompt/response traffic are sent to that host. Nothing rejected
it, so a supplied agent config (including a YAML one) could redirect a
credentialed model call to an arbitrary endpoint.
`http_options.extra_body` is recursively merged into the serialized request
body just before it is sent, and the merge aligns the incoming key case to the
target, so it can overwrite `systemInstruction`, `tools` and `generationConfig`
— the exact fields the other three checks in this validator exist to reject.
It bypassed all of them.
Reject both in the field validator. Request-time `http_options` such as
headers, timeout, and retry options are unaffected; `base_url` belongs on the
model or its client, which is already why `RunConfig.http_options` deliberately
does not merge it.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 953629510
Ensure custom metadata defined in RunConfig is copied to InvocationContext's private custom metadata attribute during model post-initialization. This enables tools to retrieve context-dependent metadata during execution.
PiperOrigin-RevId: 951981807
Resolve ManagedAgent.instruction (with {placeholder} state injection for
strings; InstructionProvider callables bypass injection) and forward it to
the Managed Agents API as the interaction's system_instruction on every
turn, including chained turns. An empty instruction sends nothing.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 951220238
Add an `instruction` field (str or InstructionProvider) to ManagedAgent
and a `canonical_instruction` resolver mirroring LlmAgent. This task only
adds the field and resolver; forwarding it to the Managed Agents API is a
follow-up change.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 951193016
The non-live runner path can apply session state changes via a state_delta,
but the live/bidi flow offered no equivalent, so live callers could not seed
or update session state alongside their input. Add an optional state_delta
field to LiveRequest and, in the live flow, apply it as a separate
state-delta event so it always takes effect regardless of content, partial,
or function-response requests. The field defaults to None, preserving
existing behavior.
Close#4220
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 951162326
The old LangGraph version range allowed releases affected by a published security advisory, and the adapter called synchronous state and invocation APIs from an async path, including a state lookup with no checkpointer. This requires a patched LangGraph release and moves the adapter to the async compiled-state APIs (CompiledStateGraph, aget_state, ainvoke), skipping the state lookup when the graph has no checkpointer.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 950895002
Task-mode LlmAgents produce intermediate conversational text responses (such as clarification questions or greetings) before completing their task via finish_task.
When output_key and output_schema were configured on a task-mode agent, ADK attempted to validate intermediate conversational text against output_schema during state_delta processing, causing a ValidationError crash on non-JSON text.
- Skip output_key state_delta processing on intermediate text responses for task-mode agents (mode="task") in __maybe_save_output_to_state and _save_output_to_state.
- Preserve output_schema validation for non-task agents.
- Ensure task-mode finish_task validated output is written to output_key state_delta on task completion.
- Add unit tests for task-mode output_key handling.
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 949357458
When a cache expired, renewal recreated the original prefix, so completed turns could stay outside every refreshed cache. It also assumed a 4,096-token floor for every model, which skipped eligible Gemini 2.5 prefixes between 2,048 and 4,095 tokens. This grows the cache to the latest validated prefix and applies the documented 2,048-token floor for Gemini 2.5 while keeping 4,096 for Gemini 3.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 949034094
The client derived cache expiry from the local clock right after creation, but network delay or server-side normalization can push the real expiry off that estimate and lead to reuse at the wrong boundary. This records the expire_time the API returns when present, and falls back to the local TTL only when the response omits it.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 947961020
Merge https://github.com/google/adk-python/pull/6367
### Link to Issue or Description of Change
- Closes: #6363
**Problem:**
[PR #6067](https://github.com/google/adk-python/pull/6067) correctly changed fingerprint-only cache metadata to use the cacheable conversation prefix rather than all request contents. This keeps trailing user contents, including request-scoped dynamic instructions, out of the stable cache identity.
For an initial request containing only the latest user content, the cacheable conversation prefix is legitimately empty. ADK therefore stores fingerprint-only metadata with `contents_count == 0`. It does not create a cache on this initial request because no token count from a previous response is available yet.
On the next request, ADK uses the stored count to calculate the current fingerprint. When the system instruction and tools remain unchanged, that fingerprint matches the stored fingerprint. If the system instruction and tools are large enough to meet Gemini’s cache-size requirement, ADK makes its first real cache-creation attempt.
The stored count is deliberately reused because it defines the prefix covered by the matched fingerprint. Even if the later request now has a non-empty cacheable conversation prefix, ADK still slices it with the stored zero. This produces `llm_request.contents[:0] == []`, which is passed as `CreateCachedContentConfig(contents=[])`.
The Google Gen AI SDK distinguishes an omitted `contents` field from an explicit empty list. It rejects `contents=[]` with `ValueError: contents are required`, while `contents=None` omits only the conversation contents and still allows the system instruction and tools to be included in the cache request.
ADK catches the exception, logs a warning, and continues the model request without a cache, so the request itself does not crash. Because no `cache_name` was created, the caller preserves the fingerprint-only metadata and its original `contents_count == 0`. This behavior is intentional so transient cache-creation failures can retry the same cache identity.
As long as the system instruction and tools remain unchanged, the next request again matches the same fingerprint using the preserved count. ADK then retries the same invalid `contents=[]` configuration, logs another warning, and again receives no `cache_name`. This cycle repeats on every matching request, so the cache is never established.
**Solution:**
Pass `None` when the cacheable conversation prefix is empty so the SDK omits the `contents` field. Preserve the existing list behavior for non-empty prefixes.
The existing zero-prefix lifecycle test now verifies that the SDK-bound configuration uses `config.contents is None`.
### Testing Plan
**Unit Tests:**
- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.
Focused test result:
- `tests/unittests/agents/test_gemini_context_cache_manager.py`: 33 passed, 5 warnings.
The five warnings are unrelated to this change: four are `BaseAgentConfig` deprecation warnings, and one reports that the experimental `AGENT_CONFIG` feature is enabled.
The relevant context-cache tests passed under tox with Python 3.10 through 3.14.
Additional validation:
- Ruff passed.
- Pyink passed.
- Pre-commit and compliance checks for the changed files passed.
- The source distribution and wheel built successfully.
- The wheel installed and imported successfully in a clean Python 3.12 environment.
- `git diff --check` passed.
**Manual End-to-End (E2E) Tests:**
Not run because this environment cannot create and inspect a real Google cache resource.
The official SDK documentation and source confirm the relevant boundary behavior:
- [`CreateCachedContentConfig`](https://googleapis.github.io/python-genai/genai.html#genai.types.CreateCachedContentConfig) allows `contents` to be `None` and defines `system_instruction` and `tools` as separate fields.
- The [SDK cache request builder](https://github.com/googleapis/python-genai/blob/v2.11.0/google/genai/caches.py#L270-L292) only transforms `contents` when it is not `None`; `system_instruction` and `tools` are handled independently.
- The SDK’s [`t_contents()` implementation](https://github.com/googleapis/python-genai/blob/v2.11.0/google/genai/_transformers.py#L476-L482) explicitly rejects an empty list with `ValueError("contents are required.")`.
Therefore, `None` omits the optional field, while `[]` enters the transformer and is rejected. The unit test verifies that ADK produces the valid side of this boundary.
### Checklist
- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas. No additional comments were needed for this focused boundary conversion.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [ ] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules. Not applicable; there are no dependent changes.
### Additional context
The root cause of the missed regression in the existing tests is that `caches.create()` is mocked and therefore does not reproduce the Google Gen AI SDK’s complete request transformation and validation behavior. The mock accepted `contents=[]` and returned success, so the existing zero-prefix lifecycle test passed even though the real SDK rejects that value before sending the request.
No user-facing API or documentation changes are required.
Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6367 from hxaxd:fix/gemini-cache-empty-contents 22f1a8b6fbb724194f37d8c9d061b4d925ee3527
PiperOrigin-RevId: 947285615
ManagedAgent and Gemini(use_interactions_api=True) both reach the Interactions
API and surface identically as tool_name=google-adk in Google's usage pipeline,
with no way to tell them apart. Thread an optional framework_label through
merge_tracking_headers / get_tracking_headers / get_client_labels /
_get_default_labels, and have ManagedAgent emit google-adk/<version>+managed_agent
on the per-request extra_headers it sends to interactions.create, so its traffic
is distinguishable via the tool_version dimension while tool_name stays
google-adk. The suffix is applied on the request-time header path because that is
what reaches the Interactions wire (the per-request extra_headers override the
genai client's construction-time headers; verified by live capture). An explicit
framework_label takes precedence over the Agent Engine (+remote_reasoning_engine)
suffix; all other callers of merge_tracking_headers keep the no-arg default and
are unchanged. Follow-up to the ManagedAgent tracking-headers change.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 947239702
Relax the parent `LlmAgent` auto-wrapping loop so any sub-agent declaring
`mode='single_turn'` is exposed as an inline `_SingleTurnAgentTool`, not just
`LlmAgent` sub-agents. This lets a `ManagedAgent` with `mode='single_turn'` be
called like a tool while staying excluded from LLM-transfer targets. `LlmAgent`
sub-agents still default to `mode='chat'` (unchanged), and sub-agents that do
not declare `mode` are never wrapped.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945960582
Override `ManagedAgent._run_impl` so that, when the agent runs as a node (e.g.
as a single-turn tool), the parent's tool-call argument arriving as `node_input`
is surfaced as the agent's `user_content` and forwarded to the interactions API.
When `node_input` is `None` (classic agent-tree run), behavior matches
`BaseAgent._run_impl`. Uses the shared `node_input_to_content` helper.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945949834
- Remove implicit on-demand fabrication of DynamicNodeScheduler during child Context derivation to avoid mutating parent context state during execution.
- Always instantiate and set the DynamicNodeScheduler at the root level in the runner.
- Clear `_workflow_scheduler` on execution exit (in both runner and workflow loops) to prevent scheduler lifetime leakage.
- Update tests to explicitly set `rerun_on_resume = True` where resumption of mock nodes is expected.
- Always rerun Workflow nodes on resume to allow their internal loops to correctly drive resumption of child nodes.
- Use `weakref` for `_workflow_scheduler` in `Context` to prevent reference cycle between scheduler, tasks, and contexts.
PiperOrigin-RevId: 945945253
Add a `mode` field to `ManagedAgent` accepting `'single_turn'` (or `None`, the
default). `single_turn` marks the agent to run as an inline single-turn tool of
a parent `LlmAgent` -- the recommended replacement for `AgentTool` -- while
`None` leaves it usable as an LLM-transfer target. This commit only adds and
validates the field; the runtime wiring follows in later commits.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945930825
Merge https://github.com/google/adk-python/pull/6212
The skill extracts Python code blocks from a markdown file, executes each in an isolated environment, and generates a report covering load, run, and coverage status.
Fixes#6211
Also includes fixes for:
- Weakref GC of mock scheduler in test_context.
- check_new_py_files.sh path matching in monorepo environments.
PiperOrigin-RevId: 945889882
ManagedAgent now forwards per-request headers on its Interactions API calls:
RunConfig.http_options.headers merged with ADK tracking headers (via
merge_tracking_headers), passed through _create_interactions as extra_headers.
This gives parity with google_llm's request-time header merge, so user-supplied
headers and ADK identifiers ride on every ManagedAgent request.
Co-authored-by: Haran Rajkumar <haranrk@google.com>
PiperOrigin-RevId: 945824470