Sitemap
Google Cloud - Community

A collection of technical articles and blogs published or curated by Google Cloud Developer Advocates. The views expressed are those of the authors and don't necessarily reflect those of Google.

Your First ADK Agent: Building a Google Tasks To-Do Manager

--

Introduction

Imagine effortlessly managing your Google Tasks just by chatting with an AI. What if you could interact with your to-do list using natural language, just like talking to an assistant? “Remind me to call John tomorrow,” or “What are my most urgent tasks?”.

That’s where Google’s Agent Development Kit (ADK) comes in. ADK is a framework that lets developers like us build AI-powered agents capable of understanding requests and interacting with external services.

In this tutorial, we’ll explore ADK by building a practical and engaging project: a conversational agent that manages your Google Tasks. You’ll see firsthand how ADK simplifies the process of integrating external APIs and giving your AI a tangible way to assist you.

Here’s what we’ll cover together:

  • A gentle introduction to the core concepts of Google’s ADK.
  • The essential steps to authorise your application to access your Google Tasks data securely using OAuth 2.0.
  • How to transform regular Python functions into “tools” that your ADK agent can use to list, add, and complete tasks.
  • Running your agent and interacting with it to manage your to-do list conversationally.

Whether you’re new to AI development or looking to explore the capabilities of Google’s ADK, this tutorial will provide a solid foundation and a working example you can build upon. Let’s get started!

If you want a video version of the blog, check out the video given below.

Step1: Setup & Prerequisites

Before we dive into building our intelligent Google Tasks agent, let’s make sure you have all the necessary tools and configurations in place. Following these steps carefully will ensure a smooth development experience.

1. Python (Version 3.9 or newer recommended)

Our agent will be written in Python. If you don’t have Python installed, or have an older version, head over to the official Python website to download and install the latest version suitable for your operating system:

python.org/downloads/

2. A Google Cloud Project

To use Google APIs like Google Tasks, you’ll need a Google Cloud Project. This project will house your API configurations and credentials.

  • If you don’t have one already, go to the Google Cloud Console.
  • You might be prompted to sign in with your Google account and agree to the terms of service.
  • Create a new project:
  • Click the project dropdown in the top navigation bar (it might say “Select a project”).
  • Click “NEW PROJECT.”
  • Give your project a descriptive name (e.g., “ADK Tasks Agent Project”) and click “CREATE.”

3. Enable the Google Tasks API

Once your project is set up, you need to enable the Google Tasks API for it:

  • In the Google Cloud Console, make sure your newly created project is selected (check the project dropdown).
  • Use the search bar at the top and type “Google Tasks API”. Select it from the results.
  • Alternatively, navigate using the menu: APIs & Services > Library. Search for “Google Tasks API.”
  • Click on the “Google Tasks API” card.
  • Click the “ENABLE” button. If it says “MANAGE”, the API is already enabled.

4. Obtain OAuth 2.0 Credentials (client_secret.json)

To allow your Python application to securely access your Google Tasks data on your behalf, you need OAuth 2.0 credentials.

  • In the Google Cloud Console, navigate to APIs & Services > Credentials.
  • Click “+ CREATE CREDENTIALS” at the top of the page.
  • Select “OAuth client ID” from the dropdown menu.
  • If prompted, you might need to configure your “OAuth consent screen” first:
    1. Choose “External” for User Type and click “CREATE.”
    2. App name: Enter something like “ADK Tasks Agent”.
    3. User support email: Select your email address.
    4. Developer contact information: Enter your email address.
    5. Click “SAVE AND CONTINUE” through the Scopes and Test Users sections for now (we define scopes in our code, and for a desktop app, test users aren’t strictly necessary for your own use). Then click “BACK TO DASHBOARD” for the consent screen.
    6. Now, go back to APIs & Services > Credentials and click “+ CREATE CREDENTIALS” > “OAuth client ID” again.
  • For “Application type,” select “Desktop app.”
  • Give your OAuth client ID a name (e.g., “ADK Tasks Agent Client”).
  • Click “CREATE.”
  • A pop-up will appear showing your “Client ID” and “Client secret.” Click “DOWNLOAD JSON”.
  • Save this downloaded file.
  • Rename it to credentials.json.
  • Place this client_secret.json file in the same directory where you plan to create your Python script (agent.py). Our code will look for this exact filename in its local directory.

5. Install Required Python Libraries

We need a few Python libraries for our project. Open your terminal or command prompt and install them using pip:

