Skip to content

etherfunlab/eros-engine

eros-engine

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-engine is 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.

CI License: AGPL v3 Crates.io: core Crates.io: store Crates.io: llm GHCR: eros-engine

English · 中文 · 日本語

Why this exists

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.

Architecture

┌─────────────────────────────────────────────────────────┐
│ /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.

Use as a library

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 clients

eros-engine-server is intentionally not published to crates.io — run it as a Docker image (below).

Run as a Docker image

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:latest

Minimal run (you bring Postgres + your own .env):

docker run --rm -p 8080:8080 --env-file .env \
  ghcr.io/etherfunlab/eros-engine:0.6.7 serve

The docker/Dockerfile is the same artifact used to build this image. Deploy it on any container host. See Deploying.

Documentation

  • 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 configmodel_config.toml schema, 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.

Quickstart

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 -- serve

The 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.

API surface

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/streamthe chat turn endpoint: token-by-token Server-Sent Events. Optional per-turn fields include tier, prompt_traits, audit, tips_amount_usd (tip the companion), image_url (send the companion a photo), and image (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.

Configuration

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_PATHmodel_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.

Roadmap

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.

What is deliberately out of scope

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.

Content note

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.

Contributing

Read CONTRIBUTING.md. All contributors must accept the CLA through cla-assistant.io on their first PR.

License

eros-engine is licensed under AGPL-3.0-only. If AGPL does not fit your distribution model, commercial licensing is available: henrylin@etherfun.xyz.

About

An open-source Rust engine for AI companions with memory, relationship state, and structured user insight.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors