- contact@verticalserve.com

It was Thursday at 5:50 PM. My calendar pinged me to send the daily digest. I opened JIRA, started clicking through tickets, realized I'd forgotten to log out two stale ones from yesterday, copied the keys into Outlook, manually colored the headers, and shipped it at 6:18 PM after most of the engineering team had already gone home. The Friday version: I just didn't send it. Monday's standup, I winged it from memory.
If you've run sprints, you've lived this. The digest is the kind of work everyone agrees is valuable and no one can justify spending an hour on. So either you do it badly, or you do it inconsistently, or you stop. I'd done all three.
I'm not building a chatbot that answers what's the status of ticket X. I'm building an automation that takes the consistent, data-driven, time-bound parts of project management — the parts I was demonstrably bad at by hand — and runs them on a clock. Same JQL, same classification rules, same delivery channels, same time every day. The agent doesn't have to be smart. It has to be relentless.
I started with the daily JIRA sprint digest because it was the worst-offending thing on my plate. Once that worked, I added five more. This post is the first one.
InsightWorker has a built-in scheduler. The whole digest is described in two files in my project's .insightworker/ folder.
The schedule (when, how, who):
And an app config that defines the actual project assumptions — none of it is hardcoded into the agent's logic, so when our team renames a board or changes recipients, I edit one file:
The corresponding app prompt is the actual brain — the agent reads the config, fetches tickets via the jira_search tool, classifies them, composes the HTML, and sends it. Every step is something the agent does in its main loop with the tools we ship.
The split is borrowed from how my engineering manager actually thinks about the sprint, not from JIRA's status field. Four buckets:
Done AND updated within the last 24 hoursIn Progressblocked OR status BlockedThe classification rules live in the prompt as plain English. The agent applies them deterministically because the JQL it generates is deterministic. This is the part where I had to stop being clever — there's no need for the LLM to reason about the right bucket. I just need it to apply my rules consistently. The fact that an LLM is in the loop is incidental.
Each 6 PM run produces three things:
#eng-sprint — same content, threaded, so engineers in different timezones can scroll back~/sprint-digests/YYYY-MM-DD.html — for the audit trail and for whoever joins mid-quarterIf JIRA is unreachable or returns 0 active sprint tickets, the app logs the error and refuses to send a misleading empty digest. Better to send nothing than to send something wrong; I learned that the hard way in week one.
The thing I'm most proud of is the stale-ticket nudge. Every weekday at 6 PM the app checks In Progress tickets that haven't been touched in 72 hours, resolves the assignee in Slack, and sends a 1:1 DM with the message template from the config:
Polite, low-shame, every day. The team gets called out by an algorithm at a predictable time, not their PM in a 1:1. Two nice consequences:
InsightWorker's permission gate sits in front of send_email by default. For the digest, I set AUTO_APPROVE_TOOLS=true because the prompt is locked down — recipients are config-pinned, content is templated, no surface area for the agent to send something to the wrong list. For ad-hoc sends (e.g. when I ask the agent to email an exec a custom report), the permission prompt comes back. I want the safety belt on for those, off for the routine.
And before I trusted the digest with the real distribution, I ran it for a week with --dry-run. The agent rendered the email locally, opened it in my browser, and didn't send anything. I read each one and edited the app until I trusted it. --dry-run is the single most underrated feature for getting comfortable with an automation.
Three things broke in the first week of running it for real:
Done tickets that had been re-opened. I refined the JQL to require the most recent status transition to Done happen within the 24-hour window, not just the updated-at field.no active sprint detected early-exit so it doesn't email noise.Each fix was a 5-line edit to the prompt or the JQL. Total fix time across the three issues: about 30 minutes. The system was usable on day 1; it became reliable by day 7.
Three things stay with me:
If you're worried an agent will replace your judgement, don't be. The judgement is the part that's hard. The cadence is the part that's monotonous. The agent does the cadence.
After the digest stabilized, I bolted on four more apps that share the same scheduler:
All four run on the same scheduler with their own YAML configs. None required new code from me — just new prompts and new schedules.
I haven't tried these yet but the pattern fits and I'll write about them when I do:
Pick the one PM app you do worst by hand. Mine was the daily digest; yours might be retros, OKR check-ins, or status updates to leadership. Set aside two days, write the config in YAML, write the app prompt in plain English, and run it with --dry-run for a week before you let it touch real recipients.
After the first cadence is reliable, the second one takes a quarter of the time. The third takes none — you copy the schedule entry, swap the prompt, and you're done. The first one is the one that teaches you the shape of the tool. Pick it carefully and accept that the first week is going to be embarrassing.
I put together the use-case page with the full app walkthrough and screenshots if you want to see what each step looks like.