pip install google-generativeai google-adk google-auth-oauthlib google-api-python-client python-dotenv
  • google-generativeai: For interacting with Google’s generative AI models like Gemini (used by ADK).
  • google-adk: The Agent Development Kit itself.
  • google-auth-oauthlib & google-api-python-client: For Google API authentication and client services.
  • python-dotenv: To manage environment variables (like your API key or model choice) easily.

6. Set Up Environment Variable for the Model (Optional but Recommended)

While not strictly required for the Google Tasks API part, ADK uses a language model. We’ll specify gemini-1.5-flash-latest in our code, but it’s good practice to know how to use environment variables.

  • Create a file named .env (note the leading dot) in your project directory (the same place as client_secret.json and your Python scripts).
  • Add the following to your .env file:

# Choose Model Backend: 0 -> ML Dev, 1 -> Vertex AI
GOOGLE_GENAI_USE_VERTEXAI=0

# Vertex AI backend config
GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
GOOGLE_CLOUD_LOCATION=us-central1

# ML Dev backend config
GOOGLE_API_KEY="your_api_key"
  • Our Python script will use the python-dotenv library to load these values.

Step2: Google Tasks API & Authentication

With our perquisites configured, its time to write some python code. In this step we’ll build the core functions that allow are application to communicate with the Google Tasks API. These functions will handles the authentication process and provide the ability to list, add, and complete tasks. These will become the tools for our ADK agent later on.

import os
import pickle # For storing token
from google.adk.agents import Agent
from google.genai import types
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError


MODEL = "gemini-2.0-flash-001"
SCOPES = ['https://www.googleapis.com/auth/tasks']
TOKEN_PICKLE_FILE = 'token.json'
CREDENTIALS_FILE = 'credentials.json'

Open your newagent.py file and start by adding the necessary imports and loading your variables.

