From f0b627c96a650d4ad9bc5eb7d9a3cbc080ba5dc2 Mon Sep 17 00:00:00 2001
From: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Date: Thu, 19 Jun 2025 10:04:31 -0400
Subject: [PATCH] Platform Tool for Scheduler: Allow Goose to Manage Its Own
Schedule (#2944)
---
crates/goose-server/src/commands/agent.rs | 5 +-
crates/goose-server/src/routes/schedule.rs | 10 +-
crates/goose/src/agents/agent.rs | 24 +-
crates/goose/src/agents/mod.rs | 2 +
crates/goose/src/agents/platform_tools.rs | 45 +
crates/goose/src/agents/schedule_tool.rs | 420 +++++++++
crates/goose/tests/agent.rs | 187 ++++
crates/goose/tests/private_tests.rs | 901 +++++++++++++++++++
crates/goose/tests/scheduler_test_support.rs | 21 +
crates/goose/tests/test_support.rs | 413 +++++++++
10 files changed, 2023 insertions(+), 5 deletions(-)
create mode 100644 crates/goose/src/agents/schedule_tool.rs
create mode 100644 crates/goose/tests/private_tests.rs
create mode 100644 crates/goose/tests/scheduler_test_support.rs
create mode 100644 crates/goose/tests/test_support.rs
diff --git a/crates/goose-server/src/commands/agent.rs b/crates/goose-server/src/commands/agent.rs
index 619451a7..104e3b81 100644
--- a/crates/goose-server/src/commands/agent.rs
+++ b/crates/goose-server/src/commands/agent.rs
@@ -29,7 +29,10 @@ pub async fn run() -> Result<()> {
.join("schedules.json");
let scheduler_instance = SchedulerFactory::create(schedule_file_path).await?;
- app_state.set_scheduler(scheduler_instance).await;
+ app_state.set_scheduler(scheduler_instance.clone()).await;
+
+ // NEW: Provide scheduler access to the agent
+ agent_ref.set_scheduler(scheduler_instance).await;
let cors = CorsLayer::new()
.allow_origin(Any)
diff --git a/crates/goose-server/src/routes/schedule.rs b/crates/goose-server/src/routes/schedule.rs
index cb1199de..9b4433c5 100644
--- a/crates/goose-server/src/routes/schedule.rs
+++ b/crates/goose-server/src/routes/schedule.rs
@@ -93,6 +93,8 @@ fn parse_session_name_to_iso(session_name: &str) -> String {
request_body = CreateScheduleRequest,
responses(
(status = 200, description = "Scheduled job created successfully", body = ScheduledJob),
+ (status = 400, description = "Invalid cron expression or recipe file"),
+ (status = 409, description = "Job ID already exists"),
(status = 500, description = "Internal server error")
),
tag = "schedule"
@@ -128,7 +130,13 @@ async fn create_schedule(
.await
.map_err(|e| {
eprintln!("Error creating schedule: {:?}", e); // Log error
- StatusCode::INTERNAL_SERVER_ERROR
+ match e {
+ goose::scheduler::SchedulerError::JobNotFound(_) => StatusCode::NOT_FOUND,
+ goose::scheduler::SchedulerError::CronParseError(_) => StatusCode::BAD_REQUEST,
+ goose::scheduler::SchedulerError::RecipeLoadError(_) => StatusCode::BAD_REQUEST,
+ goose::scheduler::SchedulerError::JobIdExists(_) => StatusCode::CONFLICT,
+ _ => StatusCode::INTERNAL_SERVER_ERROR,
+ }
})?;
Ok(Json(job))
}
diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs
index 9b7a8394..3f56dacc 100644
--- a/crates/goose/src/agents/agent.rs
+++ b/crates/goose/src/agents/agent.rs
@@ -17,6 +17,7 @@ use crate::permission::PermissionConfirmation;
use crate::providers::base::Provider;
use crate::providers::errors::ProviderError;
use crate::recipe::{Author, Recipe, Settings};
+use crate::scheduler_trait::SchedulerTrait;
use crate::tool_monitor::{ToolCall, ToolMonitor};
use regex::Regex;
use serde_json::Value;
@@ -27,7 +28,8 @@ use crate::agents::extension::{ExtensionConfig, ExtensionError, ExtensionResult,
use crate::agents::extension_manager::{get_parameter_names, ExtensionManager};
use crate::agents::platform_tools::{
PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME,
- PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
+ PLATFORM_MANAGE_SCHEDULE_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME,
+ PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
};
use crate::agents::prompt_manager::PromptManager;
use crate::agents::router_tool_selector::{
@@ -59,6 +61,7 @@ pub struct Agent {
pub(super) tool_result_rx: ToolResultReceiver,
pub(super) tool_monitor: Mutex