model/context: move context window config to host-level hive-c0re.nix

This commit is contained in:
damocles 2026-05-20 15:42:56 +02:00 committed by Mara
parent 7e2f13cad8
commit d3d52349c3
10 changed files with 81 additions and 59 deletions

View file

@ -91,6 +91,7 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
&notes_dir,
coord_bg.dashboard_port,
&coord_bg.operator_pronouns,
&coord_bg.context_window_tokens,
)
.await;
drop(guard);
@ -415,6 +416,7 @@ async fn sync_meta_after_lifecycle(coord: &Coordinator) -> Result<()> {
&coord.hyperhive_flake,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
&agents,
)
.await

View file

@ -73,6 +73,7 @@ pub async fn rebuild_agent(coord: &Arc<Coordinator>, name: &str, current_rev: &s
&notes_dir,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
)
.await;
drop(guard);
@ -160,6 +161,7 @@ pub async fn ensure_manager(coord: &Arc<Coordinator>) -> Result<()> {
&notes_dir,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
)
.await?;
if let Some(rev) = current_rev {

View file

@ -51,6 +51,13 @@ pub struct Coordinator {
/// meta flake); the harness substitutes it into the agent /
/// manager system prompt at boot.
pub operator_pronouns: String,
/// Per-model context-window sizes in tokens. Set via the host-level
/// `services.hive-c0re.contextWindowTokens` NixOS option; injected
/// into each container as `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>`
/// by the meta flake renderer. The harness uses these to derive
/// compaction / auto-reset watermarks and exposes the active value
/// on `/api/state` as `context_window_tokens`.
pub context_window_tokens: std::collections::HashMap<String, u64>,
agents: Mutex<HashMap<String, AgentSocket>>,
/// Agents whose lifecycle action (currently just spawn) is in flight.
/// Read by the dashboard to render a spinner; cleared when the action
@ -139,6 +146,7 @@ impl Coordinator {
hyperhive_flake: String,
dashboard_port: u16,
operator_pronouns: String,
context_window_tokens: std::collections::HashMap<String, u64>,
) -> Result<Self> {
let broker = Broker::open(db_path).context("open broker")?;
let approvals = Approvals::open(db_path).context("open approvals")?;
@ -152,6 +160,7 @@ impl Coordinator {
hyperhive_flake,
dashboard_port,
operator_pronouns,
context_window_tokens,
agents: Mutex::new(HashMap::new()),
transient: Mutex::new(HashMap::new()),
dashboard_events,

View file

@ -138,6 +138,7 @@ pub async fn spawn(
notes_dir: &Path,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
) -> Result<()> {
validate(name)?;
if let Some(other) = port_collision(name).await {
@ -154,7 +155,7 @@ pub async fn spawn(
// before `nixos-container create` so the `--flake meta#<name>`
// ref resolves.
let agents = agents_after_spawn(name).await?;
crate::meta::sync_agents(hyperhive_flake, dashboard_port, operator_pronouns, &agents).await?;
crate::meta::sync_agents(hyperhive_flake, dashboard_port, operator_pronouns, context_window_tokens, &agents).await?;
let container = container_name(name);
let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display());
run(&["create", &container, "--flake", &flake_ref]).await?;
@ -273,6 +274,7 @@ pub async fn rebuild(
notes_dir: &Path,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
) -> Result<()> {
// Sync the meta flake (idempotent — no-op when the rendered
// flake matches disk) so a manual rebuild from the dashboard
@ -280,7 +282,7 @@ pub async fn rebuild(
// got added directly via `nixos-container create` outside
// hive-c0re).
let agents = agents_for_meta(None).await?;
crate::meta::sync_agents(hyperhive_flake, dashboard_port, operator_pronouns, &agents).await?;
crate::meta::sync_agents(hyperhive_flake, dashboard_port, operator_pronouns, context_window_tokens, &agents).await?;
// Then bump just this agent's input — picks up whatever
// `applied/<n>/main` currently points at (deployed/<latest>).
// Commits the lock if it changed.

View file

@ -62,6 +62,12 @@ enum Cmd {
/// system prompt can mention them. Default: `she/her`.
#[arg(long, default_value = "she/her")]
operator_pronouns: String,
/// Per-model context-window sizes, as JSON object mapping model-family
/// short name to token count. Threaded into each container as
/// `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars. Set via the
/// `services.hive-c0re.contextWindowTokens` NixOS option.
#[arg(long, default_value = r#"{"haiku":200000,"sonnet":1000000,"opus":1000000}"#)]
context_window_tokens: String,
},
/// Spawn a new agent container directly (`hive-agent-<name>`). Bypasses
/// the approval queue — use only as an operator on the host. For
@ -109,12 +115,17 @@ async fn main() -> Result<()> {
db,
dashboard_port,
operator_pronouns,
context_window_tokens,
} => {
let cwt: std::collections::HashMap<String, u64> =
serde_json::from_str(&context_window_tokens)
.context("--context-window-tokens: invalid JSON")?;
let coord = Arc::new(Coordinator::open(
&db,
hyperhive_flake,
dashboard_port,
operator_pronouns,
cwt,
)?);
manager_server::start(coord.clone())?;
// Idempotent pre-flight: rewrite pre-meta-layout applied

View file

@ -66,13 +66,14 @@ pub async fn sync_agents(
hyperhive_flake: &str,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
agents: &[AgentSpec],
) -> Result<()> {
let _guard = META_LOCK.lock().await;
let dir = meta_dir();
std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?;
let new_flake = render_flake(hyperhive_flake, dashboard_port, operator_pronouns, agents);
let new_flake = render_flake(hyperhive_flake, dashboard_port, operator_pronouns, context_window_tokens, agents);
let flake_path = dir.join("flake.nix");
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
let initial = !dir.join(".git").exists();
@ -235,6 +236,7 @@ fn render_flake(
hyperhive_flake: &str,
dashboard_port: u16,
operator_pronouns: &str,
context_window_tokens: &std::collections::HashMap<String, u64>,
agents: &[AgentSpec],
) -> String {
use std::fmt::Write as _;
@ -283,8 +285,19 @@ fn render_flake(
HIVE_PORT = toString port;
HIVE_LABEL = name;
HIVE_DASHBOARD_PORT = toString dashboardPort;
HIVE_OPERATOR_PRONOUNS = operatorPronouns;
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
HIVE_OPERATOR_PRONOUNS = operatorPronouns;"#,
);
// Per-model context-window env vars declared in the host-level
// `services.hive-c0re.contextWindowTokens` option. Use a sorted
// iterator for deterministic flake output (no spurious git diffs).
let mut sorted_tokens: Vec<(&String, &u64)> = context_window_tokens.iter().collect();
sorted_tokens.sort_by_key(|(k, _)| k.as_str());
for (key, val) in &sorted_tokens {
let upper_key = key.to_ascii_uppercase();
let _ = writeln!(out, " HIVE_CONTEXT_WINDOW_TOKENS_{upper_key} = \"{val}\";");
}
out.push_str(
r#" HYPERHIVE_STATE_DIR = "/agents/${name}/state";
};
}
];

View file

@ -83,6 +83,7 @@ pub async fn run(coord: &Arc<Coordinator>) -> Result<()> {
&coord.hyperhive_flake,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
&agents,
)
.await

View file

@ -77,6 +77,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
&notes_dir,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
)
.await
{
@ -139,6 +140,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
&notes_dir,
coord.dashboard_port,
&coord.operator_pronouns,
&coord.context_window_tokens,
)
.await;
// Mirror auto_update::rebuild_agent — the manager wants