orca.ai/pkg/kernel/kernel.go
大森 6b94476347 Initial commit: Orca Agent Framework
Core features:
- Microkernel architecture with Actor model
- Session management with JSONL persistence
- Tool system (5 built-in tools)
- Skill system with SKILL.md parsing
- Sandbox security execution
- Ollama integration with gemma4:e4b
- Prompt-based tool calling (compatible with native function calling)
- REPL interface

11 packages, all tests passing
2026-05-08 00:55:48 +08:00

393 lines
9.8 KiB
Go

// 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"
"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)
}
// createLLMBackend creates the LLM backend based on configuration.
func (k *Kernel) createLLMBackend() llm.LLM {
baseURL := k.config.Ollama.BaseURL
model := k.config.Ollama.Model
timeout := k.config.Ollama.Timeout
// Allow shorter env var names to override
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
}
// 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
}
// 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
}