model/context: move context window config to host-level hive-c0re.nix
This commit is contained in:
parent
7e2f13cad8
commit
d3d52349c3
10 changed files with 81 additions and 59 deletions
|
|
@ -91,6 +91,7 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
|
||||||
¬es_dir,
|
¬es_dir,
|
||||||
coord_bg.dashboard_port,
|
coord_bg.dashboard_port,
|
||||||
&coord_bg.operator_pronouns,
|
&coord_bg.operator_pronouns,
|
||||||
|
&coord_bg.context_window_tokens,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
|
@ -415,6 +416,7 @@ async fn sync_meta_after_lifecycle(coord: &Coordinator) -> Result<()> {
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
&agents,
|
&agents,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ pub async fn rebuild_agent(coord: &Arc<Coordinator>, name: &str, current_rev: &s
|
||||||
¬es_dir,
|
¬es_dir,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
|
@ -160,6 +161,7 @@ pub async fn ensure_manager(coord: &Arc<Coordinator>) -> Result<()> {
|
||||||
¬es_dir,
|
¬es_dir,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(rev) = current_rev {
|
if let Some(rev) = current_rev {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,13 @@ pub struct Coordinator {
|
||||||
/// meta flake); the harness substitutes it into the agent /
|
/// meta flake); the harness substitutes it into the agent /
|
||||||
/// manager system prompt at boot.
|
/// manager system prompt at boot.
|
||||||
pub operator_pronouns: String,
|
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: Mutex<HashMap<String, AgentSocket>>,
|
||||||
/// Agents whose lifecycle action (currently just spawn) is in flight.
|
/// Agents whose lifecycle action (currently just spawn) is in flight.
|
||||||
/// Read by the dashboard to render a spinner; cleared when the action
|
/// Read by the dashboard to render a spinner; cleared when the action
|
||||||
|
|
@ -139,6 +146,7 @@ impl Coordinator {
|
||||||
hyperhive_flake: String,
|
hyperhive_flake: String,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: String,
|
operator_pronouns: String,
|
||||||
|
context_window_tokens: std::collections::HashMap<String, u64>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let broker = Broker::open(db_path).context("open broker")?;
|
let broker = Broker::open(db_path).context("open broker")?;
|
||||||
let approvals = Approvals::open(db_path).context("open approvals")?;
|
let approvals = Approvals::open(db_path).context("open approvals")?;
|
||||||
|
|
@ -152,6 +160,7 @@ impl Coordinator {
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
|
context_window_tokens,
|
||||||
agents: Mutex::new(HashMap::new()),
|
agents: Mutex::new(HashMap::new()),
|
||||||
transient: Mutex::new(HashMap::new()),
|
transient: Mutex::new(HashMap::new()),
|
||||||
dashboard_events,
|
dashboard_events,
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ pub async fn spawn(
|
||||||
notes_dir: &Path,
|
notes_dir: &Path,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
validate(name)?;
|
validate(name)?;
|
||||||
if let Some(other) = port_collision(name).await {
|
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>`
|
// before `nixos-container create` so the `--flake meta#<name>`
|
||||||
// ref resolves.
|
// ref resolves.
|
||||||
let agents = agents_after_spawn(name).await?;
|
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 container = container_name(name);
|
||||||
let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display());
|
let flake_ref = format!("{}#{name}", crate::meta::meta_dir().display());
|
||||||
run(&["create", &container, "--flake", &flake_ref]).await?;
|
run(&["create", &container, "--flake", &flake_ref]).await?;
|
||||||
|
|
@ -273,6 +274,7 @@ pub async fn rebuild(
|
||||||
notes_dir: &Path,
|
notes_dir: &Path,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Sync the meta flake (idempotent — no-op when the rendered
|
// Sync the meta flake (idempotent — no-op when the rendered
|
||||||
// flake matches disk) so a manual rebuild from the dashboard
|
// 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
|
// got added directly via `nixos-container create` outside
|
||||||
// hive-c0re).
|
// hive-c0re).
|
||||||
let agents = agents_for_meta(None).await?;
|
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
|
// Then bump just this agent's input — picks up whatever
|
||||||
// `applied/<n>/main` currently points at (deployed/<latest>).
|
// `applied/<n>/main` currently points at (deployed/<latest>).
|
||||||
// Commits the lock if it changed.
|
// Commits the lock if it changed.
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,12 @@ enum Cmd {
|
||||||
/// system prompt can mention them. Default: `she/her`.
|
/// system prompt can mention them. Default: `she/her`.
|
||||||
#[arg(long, default_value = "she/her")]
|
#[arg(long, default_value = "she/her")]
|
||||||
operator_pronouns: String,
|
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
|
/// Spawn a new agent container directly (`hive-agent-<name>`). Bypasses
|
||||||
/// the approval queue — use only as an operator on the host. For
|
/// the approval queue — use only as an operator on the host. For
|
||||||
|
|
@ -109,12 +115,17 @@ async fn main() -> Result<()> {
|
||||||
db,
|
db,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
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(
|
let coord = Arc::new(Coordinator::open(
|
||||||
&db,
|
&db,
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
|
cwt,
|
||||||
)?);
|
)?);
|
||||||
manager_server::start(coord.clone())?;
|
manager_server::start(coord.clone())?;
|
||||||
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,14 @@ pub async fn sync_agents(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let _guard = META_LOCK.lock().await;
|
let _guard = META_LOCK.lock().await;
|
||||||
let dir = meta_dir();
|
let dir = meta_dir();
|
||||||
std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?;
|
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 flake_path = dir.join("flake.nix");
|
||||||
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
|
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
|
||||||
let initial = !dir.join(".git").exists();
|
let initial = !dir.join(".git").exists();
|
||||||
|
|
@ -235,6 +236,7 @@ fn render_flake(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
) -> String {
|
) -> String {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
@ -283,8 +285,19 @@ fn render_flake(
|
||||||
HIVE_PORT = toString port;
|
HIVE_PORT = toString port;
|
||||||
HIVE_LABEL = name;
|
HIVE_LABEL = name;
|
||||||
HIVE_DASHBOARD_PORT = toString dashboardPort;
|
HIVE_DASHBOARD_PORT = toString dashboardPort;
|
||||||
HIVE_OPERATOR_PRONOUNS = operatorPronouns;
|
HIVE_OPERATOR_PRONOUNS = operatorPronouns;"#,
|
||||||
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
|
);
|
||||||
|
// 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";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ pub async fn run(coord: &Arc<Coordinator>) -> Result<()> {
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
&agents,
|
&agents,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
¬es_dir,
|
¬es_dir,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -139,6 +140,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
¬es_dir,
|
¬es_dir,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
|
&coord.context_window_tokens,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
// Mirror auto_update::rebuild_agent — the manager wants
|
// Mirror auto_update::rebuild_agent — the manager wants
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,31 @@ in
|
||||||
approval needed.
|
approval needed.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
contextWindowTokens = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.int;
|
||||||
|
default = {
|
||||||
|
haiku = 200000;
|
||||||
|
sonnet = 1000000;
|
||||||
|
opus = 1000000;
|
||||||
|
};
|
||||||
|
example = {
|
||||||
|
haiku = 150000;
|
||||||
|
sonnet = 900000;
|
||||||
|
};
|
||||||
|
description = ''
|
||||||
|
Per-model context-window sizes in tokens. Each key is a
|
||||||
|
model-family short name matched case-insensitively as a
|
||||||
|
substring of the active model name at runtime (e.g. `"sonnet"`
|
||||||
|
matches `"claude-sonnet-4-5"`). The defaults cover the known
|
||||||
|
Anthropic families; add entries for new models or override
|
||||||
|
existing ones here to change the window for all agents at once.
|
||||||
|
|
||||||
|
Passed to `hive-c0re serve` as JSON and injected into every
|
||||||
|
container's harness service environment as
|
||||||
|
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>`. Changes propagate
|
||||||
|
on the next `↻ R3BU1LD` — no per-agent approval needed.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
@ -89,7 +114,7 @@ in
|
||||||
];
|
];
|
||||||
environment.HYPERHIVE_GIT = "${pkgs.git}/bin/git";
|
environment.HYPERHIVE_GIT = "${pkgs.git}/bin/git";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns}";
|
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 2;
|
RestartSec = 2;
|
||||||
RuntimeDirectory = "hyperhive";
|
RuntimeDirectory = "hyperhive";
|
||||||
|
|
|
||||||
|
|
@ -36,41 +36,6 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
options.hyperhive.contextWindowTokens = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.int;
|
|
||||||
# Canonical defaults for known Anthropic model families.
|
|
||||||
# Override any entry in your agent.nix, or add new keys for
|
|
||||||
# model families not listed here.
|
|
||||||
default = {
|
|
||||||
haiku = 200000;
|
|
||||||
sonnet = 1000000;
|
|
||||||
opus = 1000000;
|
|
||||||
};
|
|
||||||
example = {
|
|
||||||
haiku = 150000;
|
|
||||||
sonnet = 900000;
|
|
||||||
};
|
|
||||||
description = ''
|
|
||||||
Per-model context-window sizes in tokens. Each key is a
|
|
||||||
model-family short name (e.g. `"haiku"`, `"sonnet"`) matched as a
|
|
||||||
case-insensitive substring of the active model name at runtime, so
|
|
||||||
`"sonnet"` matches `"claude-sonnet-4-5"` and any future variant.
|
|
||||||
|
|
||||||
The defaults declared here cover the known Anthropic model families.
|
|
||||||
Add or override entries in your `agent.nix` when using a
|
|
||||||
non-standard model or when Anthropic changes a model's window.
|
|
||||||
|
|
||||||
Each entry is rendered as
|
|
||||||
`HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` (e.g.
|
|
||||||
`HIVE_CONTEXT_WINDOW_TOKENS_SONNET = "1000000"`). The harness
|
|
||||||
checks these per-model vars in order (first substring match wins),
|
|
||||||
then falls back to `200000` when no key matches. At runtime the
|
|
||||||
effective window drives compaction (75%) and auto-reset (50%)
|
|
||||||
watermarks, and is exposed via `/api/state` as
|
|
||||||
`context_window_tokens`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
options.hyperhive.allowedBashPatterns = lib.mkOption {
|
options.hyperhive.allowedBashPatterns = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
|
|
@ -264,25 +229,15 @@
|
||||||
environment.etc."hyperhive/claude-plugins-auto-update.json".text =
|
environment.etc."hyperhive/claude-plugins-auto-update.json".text =
|
||||||
builtins.toJSON config.hyperhive.claudePluginsAutoUpdate;
|
builtins.toJSON config.hyperhive.claudePluginsAutoUpdate;
|
||||||
|
|
||||||
# Model + context-window env vars consumed by the harness at boot.
|
|
||||||
# HIVE_DEFAULT_MODEL seeds the initial model selection when no persisted
|
# HIVE_DEFAULT_MODEL seeds the initial model selection when no persisted
|
||||||
# model choice exists in the state dir.
|
# model choice exists in the state dir. SHELL must be set so claude's
|
||||||
# HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER> provides per-model overrides
|
# Bash tool finds a POSIX shell.
|
||||||
# (e.g. HIVE_CONTEXT_WINDOW_TOKENS_SONNET) from contextWindowTokens attrset.
|
# HIVE_CONTEXT_WINDOW_TOKENS_* are injected by the meta flake from the
|
||||||
# SHELL must be set so claude's Bash tool finds a POSIX shell.
|
# host-level `services.hive-c0re.contextWindowTokens` option — not set here.
|
||||||
environment.variables = lib.mkMerge (
|
environment.variables = {
|
||||||
[
|
|
||||||
{
|
|
||||||
HIVE_DEFAULT_MODEL = config.hyperhive.model;
|
HIVE_DEFAULT_MODEL = config.hyperhive.model;
|
||||||
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
||||||
}
|
};
|
||||||
]
|
|
||||||
++ lib.mapAttrsToList
|
|
||||||
(model: tokens: {
|
|
||||||
"HIVE_CONTEXT_WINDOW_TOKENS_${lib.toUpper model}" = toString tokens;
|
|
||||||
})
|
|
||||||
config.hyperhive.contextWindowTokens
|
|
||||||
);
|
|
||||||
|
|
||||||
boot.isNspawnContainer = true;
|
boot.isNspawnContainer = true;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue