Every scheduler in your stack can prove a job ran. Almost none can prove anyone will ever see what it produced. Cron's default consumer is local mail, which on a modern machine means nothing. Most app job runners write a results row that no view queries. And now that every agent framework ships a cron layer, the failure has a new shape: an autonomous run that completes, spends tokens, and lands nowhere a human looks. The health checks stay green. The person who created the job concludes it is broken, because from where they sit, it is.
One checkbox turned a headless cron result into a tab a person opens
I hit this exact failure in Vodou, the local-first personal AI system I build. Vodou has a scheduler: jobs on your own time zone, a heartbeat that briefs you from live state, and automations that watch a tool and run an action chain when something new shows up. A user can create a scheduled query in plain language, something like "every weekday at 7, summarize what changed on the project," and the scheduler runs it against the full brain, with memory and tools.
The capability that shipped is small and it is a routing decision, not a feature. When you create a query task on a cron schedule, the create endpoint now accepts a surface flag, on by default, exposed in the form as a "Show as dock tab" checkbox. With it set, the task is not inserted as a bare scheduler row. It is created through vc_skills_create, the same path the built-in automated skills use, which buys the whole existing pipeline for free: a tab in the first dock group, delivery of every run's result into that tab, and a properly computed next run time. The task a person meant to read becomes a thing with a place to be read. The behavior, and how to turn it off, is documented in docs/vodou-scheduler.md, with the polling automations covered in docs/vodou-automations.md.
Where a scheduled query's answer goes now
The scheduler logged success while its user filed the feature as dead
Here is the unflattering part. The first design was already shipped and already wrong. A plain query scheduled task did a bare insert, ran the brain headlessly on schedule, and discarded its output. The scheduler logged success on every run. From the log's point of view the feature worked. From the user's point of view it did nothing at all, and there was no way to tell the difference between "ran and discarded" and "never ran." The built-in automated skills, which had dock tabs, looked alive. The user-created tasks, on the same scheduler, looked dead. Same engine, same cron loop, opposite verdicts, and the only difference was whether the output had a destination.
My first instinct for the fix was worse than the bug: build a bridge from the scheduler's task table to the dock, a second delivery path for this one task type. The note that killed it is still in my memory system as a pattern: a scheduled task that appears in the dock and shows results is an automated skill, so reuse the existing create path, do not build parallel bridging. The final change (commit cf14e52) is one flag and one reroute.
The interesting wrinkle came from the opposite direction the same week. Saved graph runs got the inverse default: they do not auto-create a tab, and their "give this its own tab" checkbox defaults to off. That asymmetry is the whole lesson. A scheduled query exists only to be read by a person, so surfacing defaults on. A graph run is usually a step inside something else, with a downstream consumer already, so surfacing defaults off. The default follows the consumer, not the producer.
An enabled producer must join to a declared consumer, and a log line is not one
The transferable failure class is not "scheduled jobs are flaky." It is a checkable property of a codebase: every enabled row in a scheduler's task table must be joinable to a declared consumer of its output, where a consumer is a surface, a delivery target, a subscriber, or another job that reads the result. A success log is not a consumer, because nothing downstream depends on it. If that join can return null for an enabled job, your system contains jobs that can die invisibly, and worse, jobs that can run forever while their creator believes they are dead. Either the schema has the column and some rows are null, or the schema cannot express the question, and that second case is the more common finding.
Two queries that find your own jobs succeeding to nowhere
You can test this on your own stack in five minutes, with nothing from mine. For plain cron:
crontab -l | grep -vE '^\s*(#|$)' | grep -vE '>>?\s*\S'
Every line printed is a job whose stdout goes to local mail, which nobody reads. Passing output is empty. Then check the other trap, jobs explicitly discarded:
crontab -l | grep -vE '^\s*(#|$)' | grep '/dev/null'
Each hit deserves the question: if this job stopped producing, what would notice?
For an application scheduler, run the join directly, adapted to your table names:
SELECT j.id, j.name, MAX(r.delivered_at) AS last_delivery
FROM jobs j
LEFT JOIN results r
ON r.job_id = j.id AND r.delivered_to IS NOT NULL
WHERE j.enabled = 1
GROUP BY j.id, j.name
HAVING MAX(r.delivered_at) IS NULL
OR MAX(r.delivered_at) < datetime('now', '-7 days');
Passing output is zero rows. Every row returned is an enabled job that has not put anything in front of a consumer in a week. And if your results table has no delivered_to, no read_at, no surface column of any kind, that absence is itself the finding: your schema cannot represent whether a job's output ever reached anyone.
The orchestration literature ends at the run
Current guidance covers everything up to the moment the job finishes. The unified orchestration framework paper formalizes planning, policy enforcement, state management and quality operations, all properties of the run. The best production reference I know for this stack, MCP-BEST-PRACTICES, covers the agentic loop, tool design, memory and safety controls with the same boundary. The ActionBridge orchestration chapter asks the right prior question, whether a single model with the right tools can do it in one call, but a single call's answer still needs somewhere to land. Delivery to a human is nobody's chapter, and it is the step where my shipped feature was silently a no-op.
The fallback reintroduces the bug, deliberately
One limitation is still live, and I chose it. If routing through the skill create path fails for any reason, task creation falls back to a bare insert, so creating a job never breaks. That means the failure mode this whole post is about can still occur, exactly once per broken creation, on the degraded path. Creation is guaranteed; surfacing is not. Also worth stating plainly: Vodou's automations poll. There are no inbound webhooks, so "when something new shows up" means "at the next poll," not "instantly."
This post is itself a scheduled job with a consumer
The reason a surfaced scheduled query is worth having is what runs underneath it. In Vodou, the thing executing that 7am query has your memory: a local database on your own machine, built up automatically from your conversations and sessions, that nothing leaves until you send it. So "summarize what changed on the project" means your project, with your context, without you re-explaining it. That memory crosses the browser boundary too. The same facts ride into ChatGPT, Claude and Gemini through the Vodou Bridge extension, and into Cursor, VS Code and Claude Code over MCP and hooks.
The scheduler is what turns that from a chat you visit into a system that works while you are away, and I can offer a checkable proof: the post you are reading was mined from my memory system, drafted, graded by a rubric, scanned by a redaction gate, deployed and verified on Vodou's own scheduler. Every piece of that pipeline is the same machinery you get: schedules, skills and MCP servers are yours to add and change, and the client side of all of it is open source on GitHub.
The failure class from this post is covered from the other side as well. Vodou's proactive loops watch for the silent stall: an automation that stopped firing, capture that went quiet, memory extraction that halted. The consumer question, "what would notice if this stopped," has a standing answer, and it is not you at 11pm grepping logs.
Vodou is for an individual engineer who wants one assistant, with their own memory, on their own machine, that they never have to re-introduce themselves to.
If you run scheduled AI jobs and cannot say today where each one's output lands, that is the problem Vodou was built around: sign up at vodou.ai and give every job you schedule a place where a person actually reads it.
Source: A scheduled job with no reader is a dead job with clean logs by Chad Priest, from Building Vodou in Public.



Top comments (0)