radkit 0.0.5

Rust AI Agent Development Kit
Documentation
//! Negotiator for handling agent-user interactions before task creation.
//!
//! The negotiator handles the conversation flow when:
//! - A new context is being established
//! - The agent and user are determining which skill to use
//! - Additional information is needed before creating a task
//!
//! ⚠️ **Advanced API**: This module contains framework internals. Most users
//! should use the default implementation provided by the runtime.

pub mod default;

pub use default::DefaultNegotiator;

use crate::agent::AgentDefinition;
use crate::compat::{MaybeSend, MaybeSync};
use crate::errors::AgentResult;
use crate::macros::LLMOutput;
use crate::models::Content;
use crate::runtime::context::AuthContext;
use a2a_types::Message;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// The decision made by the negotiator after analyzing user intent.
///
/// The negotiator uses an LLM to determine whether to:
/// - Start a task with a specific skill
/// - Ask clarifying questions
/// - Reject the request as out of scope
#[derive(Debug, Clone, Deserialize, LLMOutput, Serialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NegotiationDecision {
    /// The user's intent is clear and matches an available skill.
    /// Proceed to create a task with the specified skill.
    StartTask {
        /// The ID of the skill that should handle this request.
        skill_id: String,

        /// Reasoning for why this skill was selected.
        /// Useful for debugging and observability.
        reasoning: String,
    },

    /// The user's intent is unclear or missing critical information.
    /// Ask clarifying questions before proceeding.
    AskClarification {
        /// The message to send to the user asking for clarification.
        /// Should be helpful and specific about what information is needed.
        message: String,
    },

    /// The user's request is outside the scope of available skills.
    /// Politely reject the request with an explanation.
    Reject {
        /// The reason why this request cannot be fulfilled.
        /// Should explain what the agent can and cannot do.
        reason: String,
    },
}

/// Trait for handling negotiation flow between agent and user.
///
/// The negotiator is responsible for:
/// - Interpreting user intent before a task is created
/// - Asking clarifying questions when needed
/// - Determining which skill should handle the request
/// - Deciding when negotiation is complete and a task should be created
#[cfg_attr(all(target_os = "wasi", target_env = "p1"), async_trait::async_trait(?Send))]
#[cfg_attr(
    not(all(target_os = "wasi", target_env = "p1")),
    async_trait::async_trait
)]
pub trait Negotiator: MaybeSend + MaybeSync {
    /// Negotiates with the user to determine the next action.
    ///
    /// The negotiator's sole responsibility is to call the LLM and return a decision.
    /// It does NOT store messages - that's the executor's responsibility.
    ///
    /// This method handles both starting a new negotiation and continuing an existing one.
    /// The distinction is made by the `history` parameter:
    /// - Empty history = new negotiation
    /// - Non-empty history = continuing negotiation
    ///
    /// The executor already fetches the history and generates the `context_id` before calling.
    ///
    /// # Arguments
    ///
    /// * `auth_ctx` - Authentication context for the current user/app
    /// * `agent_def` - The agent definition being negotiated with
    /// * `context_id` - Context ID for this conversation (generated by executor)
    /// * `content` - The user's current message content
    /// * `history` - Previous negotiation messages (empty for new conversations)
    ///
    /// # Returns
    ///
    /// Returns a `NegotiationDecision` indicating whether to:
    /// - Start a task with a specific skill
    /// - Ask for clarification
    /// - Reject the request as out of scope
    async fn negotiate(
        &self,
        auth_ctx: &AuthContext,
        agent_def: &AgentDefinition,
        context_id: &str,
        content: Content,
        history: Vec<Message>,
    ) -> AgentResult<NegotiationDecision>;
}