picoclaw/pkg/swarm/memory/sqlite_store.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

126 lines
No EOL
4.3 KiB
Go

package memory
import (
"context"
"database/sql"
"encoding/json"
"github.com/sipeed/picoclaw/pkg/swarm/core"
_ "modernc.org/sqlite"
)
type SQLiteStore struct {
db *sql.DB
}
func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
db, err := sql.Open("sqlite", dbPath)
if err != nil { return nil, err }
s := &SQLiteStore{db: db}
s.init()
return s, nil
}
func (s *SQLiteStore) init() {
s.db.Exec(`CREATE TABLE IF NOT EXISTS swarms (id TEXT PRIMARY KEY, goal TEXT, status TEXT, created_at DATETIME);`)
s.db.Exec(`CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, swarm_id TEXT, parent_id TEXT, role JSON, task TEXT, status TEXT, output TEXT, stats JSON);`)
s.db.Exec(`CREATE TABLE IF NOT EXISTS facts (swarm_id TEXT, content TEXT, confidence REAL, source TEXT, metadata JSON);`)
}
func (s *SQLiteStore) CreateSwarm(ctx context.Context, sw *core.Swarm) error {
_, err := s.db.ExecContext(ctx, "INSERT INTO swarms VALUES (?, ?, ?, ?)", sw.ID, sw.Goal, sw.Status, sw.CreatedAt)
return err
}
func (s *SQLiteStore) GetSwarm(ctx context.Context, id core.SwarmID) (*core.Swarm, error) {
row := s.db.QueryRowContext(ctx, "SELECT id, goal, status, created_at FROM swarms WHERE id=?", id)
var sw core.Swarm
err := row.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt)
return &sw, err
}
func (s *SQLiteStore) UpdateSwarm(ctx context.Context, sw *core.Swarm) error {
_, err := s.db.ExecContext(ctx, "UPDATE swarms SET status=? WHERE id=?", sw.Status, sw.ID)
return err
}
func (s *SQLiteStore) ListSwarms(ctx context.Context, status core.SwarmStatus) ([]*core.Swarm, error) {
rows, _ := s.db.QueryContext(ctx, "SELECT id, goal, status, created_at FROM swarms WHERE status=?", status)
defer rows.Close()
var list []*core.Swarm
for rows.Next() {
var sw core.Swarm
rows.Scan(&sw.ID, &sw.Goal, &sw.Status, &sw.CreatedAt)
list = append(list, &sw)
}
return list, nil
}
func (s *SQLiteStore) CreateNode(ctx context.Context, n *core.Node) error {
role, _ := json.Marshal(n.Role)
stats, _ := json.Marshal(n.Stats)
_, err := s.db.ExecContext(ctx, "INSERT INTO nodes VALUES (?, ?, ?, ?, ?, ?, ?, ?)", n.ID, n.SwarmID, n.ParentID, role, n.Task, n.Status, n.Output, stats)
return err
}
func (s *SQLiteStore) GetNode(ctx context.Context, id core.NodeID) (*core.Node, error) {
row := s.db.QueryRowContext(ctx, "SELECT * FROM nodes WHERE id=?", id)
var n core.Node
var role, stats []byte
row.Scan(&n.ID, &n.SwarmID, &n.ParentID, &role, &n.Task, &n.Status, &n.Output, &stats)
json.Unmarshal(role, &n.Role)
json.Unmarshal(stats, &n.Stats)
return &n, nil
}
func (s *SQLiteStore) UpdateNode(ctx context.Context, n *core.Node) error {
stats, _ := json.Marshal(n.Stats)
_, err := s.db.ExecContext(ctx, "UPDATE nodes SET status=?, output=?, stats=? WHERE id=?", n.Status, n.Output, stats, n.ID)
return err
}
func (s *SQLiteStore) GetSwarmNodes(ctx context.Context, id core.SwarmID) ([]*core.Node, error) {
rows, _ := s.db.QueryContext(ctx, "SELECT * FROM nodes WHERE swarm_id=?", id)
defer rows.Close()
var list []*core.Node
for rows.Next() {
var n core.Node
var role, stats []byte
rows.Scan(&n.ID, &n.SwarmID, &n.ParentID, &role, &n.Task, &n.Status, &n.Output, &stats)
json.Unmarshal(role, &n.Role)
json.Unmarshal(stats, &n.Stats)
list = append(list, &n)
}
return list, nil
}
func (s *SQLiteStore) SaveFact(ctx context.Context, f core.Fact) error {
meta, _ := json.Marshal(f.Metadata)
_, err := s.db.ExecContext(ctx, "INSERT INTO facts VALUES (?, ?, ?, ?, ?)", f.SwarmID, f.Content, f.Confidence, f.Source, meta)
return err
}
func (s *SQLiteStore) SearchFacts(ctx context.Context, id core.SwarmID, q string, limit int, global bool) ([]core.FactResult, error) {
query := "SELECT content FROM facts WHERE swarm_id=? AND content LIKE ? LIMIT ?"
args := []any{id, "%" + q + "%", limit}
if global {
query = "SELECT content FROM facts WHERE content LIKE ? LIMIT ?"
args = []any{"%" + q + "%", limit}
}
rows, _ := s.db.QueryContext(ctx, query, args...)
defer rows.Close()
var res []core.FactResult
for rows.Next() {
var content string
rows.Scan(&content)
res = append(res, core.FactResult{Content: content, Score: 1.0})
}
return res, nil
}
func (s *SQLiteStore) ClearMemory(ctx context.Context, id core.SwarmID) error {
_, err := s.db.ExecContext(ctx, "DELETE FROM facts WHERE swarm_id=?", id)
return err
}