Skip to main content

Module daemon

Module daemon 

Source
Expand description

The unattended loop: take the next task, run the graph, record what happened, take the next one.

This is what turns magi from a command a human types into something an agent can hand work to. crate::queue is the mailbox; this module is the thing that empties it. Nothing here decides how a task is implemented — that is crate::graph — it only decides which task runs next, and what a finished run means for the task that produced it.

§One run at a time, on purpose

There is no --jobs flag and there will not be one. A single run is already internally parallel: candidates implement concurrently and judges rank concurrently, so the machine is not idle while one task is in flight. The real constraint is not CPU but the agent CLIs’ quota, and two graphs at once doubles the burn rate on exactly the resource whose exhaustion produces RunStatus::Stalled. Serialising the loop is what keeps a full backlog from converting the whole day’s quota into a pile of untrustworthy verdicts.

§A crash is legible, and the loop notices on its own

The task is written as crate::queue::TaskStatus::Running, with its run id, before the graph starts, and is only rewritten once the run reaches a terminal status. A daemon killed mid-run therefore leaves the task Running and pointing at the run that was in flight. The alternative — reverting the task to Queued on the way out — would hide the abandoned run and re-spend its quota on the next poll.

A task left Running forever is not the point, though: crate::queue::TaskStatus::runnable never offers it again, so a daemon that died mid-run would otherwise strand its task for good. [reclaim_orphaned_running] runs on every poll and settles exactly the tasks no live process is actually driving — proven by Queue::claim succeeding rather than by a staleness guess — against whatever their last run actually became, through the same settle a live finish uses. A run that genuinely cannot be read still holds its task for a human; the run’s own report explains how far it got.

§Retries are bounded

Every attempt at a task consumes one of Opts::max_attempts, after which the task is crate::queue::TaskStatus::Held for a human. The one exception is a run that ended Stalled: the panel collapsed because the agent CLIs hit their quota, which is a fact about the machine and not about the task, so it must not spend an attempt. Without that exception a quota outage would quietly hold the entire backlog, and the operator would come back to a reset quota and nothing left that the loop is willing to run.

Structs§

Current
What the loop is working on, for the status file.
Opts
How the loop should behave.
Reading
The daemon’s published state, read permissively.
Status
The daemon’s liveness, published to <home>/daemon.json.
Stop
A cooperative stop, shared with whoever asked the loop to run.
Verdict
What a finished run tells the queue about the task it came from.

Constants§

HEARTBEAT
How often the status file is refreshed. A reader treats a status file older than STALE_SECS as “no daemon”, so the heartbeat has to be brisk enough that a busy daemon is never mistaken for a dead one.
POLL
Default queue poll interval.
SCHEMA
On-disk format for Status. Bumped when a field’s meaning changes.
STALE_CLAIM
How old a claim has to be before startup sweeps it. Longer than any run this graph plausibly takes, so a sweep cannot pull a task out from under a daemon that is merely slow.
STALE_SECS
How old a heartbeat may be before a reader calls the daemon dead. Six missed beats: long enough to survive a slow filesystem, short enough that a crashed daemon is not still reported as running a task.

Functions§

clear_status
Delete the status file. Called on the way out so a clean exit reads as “no daemon” rather than as a daemon whose heartbeat merely stopped.
current_work
Every run a live daemon is working on right now.
is_working_on
Whether a live daemon is working on this run at this moment.
is_working_on_task
Whether a live daemon is working on this task at this moment.
read_status
Read <home>/daemon.json permissively, or None when there is nothing usable there.
serve
Run the loop until Ctrl-C, or until the queue drains with Opts::once.
serve_until
serve, but stopping when stop is set as well as on Ctrl-C.
settle
Record a finished run against the task it came from.
status_path
Where the status file lives.
sweep_stale_claims
Remove claim files whose owner is provably dead, or that have simply outlived older_than, and return the task ids swept.
write_status
Publish the status file for this process.
write_status_to
Publish a status to an explicit path.