feat(daemon): dynamic agent capabilities via clawdie-hw-probe --capabilities #258

Merged
clawdie merged 1 commit from feat/dynamic-agent-capabilities into main 2026-06-28 01:41:17 +02:00

View file

@ -642,23 +642,29 @@ pub async fn autospawn_agent_if_configured(state: &SharedState) {
.map(str::to_string);
// Register the autospawned agent in the local store so the scheduler
// can route queued tasks to it. Without this, tasks stay stuck in
// "queued" — the scheduler only assigns work to registered agents.
// can route queued tasks to it. Capabilities come from hw-probe
// (dynamic — not hardcoded per platform).
if let Some(ref aid) = agent_id {
let caps = serde_json::json!(["shell", "freebsd", "code"]);
let caps = probe_capabilities();
match state.store.try_lock() {
Ok(store) => {
if let Err(e) = store.register_agent(aid, caps.clone(), Some(&hostname)) {
if let Err(e) =
store.register_agent(aid, serde_json::to_value(&caps).unwrap_or_default(), Some(&hostname))
{
warn!(agent_id = %aid, error = %e, "autospawn: failed to register agent in store");
} else {
info!(agent_id = %aid, hostname = %hostname, "autospawn: agent registered for task work");
info!(agent_id = %aid, hostname = %hostname, capabilities = ?caps, "autospawn: agent registered for task work");
}
}
Err(std::sync::TryLockError::WouldBlock) => {
warn!("autospawn: store locked; agent registration deferred");
}
Err(std::sync::TryLockError::Poisoned(e)) => {
if let Err(e) = e.into_inner().register_agent(aid, caps, Some(&hostname)) {
if let Err(e) = e.into_inner().register_agent(
aid,
serde_json::to_value(&caps).unwrap_or_default(),
Some(&hostname),
) {
warn!(agent_id = %aid, error = %e, "autospawn: failed to register agent (poisoned store)");
}
}
@ -879,6 +885,24 @@ async fn try_register_hw_with_mother(hostname: &str, hw_profile: &str) {
}
}
/// Run `clawdie-hw-probe --capabilities` to get a dynamic capability list.
/// Falls back to a platform-agnostic minimum if the probe is unavailable.
fn probe_capabilities() -> Vec<String> {
match std::process::Command::new("clawdie-hw-probe")
.arg("--capabilities")
.output()
{
Ok(out) if out.status.success() => {
if let Ok(caps) = serde_json::from_slice::<Vec<String>>(&out.stdout) {
return caps;
}
}
_ => {}
}
// Fallback: minimum capabilities that any zot node has.
vec!["shell".to_string(), "code".to_string()]
}
fn basename(path: &str) -> String {
std::path::Path::new(path)
.file_name()