Avagenc Rafal
2026 — An AI agent that looks after one person's Gmail, Calendar, and Contacts
01 One request, three Google products
Google keeps a person's mail, schedule, and address book in three products, Gmail, Calendar, and Contacts. One request can need more than one of them. Inviting Abraham to swim at Falajawa Beach at 5 pm means finding his address in Contacts, checking in Calendar that the hour is free, and adding the event there with him as a guest. Emailing him instead would go from Contacts to Gmail.
The risky step is the address. The person only said Abraham, and an address guessed from that name can look right and still belong to a stranger. On an event it puts that stranger on the guest list, and on an email it sends them the mail.
02 What Rafal is
Rafal is the AI agent Avagenc makes for this. It is not a program that knows Gmail. It is a language model given a written instruction about who it is and six tools. A tool is a function the program runs when the model asks for it by name.
Asked to invite Abraham to swim at Falajawa Beach at 5 pm, the model does not reply at first. It asks for get_contacts and reads Abraham's address in the result, asks for get_events to see that 5 pm is free, asks for add_event with Abraham as a guest, and only then writes its answer.
Every result goes back to the model before it picks its next move, and the work ends when it writes words instead of asking for a tool. Nothing in the code says which tools to call or in what order. The model works that out from the request, the instruction, and the way each tool describes itself, and that loop is what makes Rafal an agent.
The Go module is small. It holds the instruction, builds a Google client for each product, turns each client into tools, and hands those and the model it was given to the Agent Development Kit, a Google library that runs the loop. Rafal has no server of its own, so whichever program imports it runs it.
calendar, err := gworkspace.NewCalendar(cfg.WorkspaceClient)
gmail, err := gworkspace.NewGmail(cfg.WorkspaceClient)
contacts, err := gworkspace.NewContacts(cfg.WorkspaceClient)
calendarTools, err := adkgworkspace.CalendarTools(calendar)
gmailTools, err := adkgworkspace.GmailTools(gmail)
contactTools, err := adkgworkspace.ContactTools(contacts)
tools := make([]adktool.Tool, 0, len(calendarTools)+len(gmailTools)+len(contactTools))
tools = append(tools, calendarTools...)
tools = append(tools, gmailTools...)
tools = append(tools, contactTools...)03 Six tools
Gmail gives Rafal read_messages and send_email, Calendar gives it get_events and add_event, and Contacts gives it get_contacts and add_contact. It cannot delete or edit anything, and it cannot reply inside an email thread.
Rafal does not handle Drive, Docs, Sheets, or any other Google product. Asked for one, it says clearly that this is outside what it does and stops there, and its instruction forbids getting around that through the tools it has.
It also cannot act later. Rafal's description, the short text that tells others what an agent can do, says it cannot wake itself up later to act. It can only create calendar events, which carry Google's own reminder and not Rafal's.
Each tool carries a description of its own, which the model reads, saying when to use the tool, how to fill it in, and what comes back. get_events says to use it before creating an event to check for conflicts, and send_email says to confirm the recipient, subject, and body first when they are not already explicit.
The tools come from gworkspace, my library that keeps one Google sign-in per person, and agentkit, my library that turns it into tools an agent on the Agent Development Kit can call. Each tool takes whose account to use from the person Rafal is working for, so the model never sees a Google credential and cannot reach anyone else's mailbox.
get_contacts has no search. It returns the contacts up to a limit, and the model finds the name in the list itself.
getContacts, err := functiontool.New(
functiontool.Config{
Name: "get_contacts",
Description: `WHEN TO USE:
- I need to find someone's email or phone number in the human's contacts
- The human names a person but doesn't give their email explicitly
- Before add_event with guests, to confirm the correct guest emails
HOW TO USE:
- limit: maximum contacts to return. Defaults to all contacts (Google's limit applies).
Set a small limit when only a few are needed, to avoid a large response.
WHAT I GET BACK:
- A list of contacts with resource_name, name, emails, phones.
Use an email from here to fill in event guests or an email recipient.`,
},
func(toolCtx adktool.Context, in getContactsArgs) (contactsOutput, error) {
q := gworkspace.ContactQuery{
Limit: in.Limit,
}
contacts, err := c.GetContacts(toolCtx, toolCtx.UserID(), q)
if err != nil {
if errors.Is(err, gworkspace.ErrNotConnected) {
return contactsOutput{}, errors.New("the human hasn't connected their Google account yet — direct them to the OAuth flow before accessing Contacts")
}
return contactsOutput{}, err
}
views := make([]contactView, len(contacts))
for i, ct := range contacts {
views[i] = toContactView(ct)
}
return contactsOutput{Contacts: views}, nil
},
)04 Contacts first
Having get_contacts does not make the model use it, so the instruction says when it must. The instruction is written in Indonesian, and its one rule across tools, marked mandatory, is about people. Translated, it says "Before inviting someone to an event or sending an email to someone mentioned by NAME, not an explicit email address, I MUST call get_contacts first to make sure of the right email address. Never guess an email address from a name, and never treat a name as an email address."
A model asked to invite Abraham could otherwise put abraham@gmail.com on the guest list, an address that looks right and may belong to someone else entirely. With the rule, every address comes from the person's own contacts. When Ishmael has two of them, nothing in the request says which, and send_email's description says to confirm the recipient, so Rafal asks before it sends.
This is why the instruction calls Gmail, Calendar, and Contacts one connected surface and not three products. Most requests that name a person go through Contacts first, even when the person only asked about mail or a meeting.
05 Fresh data from Google
Guessing from a name is one way to be wrong, and answering from memory is another. An earlier message may already list the unread mail or tomorrow's meetings, and the instruction forbids answering from it. Translated, it says "I must not assume what is in the inbox, the schedule, or the contact list from the conversation history. That data can change at any time outside this conversation." Every request to read or change them has to become a tool call.
The same part covers failure. When a tool reports that the person has not connected Google yet, Rafal tells them as it is and does not call the same tool again and again hoping for a different result. The tool already words that error for the model, telling it to send the human through the sign-in first.
Connecting is not Rafal's job. The program that uses Rafal links the account through Google's consent screen, the page where a person agrees to what an app may reach, and Rafal only uses what was granted.
06 From my own loop to the Agent Development Kit
Rafal did not start on the Agent Development Kit. It began in May 2026 as a Calendar agent on AWS Lambda, an Amazon service that runs a function on demand. It had Calendar and Contacts tools and ran its own loop, sending the conversation to Gemini, running whatever tools came back, and giving up after ten round trips.
On 1 July 2026 I rebuilt it on the Agent Development Kit, which runs that loop, added Gmail, and turned it from a service into a Go module, so the program that imports it decides how it runs and where its conversation lives.
To try it alone, cmd/connect goes through Google's consent screen once and prints a refresh token, a credential that keeps the sign-in alive, and cmd/cli and cmd/web chat with Rafal in a terminal or in the Agent Development Kit's web page on that one account. Neither asks who is calling, so they stay on a laptop.
const maxToolIterations = 10
for i := 0; i < maxToolIterations; i++ {
iterCtx, cancel := context.WithTimeout(ctx, iterationTimeout)
resp, err := client.Models.GenerateContent(iterCtx, cfg.Model, contents, genConfig)
cancel()
// ...
candidate := resp.Candidates[0]
// ...
functionCalls := extractFunctionCalls(candidate.Content.Parts)
if len(functionCalls) == 0 {
finalText := extractText(candidate.Content.Parts)
// ...
return &Result{
Output: finalText,
UsageTool: usedTools,
}, nil
}
contents = append(contents, candidate.Content)
var functionResponseParts []*genai.Part
for _, fc := range functionCalls {
usedTools = append(usedTools, fc.Name)
result, err := executor.Execute(ctx, fc.Name, fc.Args)
// ...
}
contents = append(contents, &genai.Content{
Role: "user",
Parts: functionResponseParts,
})
}
return nil, fmt.Errorf("agent: exceeded maximum tool iterations (%d)", maxToolIterations)07 Use in Avagenc
Avagenc Chat is the only product importing the module today. It passes specialist.Instruction() through AdditionalInstruction, a field the module appends to its own instruction, and that text says Rafal sits in a group of one human and the Avagenc agents and goes straight to the result without repeating the request. Inside that product, Avagenc Ava can hand Rafal work as one of her specialists.
08 Why it waits
Before Avagenc Chat can open Rafal to the public, Google has to review it. Google lets an individual publish an app like this, but how hard the review is depends on what the app asks for. Calendar, Contacts, and sending mail are sensitive, which needs a free review. gmail.modify, the permission that lets Rafal read mail, is restricted, which adds a paid security assessment every year, and until the app is verified the sign-in expires after seven days.
The cheapest way out is to give up reading mail and keep only sending, which would leave Rafal with sensitive permissions alone. Keeping it needs a registered business and a budget for the assessment every year, so Rafal waits with the rest of Avagenc Chat.
Avagenc Rafal
Looks a name up in Contacts before it adds a guest or emails anyone, and asks when that name has two addresses