Get things done with a Todoist agent

We all dream to have our own AI personal assistant someday. While the perfect AI assistant might still be far away, we can build simple AI agents that work with Todoist today.

Scenario

Todoist Flowchart

For this demo, we will show how to build an AI agent that can get tasks from Todoist, complete them, and close them in Todoist.

In my Todoist, I have a simple demo task, "Email email@example.com the top 3 HN posts".

Our AI agent will:

  • Get the task from Todoist
  • Get the top 3 HN posts
  • Send them to the recipient via Gmail
  • Mark the task as done in Todoist

To do this, our AI agent is equipped with tools to:

For a simpler demo for Todoist, check out Getting tasks from Todoist.

Setup

To get started, we first set the following environment variables in a .env file:

  • TODOIST_API_TOKEN: The API key of your Todoist account (see below)
  • GMAIL_ADDRESS: The sender's email
  • GMAIL_PASSWORD: The sender's app password, not account password
  • <COMPANY>_API_KEY: The API key of the model you want to use
  • The Hacker News tools do not require an API key.

Todoist setup

To get your Todoist API key:

  1. Click on your profile icon in the top-right corner
  2. Select "Settings"
  3. Click on "Integrations"
  4. Click on "Developer"
  5. Copy the API token and save it as an environment variable

Scripts

Some APIs and frameworks (e.g. Gemini, LangGraph, and LlamaIndex agent) automatically execute tool calls, which make the code much simpler. For the rest, we will need to add a while loop so that the agent will keep working on the next step until the task is completed.

import os
import anthropic
from dotenv import load_dotenv
import stores

# Load environment variables
load_dotenv()

# Load tools and set the required environment variables
index = stores.Index(
    ["silanthro/todoist", "silanthro/hackernews", "silanthro/send-gmail"],
    env_var={
        "silanthro/todoist": {
            "TODOIST_API_TOKEN": os.environ["TODOIST_API_TOKEN"],
        },
        "silanthro/send-gmail": {
            "GMAIL_ADDRESS": os.environ["GMAIL_ADDRESS"],
            "GMAIL_PASSWORD": os.environ["GMAIL_PASSWORD"],
        },
    },
)

# Initialize Anthropic client and messages
client = anthropic.Anthropic()
messages = [
    {
        "role": "user",
        "content": "What tasks are due today? Use your tools to complete them for me. Don't ask questions.",
    }
]

# Run agent loop
while True:
    # Get the response from the model
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        messages=messages,
        # Pass tools
        tools=index.format_tools("anthropic"),
    )

    # Check if all blocks contain only text, which indicates task completion for this example
    blocks = response.content
    if all(block.type == "text" for block in blocks):
        print(f"Assistant response: {blocks[0].text}\n")
        break  # End the agent loop

    # Otherwise, process the response, which could include both text and tool use
    for block in blocks:
        if block.type == "text" and block.text:
            print(f"Assistant response: {block.text}\n")
            # Append the assistant's response as context
            messages.append({"role": "assistant", "content": block.text})
        elif block.type == "tool_use":
            name = block.name
            args = block.input

            # Execute tool call
            print(f"Executing tool call: {name}({args})\n")
            output = index.execute(name, args)
            print(f"Tool output: {output}\n")

            # Append the assistant's tool call as context
            messages.append(
                {
                    "role": "assistant",
                    "content": [
                        {
                            "type": "tool_use",
                            "id": block.id,
                            "name": block.name,
                            "input": block.input,
                        }
                    ],
                }
            )

            # Append the tool call result as context
            messages.append(
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": str(output),
                        }
                    ],
                }
            )

In the folder where you have this script, you can run the AI agent with the command:

python complete-task.py

The AI agent will get the task, complete it, and mark it as done in Todoist.

Hacker New posts in email

If you have any questions, let us know on GitHub.