everruns-core 0.9.0

Core agent abstractions for Everruns - agent loop, events, tools, LLM providers
Documentation
// Self-Budget Capability
//
// Prompt-only capability that teaches agents how to reason about a user-requested
// *indicative* budget ("you have $7") using session usage data. Distinct from the
// `budgeting` capability, which wires platform-enforced budgets and the
// `check_budget` tool. `self_budget` ships NO tools — it relies on
// `get_session_info` (already provided by the `session` capability in the generic
// harness) for cumulative usage and on the agent's own judgment about when and
// how to adapt behavior.
//
// See specs/budgeting.md (Self-Managed vs Platform-Enforced Budgets).

use super::{Capability, CapabilityStatus};

pub const SELF_BUDGET_CAPABILITY_ID: &str = "self_budget";

/// Self-budget capability — prompt-only guidance for agent-managed indicative budgets.
pub struct SelfBudgetCapability;

impl Capability for SelfBudgetCapability {
    fn id(&self) -> &str {
        SELF_BUDGET_CAPABILITY_ID
    }

    fn name(&self) -> &str {
        "Self-Budget"
    }

    fn description(&self) -> &str {
        "Prompt-only guidance for reasoning about a user-requested indicative budget. \
         The agent self-manages the target using session usage data; no tools are added \
         and no platform enforcement is performed. Use alongside `budgeting` for \
         authoritative platform budgets."
    }

    fn status(&self) -> CapabilityStatus {
        CapabilityStatus::Available
    }

    fn icon(&self) -> Option<&str> {
        Some("gauge")
    }

    fn category(&self) -> Option<&str> {
        Some("System")
    }

    fn system_prompt_addition(&self) -> Option<&str> {
        Some(SELF_BUDGET_SYSTEM_PROMPT)
    }

    fn features(&self) -> Vec<&'static str> {
        vec![]
    }
}

const SELF_BUDGET_SYSTEM_PROMPT: &str = "User-stated budgets are agent-managed soft targets, not platform-enforced limits. Track spend with `get_session_info` around expensive phases, qualify estimates when pricing is partial, and tighten scope/output as the target nears. Do not create, modify, delete, or report them as platform budgets; if close to exhausted, inform the user and continue with a scoped-down path.";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_capability_metadata() {
        let cap = SelfBudgetCapability;
        assert_eq!(cap.id(), "self_budget");
        assert_eq!(cap.name(), "Self-Budget");
        assert_eq!(cap.icon(), Some("gauge"));
        assert_eq!(cap.category(), Some("System"));
        assert_eq!(cap.status(), CapabilityStatus::Available);
    }

    #[test]
    fn test_capability_has_no_tools() {
        let cap = SelfBudgetCapability;
        assert!(cap.tools().is_empty());
        assert!(cap.tool_definitions().is_empty());
    }

    #[test]
    fn test_capability_has_system_prompt() {
        let cap = SelfBudgetCapability;
        let prompt = cap.system_prompt_addition().expect("prompt present");
        assert!(prompt.contains("agent-managed soft targets"));
        assert!(prompt.contains("get_session_info"));
        assert!(prompt.contains("platform-enforced limits"));
    }

    #[test]
    fn test_capability_has_no_features() {
        let cap = SelfBudgetCapability;
        assert!(cap.features().is_empty());
    }

    #[test]
    fn test_prompt_distinguishes_from_budgeting() {
        let cap = SelfBudgetCapability;
        let prompt = cap.system_prompt_addition().unwrap();
        // Must NOT direct the agent to the platform-budget tool
        assert!(!prompt.contains("check_budget"));
        // Must NOT direct the agent to mutate budgets
        assert!(!prompt.to_lowercase().contains("create budget"));
    }
}