model persisted to /state; stop auto-allowing claude-code unfree
model persistence: /model <name> now writes to /state/hyperhive-model (in-container), Bus::new reads it on init. operator override survives harness restart and container rebuild; gone on --purge like every other piece of agent state. path overridable via HYPERHIVE_MODEL_FILE for tests. failure to persist is a warn, not fatal — runtime override still applies, just won't survive a restart. unfree opt-in: drop the auto-allowUnfreePredicate from harness-base.nix and the claude-unstable overlay. operator now has to set nixpkgs.config.allowUnfree (or a predicate listing claude-code) in their own host config. silent unfree bypass was sketchy; this is honest. readme + gotchas updated to spell out the snippet. todo: drops model-persistence + container-crash + journald (all shipped); adds per-agent send allow-list (constrain who an agent can message).
This commit is contained in:
parent
58c3cd853b
commit
8b9f7d21b7
6 changed files with 84 additions and 19 deletions
|
|
@ -24,6 +24,36 @@ const HISTORY_CAPACITY: usize = 2000;
|
|||
/// `HYPERHIVE_EVENTS_DB` env var (used in tests and one-shot tools).
|
||||
const DEFAULT_EVENTS_DB: &str = "/state/hyperhive-events.sqlite";
|
||||
|
||||
/// Persisted model name file. Same lifecycle as the events db —
|
||||
/// survives destroy/recreate, gone on purge. Empty / missing file
|
||||
/// falls back to `DEFAULT_MODEL`.
|
||||
const DEFAULT_MODEL_FILE: &str = "/state/hyperhive-model";
|
||||
|
||||
/// Path to the persisted model file. Overridable via
|
||||
/// `HYPERHIVE_MODEL_FILE` for dev / tests.
|
||||
fn model_file_path() -> PathBuf {
|
||||
std::env::var_os("HYPERHIVE_MODEL_FILE")
|
||||
.map_or_else(|| PathBuf::from(DEFAULT_MODEL_FILE), PathBuf::from)
|
||||
}
|
||||
|
||||
fn load_model() -> Option<String> {
|
||||
let s = std::fs::read_to_string(model_file_path()).ok()?;
|
||||
let name = s.trim();
|
||||
if name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(name.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn persist_model(name: &str) -> std::io::Result<()> {
|
||||
let path = model_file_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
std::fs::write(path, format!("{name}\n"))
|
||||
}
|
||||
|
||||
fn now_unix() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
|
|
@ -177,11 +207,12 @@ impl Bus {
|
|||
}
|
||||
};
|
||||
let (tx, _) = broadcast::channel(CHANNEL_CAPACITY);
|
||||
let initial_model = load_model().unwrap_or_else(|| DEFAULT_MODEL.to_owned());
|
||||
Self {
|
||||
tx: Arc::new(tx),
|
||||
store,
|
||||
state: Arc::new(Mutex::new((TurnState::Idle, now_unix()))),
|
||||
model: Arc::new(Mutex::new(DEFAULT_MODEL.to_owned())),
|
||||
model: Arc::new(Mutex::new(initial_model)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,9 +224,16 @@ impl Bus {
|
|||
}
|
||||
|
||||
/// Switch the model for future turns. The current turn (if any)
|
||||
/// keeps the model it was already running.
|
||||
/// keeps the model it was already running. Persisted to
|
||||
/// `/state/hyperhive-model` so the override survives harness
|
||||
/// restart and container rebuild (gone on `--purge`, matching
|
||||
/// every other piece of agent state).
|
||||
pub fn set_model(&self, name: impl Into<String>) {
|
||||
*self.model.lock().unwrap() = name.into();
|
||||
let value: String = name.into();
|
||||
self.model.lock().unwrap().clone_from(&value);
|
||||
if let Err(e) = persist_model(&value) {
|
||||
tracing::warn!(error = ?e, "model: persist failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the harness's authoritative turn-loop state. Records
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue