Skip to main content

Task

Struct Task 

Source
pub struct Task {
Show 22 fields pub schema: u32, pub id: String, pub title: String, pub instruction: String, pub repo: PathBuf, pub source: Source, pub priority: i32, pub solo: bool, pub status: TaskStatus, pub attempts: usize, pub runs: Vec<String>, pub last_error: Option<String>, pub hold_reason: Option<String>, pub hold_source: Option<HoldSource>, pub diagnostic: Option<String>, pub blocked_by: Vec<String>, pub block_reason: Option<String>, pub answers: Vec<AnsweredQuestion>, pub review_branch: Option<String>, pub fresh_start: bool, pub created_at: Timestamp, pub updated_at: Timestamp,
}
Expand description

One unit of work.

Fields§

§schema: u32

On-disk format version.

§id: String

Task id, e.g. 20260902-140501-a1b2.

§title: String

One line, for lists and notifications.

§instruction: String

The task itself, handed to the graph verbatim.

§repo: PathBuf

Repository to work in.

§source: Source

Who asked.

§priority: i32

Higher runs first; ties break oldest-first so nothing starves.

§solo: bool

Run this task alone: one implementer, no panel of judges to convince.

#[serde(default)] so a queue file written before this field existed still reads, as false - the ordinary multi-candidate competition, unchanged. A task set to solo still runs the whole graph; only the candidate count the daemon builds it with changes, and crate::graph::Runner already collapses a single-candidate run to implement → review → gate → merge on its own (see crate::graph::Runner::review’s doc), so nothing about judging, deliberation or voting had to change to support this.

§status: TaskStatus

Current state.

§attempts: usize

How many times this task has been claimed.

§runs: Vec<String>

Runs this task has produced, oldest first.

§last_error: Option<String>

Why the last attempt did not land.

§hold_reason: Option<String>

What a human hold is waiting on.

None covers both the ordinary cases: a hold the loop makes itself (out of attempts, or the disk gate closed) explains itself through Task::last_error instead, and a human hold nobody bothered to explain is still a valid hold. The queue has no way to express a dependency between two tasks, so on the occasions a hold really is “wait for that other task first”, this is the only place that reason survives - see Task::hold_manual and Task::release.

#[serde(default)] so a queue file written before this field existed still reads, with no reason recorded rather than a parse error.

§hold_source: Option<HoldSource>

Who placed Task::hold_reason. None is a compatible old record; see Task::operator_held for its deliberately conservative meaning.

§diagnostic: Option<String>

Diagnostic detail excerpted from the run that led to a hold - what a human would have found opening artifacts/ by hand, not the one-line reason in Task::last_error. Set only when a run’s own attempts are exhausted and the task becomes TaskStatus::Held; daemon computes it from the run’s own record, since this module has no notion of a run’s internals. Bounded in length by the writer - see daemon::diagnostic - so a verbose run cannot make this file grow without limit.

#[serde(default)] so a queue file written before this field existed still reads, with no diagnostic recorded rather than a parse error.

§blocked_by: Vec<String>

What this task is waiting on: other task ids, unanswered crate::ask::Question ids, or both. Non-empty exactly when TaskStatus::Blocked; emptying it — see Task::unblock — is what puts the task back at TaskStatus::Queued.

Set by crate::conduct’s decisions and cleared deterministically by crate::daemon as each dependency resolves, never by a person. Never #[serde(default)] is skipped: a queue file from before this field existed has nothing to report here, and an empty list is exactly that.

§block_reason: Option<String>

One line explaining the current Task::blocked_by, written by crate::conduct. Cleared whenever blocked_by empties.

§answers: Vec<AnsweredQuestion>

Questions crate::conduct asked about this task that the operator has since answered, oldest first — what was asked, and what they said.

A blocking question’s id leaves Task::blocked_by the moment crate::ask::QuestionStatus::Answered is observed, but the id alone tells nobody what was decided. This is what carries the answer’s content forward: into the next conductor prompt for this task, and into the instruction handed to the next run — see crate::daemon’s deterministic blocker resolution. Kept for the task’s whole life, the same as Task::runs: a release resets attempts, not evidence.

§review_branch: Option<String>

Set by crate::conduct when it chooses Review recovery for a task whose branch survived a blocked run: the branch to reopen with crate::graph::Runner::review instead of competing from scratch.

Requeues the task the same way Task::release does, so it is picked up by the ordinary loop; crate::daemon reads this field once, when it actually starts the run, and clears it either way — consumed on success, dropped if the branch no longer exists by then. Never set from the conductor’s own words: crate::daemon derives the branch name itself from the task’s last run, so a hallucinated branch can never reach here.

§fresh_start: bool

A release deliberately starts a new competition instead of resuming the prior run. History remains as evidence in runs.

§created_at: Timestamp

When the task was filed.

§updated_at: Timestamp

Last change to this file.

Implementations§

Source§

impl Task

Source

pub fn new( title: String, instruction: String, repo: PathBuf, source: Source, ) -> Self

File a new task. Persist it with Queue::put.

Source

pub fn short(&self) -> &str

Short form used in reports, matching a run’s short id.

Source

pub fn start(&mut self, run: String)

Record that a run has started for this task.

Source

pub fn succeed(&mut self)

