Documentação
Bots
Cron and webhook automations owned by the Runtime, not the desktop.
Nesta página
Automation that outlives the desktop
A bot is a named automation with a cron schedule, an action and an agent persona. The important architectural fact: bots belong to the Runtime, not to the desktop. Cron scans and the local webhook listener run in the permanent per-user service, so closing the desktop window does not stop scheduled work — and a headless VPS install runs bots with no desktop at all. See Runtime.
A bot record is small:
| Field | Meaning |
|---|---|
name | Shown in the Bot panel and used as the webhook hook name. |
description | Free text. For a custom bot this becomes the task body. |
agent | The persona that carries the action. Defaults to the reserved Bot persona. |
cron | The schedule. |
action | The action identifier the Runtime dispatches. |
enabled | Off means neither cron nor webhook will fire it. |
Records live in ~/.tde/bots.db, alongside a run history with status,
output, cost and timings.
Triggers
Three things can start a bot:
| Trigger | Source |
|---|---|
| Cron slot | The Runtime's scan, one intent per exact cron instant. |
| Webhook request | An inbound local HTTP call, one intent per delivery id. |
| Manual request | An explicit "Run now" from a trusted Control surface. |
Cron accepts six fields — sec min hour day-of-month month day-of-week — with an
optional trailing year. A five-field expression is accepted and normalized by
prepending 0, so */30 * * * * means every thirty minutes.
The producer scans every 20 seconds, and the first scan happens after one full interval: starting the Runtime is never interpreted as a schedule tick.
The local webhook
The Runtime listens on 127.0.0.1:8765 when that port is free. Every request
must be a POST /hook/<name> with a non-empty body and both of these
headers:
Authorization: Bearer <contents of ~/.tde/bot-webhook.token>
Idempotency-Key: <stable unique delivery id>curl -X POST http://127.0.0.1:8765/hook/linear_sync \
-H "Authorization: Bearer $(cat ~/.tde/bot-webhook.token)" \
-H "Idempotency-Key: delivery-2026-07-25-0001" \
-d '{}'<name> matches an action id first, then a bot name case-insensitively.
The body itself is opaque — there is no schema; only the hook name selects the
bot.
| Status | Body | When |
|---|---|---|
202 | {"job_id": …, "enqueued": true|false} | Queued. false means this was a replay. |
400 | idempotency_key_required / invalid_idempotency_key | Header missing, empty, over 256 bytes, non-printable, or duplicated. |
400 | invalid_request / invalid_webhook / invalid_hook_name | Malformed HTTP, wrong path or method, empty body. |
401 | unauthorized | Bearer token missing or wrong. |
403 | loopback_only | The caller is not on loopback. |
404 | bot_not_found | No matching action or name. |
408 | request_timeout | Read exceeded 5 s. |
409 | bot_disabled / not_bot_host | Disabled, or this device does not hold the host lease. |
413 | request_too_large | Body over 64 KiB. |
503 | too_many_connections / host_lease_unavailable | Over 32 concurrent connections, or the lease check errored. |
If the port is unavailable the Runtime prints a bind warning and continues with cron only — the webhook listener is the only thing disabled.
Built-in bots
TDE seeds these idempotently, each gated on the matching integration actually
being configured, and each defaulting to the Bot persona:
| Name | Cron | What it does |
|---|---|---|
| Linear sync | */30 * * * * | Sync assigned Linear issues onto the board, deciding project, agent, labels and priority from your policy. |
| Todoist sync | */30 * * * * | Sync open Todoist tasks onto the board. |
| GitHub PR janitor | */30 * * * * | Archive TDE worktrees after their pull requests merge. |
| Slack triage | */15 * * * * | Read new Slack messages, decide which are real demands, and file them as tasks. |
| Stripe subscriber report | 0 9 * * * | List new subscribers from the last 24 hours. |
| Sprint rollover | 0 6 * * * | Close expired sprint windows, write the markdown summary, open the next sprint. |
| Email triage / Email Agent | */5 * * * * | Sync, classify and create human-reviewed work from email. |
Any action id the Runtime does not recognize falls through to a custom bot: it creates a task titled with the bot's name, described by its description, assigned to its agent, and runs it. That is how you schedule arbitrary agent work — write the instruction in the description and give it a cron.
Under Sync: one host, elected
With Sync active, only one device executes automatic work. Election happens at two levels.
The host flag. Exactly one machine is marked the Bot host. If the current host is missing or has stopped reporting presence, the earliest-registered online device is promoted automatically.
The per-slot lease. Each cron instant is claimed with a fenced lease. One
non-expired claim exists per bot; a second device asking for the same slot
gets Held, and an already-completed slot returns Terminal. An expired claim
can be reclaimed, with the fence generation incremented so the previous owner
cannot commit a stale result.
Solo installs skip all of this — without Sync, the local device always owns its own automation.
Failing closed mid-action
Ownership is not checked once at the start. While a long action runs, the Runtime renews its claim — or re-checks the host lease — every 10 seconds. Losing it aborts the action with one of:
Bot host ownership moved to another device
Bot host lease became unavailable: <error>
distributed Bot lease expired or moved to another device
Sync was deactivated while the Bot was runningThe lease is also renewed after the action finishes, before its result is accepted. A device that lost ownership mid-run cannot commit what it produced.
Scheduling controls and run history
The Bot panel is the durable control surface for editing an automation. From the list you can create or edit the cron expression, choose the persona, enable or disable the record and use Run now without changing its schedule. Invalid expressions are rejected before save.
Opening a bot shows its run history: trigger, scheduled time, start/end, status, output, cost and timing. Cron slots and webhook delivery ids are durable, so a Runtime restart or repeated request does not create a second logical run. Disable stops new cron and webhook dispatches; it does not erase prior history.
From a shell
The repository's own CLI reference documents the webhook contract but not the bot verbs. They exist:
tde bot list
tde bot create "Nightly deps audit" "0 3 * * *" nightly_deps_audit
tde bot rm 7list prints id, name, cron, action and whether it is on. These reach the local
bot store directly — the durable, journaled path is still the desktop
panel and the webhook.