From 9b72d0f0b991905db8c4e2109ebb10f27a9f0daf Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Tue, 17 Mar 2026 00:00:00 -0400 Subject: [PATCH 1/3] fix: preserve API-patched tool filters across Hand respawn When a Hand agent is respawned (on daemon restart or reactivation), activate_hand() rebuilds the manifest entirely from HAND.toml, silently discarding any tool_allowlist/tool_blocklist changes made via the API. The API returned {"status":"ok"} for these updates, creating a broken contract: changes appeared to succeed but were lost on the next restart. Fix: capture the existing agent's tool filters before killing it at respawn time, and reapply them to the freshly-built manifest. Empty filters are treated as "no override" (not "block all") so hands with no API-set filters continue to get the full tool list from HAND.toml. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-kernel/src/kernel.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 5e582d0482..f9fed65547 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -3216,6 +3216,18 @@ impl OpenFangKernel { other => KernelError::OpenFang(OpenFangError::Internal(other.to_string())), })?; + // Capture any API-patched tool filters from the existing agent before respawn so that + // changes made via PUT /api/agents/{id}/tools persist across daemon restarts and + // hand reactivations. The manifest rebuild below overwrites everything from HAND.toml, + // so we save and reapply these fields explicitly. + let existing_tool_filters: (Vec, Vec) = self + .registry + .list() + .into_iter() + .find(|e| e.name == def.agent.name) + .map(|e| (e.manifest.tool_allowlist.clone(), e.manifest.tool_blocklist.clone())) + .unwrap_or_default(); + // Build an agent manifest from the hand definition. // If the hand declares provider/model as "default", inherit the kernel's configured LLM. let hand_provider = if def.agent.provider == "default" { @@ -3287,6 +3299,16 @@ impl OpenFangKernel { ..Default::default() }; + // Restore API-patched tool filters so they survive respawn. + // Only apply if non-empty — an empty saved list means "no override set", not "block all". + let (saved_allowlist, saved_blocklist) = existing_tool_filters; + if !saved_allowlist.is_empty() { + manifest.tool_allowlist = saved_allowlist; + } + if !saved_blocklist.is_empty() { + manifest.tool_blocklist = saved_blocklist; + } + // Resolve hand settings → prompt block + env vars let resolved = openfang_hands::resolve_settings(&def.settings, &instance.config); if !resolved.prompt_block.is_empty() { From 4f3ba25d6a2748230b2076f8d51bdfabdbf38988 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Tue, 17 Mar 2026 00:00:02 -0400 Subject: [PATCH 2/3] fix: preserve model/provider/temperature across Hand respawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the API-patch preservation introduced in 81eb7cb to cover model config fields. Previously, provider, model, and temperature changes made via the agents API were lost every time a Hand was respawned (daemon restart, crash recovery, or manual reactivation) because activate_hand() rebuilt the AgentManifest entirely from the compile-time-embedded HAND.toml. Changes: - kernel.rs: single registry scan now captures both tool filters and model config before rebuild; existing_model_override carries provider/model/ temperature and is reapplied after the manifest is built, only when the live values differ from what HAND.toml would produce. system_prompt is intentionally excluded — it is assembled dynamically from HAND.toml plus settings context and must stay live. - registry.rs: add update_temperature() for hot-patching sampling temp. - routes.rs: expose temperature in list/get responses; add temperature field to PatchAgentConfigRequest and implement it in patch_agent_config with 0.0–2.0 validation. - index_body.html + agents.js: temperature input in the agent config tab. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-api/src/routes.rs | 24 +++++++++++ crates/openfang-api/static/index_body.html | 1 + crates/openfang-api/static/js/pages/agents.js | 3 +- crates/openfang-kernel/src/kernel.rs | 42 +++++++++++++++---- crates/openfang-kernel/src/registry.rs | 11 +++++ 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs index 456d867f00..a7b18f3af1 100644 --- a/crates/openfang-api/src/routes.rs +++ b/crates/openfang-api/src/routes.rs @@ -225,6 +225,7 @@ pub async fn list_agents(State(state): State>) -> impl IntoRespons "auth_status": auth_status, "ready": ready, "profile": e.manifest.profile, + "temperature": e.manifest.model.temperature, "identity": { "emoji": e.identity.emoji, "avatar_url": e.identity.avatar_url, @@ -1362,6 +1363,7 @@ pub async fn get_agent( "mcp_servers": entry.manifest.mcp_servers, "mcp_servers_mode": if entry.manifest.mcp_servers.is_empty() { "all" } else { "allowlist" }, "fallback_models": entry.manifest.fallback_models, + "temperature": entry.manifest.model.temperature, })), ) } @@ -8644,6 +8646,7 @@ pub struct PatchAgentConfigRequest { pub api_key_env: Option, pub base_url: Option, pub fallback_models: Option>, + pub temperature: Option, } /// PATCH /api/agents/{id}/config — Hot-update agent name, description, system prompt, and identity. @@ -8865,6 +8868,27 @@ pub async fn patch_agent_config( } } + // Update temperature + if let Some(temp) = req.temperature { + if !(0.0..=2.0).contains(&temp) { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": "Temperature must be between 0.0 and 2.0"})), + ); + } + if state + .kernel + .registry + .update_temperature(agent_id, temp) + .is_err() + { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "Agent not found"})), + ); + } + } + // Persist updated manifest to database so changes survive restart if let Some(entry) = state.kernel.registry.get(agent_id) { if let Err(e) = state.kernel.memory.save_agent(&entry) { diff --git a/crates/openfang-api/static/index_body.html b/crates/openfang-api/static/index_body.html index 539f1aa29e..e30bc18a5c 100644 --- a/crates/openfang-api/static/index_body.html +++ b/crates/openfang-api/static/index_body.html @@ -1025,6 +1025,7 @@

+