holon 0.14.1

A headless, event-driven runtime for long-lived agents
Documentation
use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
    runtime::{RuntimeHandle, WorkItemFocusTransition},
    tool::helpers::{normalize_optional_non_empty, parse_tool_args, validate_non_empty},
    tool::spec::typed_spec,
    types::{ToolCapabilityFamily, TrustLevel, WorkItemRecord},
};

use super::{serialize_success, BuiltinToolDefinition};

pub(crate) const NAME: &str = "PickWorkItem";

#[derive(Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct PickWorkItemArgs {
    pub(crate) work_item_id: String,
    #[serde(default)]
    pub(crate) reason: Option<String>,
}

#[derive(Serialize)]
pub(crate) struct PickWorkItemResult {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub(crate) previous_work_item: Option<WorkItemRecord>,
    pub(crate) current_work_item: WorkItemRecord,
    pub(crate) current_work_item_id: String,
    pub(crate) transition: WorkItemFocusTransition,
    pub(crate) binding_note: String,
}

pub(crate) fn definition() -> Result<BuiltinToolDefinition> {
    Ok(BuiltinToolDefinition {
        family: ToolCapabilityFamily::CoreAgent,
        spec: typed_spec::<PickWorkItemArgs>(
            NAME,
            "Make an existing open work item the current work-item focus for this agent. Include reason when switching away from runnable current work; blocked work items may be picked for inspection but remain non-runnable.",
        )?,
    })
}

pub(crate) async fn execute(
    runtime: &RuntimeHandle,
    _agent_id: &str,
    _trust: &TrustLevel,
    input: &Value,
) -> Result<crate::tool::ToolResult> {
    let args: PickWorkItemArgs = parse_tool_args(NAME, input)?;
    let work_item_id = validate_non_empty(args.work_item_id, NAME, "work_item_id")?;
    let picked = runtime
        .pick_work_item_with_reason(work_item_id, normalize_optional_non_empty(args.reason))
        .await?;
    let previous_work_item = picked.previous_work_item;
    let current_work_item = picked.current_work_item;
    let current_work_item_id = current_work_item.id.clone();
    serialize_success(
        NAME,
        &PickWorkItemResult {
            previous_work_item,
            current_work_item,
            current_work_item_id,
            transition: picked.transition,
            binding_note: "subsequent tool calls in this turn are bound to the new current work item unless they explicitly specify another work_item_id".into(),
        },
    )
}