An open-source Rust engine for AI companions that feel real: persistent memory, an evolving relationship model, and a decision engine that keeps a persona in character across thousands of turns.
eros-engineis the companion-chat core behind Eros Chat, extracted into a standalone service. It turns conversation into durable state — a structured user profile, two-layer long-term memory, and a six-dimensional affinity model — so a persona behaves like the same person each time a user comes back.
Most AI character apps treat memory as text appended to a prompt and relationship as a paragraph of instructions. That can work in a demo, but behavior drifts over long sessions, breaks character, and becomes hard to debug. eros-engine moves those concerns into explicit, inspectable state, so a companion feels like a real person — remembering you and responding to the state of the relationship — and stays in character turn after turn, because behavior is decided, not improvised.
Five pillars support this:
- 🧠 Two-layer memory — profile memory (stable user facts) and relationship memory (shared moments, callbacks, open threads) in Postgres + pgvector, so the companion remembers you across sessions and personas. → Memory layers
- 💞 Six-axis affinity + ghost mechanics — a numeric relationship vector (warmth, trust, intimacy, intrigue, patience, tension) updated with EMA smoothing and real-time decay; it reshapes tone, depth, and behavior over time, and can even decide not to reply. → Affinity model · Ghost mechanics
- 🎭 Persona Decision Engine (PDE) — selects each turn's action (reply, ghost, or send a photo) and inner state — rules-based by default, with an opt-in LLM judge. This keeps replies human and in character instead of sounding like a generic assistant; judge calls are audited to
companion_decision_events. → Model config - 🧩 Structured user insight — a JSONB profile (city, occupation, interests, MBTI signals, emotional needs, life rhythm, matching preferences) with a weighted
training_level, queryable by downstream products for matchmaking, onboarding, analytics, or gating. → API reference - ⚡ Built for fluent companion chat — token-by-token SSE streaming; image understanding (the user can send a photo) and companion-sent image generation (
reply_image/reply_text_image); per-request prompt traits and tiers; OpenRouter-backed routing with per-task model selection (fixed / round-robin / weighted, plus a fallback chain) and full call auditing. → API reference · Model config
This is not a generic agent framework. It is a focused engine for products where one persona talks to the same user across many sessions: AI companions, journaling companions, coaching agents, language tutors, and character chat.
┌─────────────────────────────────────────────────────────┐
│ /comp/* HTTP routes ← Supabase JWT middleware │
│ │ │
│ ▼ │
│ pipeline orchestrator: load → PDE → handler → chat → post│
│ │ │
│ ┌───────────────────────────────────────┴────────┐ │
│ │ post-process, spawned after reply │ │
│ │ • affinity: persist 6D delta + EMA │ │
│ │ • memory: Voyage embed → pgvector upsert │ │
│ │ • insight: extract facts → JSONB merge │ │
│ └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘The workspace is split into four crates:
| Crate | Role |
|---|---|
eros-engine-core |
Pure domain logic: affinity math, ghost decision, PDE, persona types. Zero I/O. |
eros-engine-llm |
OpenRouter chat client, Voyage embedding client, TOML model-config loader. |
eros-engine-store |
Postgres + pgvector persistence, with all tables under the engine schema. |
eros-engine-server |
Axum HTTP service, Supabase JWT middleware, OpenAPI docs, and pipeline wiring. |
You can run eros-engine-server as an HTTP API, or embed core + llm + store directly in your own Rust service. See Architecture for crate boundaries, pipeline phases, and data flow.
The three library crates are on crates.io (core · store · llm):
cargo add eros-engine-core eros-engine-store eros-engine-llm[dependencies]
eros-engine-core = "0.6"
eros-engine-store = "0.6" # only if you want the Postgres + pgvector layer
eros-engine-llm = "0.6" # only if you want the OpenRouter + Voyage clientseros-engine-server is intentionally not published to crates.io — run it as a Docker image (below).
linux/amd64 images for eros-engine-server are published to GitHub Container Registry for every v* tag (need arm64? Build it yourself from docker/Dockerfile):
docker pull ghcr.io/etherfunlab/eros-engine:0.6.7
# or track the latest tagged release
docker pull ghcr.io/etherfunlab/eros-engine:latestMinimal run (you bring Postgres + your own .env):
docker run --rm -p 8080:8080 --env-file .env \
ghcr.io/etherfunlab/eros-engine:0.6.7 serveThe docker/Dockerfile is the same artifact used to build this image. Deploy it on any container host. See Deploying.
- Architecture — crate boundaries, pipeline phases, data flow.
- Affinity model — six dimensions, EMA, time decay, relationship labels.
- Ghost mechanics — score formula, protection rules, examples.
- Memory layers — profile vs relationship memory, Voyage, pgvector retrieval.
- Model config —
model_config.tomlschema, every task (chat, vision, image generation, PDE, filters, extraction), model selection, 0.x stability commitments. - Prompt traits — per-request system-prompt injection and tier allow-lists.
- LLM / OpenRouter audit — per-user / per-session attribution passthrough.
- Deploying — Docker, bring-your-own Postgres / IdP, operational env vars.
- API reference — every
/comp/*endpoint, request fields, and SSE frame layout.
Prerequisites: a Rust toolchain (rust-toolchain.toml), Postgres 16+ with pgvector, an OpenRouter API key, a Voyage API key, and one auth source — Supabase JWKS (SUPABASE_URL) or a legacy SUPABASE_JWT_SECRET (or your own AuthValidator).
git clone https://github.com/etherfunlab/eros-engine
cd eros-engine
cp .env.example .env # fill in DATABASE_URL, OPENROUTER_API_KEY, VOYAGE_API_KEY, and one auth source
cargo run -p eros-engine-server -- migrate
cargo run -p eros-engine-server -- seed-personas examples/personas
cargo run -p eros-engine-server -- serveThe server listens on 0.0.0.0:8080 by default. Scalar API docs are available at /docs; the OpenAPI JSON is at /api-docs/openapi.json. The official Eros Chat web client is closed-source — bring your own UI or embed the crates in another service.
All /comp/* routes require Authorization: Bearer <Supabase JWT> by default (the AuthValidator trait is pluggable for other identity providers). Key endpoints:
POST /comp/chat/start— open a chat session against a persona.POST /comp/chat/{session_id}/message/stream— the chat turn endpoint: token-by-token Server-Sent Events. Optional per-turn fields includetier,prompt_traits,audit,tips_amount_usd(tip the companion),image_url(send the companion a photo), andimage(request a companion-generated image — style / model / aspect ratio / face reference).POST /comp/chat/{session_id}/message/{message_id}/image— write back the storage URL of a companion-generated image.GET /comp/chat/{session_id}/history·GET /comp/chat/{user_id}/sessions·GET /comp/user/{user_id}/profile— history, session list, and the structured insight profile.GET /comp/affinity/{session_id}— debug-only live affinity vector (EXPOSE_AFFINITY_DEBUG=true).
For the full request schema, SSE frame layout (including delta, image, ghost, and error frames), and per-field semantics, see the API reference.
Required env vars: DATABASE_URL, OPENROUTER_API_KEY, VOYAGE_API_KEY, and one auth source — SUPABASE_URL / SUPABASE_JWKS_URL (JWKS, the post-2025 Supabase default) or SUPABASE_JWT_SECRET (legacy HS256). The server refuses to boot if no auth source is set.
Everything else has sane defaults: model routing (MODEL_CONFIG_PATH → model_config.toml), OpenRouter attribution headers, the dreaming-lite / snapshot sweepers, the EMA_INERTIA relationship-difficulty dial, and debug toggles. The full annotated list lives in .env.example; operational guidance is in Deploying, and model routing in Model config.
Not part of the engine today, but on the radar:
- Agents playground — multiple AI personas interacting with each other (and the user) in one session.
- Voice messages — companion-sent and user-sent audio turns.
- Real-time voice conversation — low-latency spoken back-and-forth.
- Video generation — short companion-sent video clips, extending the image executor.
This repository is the conversation, memory, and relationship-state core. It does not include:
- Matchmaking — multi-stage filtering, soft scoring, and agent-to-agent matching simulation remain in the closed-source product.
- Full social UX — onboarding, video, voice, billing, photos, moderation UI, and mobile clients.
- Persona provenance / marketplace logic — commercial product code, not part of the engine.
If you are building a different product, the reusable part is the affinity + memory + insight pipeline.
The example personas under examples/personas/ are written as adult character-chat examples. They can flirt and express desire when the relationship state reaches that point, while still refusing disrespectful or boundary-crossing behavior. If your product needs a SFW default, replace those persona files before deploying.
Per-request behavior can be further adjusted via the prompt_traits field on the message routes — the engine treats the supplied text as opaque, so the policy defining what those traits encode lives entirely in your frontend / middleware.
Read CONTRIBUTING.md. All contributors must accept the CLA through cla-assistant.io on their first PR.
eros-engine is licensed under AGPL-3.0-only. If AGPL does not fit your distribution model, commercial licensing is available: henrylin@etherfun.xyz.