mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
* refactor(agent): update default model configuration and pricing structure - Changed the default model name in `AgentBuilder` to use a constant `DEFAULT_MODEL` instead of a hardcoded string. - Introduced new model constants (`MODEL_AGENTIC_V1`, `MODEL_CODING_V1`, `MODEL_REASONING_V1`) in `types.rs` for better clarity and maintainability. - Refactored the pricing structure in `identity_cost.rs` to utilize the new model constants, improving consistency across the pricing definitions. These changes enhance the configurability and readability of the agent's model and pricing settings. * refactor(models): update default model references and suggestions - Replaced hardcoded model names with a constant `DEFAULT_MODEL` in multiple files to enhance maintainability. - Updated model suggestions in the `TauriCommandsPanel` and `Conversations` components to reflect new model names, improving user experience and consistency across the application. These changes streamline model management and ensure that the application uses the latest model configurations. * style: fix Prettier formatting for model suggestions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(agent): introduce multi-agent harness with archetypes and task DAG - Added a new module for the multi-agent harness, defining 8 specialized archetypes (Orchestrator, Planner, CodeExecutor, SkillsAgent, ToolMaker, Researcher, Critic, Archivist) to enhance task management and execution. - Implemented a Directed Acyclic Graph (DAG) structure for task planning, allowing the Planner archetype to create and manage task dependencies. - Introduced a session queue to serialize tasks within sessions, preventing race conditions and enabling parallelism across different sessions. - Updated configuration schema to support orchestrator settings, including per-archetype configurations and maximum concurrent agents. These changes significantly improve the agent's architecture, enabling more complex task management and execution strategies. * feat(agent): implement orchestrator executor and interrupt handling - Introduced a new `executor.rs` module for orchestrated multi-agent execution, enabling a structured run loop that includes planning, executing, reviewing, and synthesizing tasks. - Added an `interrupt.rs` module to handle graceful interruptions via SIGINT and `/stop` commands, ensuring running sub-agents can be cancelled and memory flushed appropriately. - Implemented a self-healing interceptor in `self_healing.rs` to automatically create polyfill scripts for missing commands, enhancing the robustness of tool execution. - Updated the `mod.rs` file to include new modules and functionalities, improving the overall architecture of the agent harness. These changes significantly enhance the agent's capabilities in managing multi-agent workflows and handling interruptions effectively. * feat(agent): implement orchestrator executor and interrupt handling - Introduced a new `executor.rs` module for orchestrated multi-agent execution, enabling a structured run loop that includes planning, executing, reviewing, and synthesizing tasks. - Added an `interrupt.rs` module to handle graceful interruptions via SIGINT and `/stop` commands, ensuring running sub-agents are cancelled and memory is flushed. - Implemented a `SelfHealingInterceptor` in `self_healing.rs` to automatically generate polyfill scripts for missing commands, enhancing the agent's resilience. - Updated the `mod.rs` file to include new modules and functionalities, improving the overall architecture of the agent harness. These changes significantly enhance the agent's ability to manage complex tasks and respond to interruptions effectively. * feat(agent): add context assembly module for orchestrator - Introduced a new `context_assembly.rs` module to handle the assembly of the bootstrap context for the orchestrator, integrating identity files, workspace state, and relevant memory. - Implemented functions to load archetype prompts and identity contexts, enhancing the orchestrator's ability to generate a comprehensive system prompt. - Added a `BootstrapContext` struct to encapsulate the assembled context, improving the organization and clarity of context management. - Updated `mod.rs` to include the new context assembly module, enhancing the overall architecture of the agent harness. These changes significantly improve the orchestrator's context management capabilities, enabling more effective task execution and user interaction. * style: apply cargo fmt to multi-agent harness modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve merge conflict in config/mod.rs re-exports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review findings — security, correctness, observability Inline fixes: - executor: wire semaphore to enforce max_concurrent_agents cap - executor: placeholder sub-agents now return success=false - executor: halt DAG when level has failed tasks after retries - self_healing: remove overly broad "not found" pattern - session_queue: fix gc() race with acquire() via Arc::strong_count check - skills_agent.md: reference injected memory context, not memory_recall tool - init.rs: run EPISODIC_INIT_SQL during UnifiedMemory::new() - ask_clarification: make "question" param optional to match execute() default - insert_sql_record: return success=false for unimplemented stub - spawn_subagent: return success=false for unimplemented stub - run_linter: reject absolute paths and ".." in path parameter - run_tests: catch spawn/timeout errors as ToolResult, fix UTF-8 truncation - update_memory_md: add symlink escape protection, use async tokio::fs::write Nitpick fixes: - archivist: document timestamp offset intent - dag: add tracing to validate(), hoist id_map out of loop in execution_levels() - session_queue: add trace logging to acquire/gc - types: add serde(rename_all) to ReviewDecision, preserve sub-second Duration - ORCHESTRATOR.md: add escalation rule for Core handoff - read_diff: add debug logging, simplify base_str with Option::map - workspace_state: add debug logging at entry and exit - run_tests: add debug logging for runner selection and exit status Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
5.2 KiB
Rust
159 lines
5.2 KiB
Rust
//! Per-session serialised lane queue.
|
|
//!
|
|
//! All incoming tasks are serialised per-session to prevent race conditions when
|
|
//! writing to files, memory, or other shared resources. Cross-session requests
|
|
//! run concurrently.
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore};
|
|
|
|
/// A queue that serialises work within a session while allowing parallelism
|
|
/// across sessions.
|
|
///
|
|
/// Each session ID maps to a `Semaphore(1)`. Acquiring the permit blocks
|
|
/// subsequent requests for the *same* session until the permit is released.
|
|
pub struct SessionQueue {
|
|
lanes: Mutex<HashMap<String, Arc<Semaphore>>>,
|
|
}
|
|
|
|
impl SessionQueue {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
lanes: Mutex::new(HashMap::new()),
|
|
}
|
|
}
|
|
|
|
/// Acquire the lane for `session_id`.
|
|
///
|
|
/// Returns an `OwnedSemaphorePermit` that the caller must hold for the
|
|
/// duration of the request. Subsequent requests on the same session will
|
|
/// block until this permit is dropped.
|
|
pub async fn acquire(&self, session_id: &str) -> OwnedSemaphorePermit {
|
|
let sem = {
|
|
let mut map = self.lanes.lock().await;
|
|
let is_new = !map.contains_key(session_id);
|
|
let sem = map
|
|
.entry(session_id.to_string())
|
|
.or_insert_with(|| Arc::new(Semaphore::new(1)))
|
|
.clone();
|
|
if is_new {
|
|
tracing::trace!("[session-queue] created lane for session={session_id}");
|
|
}
|
|
tracing::trace!(
|
|
"[session-queue] acquiring lane session={session_id}, permits={}",
|
|
sem.available_permits()
|
|
);
|
|
sem
|
|
};
|
|
let permit = sem.acquire_owned().await.expect("session semaphore closed");
|
|
tracing::trace!("[session-queue] acquired lane for session={session_id}");
|
|
permit
|
|
}
|
|
|
|
/// Remove stale session lanes that have no waiters.
|
|
/// Call periodically or after sessions end to prevent unbounded growth.
|
|
pub async fn gc(&self) {
|
|
let mut map = self.lanes.lock().await;
|
|
let before = map.len();
|
|
map.retain(|id, sem| {
|
|
let keep = sem.available_permits() < 1 || Arc::strong_count(sem) > 1;
|
|
if !keep {
|
|
tracing::trace!("[session-queue] pruning idle lane session={id}");
|
|
}
|
|
keep
|
|
});
|
|
let removed = before - map.len();
|
|
if removed > 0 {
|
|
tracing::debug!(
|
|
"[session-queue] gc removed {removed} idle lane(s), {} remaining",
|
|
map.len()
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Number of tracked session lanes (for diagnostics).
|
|
pub async fn lane_count(&self) -> usize {
|
|
self.lanes.lock().await.len()
|
|
}
|
|
}
|
|
|
|
impl Default for SessionQueue {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use tokio::time::{sleep, Duration};
|
|
|
|
#[tokio::test]
|
|
async fn serialises_within_same_session() {
|
|
let queue = Arc::new(SessionQueue::new());
|
|
let counter = Arc::new(AtomicUsize::new(0));
|
|
|
|
let mut handles = Vec::new();
|
|
for _ in 0..5 {
|
|
let q = queue.clone();
|
|
let c = counter.clone();
|
|
handles.push(tokio::spawn(async move {
|
|
let _permit = q.acquire("session-1").await;
|
|
// If serialised, at most 1 task holds the permit at a time.
|
|
let prev = c.fetch_add(1, Ordering::SeqCst);
|
|
// While we hold the permit, sleep briefly.
|
|
sleep(Duration::from_millis(10)).await;
|
|
let current = c.load(Ordering::SeqCst);
|
|
// Nobody else should have incremented while we held the permit.
|
|
assert_eq!(current, prev + 1);
|
|
}));
|
|
}
|
|
|
|
for h in handles {
|
|
h.await.unwrap();
|
|
}
|
|
assert_eq!(counter.load(Ordering::SeqCst), 5);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn parallel_across_sessions() {
|
|
let queue = Arc::new(SessionQueue::new());
|
|
let active = Arc::new(AtomicUsize::new(0));
|
|
let max_active = Arc::new(AtomicUsize::new(0));
|
|
|
|
let mut handles = Vec::new();
|
|
for i in 0..4 {
|
|
let q = queue.clone();
|
|
let a = active.clone();
|
|
let m = max_active.clone();
|
|
let session = format!("session-{i}");
|
|
handles.push(tokio::spawn(async move {
|
|
let _permit = q.acquire(&session).await;
|
|
let current = a.fetch_add(1, Ordering::SeqCst) + 1;
|
|
m.fetch_max(current, Ordering::SeqCst);
|
|
sleep(Duration::from_millis(50)).await;
|
|
a.fetch_sub(1, Ordering::SeqCst);
|
|
}));
|
|
}
|
|
|
|
for h in handles {
|
|
h.await.unwrap();
|
|
}
|
|
// Multiple sessions should have run concurrently.
|
|
assert!(max_active.load(Ordering::SeqCst) > 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gc_removes_idle_lanes() {
|
|
let queue = SessionQueue::new();
|
|
{
|
|
let _permit = queue.acquire("temp-session").await;
|
|
}
|
|
// Permit dropped, lane is idle.
|
|
queue.gc().await;
|
|
assert_eq!(queue.lane_count().await, 0);
|
|
}
|
|
}
|