In Google ADK, how does a tool get a ToolContext and what can it do with it?
answer
- Declared, not looked up
- Hidden from the model's schema
- State writes ride on the tool's event
- One field can redirect the turn
- Ties the call id for later replies
basics
~20 sDeclare a parameter annotated with ADK's ToolContext type and the framework injects it, hiding it from the model's schema. Through it a tool reads and writes session state, sets actions such as skipping summarization or transferring control, and reaches artifacts, memory and auth.
solid answer
~40 s`ToolContext` is ADK's in-tool handle on the running invocation. You get one by adding a parameter annotated `tool_context: ToolContext` to your function — ADK injects it at call time and strips it from the function declaration, so the model never sees it and never passes it. What it gives you: `tool_context.state`, a dict-like view of session state whose writes are collected as a state delta on the event ADK emits for this tool's response, so state changes land atomically with the result rather than being written behind the framework's back; `tool_context.actions`, which lets a tool influence control flow (`skip_summarization`, `transfer_to_agent`, `escalate`); `tool_context.function_call_id`, tying the call to the model's request, which auth and long-running flows need; plus artifact helpers (`save_artifact`, `load_artifact`, `list_artifacts`), `search_memory`, and the credential request/response pair for authenticated tools.
code
python · 12 linesfrom google.adk.tools import ToolContext
def set_language(language: str, tool_context: ToolContext) -> dict:
"""Stores the user's preferred output language for later turns.
Args:
language: A language name such as "Japanese".
"""
tool_context.state["user:language"] = language
tool_context.actions.skip_summarization = True
return {"status": "success", "language": language}go deeper
Remember that adding a context-typed parameter is how a tool reaches session state, and that the model never supplies it because ADK hides it from the schema.
Explain the mechanics: injection by annotation, state writes recorded as a delta on the tool's response event, and the action fields that let a tool skip summarization, transfer, or escalate.
Show the operational judgment — why globals break under persisted sessions and multiple instances, why bulk content belongs in artifacts, and when deterministic transfer beats letting the model route.
Set the house rules: which state keys tools may write and under which scope, when a tool is allowed to redirect control flow, and how that stays reviewable as the number of tools grows.
## Why a context object exists at all A tool that only maps arguments to a return value needs nothing from the framework. Real tools need more: they want to remember something for later turns, hand a file to the user, look something up in long-term memory, ask for OAuth credentials, or nudge control flow. `ToolContext` is the sanctioned channel for all of that. Without it, a tool would have to reach for module-level globals — which are invisible to ADK's session layer, do not survive a process restart, and break the moment sessions are backed by a database or the managed service instead of memory. ## Getting one There is no lookup function and no global. You declare it: ``` def remember_choice(choice: str, tool_context: ToolContext) -> dict: ... ``` ADK recognises the annotation, injects the object when it calls your function, and removes that parameter from the declaration sent to the model. The model sees a one-argument tool. This matters for schema hygiene: the model can never be tempted to hallucinate a context argument. ## State `tool_context.state` behaves like a dictionary. Reads see the state as it stands for this invocation, including changes made earlier in the same run. Writes are the interesting part: they are not applied directly to storage. ADK records them as a delta on the event it emits for the tool's response, and the session service applies the delta when that event is appended. Two consequences worth saying out loud in an interview: 1. **Atomicity with the result.** The state change and the tool result are committed together as one event. A tool that fails before returning does not leave half-written state. 2. **Auditability.** Because every change rides on an event, the session's event log explains not just *what* the state is but *which tool call changed it* — which is why ADK's dev UI can show state evolving step by step. Key names are meaningful: ADK's session layer routes keys by prefix into different scopes (per-app, per-user, and a scratch scope that is not persisted), so a tool that writes a user preference should use the user-scoped naming rather than a bare key. ## Actions `tool_context.actions` carries the levers a tool can pull on the surrounding flow: - `skip_summarization = True` — return the tool's output as the response instead of letting the calling model rewrite it. Useful when the tool already produced exactly the text or structured payload you want the user to see, and re-summarizing would only cost tokens and risk distortion. - `transfer_to_agent = "<agent name>"` — hand the rest of the turn to another agent, in code rather than by the model deciding. This is the escape hatch for deterministic routing: a `check_permissions` tool that discovers the user isn't entitled can route to a different agent instead of hoping the model reads its error message correctly. - `escalate = True` — signal upward, most commonly to break out of a loop-style workflow when a completion condition is met. These are the reason an interviewer will say "a tool is not just a function in ADK": tools can change what happens next, not merely produce data. ## Identity and long-running flows `tool_context.function_call_id` is the id of the model's function call that triggered this execution. It is what lets a result be correlated back later — the basis of ADK's long-running tool pattern, where the final answer arrives in a separate message minutes or hours after the call, and of the credential flow, where an auth response must be matched to the request that asked for it. ## Artifacts, memory and auth - **Artifacts** — `save_artifact(filename, artifact)`, `load_artifact(filename)`, `list_artifacts()`. Binary or large content belongs here, not in state and not in the tool's return value; the tool returns a filename and the payload stays out of the prompt. - **Memory** — `search_memory(query)` lets a tool query the configured memory service for material from past sessions. - **Auth** — `request_credential(...)` and `get_auth_response(...)` implement the two-phase flow where a tool discovers it needs credentials, the client obtains them, and the tool is re-entered with them available. ## Common failure modes Mutating a module-level dict instead of `tool_context.state` is the classic one: it works in a single-process dev run and silently loses data as soon as sessions are persisted or the app is scaled out. A second is writing large blobs into state — state is carried and serialized per event, so it should hold small facts, with bulk content in artifacts. A third is expecting state writes to be visible to other concurrently-running branches mid-step; they become visible once the event carrying them is appended. ## What interviewers listen for That you know the injection is by annotation and invisible to the model; that state writes flow through events rather than straight to storage; and that `actions` is where a tool influences the conversation itself.
- Why not just write to a module-level dict instead of tool_context.state?Because the framework never sees it. State written through the context is recorded as a delta on the tool's response event and applied by the session service, so it persists, appears in the session's event log, and survives a restart or a move to a database-backed session service. A module global lives in one process, disappears on restart, and diverges the moment you run more than one instance.
- When would a tool set skip_summarization on its actions?When the tool's output is already the answer — a formatted report, a rendered table, an exact quote, or a structured payload the client will render itself. Letting the calling model rewrite it costs another model round trip and risks paraphrasing away precision. Leave summarization on when the raw result is machine-shaped and needs turning into prose.
- What is function_call_id used for?It identifies the specific model-issued call your tool is servicing. ADK uses it to pair a later response with the original request — which is what makes long-running tools work, since the real result may be delivered in a separate message much later, and what lets an authentication response be matched to the tool that asked for credentials.
saying these in an interview costs you the question
- Says you call a global getter to obtain the context
- Thinks the model must pass tool_context as an argument
- Writes state to module globals instead of the context
- Believes state writes hit the database immediately
- Stores large blobs in state rather than artifacts