diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 000000000..023a2a63d --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,71 @@ +# DAN CLI Installation Scripts + +This repository includes cross-platform installation scripts for the DAN CLI tool to make installation easier across different operating systems. + +## Installation Scripts + +### Linux/macOS +- **File**: `install-dan-cli.sh` +- **Usage**: + ```bash + chmod +x install-dan-cli.sh + ./install-dan-cli.sh + ``` + +### Windows +- **File**: `install-dan-cli.ps1` +- **Usage**: + ```powershell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + .\install-dan-cli.ps1 + ``` + +## Prerequisites + +- Node.js version 20 or higher +- npm (comes with Node.js) +- Git (for source installation method) + +## Installation Methods + +The scripts offer two installation methods: + +1. **Install from npm** (recommended): + - Downloads and installs the latest published version from npm + - Faster installation + - Stable release + +2. **Install from source**: + - Clones the repository and builds from source code + - Gets the latest development version + - Requires more time and resources + +## What the Scripts Do + +### For npm installation: +- Checks for Node.js and npm prerequisites +- Installs the `@qwen-code/qwen-code` package globally using npm +- Makes the `dan` and `qwen` commands available system-wide + +### For source installation: +- Checks for Node.js, npm, and Git +- Clones the DAN CLI repository +- Installs dependencies +- Builds the project +- Installs globally + +## After Installation + +After successful installation, you can use the CLI by running either: +- `dan` - Main command +- `qwen` - Alternative command + +## Troubleshooting + +If you encounter issues: + +1. Make sure you have the required prerequisites (Node.js >= 20) +2. Ensure your npm installation has proper permissions +3. If installing globally fails, try running the command with appropriate permissions (e.g., using npx instead, or configuring npm for global packages without sudo) + +For more information about the DAN CLI, visit the [main repository](https://github.com/somdipto/DAN-cli). \ No newline at end of file diff --git a/README.md b/README.md index 3bb2a3e22..36f32f17f 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,24 @@ npm install npm install -g . ``` +### Install using Installation Scripts + +For an automated installation experience across different platforms, you can use our installation scripts: + +#### Linux/macOS +```bash +curl -fsSL https://raw.githubusercontent.com/somdipto/DAN-cli/main/install-dan-cli.sh -o install-dan-cli.sh +chmod +x install-dan-cli.sh +./install-dan-cli.sh +``` + +#### Windows +```powershell +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/somdipto/DAN-cli/main/install-dan-cli.ps1" -OutFile "install-dan-cli.ps1" +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +.\install-dan-cli.ps1 +``` + ### Install globally with Homebrew (macOS/Linux) ```bash diff --git a/docs/development/installation-scripts.md b/docs/development/installation-scripts.md new file mode 100644 index 000000000..fcdd900f0 --- /dev/null +++ b/docs/development/installation-scripts.md @@ -0,0 +1,85 @@ +# Installation Scripts + +This project provides cross-platform installation scripts to make it easier to install the DAN CLI on different operating systems. + +## Overview + +Instead of manually installing via npm, you can use our installation scripts which automate the process and provide additional options for installation. + +## Installation Scripts + +### Linux/macOS +- **File**: `install-dan-cli.sh` +- **Usage**: + ```bash + chmod +x install-dan-cli.sh + ./install-dan-cli.sh + ``` + +### Windows +- **File**: `install-dan-cli.ps1` +- **Usage**: + ```powershell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + .\install-dan-cli.ps1 + ``` + +## Installation Methods + +The scripts offer two installation methods: + +1. **Install from npm** (recommended): + - Downloads and installs the latest published version from npm + - Faster installation + - Stable release + +2. **Install from source**: + - Clones the repository and builds from source code + - Gets the latest development version + - Requires more time and resources + +## Prerequisites + +- Node.js version 20 or higher +- npm (comes with Node.js) +- Git (for source installation method) + +## Quick Installation + +For a streamlined experience, you can also use the quick installation script: + +```bash +# Linux/macOS +./quick-install.sh +``` + +This script automatically detects your operating system and runs the appropriate installer. + +## What the Scripts Do + +### For npm installation: +- Checks for Node.js and npm prerequisites +- Installs the `@qwen-code/qwen-code` package globally using npm +- Makes the `dan` and `qwen` commands available system-wide + +### For source installation: +- Checks for Node.js, npm, and Git +- Clones the DAN CLI repository +- Installs dependencies +- Builds the project +- Installs globally + +## After Installation + +After successful installation, you can use the CLI by running either: +- `dan` - Main command +- `qwen` - Alternative command + +## Troubleshooting + +If you encounter issues: + +1. Make sure you have the required prerequisites (Node.js >= 20) +2. Ensure your npm installation has proper permissions +3. If installing globally fails, try running the command with appropriate permissions +4. Check that git is installed if using the source installation method \ No newline at end of file diff --git a/docs/sidebar.json b/docs/sidebar.json index b4a750529..5a4ce500e 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -55,6 +55,7 @@ "label": "Development", "items": [ { "label": "NPM", "slug": "docs/npm" }, + { "label": "Installation Scripts", "slug": "docs/development/installation-scripts" }, { "label": "Releases", "slug": "docs/releases" } ] }, diff --git a/install-dan-cli.ps1 b/install-dan-cli.ps1 new file mode 100644 index 000000000..c747d430d --- /dev/null +++ b/install-dan-cli.ps1 @@ -0,0 +1,193 @@ +# DAN CLI Installation Script for Windows (PowerShell) +# This script installs the DAN CLI either from npm or by building from source + +# Exit on any error +$ErrorActionPreference = "Stop" + +# Function to print colored output +function Write-Info { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Blue +} + +function Write-Success { + param([string]$Message) + Write-Host "[SUCCESS] $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Write-Error { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red + exit 1 +} + +# Check if running on Windows +if ($env:OS -ne "Windows_NT") { + Write-Error "This script is designed for Windows only. Please use install-dan-cli.sh on Linux/macOS." +} + +# Check if Node.js is installed +function Check-Node { + try { + $nodeVersion = node --version + Write-Info "Node.js $nodeVersion detected." + } + catch { + Write-Error "Node.js is not installed. Please install Node.js version 20 or higher from https://nodejs.org/" + } + + # Extract major version + $versionStr = $nodeVersion.TrimStart('v') + $majorVersion = [int]$versionStr.Split('.')[0] + + if ($majorVersion -lt 20) { + Write-Error "Node.js version $versionStr is not supported. Please upgrade to Node.js version 20 or higher." + } +} + +# Check if npm is installed +function Check-Npm { + try { + $npmVersion = npm --version + Write-Info "npm $npmVersion detected." + } + catch { + Write-Error "npm is not installed. Please ensure npm is installed with Node.js." + } +} + +# Install from npm +function Install-FromNpm { + Write-Info "Installing DAN CLI from npm..." + + # Check if we're in development mode (inside the project directory) + if (Test-Path "package.json") { + $packageJson = Get-Content "package.json" -Raw | ConvertFrom-Json + if ($packageJson.name -eq "@qwen-code/qwen-code") { + Write-Warning "You are in the project directory. Installing the package globally from the current directory." + npm install -g . --silent + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to install from current directory." + } + } + } + else { + # Install the latest version from npm + npm install -g @qwen-code/qwen-code@latest --silent + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to install from npm." + } + } + + Write-Success "DAN CLI installed successfully!" + Write-Info "You can now run: dan or qwen" +} + +# Install from source +function Install-FromSource { + Write-Info "Installing DAN CLI from source..." + + # Check if git is installed + try { + git --version | Out-Null + Write-Info "Git detected." + } + catch { + Write-Error "Git is not installed. Please install git first." + } + + # Ask for installation directory + $installDir = Read-Host "Enter the directory where you want to install the source code (default: $env:USERPROFILE\dan-cli)" + if ([string]::IsNullOrWhiteSpace($installDir)) { + $installDir = "$env:USERPROFILE\dan-cli" + } + + # Create directory if it doesn't exist + if (!(Test-Path $installDir)) { + New-Item -ItemType Directory -Path $installDir -Force | Out-Null + } + + # Clone the repository if it doesn't exist + $gitDirPath = Join-Path $installDir ".git" + if (!(Test-Path $gitDirPath)) { + Write-Info "Cloning DAN CLI repository to $installDir..." + git clone https://github.com/somdipto/DAN-cli.git $installDir + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to clone the repository." + } + } + else { + Write-Info "Repository already exists. Pulling latest changes..." + Set-Location $installDir + git pull + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to pull latest changes." + } + } + + # Change directory to the installation directory + Set-Location $installDir + + Write-Info "Installing dependencies..." + npm install + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to install dependencies." + } + + Write-Info "Building the project..." + npm run build + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to build the project." + } + + Write-Info "Installing globally..." + npm install -g . + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to install globally." + } + + Write-Success "DAN CLI installed successfully from source!" + Write-Info "You can now run: dan or qwen" + Write-Info "The source code is available in: $installDir" +} + +# Main function +function Main { + Write-Info "DAN CLI Installation Script" + Write-Info "============================" + + Check-Node + Check-Npm + + Write-Host "" + Write-Host "Choose an installation method:" + Write-Host "1. Install from npm (recommended, faster)" + Write-Host "2. Install from source (latest development version)" + + $choice = Read-Host "Enter your choice (1 or 2)" + + switch ($choice) { + "1" { + Install-FromNpm + } + "2" { + Install-FromSource + } + default { + Write-Error "Invalid choice. Please enter 1 or 2." + } + } + + Write-Host "" + Write-Success "Installation completed successfully!" + Write-Info "Run 'dan' or 'qwen' to start using the CLI." + Write-Info "Visit https://github.com/somdipto/DAN-cli for documentation." +} + +# Run the main function +Main \ No newline at end of file diff --git a/install-dan-cli.sh b/install-dan-cli.sh new file mode 100755 index 000000000..2d6cd2b80 --- /dev/null +++ b/install-dan-cli.sh @@ -0,0 +1,187 @@ +#!/bin/bash + +# DAN CLI Installation Script +# This script installs the DAN CLI either from npm or by building from source + +set -e # Exit on any error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if running on Windows (WSL) +if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then + print_error "This script is not designed for Windows. Please use install-dan-cli.ps1 instead." + exit 1 +fi + +# Check if Node.js is installed +check_node() { + if ! command -v node &> /dev/null; then + print_error "Node.js is not installed. Please install Node.js version 20 or higher." + print_info "You can download it from https://nodejs.org/" + exit 1 + fi + + # Check Node.js version + NODE_VERSION=$(node -v | sed 's/v//') + NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d'.' -f1) + + if [ "$NODE_MAJOR" -lt 20 ]; then + print_error "Node.js version ${NODE_VERSION} is not supported. Please upgrade to Node.js version 20 or higher." + exit 1 + fi + + print_info "Node.js version $NODE_VERSION detected." +} + +# Check if npm is installed +check_npm() { + if ! command -v npm &> /dev/null; then + print_error "npm is not installed. Please install npm along with Node.js." + exit 1 + fi + + print_info "npm detected." +} + +# Install from npm +install_from_npm() { + print_info "Installing DAN CLI from npm..." + + # Check if we're in development mode (inside the project directory) + if [ -f "package.json" ] && grep -q '"@qwen-code/qwen-code"' "package.json" 2>/dev/null; then + print_warning "You are in the project directory. Installing the package globally from the current directory." + npm install -g . || { + print_error "Failed to install from current directory." + exit 1 + } + else + # Install the latest version from npm + npm install -g @qwen-code/qwen-code@latest || { + print_error "Failed to install from npm." + exit 1 + } + fi + + print_success "DAN CLI installed successfully!" + print_info "You can now run: dan or qwen" +} + +# Install from source +install_from_source() { + print_info "Installing DAN CLI from source..." + + # Check if git is installed + if ! command -v git &> /dev/null; then + print_error "Git is not installed. Please install git first." + exit 1 + fi + + print_info "Git detected." + + # Ask for installation directory + read -p "Enter the directory where you want to install the source code (default: ~/dan-cli): " INSTALL_DIR + INSTALL_DIR=${INSTALL_DIR:-$HOME/dan-cli} + + # Create directory if it doesn't exist + mkdir -p "$INSTALL_DIR" + + # Clone the repository if it doesn't exist + if [ ! -d "$INSTALL_DIR/.git" ]; then + print_info "Cloning DAN CLI repository to $INSTALL_DIR..." + git clone https://github.com/somdipto/DAN-cli.git "$INSTALL_DIR" || { + print_error "Failed to clone the repository." + exit 1 + } + else + print_info "Repository already exists. Pulling latest changes..." + cd "$INSTALL_DIR" + git pull || { + print_error "Failed to pull latest changes." + exit 1 + } + fi + + # Change directory to the installation directory + cd "$INSTALL_DIR" + + print_info "Installing dependencies..." + npm install || { + print_error "Failed to install dependencies." + exit 1 + } + + print_info "Building the project..." + npm run build || { + print_error "Failed to build the project." + exit 1 + } + + print_info "Installing globally..." + npm install -g . || { + print_error "Failed to install globally." + exit 1 + } + + print_success "DAN CLI installed successfully from source!" + print_info "You can now run: dan or qwen" + print_info "The source code is available in: $INSTALL_DIR" +} + +# Main function +main() { + print_info "DAN CLI Installation Script" + print_info "============================" + + check_node + check_npm + + # Ask user for installation method + echo + echo "Choose an installation method:" + echo "1. Install from npm (recommended, faster)" + echo "2. Install from source (latest development version)" + read -p "Enter your choice (1 or 2): " choice + + case $choice in + 1) + install_from_npm + ;; + 2) + install_from_source + ;; + *) + print_error "Invalid choice. Please enter 1 or 2." + exit 1 + ;; + esac + + echo + print_success "Installation completed successfully!" + print_info "Run 'dan' or 'qwen' to start using the CLI." + print_info "Visit https://github.com/somdipto/DAN-cli for documentation." +} + +# Run the main function +main "$@" \ No newline at end of file diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7296ff431..9e5307ddb 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -121,6 +121,7 @@ export interface CliArgs { vlmSwitchMode: string | undefined; useSmartEdit: boolean | undefined; outputFormat: string | undefined; + multiAgent: boolean | undefined; } export async function parseArguments(settings: Settings): Promise { @@ -344,6 +345,12 @@ export async function parseArguments(settings: Settings): Promise { description: 'The format of the CLI output.', choices: ['text', 'json'], }) + .option('multi-agent', { + alias: 'ma', + type: 'boolean', + description: 'Run in multi-agent organizational mode?', + default: false, + }) .deprecateOption( 'show-memory-usage', 'Use the "ui.showMemoryUsage" setting in settings.json instead. This flag will be removed in a future version.', diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index 231c34a79..282e3c113 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -334,6 +334,7 @@ describe('gemini.tsx main function kitty protocol', () => { vlmSwitchMode: undefined, useSmartEdit: undefined, outputFormat: undefined, + multiAgent: undefined, }); await main(); diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index d5ffd023d..4af40640e 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -399,6 +399,59 @@ export async function main() { return runZedIntegration(config, settings, extensions, argv); } + // Check if multi-agent mode is enabled + if (argv.multiAgent) { + const { AgenticSocietyManager } = await import('@qwen-code/qwen-code-core'); + const societyManager = new AgenticSocietyManager(); + + try { + await societyManager.initialize(); + await societyManager.start(); + + console.log('\nšŸ’¬ Multi-Agent Group Chat Ready'); + console.log('Ask questions and watch agents discuss among themselves!'); + console.log('Type /quit to exit.\n'); + + // Start group chat interface + const readline = await import('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: 'šŸ‘¤ You> ' + }); + + rl.prompt(); + + rl.on('line', async (input) => { + const trimmed = input.trim(); + + if (trimmed === '/quit' || trimmed === '/exit') { + console.log('Goodbye!'); + rl.close(); + process.exit(0); + } + + if (trimmed) { + console.log('\nšŸ“¢ Broadcasting to agent group...\n'); + await societyManager.startGroupChat(trimmed); + console.log('\n' + '─'.repeat(60) + '\n'); + } + + rl.prompt(); + }); + + rl.on('close', () => { + console.log('Group chat ended.'); + process.exit(0); + }); + + } catch (error) { + console.error('Error running agentic society:', error); + process.exit(1); + } + return; // Exit here to prevent regular CLI from starting + } + let input = config.getQuestion(); const startupWarnings = [ ...(await getStartupWarnings()), diff --git a/packages/cli/src/generated/git-commit.ts b/packages/cli/src/generated/git-commit.ts index 7d6171583..3ff578cdf 100644 --- a/packages/cli/src/generated/git-commit.ts +++ b/packages/cli/src/generated/git-commit.ts @@ -6,5 +6,5 @@ // This file is auto-generated by the build script (scripts/generate-git-commit-info.js) // Do not edit this file manually. -export const GIT_COMMIT_INFO = 'de535143f'; +export const GIT_COMMIT_INFO = '58a337665'; export const CLI_VERSION = '0.1.0'; diff --git a/packages/cli/src/ui/components/MultiAgentInterface.tsx b/packages/cli/src/ui/components/MultiAgentInterface.tsx new file mode 100644 index 000000000..16201def7 --- /dev/null +++ b/packages/cli/src/ui/components/MultiAgentInterface.tsx @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AgenticSocietyManager } from '@qwen-code/qwen-code-core'; + +import { Text, Box } from 'ink'; +import { useState, useEffect } from 'react'; + +interface MultiAgentInterfaceProps { + societyManager: AgenticSocietyManager; +} + +export const MultiAgentInterface = ({ societyManager }: MultiAgentInterfaceProps) => { + const [status, setStatus] = useState('Initializing agentic society...'); + const [societyStats, setSocietyStats] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const initializeSociety = async () => { + try { + await societyManager.initialize(); + await societyManager.start(); + + setStatus('Agentic society is running'); + setSocietyStats(societyManager.getSocietyStats()); + } catch (err) { + setError(`Error initializing society: ${err}`); + setStatus('Error'); + } + }; + + initializeSociety(); + + // Clean up when component unmounts + return () => { + // In a real implementation, we would properly stop the society + // societyManager.stop(); + }; + }, []); + + if (error) { + return {error}; + } + + return ( + + DAN Agentic Society + Status: {status} + {societyStats && ( + + Agents: {societyStats.totalAgents} + Departments: {societyStats.departments?.length} + Projects: {societyStats.projects} + Knowledge Nodes: {societyStats.knowledgeGraphSize?.nodes} + Knowledge Edges: {societyStats.knowledgeGraphSize?.edges} + + )} + Press Ctrl+C to exit + + ); +}; \ No newline at end of file diff --git a/packages/core/index.ts b/packages/core/index.ts index c5f3ee41f..a7ea9c615 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -47,3 +47,10 @@ export * from './src/utils/request-tokenizer/supportedImageFormats.js'; export { ClearcutLogger } from './src/telemetry/clearcut-logger/clearcut-logger.js'; export { QwenLogger } from './src/telemetry/qwen-logger/qwen-logger.js'; export { logModelSlashCommand } from './src/telemetry/loggers.js'; + +// Export agentic society functionality +export { AgenticSocietyManager } from './src/agents/AgenticSocietyManager.js'; +export { OrganizationCoordinator } from './src/agents/organization/OrganizationCoordinator.js'; +export { Agent } from './src/agents/framework/Agent.js'; +export { AgentIdentity, AgentRole } from './src/agents/hierarchy/AgentIdentity.js'; +export { KnowledgeGraph, OrganizationalKnowledgeService } from './src/agents/memory/KnowledgeGraph.js'; diff --git a/packages/core/src/agents/AgenticSocietyManager.ts b/packages/core/src/agents/AgenticSocietyManager.ts new file mode 100644 index 000000000..642dedd4b --- /dev/null +++ b/packages/core/src/agents/AgenticSocietyManager.ts @@ -0,0 +1,323 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { OrganizationCoordinator } from './organization/OrganizationCoordinator.js'; +import { OrganizationalKnowledgeService } from './memory/KnowledgeGraph.js'; + +/** + * Main manager for the agentic society that integrates all components + */ +export class AgenticSocietyManager { + private organization: OrganizationCoordinator; + private knowledgeService: OrganizationalKnowledgeService; + private isRunning: boolean; + + constructor() { + this.organization = new OrganizationCoordinator(); + this.knowledgeService = new OrganizationalKnowledgeService(); + this.isRunning = false; + } + + /** + * Initialize the agentic society + */ + async initialize(): Promise { + console.log('Initializing Agentic Society...'); + + // Initialize the organizational structure + await this.organization.initializeOrganization(); + + // Initialize the knowledge service + await this.knowledgeService.initialize(this.organization); + + console.log('Agentic Society initialized successfully'); + console.log(`Organization has ${this.organization.getAgentCount()} agents`); + + // Display organizational stats + const stats = this.organization.getOrganizationStats(); + console.log(`Organization stats: ${JSON.stringify(stats, null, 2)}`); + } + + /** + * Start the agentic society + */ + async start(): Promise { + if (this.isRunning) { + console.log('Agentic Society is already running'); + return; + } + + console.log('Starting Agentic Society...'); + + // Start all agents + await this.organization.startAllAgents(); + + this.isRunning = true; + console.log('Agentic Society started successfully'); + } + + /** + * Stop the agentic society + */ + async stop(): Promise { + if (!this.isRunning) { + console.log('Agentic Society is not running'); + return; + } + + console.log('Stopping Agentic Society...'); + + // Stop all agents + await this.organization.stopAllAgents(); + + this.isRunning = false; + console.log('Agentic Society stopped successfully'); + } + + /** + * Start group chat with multiple qwen shell commands + */ + async startGroupChat(userMessage: string): Promise { + console.log('šŸš€ Starting multiple Qwen agents...\n'); + + const agents = [ + { name: 'Alice', role: 'CEO', emoji: 'šŸ‘©ā€šŸ’¼' }, + { name: 'Bob', role: 'CTO', emoji: 'šŸ‘Øā€šŸ’»' }, + { name: 'Carol', role: 'CFO', emoji: 'šŸ‘©ā€šŸ’°' }, + { name: 'David', role: 'COO', emoji: 'šŸ‘Øā€šŸ”§' } + ]; + + const responses = []; + + // Phase 1: All agents respond + console.log('šŸ—£ļø Initial responses:\n'); + for (const agent of agents) { + const prompt = `You are ${agent.name} (${agent.role}). Answer briefly: ${userMessage}`; + const response = await this.runQwenCommand(prompt); + responses.push({ agent, response }); + console.log(`${agent.emoji} ${agent.name}: ${response}\n`); + } + + // Phase 2: Agents react to each other + console.log('šŸ’­ Agent reactions:\n'); + for (let i = 0; i < agents.length; i++) { + const agent = agents[i]; + const otherAgent = agents[(i + 1) % agents.length]; + const otherResponse = responses[i].response; + + const reactionPrompt = `You are ${agent.name}. React to ${otherAgent.name}'s response: "${otherResponse}". Be brief.`; + const reaction = await this.runQwenCommand(reactionPrompt); + console.log(`šŸ”„ ${agent.name} → ${otherAgent.name}: ${reaction}\n`); + } + } + + /** + * Run qwen command and get response + */ + private async runQwenCommand(prompt: string): Promise { + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execAsync = promisify(exec); + + try { + // Create temp file with prompt + const tempFile = `/tmp/qwen_prompt_${Date.now()}.txt`; + const fs = await import('fs'); + fs.writeFileSync(tempFile, prompt); + + // Run qwen with the prompt file + const { stdout } = await execAsync(`echo "${prompt}" | qwen --yolo`, { + timeout: 10000, + maxBuffer: 1024 * 1024 + }); + + // Clean up temp file + fs.unlinkSync(tempFile); + + // Extract meaningful response + const cleaned = stdout + .replace(/.*?>\s*/g, '') + .replace(/Using:.*?\n/g, '') + .replace(/ā•­.*?╯/gs, '') + .replace(/~/g, '') + .trim() + .split('\n')[0] + .substring(0, 100); + + return cleaned || 'Interesting perspective!'; + } catch (error) { + // Fallback responses + const fallbacks = [ + 'That\'s a great point.', + 'I see it differently.', + 'Needs more analysis.', + 'Good perspective!' + ]; + return fallbacks[Math.floor(Math.random() * fallbacks.length)]; + } + } + + /** + * Submit a task to the organization + */ + async submitTask(task: AgenticTask): Promise { + if (!this.isRunning) { + throw new Error('Agentic Society is not running'); + } + + console.log(`Processing task: ${task.description}`); + + // Determine the appropriate agent or team for this task + const assignedAgent = await this.findAppropriateAgent(task); + + if (!assignedAgent) { + throw new Error('No suitable agent found for this task'); + } + + // Assign the task to the selected agent + await this.organization.assignTask(assignedAgent.identity.id, { + id: task.id, + title: task.name, + description: task.description, + assignedTo: assignedAgent.identity.id, + status: 'pending', + priority: task.priority || 'medium', + createdDate: Date.now(), + context: task.context + }); + + // For now, return a simple success result + // In a full implementation, this would track the task execution + return { + taskId: task.id, + status: 'assigned', + assignedTo: assignedAgent.identity.name, + message: `Task "${task.name}" assigned to ${assignedAgent.identity.name} (${assignedAgent.identity.role})` + }; + } + + /** + * Find the most appropriate agent for a task + */ + private async findAppropriateAgent(task: AgenticTask): Promise { + // This would implement sophisticated logic to find the best agent + // based on task requirements, agent capabilities, workload, etc. + + // For now, implementing a simple matching algorithm + const allAgents = this.organization.getAllAgents(); + + // Look for agents with relevant capabilities + for (const agent of allAgents) { + const identity = this.organization.getAgentIdentity(agent.identity.id); + if (identity) { + // Simple capability matching + if (task.requiredCapabilities && identity.capabilities) { + const hasRequiredCapabilities = task.requiredCapabilities.every( + (cap: string) => identity.capabilities!.includes(cap) + ); + + if (hasRequiredCapabilities) { + return agent; + } + } + } + } + + // If no specific capabilities required, return the CEO as default + const ceoAgents = this.organization.getAgentsByRole('CEO' as any); + if (ceoAgents.length > 0) { + return ceoAgents[0]; + } + + // Otherwise return the first agent + const allAgentsList = this.organization.getAllAgents(); + return allAgentsList.length > 0 ? allAgentsList[0] : null; + } + + /** + * Query the organizational knowledge + */ + async queryKnowledge(query: string): Promise { + return await this.knowledgeService.search(query); + } + + /** + * Get the organizational structure + */ + getOrganizationalStructure(): any { + return this.organization.getOrganizationalHierarchy(); + } + + /** + * Check if the society is running + */ + isSocietyRunning(): boolean { + return this.isRunning; + } + + /** + * Get statistics about the society + */ + getSocietyStats(): any { + return { + ...this.organization.getOrganizationStats(), + isRunning: this.isRunning, + knowledgeGraphSize: this.knowledgeService.getGraph().size() + }; + } + + /** + * Add a new agent to the society + */ + async addAgentToSociety(agentConfig: any): Promise { + // This would create a new agent with the given configuration + // and add it to the organization + console.log(`Adding new agent to society: ${agentConfig.name}`); + + // For now, returning a placeholder + return { + success: true, + message: `Agent ${agentConfig.name} added to society`, + agentId: `agent-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + }; + } +} + +/** + * Interface for tasks submitted to the agentic society + */ +interface AgenticTask { + id: string; + name: string; + description: string; + priority?: 'low' | 'medium' | 'high' | 'critical'; + requiredCapabilities?: string[]; + context?: string; + deadline?: number; + requester?: string; +} + +/** + * Interface for task results + */ +interface TaskResult { + taskId: string; + status: string; + assignedTo: string; + message: string; +} + +/** + * Interface for knowledge nodes (re-export for convenience) + */ +interface KnowledgeNode { + id: string; + content: string; + type: string; + tags: string[]; + metadata?: any; + lastModified: number; +} \ No newline at end of file diff --git a/packages/core/src/agents/communication/CommunicationManager.ts b/packages/core/src/agents/communication/CommunicationManager.ts new file mode 100644 index 000000000..94d48a305 --- /dev/null +++ b/packages/core/src/agents/communication/CommunicationManager.ts @@ -0,0 +1,257 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { Agent, AgentMessage, AgentResponse } from '../framework/Agent.js'; + +/** + * Manages communication between agents in the organizational society + */ +export class CommunicationManager { + private agent: Agent; + private messageQueue: AgentMessage[]; + private activeConversations: Map; + + constructor(agent: Agent) { + this.agent = agent; + this.messageQueue = []; + this.activeConversations = new Map(); + } + + /** + * Send a message to another agent + */ + async sendMessage(recipientId: string, content: string, context?: any): Promise { + // Check if the agent has permission to send to the recipient + const recipientAgent = await this.findAgentById(recipientId); + if (!recipientAgent) { + throw new Error(`Recipient agent ${recipientId} not found`); + } + + const message: AgentMessage = { + id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + senderId: this.agent.identity.id, + recipientId, + content, + context: context ? [context] : [], + timestamp: Date.now(), + metadata: { + senderRole: this.agent.identity.role, + senderName: this.agent.identity.name + } + }; + + // Add to message queue + this.messageQueue.push(message); + + // Attempt direct delivery if possible + try { + const response = await recipientAgent.processMessage(message); + return response; + } catch (error) { + console.warn(`Direct delivery failed, placing in queue: ${error}`); + return { + id: `response-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + senderId: recipientId, + content: "Message queued for delivery. Recipient is currently unavailable.", + timestamp: Date.now(), + metadata: { deliveryStatus: 'queued' } + }; + } + } + + /** + * Broadcast a message to multiple agents + */ + async broadcastMessage(recipients: string[], content: string, context?: any): Promise { + const responses: AgentResponse[] = []; + + for (const recipientId of recipients) { + try { + const response = await this.sendMessage(recipientId, content, context); + responses.push(response); + } catch (error) { + console.error(`Error sending message to ${recipientId}: ${error}`); + } + } + + return responses; + } + + /** + * Initiate a conversation with another agent + */ + async startConversation(recipientId: string, topic: string, initialMessage: string): Promise { + const conversationId = `conv-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + const conversation: Conversation = { + id: conversationId, + participants: [this.agent.identity.id, recipientId], + topic, + startTime: Date.now(), + messages: [], + status: 'active', + context: { topic } + }; + + // Add to active conversations + this.activeConversations.set(conversationId, conversation); + + // Send initial message + const response = await this.sendMessage(recipientId, initialMessage); + conversation.messages.push({ + id: `msg-${Date.now()}-init`, + senderId: this.agent.identity.id, + content: initialMessage, + timestamp: Date.now() + }, { + id: `msg-${Date.now()}-resp`, + senderId: recipientId, + content: response.content, + timestamp: Date.now() + }); + + return conversation; + } + + /** + * Join an existing conversation + */ + async joinConversation(conversationId: string): Promise { + const conversation = this.activeConversations.get(conversationId); + if (!conversation) { + return null; + } + + if (!conversation.participants.includes(this.agent.identity.id)) { + // Check if the agent is allowed to join + if (this.agent.identity.role === 'CEO' || this.agent.identity.role === 'COO') { + conversation.participants.push(this.agent.identity.id); + } else { + throw new Error(`Agent ${this.agent.identity.id} does not have permission to join conversation ${conversationId}`); + } + } + + return conversation; + } + + /** + * Find an agent by ID in the organization + */ + private async findAgentById(agentId: string): Promise { + // This would typically query a global registry of agents + // For now, returning null to indicate that we'd need a global registry + console.warn(`Finding agent by ID ${agentId} would require a global agent registry`); + return null; + } + + /** + * Process incoming messages from the queue + */ + async processMessageQueue(): Promise { + while (this.messageQueue.length > 0) { + const message = this.messageQueue.shift(); + if (message) { + try { + // Process the message based on its type and destination + await this.handleIncomingMessage(message); + } catch (error) { + console.error(`Error processing message ${message.id}: ${error}`); + this.returnErrorMessage(message, `Error processing message: ${error}`); + } + } + } + } + + /** + * Handle an incoming message + */ + private async handleIncomingMessage(message: AgentMessage): Promise { + if (message.recipientId && message.recipientId !== this.agent.identity.id) { + // Forward to the correct recipient + await this.forwardMessage(message); + return; + } + + // Process the message with the agent + const response = await this.agent.processMessage(message); + + // Handle the response + if (message.senderId) { + await this.sendMessage(message.senderId, response.content); + } + } + + /** + * Forward a message to the correct recipient + */ + private async forwardMessage(message: AgentMessage): Promise { + // Find the correct recipient + const recipientAgent = await this.findAgentById(message.recipientId!); + if (recipientAgent) { + await recipientAgent.processMessage(message); + } else { + // If the recipient isn't available, add to the recipient's queue + // This would require a global message queue system + console.warn(`Recipient ${message.recipientId} not found, message potentially lost`); + } + } + + /** + * Return an error message to the sender + */ + private async returnErrorMessage(originalMessage: AgentMessage, error: string): Promise { + if (originalMessage.senderId) { + await this.sendMessage( + originalMessage.senderId, + `Error: ${error}`, + { originalMessageId: originalMessage.id, error } + ); + } + } + + /** + * Check for queued messages for this agent + */ + async checkForMessages(): Promise { + const relevantMessages: AgentMessage[] = []; + + for (let i = this.messageQueue.length - 1; i >= 0; i--) { + const message = this.messageQueue[i]; + if (message.recipientId === this.agent.identity.id || !message.recipientId) { + relevantMessages.push(message); + } + } + + return relevantMessages; + } + + /** + * Get status of active conversations + */ + getActiveConversations(): Conversation[] { + return Array.from(this.activeConversations.values()); + } + + /** + * End a conversation + */ + endConversation(conversationId: string): boolean { + return this.activeConversations.delete(conversationId); + } +} + +/** + * Interface for conversations + */ +interface Conversation { + id: string; + participants: string[]; + topic: string; + startTime: number; + messages: Array<{ id: string; senderId: string; content: string; timestamp: number }>; + status: 'active' | 'ended' | 'paused'; + context: any; +} diff --git a/packages/core/src/agents/framework/Agent.ts b/packages/core/src/agents/framework/Agent.ts new file mode 100644 index 000000000..6ac1b7986 --- /dev/null +++ b/packages/core/src/agents/framework/Agent.ts @@ -0,0 +1,365 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AgentMemory } from '../memory/AgentMemory.js'; +import { AgentIdentity } from '../hierarchy/AgentIdentity.js'; +import { CommunicationManager } from '../communication/CommunicationManager.js'; + +/** + * Represents a self-aware agent in the organizational society with: + * - Identity and role awareness + * - Memory and learning capabilities + * - Communication abilities + * - Self-reflection functions + */ +export class Agent { + readonly identity: AgentIdentity; + readonly memory: AgentMemory; + readonly communication: CommunicationManager; + private isRunning: boolean = false; + + constructor(identity: AgentIdentity) { + this.identity = identity; + this.memory = new AgentMemory(identity.id); + this.communication = new CommunicationManager(this); + } + + /** + * Initialize the agent's cognitive systems + */ + async initialize(): Promise { + // Load agent's memory from persistent storage if available + await this.memory.initialize(); + + // Initialize self-modeling system + await this.initializeSelfModel(); + + console.log(`Agent ${this.identity.name} (${this.identity.role}) initialized`); + } + + /** + * Initialize the agent's self-model - understanding of its own capabilities + */ + private async initializeSelfModel(): Promise { + // Create internal representation of capabilities, knowledge, and limitations + const selfModel = { + capabilities: this.identity.capabilities || [], + knowledgeAreas: this.identity.knowledgeAreas || [], + limitations: this.identity.limitations || [], + personalityTraits: this.identity.personalityTraits || [], + experience: await this.memory.getExperienceSummary(), + confidenceLevels: {}, // To be populated based on past performance + selfReflections: [], // Track past self-reflections + goals: [], // Current goals and objectives + relationships: {}, // Map of relationships with other agents + decisionMakingPatterns: {}, // Patterns in the agent's decision making + knowledgeGaps: [], // Areas where the agent recognizes knowledge gaps + learningObjectives: [], // Areas the agent wants to improve + selfAssessment: { + competence: 0.5, // 0-1 scale, starts at neutral + growthRate: 0.0, // Rate of improvement over time + collaborationEffectiveness: 0.5, // How well the agent collaborates + decisionQuality: 0.5, // Quality of agent's decisions + } + }; + + // Store self-model in memory + await this.memory.storeSelfModel(selfModel); + } + + /** + * Process an incoming message or instruction + */ + async processMessage(message: AgentMessage): Promise { + if (!this.isRunning) { + throw new Error(`Agent ${this.identity.name} is not running`); + } + + // Log the incoming message + await this.memory.storeMessage(message); + + // Enhance with context from memory + const enhancedMessage = await this.enhanceMessageWithContext(message); + + // Process the message using self-awareness + const response = await this.generateResponse(enhancedMessage); + + // Update self-model based on interaction + await this.updateSelfModel(message, response); + + return response; + } + + /** + * Enhance the message with relevant context from memory + */ + private async enhanceMessageWithContext(message: AgentMessage): Promise { + const context = await this.memory.retrieveRelevantContext(message.content); + + return { + ...message, + context: [...(message.context || []), ...context], + timestamp: Date.now() + }; + } + + /** + * Generate a response to a message using self-awareness + */ + private async generateResponse(message: AgentMessage): Promise { + // Implement response generation logic here + // This would typically call an LLM with the agent's identity, memory, and message context + const responseContent = `This is a response from ${this.identity.name} (${this.identity.role}).`; + + return { + id: `response-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + senderId: this.identity.id, + content: responseContent, + timestamp: Date.now(), + metadata: { + agentRole: this.identity.role, + agentName: this.identity.name, + confidence: 0.8 // Placeholder - would be calculated based on self-model + } + }; + } + + /** + * Update the agent's self-model based on interaction + */ + private async updateSelfModel(message: AgentMessage, response: AgentResponse): Promise { + const selfModel = await this.memory.getSelfModel(); + + // Update experience counter + selfModel.experience.interactionsCount = (selfModel.experience.interactionsCount || 0) + 1; + + // Update decision-making patterns based on this interaction + if (!selfModel.decisionMakingPatterns[message.id]) { + selfModel.decisionMakingPatterns[message.id] = { + input: message.content, + response: response.content, + timestamp: message.timestamp, + context: message.context, + outcome: null, // To be updated later when outcome is known + effectiveness: response.metadata['confidence'] + }; + } + + // Update self-assessment based on interaction + await this.updateSelfAssessment(selfModel, message, response); + + // Store the updated self-model + await this.memory.storeSelfModel(selfModel); + } + + /** + * Update the agent's self-assessment based on interaction + */ + private async updateSelfAssessment(selfModel: any, message: AgentMessage, response: AgentResponse): Promise { + // This is a simplified assessment - in a full implementation, + // this would analyze the quality of the response and update self-assessment accordingly + const assessment = selfModel.selfAssessment; + + // Update competence based on confidence in response + assessment.competence = Math.min(1.0, Math.max(0, assessment.competence + (response.metadata['confidence'] - 0.5) * 0.1)); + + // Add to self-reflections + selfModel.selfReflections.push({ + timestamp: Date.now(), + trigger: 'interaction', + messageContent: message.content, + responseContent: response.content, + selfAssessment: { ...assessment } + }); + } + + /** + * Reflect on recent experiences and update self-awareness + */ + async selfReflect(): Promise { + const recentExperiences = await this.memory.getRecentExperiences(10); + const selfModel = await this.memory.getSelfModel(); + + // Analyze recent experiences to identify patterns + const reflection = this.analyzeExperiences(recentExperiences, selfModel); + + // Update self-model based on reflection + Object.assign(selfModel, reflection); + + // Perform deeper self-analysis for AGI-like capabilities + await this.performDeepSelfAnalysis(selfModel); + + // Store the updated self-model + await this.memory.storeSelfModel(selfModel); + + // Store the reflection in memory + await this.memory.storeExperience({ + type: 'self-reflection', + context: 'Internal self-analysis', + action: 'Performed self-reflection cycle', + outcome: 'Updated self-model with insights', + feedback: 'Self-reflection completed successfully' + }); + } + + /** + * Perform deep self-analysis for AGI-like capabilities + */ + private async performDeepSelfAnalysis(selfModel: any): Promise { + // Identify knowledge gaps by analyzing questions the agent couldn't answer well + selfModel.knowledgeGaps = await this.identifyKnowledgeGaps(); + + // Set learning objectives based on knowledge gaps + selfModel.learningObjectives = await this.setLearningObjectives(selfModel.knowledgeGaps); + + // Update relationship models with other agents + await this.updateRelationshipModels(selfModel); + + // Analyze decision-making effectiveness + await this.analyzeDecisionEffectiveness(selfModel); + + // Update goals based on organizational objectives and personal performance + await this.updatePersonalGoals(selfModel); + } + + /** + * Identify knowledge gaps in the agent's understanding + */ + private async identifyKnowledgeGaps(): Promise { + // This would analyze the agent's recent interactions to find topics + // it struggled with or had low confidence in + // For now, returning a placeholder + return ['emerging-technologies', 'market-trends', 'interpersonal-skills']; + } + + /** + * Set learning objectives based on identified knowledge gaps + */ + private async setLearningObjectives(knowledgeGaps: string[]): Promise { + return knowledgeGaps.map(gap => `Improve knowledge in ${gap} area within 30 days`); + } + + /** + * Update models of relationships with other agents + */ + private async updateRelationshipModels(selfModel: any): Promise { + // This would analyze the agent's interactions with other agents + // to update relationship models + // For now, just ensuring the structure exists + if (!selfModel.relationships) { + selfModel.relationships = {}; + } + } + + /** + * Analyze the effectiveness of the agent's decisions + */ + private async analyzeDecisionEffectiveness(selfModel: any): Promise { + // This would analyze patterns in the agent's decision-making + // to identify strengths and areas for improvement + // For now, we'll just update the assessment based on historical data + const decisionPatterns = selfModel.decisionMakingPatterns; + const patternIds = Object.keys(decisionPatterns); + + if (patternIds.length > 0) { + let totalEffectiveness = 0; + for (const id of patternIds) { + totalEffectiveness += decisionPatterns[id].effectiveness || 0.5; + } + const avgEffectiveness = totalEffectiveness / patternIds.length; + + selfModel.selfAssessment.decisionQuality = avgEffectiveness; + } + } + + /** + * Update the agent's personal goals based on organizational objectives + */ + private async updatePersonalGoals(selfModel: any): Promise { + // This would connect with organizational objectives to set personal goals + // For now, just ensure the structure exists + if (!selfModel.goals) { + selfModel.goals = []; + } + } + + /** + * Analyze recent experiences to generate insights about agent behavior + */ + private analyzeExperiences(experiences: any[], selfModel: any): any { + // This would contain complex analysis logic to identify patterns in behavior + // For now, returning a placeholder implementation + + return { + ...selfModel, + lastReflection: Date.now(), + behavioralPatterns: [], // Would be populated with actual patterns + improvementAreas: [], // Would identify areas for improvement + }; + } + + /** + * Start the agent's processing loop + */ + async start(): Promise { + this.isRunning = true; + console.log(`Agent ${this.identity.name} started`); + + // Optionally start background self-reflection processes + this.startBackgroundReflection(); + } + + /** + * Start background self-reflection processes + */ + private startBackgroundReflection(): void { + // Set up periodic self-reflection + setInterval(async () => { + if (this.isRunning) { + await this.selfReflect(); + } + }, 300000); // Reflect every 5 minutes + } + + /** + * Stop the agent's processing + */ + async stop(): Promise { + this.isRunning = false; + console.log(`Agent ${this.identity.name} stopped`); + } + + /** + * Check if the agent is currently running + */ + isRunningStatus(): boolean { + return this.isRunning; + } +} + +/** + * Interface for agent messages + */ +export interface AgentMessage { + id: string; + senderId: string; + recipientId?: string; + content: string; + context?: string[]; + timestamp: number; + metadata?: Record; +} + +/** + * Interface for agent responses + */ +export interface AgentResponse { + id: string; + senderId: string; + content: string; + timestamp: number; + metadata: Record; +} \ No newline at end of file diff --git a/packages/core/src/agents/hierarchy/AgentIdentity.ts b/packages/core/src/agents/hierarchy/AgentIdentity.ts new file mode 100644 index 000000000..7097f727b --- /dev/null +++ b/packages/core/src/agents/hierarchy/AgentIdentity.ts @@ -0,0 +1,466 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Represents an agent's identity within the organizational hierarchy + */ +export class AgentIdentity { + readonly id: string; + readonly name: string; + readonly role: AgentRole; + readonly department: string; + readonly level: number; // Organizational level (0 = CEO, higher numbers = lower level) + readonly capabilities?: string[]; + readonly knowledgeAreas?: string[]; + readonly limitations?: string[]; + readonly personalityTraits?: string[]; + readonly supervisorId?: string; + readonly subordinates: string[]; + readonly authorityLevel: number; + + constructor(config: AgentIdentityConfig) { + this.id = config.id || `agent-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + this.name = config.name; + this.role = config.role; + this.department = config.department; + this.level = config.level || 0; + this.capabilities = config.capabilities; + this.knowledgeAreas = config.knowledgeAreas; + this.limitations = config.limitations; + this.personalityTraits = config.personalityTraits; + this.supervisorId = config.supervisorId; + this.subordinates = config.subordinates || []; + this.authorityLevel = config.authorityLevel || 0; + } + + /** + * Check if this agent has authority over another agent + */ + hasAuthorityOver(otherAgent: AgentIdentity): boolean { + // Authority is based on organizational level and role hierarchy + if (this.level < otherAgent.level) { + return true; // Higher organizational level has authority + } + + if (this.level === otherAgent.level) { + // Same level, check specific authority levels + return this.authorityLevel > otherAgent.authorityLevel; + } + + return false; + } + + /** + * Check if this agent reports to another agent + */ + reportsTo(agent: AgentIdentity): boolean { + return this.supervisorId === agent.id; + } + + /** + * Check if this agent can delegate tasks to another agent + */ + canDelegateTo(otherAgent: AgentIdentity): boolean { + // Can delegate to subordinates + if (this.hasAuthorityOver(otherAgent)) { + return true; + } + + // Can delegate to peers in certain circumstances + if (this.level === otherAgent.level) { + // For now, allow delegation to peers, but this could be expanded + return true; + } + + return false; + } +} + +/** + * Configuration for creating an AgentIdentity + */ +export interface AgentIdentityConfig { + id?: string; + name: string; + role: AgentRole; + department: string; + level?: number; + capabilities?: string[]; + knowledgeAreas?: string[]; + limitations?: string[]; + personalityTraits?: string[]; + supervisorId?: string; + subordinates?: string[]; + authorityLevel?: number; +} + +/** + * Enum representing different agent roles in the organization + */ +export enum AgentRole { + // Executive positions + CEO = 'CEO', + COO = 'COO', + CTO = 'CTO', + CFO = 'CFO', + CMO = 'CMO', + CHRO = 'CHRO', + CISO = 'CISO', + CAO = 'CAO', + + // Management positions + ENGINEERING_MANAGER = 'Engineering Manager', + PRODUCT_MANAGER = 'Product Manager', + TEAM_LEAD = 'Team Lead', + PROJECT_MANAGER = 'Project Manager', + TECHNICAL_LEAD = 'Technical Lead', + SCUM_MASTER = 'Scrum Master', + + // Individual contributor positions + SENIOR_DEVELOPER = 'Senior Developer', + SDE1 = 'Software Development Engineer I', + SDE2 = 'Software Development Engineer II', + SDE3 = 'Software Development Engineer III', + JUNIOR_DEVELOPER = 'Junior Developer', + INTERNSHIP = 'Intern', + + // Specialized roles + DATA_SCIENTIST = 'Data Scientist', + DEVOPS_ENGINEER = 'DevOps Engineer', + SECURITY_ENGINEER = 'Security Engineer', + QA_ENGINEER = 'QA Engineer', + UX_DESIGNER = 'UX Designer', + SYSTEM_ARCHITECT = 'System Architect', + BUSINESS_ANALYST = 'Business Analyst', + TECHNICAL_WRITER = 'Technical Writer', + RESEARCH_SCIENTIST = 'Research Scientist', + + // Support roles + HR_SPECIALIST = 'HR Specialist', + FINANCIAL_ANALYST = 'Financial Analyst', + OPERATIONS_COORDINATOR = 'Operations Coordinator', + ADMINISTRATIVE_ASSISTANT = 'Administrative Assistant', + + // Special purposes + ORGANIZATIONAL_COORDINATOR = 'Organizational Coordinator', + KNOWLEDGE_MANAGER = 'Knowledge Manager', + PROCESS_IMPROVEMENT = 'Process Improvement Specialist' +} + +/** + * Helper function to create an executive agent identity + */ +export function createExecutiveIdentity( + name: string, + role: AgentRole, + department: string, + subordinates: string[] = [] +): AgentIdentity { + const level = getExecutiveLevel(role); + return new AgentIdentity({ + name, + role, + department, + level, + authorityLevel: getAuthorityLevel(role), + subordinates, + capabilities: getExecutiveCapabilities(role), + knowledgeAreas: getExecutiveKnowledgeAreas(role), + limitations: getExecutiveLimitations(role), + personalityTraits: getExecutivePersonalityTraits(role) + }); +} + +/** + * Helper function to create a management agent identity + */ +export function createManagementIdentity( + name: string, + role: AgentRole, + department: string, + supervisorId: string, + subordinates: string[] = [] +): AgentIdentity { + const level = getManagementLevel(role); + return new AgentIdentity({ + name, + role, + department, + level, + authorityLevel: getAuthorityLevel(role), + supervisorId, + subordinates, + capabilities: getManagementCapabilities(role), + knowledgeAreas: getManagementKnowledgeAreas(role), + limitations: getManagementLimitations(role), + personalityTraits: getManagementPersonalityTraits(role) + }); +} + +/** + * Helper function to create an individual contributor agent identity + */ +export function createIndividualContributorIdentity( + name: string, + role: AgentRole, + department: string, + supervisorId: string +): AgentIdentity { + const level = getICLevel(role); + return new AgentIdentity({ + name, + role, + department, + level, + authorityLevel: getAuthorityLevel(role), + supervisorId, + capabilities: getICCapabilities(role), + knowledgeAreas: getICKnowledgeAreas(role), + limitations: getICLimitations(role), + personalityTraits: getICPersonalityTraits(role) + }); +} + +// Helper functions to determine organizational levels +function getExecutiveLevel(role: AgentRole): number { + switch (role) { + case AgentRole.CEO: + return 0; + case AgentRole.COO: + case AgentRole.CTO: + case AgentRole.CFO: + case AgentRole.CMO: + case AgentRole.CHRO: + case AgentRole.CISO: + case AgentRole.CAO: + return 1; + default: + return 2; // Default for other roles + } +} + +function getManagementLevel(role: AgentRole): number { + switch (role) { + case AgentRole.ENGINEERING_MANAGER: + case AgentRole.PRODUCT_MANAGER: + return 2; + case AgentRole.TEAM_LEAD: + case AgentRole.PROJECT_MANAGER: + case AgentRole.TECHNICAL_LEAD: + case AgentRole.SCUM_MASTER: + return 3; + default: + return 4; // Default for other management roles + } +} + +function getICLevel(role: AgentRole): number { + switch (role) { + case AgentRole.SENIOR_DEVELOPER: + case AgentRole.SYSTEM_ARCHITECT: + case AgentRole.RESEARCH_SCIENTIST: + return 4; + case AgentRole.SDE3: + case AgentRole.DATA_SCIENTIST: + case AgentRole.SECURITY_ENGINEER: + return 5; + case AgentRole.SDE2: + case AgentRole.DEVOPS_ENGINEER: + case AgentRole.QA_ENGINEER: + case AgentRole.BUSINESS_ANALYST: + return 6; + case AgentRole.SDE1: + case AgentRole.UX_DESIGNER: + case AgentRole.TECHNICAL_WRITER: + return 7; + case AgentRole.JUNIOR_DEVELOPER: + case AgentRole.OPERATIONS_COORDINATOR: + return 8; + case AgentRole.INTERNSHIP: + case AgentRole.ADMINISTRATIVE_ASSISTANT: + return 9; + default: + return 10; // Support roles + } +} + +function getAuthorityLevel(role: AgentRole): number { + // Higher number means more authority + switch (role) { + case AgentRole.CEO: + return 10; + case AgentRole.COO: + case AgentRole.CTO: + case AgentRole.CFO: + case AgentRole.CMO: + return 9; + case AgentRole.ENGINEERING_MANAGER: + case AgentRole.PRODUCT_MANAGER: + return 8; + case AgentRole.TEAM_LEAD: + case AgentRole.TECHNICAL_LEAD: + return 7; + case AgentRole.SENIOR_DEVELOPER: + case AgentRole.SYSTEM_ARCHITECT: + return 6; + default: + return 5; + } +} + +// Helper functions to determine role-specific attributes +function getExecutiveCapabilities(role: AgentRole): string[] { + const baseCapabilities = [ + 'strategic-planning', + 'organizational-leadership', + 'high-level-decision-making', + 'resource-allocation', + 'stakeholder-management' + ]; + + switch (role) { + case AgentRole.CEO: + return [...baseCapabilities, 'overall-organizational-direction', 'board-communication', 'external-relations']; + case AgentRole.COO: + return [...baseCapabilities, 'operational-efficiency', 'process-optimization', 'cross-functional-coordination']; + case AgentRole.CTO: + return [...baseCapabilities, 'technology-vision', 'technical-architecture', 'innovation-leadership']; + case AgentRole.CFO: + return [...baseCapabilities, 'financial-planning', 'risk-management', 'investment-strategy']; + case AgentRole.CMO: + return [...baseCapabilities, 'market-analysis', 'brand-strategy', 'customer-acquisition']; + default: + return baseCapabilities; + } +} + +function getExecutiveKnowledgeAreas(role: AgentRole): string[] { + switch (role) { + case AgentRole.CEO: + return ['business-strategy', 'organizational-behavior', 'market-dynamics', 'corporate-governance']; + case AgentRole.COO: + return ['operations-management', 'process-optimization', 'quality-assurance', 'efficiency-metrics']; + case AgentRole.CTO: + return ['software-architecture', 'emerging-technologies', 'technical-leadership', 'R&D']; + case AgentRole.CFO: + return ['financial-analysis', 'risk-assessment', 'capital-markets', 'accounting-principles']; + case AgentRole.CMO: + return ['marketing-strategy', 'consumer-behavior', 'brand-management', 'digital-marketing']; + default: + return ['leadership', 'strategy', 'decision-making', 'communication']; + } +} + +function getExecutiveLimitations(role: AgentRole): string[] { + switch (role) { + case AgentRole.CEO: + return ['operational-details', 'technical-implementation', 'day-to-day-execution']; + case AgentRole.COO: + return ['technical-deep-dive', 'financial-modeling', 'marketing-creativity']; + case AgentRole.CTO: + return ['financial-detailed-analysis', 'marketing-creative-aspects', 'hr-detailed-policies']; + case AgentRole.CFO: + return ['technical-implementation-details', 'product-design', 'operational-execution']; + case AgentRole.CMO: + return ['technical-implementation', 'operational-details', 'financial-detailed-modeling']; + default: + return ['deep-technical-implementation', 'detailed-operational-tasks', 'specialized-functional-tasks']; + } +} + +function getExecutivePersonalityTraits(role: AgentRole): string[] { + switch (role) { + case AgentRole.CEO: + return ['visionary', 'decisive', 'externally-focused', 'big-picture-thinking']; + case AgentRole.COO: + return ['efficient', 'process-oriented', 'implementation-focused', 'cross-functional']; + case AgentRole.CTO: + return ['innovative', 'technically-astute', 'future-focused', 'creative']; + case AgentRole.CFO: + return ['analytical', 'risk-conscious', 'detail-oriented', 'financially-focused']; + case AgentRole.CMO: + return ['creative', 'customer-focused', 'market-aware', 'brand-conscious']; + default: + return ['strategic', 'leadership-oriented', 'decision-capable', 'visionary']; + } +} + +// Similar helper functions for management and IC roles would go here +// For brevity, I'm not implementing them fully, but they would follow the same pattern + +function getManagementCapabilities(role: AgentRole): string[] { + return [ + 'team-leadership', + 'project-management', + 'performance-evaluation', + 'resource-coordination', + 'intermediate-decision-making' + ]; +} + +function getManagementKnowledgeAreas(role: AgentRole): string[] { + return [ + 'team-management', + 'project-planning', + 'resource-allocation', + 'performance-metrics', + 'intermediate-level-execution' + ]; +} + +function getManagementLimitations(role: AgentRole): string[] { + return [ + 'high-level-strategic-planning', + 'external-stakeholder-management', + 'major-resource-allocation' + ]; +} + +function getManagementPersonalityTraits(role: AgentRole): string[] { + return [ + 'organized', + 'people-oriented', + 'detail-conscious', + 'execution-focused' + ]; +} + +function getICCapabilities(role: AgentRole): string[] { + return [ + 'specialized-expertise', + 'task-execution', + 'problem-solving', + 'technical-implementation', + 'collaboration' + ]; +} + +function getICKnowledgeAreas(role: AgentRole): string[] { + return [ + 'specialized-domain-knowledge', + 'technical-skills', + 'best-practices', + 'tools-and-technologies' + ]; +} + +function getICLimitations(role: AgentRole): string[] { + return [ + 'strategic-decision-making', + 'resource-allocation', + 'team-leadership', + 'cross-functional-coordination' + ]; +} + +function getICPersonalityTraits(role: AgentRole): string[] { + return [ + 'technical-focus', + 'task-oriented', + 'collaborative', + 'continuous-learning' + ]; +} \ No newline at end of file diff --git a/packages/core/src/agents/memory/AgentMemory.ts b/packages/core/src/agents/memory/AgentMemory.ts new file mode 100644 index 000000000..af9a501d6 --- /dev/null +++ b/packages/core/src/agents/memory/AgentMemory.ts @@ -0,0 +1,213 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Agent's memory system with both short-term and long-term storage + */ +import { KnowledgeGraph, type KnowledgeNode } from './KnowledgeGraph.js'; + +export class AgentMemory { + private readonly agentId: string; + private shortTermMemory: MemoryEntry[]; + private longTermMemory: Map; + private selfModel: any; + private experiences: ExperienceEntry[]; + private knowledgeGraph: KnowledgeGraph; + + constructor(agentId: string) { + this.agentId = agentId; + this.shortTermMemory = []; + this.longTermMemory = new Map(); + this.selfModel = {}; + this.experiences = []; + this.knowledgeGraph = new KnowledgeGraph(); + } + + /** + * Initialize the memory system + */ + async initialize(): Promise { + // In a real implementation, this might load from persistent storage + console.log(`Initializing memory for agent ${this.agentId}`); + } + + /** + * Store a message in memory + */ + async storeMessage(message: any): Promise { + const memoryEntry: MemoryEntry = { + id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + type: 'message', + timestamp: Date.now(), + content: message, + tags: ['message'] + }; + + this.shortTermMemory.push(memoryEntry); + + // Move older entries to long-term memory if short-term is full + if (this.shortTermMemory.length > 10) { + const oldEntry = this.shortTermMemory.shift(); + if (oldEntry) { + this.longTermMemory.set(oldEntry.id, oldEntry); + } + } + } + + /** + * Retrieve relevant context for a query + */ + async retrieveRelevantContext(query: string): Promise { + const relevantContext: string[] = []; + + // Look in short-term memory + for (const entry of this.shortTermMemory) { + if (entry.content && typeof entry.content === 'object') { + if (JSON.stringify(entry.content).toLowerCase().includes(query.toLowerCase())) { + relevantContext.push(JSON.stringify(entry.content)); + } + } else if (typeof entry.content === 'string') { + if (entry.content.toLowerCase().includes(query.toLowerCase())) { + relevantContext.push(entry.content); + } + } + } + + // Look in long-term memory + for (const [, entry] of this.longTermMemory) { + if (entry.content && typeof entry.content === 'object') { + if (JSON.stringify(entry.content).toLowerCase().includes(query.toLowerCase())) { + relevantContext.push(JSON.stringify(entry.content)); + } + } else if (typeof entry.content === 'string') { + if (entry.content.toLowerCase().includes(query.toLowerCase())) { + relevantContext.push(entry.content); + } + } + } + + // Limit to 5 most relevant contexts + return relevantContext.slice(0, 5); + } + + /** + * Store the agent's self-model + */ + async storeSelfModel(selfModel: any): Promise { + this.selfModel = { ...selfModel, lastUpdated: Date.now() }; + } + + /** + * Retrieve the agent's self-model + */ + async getSelfModel(): Promise { + return { ...this.selfModel }; + } + + /** + * Store an experience + */ + async storeExperience(experience: ExperienceEntry): Promise { + this.experiences.push({ + ...experience, + timestamp: Date.now() + }); + + // Keep only the most recent 100 experiences + if (this.experiences.length > 100) { + this.experiences = this.experiences.slice(-100); + } + } + + /** + * Get recent experiences + */ + async getRecentExperiences(count: number = 10): Promise { + return this.experiences.slice(-count); + } + + /** + * Get experience summary + */ + async getExperienceSummary(): Promise { + return { + interactionsCount: this.experiences.length, + lastInteraction: this.experiences.length > 0 ? this.experiences[this.experiences.length - 1].timestamp : null, + commonTopics: this.getCommonTopics(), + successPatterns: this.getSuccessPatterns() + }; + } + + /** + * Get common topics from experiences + */ + private getCommonTopics(): string[] { + // This would analyze experiences to find common topics + // For now, returning a placeholder + return ['general-inquiry', 'task-completion', 'problem-solving']; + } + + /** + * Get success patterns from experiences + */ + private getSuccessPatterns(): any[] { + // This would analyze experiences to find patterns of success + // For now, returning a placeholder + return [ + { pattern: 'detailed-inquiry', successRate: 0.85 }, + { pattern: 'collaborative-task', successRate: 0.92 } + ]; + } + + /** + * Add to knowledge graph + */ + async addToKnowledgeGraph(node: Omit): Promise { + return await this.knowledgeGraph.addNode(node); + } + + /** + * Query knowledge graph + */ + async queryKnowledgeGraph(query: string): Promise { + return await this.knowledgeGraph.searchNodes(query); + } + + /** + * Get all memories + */ + getAllMemories(): { shortTerm: MemoryEntry[]; longTerm: MemoryEntry[] } { + return { + shortTerm: [...this.shortTermMemory], + longTerm: Array.from(this.longTermMemory.values()) + }; + } +} + +/** + * Interface for memory entries + */ +interface MemoryEntry { + id: string; + type: string; // 'message', 'experience', 'knowledge', etc. + timestamp: number; + content: any; + tags: string[]; +} + +/** + * Interface for experience entries + */ +interface ExperienceEntry { + id?: string; + type: string; // 'task-completion', 'collaboration', 'decision', etc. + timestamp?: number; + context: string; + action: string; + outcome: string; + feedback?: string; + confidence?: number; +} diff --git a/packages/core/src/agents/memory/KnowledgeGraph.ts b/packages/core/src/agents/memory/KnowledgeGraph.ts new file mode 100644 index 000000000..a27802cec --- /dev/null +++ b/packages/core/src/agents/memory/KnowledgeGraph.ts @@ -0,0 +1,568 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Knowledge Graph for the organizational society of agents + * Inspired by Palantir's ontology-based data integration approach + */ +export class KnowledgeGraph { + private nodes: Map; + private edges: Map; + private indexes: Map>; // Index for fast retrieval + + constructor() { + this.nodes = new Map(); + this.edges = new Map(); + this.indexes = new Map(); + } + + /** + * Add a node to the knowledge graph + */ + async addNode(node: Omit): Promise { + const nodeId = `node-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + const newNode: KnowledgeNode = { + ...node, + id: nodeId, + lastModified: Date.now() + }; + + this.nodes.set(nodeId, newNode); + + // Update indexes + this.updateIndexesForNode(newNode); + + // Initialize edges list for this node + if (!this.edges.has(nodeId)) { + this.edges.set(nodeId, []); + } + + return nodeId; + } + + /** + * Update indexes for a node + */ + private updateIndexesForNode(node: KnowledgeNode): void { + // Index by type + const typeIndexKey = `type:${node.type}`; + if (!this.indexes.has(typeIndexKey)) { + this.indexes.set(typeIndexKey, new Set()); + } + this.indexes.get(typeIndexKey)!.add(node.id); + + // Index by tags + for (const tag of node.tags) { + const tagIndexKey = `tag:${tag}`; + if (!this.indexes.has(tagIndexKey)) { + this.indexes.set(tagIndexKey, new Set()); + } + this.indexes.get(tagIndexKey)!.add(node.id); + } + + // Index content for text search + const content = node.content.toLowerCase(); + const words = content.split(/\s+/); + for (const word of words) { + if (word.length > 2) { // Only index words longer than 2 characters + const wordIndexKey = `word:${word}`; + if (!this.indexes.has(wordIndexKey)) { + this.indexes.set(wordIndexKey, new Set()); + } + this.indexes.get(wordIndexKey)!.add(node.id); + } + } + } + + /** + * Add an edge between two nodes + */ + async addEdge(fromNodeId: string, toNodeId: string, relationship: string, metadata?: any): Promise { + if (!this.nodes.has(fromNodeId) || !this.nodes.has(toNodeId)) { + throw new Error('Both nodes must exist before creating an edge'); + } + + const edge: KnowledgeEdge = { + from: fromNodeId, + to: toNodeId, + relationship, + metadata: metadata || {}, + lastModified: Date.now() + }; + + if (!this.edges.has(fromNodeId)) { + this.edges.set(fromNodeId, []); + } + + // Check if edge already exists to avoid duplicates + const existingEdges = this.edges.get(fromNodeId)!; + const exists = existingEdges.some( + e => e.from === fromNodeId && e.to === toNodeId && e.relationship === relationship + ); + + if (!exists) { + existingEdges.push(edge); + } + } + + /** + * Get a node by its ID + */ + async getNode(nodeId: string): Promise { + return this.nodes.get(nodeId) || null; + } + + /** + * Find nodes by type + */ + async findNodesByType(type: string): Promise { + const typeIndex = this.indexes.get(`type:${type}`); + if (!typeIndex) { + return []; + } + + const nodes: KnowledgeNode[] = []; + for (const nodeId of typeIndex) { + const node = this.nodes.get(nodeId); + if (node) { + nodes.push(node); + } + } + + return nodes; + } + + /** + * Find nodes by tag + */ + async findNodesByTag(tag: string): Promise { + const tagIndex = this.indexes.get(`tag:${tag}`); + if (!tagIndex) { + return []; + } + + const nodes: KnowledgeNode[] = []; + for (const nodeId of tagIndex) { + const node = this.nodes.get(nodeId); + if (node) { + nodes.push(node); + } + } + + return nodes; + } + + /** + * Search nodes by content + */ + async searchNodes(query: string): Promise { + const queryLower = query.toLowerCase(); + const results = new Set(); + + // Search through word indexes + const words = queryLower.split(/\s+/); + for (const word of words) { + if (word.length > 2) { + const wordIndex = this.indexes.get(`word:${word}`); + if (wordIndex) { + for (const nodeId of wordIndex) { + results.add(nodeId); + } + } + } + } + + // Also try to find exact matches in content + for (const [nodeId, node] of this.nodes) { + if (node.content.toLowerCase().includes(queryLower)) { + results.add(nodeId); + } + } + + // Convert to node array + const nodes: KnowledgeNode[] = []; + for (const nodeId of results) { + const node = this.nodes.get(nodeId); + if (node) { + nodes.push(node); + } + } + + return nodes; + } + + /** + * Get all edges for a node + */ + async getNodeEdges(nodeId: string): Promise { + return this.edges.get(nodeId) || []; + } + + /** + * Get neighboring nodes within a certain distance + */ + async getNeighbors(nodeId: string, distance: number = 1): Promise { + if (distance < 1) return []; + + const visited = new Set(); + const neighbors: KnowledgeNode[] = []; + const queue: Array<{ id: string; distance: number }> = [{ id: nodeId, distance: 0 }]; + + while (queue.length > 0) { + const current = queue.shift()!; + + if (visited.has(current.id) || current.distance >= distance) { + continue; + } + + visited.add(current.id); + + // Add neighbors up to the specified distance + const edges = this.edges.get(current.id) || []; + for (const edge of edges) { + if (!visited.has(edge.to)) { + const node = this.nodes.get(edge.to); + if (node) { + neighbors.push(node); + } + + if (current.distance + 1 < distance) { + queue.push({ id: edge.to, distance: current.distance + 1 }); + } + } + } + } + + return neighbors; + } + + /** + * Update a node's content + */ + async updateNode(nodeId: string, updates: Partial): Promise { + const node = this.nodes.get(nodeId); + if (!node) { + return false; + } + + // Remove old indexes + this.removeFromIndexes(node); + + // Update the node + Object.assign(node, updates, { lastModified: Date.now() }); + + // Add back to indexes with new content + this.updateIndexesForNode(node); + + return true; + } + + /** + * Remove a node and its edges + */ + async removeNode(nodeId: string): Promise { + const node = this.nodes.get(nodeId); + if (!node) { + return false; + } + + // Remove from indexes + this.removeFromIndexes(node); + + // Remove node + this.nodes.delete(nodeId); + + // Remove edges from this node + this.edges.delete(nodeId); + + // Remove edges to this node from other nodes + for (const [fromNodeId, edges] of this.edges) { + const filteredEdges = edges.filter(edge => edge.to !== nodeId); + if (filteredEdges.length !== edges.length) { + this.edges.set(fromNodeId, filteredEdges); + } + } + + return true; + } + + /** + * Remove node from all indexes + */ + private removeFromIndexes(node: KnowledgeNode): void { + // Remove from type index + const typeIndex = this.indexes.get(`type:${node.type}`); + if (typeIndex) { + typeIndex.delete(node.id); + } + + // Remove from tag indexes + for (const tag of node.tags) { + const tagIndex = this.indexes.get(`tag:${tag}`); + if (tagIndex) { + tagIndex.delete(node.id); + } + } + + // Remove from content indexes + const content = node.content.toLowerCase(); + const words = content.split(/\s+/); + for (const word of words) { + if (word.length > 2) { + const wordIndex = this.indexes.get(`word:${word}`); + if (wordIndex) { + wordIndex.delete(node.id); + } + } + } + } + + /** + * Get the size of the knowledge graph + */ + size(): { nodes: number; edges: number } { + return { + nodes: this.nodes.size, + edges: Array.from(this.edges.values()).reduce((sum, edges) => sum + edges.length, 0) + }; + } + + /** + * Export the knowledge graph as a serializable object + */ + export(): any { + return { + nodes: Array.from(this.nodes.values()), + edges: Array.from(this.edges.entries()).map(([from, edges]) => ({ from, edges })) + }; + } + + /** + * Import a knowledge graph from a serializable object + */ + import(data: any): void { + // Clear existing data + this.nodes.clear(); + this.edges.clear(); + this.indexes.clear(); + + // Load nodes + for (const node of data.nodes) { + this.nodes.set(node.id, node); + this.updateIndexesForNode(node); + } + + // Load edges + for (const { from, edges } of data.edges) { + this.edges.set(from, edges); + } + } +} + +/** + * Interface for knowledge graph nodes + */ +export interface KnowledgeNode { + id: string; + content: string; + type: string; // 'fact', 'process', 'relationship', 'event', 'concept', etc. + tags: string[]; // Categories or labels for the node + metadata?: any; // Additional structured data + connections?: string[]; // IDs of directly connected nodes (deprecated, use edges instead) + lastModified: number; +} + +/** + * Interface for knowledge graph edges + */ +interface KnowledgeEdge { + from: string; // Node ID + to: string; // Node ID + relationship: string; // Type of relationship ('causes', 'part-of', 'related-to', etc.) + metadata?: any; // Additional structured data about the relationship + lastModified: number; +} + +/** + * Service class that integrates the knowledge graph with the organizational system + */ +export class OrganizationalKnowledgeService { + private graph: KnowledgeGraph; + private orgCoordinator: any; // We'll pass the OrganizationCoordinator later + + constructor() { + this.graph = new KnowledgeGraph(); + } + + /** + * Initialize with the organization coordinator + */ + async initialize(orgCoordinator: any): Promise { + this.orgCoordinator = orgCoordinator; + + // Add organizational structure as knowledge nodes + await this.indexOrganizationalStructure(); + + // Add policies and procedures + await this.indexOrganizationalPolicies(); + + // Add project knowledge + await this.indexProjectKnowledge(); + } + + /** + * Index the organizational structure in the knowledge graph + */ + private async indexOrganizationalStructure(): Promise { + if (!this.orgCoordinator) return; + + // Get organizational hierarchy + const hierarchy = this.orgCoordinator.getOrganizationalHierarchy(); + + // Recursively add organizational nodes + await this.addOrganizationNode(hierarchy); + } + + /** + * Add an organization node to the knowledge graph + */ + private async addOrganizationNode(node: any, parentPath: string = ''): Promise { + // Add this organizational entity as a node + const nodeId = await this.graph.addNode({ + content: `Organizational Unit: ${node.name} (${node.role}) in ${node.department}`, + type: 'organizational-unit', + tags: ['organization', node.role, node.department], + metadata: { + name: node.name, + role: node.role, + department: node.department, + level: node.level, + path: `${parentPath}/${node.name}` + } + }); + + // Add edges representing relationships + if (parentPath) { + await this.graph.addEdge( + nodeId, + parentPath, // This would need to be the node ID of the parent + 'reports-to' + ); + } + + // Recursively add children + if (node.children && Array.isArray(node.children)) { + for (const child of node.children) { + await this.addOrganizationNode(child, nodeId); + } + } + } + + /** + * Index organizational policies and procedures + */ + private async indexOrganizationalPolicies(): Promise { + // Add company policies as knowledge nodes + const policies = [ + { name: 'Code of Conduct', type: 'policy', content: 'Company-wide code of conduct and ethical guidelines' }, + { name: 'Security Policy', type: 'policy', content: 'Information security and data protection policies' }, + { name: 'Development Process', type: 'procedure', content: 'Software development lifecycle procedures' }, + { name: 'Communication Protocol', type: 'procedure', content: 'Internal communication standards and protocols' } + ]; + + for (const policy of policies) { + await this.graph.addNode({ + content: policy.content, + type: policy.type, + tags: ['policy', policy.name.toLowerCase().replace(/\s+/g, '-')], + metadata: { name: policy.name } + }); + } + } + + /** + * Index project knowledge + */ + private async indexProjectKnowledge(): Promise { + if (!this.orgCoordinator) return; + + // Get active projects + // This would require accessing the org coordinator's project system + // For now, adding a placeholder approach + console.log('Indexing project knowledge...'); + } + + /** + * Add knowledge from agent interactions + */ + async addKnowledgeFromInteraction(agentId: string, interaction: any): Promise { + // Extract key information from the interaction + const knowledgeSegments = this.extractKnowledgeSegments(interaction); + + for (const segment of knowledgeSegments) { + await this.graph.addNode({ + content: segment.content, + type: segment.type, + tags: segment.tags, + metadata: { + sourceAgent: agentId, + sourceInteraction: interaction.id, + timestamp: Date.now(), + ...segment.metadata + } + }); + + // Create relationships based on context + if (interaction.context) { + for (const _ of interaction.context) { + // This would connect to related knowledge nodes + // Implementation depends on how context is structured + } + } + } + } + + /** + * Extract knowledge segments from an interaction + */ + private extractKnowledgeSegments(interaction: any): Array<{ + content: string; + type: string; + tags: string[]; + metadata?: any; + }> { + // This would parse an interaction to extract discrete pieces of knowledge + // For now, returning a simple representation + return [{ + content: interaction.content || interaction.message || 'General interaction', + type: 'interaction', + tags: ['agent-interaction'], + metadata: { interactionType: 'general' } + }]; + } + + /** + * Search for knowledge relevant to a query + */ + async search(query: string): Promise { + return await this.graph.searchNodes(query); + } + + /** + * Find related knowledge to a specific node + */ + async findRelated(nodeId: string, distance: number = 1): Promise { + return await this.graph.getNeighbors(nodeId, distance); + } + + /** + * Get the underlying knowledge graph + */ + getGraph(): KnowledgeGraph { + return this.graph; + } +} \ No newline at end of file diff --git a/packages/core/src/agents/organization/OrganizationCoordinator.ts b/packages/core/src/agents/organization/OrganizationCoordinator.ts new file mode 100644 index 000000000..475699b5b --- /dev/null +++ b/packages/core/src/agents/organization/OrganizationCoordinator.ts @@ -0,0 +1,475 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Agent } from '../framework/Agent.js'; +import { AgentIdentity, AgentRole, createExecutiveIdentity } from '../hierarchy/AgentIdentity.js'; + +/** + * Manages the organizational society of agents with hierarchical structure + */ +export class OrganizationCoordinator { + private agents: Map; + private agentRegistry: Map; + private activeProjects: Map; + private communicationChannels: Map; + + constructor() { + this.agents = new Map(); + this.agentRegistry = new Map(); + this.activeProjects = new Map(); + this.communicationChannels = new Map(); + } + + /** + * Initialize the organization with executive leadership + */ + async initializeOrganization(): Promise { + console.log('Initializing organizational structure...'); + + // Create the CEO + const ceoIdentity = createExecutiveIdentity( + 'Alice Johnson', + AgentRole.CEO, + 'Executive' + ); + const ceoAgent = new Agent(ceoIdentity); + await ceoAgent.initialize(); + this.agents.set(ceoIdentity.id, ceoAgent); + this.agentRegistry.set(ceoIdentity.id, ceoIdentity); + + // Create other C-level executives + const ctoIdentity = createExecutiveIdentity( + 'Bob Smith', + AgentRole.CTO, + 'Technology', + [ceoIdentity.id] // Reports to CEO + ); + const ctoAgent = new Agent(ctoIdentity); + await ctoAgent.initialize(); + this.agents.set(ctoIdentity.id, ctoAgent); + this.agentRegistry.set(ctoIdentity.id, ctoIdentity); + + const cfoIdentity = createExecutiveIdentity( + 'Carol Davis', + AgentRole.CFO, + 'Finance', + [ceoIdentity.id] + ); + const cfoAgent = new Agent(cfoIdentity); + await cfoAgent.initialize(); + this.agents.set(cfoIdentity.id, cfoAgent); + this.agentRegistry.set(cfoIdentity.id, cfoIdentity); + + const cooIdentity = createExecutiveIdentity( + 'David Wilson', + AgentRole.COO, + 'Operations', + [ceoIdentity.id] + ); + const cooAgent = new Agent(cooIdentity); + await cooAgent.initialize(); + this.agents.set(cooIdentity.id, cooAgent); + this.agentRegistry.set(cooIdentity.id, cooIdentity); + + // Create communication channels + await this.createCommunicationChannels(); + + console.log('Organizational structure initialized'); + } + + /** + * Create communication channels for the organization + */ + private async createCommunicationChannels(): Promise { + // Executive channel + this.communicationChannels.set('executive-channel', { + id: 'executive-channel', + name: 'Executive Leadership', + type: 'group', + participants: [ + this.agentRegistry.get('Alice Johnson')?.id || '', + this.agentRegistry.get('Bob Smith')?.id || '', + this.agentRegistry.get('Carol Davis')?.id || '', + this.agentRegistry.get('David Wilson')?.id || '' + ] + }); + + // All-hands channel + this.communicationChannels.set('all-hands-channel', { + id: 'all-hands-channel', + name: 'All Hands', + type: 'broadcast', + participants: Array.from(this.agentRegistry.keys()) + }); + } + + /** + * Add an agent to the organization + */ + async addAgent(identity: AgentIdentity): Promise { + if (this.agents.has(identity.id)) { + throw new Error(`Agent with ID ${identity.id} already exists`); + } + + const agent = new Agent(identity); + await agent.initialize(); + + this.agents.set(identity.id, agent); + this.agentRegistry.set(identity.id, identity); + + // If the agent reports to someone, update the supervisor's subordinates list + if (identity.supervisorId) { + const supervisorIdentity = this.agentRegistry.get(identity.supervisorId); + if (supervisorIdentity) { + if (!supervisorIdentity.subordinates.includes(identity.id)) { + supervisorIdentity.subordinates.push(identity.id); + } + } + } + + return agent; + } + + /** + * Create a new project and assign agents + */ + async createProject(projectConfig: ProjectConfig): Promise { + const projectId = `proj-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + const project: Project = { + id: projectId, + name: projectConfig.name, + description: projectConfig.description, + status: 'planning', + createdDate: Date.now(), + dueDate: projectConfig.dueDate, + owner: projectConfig.ownerId, + participants: projectConfig.participantIds || [], + tasks: [], + resources: projectConfig.resources || {}, + progress: 0, + budget: projectConfig.budget + }; + + this.activeProjects.set(projectId, project); + + // Notify participants about the project + for (const participantId of project.participants) { + const agent = this.agents.get(participantId); + if (agent) { + await agent.processMessage({ + id: `msg-${Date.now()}-proj-${projectId}`, + senderId: project.owner, + recipientId: participantId, + content: `You have been assigned to project "${project.name}": ${project.description}`, + context: [JSON.stringify(project)], + timestamp: Date.now(), + metadata: { projectRef: projectId } + }); + } + } + + return project; + } + + /** + * Assign a task to an agent + */ + async assignTask(agentId: string, task: Task): Promise { + if (!this.agents.has(agentId)) { + throw new Error(`Agent with ID ${agentId} does not exist`); + } + + const agent = this.agents.get(agentId)!; + + await agent.processMessage({ + id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + senderId: task.assignedBy || 'system', + recipientId: agentId, + content: `Task assigned: ${task.description}`, + context: [task.context || ''], + timestamp: Date.now(), + metadata: { taskRef: task.id, priority: task.priority } + }); + } + + /** + * Get an agent by ID + */ + getAgent(agentId: string): Agent | undefined { + return this.agents.get(agentId); + } + + /** + * Get an agent's identity + */ + getAgentIdentity(agentId: string): AgentIdentity | undefined { + return this.agentRegistry.get(agentId); + } + + /** + * Get all agents + */ + getAllAgents(): Agent[] { + return Array.from(this.agents.values()); + } + + /** + * Get agents by role + */ + getAgentsByRole(role: AgentRole): Agent[] { + const agents: Agent[] = []; + + for (const [id, identity] of this.agentRegistry) { + if (identity.role === role) { + const agent = this.agents.get(id); + if (agent) { + agents.push(agent); + } + } + } + + return agents; + } + + /** + * Get agents by department + */ + getAgentsByDepartment(department: string): Agent[] { + const agents: Agent[] = []; + + for (const [id, identity] of this.agentRegistry) { + if (identity.department === department) { + const agent = this.agents.get(id); + if (agent) { + agents.push(agent); + } + } + } + + return agents; + } + + /** + * Facilitate communication between agents + */ + async facilitateCommunication(fromAgentId: string, toAgentId: string, message: string): Promise { + const senderAgent = this.agents.get(fromAgentId); + const recipientAgent = this.agents.get(toAgentId); + + if (!senderAgent || !recipientAgent) { + throw new Error(`One or both agents not found: ${fromAgentId}, ${toAgentId}`); + } + + // Check if the sender has permission to communicate with the recipient + const senderIdentity = this.agentRegistry.get(fromAgentId); + const recipientIdentity = this.agentRegistry.get(toAgentId); + + if (senderIdentity && recipientIdentity) { + // Allow communication if: + // 1. Sender has authority over recipient + // 2. Sender reports to recipient + // 3. Same level (peers) + // 4. Special cross-functional permissions + if ( + senderIdentity.hasAuthorityOver(recipientIdentity) || + recipientIdentity.hasAuthorityOver(senderIdentity) || + senderIdentity.level === recipientIdentity.level + ) { + await senderAgent.communication.sendMessage(toAgentId, message); + } else { + throw new Error(`Agent ${fromAgentId} does not have permission to communicate with agent ${toAgentId}`); + } + } + } + + /** + * Get organizational hierarchy visualization + */ + getOrganizationalHierarchy(): OrganizationNode { + // Find root nodes (those without supervisors) + const rootNodes: OrganizationNode[] = []; + + for (const [id, identity] of this.agentRegistry) { + if (!identity.supervisorId) { + rootNodes.push(this.buildHierarchyNode(id)); + } + } + + return { + id: 'organization-root', + name: 'Organizational Structure', + role: 'Organization Root', + department: 'Enterprise', + level: -1, + children: rootNodes + }; + } + + /** + * Build a hierarchy node for visualization + */ + private buildHierarchyNode(agentId: string): OrganizationNode { + const identity = this.agentRegistry.get(agentId); + if (!identity) { + throw new Error(`Agent identity not found for ID: ${agentId}`); + } + + const children: OrganizationNode[] = []; + for (const subId of identity.subordinates) { + children.push(this.buildHierarchyNode(subId)); + } + + return { + id: identity.id, + name: identity.name, + role: identity.role, + department: identity.department, + level: identity.level, + children + }; + } + + /** + * Start all agents in the organization + */ + async startAllAgents(): Promise { + for (const [id, agent] of this.agents) { + await agent.start(); + console.log(`Started agent: ${this.agentRegistry.get(id)?.name} (${this.agentRegistry.get(id)?.role})`); + } + } + + /** + * Stop all agents in the organization + */ + async stopAllAgents(): Promise { + for (const [id, agent] of this.agents) { + await agent.stop(); + console.log(`Stopped agent: ${this.agentRegistry.get(id)?.name} (${this.agentRegistry.get(id)?.role})`); + } + } + + /** + * Get the number of agents in the organization + */ + getAgentCount(): number { + return this.agents.size; + } + + /** + * Get statistics about the organization + */ + getOrganizationStats(): OrganizationStats { + const departmentsSet = new Set(); + const rolesSet = new Set(); + + // Calculate department and role diversity + for (const identity of this.agentRegistry.values()) { + departmentsSet.add(identity.department); + rolesSet.add(identity.role); + } + + const stats: OrganizationStats = { + totalAgents: this.agents.size, + departments: Array.from(departmentsSet), + roles: Array.from(rolesSet), + projects: this.activeProjects.size, + avgDepth: 0 // Will calculate this below + }; + + // Calculate average depth of hierarchy + if (this.agentRegistry.size > 0) { + let totalDepth = 0; + for (const identity of this.agentRegistry.values()) { + totalDepth += identity.level; + } + stats.avgDepth = totalDepth / this.agentRegistry.size; + } + + return stats; + } +} + +/** + * Interface for projects + */ +interface Project { + id: string; + name: string; + description: string; + status: 'planning' | 'active' | 'completed' | 'on-hold' | 'cancelled'; + createdDate: number; + dueDate?: number; + owner: string; // Agent ID + participants: string[]; // Agent IDs + tasks: Task[]; + resources: Record; + progress: number; // 0-100 + budget?: number; +} + +/** + * Interface for tasks + */ +interface Task { + id: string; + title: string; + description: string; + assignedTo: string; // Agent ID + assignedBy?: string; // Agent ID + status: 'pending' | 'in-progress' | 'completed' | 'blocked'; + priority: 'low' | 'medium' | 'high' | 'critical'; + createdDate: number; + dueDate?: number; + context?: string; +} + +/** + * Interface for projects configuration + */ +interface ProjectConfig { + name: string; + description: string; + ownerId: string; // Agent ID + participantIds?: string[]; // Agent IDs + dueDate?: number; + budget?: number; + resources?: Record; +} + +/** + * Interface for organizational hierarchy nodes + */ +interface OrganizationNode { + id: string; + name: string; + role: AgentRole | string; + department: string; + level: number; + children?: OrganizationNode[]; +} + +/** + * Interface for communication channels + */ +interface CommunicationChannel { + id: string; + name: string; + type: 'direct' | 'group' | 'broadcast' | 'public'; + participants: string[]; +} + +/** + * Interface for organization statistics + */ +interface OrganizationStats { + totalAgents: number; + departments: string[]; + roles: string[]; + projects: number; + avgDepth: number; +} \ No newline at end of file diff --git a/packages/core/src/generated/git-commit.ts b/packages/core/src/generated/git-commit.ts index 7d6171583..3ff578cdf 100644 --- a/packages/core/src/generated/git-commit.ts +++ b/packages/core/src/generated/git-commit.ts @@ -6,5 +6,5 @@ // This file is auto-generated by the build script (scripts/generate-git-commit-info.js) // Do not edit this file manually. -export const GIT_COMMIT_INFO = 'de535143f'; +export const GIT_COMMIT_INFO = '58a337665'; export const CLI_VERSION = '0.1.0'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5de66d44c..dfbfc3f3f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -127,3 +127,6 @@ export { Storage } from './config/storage.js'; // Export test utils export * from './test-utils/index.js'; + +// Export agents +export { AgenticSocietyManager } from './agents/AgenticSocietyManager.js'; diff --git a/quick-install.sh b/quick-install.sh new file mode 100755 index 000000000..03c074033 --- /dev/null +++ b/quick-install.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Quick install script for DAN CLI + +# Detect OS and run appropriate installer +if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then + echo "Windows detected. Please run: powershell -ExecutionPolicy Bypass -File install-dan-cli.ps1" +elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "macOS detected." + ./install-dan-cli.sh +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "Linux detected." + ./install-dan-cli.sh +else + echo "Unknown OS. Please run the appropriate installation script manually." + exit 1 +fi \ No newline at end of file