Initial scaffold: GyanDoot v2.0 in Rust

Full Cargo workspace with 11 crates:
- gyandoot-core: Shared types, config, error types
- gyandoot-agent: Agent loop, tool execution, context compression
- gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama)
- gyandoot-memory: MemPalace persistent memory system
- gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI)
- gyandoot-security: Credential vault, sanitizer, threat detector
- gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics
- gyandoot-subagents: Sub-agent spawning and orchestration
- gyandoot-skills: Skills discovery, loading, preprocessing, bundles
- gyandoot-profile: Multi-agent profile manager
- gyandoot-gateway: Gateway sessions, routing, access control

Plus: CLI entry point with clap, cross-platform installer script,
KANBAN.md, .gnpl/AGENTS.md

Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
2026-06-11 20:04:12 +05:30
commit 851c838ca5
53 changed files with 8037 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
[package]
name = "gyandoot-subagents"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "GyanDoot v2 — Sub-agent spawning and orchestration"
[dependencies]
gyandoot-core = { path = "../gyandoot-core" }
gyandoot-providers = { path = "../gyandoot-providers" }
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
chrono.workspace = true
thiserror.workspace = true
anyhow.workspace = true
tracing.workspace = true
uuid.workspace = true
async-trait.workspace = true

View File

@@ -0,0 +1,217 @@
//! # Gyandoot Sub-Agents
//!
//! Isolated sub-agent spawning and orchestration for parallel task execution.
//! Each sub-agent runs in its own context with filtered tools and optional workdir.
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
use chrono::Utc;
use tracing::{info, error};
/// Unique identifier for a sub-agent worker.
pub type WorkerId = Uuid;
/// Status of a sub-agent task.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkerStatus {
Pending,
Running,
Completed { success: bool },
Failed(String),
}
/// Configuration for a single sub-agent worker.
#[derive(Debug, Clone)]
pub struct WorkerConfig {
pub id: WorkerId,
pub goal: String,
pub context: Option<String>,
pub allowed_tools: Option<Vec<String>>,
pub workdir: Option<String>,
pub max_iterations: usize,
}
/// Result produced by a completed sub-agent.
#[derive(Debug, Clone)]
pub struct WorkerResult {
pub worker_id: WorkerId,
pub goal: String,
pub success: bool,
pub summary: String,
pub iteration_count: usize,
pub error: Option<String>,
pub started_at: chrono::DateTime<Utc>,
pub completed_at: chrono::DateTime<Utc>,
}
/// A single sub-agent worker that runs a goal in isolation.
pub struct SubAgentWorker {
pub config: WorkerConfig,
status: Arc<Mutex<WorkerStatus>>,
}
impl SubAgentWorker {
pub fn new(goal: &str) -> Self {
Self {
config: WorkerConfig {
id: Uuid::new_v4(),
goal: goal.to_string(),
context: None,
allowed_tools: None,
workdir: None,
max_iterations: 25,
},
status: Arc::new(Mutex::new(WorkerStatus::Pending)),
}
}
pub fn with_context(mut self, ctx: &str) -> Self {
self.config.context = Some(ctx.to_string());
self
}
pub fn with_tools(mut self, tools: Vec<String>) -> Self {
self.config.allowed_tools = Some(tools);
self
}
pub fn with_workdir(mut self, wd: &str) -> Self {
self.config.workdir = Some(wd.to_string());
self
}
/// Execute the sub-agent task (simulated — real execution delegates to agent loop).
pub async fn execute(&self) -> WorkerResult {
let started_at = Utc::now();
{
let mut s = self.status.lock().await;
*s = WorkerStatus::Running;
}
info!(worker_id = %self.config.id, goal = %self.config.goal, "Sub-agent started");
// Placeholder: real logic will run an isolated AIAgent session
let success = true;
let summary = format!("Completed: {}", self.config.goal);
let iterations = 1;
{
let mut s = self.status.lock().await;
*s = WorkerStatus::Completed { success };
}
WorkerResult {
worker_id: self.config.id,
goal: self.config.goal.clone(),
success,
summary,
iteration_count: iterations,
error: None,
started_at,
completed_at: Utc::now(),
}
}
pub async fn status(&self) -> WorkerStatus {
self.status.lock().await.clone()
}
}
/// Manages concurrent sub-agent workers (max parallelism bounded).
pub struct SubAgentManager {
workers: Arc<Mutex<HashMap<WorkerId, SubAgentWorker>>>,
max_concurrent: usize,
}
impl SubAgentManager {
pub fn new(max_concurrent: usize) -> Self {
Self {
workers: Arc::new(Mutex::new(HashMap::new())),
max_concurrent,
}
}
pub async fn spawn(&self, worker: SubAgentWorker) -> WorkerId {
let id = worker.config.id;
let mut w = self.workers.lock().await;
w.insert(id, worker);
id
}
/// Execute all pending workers with bounded concurrency.
pub async fn execute_all(&self) -> Vec<WorkerResult> {
let ids: Vec<WorkerId>;
{
let w = self.workers.lock().await;
ids = w.keys().copied().collect();
}
let semaphore = Arc::new(tokio::sync::Semaphore::new(self.max_concurrent));
let mut handles = Vec::new();
for id in &ids {
let sem = semaphore.clone();
let worker: SubAgentWorker;
{
let w = self.workers.lock().await;
worker = w.get(id).unwrap().clone(); // requires Clone — see note
}
handles.push(tokio::spawn(async move {
let _permit = sem.acquire().await.unwrap();
worker.execute().await
}));
}
let mut results = Vec::new();
for h in handles {
results.push(h.await.unwrap_or_else(|e| WorkerResult {
worker_id: Uuid::nil(),
goal: "failed".into(),
success: false,
summary: String::new(),
iteration_count: 0,
error: Some(e.to_string()),
started_at: Utc::now(),
completed_at: Utc::now(),
}));
}
results
}
}
// Manual clone impl since Arc<Mutex<>> is Clone automatically
impl Clone for SubAgentWorker {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
status: Arc::clone(&self.status),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_worker_creation_and_execution() {
let worker = SubAgentWorker::new("Refactor auth module")
.with_context("Project uses JWT tokens")
.with_workdir("/tmp/test");
let result = worker.execute().await;
assert!(result.success);
assert_eq!(result.goal, "Refactor auth module");
}
#[tokio::test]
async fn test_manager_bounded_concurrency() {
let manager = SubAgentManager::new(2);
for i in 0..4 {
let w = SubAgentWorker::new(&format!("Task {}", i));
manager.spawn(w).await;
}
let results = manager.execute_all().await;
assert_eq!(results.len(), 4);
}
}