Farooq Haider
All posts
9 min read

Agentic AI in practice, building real agents with Google ADK

What agentic AI actually means in production, and a hands-on walkthrough of building, tooling, and shipping an agent with Google's Agent Development Kit.

Agentic AIGoogle ADKLLMArchitecture

Most teams I talk to have shipped a chatbot. Very few have shipped an agent. The difference is not the model, it is the control loop. A chatbot answers. An agent decides, calls tools, observes what happened, and keeps going until a goal is met or a budget runs out.

What makes a system agentic

Strip away the marketing and an agent is four things wired into a loop: a goal, a set of tools, memory of what already happened, and a stopping rule. Everything else, planning, reflection, multi-agent delegation, is an optimization on top of that loop.

  • Goal: a task description precise enough that success is checkable.
  • Tools: typed functions the model can call, with real side effects and real failure modes.
  • Memory: short-term scratchpad for the current run, long-term store for things worth remembering.
  • Stopping rule: max steps, cost ceiling, or an explicit done signal. Without this, agents burn money.

Where Google ADK fits

The Agent Development Kit is Google's open-source framework for building and evaluating agents. It gives you the loop, tool registration, session and state handling, streaming, evaluation harnesses, and a local dev UI, so you are not rebuilding orchestration plumbing for the third time. It is model-agnostic in practice, though it is best integrated with Gemini.

A first agent

ADK is Python-first. A minimal agent is a model, an instruction, and a list of tools. Tools are just plain functions, the docstring and type hints become the schema the model sees, which is why writing a good docstring is now an engineering skill.

from google.adk.agents import Agent

def get_order_status(order_id: str) -> dict:
    """Look up the fulfillment status of a customer order.

    Args:
        order_id: The public order identifier, e.g. "ORD-10422".
    """
    record = orders_api.fetch(order_id)
    return {"status": record.status, "eta": record.eta_iso}

root_agent = Agent(
    name="support_agent",
    model="gemini-2.0-flash",
    instruction=(
        "You help customers with order questions. "
        "Always call get_order_status before answering about an order. "
        "If the tool fails, say so plainly instead of guessing."
    ),
    tools=[get_order_status],
)

Run it locally with the dev UI and you get a trace of every model call, tool invocation, and state mutation. That trace is the single most useful debugging artifact in agent work, more useful than logs.

pip install google-adk
adk web        # local UI with full run traces
adk run app    # terminal loop

Multi-agent, only when it earns its place

ADK lets you compose agents as sub-agents, so a coordinator can delegate to specialists. This is genuinely useful when subtasks need different tools, different instructions, or different models. It is a mistake when you do it because the diagram looks good. Every hop adds latency, cost, and a new place for context to get lost.

from google.adk.agents import Agent

billing = Agent(name="billing", model="gemini-2.0-flash",
                instruction="Handle refunds and invoices.", tools=[issue_refund])

shipping = Agent(name="shipping", model="gemini-2.0-flash",
                 instruction="Handle delivery and tracking.", tools=[get_order_status])

coordinator = Agent(
    name="coordinator",
    model="gemini-2.0-flash",
    instruction="Route the customer to the right specialist. Do not answer directly.",
    sub_agents=[billing, shipping],
)

Treating agents like distributed systems

This is the part my background keeps pulling me back to. An agent run is a distributed workflow with a non-deterministic scheduler. The same discipline applies.

  • Make tools idempotent, the model will retry, sometimes twice in one run.
  • Put a hard budget on steps and tokens, and fail closed when it is hit.
  • Never let an agent take an irreversible action without a typed confirmation step.
  • Log every tool call with inputs, outputs, and latency, and treat evaluation runs as a regression suite.
  • Version instructions like code, a prompt change is a deploy.

Evaluation is the whole game

ADK ships an eval harness where you define a set of scenarios and expected tool trajectories, not just expected text. Checking that the agent called the right tools in a sensible order catches far more regressions than string matching on the final answer. Build ten scenarios before you build the eleventh feature.

Where I would start

Pick one workflow that is currently a human copying data between two systems. Wrap those two systems as tools, give the agent a narrow instruction, put a human approval gate on the write path, and measure it for two weeks. That is a far better first project than a general assistant, and it is the kind of thing that actually stays in production.