Add the paths, channel, loop and baseline config tables

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-18 17:36:20 -07:00
parent 06298d6a8e
commit ec5d887daa
4 changed files with 185 additions and 1 deletions
+75 -1
View File
@@ -82,6 +82,60 @@ impl Default for Limits {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
#[serde(deny_unknown_fields, default)]
pub struct Channel {
pub socket: std::path::PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct Paths {
pub home: std::path::PathBuf,
}
impl Default for Paths {
fn default() -> Self {
Self {
home: std::env::var_os("BOXMAKER_HOME")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from("/var/lib/boxmaker")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct Loop {
pub tool_iterations: u32,
pub repeat_detection: bool,
pub tool_result_cap: usize,
}
impl Default for Loop {
fn default() -> Self {
Self {
tool_iterations: 8,
repeat_detection: true,
tool_result_cap: 16_384,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct Baseline {
pub system: std::path::PathBuf,
}
impl Default for Baseline {
fn default() -> Self {
Self {
system: std::path::PathBuf::from("system.md"),
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
@@ -92,6 +146,14 @@ pub struct Config {
pub sampling: Sampling,
#[serde(default)]
pub limits: Limits,
#[serde(default)]
pub paths: Paths,
#[serde(default)]
pub channel: Channel,
#[serde(default)]
pub r#loop: Loop,
#[serde(default)]
pub baseline: Baseline,
}
#[derive(Debug)]
@@ -118,6 +180,18 @@ impl Config {
pub fn load(path: &std::path::Path) -> Result<Config, ConfigError> {
let text =
std::fs::read_to_string(path).map_err(|e| ConfigError::Read(path.to_path_buf(), e))?;
toml::from_str(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))
let mut config: Config =
toml::from_str(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;
if let Some(parent) = path.parent() {
config.baseline.system = parent.join(&config.baseline.system);
}
Ok(config)
}
pub fn channel_socket(&self) -> std::path::PathBuf {
if self.channel.socket.as_os_str().is_empty() {
self.paths.home.join("run/loop/loop.sock")
} else {
self.channel.socket.clone()
}
}
}
+83
View File
@@ -141,3 +141,86 @@ fn load_reports_which_file_failed() {
let e: Box<dyn std::error::Error> = Box::new(Config::load(&missing).unwrap_err());
assert!(e.to_string().contains("does-not-exist.toml"));
}
// ---- M2b: paths, channel, loop and baseline ----
#[test]
fn the_m2b_tables_have_defaults() {
let c = Config::load(&fixture("minimal.toml")).unwrap();
assert_eq!(
c.channel.socket,
PathBuf::new(),
"empty means: under the home"
);
assert_eq!(c.channel_socket(), c.paths.home.join("run/loop/loop.sock"));
assert_eq!(
(
c.r#loop.tool_iterations,
c.r#loop.repeat_detection,
c.r#loop.tool_result_cap
),
(8, true, 16 * 1024)
);
// A relative system prompt is taken from the config file's directory.
assert_eq!(c.baseline.system, fixture("system.md"));
// The home comes from BOXMAKER_HOME when set, else /var/lib/boxmaker. Either way it is absolute.
assert!(c.paths.home.is_absolute(), "{:?}", c.paths.home);
}
#[test]
fn the_m2b_tables_can_be_set() {
let c = Config::load(&fixture("m2b.toml")).unwrap();
assert_eq!(c.paths.home, PathBuf::from("/srv/boxmaker"));
assert_eq!(
c.channel.socket,
PathBuf::from("/run/boxmaker/loop/loop.sock")
);
assert_eq!(
c.channel_socket(),
PathBuf::from("/run/boxmaker/loop/loop.sock")
);
assert_eq!(
(
c.r#loop.tool_iterations,
c.r#loop.repeat_detection,
c.r#loop.tool_result_cap
),
(3, false, 1024)
);
assert_eq!(
c.baseline.system,
fixture("prompts/agent.md"),
"relative to the config file"
);
let text = std::fs::read_to_string(fixture("m2b.toml")).unwrap();
let absolute = text.replace("prompts/agent.md", "/etc/boxmaker/system.md");
assert_eq!(
Config::parse(&absolute).unwrap().baseline.system,
PathBuf::from("/etc/boxmaker/system.md")
);
}
#[test]
fn unknown_keys_are_errors_in_the_m2b_tables_too() {
let text = std::fs::read_to_string(fixture("m2b.toml")).unwrap();
assert!(Config::parse(&text).is_ok());
for table in ["paths", "channel", "loop", "baseline"] {
let header = format!("[{table}]\n");
let bad = text.replacen(&header, &format!("{header}zz_unknown = 1\n"), 1);
assert!(
Config::parse(&bad).is_err(),
"[{table}] accepted an unknown key"
);
}
let partial = text.replace("tool_iterations = 3\nrepeat_detection = false\n", "");
let c = Config::parse(&partial).unwrap();
assert_eq!(
(
c.r#loop.tool_iterations,
c.r#loop.repeat_detection,
c.r#loop.tool_result_cap
),
(8, true, 1024),
"a partial table keeps the other defaults"
);
}
+26
View File
@@ -0,0 +1,26 @@
[infer]
socket = "/tmp/infer.sock"
model = "some-model"
[slots]
main = 0
background = 1
[expect]
template_sha256 = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
n_ctx = 4096
slots = 2
[paths]
home = "/srv/boxmaker"
[channel]
socket = "/run/boxmaker/loop/loop.sock"
[loop]
tool_iterations = 3
repeat_detection = false
tool_result_cap = 1024
[baseline]
system = "prompts/agent.md"