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
11
README.md
11
README.md
|
|
@ -91,6 +91,17 @@ hive-c0re will then:
|
|||
- auto-create the manager container (`hm1nd`) if missing,
|
||||
- auto-rebuild any managed container whose hyperhive rev is stale.
|
||||
|
||||
`claude-code` is unfree; hyperhive does not auto-allow it for you.
|
||||
Add to your host config:
|
||||
|
||||
```nix
|
||||
nixpkgs.config.allowUnfreePredicate =
|
||||
pkg: builtins.elem (nixpkgs.lib.getName pkg) [ "claude-code" ];
|
||||
```
|
||||
|
||||
(or `nixpkgs.config.allowUnfree = true`, your call). Each per-agent
|
||||
container inherits this through the same nixpkgs evaluation.
|
||||
|
||||
## Build / deploy
|
||||
|
||||
```sh
|
||||
|
|
|
|||
20
TODO.md
20
TODO.md
|
|
@ -3,6 +3,17 @@
|
|||
Pick anything from here when relevant. Cross-cutting design notes live in
|
||||
[CLAUDE.md](CLAUDE.md); high-level project intro in [README.md](README.md).
|
||||
|
||||
## Permissions / policy
|
||||
|
||||
- **Per-agent send allow-list.** Today any agent can `send` to any
|
||||
other recipient (peer, manager, operator). Add a per-agent
|
||||
policy that constrains the `to` field — declared in `agent.nix`,
|
||||
e.g. `hyperhive.allowedRecipients = [ "manager" "alice" ]`.
|
||||
Broker rejects with an `Err { message }` when the policy denies.
|
||||
Default: unrestricted (back-compat). The manager can still
|
||||
always send anywhere. Useful for sandboxing untrusted sub-agents
|
||||
so they can only talk to the manager, not other sub-agents.
|
||||
|
||||
## Security
|
||||
|
||||
- **Unprivileged containers (userns mapping).** Today the nspawn container
|
||||
|
|
@ -31,15 +42,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in
|
|||
derived from the same config so the operator stays in control of
|
||||
what's exposed.
|
||||
|
||||
## Per-agent settings
|
||||
|
||||
- **Model override persistence.** `/model <name>` already switches
|
||||
the model at runtime via `Bus::set_model`; the chip on the agent
|
||||
page reflects the current value. Override is in-memory only and
|
||||
resets on harness restart — by design for now, but consider
|
||||
optional persistence (`/state/model` file?) so an operator-set
|
||||
model survives a rebuild.
|
||||
|
||||
## UI / UX
|
||||
|
||||
- **Terminal: `/model` slash command.** Operator-typeable model
|
||||
|
|
|
|||
|
|
@ -53,11 +53,15 @@ socket without needing a clean reinstall.
|
|||
|
||||
## `claude-code` is unfree
|
||||
|
||||
`harness-base.nix` allow-list's it specifically. The flake pins it to
|
||||
**nixpkgs-unstable** via `overlays.claude-unstable` (stable lags too
|
||||
far). The overlay imports unstable with its own
|
||||
`allowUnfreePredicate` so the access inside the overlay doesn't
|
||||
itself trip.
|
||||
The flake pins it to **nixpkgs-unstable** via
|
||||
`overlays.claude-unstable` (stable lags too far). The overlay
|
||||
imports unstable inheriting the user's `nixpkgs.config`, so the
|
||||
operator must opt in by setting `allowUnfree = true` (or an
|
||||
`allowUnfreePredicate` that whitelists `claude-code`) on their host
|
||||
config. hyperhive deliberately does NOT auto-allow — silent unfree
|
||||
bypass would be sketchy, and the error message is clear enough that
|
||||
the operator can fix it once and forget about it. Same on the
|
||||
per-agent containers (they inherit through the same nixpkgs).
|
||||
|
||||
## Claude credentials are per-agent
|
||||
|
||||
|
|
|
|||
|
|
@ -67,9 +67,14 @@
|
|||
claude-unstable =
|
||||
final: prev:
|
||||
let
|
||||
# Inherit the *user's* nixpkgs config so allowUnfree (or an
|
||||
# `allowUnfreePredicate` they set on their flake) propagates
|
||||
# into the unstable import. hyperhive does not silently
|
||||
# bypass the unfree gate — if the operator hasn't opted in,
|
||||
# this overlay's `claude-code` access fails honestly.
|
||||
unstable = import nixpkgs-unstable {
|
||||
inherit (prev.stdenv.hostPlatform) system;
|
||||
config.allowUnfreePredicate = pkg: builtins.elem (prev.lib.getName pkg) [ "claude-code" ];
|
||||
config = prev.config;
|
||||
};
|
||||
in
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
boot.isNspawnContainer = true;
|
||||
|
||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "claude-code" ];
|
||||
# `claude-code` is unfree. hyperhive intentionally does NOT auto-allow
|
||||
# it — the operator opts in by setting
|
||||
# `nixpkgs.config.allowUnfreePredicate` (or `allowUnfree = true`) in
|
||||
# their own host config / agent.nix. Without that, the per-agent
|
||||
# build fails on this package and the operator sees an honest "this
|
||||
# is unfree, are you sure?" error.
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
hyperhive
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue