Ctrl K
ComunidadGuías…

README.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Novaberg — The Nova Anima Resonance System

**A cognitive architecture for personal AI — with emergent personality, intrinsic curiosity, and emotional intelligence. Local, private, yours.**

🇩🇪 [Deutsche Version](README.de.md)

---

**Nova** is a local, privacy-focused personal AI assistant built around a cognitive architecture. Not a chatbot — a system of specialized agents, layered memory, emotional intelligence, and an autonomous background agent that keeps thinking when nobody is chatting. Nova develops her own interests, goals, and emotions that shape her conversations and thinking — with her own emotional stream alongside the user's.

**Core principle:** The LLM is a language processor, not a knowledge store. Real knowledge lives in PostgreSQL, Redis, and on the web. Intelligence emerges from simultaneous evaluation across specialized perspectives.

---

## Overview

A single conversational turn runs through **two** graphs, not one:

- **Human Graph** (5 nodes) — Perception → Enricher → EI-Calc → Salience → Dispatcher. Classifies what the user said, loads context, computes emotion and salience, and stores what is worth keeping. It deliberately produces no answer.
- **Character Graph** (18 nodes) — DB-Access → EI-Calc → Enricher → Emotional Gravitation → Reducer → Router → Planner → Agent-Dispatch → Conversational Vector → Responder → Thinker → Tribunal → Evaluate, then Nova's own perception, salience and storage. This is where the answer forms — and where Nova perceives and remembers her own turn the same way she does the user's.

The two are joined by a Redis event queue: the Human Graph leaves an event, a consumer starts a Character Graph run, and the answer reaches the client over a WebSocket. A third graph, the **Agent Graph** (3 nodes), handles turns that agents produce rather than people.

- **Pixie** (background) — the autonomous background agent. Sixteen agents compete by priority: promotion and decay across both memory layers, character distillation, reminders, research, calibration, knowledge gaps, timeline and notes.

Layered memory:

| Layer | Technology | Purpose |
|-------|-----------|---------|
| Short-term (STM) | Redis + vector search | Active thoughts, TTL-based |
| Long-term (LTM) | PostgreSQL | Consolidated memories with Ebbinghaus decay |
| Knowledge Graph | PostgreSQL | Entities, facts, bi-temporal model |
| Character Hash | PostgreSQL | Distilled personality profiles and the character wheels derived from them |

Multi-channel: Desktop client (GTK4) and Telegram bot share the same FastAPI server instance.

---

## Screenshots

### Chat — Cognitive Pipeline in Real Time

![Chat with pipeline stages](images/nova-ui-chat-1.png)

Every message passes through the full cognitive pipeline. The stage indicators below the conversation show each processing step as it happens: Perception classifies intent and emotion, the Router determines the processing path, the Enricher loads relevant context from memory, the Conversational Vector shapes Nova's own conversational intention, and the Responder generates the final answer. This is not decoration — it is the live trace of the cognitive pipeline: five nodes on the perception path, eighteen more on the path that forms the answer.

![Chat — clean conversation](images/nova-ui-chat-2.png)

The same conversation after the pipeline stages fade. Markdown rendering, native emoji support, and distinct visual separation between user and assistant messages. Nova's personality emerges from layered character distillation, not from a static system prompt.

### Emotional Intelligence — Plutchik Octagon

![Emotions panel with radar diagrams](images/nova-ui-emotion-2.png)

Nova's own emotional state across the 8 Plutchik sectors (Joy, Trust, Fear, Surprise, Sadness, Disgust, Anger, Anticipation) — the perspective selector switches between hers and the user's, and both are tracked separately throughout. Two radar diagrams compare the current session profile against the longer-run emotional landscape held in short-term memory. Below, all 16 canonical emotions are listed with their current values. The session radar shows what is happening now; the STM radar shows the emotional fingerprint across weeks of conversation.

### Session — Turn-Level Analysis

