- Files:
/work, mounted in memory for the agent’s scratch files. To keep them, mount a disk folder, an S3 bucket or another VFS there instead. - Python:
python3, in-process on Monty. - CLIs:
himalayareads and sends mail,gwscalls Google Calendar andslackposts to Slack, with credentials the server holds, so the agent never sees one.
Installation
himalaya needs the email extra in Python and three mail libraries in
Node:
- FastAPI
- Express
Server
This is the server from the Overview with one addition: it registershimalaya, gws and slack, each with one account.
To run this page you need three accounts: a mailbox the agent reads and sends
from over IMAP and SMTP, a Google account with Calendar access that can see
both interviewers’ free and busy times, and a Slack bot that is a member of the
hiring channel. Email, Google and
Slack show how to get each credential. The server reads
them from environment variables, and
Service Credentials shows how to keep them in a
secret store instead:
EMAIL_SENT_FOLDER names the folder the account keeps sent mail in: Sent
on most servers, [Gmail]/Sent Mail on Gmail and Sent Items on Exchange.
himalaya saves each sent message there. The ports, the TLS switch and the
two API addresses default to the services’ own. Set them only for a
self-hosted mail server or a proxy. A terminal has its own environment, not
the server’s, so the agent uses the three accounts without seeing their
credentials.
- FastAPI
- Express
app.py
Usage
Create a terminal, and definerun, which sends commands to it and prints
what they print. It needs jq:
SH and SH in the blocks below run in the terminal
on the server, not in the shell that calls run.
Read the inbox
The agent counts the messages in the inbox, then searches for the candidates’ replies by subject, skipping any it has already answered. It saves each reply to a file under/work/replies, named by its message ID, so later steps read
the files instead of the mailbox:
--page-size 0 returns every match
instead of a page of 25, up to the newest 200 messages, the account’s
max_messages.
Find the offered times
The recruiting team asked each candidate for two times, one per line, written as2026-11-16 10:00. A short Python script takes each candidate’s name and
address from the sender in the search results, using the address as the name
when there is none, and the times from the reply’s body. It saves them to
/work/candidates.json and prints a table:
Check the calendars
Google Calendar’s free/busy query returns the times each person is busy, without the details of their meetings. The agent asks for both interviewers for the whole week and saves the answer to a file. Monty has no network, so the Python script reads that file. For each candidate, in the order they replied, it picks the first offered hour when neither interviewer is busy. A booked hour counts as busy for the candidates after it:2026-11-16T10:00:00Z. The script
keeps their first 19 characters, the date and the time to the second, so it
can compare them with the candidates’ times.
Maya and Lucas both offered Monday at 10:00. Maya replied first and gets it,
so Lucas gets his second time. Each of Priya’s times clashes with at least
one interviewer, so the agent leaves her for a person.
Book the interviews
jq turns each booked row of the plan into a Calendar event, one per line,
with the candidate and both interviewers as guests. The agent creates the
events on the recruiting calendar, then lists that calendar to check them:
Confirm and tell the team
The agent replies to each booked candidate in their own thread and, once the reply is sent, flags their message as answered. It then checks the sent folder for the five replies.mailbox list gives that folder the role
sent. It writes the schedule to a file, posts it to the hiring channel,
#hiring-backend, by its Slack channel ID, and reads the post back:
#hiring-backend.
himalaya message reply without --send only prints the reply, which is a
safe way for an agent to show a draft before it sends.
The answered flag is kept in the mailbox, so the next morning’s run skips
the five booked candidates and plans only new replies and Priya’s. A reply
that fails to send prints its error and leaves the message unflagged, so the
next run tries that candidate again.
The round ends with what the agent can report:
- Five interviews are booked, each on the recruiting calendar with the candidate, Dana and Omar as guests, and each candidate has a confirmation in their thread.
- Lucas Ortega’s first time was taken by Maya Chen, so he has his second, Monday at 15:00.
- Priya Raman cannot be scheduled: Dana or Omar is busy at each of her times. A person needs to ask her for more times or move a meeting.
#hiring-backendhas the full schedule.