Expand description
Which advisors can be asked right now, and why each one that cannot, cannot.
Three inputs, all cheap and none over the network (ADR 0032):
- presence — an executable on
PATH, a credential variable set (its value is never read). Presence is what the crate can know; it says “installed/configured”, not “answering”, and the status says which it measured. - the reasoning mode —
offlineadmits only advisors that stay on the machine,ciadmits none, the rest admit all; an explicit disable list removes any. - recorded outcomes — every consultation a session recorded carries its status.
A run of transient failures opens the advisor’s circuit for a cooldown, a rate limit
for its own, an authentication failure for longer; once the cooldown passes the
advisor is available again and marked
recovering. The circuit is therefore a pure function of the records and the clock: deterministic, replayable, and never a permanent failure nobody configured.
An empty result is an ordinary answer. A session with no advisor still reasons; it reasons locally, and says so.
§Example
One advisor through its lifecycle: present, then two timeouts open its circuit, then
the cooldown passes and it is available again, marked recovering.
use majordomus_cli::reasoning::availability::{
derive_availability, resolve_mode, AdvisorState, AdvisorStatus, Inputs, Outcome,
Presence, FAILURE_COOLDOWN,
};
use majordomus_cli::reasoning::catalogue::{AdvisorCatalogue, References};
use majordomus_cli::reasoning::record::ConsultationStatus;
// Presence is the seam a test fakes: this "machine" has the executable `a`.
struct Installed;
impl Presence for Installed {
fn executable(&self, name: &str) -> bool { name == "a" }
fn variable(&self, _: &str) -> bool { false }
}
let catalogue: AdvisorCatalogue = majordomus_cli::metadata::yaml::parse_into(
"version: 1\ncapabilities:\n - id: code_review\n description: x\nadvisors:\n - id: a\n title: A\n transport: cli\n adapter: a-cli\n executable: a\n capabilities: [code_review]\n",
).unwrap();
let refs = References::default();
let mode = resolve_mode(None, false, None);
let at = |now: i64, history: &[Outcome]| -> AdvisorState {
derive_availability(&catalogue, &Inputs {
refs: &refs, presence: &Installed, mode: mode.mode, disabled: &[],
history, peers: &[], now, cooldown: None,
})
.remove(0)
};
assert_eq!(at(0, &[]).status, AdvisorStatus::Available);
let timeouts: Vec<Outcome> = (0..2)
.map(|i| Outcome {
id: format!("consultation-{i}"),
advisor: "a".into(),
status: ConsultationStatus::Timeout,
at: 100 + i,
retry_after: None,
})
.collect();
let open = at(102, &timeouts);
assert_eq!(open.status, AdvisorStatus::TemporarilyFailed);
assert_eq!(open.reason, "consecutive_failures");
let later = at(101 + FAILURE_COOLDOWN, &timeouts);
assert_eq!(later.status, AdvisorStatus::Available);
assert!(later.recovering);Structs§
- Advisor
Circuit - An open circuit: the consultation that opened it, the failures behind it, and when it closes. Derived from the records and the clock, never stored.
- Advisor
State - One advisor as it stands now: what the catalogue declares of it, its status, the one
machine word saying why, and what was probed. The row
reasoning.advisorsreports and the policy plans over. - Inputs
- Everything availability reads besides the catalogue: presence, the mode, the disable
list, the recorded outcomes, the linked peers and the clock. Holding them in one value
is what keeps
derive_availabilitypure. - Linked
Peer - A linked mesh runtime, as the mesh reports it. Those carrying the feature the
catalogue’s
peersblock names become advisors with the idpeer:<runtime>. - Mode
Resolution - The mode in force and what decided it, as
resolve_modereturns it. A plan and a report carry the source beside the mode, so a reader learns why the mode is what it is. - Outcome
- The recorded outcome of one consultation, as availability reads it: the advisor, how the consultation ended and when. The circuit is computed from these alone.
- System
Presence - The process’s own
PATHand environment: an executable counts when a file of its name with an execute bit stands in aPATHdirectory, a variable when it is set and non-empty. A name containing/is never an executable onPATH.
Enums§
- Advisor
Status - An advisor’s standing: whether it may be asked now and, when not, which kind of
reason keeps it out. Only
availableadvisors enter a plan. - Reasoning
Mode - How much independent review a session may use, and where from. The policy reads it
for the review budget; availability reads it to leave advisors out (
ciall of them,offlineevery one that leaves the machine).standardis the default.
Constants§
- AUTH_
COOLDOWN - How long an authentication failure keeps an advisor out, seconds: a credential is not fixed by asking again, so it is not asked again soon.
- COOLDOWN_
ENV - The environment variable overriding every circuit cooldown, in seconds.
- DISABLE_
ENV - The environment variable listing advisors to leave out (comma-separated, or
all). - FAILURES_
TO_ OPEN - Consecutive transient failures that open a circuit.
- FAILURE_
COOLDOWN - How long a circuit opened by transient failures stays open, seconds.
- MODE_
ENV - The environment variable that names the mode outright.
- RATE_
LIMIT_ COOLDOWN - How long a rate limit is honoured when it names no retry-after, seconds.
Traits§
- Presence
- What the crate can observe about an advisor’s installation. The one seam tests fake:
SystemPresencereads the process, and an example or a test answers from a list.
Functions§
- derive_
availability - Every advisor — declared ones in preference order, then linked peers by runtime — with its status. Pure over its inputs.
- resolve_
mode - Decide the mode: the variable, else a CI runner, else the active profile’s declared
mode, else
standard. An unreadable word is skipped with its source, never guessed.