picoclaw/pkg/swarm/runtime/memory_tools.go
Danieldd28 52e3470e06 feat: implement swarm agents with hierarchical collaboration and vector memory
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.
2026-02-10 21:53:43 +07:00

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
}