Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/core-boundaries/explicit-test-topology.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const servicesCoreIntegrationTestTargets = [
{ name: 'permission_store_contracts', path: 'tests/permission_store_contracts.rs' },
{ name: 'workspace_instruction_contracts', path: 'tests/workspace_instruction_contracts.rs' },
{ name: 'session_write_lock_contracts', path: 'tests/session_write_lock_contracts.rs' },
{ name: 'exclusive_file_lease_contracts', path: 'tests/exclusive_file_lease_contracts.rs' },
{ name: 'process_runtime_contracts', path: 'tests/process_runtime_contracts.rs' },
{ name: 'service_contracts', path: 'tests/service_contracts.rs' },
{ name: 'storage_owner_contracts', path: 'tests/storage_owner_contracts.rs' },
Expand Down
11 changes: 10 additions & 1 deletion src/crates/assembly/core/src/agentic/coordination/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ pub use openbitfun_runtime_ports::{
DialogSubmitOutcome,
};

/// Rejection prefix for a submission that reuses a dialog turn ID the session
/// already owns.
///
/// The scheduled-job service classifies enqueue failures from the port message
/// text, so producers and classifiers share this constant instead of repeating
/// the wording.
pub(crate) const DIALOG_TURN_ID_ALREADY_SETTLED_MESSAGE: &str =
"Dialog turn ID is already active or completed";

/// A message waiting to be dispatched to the coordinator
#[derive(Debug, Clone)]
pub struct QueuedTurn {
Expand Down Expand Up @@ -2772,7 +2781,7 @@ impl DialogScheduler {
PortError::new(
PortErrorKind::InvalidRequest,
format!(
"Dialog turn ID is already active or completed: session_id={}, turn_id={resolved_turn_id}",
"{DIALOG_TURN_ID_ALREADY_SETTLED_MESSAGE}: session_id={}, turn_id={resolved_turn_id}",
request.session_id
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,14 @@ Patch schema for "update":
session_id
));
}
if !cron_service.is_scheduling_owner() {
// Two instances can share one user data directory. Saying so
// here stops the agent from presenting a standby list as the
// state that will actually run.
result_for_assistant.push_str(
"\n\nScheduling note: another running OpenBitFun instance owns scheduled job execution right now, so these jobs run there and edits or manual runs are refused in this instance.",
);
}

Ok(vec![ToolResult::Result {
data: json!({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,15 @@ impl PathManager {
self.user_cron_dir().join("jobs.json")
}

/// Lease file identifying the process that owns scheduled job scheduling.
///
/// It sits beside `jobs.json` because it guards exactly that store: the
/// holder is the only process allowed to schedule jobs and write the file.
/// The file itself is inert — ownership is the OS lock on it.
pub fn cron_scheduler_lease_file(&self) -> PathBuf {
self.user_cron_dir().join("scheduler.lock")
}

/// Get miniapps root directory: ~/.config/openbitfun/data/miniapps/
pub fn miniapps_dir(&self) -> PathBuf {
self.user_data_dir().join("miniapps")
Expand Down
Loading
Loading