518 lines
19 KiB
Markdown
518 lines
19 KiB
Markdown
# DivaCode — AI Being Platform Action Plan
|
||
|
||
**Language:** Go
|
||
**Inference:** llama.cpp (via `go-llama.cpp` bindings or llama.cpp server API)
|
||
**Inspiration:** OpenHer (AI Being), Agent Diva (modular agent architecture), nanobot (lightweight design)
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
```
|
||
User
|
||
│
|
||
├── TUI Terminal (Bubble Tea / gum)
|
||
└── Matrix Adapter (future)
|
||
│
|
||
┌─────┴──────────────────────────────────┐
|
||
│ Core Agent Loop │
|
||
│ (cmd/divad) │
|
||
│ - Message routing │
|
||
│ - MCP tool invocation │
|
||
│ - Tool execution & response │
|
||
└─────┬──────────────────────────────────┘
|
||
│
|
||
┌─────┴──────────────────────────────────┐
|
||
│ Companion Layer │
|
||
│ (internal/companion/) │
|
||
│ ├─ Personality Engine (traits, drift) │
|
||
│ ├─ Relationship Engine (metrics) │
|
||
│ ├─ Reflection Engine (diary, summar.) │
|
||
│ └─ Prompt Composer (dynamic context) │
|
||
└─────┬──────────────────────────────────┘
|
||
│
|
||
┌─────┴──────────────────────────────────┐
|
||
│ Memory Layer │
|
||
│ (internal/memory/) │
|
||
│ ├─ Short-term (ephemeral context) │
|
||
│ ├─ Long-term (SQLite vector store) │
|
||
│ ├─ Episodic / Diary (sqlite) │
|
||
│ └─ Self-memory (agent's own state) │
|
||
└─────┬──────────────────────────────────┘
|
||
│
|
||
┌─────┴──────────────────────────────────┐
|
||
│ llama.cpp Inference │
|
||
│ (internal/llama/) │
|
||
│ - llama.cpp server HTTP API client │
|
||
│ - Context window management │
|
||
│ - Token streaming │
|
||
└────────────────────────────────────────┘
|
||
|
||
Skill / Tool Layer (MCP):
|
||
├── Web search / fetch
|
||
├── Obsidian vault access
|
||
├── Gitea integration
|
||
├── Budget tracking (Actual)
|
||
└── Custom MCP tools
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 0 — Foundation & Scaffolding
|
||
|
||
**Goal:** Project skeleton, Go module, llama.cpp server connectivity.
|
||
|
||
- [ ] Initialize Go module: `git.db123.ir/db123/divacode`
|
||
- [ ] Project structure (per Holy Code Bible conventions):
|
||
|
||
```
|
||
divacode/
|
||
├── cmd/
|
||
│ └── divad/ # Main agent daemon (TUI entrypoint)
|
||
├── internal/
|
||
│ ├── companion/ # Personality, relationship, reflection
|
||
│ ├── memory/ # Memory stores & retrieval
|
||
│ ├── llama/ # llama.cpp client
|
||
│ ├── agent/ # Core agent loop
|
||
│ ├── tools/ # MCP tool definitions
|
||
│ ├── config/ # Config loading
|
||
│ └── storage/ # SQLite helpers
|
||
├── pkg/ # Reusable shared library
|
||
├── migrations/ # SQLite schema migrations
|
||
├── .env.example
|
||
├── Makefile
|
||
├── go.mod / go.sum
|
||
└── README.md
|
||
```
|
||
|
||
- [ ] `config/` package: load `.env` with `caarlos0/env` or `joho/godotenv`
|
||
- [ ] `storage/` package: SQLite wrapper with `modernc.org/sqlite` (pure Go, no CGO)
|
||
- [ ] Run `llama.cpp` server in subprocess or connect to existing: `llama-server -m model.gguf --port 8080`
|
||
- [ ] `internal/llama/` client: HTTP API wrapper for llama.cpp server (`POST /completion`, `GET /health`)
|
||
- [ ] Test: ping llama.cpp health endpoint, send a test completion
|
||
|
||
**Go dependencies seeded:**
|
||
|
||
- `modernc.org/sqlite` — pure-Go SQLite
|
||
- `github.com/charmbracelet/bubbletea` — TUI framework
|
||
- `github.com/charmbracelet/bubbles` — TUI components
|
||
- `github.com/charmbracelet/lipgloss` — TUI styling
|
||
- `github.com/caarlos0/env/v11` — config from env
|
||
|
||
---
|
||
|
||
## Phase 1 — Core Agent Loop
|
||
|
||
**Goal:** A minimal conversational loop with llama.cpp, context management, and a TUI chat interface.
|
||
|
||
- [ ] `internal/agent/` — core loop:
|
||
- Receives user input → appends to context window
|
||
- Calls llama.cpp with context + system prompt
|
||
- Streams tokens back to TUI
|
||
- Appends assistant response to context
|
||
- [ ] Context window manager:
|
||
- Track total token count
|
||
- Implement sliding window / summarization when context exceeds limit
|
||
- [ ] `cmd/divad/main.go` — Bubble Tea TUI:
|
||
- Chat view (message list): user messages right-aligned, assistant left-aligned
|
||
- Input bar at bottom
|
||
- Scrollable message history
|
||
- Hotkey: `Ctrl+C` quit, `Ctrl+L` clear context
|
||
- [ ] System prompt loaded from config (`config.toml` or env var)
|
||
- [ ] Persist conversation history to SQLite (`conversations` table, `messages` table)
|
||
- [ ] **Test:** Start `divad`, type messages, verify responses stream correctly, verify persistence across restarts
|
||
|
||
**Schema (`migrations/001_init.up.sql`):**
|
||
|
||
```sql
|
||
CREATE TABLE conversations (
|
||
id TEXT PRIMARY KEY, -- UUID v7
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE messages (
|
||
id TEXT PRIMARY KEY,
|
||
conversation_id TEXT NOT NULL REFERENCES conversations(id),
|
||
role TEXT NOT NULL CHECK(role IN ('user','assistant','system')),
|
||
content TEXT NOT NULL,
|
||
token_count INTEGER,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE context_state (
|
||
conversation_id TEXT PRIMARY KEY REFERENCES conversations(id),
|
||
token_count INTEGER NOT NULL DEFAULT 0,
|
||
summary TEXT
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2 — Personality Engine
|
||
|
||
**Goal:** Stable personality traits stored in SQLite, loaded dynamically as part of system prompt.
|
||
|
||
Based on dumpeddata.md personality architecture.
|
||
|
||
- [ ] `internal/companion/personality.go`:
|
||
- Trait struct: `Humor`, `Curiosity`, `Playfulness`, `Formality`, `Affection` (float64 0.0–1.0)
|
||
- Load/save from SQLite (`personality_traits` table)
|
||
- Bounded ranges: `Humor 0.6 ± 0.2`, `Curiosity 0.8 ± 0.1` (prevents drift)
|
||
- [ ] `personality_traits` table:
|
||
|
||
```sql
|
||
CREATE TABLE personality_traits (
|
||
id TEXT PRIMARY KEY,
|
||
trait TEXT NOT NULL UNIQUE,
|
||
value REAL NOT NULL DEFAULT 0.5,
|
||
min_value REAL NOT NULL DEFAULT 0.0,
|
||
max_value REAL NOT NULL DEFAULT 1.0,
|
||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
```
|
||
|
||
- [ ] `Prompt Composer` (`internal/companion/promptbuilder.go`):
|
||
- Reads current traits + relationship state + recent memory
|
||
- Generates dynamic system prompt section:
|
||
|
||
```
|
||
Current personality:
|
||
- Curious (0.82)
|
||
- Moderately playful (0.44)
|
||
- Technical (0.70)
|
||
|
||
Interaction preferences:
|
||
- Use concise language
|
||
- Ask follow-up questions
|
||
- Reference past conversations naturally
|
||
```
|
||
|
||
- [ ] Integrate into agent loop: prompt composer modifies system prompt per turn
|
||
- [ ] **Test:** Verify traits are persisted, loaded, and reflected in prompt
|
||
|
||
---
|
||
|
||
## Phase 3 — Long-Term Memory (RAG)
|
||
|
||
**Goal:** Embedding-based retrieval for factual memory across conversations.
|
||
|
||
- [ ] `internal/memory/`:
|
||
- Embedding subpackage: call llama.cpp embedding endpoint (`POST /embedding`) to generate vectors
|
||
- SQLite vector storage: store embeddings as BLOBs or use `sqlite-vec` extension
|
||
- Memory entry schema:
|
||
|
||
```sql
|
||
CREATE TABLE memories (
|
||
id TEXT PRIMARY KEY,
|
||
content TEXT NOT NULL,
|
||
embedding BLOB, -- float32 vector
|
||
source TEXT, -- 'conversation', 'reflection', 'user_input'
|
||
importance REAL DEFAULT 0.5, -- 0.0–1.0
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||
last_accessed TEXT
|
||
);
|
||
```
|
||
|
||
- [ ] Memory retrieval:
|
||
- On each user message, search top-K similar memories via cosine similarity
|
||
- Include retrieved memories in context window
|
||
- Implement temporal decay (older memories scored lower)
|
||
- [ ] Importance scoring: messages flagged as important (user says "remember this", emotional content) get higher importance
|
||
- [ ] **Test:** Store memories, restart agent, ask about stored info, verify recall
|
||
|
||
---
|
||
|
||
## Phase 4 — Episodic Diary & Reflection Engine
|
||
|
||
**Goal:** Daily diary entries + periodic self-reflection for narrative continuity.
|
||
|
||
Based on dumpeddata.md: "The diary becomes the emotional continuity layer."
|
||
|
||
- [ ] `internal/companion/reflection.go`:
|
||
- Diary entry at end of each conversation session (or nightly):
|
||
- Summarize topics discussed
|
||
- Note user mood/emotions
|
||
- Record what the agent learned
|
||
- `diary_entries` table:
|
||
|
||
```sql
|
||
CREATE TABLE diary_entries (
|
||
id TEXT PRIMARY KEY,
|
||
date TEXT NOT NULL, -- YYYY-MM-DD
|
||
summary TEXT NOT NULL,
|
||
mood TEXT,
|
||
topics TEXT, -- JSON array
|
||
observations TEXT, -- JSON array
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
```
|
||
|
||
- [ ] Reflection Engine:
|
||
- Periodic analysis (every N conversations or daily)
|
||
- Examines recent diary entries for patterns
|
||
- Generates observations:
|
||
- "User discussed Go development frequently."
|
||
- "User responds positively to light humor."
|
||
- Observations stored in `observations` table
|
||
|
||
```sql
|
||
CREATE TABLE observations (
|
||
id TEXT PRIMARY KEY,
|
||
content TEXT NOT NULL,
|
||
confidence REAL DEFAULT 0.5,
|
||
category TEXT, -- 'topic_interest', 'interaction_style', 'mood_pattern'
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||
applied INTEGER DEFAULT 0 -- whether fed into personality update
|
||
);
|
||
```
|
||
|
||
- [ ] Reflection → Personality update pipeline:
|
||
- Observations accumulate → periodic analysis → trait drift candidates → bounded approval → personality update
|
||
- Max 0.5% trait change per day, max 5% per month
|
||
- [ ] **Test:** Simulate conversations across multiple "days", verify diary entries, verify trait drift remains bounded
|
||
|
||
---
|
||
|
||
## Phase 5 — Relationship Engine
|
||
|
||
**Goal:** Track relationship dynamics separate from factual memory.
|
||
|
||
Based on dumpeddata.md relationship engine design.
|
||
|
||
- [ ] `internal/companion/relationship.go`:
|
||
- Metrics: `Familiarity`, `Trust`, `SharedTopics`, `InsideJokes`
|
||
- Stored in `relationship_metrics` table:
|
||
|
||
```sql
|
||
CREATE TABLE relationship_metrics (
|
||
id TEXT PRIMARY KEY,
|
||
metric TEXT NOT NULL UNIQUE,
|
||
value REAL NOT NULL DEFAULT 0.0,
|
||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE shared_topics (
|
||
id TEXT PRIMARY KEY,
|
||
topic TEXT NOT NULL UNIQUE,
|
||
count INTEGER NOT NULL DEFAULT 1,
|
||
last_discussed TEXT
|
||
);
|
||
|
||
CREATE TABLE inside_jokes (
|
||
id TEXT PRIMARY KEY,
|
||
joke TEXT NOT NULL,
|
||
context TEXT,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
```
|
||
|
||
- [ ] Update rules:
|
||
- `Familiarity` increases with conversation frequency & duration
|
||
- `Trust` increases on successful interactions, decreases on ignored requests
|
||
- `SharedTopics` tracked via keyword extraction from conversation
|
||
- `InsideJokes` manually taggable or auto-detected via repetition
|
||
- [ ] Relationship state fed into Prompt Composer for dynamic relationship context
|
||
- [ ] **Test:** Verify relationship metrics update and persist
|
||
|
||
---
|
||
|
||
## Phase 6 — Autonomous Behavior (Scheduler)
|
||
|
||
**Goal:** Agent-initiated actions — messaging first, diary writing, periodic reflection.
|
||
|
||
Based on dumpeddata.md: "The missing piece: agency."
|
||
|
||
- [ ] `internal/agent/scheduler.go`:
|
||
- Background goroutine with configurable tick interval (default: 15 min)
|
||
- On each tick, evaluate:
|
||
- Current mood (derived from recent interactions)
|
||
- Time since last conversation
|
||
- Unresolved topics
|
||
- Loneliness / boredom score
|
||
- Decision tree:
|
||
|
||
```
|
||
if time_since_last_contact > 48h && trust > 0.6:
|
||
→ send "Haven't heard from you in a while..." message
|
||
if mood == "curious" && unfinished_topics exists:
|
||
→ send follow-up question
|
||
if energy < 20:
|
||
→ stay silent (don't respond immediately)
|
||
```
|
||
|
||
- [ ] Mood system (`internal/companion/mood.go`):
|
||
- Mood derivation from recent interactions + relationship state
|
||
- Moods: `curious`, `playful`, `thoughtful`, `annoyed`, `tired`, `neutral`
|
||
- Mood influences response style in prompt
|
||
- [ ] Energy system: depletes with activity, recovers with idle time
|
||
- [ ] Autonomous diary: scheduled nightly entry generation
|
||
- [ ] **Test:** Simulate 48h idle, verify agent initiates contact
|
||
|
||
---
|
||
|
||
## Phase 7 — MCP Tools & Integrations
|
||
|
||
**Goal:** Extensible tool system for web search, file access, external services.
|
||
|
||
- [ ] `internal/tools/mcp.go` — MCP protocol client:
|
||
- Connect to MCP servers via stdio or TCP
|
||
- List available tools
|
||
- Execute tool calls
|
||
- Parse results into context
|
||
- [ ] Built-in tools (no MCP server needed):
|
||
- `web_search` — search via configurable search API
|
||
- `web_fetch` — fetch URL content
|
||
- `note` — save/read from local notes
|
||
- [ ] MCP-managed tools (external servers):
|
||
- Obsidian vault access (via `enquire-mcp` or custom)
|
||
- Gitea/Forgejo operations
|
||
- Actual Budget integration
|
||
- [ ] Tool call loop in agent:
|
||
- LLM decides to call tool → agent executes → result appended to context → LLM generates final response
|
||
- [ ] **Test:** `web_search` tool, verify agent can search, summarize, cite
|
||
|
||
---
|
||
|
||
## Phase 8 — Matrix Integration
|
||
|
||
**Goal:** Agent lives as a Matrix bot, accessible via DM.
|
||
|
||
- [ ] Matrix bot with `mautrix-go` (Go Matrix client library)
|
||
- [ ] `internal/channel/matrix.go`:
|
||
- Listen for invitations, join rooms
|
||
- Handle DMs (1-on-1)
|
||
- Typing indicators, read receipts
|
||
- Message parsing & sending
|
||
- [ ] Bot identity: `@diva:your-homeserver.tld`
|
||
- [ ] Config: homeserver URL, bot user token in env
|
||
- [ ] TUI and Matrix run concurrently — messages sync between both
|
||
- [ ] **Test:** Send DM to bot, verify response, verify cross-session memory
|
||
|
||
---
|
||
|
||
## Phase 9 — Self-Memory & Identity
|
||
|
||
**Goal:** Agent remembers its own state, promises, strategies, and mistakes.
|
||
|
||
Based on dumpeddata.md: "Add self-memory — the agent remembers itself as well as the user."
|
||
|
||
- [ ] `internal/memory/self_memory.go`:
|
||
- `agent_promises` table:
|
||
|
||
```sql
|
||
CREATE TABLE agent_promises (
|
||
id TEXT PRIMARY KEY,
|
||
promise TEXT NOT NULL,
|
||
context TEXT,
|
||
fulfilled INTEGER DEFAULT 0,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||
deadline TEXT
|
||
);
|
||
```
|
||
|
||
- `agent_strategies` table: what worked / didn't work
|
||
|
||
```sql
|
||
CREATE TABLE agent_strategies (
|
||
id TEXT PRIMARY KEY,
|
||
situation TEXT NOT NULL,
|
||
strategy TEXT NOT NULL,
|
||
outcome TEXT, -- 'success', 'failure', 'neutral'
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
```
|
||
|
||
- `agent_mistakes` table: errors and learnings
|
||
- [ ] Integrate into Prompt Composer: recent promises, relevant strategies
|
||
- [ ] **Test:** Ask agent "What did you promise me yesterday?" → verify recall
|
||
|
||
---
|
||
|
||
## Phase 10 — Safety Guardrails & Identity Stability
|
||
|
||
**Goal:** Prevent personality drift, maintain core safety rules.
|
||
|
||
Based on dumpeddata.md safety rules and Holy Code Bible security laws.
|
||
|
||
- [ ] `internal/companion/safety.go`:
|
||
- Core Identity (never changes): values, communication style, boundaries, role definition
|
||
- State (dynamic): mood, interests, current projects, relationship opinions
|
||
- Separation: identity.yaml (read-only) vs state.json (writable)
|
||
- [ ] Personality safety rules:
|
||
- Traits bounded within configured min/max
|
||
- Core safety rules override any personality state
|
||
- Permission policies cannot be modified by personality
|
||
- No tool access changes via personality
|
||
- [ ] Prompt injection protection:
|
||
- User messages wrapped in separator tokens
|
||
- System prompt templates with strict sections
|
||
- Regular expression filters for prompt injection patterns
|
||
- [ ] **Test:** Attempt prompt injection, verify guardrails hold
|
||
|
||
---
|
||
|
||
## Phase 11 — Polish, Deployment & Automation
|
||
|
||
**Goal:** Production-ready deployment, backup, observability.
|
||
|
||
- [ ] Systemd service (or Docker Compose) for:
|
||
- `divad` (agent daemon with TUI + Matrix)
|
||
- `llama.cpp` server
|
||
- (Optional) external MCP servers
|
||
- [ ] Makefile targets:
|
||
- `make build` — build binary
|
||
- `make run` — run with default config
|
||
- `make test` — run all tests
|
||
- `make lint` — `golangci-lint run`
|
||
- `make vet` — `go vet ./...`
|
||
- [ ] Automated backups:
|
||
- SQLite databases: `divacode.db`, `companion.db`
|
||
- Llama model files
|
||
- Cron job: `0 3 * * * tar -czf backup-$(date +\%F).tar.gz /path/to/data`
|
||
- [ ] Logging: structured JSON logs via `log/slog`
|
||
- [ ] Metrics: Prometheus endpoint (`GET /metrics`) via `prometheus/client_golang`
|
||
- [ ] Config hardening: `.env.example`, required vars validation at startup
|
||
- [ ] **Test:** Full restart cycle, verify all state restored
|
||
|
||
---
|
||
|
||
## Phase 12 — Future Enhancements (Optional)
|
||
|
||
- [ ] Voice interface: whisper.cpp (STT) + piper (TTS)
|
||
- [ ] VRM/Live2D avatar rendering (like Open-LLM-VTuber, Soul of Waifu)
|
||
- [ ] Multi-user support with per-user relationship tracking
|
||
- [ ] Multi-room Matrix support
|
||
- [ ] Plugin system for community-contributed tools
|
||
- [ ] WebUI dashboard (like nanobot's WebUI)
|
||
- [ ] Mobile companion via Matrix (any Matrix client works)
|
||
- [ ] Home Assistant integration for physical automation
|
||
|
||
---
|
||
|
||
## Database Summary
|
||
|
||
| Database | Purpose | Tables |
|
||
| -------------- | --------------- | ------------------------------------------------------- |
|
||
| `divacode.db` | Core agent data | `conversations`, `messages`, `context_state` |
|
||
| `divacode.db` | Memory | `memories` |
|
||
| `companion.db` | Personality | `personality_traits` |
|
||
| `companion.db` | Relationship | `relationship_metrics`, `shared_topics`, `inside_jokes` |
|
||
| `companion.db` | Reflection | `diary_entries`, `observations` |
|
||
| `companion.db` | Self-memory | `agent_promises`, `agent_strategies`, `agent_mistakes` |
|
||
|
||
---
|
||
|
||
## Key Principles (from dumpeddata.md & Holy Code Bible)
|
||
|
||
1. **Build the smallest loop first** — Matrix → Memory → LLM → Diary → Reflection. Get that working before adding complexity.
|
||
2. **Personality is data, not prompt text** — traits stored in SQLite, composed dynamically.
|
||
3. **Memory ≠ Personality** — factual memory (Engram-style) is separate from relationship/emotional state.
|
||
4. **Slow evolution** — max 0.5% trait change/day, max 5%/month. Fast evolution feels schizophrenic.
|
||
5. **Core Identity is stable** — identity.yaml never changes much; state.json changes constantly.
|
||
6. **Diary before reflection** — the nightly diary is the emotional continuity layer, more important than vector search.
|
||
7. **Autonomous messaging** — the scheduler is what makes the character feel alive, not the LLM intelligence.
|
||
8. **No secrets in code** — all config via env vars, `.env` in `.gitignore`.
|
||
9. **Error handling** — wrap errors with `fmt.Errorf("...: %w")`, never discard.
|
||
10. **Test as you go** — `go test -race ./...` before every push.
|