chat() answers a person and run() answers your code. .on() answers
neither: it declares work that should happen without a caller at all.
lib/agent.ts
.on() is a declaration —
collected when the module loads, reconciled once when your process boots.
Every shape
{ event } is your own product event — the one you vendo.emit(...).
{ webhook } is a signed delivery from a connected service, which lands on your
deployment’s own webhook door.
The task is always a goal here: words, thought through by this agent. Step
pipelines come from the chat and manifest doors, which know the tools.
Options
id— stable identity. Leave it out and the identity is a hash of the schedule and the task, which means editing either one mints a new automation and the old one is disarmed at the next reconcile. Set anidwhen you want to edit the words in place and keep the run history.timezone— the zonecronandeveryare evaluated in. Unset is UTC.budget— a ceiling on one firing,{ maxToolCalls }. Unset is 50.
It fails at the declaration, not at 2am
Validation is synchronous, at the line you wrote it on, before your process serves a single request.every outside <n><s|m|h|d>, an at that is not an
instant, an unnamed event or webhook — each one stops the boot, with the nearest
valid form to paste. An automation you cannot see failing is worse than a deploy
that will not start.
Arm them
A declaration is inert until a lifecycle reconciles it. There are two, and they run the same reconcile. In a standalone backend,serve() is the lifecycle. It takes the agents whose
declarations this process runs and hands back the handle that stops the
scheduler:
close() stops the scheduler and nothing else: the records stay in the store,
armed, because stopping a process is not a decision about what should fire.
Inside a Vendo deployment, createVendo’s own boot is the lifecycle, and
registering the agent is what arms it. An automation record stores the agent’s
name, never the agent, and your code is looked up under that name when it
fires:
app/api/vendo/[...vendo]/route.ts
vendo.agent — the embed’s own composed agent — is registered for you, and
.on() works on it too. Two agents claiming one name throw at startup. A record
naming an agent nobody registered writes a failed run that names the missing
name; there is no fallback brain, because running the wrong agent under this
record’s grants is worse than not running.
What a deploy does
Every boot reconciles what your source declares against what is stored for code-authored automations only. Chat-authored ones are untouched — a user’s automation is not yours to reconcile.1
New declaration
Created, and armed.
2
Edited declaration
A new identity is created and armed; the one it replaced is disarmed. It is
disarmed rather than deleted, so its run history survives.
3
Declaration deleted from your source
Disarmed. Consent was the code, and the code no longer says it.
4
Anything a person switched off
Left alone, forever.
automations.disable(id, ctx) outranks your source, and
no redeploy will re-arm it.Authority
A goal runs with the owner’s grants, and nobody is present to be asked. So the permissions are settled once, when the automation is turned on:runs.rerun(runId) is the second
half of that.
Where to go next
Automations
The whole model: records, the two authors, and what wakes them.Automations →
Run — run()
One unattended run, right now, with a typed answer.run() →