![Session panel](images/nova-ui-session-1.png)

Each conversation turn is stored with its analytical metadata: classified intent, detected emotion, and conversational mode. The summary at the top is auto-generated from the session context. This is the raw material the Enricher uses to build situational awareness for the Responder — visible here for calibration and debugging.

### Short-Term Memory (STM)

![STM panel](images/nova-ui-shorttermmemory-1.png)

Nova's short-term memory entries, shown here for user "nova" — Nova's own thoughts. Each entry carries a salience score, thematic tags, a dimension classification, and a TTL countdown. Entries above the promotion gate — 0.94 on the scale shown here, which is 0.7 before the salience curve is applied — are candidates for consolidation into long-term memory via the Pixie background agent. The content is Nova's own perspective: "For Nova, there is hardly anything better than a ripe tomato fresh from the bush."

### Long-Term Memory (LTM)

![LTM panel](images/nova-ui-longtermmemory-1.png)

Consolidated memories that survived the promotion process. Each entry has a weight (reinforced by retrieval — the Testing Effect), a knowledge dimension, and timestamps for creation and last reinforcement. This is the persistent layer — memories here decay according to the Ebbinghaus forgetting curve but are strengthened each time they are retrieved by the Enricher.

### Character — Emergent Personality Profiles

![Character panel — Nova's self-model](images/nova-ui-character-1.png)

Five distilled personality profiles, shown here for Nova herself. These are not hand-written descriptions but the output of Pixie's character distillation agent, which periodically compresses short-term and long-term memory into structured profiles. The Core profile captures who Nova has become; the Adaptive profile reflects her current preoccupations; Intentions and Emotions describe her communication patterns and emotional baseline; the Relationship profile models her perception of the user. All five layers feed into the Responder's identity block.

### Character Wheels — the part that acts

![Character panel — the two character wheels](images/nova-ui-character-2.png)

Two **character wheels** are derived from those profiles, and they are the part that acts rather than describes. Each asks a set of single questions against the distilled character — twelve for attachment, ten for initiative — which an LLM rates as absent, hinted or pronounced; the resulting factor is then *computed*, never estimated. Attachment weighs how much the other person's concerns count when Nova decides what to remember; initiative shifts the axis that decides who is leading the conversation.

Every rating is stored alongside the number, so each factor can be recalculated by hand — the spoke list under each chart is that derivation, not a decoration. The orange marker shows how far the result sits from the hub, scaled against the span on its own side. That matters: a wheel can land *exactly* on its neutral point while the shape leans clearly to one side, which means two groups of spokes are cancelling each other out. Both wheels therefore carry a provenance field, because a measured neutral value must not look like a value that was never taken at all.

### Conversational Vector — how Nova decides what kind of turn to make

![Conversational vector panel](images/nova-ui-vector-1.png)

Before the Responder writes anything, the conversation is placed in a space of 64 sectors grouped into 14 clusters, and the cluster decides which rhetorical strategies are even available. Here the sector is *Werkstatt* — a working, constructive register — and the seven strategies are ranked against it: two fit, two are rare, two are ruled out, and one is the cluster's core. The percentages are Nova's own affinity from her character; the labels are what the landscape permits. Where the two disagree, the landscape wins, and the panel says whether the corridor was kept.

Below it, the initiative axis is broken into the three measures it is computed from, plus the character offset from the wheel above. This is the number that decides whether Nova treats the turn as the user leading or herself leading — and the threshold beside it is the constant still in force, not the one the calibration agent has measured since.

The remaining blocks are the working material: knowledge gaps that qualified this turn, memories the gravitational pull brought in, and the tone the Responder is handed. Nothing here is written by a prompt. All of it is computed, and all of it is visible while it happens.

### Drive — goals that decay

![Goals panel](images/nova-ui-goals-1.png)

Nova's own long-term goals, each with the emotion that carries it and a motivation value that falls over time. Faded entries have decayed past the threshold and no longer pull. These are not configuration — they emerge from what she has been thinking about, and they feed back into what she finds worth remembering.

### Gravity Map — what pulls on what

![Gravity map panel](images/nova-ui-gravitation-1.png)

The same goals as a force-directed map, with the turns of the current conversation drawn against them. A line means the turn's embedding came close enough to a goal to exert pull; solid lines are long-term goals, dashed ones mid-term. The shaded regions are thematic clusters the goals fall into.

This is the clearest picture of why Nova remembers unevenly. A turn that lands inside a region she is already pulled toward is weighted differently from one that does not — not because a rule says so, but because the distance in the embedding space is smaller.

---

## Technology Stack

- **Backend:** Python, FastAPI, LangGraph, APScheduler
- **Databases:** PostgreSQL 16 with pgvector, Redis Stack
- **LLM:** Ollama (local) with Gemma 4 or Mistral Small 3.2; optional Anthropic Claude API
- **Embedding:** `nomic-embed-text-v2-moe` via Ollama
- **Search engine:** SearXNG (Docker)
- **Desktop client:** GTK4 (PyGObject) + WebKitGTK with SSE pipeline visualization
- **Chat integration:** Telegram Bot (long polling, whitelist)

---

## Requirements

- **OS:** Linux (developed on Nobara). Windows/Mac technically possible, untested.
- **Hardware:**
  - **GPU** with ≥ 24 GB VRAM (tested: AMD Radeon 7900 XTX)
  - **RAM** ≥ 64 GB — required, not merely recommended
  - **CPU** with many cores (tested: Ryzen 9 7900X3D)
- **Software:**
  - Docker + Docker Compose
  - Ollama (host-native, not containerized)
  - GTK4, WebKitGTK, PyGObject (pre-installed on Fedora/Nobara)

### Model Footprint

Nova runs three models in parallel — two language models and one for embeddings:

| Model | Execution | Purpose | Size |
|-------|----------|---------|------|
| `gemma4-gpu` | GPU (VRAM) | Chat, agents, responder | ~17 GB |
| `nomic-embed-text-v2-moe` | GPU (VRAM) | Embeddings for STM/LTM/entity resolution | ~1.0 GB |
| `qwen36-cpu` | CPU (RAM) | Pixie — everything in the background, language and analysis alike | ~24 GB |

Gemma runs on the GPU only. The background agent used to split its work across two CPU models, one for language and one for analysis; it now uses a single one for both, which is why the count went from four models to three.

The CPU model sits in RAM alongside PostgreSQL, Redis and the server process, and it is the large one. This is why 64 GB is a requirement rather than a recommendation — with less, the system starts swapping, and swapping destroys Pixie throughput.

Ollama runs host-native by design so the GPU is directly accessible. Containerized services connect via `host.docker.internal`.

---

## Quick Start

### 1. Clone the repository

```bash
git clone https://github.com/Claus2702/novaberg.git
cd novaberg
```

### 2. Set up the project directory

The repository goes inside a project directory alongside runtime files:

```
<project-directory>/
├── novaberg/             # cloned repository
├── searxng/              # SearXNG runtime state (create empty, populated on first start)
├── matrix/               # Matrix runtime state — only for the Matrix channel (step 6)
│   ├── config/           #   AS registration + tokens (fill in from templates)
│   ├── data/             #   Synapse data, homeserver.yaml, signing key
│   └── state/            #   room id and avatar fingerprint (create empty)
├── .env                  # secrets (generate from template)
└── docker-compose.yml    # (generate from template)
```

```bash
cd ..
mkdir searxng
cp novaberg/.env.template .env
cp novaberg/docker-compose.template.yml docker-compose.yml

# Only if you want the Matrix channel (step 6):
cp -r novaberg/matrix matrix
mkdir -p matrix/state
```

### 3. Configure `.env`

Set at minimum:

- `ANTHROPIC_API_KEY` — only if using `LLM_PROFILE=claude`

The Telegram keys (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_USER_MAP`) are **no longer
required**: the Telegram channel was switched off on 2026-08-24 and the service
is gone from the Compose file. `telegram_bot/` is still in the repository — the
service is off, the code is not deleted. Set them only if you restore the
channel; the Compose block is commented in at its old place in
`docker-compose.template.yml`.

### 4. Set up Ollama

Two Ollama instances on different ports (GPU + CPU):

```bash
OLLAMA_HOST=0.0.0.0:11434 ollama serve &    # GPU
OLLAMA_HOST=0.0.0.0:11435 ollama serve &    # CPU
```

Pull all three models:

```bash
# GPU — chat and embedding
ollama pull gemma4-gpu
ollama pull nomic-embed-text-v2-moe

# CPU — Pixie background work
ollama pull qwen36-cpu
```

Modelfiles (quantization, context size, system prompts) are in `ollama/modelfiles/` for reference.

### 5. Start services

```bash
docker compose up -d
```

Services:

- PostgreSQL on `:5432`
- Redis on `:6379`
- SearXNG on `:8080`
- Nova server on `:8000`
- Synapse on `:8008` and the Matrix connector — **only after step 6**; without
  it both containers restart in a loop

The Telegram channel was switched off on 2026-08-24 and is no longer part of
the stack. `telegram_bot/` is still in the repository — the service is off, the
code is not deleted, and the compose block sits as a comment in its old place.

### 6. Matrix channel (optional)

This is the remote channel: Nova reachable from a phone over a VPN, in a real
Matrix client. **It is not a bot.** A bot has exactly one sender, so anything
you type on the desktop would show up in the channel as `[Du] ...` out of
Nova's mouth. Novaberg registers an *application service* instead, which may
send in the name of any user in its namespace — so a desktop utterance appears
as a message from **you**, and Nova's answer as a message from **Nova**. The
reasoning is in `docs/novaberg-matrix-kanal_k.md`.

Four things have to exist before the first start. Nothing here is optional.

**a) The database.** Synapse insists on `C` collation, and this cannot be
changed later without rebuilding the database — do it now, not after the first
error:

```bash
docker compose up -d postgres
docker compose exec -T postgres psql -U ki -d postgres -c \
  "CREATE DATABASE synapse WITH OWNER ki TEMPLATE template0 LC_COLLATE='C' LC_CTYPE='C';"
```

**b) The homeserver config.** Let Synapse generate its secrets and signing key,
then put the template over it and copy the three generated values in:

```bash
docker run --rm -v "$(pwd)/matrix/data:/data" \
  -e SYNAPSE_SERVER_NAME=novaberg.de -e SYNAPSE_REPORT_STATS=no \
  ghcr.io/element-hq/synapse:latest generate

cp matrix/data/homeserver.template.yaml matrix/data/homeserver.yaml
# then edit: registration_shared_secret, macaroon_secret_key, form_secret
```

`server_name` sits inside **every** user and room id and cannot be changed
afterwards. Use a domain, not the host's address — the address comes from DHCP
and moves.

**c) The application service.** Two tokens, freely chosen but long, random and
different from each other. They go into **two** files: the homeserver reads one,
the connector the other, and if they disagree each side rejects the other.

```bash
cp matrix/config/novaberg-as.template.yaml matrix/config/novaberg-as.yaml
cp matrix/config/as-tokens.env.template     matrix/config/as-tokens.env
openssl rand -hex 32   # as_token  -> both files
openssl rand -hex 32   # hs_token  -> both files
```

**d) The two accounts.** One for you, one for Nova:

```bash
docker compose up -d synapse
docker compose exec -T synapse register_new_matrix_user \
  -c /data/homeserver.yaml -u meister -p '<password>' --no-admin
docker compose exec -T synapse register_new_matrix_user \
  -c /data/homeserver.yaml -u nova -p "$(openssl rand -base64 24)" --no-admin
```

Nova's password is never needed — the application service speaks for her
through its `as_token`. Yours is what you log in with.

Then `docker compose up -d` and point a client (FluffyChat, Fractal, Element)
at `http://<host>:8008`, logging in as `meister`. The room is created on the
connector's first run and its id is kept in `matrix/state/` — **without that
volume every restart creates a new room** and the old history stays behind
without anything raising an alarm.

Two things this setup does not do: there is **no TLS** in front of Synapse, so
outside a VPN tunnel your password and every message travel in the clear; and
`matrix/config/avatar-nova.png` is not shipped — drop any image there to give
the character a picture, or leave it out.

### 7. Start the desktop client (optional)

```bash
# Install dependencies (Fedora/Nobara — most are pre-installed)
sudo dnf install -y python3-requests python3-websocket-client python3-markdown

# Start
python3 novaberg/client/main.py
```

---

## Configuration

Central configuration lives in `server/config.py`. All parameters can be overridden via environment variables (see `.env.template`).

**Key switches:**

| Variable | Values | Effect |
|----------|--------|--------|
| `LLM_PROFILE` | `lokal`, `claude` | Switch between Ollama and Anthropic API |
| `OLLAMA_CONNECTOR` | `mistral`, `gemma4`, `qwen36` | Model stack within `lokal`. Default is `qwen36`, which is the set listed above. **The name refers to the background model, not the one that answers** — `gemma4` and `qwen36` both run Gemma on the GPU and differ only on the CPU side. The other two connectors expect CPU models that have to be built first. |

Fine-grained parameters (Pixie intervals, STM thresholds, search limits, etc.) are also documented in `config.py` and controllable via `PIXIE_*`, `KZG_*`, `NOTIZEN_*` variables.

> **Security note (dev-only defaults):**
> Default values in `config.py` and `docker-compose.template.yml` contain PostgreSQL credentials `ki:ki`. These are intended for local development only. For any other deployment, set `POSTGRES_URL` / `POSTGRES_PASSWORD` to secure values in your `.env`. The `.env` file must never be committed to the repository.

---

## Documentation

The architecture is extensively documented in `docs/`:

- `novaberg-architecture.md` — System overview
- `novaberg-graph.md` — Graph architecture and pipeline
- `novaberg-pixie.md` — Background agent
- `novaberg-memory.md` — Memory layers
- `novaberg-ei.md` — Emotional intelligence
- `novaberg-node-*.md` — Individual pipeline nodes (23 documents)
- `novaberg-thinking-curiosity_k.md` — Curiosity and intrinsic motivation
- `novaberg-thinking-drive_k.md` — Drive, goals, and dual-emotion architecture
- `novaberg-roadmap.md` — Project chronicle
- `novaberg-backlog.md` — Future concepts

Architecture documents are in German with English module names.

---

## Troubleshooting

**Ollama unreachable from container:**
Check if `host.docker.internal` resolves. On Linux, `extra_hosts: - "host.docker.internal:host-gateway"` is needed in the Compose file (already included).

**SearXNG returns no results:**
Engines may be blocked by rate limits. Edit the settings file at `searxng/settings.yml` — SearXNG generates it on first start.

**PostgreSQL connection fails:**
The server uses `depends_on.postgres.condition: service_healthy`. If the health check fails, check whether port 5432 is already in use on the host.

**Gemma 4 JSON output truncated:**
Known issue (Ollama #15260). Workaround is implemented in the code (cleanup pipeline in `config.py`).

---

## Status

Nova is under active development. Solo project, no external contributions planned. The roadmap in `docs/novaberg-roadmap.md` shows historical progress, `novaberg-backlog.md` lists open concepts.

---

## License
Licensed under the Apache License, Version 2.0.