Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
b4ebc61
A test that runs the installed xdg-open or gio waits for it at most 2…
netzenbot Nov 15, 2026
1c5c512
Start every unix stage in a session of its own and end a stage found …
netzenbot Nov 15, 2026
c1d84d2
Hold one job_output wait under the prompt cache lifetime in force
netzenbot Nov 15, 2026
086d97a
Name the signal that ended a step in the sentence about how a run ended
netzenbot Nov 15, 2026
078b43f
File tools name no route to a person where nobody can be asked
netzenbot Nov 15, 2026
118cd9c
Open a reference to a listed file under bypass with the reach read_fi…
netzenbot Nov 15, 2026
2d2d23c
Enter mid-turn on a line that begins with ! holds the line in the box
netzenbot Nov 15, 2026
388b59d
Take colour codes out of the output the planner reads and say how many
netzenbot Nov 15, 2026
f85d30b
Name remove, the permanent form and the * pattern in the /mcp referen…
netzenbot Nov 15, 2026
282e612
Bedrock records refused cache breakpoints only after a second refusal…
netzenbot Nov 15, 2026
1ba8748
The planner is told when a delegate ended transport or incomplete, an…
netzenbot Nov 15, 2026
c9accc4
Mark the three string slices in colour_codes::without with why each i…
netzenbot Nov 15, 2026
11b877b
read_output is given no reach under bypass for a reference to a liste…
netzenbot Nov 15, 2026
61238b3
The colour code scan is a bounds-checked helper with a Verus contract
netzenbot Nov 15, 2026
d3345fd
A reference to a file refused for want of reach names /add-dir as a f…
netzenbot Nov 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agents/skills/security-audit/lanes/checkout-controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ anything, so that sentence is no reason to drop a candidate about them. This lan
permission, a program that starts or a string that is shown?**

`BACKEND-24` says which layer answers for which name, and `BACKEND-33` names the variables an `env`
block sets and the eighteen read from the person's layers alone:
block sets and the twenty read from the person's layers alone:

{layer_clauses}

Expand Down
142 changes: 103 additions & 39 deletions crates/agent/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! environment and the user's own settings file before any turn starts, and nothing a model says can
//! reach it.

use crate::outcome::{BackendKind, Category, Diagnosis};
use crate::outcome::{BackendKind, Category, Diagnosis, RetrySetting};
use bravebot_aichat::anthropic::AnthropicClient;
use bravebot_aichat::anthropic::protocol::Failure;
use bravebot_aichat::call::Conceded;
Expand Down Expand Up @@ -75,10 +75,10 @@ pub enum BackendError {
conceded: Conceded,
/// The backend that sent the requests, where the caller said so.
backend: Option<BackendKind>,
/// Whether the backend that sent the requests has a `provider` entry to state `maxRetries`
/// in, set from the variant that sent them and never from a reply
/// Where the backend that sent the requests lets a person state `maxRetries`, set from the
/// variant that sent them and never from a reply
/// ([BACKEND-37](../../../docs/specs/backends.md#BACKEND-37)).
retry_setting: bool,
retry_setting: RetrySetting,
cause: Box<BackendError>,
},
}
Expand Down Expand Up @@ -171,7 +171,7 @@ impl BackendError {
context_tokens,
conceded: Conceded::default(),
backend: None,
retry_setting: false,
retry_setting: RetrySetting::None,
cause: Box::new(self),
}
}
Expand Down Expand Up @@ -226,11 +226,10 @@ impl BackendError {
}
}

