Agents That Push, Not Chat
Every weekday at nine, a Slack message lands in our users' channels: yesterday's top-performing ad creatives, with thumbnails, a strip of metrics, and a deep link into the dashboard. Nobody typed a prompt. Nobody opened a chat. It is the agent feature users mention most, and it has no chat box.
Product details anonymized. Real engineering patterns.
I think that is where agents are heading, and most teams are still over-investing in the conversation.
The chat box has a cold-start problem
Chat puts the burden of initiative on the user. They have to remember the agent exists, open it, and formulate a question. That works for novel, exploratory asks. It fails completely for the information people need on a rhythm: how did campaigns do yesterday, what changed this week, which creative is quietly dying.
Rhythmic information should not depend on someone remembering to ask. The assistant that actually feels like an assistant is the one that shows up. Push inverts the relationship: the agent comes to you, on your schedule, in the tool you already have open.
A routine is a product surface, not a cron job
Our first implementation was the obvious one: a dedicated cron job and a settings object. It got killed in review, and the reviewer was right.
We rebuilt it as an agent task: a scheduled agent run, visible in the product next to the user's other routines, with a "Run now" button and instructions the user can edit in plain language. The difference is not cosmetic. A cron job is invisible infrastructure that only developers can change. A routine is something users own: they can see it, pause it, reschedule it, or tell it "include the weekend in Monday's digest" without filing a ticket.
It also changed our economics. Because the routine runs through the same scheduler and agent-session infrastructure as everything else, the next scheduled notification is a new tool, not a new system.
Decide what the agent must not do
Here is the design problem nobody warns you about: a push message has no human in the loop at read time. In chat, a wrong number gets challenged in the next message. In a Slack channel the CFO reads over coffee, a hallucinated spend figure does not get challenged. It gets screenshotted, and your feature dies.
So the architecture splits responsibilities sharply:
The agent never relays a metric. The posting tool re-fetches everything server-side at post time, and renders the message with the same logic that renders the dashboard cards. We call it the "Slack equals dashboard" guarantee: any number in the message is, by construction, the number the user would see in the product.
The agent's creative freedom is scoped to one place: a short intro sentence, fed by a separate read-only data tool. It can say "rough day for video creatives"; it physically cannot invent the numbers below. Same lesson as my MCP article, pushed further: a tool is an interface for a reasoning engine, and sometimes the interface's job is to take the pen away.
One more boundary worth stealing: the Slack channel is injected by the system from workspace configuration. It is not a tool parameter. An agent that cannot choose its destination is an agent that no prompt injection can redirect.
Push surfaces are unforgiving
Chat failures are conversational: the user sees the error and retries. Push failures are silent, or worse, duplicated. Two rules followed from that:
Retry only before anything landed. A scheduled run that crashes mid-post must not blindly retry, or the channel gets the digest twice. The posting flow is safe to retry only up to the first delivered message; after that, failures log and stop.
No digest beats a wrong digest. If the ad platform is disconnected, or data never finished syncing, the run skips and logs. It does not post a half-empty message at 9 a.m. and let users guess whether the product is broken or their campaigns are.
The 9 a.m. digest that arrived at 11
The war story. A user configures "daily at 9:00 AM" and gets the message at 11:00. Root cause: the cron expression was parsed in UTC, end of story. The timezone label in the UI was cosmetic; it was never persisted, never applied. For a UTC+2 user, 9:00 became 11:00.
The fix was unglamorous: store an IANA timezone on every task, parse the cron with it, capture the browser's timezone when the user saves the schedule. The interesting part was the deliberate scope cut: the report day ("yesterday") stays UTC, because it must match the dashboard's date convention. Firing time is local; the data window is not. Moving one without the other would silently break the guarantee that the message matches the product.
Scheduled agents inherit every boring bug cron systems ever had, plus some new ones. Your users will discover them at two hours past nine.
The chat box becomes the remote control
None of this makes chat obsolete. It demotes it. Chat is where routines get created and edited ("send me this every morning, sorted by spend"), and it is excellent at that. Delivery happens elsewhere, on schedule, with the numbers guaranteed.
If you are building agent features, here is the exercise: find the dashboard your users check every morning, and deliver it to them instead. Then spend your design effort where it matters, on everything the agent is not allowed to do.
Working on a similar AI project? Let's talk about it.