ComposioComposio 0.21 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
8 small wins to finish your path

GitHub and Gmail assistant with tested modifiers

Put it together: an agent that may use two exact tools for one user, with modifiers that label its GitHub issues and strip email bodies, and tests for those modifiers.

Examplemodifiers.py
from composio import after_execute, before_execute


@before_execute(tools=["GITHUB_CREATE_AN_ISSUE"])
def label_agent_issues(tool, toolkit, params):
    params["arguments"].setdefault("labels", []).append("created-by-agent")
    return params


@after_execute(toolkits=["gmail"])
def keep_only_subjects(tool, toolkit, response):
    messages = response["data"].get("messages", [])
    response["data"] = {"subjects": [m.get("subject") for m in messages]}
    return response
Exampleagent.py, runs with COMPOSIO_API_KEY and OPENAI_API_KEY
import json

from composio import Composio
from openai import OpenAI

from modifiers import keep_only_subjects, label_agent_issues

composio = Composio(allow_tracking=False)  # reads COMPOSIO_API_KEY
client = OpenAI()                          # reads OPENAI_API_KEY
TOOLS = ["GITHUB_CREATE_AN_ISSUE", "GMAIL_FETCH_EMAILS"]
MODIFIERS = [label_agent_issues, keep_only_subjects]


def run(user_id: str, task: str) -> str:
    tools = composio.tools.get(user_id, tools=TOOLS)
    messages = [{"role": "user", "content": task}]
    for _ in range(5):
        response = client.chat.completions.create(model="gpt-4.1-mini", messages=messages, tools=tools)
        reply = response.choices[0].message
        if not reply.tool_calls:
            return reply.content
        messages.append(reply)
        results = composio.provider.handle_tool_calls(user_id=user_id, response=response, modifiers=MODIFIERS)
        for call, result in zip(reply.tool_calls, results):
            messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    return "Stopped after 5 steps."
  • The model sees exactly two tools, fetched for the user who asked.
  • Every call goes through handle_tool_calls with the modifiers, so an agent issue is always labelled and email bodies never reach the model.
  • Results are sent back in the order of the tool calls; the loop stops after 5 steps.
  • The user must have connected GitHub and Gmail first, as in lesson 2.
Two tools, two modifiers
callresultagent.pyat most 5 stepslabel_agent_issuesbefore GITHUB_CREATE_AN_ISSUEComposiowith the user's connectionkeep_only_subjectsafter any gmail tool
Hover or tap a piece to see what it is and which lesson built it.
Follow a tool call

Running agent.py acts on real accounts, so it is not run here. The modifiers are what your customer is trusting, and they are plain functions:

Exampletest_modifiers.py
from modifiers import keep_only_subjects, label_agent_issues


def test_agent_issues_are_labelled():
    params = {"arguments": {"title": "Refund failed for A-1002"}}
    out = label_agent_issues.apply("github", "GITHUB_CREATE_AN_ISSUE", params, "before_execute")
    assert out["arguments"]["labels"] == ["created-by-agent"]


def test_other_tools_are_untouched():
    params = {"arguments": {"text": "hello"}}
    out = label_agent_issues.apply("slack", "SLACK_SEND_MESSAGE", params, "before_execute")
    assert out == {"arguments": {"text": "hello"}}


def test_gmail_bodies_never_reach_the_model():
    response = {"successful": True, "error": None, "data": {"messages": [{"subject": "Invoice", "body": "card 4111 1111 1111 1111"}]}}
    out = keep_only_subjects.apply("gmail", "GMAIL_FETCH_EMAILS", response, "after_execute")
    assert out["data"] == {"subjects": ["Invoice"]}
    assert "4111" not in str(out)
Example
pytest -q -p no:warnings

The tests call apply exactly as the SDK does, with no key and no network: agent issues are always labelled, other tools are untouched, and a card number in an email body never reaches the model.

Beyond tools and modifiers

TopicWhat it is for
TriggersEvents from connected apps, like a new email, over a WebSocket or webhooks.
MCPServing a session's tools to an MCP client with create(..., mcp=True).
Your own OAuth appsCustom auth configs with your branding and scopes.
The sandboxProcessing large tool results in a session-scoped sandbox.
Other providersAnthropic, LangChain, CrewAI, OpenAI Agents SDK and more.
TypeScript SDKThe same concepts for TypeScript apps.

You understood something today that you didn't yesterday.