use std::fmt::Write as _;
const PLATFORM_DEFAULT_SOURCE: &str = "platform default config path";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SettingScope {
Shared,
Cli,
Mcp,
}
impl SettingScope {
pub const fn as_str(self) -> &'static str {
match self {
Self::Shared => "shared",
Self::Cli => "CLI",
Self::Mcp => "MCP",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SettingDoc {
pub section: &'static str,
pub key: &'static str,
pub value_type: &'static str,
pub default: &'static str,
pub description: &'static str,
pub scope: SettingScope,
}
#[derive(Clone, Copy, Debug)]
pub struct SettingsHelp {
docs: &'static [SettingDoc],
config_path_precedence: &'static [&'static str],
}
impl SettingsHelp {
pub const fn docs(self) -> &'static [SettingDoc] {
self.docs
}
pub const fn config_path_precedence(self) -> &'static [&'static str] {
self.config_path_precedence
}
pub fn render_human(self) -> String {
let mut output = String::from("plugmem settings\n\n");
output.push_str("Config file precedence:\n");
for (index, source) in self.config_path_precedence.iter().enumerate() {
if *source == PLATFORM_DEFAULT_SOURCE {
match crate::default_config_path() {
Some(path) => {
let _ = writeln!(output, " {}. {}", index + 1, path.display());
}
None => {
let _ = writeln!(output, " {}. {source} (unavailable)", index + 1);
}
}
} else {
let _ = writeln!(output, " {}. {source}", index + 1);
}
}
output.push('\n');
let mut section = None;
for doc in self.docs {
if section != Some(doc.section) {
if section.is_some() {
output.push('\n');
}
let _ = writeln!(output, "[{}]", doc.section);
section = Some(doc.section);
}
let _ = writeln!(
output,
" {} ({}, default: {}) — {} [{}]",
doc.key,
doc.value_type,
doc.default,
doc.description,
doc.scope.as_str()
);
}
output
}
}
const CONFIG_PATH_PRECEDENCE: &[&str] = &[
"--config PATH",
"$PLUGMEM_CONFIG",
"platform default config path",
"built-in defaults",
];
const DOCS: &[SettingDoc] = &[
SettingDoc {
section: "database",
key: "path",
value_type: "path string",
default: "platform data directory/memory.plugmem",
description: "Persistent database file; explicit --db/constructor path and PLUGMEM_DB override it",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "dim",
value_type: "non-negative integer",
default: "0",
description: "Embedding dimension; 0 disables vector storage",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "max_bytes",
value_type: "non-negative integer",
default: "2147483648",
description: "Total byte-pool ceiling",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "max_text",
value_type: "non-negative integer",
default: "4096",
description: "Maximum fact text length in bytes",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "max_blob",
value_type: "non-negative integer",
default: "65536",
description: "Maximum single blob length in bytes",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "shards_facts",
value_type: "power-of-two integer",
default: "1024",
description: "Facts arena shard count",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "shards_entities",
value_type: "power-of-two integer",
default: "256",
description: "Entities arena shard count",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "shards_edges",
value_type: "power-of-two integer",
default: "512",
description: "Edges arena shard count",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "shards_temporal",
value_type: "power-of-two integer",
default: "512",
description: "Temporal arena shard count",
scope: SettingScope::Shared,
},
SettingDoc {
section: "engine",
key: "shards_postings",
value_type: "power-of-two integer",
default: "2048",
description: "BM25 postings arena shard count",
scope: SettingScope::Shared,
},
SettingDoc {
section: "embedder",
key: "kind",
value_type: "string",
default: "none",
description: "Embedding provider: none, ollama, openai, lmstudio, vllm or llamacpp",
scope: SettingScope::Shared,
},
SettingDoc {
section: "embedder",
key: "url",
value_type: "string",
default: "unset",
description: "OpenAI-compatible /v1/embeddings endpoint",
scope: SettingScope::Shared,
},
SettingDoc {
section: "embedder",
key: "model",
value_type: "string",
default: "unset",
description: "Embedding model name",
scope: SettingScope::Shared,
},
SettingDoc {
section: "embedder",
key: "api_key_env",
value_type: "string",
default: "unset",
description: "Environment variable containing the bearer token",
scope: SettingScope::Shared,
},
SettingDoc {
section: "maintenance",
key: "snapshot_every_ops",
value_type: "non-negative integer",
default: "1024",
description: "Snapshot after this many mutations",
scope: SettingScope::Shared,
},
SettingDoc {
section: "maintenance",
key: "snapshot_journal_bytes",
value_type: "non-negative integer",
default: "4194304",
description: "Snapshot when the journal reaches this size",
scope: SettingScope::Shared,
},
SettingDoc {
section: "maintenance",
key: "maintain_every_forgets",
value_type: "non-negative integer",
default: "off",
description: "Run physical tombstone maintenance after this many forgets",
scope: SettingScope::Shared,
},
SettingDoc {
section: "maintenance",
key: "batch_size",
value_type: "positive integer",
default: "128",
description: "CLI import facts per embedding request and journal fsync",
scope: SettingScope::Cli,
},
SettingDoc {
section: "server",
key: "workers",
value_type: "positive integer",
default: "half of available cores",
description: "MCP worker threads",
scope: SettingScope::Mcp,
},
];
static SETTINGS_HELP: SettingsHelp = SettingsHelp {
docs: DOCS,
config_path_precedence: CONFIG_PATH_PRECEDENCE,
};
pub const fn settings_help() -> &'static SettingsHelp {
&SETTINGS_HELP
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_documented_setting_has_a_complete_description() {
assert!(!DOCS.is_empty());
for doc in DOCS {
assert!(!doc.section.is_empty());
assert!(!doc.key.is_empty());
assert!(!doc.value_type.is_empty());
assert!(!doc.default.is_empty());
assert!(!doc.description.is_empty());
}
}
#[test]
fn human_help_contains_every_documented_key() {
let rendered = settings_help().render_human();
for doc in DOCS {
assert!(
rendered.contains(doc.key),
"missing {}.{}",
doc.section,
doc.key
);
}
}
}