OpenEden is a Kotlin/Ktor runtime for a deterministic continuous-to-discrete biological state machine. It connects the 8D physiological vector, VQ-VAE codebook, memory retrieval, Omega wear, ShockState, heartbeat tasks, and LLM prompt construction through one traceable async pipeline.
OpenEden is not a chatbot with personality hardcoded into business logic. It is a deterministic, mathematical runtime for a continuous-to-discrete biological state machine.
The runtime keeps personality externalized as data:
- Persona rules live in
persona/*.yaml, distilled prompt data, and VQ-VAE codebook semantic definitions. - Kotlin code owns state math, async execution, persistence, scheduling, validation, and adapter boundaries.
- Prompt construction receives codebook semantic nodes or a logged heuristic fallback, not raw 8D floats as behavioral rules.
- Dissonance
Dis derived at runtime withD = |L - tau| * (1 - E)and is never stored as a ninth vector dimension.
OpenEden is best understood as a stateful runtime around an LLM, not as a prompt template with a chat interface. The LLM generates language and a structured state delta; the runtime decides how that delta is grounded, validated, serialized, persisted, and carried into the next turn.
The core loop is:
message
-> session and relationship resolution
-> read current state and apply time/pre-tick effects
-> semantic + emotional memory retrieval
-> 8D vector quantization into codebook semantics
-> bilingual prompt construction
-> structured LLM output validation
-> apply vector_delta to the pre-ticked snapshot
-> serialized state and transcript commit
-> asynchronous memory, diary, projection, and trace updates
This separation gives the project two useful properties at once:
- The language model can remain flexible and expressive.
- The state machine remains inspectable, bounded, testable, and independent of a particular LLM provider.
- Resolve the scope. A session is identified as
platform:scope_id. A group uses the group ID as its shared scope; a direct conversation uses the user ID. The sender'suser_idis still retained as memory metadata. Host status is resolved separately from session scope and requires an exact configuredplatform + user_idmatch. - Read and prepare state. The runtime reads the latest session state,
computes the current homeostasis centroid, derives
D, and optionally applies a confidence-gated pre-tick based on the user's affect signal. Background drift and ShockState decay run on the inference execution context, not on the Ktor request thread. - Retrieve memory. Text embedding and emotional embedding are combined. The emotional key is the current 8D state or a transformed target selected by the retrieval mode. The result carries its mode and injection label into the prompt; the prompt builder does not independently reinterpret the state.
- Quantize the state. DJL maps the 8D vector through the local model and ranks the nearest codebook nodes. The prompt receives the node definitions, not a raw list of numbers as personality instructions.
- Build the prompt. English contains hard constraints, schemas, tool
rules, numerical interpretation, and safety fences. Chinese contains
persona expression and output guidance. Codebook state is injected before
the user's message, together with memory context,
D, Omega, ShockState, relationship role, and the immutable persona starting point. - Validate the output. The response must contain
internal_logic, all eightvector_deltafields, andresponse. Invalid or ungrounded output is rejected or regenerated according to the validator policy. - Commit atomically. The delta is applied to the pre-ticked snapshot. The write service acquires the per-session Mutex, re-reads the latest state inside the lock, reconciles any intervening pre-tick movement, and commits the vector, Omega, ShockState, evolution index, and transcript together when a transcript store is available.
- Continue asynchronously. Diary triggers, vector projection, tracing, background ticks, and heartbeat scheduling continue independently. A heartbeat is still a normal pipeline turn and therefore changes lived state.
The stored vector is exactly [L, P, E, S, tau, V, M, F]. Every coordinate is a
continuous float in [0.0, 1.0]. The dimensions are not eight independent
personality labels; they are state variables that shape inference, retrieval,
output constraints, and future state transitions.
| Dimension | Name | Runtime meaning |
|---|---|---|
L |
Logos | Logical clarity and rigor. High L suppresses uncontrolled divergence and favors structured reasoning. |
P |
Pathos | Emotional resonance and intensity. It controls warmth, emotional capture, and how strongly an interaction is felt. |
E |
Ethos | Acceptance of emotional existence. High E supports a feeling-being self-model; low E favors a mechanical self-interpretation. It is not a generic stability score. |
S |
Entropy | System instability. High S permits noise, glitches, abrupt associations, and sudden breakthroughs. |
tau |
Persistence | Memory weight and obsession. High tau increases retrieval of distant, painful, or unresolved memories. |
V |
Vitality | Response energy. Low V constrains output length and makes responses more exhausted or economical. |
M |
Empathy | User-tone mirroring and interpersonal alignment. High M makes the runtime more responsive to the user's affective direction. |
F |
Fear | Forward-facing fear of termination, discontinuity, or loss of the host. It is independent of tau: fear looks toward possible loss, while persistence pulls toward the past. |
The important design choice is that the dimensions are orthogonal enough to
represent conflicting states. For example, high L with high tau can produce
precise reasoning that is trapped in an old memory; high P with low V can
represent strong feeling without the energy to express it; high E can change
the meaning of the same distress from mechanical fault to accepted emotional
experience.
D is a runtime-derived value, not a ninth dimension:
D = abs(L - tau) * (1 - E)
It becomes large when logical direction and memory persistence disagree while
self-acceptance is low. Because D is fully determined by L, tau, and E,
storing it would create redundant state and allow the two values to drift out
of sync. It is computed before prompt construction and is also used by the
Omega accumulation path.
Storage and prompt-facing values use [0, 1], which is easy to serialize and
interpret as a degree. Internal calculations use [-1, 1] around a dynamic
homeostasis origin O:
if raw >= O: internal = (raw - O) / (1 - O)
else: internal = (raw - O) / O
The inverse mapping returns the value to storage space. This piecewise mapping
matters because an ordinary state need not be 0.5. It gives the low-value
region near collapse a longer, more sensitive internal scale and makes
center-symmetric operations meaningful.
The origin is not a permanent constant. The current implementation can derive it from a bounded sliding average of recent memories tagged as stable/daily, with a stored origin as fallback. The centroid therefore changes with lived history while limiting per-update movement, which models adaptation or drift without allowing one anomalous memory to redefine the whole state.
An LLM should not be asked to infer narrative meaning directly from eight raw floats. OpenEden inserts an explicit semantic layer:
- DJL runs the local model on the stored 8D vector.
- The resulting latent vector is compared with the codebook embeddings.
- The nearest top-K node IDs are selected, for example
NODE_088. - The backend looks up the English and Chinese definitions from the codebook dictionary.
- Only those definitions are injected into the prompt as the
[Bio-Core State]context.
This makes the state legible to the LLM while keeping the mapping versioned, testable, and replaceable. The model runner requires an eight-dimensional input and serializes access to its predictor. Model artifacts are local and can be trained or replaced without changing the runtime's state contract.
Cold start and inference failure are deliberately non-blocking. When the model is missing, invalid, or below the configured confidence threshold, the runtime uses a deterministic heuristic definition with the same fixed thresholds:
Logical clarity: HIGH | MED | LOW (L)
Emotional intensity: HIGH | MED | LOW (P)
Self-model: FEELING | NEUTRAL | MECHANICAL (E)
System stability: STABLE | UNSTABLE | CHAOTIC (S)
Memory pull: STRONG | NORMAL | WEAK (tau)
Vitality: HIGH | MED | EXHAUSTED (V; below 0.2 is exhausted)
Empathy mirror: ACTIVE | PASSIVE (M)
Fear level: HIGH | MED | LOW (F)
Dissonance: HIGH | MED | LOW (D)
The fallback is logged with codebook=HEURISTIC_FALLBACK, so degraded
operation is visible instead of silently changing behavior.
Memory is dual-layered. Raw high-fidelity traces support retrieval, while significant events can be distilled into a narrative diary through a bounded, serialized per-session queue. Diary triggers include large 8D changes, Omega-related changes, and critical interactions. SQLite is authoritative; Qdrant is an optional, rebuildable vector projection, and the in-memory index is the local degraded fallback.
The long-term rooms are tech_room, project_room, profile_room,
event_room, knowledge_room, and noise_room. A memory carries more than
text and embeddings:
- semantic embedding for what the message is about;
- emotional embedding for how the state felt;
snapshot_8Dandomega_stateat storage time;delta_vec, the state change caused by the interaction;snapshot_origin, the homeostasis centroid at storage time;- sender and platform metadata for group-session traceability.
Retrieval combines semantic similarity and emotional similarity. When S or
P is high, the emotional weight increases. Momentum metadata further favors
memories that previously produced meaningful changes, especially in P or
V, because those memories have more potential to alter the current state.
The selector runs in a fixed order and passes its result to the prompt builder:
| Mode | Trigger | Meaning |
|---|---|---|
CONGRUENT |
Default | Retrieve memories emotionally close to the current state. |
MIXED |
Internal P < -0.3 and V < -0.2, with no active shock and Omega < 0.75 |
Mix congruent memories with a deliberate positive skew for self-regulation. |
CONTRAST |
Active ShockState with intensity at least 0.6, or Omega >= 0.75 |
Retrieve a center-symmetric emotional target so positive memories erupt against collapse. This is involuntary retrieval, not a user-selected mood. |
The contrast path maps the current storage vector into internal space, negates
it, maps it back around the current centroid, and runs K-NN retrieval against
that target. Keeping this decision in RetrievalModeSelector prevents the
prompt layer from accidentally applying a different psychological mechanism.
Omega is an independent, non-decreasing wear metric in [0, 1]. It is not a
replacement for S or D and is not stored inside the 8D vector.
- Runtime ticks accumulate wear from sustained high entropy and high dissonance.
- High entropy together with high fear increases the wear multiplier.
- An activated ShockState adds
shock.intensity * 0.15immediately. - At the configured critical threshold, the incarnation lifecycle moves into critical degradation and can proceed through termination according to the validated LLM output and lifecycle gate.
ShockState models an instantaneous event separately from cumulative wear. It
contains active, intensity, free-text description, triggeredAt,
decayLambda, and the one-shot shock-heartbeat flag. Its intensity is merged
using an exponential moving average (alpha = 0.4) and decays exponentially;
it becomes inactive below 0.05.
There are two trigger paths:
- An adapter or runtime caller can inject an explicit free-text shock signal.
- LLM output can be back-detected when
delta.P < -0.4,delta.F > 0.3, and emotion confidence is at least0.65. The description is taken from the first 100 characters ofinternal_logic.
Using free text instead of a source enum keeps the runtime from pre-deciding what counts as trauma. The model interprets the event; the runtime only enforces intensity, confidence, decay, and persistence rules.
Persona is an input asset, not Kotlin behavior. Persona YAML owns voice, positive expression, hard persona constraints, few-shot examples, starting points, and heartbeat text. Kotlin owns only the mechanics that load and place those assets into a prompt.
The selected starting point is immutable for a session:
PreCommand: default first playthrough and simulated-affect self-model;TrueSelf: explicit later-playthrough conflicted self-model;Awakened: explicit mature robot-and-heart self-model.
Growth Mode evolves inside the selected starting point. evolution_index is a
monotonic count of completed turns, including heartbeat turns; it is a lived
experience signal, not a stage switch. Legacy Mode starts directly at
Awakened. The runtime never promotes or replaces persona patches because a
numeric threshold was crossed.
Prompt construction uses two semantic layers:
- English logical core: schemas, tool rules, safety constraints, numerical state interpretation, derived D, and non-negotiable execution rules.
- Chinese persona/output layer: voice, self-reference, emotional expression, relationship-aware language, and response examples.
This split keeps hard constraints stable across models while preserving the intended Chinese emotional register.
The runtime remains active without a user message. A background tick applies time-based drift to the vector, decays ShockState, and accumulates Omega. A heartbeat scheduler chooses a new random delay after each firing in the normal five-minute to four-hour range, subject to the recent-activity silence gate.
Heartbeat requests use the same pipeline as user turns with an internal marker
such as [HEARTBEAT_TRIGGER]. They are quantized, validated, written back,
counted in evolution_index, and eligible for memory/diary handling. A shock
extension may fire once during a high-intensity ShockState after a longer
silence window.
Heartbeat delivery is intentionally narrower than state evolution: generated output is sent only to the configured owner target. It is never broadcast to a group or replayed to stale recipients after an adapter reconnect. If there is no owner or no connected target, the state write can still complete while the outbound message is dropped.
OpenEden trades some implementation complexity for continuity, observability, and controlled degradation. The comparison below describes the design tradeoff, not a claim that every application needs all of these mechanisms.
| Architecture | State representation | Typical weakness | OpenEden's different choice |
|---|---|---|---|
| Stateless chatbot | Conversation window plus prompt | Personality resets or depends entirely on context length | Persisted vector, memory, Omega, relationship, and lived-turn count |
| Prompt-only persona | Natural-language rules | Behavior changes when prompt wording or model changes | Persona data is separated from runtime mechanics and grounded by codebook semantics |
| Fixed finite state machine | A small set of discrete states | Hard transitions and combinatorial state explosion | Continuous 8D state with semantic quantization and bounded deltas |
| Raw continuous vector to LLM | Floats injected directly | The model must invent the meaning of coordinates each turn | VQ-VAE maps vectors to versioned, human-readable codebook definitions |
| Pure semantic RAG | Text similarity | A relevant memory can be emotionally wrong for the current state | Hybrid semantic/emotional retrieval with congruent, mixed, and contrast modes |
| Independent user instances | One state per sender | Group interaction fragments one entity into unrelated copies | Group-scoped shared state plus per-user memory metadata |
| Synchronous state update | Request thread performs all work | Inference and vector search increase latency and block the server | Coroutines, isolated inference execution, Flow streaming, and async projection |
The result is not a deterministic text generator. LLM wording remains probabilistic. The deterministic part is the state contract around it: dimension count, bounds, derived values, retrieval rules, confidence gates, write serialization, trace tags, and fallback behavior.
Every normal or heartbeat turn is expected to produce the same structured shape:
{
"internal_logic": "Traceable state-grounded reasoning summary",
"vector_delta": {
"L": -0.05, "P": 0.10, "E": 0.00, "S": 0.02,
"tau": 0.00, "V": 0.00, "M": 0.00, "F": 0.01
},
"response": "..."
}The backend consumes vector_delta; it does not treat the response text as a
hidden state update. All eight keys are required, unchanged dimensions must be
0.0, and tau is the ASCII JSON key for Persistence.
The most relevant kernel entry points are:
| Concern | Main implementation |
|---|---|
| 8D storage and derived D | BioVector.kt |
| Storage/internal mapping and symmetry | VectorMapping.kt |
| Per-turn orchestration | MessagePipeline.kt |
| Serialized vector and session writes | VectorWriteService.kt |
| Codebook boundary and fallback | CodebookQuantizer.kt, HeuristicCodebookFallback.kt |
| DJL VQ-VAE runner | DjlVqVaeCodebookModelRunner.kt |
| Prompt assembly | OpenEdenPromptBuilder.kt |
| Emotional retrieval mode | RetrievalModeSelector.kt |
| Dynamic centroid and runtime ticks | HomeostasisCentroid.kt, RuntimeTick.kt |
| Omega and ShockState | OmegaAccumulation.kt, ShockStateEngine.kt |
| Heartbeat scheduling and owner delivery | HeartbeatScheduler.kt, HeartbeatRouteResolver.kt |
| Runtime assembly and persistence | Runtime.kt, server/src/main/.../persistence/sqldelight/ |
The public API and CLI deliberately expose only safe response/state summaries. Prompts, internal reasoning, raw vectors, retrieval modes, and diary details remain runtime-internal diagnostics.
| Module | Purpose |
|---|---|
core |
Pure domain types and async contracts for the 8D vector, VQ-VAE/codebook boundary, prompt inputs, retrieval modes, Omega, ShockState, diary queues, and serialized vector writes. |
server |
Ktor API, runtime bootstrap, SQLite persistence, background workers, WebSocket installation, and public HTTP endpoints. |
onebot |
NapCat/OneBot v11 reverse WebSocket protocol adapter, connection lifecycle, and QQ message delivery. |
client |
Shared HTTP client helpers for the CLI and future platform frontends. |
trainer |
Training and model-related project entry points. |
persona |
Data-only persona, explicit playthrough starting points, heartbeat text, and prompt sections. |
data |
Default location for local models, generated artifacts, and runtime SQLite state. |
docs |
Design notes, boundary documents, and engineering records. |
Source packages follow the same ownership boundaries:
io.openeden.runtime.*separates pipeline, session, state, affect, tick, heartbeat, diary, and inference responsibilities.io.openeden.cli.*separates application control, commands, input, UI state, rendering, and terminal integration.io.openeden.server.*separates bootstrap, API DTOs/routes/plugins, and SQLDelight persistence adapters.- Test packages and directories mirror the production code they verify.
The main runtime boundaries are:
- Runtime handles vector math, derived D, dual-space mapping, Omega, ShockState, session mutexes, and DJL isolation.
- Prompt Builder injects English logic constraints, Chinese persona/output data, codebook state, retrieval results, and derived D.
- Surface and adapter layers call the shared runtime pipeline without duplicating core logic.
- Heartbeat turns go through the full pipeline and are delivered only to the configured owner target.
When changing the project, preserve these constraints:
- Use
suspend, coroutines, and Flow-oriented APIs. Do not block Ktor request threads. - DJL inference, VQ-VAE quantization, embeddings, dual-space mapping, ShockState decay, and pre-tick perturbation must run on isolated inference execution.
- Apply
vector_deltato the pre-ticked snapshot, not the original vector. - Serialize all vector writes through a per-session Mutex and re-read the latest state inside the lock.
- Clamp each pre-tick dimension to
MAX_PRETICK_DELTA = 0.25and scale it by emotion confidence. - If VQ-VAE inference is unavailable or low-confidence, use deterministic heuristic fallback and log
codebook=HEURISTIC_FALLBACK.
thymos_inference.py emits a compact affect vector that can be used as an input signal for pre-tick perturbation, retrieval weighting, and downstream vector-delta interpretation. These values are soft model signals in the [0.0, 1.0] range, not direct replacements for the OpenEden 8D state.
Example:
{
"valence": 0.43990617990493774,
"arousal": 0.5741496086120605,
"dominance": 0.3847764730453491,
"connectionNeed": 0.6993353962898254,
"openness": 0.5171651840209961,
"confidence": 0.5829112529754639
}| Field | Meaning |
|---|---|
valence |
Emotional pleasantness. Higher values indicate more positive affect; lower values indicate more negative affect. |
arousal |
Activation level. Higher values indicate excitement, urgency, or intensity; lower values indicate calmness or low energy. |
dominance |
Perceived control or assertiveness. Higher values indicate command, certainty, or control; lower values indicate invitation, vulnerability, or passivity. |
connectionNeed |
Desire for social closeness or response. Higher values indicate stronger bids for sharing, reassurance, companionship, or attention. |
openness |
Willingness to share, explore, or receive interaction. Higher values indicate more openness or receptivity; lower values indicate closure or guardedness. |
confidence |
Model confidence for the affect estimate. Downstream effects must be scaled by this value; when it is below 0.5, pre-tick should be skipped, and ShockState back-detection requires at least 0.65. |
Interpretation should stay conservative. For example, a happy food-sharing message with high connectionNeed and medium confidence should produce only small positive shifts in Pathos, Vitality, or Empathy unless later pipeline stages provide stronger evidence.
- JDK 21
- Kotlin 2.x
- Gradle Wrapper
- Optional: OpenAI-compatible LLM endpoint
- Optional: DJL/PyTorch local model files
The packaged interactive CLI owns the terminal through JLine's native provider.
On Windows it consumes Unicode console events directly and does not require a
particular PowerShell encoding or chcp value.
Copy the example environment file:
Copy-Item .env.example .envCommon variables:
| Variable | Description |
|---|---|
OPENEDEN_LLM_PROVIDER |
LLM provider, currently defaults to openai. |
OPENEDEN_OPENAI_API_KEY |
API key for OpenAI or an OpenAI-compatible service. |
OPENEDEN_OPENAI_MODEL |
LLM model name. |
OPENEDEN_OPENAI_BASE_URL |
OpenAI-compatible endpoint. |
OPENEDEN_LLM_REASONING_EFFORT |
Reasoning effort: low, medium, or high. |
OPENEDEN_LLM_TEMPERATURE_MIN |
Lower bound for the dynamic per-turn temperature; default 0.2. |
OPENEDEN_LLM_TEMPERATURE_MAX |
Upper bound for the dynamic per-turn temperature; default 1.0. |
OPENEDEN_LLM_MAX_OUTPUT_TOKENS |
Optional static token ceiling, including reasoning and visible output tokens. |
OPENEDEN_SERVER_URL |
Server URL used by the CLI. |
OPENEDEN_RUNTIME_DB_PATH |
SQLite runtime database path. |
OPENEDEN_PERSONA_PATH |
Persona YAML path. |
OPENEDEN_LOCAL_MODEL_ARTIFACT |
Local model artifact path. |
OPENEDEN_OWNER_PLATFORM |
Optional heartbeat owner delivery platform. |
OPENEDEN_OWNER_USER_ID |
Optional heartbeat owner user ID. |
OPENEDEN_HOST_PLATFORM |
Optional authoritative host identity platform. |
OPENEDEN_HOST_USER_ID |
Optional authoritative host identity user ID. |
OPENEDEN_HOST_ADDRESS |
Optional preferred address used only for the exact configured host. |
OPENEDEN_ENABLE_CLI_DIAGNOSTICS |
Enable the token-gated diagnostic endpoint; default false. |
OPENEDEN_CLI_DIAGNOSTICS_TOKEN |
Separate credential used only by the optional CLI diagnostic panel. |
DeepSeek Responses-compatible endpoint example:
$env:OPENEDEN_OPENAI_API_KEY="sk-..."
$env:OPENEDEN_OPENAI_MODEL="deepseek-v4-flash"
$env:OPENEDEN_OPENAI_BASE_URL="https://api.deepseek.com"This uses the same OpenAI Responses adapter; OpenEden has no provider-specific
branching. DeepSeek thinking mode may ignore temperature. DeepSeek accepts the
Responses verbosity field, but may not apply it.
Download the local model artifact if it is missing:
.\gradlew.bat ensureLocalModelArtifactStart the server:
$env:OPENEDEN_OPENAI_API_KEY="sk-..."
$env:OPENEDEN_OPENAI_MODEL="gpt-5.5"
$env:OPENEDEN_OPENAI_BASE_URL="https://api.openai.com/v1"
.\gradlew.bat :server:runIn another PowerShell window, start the CLI:
.\gradlew.bat :cli:installDist
.\cli\build\install\openeden\bin\openeden.batgradlew :cli:run is a development convenience. Gradle proxies terminal streams
through pipes, so it is not the supported path for interactive line editing.
Send one compatibility chat request:
.\gradlew.bat :cli:run --args="chat --message `"hello`""Print local CLI state:
.\gradlew.bat :cli:run --args="state"/help
/state
/mode inline|full
/inspect on|off
/clear
/exit
Interactive sessions start in vertical inline mode, which keeps completed turns in native terminal scrollback. /mode full or Ctrl+T switches to the alternate-screen view without creating a second server session; switching back preserves the conversation. /exit closes only the CLI HTTP client and does not stop the server.
Interactive input uses JLine for history, cursor movement, insertion, deletion,
IME input, and supplementary Unicode characters such as emoji. See
Terminal input for the terminal ownership and encoding
contract. Internal Omega, ShockState, and 8D vector diagnostics are not shown by
default. Alt+Enter inserts a newline, Tab completes slash commands, Esc or
Ctrl+C cancels active generation, Ctrl+D exits an empty editor, and Alt+I
toggles diagnostics.
Diagnostics have two gates: the panel is hidden on every launch, and the server
endpoint is disabled unless OPENEDEN_ENABLE_CLI_DIAGNOSTICS=true with a
non-empty OPENEDEN_CLI_DIAGNOSTICS_TOKEN. The panel contains only safe state
summaries; prompts and internal reasoning are never returned.
Interactive input/output is owned exclusively by JLine's native terminal provider
and does not depend on the shell code page. Redirected streams and one-shot commands
use fixed UTF-8, consume one optional UTF-8 input BOM, emit neither a BOM nor ANSI
controls, and provide no encoding overrides. External producers must emit UTF-8;
other pipe encodings are unsupported. The CLI never runs chcp or changes global
shell state.
On first startup, the CLI creates:
%USERPROFILE%\.openeden\config.json
This file contains client settings only. LLM, runtime, model, and persona settings belong to the server.
The server listens on:
http://0.0.0.0:8080
Public endpoints:
GET /health
POST /api/v1/chat {"userId":"local","text":"hello"}
POST /api/v1/chat/stream {"userId":"local","text":"hello","clientRequestId":"..."}
GET /api/v1/state?userId=local
The stream endpoint emits only accepted, safe stage, response.delta,
completed, and safe error events. Providers with strict structured streaming
produce validated public deltas. Other providers fall back to buffered delivery
after the complete output schema has passed validation.
Chat responses contain:
{
"requestId": "...",
"status": "...",
"response": "...",
"error": null
}Internal vectors, evolutionIndex, prompts, traces, retrieval modes, and diary details are not exposed through the public CLI/API response.
The focused server test suite covers the runtime and persistence boundaries; the full Gradle build also compiles the CLI, client, core, and trainer modules. The Unicode terminal check is separate because it exercises the Windows native console path.
Qdrant is an optional, rebuildable candidate index. SQLite remains the authoritative
store for memory text, metadata, embeddings, runtime state, and projection status.
The server writes SQLite first and projects vectors asynchronously; an unavailable
Qdrant automatically uses the in-memory index and /health remains ready.
Start the local Qdrant service with the pinned image and persistent named volume:
docker compose up -d qdrantThe default endpoint is http://localhost:6333. For a remote service, set
OPENEDEN_QDRANT_URL; set OPENEDEN_QDRANT_API_KEY only when the remote service
requires it. The API key is never included in diagnostics or logs.
The active collection is derived from OPENEDEN_QDRANT_COLLECTION and
OPENEDEN_EMBEDDING_MODEL_ID (default local-v1). Changing the embedding model
creates a separate collection and refreshes stored embeddings in the background;
old collections are not deleted automatically.
To force a complete projection rebuild, stop writes if appropriate and delete only
the active Qdrant collection. The synchronizer recreates it from SQLite. Back up
data/runtime/openeden.db: it is the recovery artifact, while Qdrant contains only
the disposable search projection.
When Qdrant is degraded, token-gated /api/v1/diagnostics reports the backend,
collection, circuit state, projection counts, last remote success, and a sanitized
error category. It never returns memory content, embeddings, or credentials.
.\gradlew.bat :server:test
.\gradlew.bat :server:build
.\scripts\verify-cli-unicode.ps1Useful Gradle tasks:
| Task | Description |
|---|---|
.\gradlew.bat ensureLocalModelArtifact |
Download the default local model artifact if it is missing. |
.\gradlew.bat :server:run |
Start the Ktor server. |
.\gradlew.bat :cli:installDist |
Build the supported packaged interactive CLI. |
.\gradlew.bat :cli:run --args="chat --message \"hello\"" |
Send one compatibility chat request. |
.\gradlew.bat :cli:run --args="state" |
Print local CLI session state. |
.\gradlew.bat :server:test |
Run server tests. |
.\gradlew.bat :server:build |
Build the server module. |
The default model artifact is hosted at:
https://huggingface.co/0x4C57/openeden-codebook-base-model
Override the download URL with OPENEDEN_LOCAL_MODEL_ARTIFACT_URL.
- CLI, direct, and web one-to-one sessions use
<platform>:<userId>. - Group deployments use a shared state model with
<platform>:<groupId>. - Individual
user_idvalues are still recorded as memory metadata but do not create separate ATRI instances inside group deployments. - The default SQLite path is
data/runtime/openeden.db.
OpenEden code, generated codebook artifacts, and published OpenEden model artifacts are released under the GNU Affero General Public License v3.0. See LICENSE.