This commit introduces a multi-agent Swarm Engine to PicoClaw, enabling complex task orchestration through autonomous collaboration and long-term semantic memory. Key Features: - Actor Model Architecture: Every agent (node) runs in an isolated goroutine, communicating via a lightweight internal Event Bus. - Hierarchical Delegation: Manager nodes can dynamically spawn specialized worker nodes (Researchers, Analysts, etc.) to perform sub-tasks in parallel. - Hybrid Memory System: Combined SQLite for state persistence and Chromem-go for a persistent, cross-swarm Vector Knowledge Base. - Progressive Summarization: Implemented an intelligent memory pruning mechanism that summarizes older context before truncation to preserve findings without exceeding token limits. - Role-Based Access Control (RBAC): Configurable tool policies per role (e.g., Researchers can browse but not execute shell commands). Performance & Scaling: - Low Footprint: Idle memory usage is ~7-8 MB RSS. - Efficient Scaling: During a stress test with 10 concurrent agents performing intensive research, memory usage peaked at only 27 MB. - Per-Node Cost: Each active agent consumes approximately 0.6 MB to 2 MB of physical RAM depending on conversation length and summarization state. - Stability: Successfully handled 10 parallel LLM requests with zero race conditions, showcasing the robustness of the Go-based Actor model. Integration: - Native CLI Support: Added /swarm spawn, /swarm list, and /swarm status commands. - Mermaid Visualization: Support for /swarm viz <id> to generate organizational charts of active agent hierarchies. - Configurable: Roles, models, and security policies are fully customizable via config.json.
128 lines
2.9 KiB
Go
128 lines
2.9 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/swarm/core"
|
|
)
|
|
|
|
type MemorySaveTool struct {
|
|
memory core.SharedMemory
|
|
swarmID core.SwarmID
|
|
}
|
|
|
|
func NewMemorySaveTool(mem core.SharedMemory, swarmID core.SwarmID) *MemorySaveTool {
|
|
return &MemorySaveTool{
|
|
memory: mem,
|
|
swarmID: swarmID,
|
|
}
|
|
}
|
|
|
|
func (t *MemorySaveTool) Name() string {
|
|
return "save_memory"
|
|
}
|
|
|
|
func (t *MemorySaveTool) Description() string {
|
|
return "Save a fact or finding to the swarm's long-term memory for other agents to use."
|
|
}
|
|
|
|
func (t *MemorySaveTool) Parameters() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"content": map[string]interface{}{
|
|
"type": "string",
|
|
"description": "The fact or information to save",
|
|
},
|
|
"tags": map[string]interface{}{
|
|
"type": "string",
|
|
"description": "Comma-separated tags for organization",
|
|
},
|
|
},
|
|
"required": []string{"content"},
|
|
}
|
|
}
|
|
|
|
func (t *MemorySaveTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
|
|
content, ok := args["content"].(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("content is required")
|
|
}
|
|
|
|
tagsStr, _ := args["tags"].(string)
|
|
metadata := map[string]any{
|
|
"tags": tagsStr,
|
|
}
|
|
|
|
fact := core.Fact{
|
|
SwarmID: t.swarmID,
|
|
Content: content,
|
|
Confidence: 1.0, // Self-reported facts are trusted
|
|
Source: "agent_tool",
|
|
Metadata: metadata,
|
|
}
|
|
|
|
if err := t.memory.SaveFact(ctx, fact); err != nil {
|
|
return "", fmt.Errorf("failed to save memory: %w", err)
|
|
}
|
|
|
|
return "Fact saved to memory.", nil
|
|
}
|
|
|
|
type MemorySearchTool struct {
|
|
memory core.SharedMemory
|
|
swarmID core.SwarmID
|
|
global bool
|
|
}
|
|
|
|
func NewMemorySearchTool(mem core.SharedMemory, swarmID core.SwarmID, global bool) *MemorySearchTool {
|
|
return &MemorySearchTool{
|
|
memory: mem,
|
|
swarmID: swarmID,
|
|
global: global,
|
|
}
|
|
}
|
|
|
|
func (t *MemorySearchTool) Name() string {
|
|
return "search_memory"
|
|
}
|
|
|
|
func (t *MemorySearchTool) Description() string {
|
|
return "Search the swarm's long-term memory for relevant facts."
|
|
}
|
|
|
|
func (t *MemorySearchTool) Parameters() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"query": map[string]interface{}{
|
|
"type": "string",
|
|
"description": "The search query",
|
|
},
|
|
},
|
|
"required": []string{"query"},
|
|
}
|
|
}
|
|
|
|
func (t *MemorySearchTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
|
|
query, ok := args["query"].(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("query is required")
|
|
}
|
|
|
|
results, err := t.memory.SearchFacts(ctx, t.swarmID, query, 5, t.global)
|
|
if err != nil {
|
|
return "", fmt.Errorf("search failed: %w", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
return "No relevant facts found.", nil
|
|
}
|
|
|
|
out := "Found facts:\n"
|
|
for _, r := range results {
|
|
out += fmt.Sprintf("- %s (Score: %.2f)\n", r.Content, r.Score)
|
|
}
|
|
return out, nil
|
|
}
|