pub struct Graph {Show 22 fields
pub candidates: usize,
pub judges: usize,
pub deliberate_rounds: usize,
pub reviewers: usize,
pub review_rounds: usize,
pub max_parallel: usize,
pub language: String,
pub sessions: bool,
pub timeout_implement: u64,
pub timeout_judge: u64,
pub timeout_review: u64,
pub timeout_verify: Option<u64>,
pub timeout_fix: u64,
pub timeout_talk: u64,
pub retries: usize,
pub worktree_root: Option<PathBuf>,
pub land: bool,
pub land_rounds: usize,
pub land_approval: bool,
pub answer_timeout: u64,
pub incomplete_review: IncompleteReviewPolicy,
pub e2e_every_round: bool,
}Expand description
Graph shape and limits.
Fields§
§candidates: usizeParallel implementations of the same task. One by default.
Competition is the thing magi is for, and it is still here - it is just no longer what every task buys without being asked. Three days and 13 runs on this repository, which is the workload these numbers are drawn from:
- 0 of 13 competed runs reached a merge. Everything that landed in
that window went through
magi review- the cheap half, no competition - and passed on the first try. - The judges’ first choices split 73% of the time (8 of 11 tallies). Candidates that close together make the ranking a weak signal for what it costs to produce.
- One run’s own breakdown: implement 60min, judge 40min, fix 40min, review 28min, verify 5min - and verify is the node that caught a defect every reviewer had passed as clean. The cheapest step is the one that earns its place every time.
It is not worthless: oc won 3 of those tallies against sonnet, so a
single-seat default would have shipped the worse implementation in
roughly a quarter of them. That is exactly why this is a default and
not a removal - magi run --candidates N and a per-task seat count are
how a task that deserves a competition gets one.
A single-candidate run needs no special case: Runner::review’s doc
records that execute already degrades to implement -> review -> gate
-> merge, because judge skips a one-candidate field, deliberate has
no two first choices to reconcile and vote returns early.
judges: usizeIndependent judges.
deliberate_rounds: usizeDeliberation rounds when the judges’ first choices disagree.
reviewers: usizeReviewers per review round. Three by default - the smallest panel
a lens cycle (see crate::prompt::Lens) covers exactly once, so the
default panel reads the patch for spec compliance, regressions, and
simplicity without repeating an angle. Review is also the one stage
Self::candidates’s doc describes as running on every task
regardless of competition, which is what makes a panel worth its cost
here even though candidates itself defaults to one.
review_rounds: usizeMaximum review+fix rounds before the run is declared blocked.
max_parallel: usizeMaximum agent processes running at once.
language: StringLanguage for the prose the agents write (en / ja / any language name).
sessions: boolKeep one CLI conversation per seat, so a judge remembers its own argument across deliberation rounds and the fixer remembers its own implementation across review rounds.
Sessions are scoped to a seat, never to an agent id: the same model sitting as implementer and as judge gets two unrelated conversations, which is what keeps blind judging blind.
timeout_implement: u64Per-node timeouts, seconds.
timeout_judge: u64Per-node timeouts, seconds.
timeout_review: u64Per-node timeouts, seconds.
timeout_verify: Option<u64>Timeout for verify.e2e and verify.gate, seconds. Separate from
Self::timeout_review so shrinking a reviewer’s budget cannot
silently shrink a real-machine command’s budget too — the two used to
share timeout_review, and turning a slow reviewer down cut the
timeout cargo test --all-targets runs under along with it. When
omitted, preserves legacy configurations by using
Self::timeout_review. Set an explicit value to make verification
independent of later review-seat budget changes.
timeout_fix: u64Per-node timeouts, seconds.
timeout_talk: u64Wall-clock limit for one turn of crate::talk’s standing
conversation, seconds.
An hour: the operator is not watching this turn resolve in real time, so the budget can match what the work - reading files, running commands, checking their output - actually needs rather than what a person waiting on a phone can tolerate.
retries: usizeRetries for an agent invocation that fails or returns nothing usable.
worktree_root: Option<PathBuf>Root for candidate / judge worktrees. Defaults to ~/wt/magi.
land: boolAfter the pull request is open, keep going: watch its checks and reviews, run a fix round when they are unhappy, and ask to merge.
On, because stopping at an open pull request left the operator doing
the watching by hand - six times in the session this was built in - and
that is the work the loop exists to take. It only engages for
merge = "pr"; every other merge mode ends the run as before.
Turning this on does not hand magi the merge button:
Graph::land_approval is on too, and nothing merges without an
explicit answer. Setting both to their non-defaults is the only way to
get an unattended merge, and it has to be chosen twice.
land_rounds: usizeLand rounds - watch, fix, push - before the run is left for a human.
land_approval: boolAsk the owner before merging, showing what is about to land.
On, and it is what makes land safe to have on: the question carries a
rendered panel - the diffstat, the patch, the checks, the review
comments that were addressed, and the subject the squash will use - so
the decision is made on evidence rather than on trust, from wherever
the operator happens to be.
Silence is a hold. An unanswered approval never merges, and neither
does any answer other than the word merge.
answer_timeout: u64How long to wait for an owner to answer a question before the run is abandoned, seconds. A parked run costs nothing, so this is generous; it exists so a forgotten question cannot pin a worktree forever.
incomplete_review: IncompleteReviewPolicyWhat a round does when one or more reviewer seats never answered (timeout, crash, unparsable output).
e2e_every_round: boolRun verify.e2e on every round, even one that already has blocking
findings and another round left to try.
Off by default: a round with a blocking finding and rounds still left
is going back to the fixer regardless of what verify.e2e says, so
running it first only spends the round’s slowest step (minutes, on a
Rust repo’s cargo test --all-targets) on a head about to be
rewritten anyway. verify.e2e still runs once a round has no
blocking findings left (a round cannot go clean without it) and the
final verify.gate always runs on the actual tree that would land —
deferring is about when e2e runs mid-loop, never about skipping it.
Set this to restore the old every-round diagnostic behaviour: e2e output from a round that still has blocking findings is occasionally useful on its own (a runtime failure a reviewer’s panel would not have caught by reading), and this is the way back to seeing it every round instead of only once the panel has nothing left to flag.
Implementations§
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Graph
impl<'de> Deserialize<'de> for Graph
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for Graph
impl RefUnwindSafe for Graph
impl Send for Graph
impl Sync for Graph
impl Unpin for Graph
impl UnsafeUnpin for Graph
impl UnwindSafe for Graph
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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