pub mod adapter;
pub mod budget;
pub mod child_dispatch;
pub mod clock;
pub mod compactor;
pub mod dispatch;
pub mod dispatch_cli;
pub mod inbox;
mod pin;
mod resolve;
pub mod role;
pub mod step;
pub mod stop;
pub mod subagent;
pub mod tool;
mod workflow_actions;
#[cfg(test)]
mod tests;
pub use adapter::{AdapterRunner, SpawnAdapter};
pub use child_dispatch::ChildDispatchRequest;
pub use clock::{Clock, IdGen, NanoIdGen, SystemClock};
pub use dispatch::{RealSleeper, Sleeper, install_stop_handler, stop_flag};
pub use pin::brazen_pin;
pub use tool::{ExecError, SpawnTool, ToolExecutor};
use crate::template::GitRunner;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub(crate) const WORKER_ROLE: &str = "worker";
pub(crate) const SOULS_DIR: &str = "souls";
const PER_REPO_PROVIDERS_FILE: &str = "providers.yaml";
pub(crate) const WORKFLOW_FILE: &str = "workflow.yaml";
const GLOBAL_MODELS_FILE: &str = "models.yaml";
#[derive(Debug, Error)]
#[allow(clippy::enum_variant_names)]
pub enum Error {
#[error("config: {0}")]
Config(#[from] crate::config::LoadError),
#[error("harness root: {0}")]
HarnessRoot(#[from] crate::harness_root::Error),
#[error("providers.yaml has no {0:?} role")]
RoleMissing(String),
#[error(transparent)]
Layout(#[from] crate::workspace::LayoutError),
#[error("read control {path} from the config commit (§2.2): {source}")]
ControlRead {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"model id {0:?} collides with the reserved transcript origin token `tool` (§2.3); \
rename the model row"
)]
ReservedModelId(String),
#[error("i/o writing conversation artifact: {0}")]
Io(#[from] std::io::Error),
#[error("adapter subprocess: {0}")]
AdapterSpawn(#[source] std::io::Error),
#[error(
"provider adapter {binary:?} not found (§4.4 — the default adapter is `bz` on \
PATH; install the pinned binary: cargo install brazen --version ={pin} --locked, \
or name an adapter you have with `adapter:` in the harness root's models.yaml): \
{source}"
)]
AdapterMissing {
binary: String,
pin: String,
#[source]
source: std::io::Error,
},
#[error("tool {tool}: {source}")]
ToolExec {
tool: String,
#[source]
source: ExecError,
},
#[error("adapter emitted malformed v=1 event JSON: {0}")]
AdapterJson(#[source] serde_json::Error),
#[error("provider error ({kind}): {message}")]
AdapterError { kind: String, message: String },
#[error(
"adapter stream ended without a terminal `end` (killed mid-stream, §2.9); \
adapter stderr tail: {tail} (full capture: {stderr_log})"
)]
AdapterHalfStream { stderr_log: PathBuf, tail: String },
#[error(
"bz version {found:?} does not match the linked brazen crate {expected:?} \
(§4.4 — install the pinned binary: cargo install brazen --version ={expected})"
)]
VersionSkew { found: String, expected: String },
#[error("adapter-override handshake failed: MessageStart.v={found:?}, expected {expected}")]
HandshakeMismatch { found: Option<u8>, expected: u8 },
#[error("git {op}: {source}")]
Git {
op: &'static str,
#[source]
source: std::io::Error,
},
#[error(
"dispatch of {child} from {parent} refused: {exhausted} (§6 budgets — \
the limit is declared in the governing config's workflow.yaml)"
)]
DispatchRefused {
child: String,
parent: String,
exhausted: budget::Exhausted,
},
#[error(transparent)]
UnknownAgent(#[from] crate::workspace::UnknownAgent),
#[error("acquire executor lock on inbox {path}: {source}")]
ExecutorLock {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"adopt LERNIE_LOCK_FD lease for {agent}: {detail} — a bad fd means a defective \
launcher; declined, never silently reacquired (§6)"
)]
LeaseAdopt { agent: String, detail: String },
#[error(
"branch {branch} tip is an assistant entry with tool_use unmatched by committed \
tool results — a mid-step crash after the assistant entry landed; tool side \
effects are not replayable, so this is declined (§6). Recover by fork-from-history \
(§2.3)."
)]
UnpairedToolUse { branch: String },
#[error(
"workflow action {action:?} is not yet interpreted at the {event} event (§6 \
binding interpreter — shipped subset is the terminal ref marks); the action is \
in the closed set but its executor is a tracked follow-on of bl-6a3b"
)]
ActionUnsupported { action: String, event: &'static str },
#[error("deposit initial user message: {0}")]
Deposit(#[from] inbox::DepositError),
#[error("tool {name} schema unreadable at {path}: {source}")]
ToolSchemaIo {
name: String,
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("tool {name} schema at {path} is not valid JSON: {source}")]
ToolSchemaJson {
name: String,
path: PathBuf,
#[source]
source: serde_json::Error,
},
#[error("tool {name} skill frontmatter unreadable at {path}: {source}")]
SkillFrontmatterIo {
name: String,
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("tool {name} skill frontmatter at {path} is malformed: {source}")]
SkillFrontmatter {
name: String,
path: PathBuf,
#[source]
source: serde_yaml_ng::Error,
},
}
pub struct Deps<'a> {
pub adapter: &'a dyn AdapterRunner,
pub sleeper: &'a dyn Sleeper,
pub git: &'a dyn GitRunner,
pub clock: &'a dyn Clock,
pub id_gen: &'a dyn IdGen,
pub tool_executor: &'a dyn ToolExecutor,
pub config_root: &'a Path,
pub adapter_target: Option<&'a Path>,
pub stop: &'a std::sync::atomic::AtomicBool,
pub launcher: &'a dyn inbox::Launcher,
}
pub fn run(repo: &Path, user_message: &str, deps: &Deps<'_>) -> Result<String, Error> {
crate::workspace::require(repo)?;
let source = resolve::ConfigSource::ConfigBranch(crate::workspace::DEFAULT_CONFIG_REF);
let cfg = resolve::resolve_worker(repo, source, deps)?;
dispatch::run_exchange(repo, user_message, &cfg.as_resolved(), deps)
}