// Package kernel implements the microkernel core of the Orca framework. // // The kernel is the minimal runtime that manages plugin lifecycle, // message routing, and inter-component communication. package kernel import ( "context" "fmt" "io" "log" "os" "sync" "time" "github.com/orca/orca/internal/config" "github.com/orca/orca/pkg/actor" "github.com/orca/orca/pkg/bus" "github.com/orca/orca/pkg/llm" "github.com/orca/orca/pkg/plugin" "github.com/orca/orca/pkg/session" "github.com/orca/orca/pkg/skill" "github.com/orca/orca/pkg/tool" ) // Kernel is the microkernel core of the Orca framework. // // It orchestrates plugin lifecycle, message routing, and inter-component // communication. The kernel initializes and manages: // - Message bus for inter-component communication // - Plugin registry for extensibility // - Session manager for conversation persistence // - Tool manager with built-in tools // - Skill manager for skill-based automation // - Actor system with orchestrator, workers, and LLM agent type Kernel struct { mu sync.RWMutex mb bus.MessageBus registry *plugin.Registry plugins []plugin.Plugin started bool // Integration components config *config.Config sessionMgr *session.Manager toolMgr *tool.Manager skillMgr *skill.Manager actorSystem *actor.System orch *actor.Orchestrator llmAgent *actor.LLMAgent toolWorker *actor.ToolWorker } // New creates a new Kernel instance with default configuration. func New() *Kernel { return NewWithConfig(config.DefaultConfig()) } // NewWithConfig creates a new Kernel instance with the given configuration. func NewWithConfig(cfg *config.Config) *Kernel { if cfg == nil { cfg = config.DefaultConfig() } k := &Kernel{ mb: bus.New(), registry: plugin.NewRegistry(), config: cfg, actorSystem: actor.NewSystem(), } // Initialize session manager store, err := session.NewJSONLStore(cfg.Session.StorageDir) if err != nil { log.Printf("kernel: warning: failed to create session store: %v", err) } else { k.sessionMgr = session.NewManager(store, k.mb) } // Initialize tool manager with all built-in tools k.toolMgr = tool.NewManager() k.registerBuiltinTools() // Initialize skill manager k.skillMgr = skill.NewManager(cfg.Session.StorageDir + "/skills") // Initialize actor system k.initializeActorSystem() return k } // registerBuiltinTools registers all built-in tools with the tool manager. func (k *Kernel) registerBuiltinTools() { tools := []tool.Tool{ tool.NewExecTool(nil), // exec - shell commands tool.NewReadFileTool(), // read_file tool.NewWriteFileTool(), // write_file tool.NewListDirTool(), // list_dir tool.NewSearchFilesTool(), // search_files } for _, t := range tools { if err := k.toolMgr.Register(t); err != nil { log.Printf("kernel: warning: failed to register tool %q: %v", t.Name(), err) } } } // initializeActorSystem sets up the orchestrator, tool worker, and LLM agent. func (k *Kernel) initializeActorSystem() { // Create orchestrator orch, err := k.actorSystem.CreateOrchestrator(k) if err != nil { log.Printf("kernel: warning: failed to create orchestrator: %v", err) return } k.orch = orch // Create tool worker tw, err := k.actorSystem.CreateToolWorker(k.toolMgr) if err != nil { log.Printf("kernel: warning: failed to create tool worker: %v", err) return } k.toolWorker = tw // Create LLM backend ollama := k.createLLMBackend() // Create LLM agent llmAgentID := fmt.Sprintf("llm-%d", len(k.actorSystem.ListAgents())+1) llmOpts := []actor.LLMAgentOption{ actor.WithToolManager(k.toolMgr), actor.WithToolWorker(k.toolWorker), actor.WithWindowSize(k.config.Session.MaxHistory), } if k.sessionMgr != nil { sessionID := "default" if _, err := k.sessionMgr.GetSession(sessionID); err != nil { k.sessionMgr.CreateSession(sessionID, map[string]string{ "source": "kernel", }) } llmOpts = append(llmOpts, actor.WithSessionManager(k.sessionMgr), actor.WithSessionID(sessionID), ) } llmAgent := actor.NewLLMAgent(llmAgentID, ollama, llmOpts...) k.llmAgent = llmAgent // Register LLM agent as orchestrator's worker k.orch.AddWorker(llmAgent) // Also register tool worker as a fallback worker k.orch.AddWorker(tw) } func (k *Kernel) createLLMBackend() llm.LLM { switch k.config.Provider { case config.ProviderDeepSeek: return k.createDeepSeekBackend() default: return k.createOllamaBackend() } } func (k *Kernel) createOllamaBackend() llm.LLM { baseURL := k.config.Ollama.BaseURL model := k.config.Ollama.Model timeout := k.config.Ollama.Timeout if v := os.Getenv("OLLAMA_BASE_URL"); v != "" { baseURL = v } if v := os.Getenv("OLLAMA_MODEL"); v != "" { model = v } if v := os.Getenv("OLLAMA_TIMEOUT"); v != "" { if d, err := time.ParseDuration(v); err == nil { timeout = d } } client := llm.NewOllamaClient( llm.WithBaseURL(baseURL), llm.WithModel(model), llm.WithTimeout(timeout), ) log.Printf("kernel: created Ollama client (model=%s, url=%s)", model, baseURL) return client } func (k *Kernel) createDeepSeekBackend() llm.LLM { baseURL := k.config.DeepSeek.BaseURL model := k.config.DeepSeek.Model apiKey := k.config.DeepSeek.APIKey timeout := k.config.DeepSeek.Timeout if v := os.Getenv("DEEPSEEK_BASE_URL"); v != "" { baseURL = v } if v := os.Getenv("DEEPSEEK_MODEL"); v != "" { model = v } if v := os.Getenv("DEEPSEEK_API_KEY"); v != "" { apiKey = v } if v := os.Getenv("DEEPSEEK_TIMEOUT"); v != "" { if d, err := time.ParseDuration(v); err == nil { timeout = d } } client := llm.NewDeepSeekClient( llm.WithDeepSeekBaseURL(baseURL), llm.WithDeepSeekModel(model), llm.WithDeepSeekAPIKey(apiKey), llm.WithDeepSeekTimeout(timeout), ) log.Printf("kernel: created DeepSeek client (model=%s)", model) return client } // Bus returns the kernel's message bus. func (k *Kernel) Bus() bus.MessageBus { return k.mb } // Registry returns the plugin registry. func (k *Kernel) Registry() *plugin.Registry { return k.registry } // SessionManager returns the session manager. func (k *Kernel) SessionManager() *session.Manager { return k.sessionMgr } // ToolManager returns the tool manager. func (k *Kernel) ToolManager() *tool.Manager { return k.toolMgr } // SkillManager returns the skill manager. func (k *Kernel) SkillManager() *skill.Manager { return k.skillMgr } // ActorSystem returns the actor system. func (k *Kernel) ActorSystem() *actor.System { return k.actorSystem } // Orchestrator returns the orchestrator agent. func (k *Kernel) Orchestrator() *actor.Orchestrator { return k.orch } // LLMAgent returns the LLM agent. func (k *Kernel) LLMAgent() *actor.LLMAgent { return k.llmAgent } // SetStreamWriter sets the writer for streaming LLM output. func (k *Kernel) SetStreamWriter(w io.Writer) { if k.llmAgent != nil { k.llmAgent.SetStreamWriter(w) } } // SendMessage sends a message from a source to the LLM agent. // // This is the primary public API for interacting with the Orca system. // It creates a task request message and sends it through the orchestrator // to the LLM agent for processing. // // Parameters: // - from: the sender identifier (e.g., "user", "cli") // - to: the recipient (use "llm" for the LLM agent) // - content: the message content (plain text) // // Returns the response content as a string, or an error. func (k *Kernel) SendMessage(from, to, content string) (string, error) { if !k.IsRunning() { return "", fmt.Errorf("kernel: kernel is not running") } if k.orch == nil { return "", fmt.Errorf("kernel: orchestrator not initialized") } // Create a task request message msg := bus.Message{ Type: bus.MsgTypeTaskRequest, From: from, To: to, Content: content, } // Send through the orchestrator ctx := context.Background() resp, err := k.orch.Process(ctx, msg) if err != nil { return "", fmt.Errorf("kernel: orchestrator processing failed: %w", err) } // Extract response content switch v := resp.Content.(type) { case string: return v, nil default: return fmt.Sprintf("%v", v), nil } } // InitPlugins loads and initializes skills from the skills directory. func (k *Kernel) InitPlugins() error { if k.skillMgr == nil { return nil } count, err := k.skillMgr.LoadAll() if err != nil { log.Printf("kernel: warning: skill loading had errors: %v", err) } if count > 0 { log.Printf("kernel: loaded %d skills", count) } return nil } // GetPlugin returns a registered plugin by name. func (k *Kernel) GetPlugin(name string) (plugin.Plugin, bool) { return k.registry.Get(name) } // ListPlugins returns all currently registered plugins. func (k *Kernel) ListPlugins() []plugin.Plugin { return k.registry.List() } // RegisterPlugin registers a plugin without starting it. func (k *Kernel) RegisterPlugin(p plugin.Plugin) error { k.mu.Lock() defer k.mu.Unlock() if k.started { return fmt.Errorf("kernel: cannot register plugin %q: kernel already started", p.Name()) } return k.registry.Register(p) } // UnregisterPlugin removes a plugin from the registry. func (k *Kernel) UnregisterPlugin(name string) error { k.mu.Lock() defer k.mu.Unlock() return k.registry.Unregister(name) } // Start initializes all registered plugins and marks the kernel as running. func (k *Kernel) Start() error { k.mu.Lock() defer k.mu.Unlock() if k.started { return fmt.Errorf("kernel: already started") } k.started = true // Initialize plugins plugins := k.registry.List() k.plugins = make([]plugin.Plugin, 0, len(plugins)) for _, p := range plugins { k.registry.SetState(p.Name(), plugin.StateInitialized) if err := p.Init(k); err != nil { log.Printf("kernel: warning: failed to init plugin %q: %v", p.Name(), err) k.registry.SetState(p.Name(), plugin.StateError) continue } k.registry.SetState(p.Name(), plugin.StateRunning) k.plugins = append(k.plugins, p) log.Printf("kernel: plugin %q (%s) initialized", p.Name(), p.Version()) } log.Printf("kernel: started (tools=%d)", k.toolMgr.Count()) return nil } // Stop gracefully shuts down the kernel. func (k *Kernel) Stop() error { k.mu.Lock() defer k.mu.Unlock() if !k.started { return nil } // Stop actor system first if k.actorSystem != nil { if err := k.actorSystem.StopAll(); err != nil { log.Printf("kernel: warning: error stopping actor system: %v", err) } } // Stop plugins for i := len(k.plugins) - 1; i >= 0; i-- { p := k.plugins[i] k.registry.SetState(p.Name(), plugin.StateStopped) if err := p.Shutdown(); err != nil { log.Printf("kernel: warning: error shutting down plugin %q: %v", p.Name(), err) continue } log.Printf("kernel: plugin %q shut down", p.Name()) } k.plugins = nil k.started = false return k.mb.Close() } // IsRunning returns whether the kernel has been started and not yet stopped. func (k *Kernel) IsRunning() bool { k.mu.RLock() defer k.mu.RUnlock() return k.started }