/// This failure marked with whether the backend that sent the requests has a `provider` entry to
/// state `maxRetries` in, which is a fact about the variant and its configuration and never
/// anything a reply or an address said. A failure that counted no requests is returned as it
/// was.
pub(crate) fn with_retry_setting(self, entry: bool) -> Self {
/// This failure marked with where the backend that sent the requests lets a person state
/// `maxRetries`, which is a fact about the variant and its configuration and never anything a
/// reply or an address said. A failure that counted no requests is returned as it was.
pub(crate) fn with_retry_setting(self, setting: RetrySetting) -> Self {
match self {
Self::Attempted {
attempts,
Expand All @@ -246,7 +245,7 @@ impl BackendError {
context_tokens,
conceded,
backend,
retry_setting: entry,
retry_setting: setting,
cause,
},
other => other,
Expand Down Expand Up @@ -280,12 +279,11 @@ impl BackendError {
cause,
..
} => {
let diagnosis = cause.diagnosis().after(*attempts).with_conceded(*conceded);
let diagnosis = if *retry_setting {
diagnosis.with_retry_setting()
} else {
diagnosis
};
let diagnosis = cause
.diagnosis()
.after(*attempts)
.with_conceded(*conceded)
.with_retry_setting(*retry_setting);
return match backend {
Some(kind) => diagnosis.on_backend(*kind),
None => diagnosis,
Expand Down Expand Up @@ -749,6 +747,16 @@ pub fn bedrock_reads_effort(model: &str) -> bool {
!bravebot_bedrock::refusals(model).effort
}

/// Whether the Bedrock model `model` is recorded as refusing the cache breakpoints, so that its
/// requests carry none and the whole prefix is paid for on each of them.
///
/// False for a model no Bedrock account offers, whose requests this record says nothing about.
/// Not content. What a service refused is the transport's own report, read from a status and from
/// counts of requests ([BACKEND-27](../../../docs/specs/backends.md#BACKEND-27)).
pub fn breakpoints_given_up(config: &Config, model: &str) -> bool {
config.bedrock_for(model).is_some() && bravebot_bedrock::breakpoints_given_up(model)
}

/// What the Ollama at `host` has pulled, for the import to offer: [`bravebot_aichat::ollama`]'s
/// answer, reached from here because a front end does not talk to a backend crate itself.
pub fn installed_on_ollama<S: Sink>(
Expand Down Expand Up @@ -1432,17 +1440,21 @@ impl<'a> Backend<'a> {
}
}

/// Whether this backend is reached through a `provider` entry, which is where `maxRetries` is
/// stated ([BACKEND-37](../../../docs/specs/backends.md#BACKEND-37)).
/// Where this backend lets a person state `maxRetries`, which is a `provider` entry or, for an
/// AWS account reached through the tier variables, `BRAVEBOT_BEDROCK_MAX_RETRIES`
/// ([BACKEND-37](../../../docs/specs/backends.md#BACKEND-37)).
///
/// Read from the configuration the backend was selected with. The Brave endpoint, a tier-variable
/// Bedrock account, the Vertex service the environment names and the two sign-in accounts have no
/// entry to put it in.
fn has_retry_setting(&self) -> bool {
/// Read from the configuration the backend was selected with. The Brave endpoint, the Vertex
/// service the environment names and the two sign-in accounts have no place to put it.
fn retry_setting(&self) -> RetrySetting {
match self {
Self::Bedrock { config, .. } => config.named_by_block(),
Self::Gateway { provider, .. } => provider.written_in_entry(),
Self::Aichat { .. } | Self::Account { .. } | Self::XaiAccount { .. } => false,
Self::Bedrock { config, .. } if config.named_by_block() => RetrySetting::Entry,
Self::Bedrock { .. } => RetrySetting::Variable,
Self::Gateway { provider, .. } if provider.written_in_entry() => RetrySetting::Entry,
Self::Gateway { .. }
| Self::Aichat { .. }
| Self::Account { .. }
| Self::XaiAccount { .. } => RetrySetting::None,
}
}

Expand All @@ -1453,7 +1465,7 @@ impl<'a> Backend<'a> {
request: &ChatRequest,
) -> Result<Completion, BackendError> {
let kind = self.kind();
let entry = self.has_retry_setting();
let entry = self.retry_setting();
self.send(policy, request)
.map_err(|error| error.sent_by(kind).with_retry_setting(entry))
}
Expand Down Expand Up @@ -1536,7 +1548,7 @@ impl<'a> Backend<'a> {
progress: impl FnMut(Progress),
) -> Result<Completion, BackendError> {
let kind = self.kind();
let entry = self.has_retry_setting();
let entry = self.retry_setting();
self.send_streaming(policy, request, progress)
.map_err(|error| error.sent_by(kind).with_retry_setting(entry))
}
Expand Down Expand Up @@ -2572,14 +2584,14 @@ mod tests {
));
}

/// BACKEND-37: only a backend reached through a `provider` entry has a `maxRetries` to raise,
/// so only it is told to raise one. A gateway entry and an AWS entry have one; Brave's endpoint
/// and an AWS account reached through the tier variables have none.
/// BACKEND-37: only a backend with a place to state `maxRetries` is told to raise one. A gateway
/// entry and an AWS entry have `options.maxRetries`; an AWS account reached through the tier
/// variables has `BRAVEBOT_BEDROCK_MAX_RETRIES`; Brave's endpoint has none.
///
/// The faults this rejects: the flag true for every backend, false for every backend, and
/// decided by the variant alone, which would treat the two kinds of AWS account alike.
/// The faults this rejects: the setting the same for every backend, `None` for every backend,
/// and decided by the variant alone, which would treat the two kinds of AWS account alike.
#[test]
fn only_a_backend_reached_through_a_provider_entry_has_a_retry_setting() {
fn each_backend_names_the_place_it_states_max_retries_in() {
let mut config = with_a_gateway();
let serde_json::Value::Object(root) = serde_json::from_str(
r#"{"provider": {"amazon-bedrock": {
Expand All @@ -2594,12 +2606,20 @@ mod tests {
.providers
.extend(bravebot_config::provider::Provider::all(&root));
let egress = Egress::new();
let has = |model: &str| Backend::select(&config, &egress, model).has_retry_setting();
let has = |model: &str| Backend::select(&config, &egress, model).retry_setting();

assert!(has("z-ai/glm-4.6"), "a gateway entry");
assert!(has("openai.gpt-5.6-sol"), "an AWS entry");
assert!(!has("opus-arn"), "an AWS account the tier variables name");
assert!(!has(DEFAULT_MODEL), "Brave's endpoint");
assert_eq!(has("z-ai/glm-4.6"), RetrySetting::Entry, "a gateway entry");
assert_eq!(
has("openai.gpt-5.6-sol"),
RetrySetting::Entry,
"an AWS entry"
);
assert_eq!(
has("opus-arn"),
RetrySetting::Variable,
"an AWS account the tier variables name"
);
assert_eq!(has(DEFAULT_MODEL), RetrySetting::None, "Brave's endpoint");
}

/// A `provider` block naming AWS reaches Bedrock, which signs, rather than the gateway client,
Expand Down Expand Up @@ -5709,6 +5729,50 @@ mod tests {
}
}

/// BACKEND-37: which endings of a request are told as a request that did not get through and
/// which as a reply that stopped. A transport failure, a redirect that did not end or named no
/// location, and a withdrawn request are transport; a reply cut at the time its request stated
/// for it is incomplete. The category is chosen from the kind of error the transport raised,
/// never from its text.
///
/// The faults this rejects: a deadline told as transport, which sends a person to check a
/// network that answered, and a dropped connection told as incomplete before any of the reply
/// arrived.
#[test]
fn each_ending_of_a_request_is_told_as_transport_or_as_incomplete() {
use bravebot_net::EgressError;
let url = || "https://bedrock.example/invoke".to_string();
let category = |error: EgressError| of_egress(&error).category;

assert_eq!(
category(EgressError::Transport {
url: url(),
detail: "reset".to_string(),
transient: true,
}),
Category::Transport,
"a connection that dropped or went silent"
);
assert_eq!(
category(EgressError::TooManyRedirects { url: url() }),
Category::Transport
);
assert_eq!(
category(EgressError::MissingLocation { url: url() }),
Category::Transport
);
assert_eq!(
category(EgressError::Stopped { url: url() }),
Category::Transport,
"a withdrawn request, which is reported as a stop before it gets here"
);
assert_eq!(
category(EgressError::OutOfTime { url: url() }),
Category::Incomplete,
"a reply cut at the time its request stated for it"
);
}

/// A turn asks again after an empty reply, so a reply that was cut or garbled must not pass
/// for one: asking a model to carry on from something it never finished saying is answering
/// the wrong failure.
Expand Down
28 changes: 11 additions & 17 deletions crates/agent/src/confine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2625,18 +2625,18 @@ pub(crate) struct Contained {
_sandbox: Box<dyn bravebot_sandbox::Sandbox>,
}

/// `command`, started as the leader of a process group of its own on Unix.
/// `command`, started as the leader of a session of its own on Unix, which is a process group of
/// its own with no controlling terminal.
///
/// The group is what ending a stage addresses, so that the programs the stage started are ended
/// with it ([`crate::exec`], RUN-11). A stage that stayed in the session's group could not be
/// signalled without signalling the session.
fn leading_a_group(command: Command) -> Command {
/// signalled without signalling the session. The missing terminal is what keeps a program that
/// opens or configures the terminal from being stopped by it (RUN-11).
fn leading_a_session(command: Command) -> Command {
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;

let mut command = command;
command.process_group(0);
bravebot_sandbox::process::leading_a_session(&mut command);
command
}
#[cfg(not(unix))]
Expand All @@ -2651,22 +2651,16 @@ pub(crate) fn begin(
directory: &Path,
) -> Result<Launch, ExecError> {
let Some(confinement) = confinement else {
return Ok(Launch::Command(leading_a_group(command)));
return Ok(Launch::Command(leading_a_session(command)));
};
#[cfg(unix)]
{
// A confined stage is a session leader once the backend has called `setsid`, and a session
// leader leads a group of its own. Starting it as a leader here as well would make that
// call fail, since it refuses a process that already leads a group.
confinement
.wrap(command, step, directory)
// A Linux stage is a session leader once the backend has called `setsid`, and a
// session leader leads a group of its own. Asking for the group as well would make
// that call fail, since it refuses a process that already leads a group.
.map(|wrapped| {
if cfg!(target_os = "linux") {
Launch::Command(wrapped)
} else {
Launch::Command(leading_a_group(wrapped))
}
})
.map(Launch::Command)
}
#[cfg(windows)]
{
Expand Down
Loading
Loading