Skip to main content
A chat is something you start. Everything on this page is the other direction: the agent starting something, because a message came in, or because a condition you named turned true. What the agent builds for that is a program — a loop that lives on your machine, in the same repository as everything else about your agent, running as long as the machine does. It is not a webhook you register, and there is no integrations page to fill in.

What the agent did about it

  • Found the address. Your agent has an email address of its own, from the platform’s email package — it did not need one from me. It printed it: [email protected].
  • Read its own repository firstPROGRAMS.md, AGENTS.md, and the two programs already there — before writing anything.
  • Wrote a program: a poll loop, a Butler agent for composing the reply, a prompt file, a state file and a README.
  • Started it with the ordinary command anybody would use for a background script, so it outlives the turn:
  • Changed its own design mid-run. Its first version had the agent send the mail itself; it decided the loop should send it instead, in its own words, “that way the ‘reply only to the sender’ rule is enforced in code, not by prompt obedience,” and restructured.
Trimmed: the logging helpers, the state file reader and writer, and the message hashing are cut for length. The whole file is on the machine at ~/harness/programs/mailbutler/main.py.

What it needs from you

Exactly one thing, and which one depends on how the outside world reaches in.
The Environment secrets card on the Splox Connections screen, holding TELEGRAM_BOT_TOKEN

Connections → Secrets: a token the agent's own code reads from the environment. The value is never shown again.

Put the token there rather than in the chat. Secrets are injected as environment variables into every run on the machine, so the program reads os.environ["TELEGRAM_BOT_TOKEN"] and nothing writes a token into a file in git.
There is one trap here, and it is the account’s own history: version 4 of this harness was Add telegram bot program, and version 5 was telegram: read bot token inside call(), not at import time.The bot read os.environ["TELEGRAM_BOT_TOKEN"] at the top of the file. The platform imports that file every time it needs to know who an agent is — from processes that do not carry your secrets — so every such question died with KeyError: 'TELEGRAM_BOT_TOKEN' before the agent was ever named. Reading the token inside the function that uses it fixes it. If a bot you asked for works when you test it and then fails oddly, this is the first thing to ask about.
The webhook case is worth being blunt about: your machine is not addressable from the internet, so “let Stripe call you” is not something the agent can arrange on its own. What it can do is poll — an API, a mailbox, a repository — on a timer, which is On a schedule.

What running forever means

A program started in a chat belongs to the machine, not to the run and not to the conversation. It keeps going after the turn ends, after the chat ends, and into tomorrow. It stops in exactly two ways from outside: somebody presses Stop on the machine, or the plan behind the account stops keeping machines running. Nothing reaps it on a schedule, and no conversation ending takes it down. On a paid plan the machine’s live state carries no idle timeout at all; on a free plan it stops itself after 30 minutes with nothing touching it, and the program stops with it. See Machine. It can also stop on its own, by crashing. That is what the loop above is defending against with its try around the poll — an exception writes a line to the log and the loop sleeps and tries again, instead of the watcher quietly disappearing. So the sentence worth adding to the ask is: “make restarting it harmless.” The agent that wrote the mail butler did it unasked — a state file with a watermark and the keys of everything already answered, so a restart answers nothing twice, and a lockfile so starting it a second time exits instead of double-replying. Starting it again while it was already up printed exactly that and stopped:
Ask for it if the agent does not offer it, because the alternative is that a machine restart mails your correspondents twice.
Starting a program is not publishing it. The code goes into the harness and becomes a version; the running process is on this machine only. If you move to another machine, or restart this one, somebody has to start it again — which is one sentence in a chat, and a reasonable thing to ask the agent to check for at the top of a conversation.

Checking it

Three checks, cheapest first. Ask. “Is the mail watcher running?” The agent looks and tells you. It is one ps on its own machine. Read the log. The program the agent wrote logs both sides of every exchange to /home/daytona/mailbutler.log, and it says so in its own README:
That second line is the log doing its job. The poll had failed with a 524 from the platform’s own tool endpoint; the loop caught it, wrote the line, and slept instead of dying. A watcher without a log is a watcher you cannot answer any question about, so “keep a log I can read” is worth putting in the original ask. Use it from outside. Mail the address; message the bot. This is the only check that tests the whole path, and it is the one to do before you rely on it.
The agent’s runs are visible too. Every time the loop hands a message to its agent, that is a real run: it shows up in your chat list with its whole conversation, and it is billed like any other. The program’s log says what came in; the run says what the agent thought about it.

Stopping it, and undoing it

Two separate things. Going back to before the change is the same as any other rollback — a version carries the program, so undoing the version undoes the program. Versions is that page.

On a schedule

The same shape, triggered by the calendar instead of by a message.

A Telegram bot, end to end

The whole conversation, including the token trap as it actually happened.