Record a successful run.

Both magi task done and POST /api/queue/{id}/done can close a held task directly, with no release in between, so this clears hold_reason the same way Task::release does. Otherwise a task held for “waiting on 3ed9” and then closed as done without ever being released would still read as waiting on something in magi task show and on its card, after it no longer is.

Source

pub fn fail(&mut self, why: impl Into<String>, max_attempts: usize)

Record a failed attempt. Out of attempts means held for a human, rather than retried until the money runs out.

Clears Task::diagnostic unconditionally: it belongs to whatever run produced it, and a caller that has one for this attempt sets it itself right after calling this, once it knows the task actually ended up TaskStatus::Held - see daemon::diagnostic. Without the clear, a task released after a diagnosed hold and then failed again for an unrelated, undiagnosed reason (a config error, say) would go on showing the previous run’s diagnostic as if it explained the new one.

Source

pub fn stall(&mut self, why: impl Into<String>)

Record an attempt that failed for a reason the task is not responsible for - the agent CLIs ran out of quota and the judging panel collapsed.

This refunds the attempt on purpose. A quota window closing at 4am must not spend the backlog’s retry budget: the operator would come back to a queue of held tasks that were never actually judged, and would have to release every one by hand to find out which had a real problem. The task goes back to Failed, which the loop retries, so a reset quota picks the work up where it stopped.

Source

pub fn operator_held(&self) -> bool

Whether this held task may only be released by an operator.

Old files did not record a source. Preserve every such hold rather than guessing that it was automatic and risking duplicate work. New automatic holds record HoldSource::Machine and remain recoverable.

Source

pub fn hold_manual(&mut self, reason: Option<String>)

Take this task out of the loop’s reach by an operator action.

Source

pub fn hold_machine(&mut self, reason: Option<String>)

Take this task out of the loop’s reach during automatic recovery.

Source

pub fn block(&mut self, blocked_by: Vec<String>, reason: Option<String>)

Block this task on other task ids and/or open question ids, chosen by crate::conduct. Pure: the caller still owns writing it back with Queue::put.

Source

pub fn unblock(&mut self, resolved_id: &str)

Remove one resolved dependency (a task id that became TaskStatus::Done, or a question id that became crate::ask::QuestionStatus::Answered). Once nothing is left in Task::blocked_by, the task returns to TaskStatus::Queued on its own - deciding why a task was blocked was crate::conduct’s job, but noticing a dependency resolved needs no model at all.

A no-op, on purpose, for a task that is not TaskStatus::Blocked: crate::daemon’s deterministic resolver runs over every task on every poll, and a task that moved on for some other reason must not be dragged back to Queued by a stale id it still happens to carry.

Source

pub fn record_answer(&mut self, question: String, answer: String)

Record that a question crate::conduct asked about this task has been answered, so the answer’s content — not just the fact that the question is gone — reaches the next conductor prompt and the next run’s instruction. See Task::answers.

Source

pub fn request_review(&mut self, branch: String)

Requeue this task to reopen its last run as a review-only pass against branch (crate::graph::Runner::review) rather than competing from scratch. See Task::review_branch.

Source

pub fn requeue(&mut self)

Requeue after a conductor chose a new competition. Unlike an ordinary operator release, this deliberately does not resume the old run.

Source

pub fn set_priority(&mut self, priority: i32) -> Result<()>

Change how urgently this task should run next.

Refused once the task is running: priority only feeds the sort Queue::next_runnable does over tasks waiting to be claimed, and a running task has already left that pool. Accepting the write anyway would look like it worked while changing nothing until - and unless - this attempt fails and the task becomes runnable again, which is a surprise the phone should not hand back as a success.

Source

pub fn edit(&mut self, title: String, instruction: String) -> Result<()>

Replace this task’s title and instruction wholesale.

Restricted to queued and held. A running task’s instruction has already been handed to the graph, so a run in flight and the file on disk must not be allowed to disagree about what was asked; a done or failed task is a record of what actually happened and editing it after the fact would falsify that record. id, created_at, source, and runs are left untouched on purpose - an edit stands in for “delete and refile”, and keeping the id, the timestamp, the attribution, and the run history is the entire reason it exists instead.

Source

pub fn handed_off(&mut self, why: impl Into<String>)

Record a run that produced a pull request without merging it.

The task is held rather than retried, and it costs no further attempt either way. The work the task asked for exists: it is sitting on a branch, in a pull request, waiting for CI or for a person. Retrying would spend the whole competition budget a second time and then race a second branch against the pull request the first one opened - which is exactly what happened to run 01c2, whose finished and green pull request was re-competed from scratch four seconds after it opened.

A pull request nobody merged is a request for a person, not a failure.

Source

pub fn release(&mut self)

Put a held or finished task back in line, with its attempt count reset so a release is a real second chance rather than an instant re-hold. The run history is kept: attempts reset, evidence does not.

Trait Implementations§

Source§

impl Clone for Task

Source§

fn clone(&self) -> Task

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Task

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Task

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for Task

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl Freeze for Task

§

impl RefUnwindSafe for Task

§

impl Send for Task

§

impl Sync for Task

§

impl Unpin for Task

§

impl UnsafeUnpin for Task

§

impl UnwindSafe for Task

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more