# --- Google Tasks API Service ---
def get_tasks_service():
"""Shows basic usage of the Google Tasks API.
Returns an authenticated Google Tasks API service object.
"""
creds = None
if os.path.exists(TOKEN_PICKLE_FILE):
with open(TOKEN_PICKLE_FILE, 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not os.path.exists(CREDENTIALS_FILE):
raise FileNotFoundError(
f"'{CREDENTIALS_FILE}' not found. "
"Please download it from Google Cloud Console and place it in the current directory."
)
flow = InstalledAppFlow.from_client_secrets_file(
CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(TOKEN_PICKLE_FILE, 'wb') as token:
pickle.dump(creds, token)
try:
service = build('tasks', 'v1', credentials=creds)
return service
except Exception as e:
print(f"An error occurred building the service: {e}")
if os.path.exists(TOKEN_PICKLE_FILE):
print(f"Try deleting '{TOKEN_PICKLE_FILE}' and running again.")
raise

We start off by checking if our TOKEN_PICKLE_FILE exists. If it does, it tries to load the store credentials using pickle.load() . If credentials are loaded but have expired, it attempts to refresh them using creds.refresh(Request()). If refreshing fails or no credentials are found, we trigger a full OAuth 2.0 flow. Before that, we check if the credentials.json file exists—this file contains our client secrets and is essential for authentication.

We then use InstalledAppFlow.from_client_secrets_file() to load the config and start a local server with flow.run_local_server(port=0), which opens a browser window prompting the user to sign in and authorise access.

Once the user grants permission, the flow captures the authorization code, exchanges it for access and refresh tokens, and stores them securely in token.json using pickle.dump() for future sessions.

Finally, with the valid credentials in hand, we build the Google Tasks API service using build('tasks', 'v1', credentials=creds)—this is what enables authenticated interactions with the API.

The get_tasks_services() function is a cornerstone of our interaction with Google Tasks, handling all the complexities of OAuth 2.0 for us.

_task_list_cache = None
_task_id_map_cache = None

def _fetch_and_cache_tasks(service):
"""Helper to fetch tasks and populate caches."""
global _task_list_cache, _task_id_map_cache
_task_list_cache = []
_task_id_map_cache = {}
try:
results = service.tasks().list(tasklist='@default', showCompleted=False, showHidden=False, maxResults=100).execute()
items = results.get('items', [])
if items:
for i, task_item in enumerate(items):
if task_item.get('status') != 'completed':
_task_list_cache.append({
'id': task_item['id'], # Google's Task ID
'title': task_item['title'],
'notes': task_item.get('notes', '')
})
_task_id_map_cache[i + 1] = task_item['id'] # Map 1, 2, 3... to Google ID
return items
except HttpError as err:
print(f"An API error occurred while fetching tasks: {err}")
return []

When a user wants to complete a tasks, its far more intuitive for them to say “complete task 2” rather than recalling the full task description or dealing with Google’s long, unique task IDs. To facilitate this, we’ll implement a simple in-memory caching system. For each user query where tasks are listed, we’ll:

  1. Fetch the current list of non-completed tasks.
  2. Store a simplified version of those tasks.
  3. Create a mapping from user friendly numbers to their actual Google Tasks IDs.

This cache is typically refreshed each time the list_tasks() tool is used.

Now we’ll define the Python functions that our ADK agent will use as tools. Remember ADK uses function signatures and docstrings to understand what these tools do.

The first function we are going to be working on the list_tasks() function that retrieves and displays the current non-completed tasks, using our caching mechanism.

def list_tasks() -> str:
"""Lists all current, non-completed tasks from Google Tasks with a simple numeric ID for user interaction."""
global _task_list_cache, _task_id_map_cache
service = get_tasks_service()
_fetch_and_cache_tasks(service) # Refresh cache

if not _task_list_cache:
return "Your Google Tasks list is empty or all tasks are completed."

output = "Your Google To-Do List (pending tasks):\n"
for i, task in enumerate(_task_list_cache):
output += f"{i + 1}. {task['title']}\n" # User sees 1, 2, 3...
output += "\nUse the number to refer to tasks for completion."
return output.strip()

It calls the get_tasks_services() function to ensure we have an authenticated API service object, followed by calling the _fetch_and_cache_tasks(service) to refresh our local view of the tasks. It formats the tasks from _task_list_cache into a user-friendly string, assigning a simple number to each.

def add_task(description: str) -> str:
"""Adds a new task to the default Google Tasks list."""
global _task_list_cache # Invalidate cache
service = get_tasks_service()
task_body = {
'title': description,
# 'notes': 'Optional notes here' # You can extend this
}
try:
created_task = service.tasks().insert(tasklist='@default', body=task_body).execute()
_task_list_cache = None # Invalidate cache as list has changed
return f"Task '{description}' added to Google Tasks with ID {created_task['id']}."
except HttpError as err:
print(f"An API error occurred while adding task: {err}")
return f"Error adding task '{description}' to Google Tasks. Please check logs."

This function deals with adding a new task to the user’s Google Tasks List. It takes a description for the new tasks, and creates and task_body as required by the Google Tasks API. After which, making an API call to create the tasks. Crucially, it sets _task_list_cache = None and _task_id_map_cache = None to invalidate our caches, because the list of tasks has changed. The next call to list_tasks() will fetch the fresh list.

def complete_task(task_number: int) -> str:
"""
Marks a task as complete in Google Tasks given its simple numeric ID from the list_tasks command.
"""
global _task_list_cache, _task_id_map_cache
service = get_tasks_service()
if not _task_id_map_cache:
_fetch_and_cache_tasks(service)
if not _task_id_map_cache:
return "Could not find tasks to complete. Please list tasks first."

google_task_id = _task_id_map_cache.get(task_number)

if not google_task_id:
return f"Error: Task number {task_number} not found in the current list. Please use 'list_tasks' to see available task numbers."

try:
task_to_complete = service.tasks().get(tasklist='@default', task=google_task_id).execute()
task_title = task_to_complete.get('title', 'Unknown Task')

if task_to_complete.get('status') == 'completed':
return f"Task '{task_title}' (ID: {google_task_id}) is already completed."

updated_task_body = {
'id': google_task_id,
'status': 'completed'
}
service.tasks().update(tasklist='@default', task=google_task_id, body=updated_task_body).execute()
_task_list_cache = None
_task_id_map_cache = None
return f"Task '{task_title}' (ID: {google_task_id}) has been marked as completed in Google Tasks."
except HttpError as err:
if err.resp.status == 404:
return f"Error: Task with Google ID {google_task_id} (number {task_number}) not found in Google Tasks."
print(f"An API error occurred while completing task: {err}")
return f"Error completing task number {task_number}. Please check logs."
except Exception as e:
print(f"An unexpected error occurred: {e}")
return f"An unexpected error occurred while completing task number {task_number}."

This function marks a task as complete using the simple numeric ID obtained from our cached list. To mark a task as completed, the function first checks if the _task_id_map_cache exists; if not, it calls _fetch_and_cache_tasks() to populate it, ensuring robustness even if tasks weren’t listed beforehand.

It retrieves the google_task_id using the provided task_number and checks if the task is already completed. If not, it uses the Google Tasks API (service.tasks().update(...)) to update the task’s status to 'completed', then invalidates the cache to reflect the updated task list. The function returns a confirmation or an informative error message.

Before we wrap these functions into an ADK agent, it’s a very good idea to test them directly to ensure the Google Tasks API integration and authentication are working correctly.

This completes the core API interaction logic! We now have a set of python functions that can manage Google task, complete with authentication and a user-friendly caching system for task identification.

Setting up ADK

We’ve successfully built a set of python functions that can directly interact with the Google Tasks API. Now its time to bring in Google’s Agent Development Kit to transform these functions into tools that can be used. We’ll define an ADK agent, powered by a Gemini model, and provide it with clear instructions on how to assist users with their to-do lists.

System Instructions and Agent Definition

Think of an ADK agent as an AI brain that you can program with instructions and equip with tools. It can understand natural language, reason about what to do and then decide to user one of its tools to interact with the outside world.

ADK Tools: These are simply your Python functions.

  1. Name: lists_tasks, add_task, complete_task
  2. Parameters: description: str ,task_number: int
  3. DocStrings: The description text we provided under each function, is used by the LLM to understand what each tool does, what inputs it need, and when it should be used.

To guide our agent’s behaviour, we provide it with a “system instruction”, often called a system prompt. This is a set of directions that tells the agents its persona, its capabilities, how it should interact with the user, etc.

# --- Agent Definition ---
agent_todo_instruction_text = """
You are a helpful to-do list assistant that interacts with Google Tasks.
You have tools to add, list, and complete tasks.
- To add a task, use the 'add_task' tool with the task description.
- To see your tasks, use the 'list_tasks' tool. This will show pending tasks with a number.
- To complete a task, use the 'complete_task' tool with the task's number (e.g., if 'list_tasks' shows "1. Buy milk", use 1 for 'task_number').
If the user refers to a task by description for completion, first list the tasks to help them find the correct number, then ask for the number.
Always confirm actions taken.
When listing tasks, inform the user that the numbers provided are for use with the 'complete_task' tool.
If 'complete_task' is called with a description, tell the user you need the task number from the list and suggest they list tasks first.
"""

This is the agents instruction text is this core programming for your agent. It clearly defines:

  • Its persona and goal.
  • The available tools and a brief description of what they do and what parameters they expect.
  • Crucial interaction guidelines, especially the multi-step process for completing tasks if the user refers to them by description rather than number. This level of detail helps the LLM behave reliably.

Define the ADK Agent Instance

Now, let’s bring it all together and create our Agent instance. This object will bundle the LLM model, our system instruction, and the list of tool functions.

root_agent = Agent(
model=MODEL,
name="agent_todo_google_tasks",
description="A conversational agent to manage a to-do list using Google Tasks."+agent_todo_instruction_text,
tools=[list_tasks, add_task, complete_task],
)

We start off by specifiying which LLM our agent will use, followed by a name and description of the agent. The magic happens next, where we provide a list containing our tool functions. ADK will introspect these functions and make them available to the LLM.

And with that, our ADK powered Google Tasks Agent is ready and good to go. Once any initial authentication is complete(or if not needed), run the following command in your terminal.

adk web agent.py

On running the command you should see something like this, and if you click on the local testing link, it will to you to the Web UI.

On launching the Web UI, you can select from a range of agents that you have built and stored in the master folder. Select the agent you want to work with, which in our case is the Google Tasks Agent. You can now go ahead and start add, updating, listing your Google Tasks in natural language.

Here are some example interactions. The adk web UI does a great job of showing not just the agent’s final responses, but also when it decides to use a tool and what the tool’s output was.

Congratulations! You’ve successfully built your very first AI agent using Google’s Agent Development Kit (ADK), one that can intelligently manage your Google Tasks through a conversational web interface.

This project, while focused on a simple to-do list, demonstrate the fundamental principles behind building much more complex and powerful agents. The ability to connect to LLMs to external APIs and custom logic opens up a universe of possibilities.

Conclusion

In this tutorial, you’ve successfully journeyed from setting up your development environment to building a full functional AI Agent with Google’s Agent Development Kit. You learned to harness the Google Tasks API with secure OAuth 2.0 authentication, crafted python functions to act as intelligent tools, and programmed an ADK agent with clear system instructions to manage your to-do list.

By leveraging the adk web interface, you brought your agent to life, interacting with it conversationally to list, add, and complete tasks, all while gaining a foundational understanding of how to build practical, AI-powered applications that can interact with real-world services.

You can work with the code by checking out the link given below.

Feel free to reach out if you have any issues/feedback at aryanirani123@gmail.com.

--

--

Google Cloud - Community
Google Cloud - Community

Published in Google Cloud - Community

A collection of technical articles and blogs published or curated by Google Cloud Developer Advocates. The views expressed are those of the authors and don't necessarily reflect those of Google.

Aryan Irani
Aryan Irani

Written by Aryan Irani

I write and create on the internet :)

Responses (1)