Files
DivaCode/plan.md

518 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.01.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.01.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.