The last Chartreuse post was a specification for an operating system. This one concerns something much smaller, although the same method is present in both: begin with a requirement rather than a complaint, identify which guarantees should emerge from the architecture, and refuse capabilities that would weaken the boundaries of the system merely because they appear convenient.
The smaller project also shipped first.
This week I published demorari, a collection of three small C programs that gives the s6 supervision ecosystem something it has never provided on its own: timers.
The absence is unusual because supervision suites in the daemontools lineage are built around a clear and powerful model. A service is a long-running process, and a supervisor keeps that process alive. s6 is the strongest modern expression of this model, but the model itself has no real vocabulary for time. A request such as "run this backup at 4:17, then again every four hours" does not describe a long-running process, and s6 ships no native mechanism for expressing it.
Traditional Unix has cron. systemd has timer units. s6 has neither.
The available workarounds solve parts of the problem, but each also introduces a second model into the system. Running cron beside s6 means accepting another scheduler with its own configuration, state, logging, and failure behavior, while also leaving scheduled jobs outside the service dependency graph. A sleep loop inside a run script appears simpler, but it becomes imprecise and handles clock changes poorly. Leah Neukirchen's snooze already provides a good answer to the waiting problem, and demorari's schedule grammar is consciously in the same family, but waiting is only one part of what a supervised timer service requires.
With that gap in mind, the requirement becomes more useful than the grievance:
Scheduled jobs should be ordinary supervised services.
They should be visible through the same status tools as every other service, participate in the same dependency graph, use the same logging pipeline, and be restarted through the same supervisor. There should not be a second daemon responsible for managing timers, nor a sidecar scheduler maintaining its own state and introducing another independent way for the system to fail.
The architecture that satisfies this requirement is simple, although its consequences are larger than the amount of code might suggest.
A demorari timer begins when its run script execs into the waiter. The waiter calculates the next absolute instant matching the configured schedule and sleeps until that moment. When the time arrives, it execs into the scheduled job.
The scheduler does not launch the job. It becomes the job.
While the job is running, no scheduler process exists. When the job exits, the service exits with it. The supervisor then restarts the service, which begins a new wait and arms the next scheduled run.
This transformation is the central design decision because it produces two important guarantees without requiring a second mechanism to enforce either of them.
First, there is no scheduler daemon that can fail independently of the jobs it schedules, because there is no scheduler daemon. Second, a single timer cannot overlap with itself, because starting a second instance would require a waiter process that structurally does not exist while the first job is running. No lockfile is needed, and there is no separate overlap-prevention routine whose failure must also be considered.
This distinction matters. A guarantee enforced by a mechanism depends on every code path using that mechanism correctly. A guarantee produced by the architecture has no alternate code path through which the prohibited condition can quietly appear. In doing so, the design moves correctness away from coordination and into structure.
I did not invent this chainloading pattern. Laurent Bercot has publicly recommended the approach on the skarnet mailing lists, and demorari is an independent project built around the conventions of that ecosystem and its supporting library, skalibs. No affiliation with the skarnet project is implied. What demorari contributes is the surrounding material required to turn the pattern into a usable, inspectable timer service rather than a clever run script.
That surrounding material is divided between two additional tools, and the separation of their responsibilities is itself part of the design.
demorari-maker reads a source directory containing one value per file, which follows the native configuration idiom used throughout this ecosystem. From that source it renders a complete supervised service tree, including the run script, finish script, logging pipeline, and dependencies.
demorari-diff renders the same source into a private temporary directory and compares the result with the deployed tree. Drift is reported path by path, using a strict definition of what it means for a service to be current: the deployed tree must be exactly what the present version of the tool would render from the present configuration.
This definition avoids treating a successful installation from some unknown point in the past as evidence that a service remains correct today. The rendered tree is reproducible, the source remains visible, and the comparison is made against the actual output rather than against a version marker or an assumption.
Just as important is what none of the three programs will do.
They do not modify a live service store. They do not compile a service database. They do not bring services up or down. The maker generates a tree, but it is not an installer, and it refuses to replace a directory unless it can prove that the directory was previously created by the same tool.
For software that generates init configuration, direct access to the live service state is the most dangerous capability it could possess. Demorari therefore does not possess it. Installation remains a deliberate human action, and the generated material can be inspected before it becomes part of the running system.
Readers of the previous Chartreuse post will recognize the larger position behind this choice. Trust boundaries should remain narrow and understandable, even when widening them would make a demonstration appear smoother. Convenience is not harmless when it joins configuration generation, installation, compilation, and service-state mutation into a single action. Separating those steps creates a little more work for the operator, but it also makes the point at which the system changes visible.
The same principle shapes how demorari handles persistent timer state.
A timer may be configured to catch up on runs missed while the machine was powered off. This requires a timefile recording the most recent successful run. The waiter, however, only reads that file. It never writes to it.
Writing belongs to the service's finish hook, which records the run only after the scheduled job exits successfully. A failed job does not receive credit merely because the scheduled time passed or the process started. The state therefore describes completed work rather than attempted work.
There is also a more difficult failure case. A job may succeed while the finish hook is unable to record that success. In this situation, continuing automatically would create ambiguity. The service could run the job again, even though the previous run completed, or skip it while lacking the durable state needed to prove that skipping was correct.
Demorari stops instead.
This fail-stop behavior demands an operator because silent continuation would risk creating a difference between what happened and what the scheduler believes happened. The choice is less convenient than retrying indefinitely, but it is more honest. When successful work cannot be recorded, the system should not invent certainty and continue as though its state remains authoritative.
Scheduling correctness also requires specifying behavior around local time rather than inheriting whatever behavior happens to emerge from the host C library. Daylight-saving transitions can create local times that occur twice or do not occur at all, meaning local time is not always a clean, well-ordered sequence. Demorari defines its own behavior for these cases so that identical configuration produces identical firing decisions on glibc and musl.
A scheduler that leaves this unspecified has still chosen a behavior. It has simply allowed the platform to choose on its behalf.
The project's current status should be stated as plainly as its architecture.
Demorari 0.4.3 is a first public release maintained by one person. It is known to run in production on exactly one host, mine, where three real timers were migrated to it. That migration exposed and allowed me to correct a real defect before release, which is one reason migration against actual work matters more than a clean demonstration built only for the project itself.
The release is source-only. There are no distribution packages, registry releases, or prebuilt binaries. Building requires a C99 compiler, POSIX make, and skalibs.
Demorari is also intentionally a stopgap. Its roadmap states that if the skarnet project eventually provides native timer support, demorari will freeze, document a migration path, and recommend moving to the native implementation. This transition should remain inexpensive because the generated services are ordinary s6 material rather than opaque state owned by a scheduler daemon. The source can be rendered differently, the deployed tree can be replaced, and the surrounding supervision model remains intact.
A tool that defines the conditions under which it should stop being used is not weakening its purpose. It is clarifying that purpose.
In this sense, demorari is part of the Chartreuse work even though it is not the operating system itself. It applies the same method at a smaller scale: replace dissatisfaction with explicit requirements, prefer architectural guarantees over defensive mechanisms, keep generated configuration separate from live state, treat failure honestly, and leave an understandable path by which the tool may eventually disappear.
The result is a scheduler whose most important property is that, when the scheduled work begins, the scheduler stops existing.
The code, README, and man pages are available from the trunk branch of the demorari repository.
Chartreuse resumes next post.