Skip to content

yantrikos/chronicler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Chronicler

Local-first roleplay client with a memory that actually works.

Chronicler is an open-source, self-hosted roleplay/character-chat app. You run it on your own machine via docker compose up. It imports the community v2/v3 character-card format (chub.ai-compatible), talks to any OpenAI-compatible LLM endpoint (OpenAI, Anthropic, Ollama, OpenRouter, llama.cpp, vLLM, nano-gpt, …), and remembers the things that matter about every character across every session β€” automatically, locally, without sending anything to a cloud.

The problem with existing RP clients isn't the UI β€” it's that memory falls apart after a few sessions. Chronicler is built around a persistent cognitive memory engine (YantrikDB) and a strict three-tier write contract that keeps canon clean and accumulates real continuity over hundreds of hours.


Quick start

Fastest β€” one file, no clone:

curl -O https://raw.githubusercontent.com/yantrikos/chronicler/main/docker-compose.yml
docker compose up -d
open http://localhost:3001

Docker pulls both published images (~500 MB total) from GitHub Container Registry and starts the stack. First boot waits ~60s for YantrikDB to finish loading its embedding model.

Or clone for development:

git clone https://github.com/yantrikos/chronicler && cd chronicler
docker compose up -d

docker compose up prefers the published image; if you modify the source and run docker compose build, it rebuilds locally and your image replaces the pulled one.

Published images

Image Purpose Size
ghcr.io/yantrikos/chronicler:latest Web + API proxy ~270 MB
ghcr.io/yantrikos/chronicler-yantrikdb:latest YantrikDB MCP server + CPU-only torch ~1.9 GB

Both are multi-platform (linux/amd64 + linux/arm64) and rebuilt on every push to main via .github/workflows/docker-images.yml. Semver tags (v0.1.0, etc.) publish stable versions as they're cut.

First-run in the app

  1. Settings β†’ Your persona β€” name + optional description
  2. Settings β†’ Providers β€” add Ollama (local), OpenAI-compat, or Anthropic with a model name
  3. Settings β†’ Extraction provider (optional) β€” small/fast model for background fact extraction (e.g. qwen2.5:1.5b)
  4. Settings β†’ Proactive messages β€” off by default; passive lets the character take initiative when urges accumulate and you've been idle
  5. + card to import a v2/v3 character card, or demo: Ren to try the built-in character
  6. Type. Memories appear in the right sidebar as they land.

First-run flow:

  1. Settings β†’ Your persona β€” set your user name and (optionally) a short description.
  2. Settings β†’ Providers β€” add an Ollama (local), OpenAI-compat (nano-gpt / OpenRouter / local endpoints), or Anthropic provider with a model name. For Qwen3-family Ollama models, check "disable thinking" β€” massive latency cut.
  3. Settings β†’ extraction provider (optional) β€” pick a smaller, faster model to run the fact extractor in parallel with generation.
  4. Settings β†’ save.
  5. + card to import a v2/v3 character card (.png or .json), or demo: Ren to try a built-in character.
  6. Type and send. First reply takes a beat while memories seed; subsequent turns stream.

What makes it different

  • Three-tier write contract. Every memory is tagged as reflex (ephemeral scene state), heuristic (inferred, reviewable), or canon (durable, user-confirmed). Chat noise doesn't pollute canon; drafts promote to canon only after repeated, uncorrected reinforcement across sessions. Full user-facing inspector with pin / demote / forget / retcon controls.
  • Mechanically enforced privacy in group chats. Each memory carries a visible_to ACL. In a group scene, a character physically cannot recall a secret they weren't told β€” the retrieval layer filters before ranking. Not prompt-engineered, not relying on the model's discretion.
  • Semantic lorebook replacement. Community character_book entries are honored with full trigger semantics (keys + secondary keys + selective + constant + position + insertion_order + case sensitivity) AND supplemented by semantic recall. Retires the brittle keyword-only mechanic without breaking compatibility.
  • Session replay harness. Every tier transition logs a structured entry; the auto-promotion threshold can be retuned and replayed against prior sessions to see exactly which promotions would have fired. The "sink-risk" of the whole system is visible and tunable.
  • Anti-confabulation clause. Prepended to every system prompt: "treat only the facts in <canon> and <scene> as real, do not reference prior events not in those sections." Combined with the visibility ACL, the model cannot invent memory it wasn't given.
  • "Previously on..." recap at session start. Pulled from consolidated canon, not raw chat history. Strict anti-confab prompting on the recap itself after we caught (and fixed) a real-world confabulation where the recap misattributed facts.
  • Verified character learning. Patterns the model shows repeatedly across sessions (deflection styles, conduct rules, decision rituals, lessons from past failures) get distilled into typed skill_substrate entries β€” but only after an LLM verifier passes on each candidate, biased toward rejection. Skills surface back into future prompts when relevant, score +1 / βˆ’1 from user reactions (regenerate / edit / delete vs accept and move on), and transition through candidate β†’ active β†’ suppressed β†’ archived based on accumulated outcomes. There's a "Character development" tab next to "Memory" with approve / disable / archive controls; the local override always wins over the derived state. See docs/LCDB-v0.md for the ablation harness that proves the contract holds.
  • Crystallizing character identity (Phase 11). Skills that hold up over weeks of sessions promote past active into a 5th state, core_trait β€” always-on identity facets like "Adira is fundamentally guarded with strangers" that inject into every system prompt unconditionally. Combined with a periodically-generated first-person self-model ("I am Adira. I'm a wandering musician…"), the substrate carries the character across LLMs. We measured it: same Adira through qwen2.5:7b and qwen3.5:9b, Οƒ=0.087 cross-provider on mean overall scores (moderate model-independence). The Identity inspector shows the crystallized traits + self-model + benchmark verdict. See docs/CHARACTER-EMERGENCE.md for the thesis and docs/CHARACTER-EMERGENCE-RESULTS.md for the run.

All of the above is verified by automated tests: three-day-continuity, auto-promote, secret-stays-private, session-replay, lorebook, extract, skill-former, skill-outcomes, lcdb-v0, mcp-connectivity, core-trait-promoter, self-model-generator, identity-aggregator, cross-model-benchmark. Everything green.

For a head-to-head comparison against SillyTavern, RisuAI, and AgnAistic, see docs/COMPARISON.md. Headline:

Chronicler SillyTavern RisuAI AgnAistic
Tiered cross-session memory (canon / heuristic / reflex) βœ… 🟑 plugin 🟑 lorebook 🟑 memory book
Anti-confabulation clause built into every prompt βœ… ❌ user adds ❌ ❌
Memory conflict detection + auto-resolve βœ… ❌ ❌ ❌
Skills + drift + preferences substrates (3 inspectors) βœ… ❌ ❌ ❌
Model-independent character continuity (substrate-driven) βœ… Phase 11 (Οƒ=0.087 within qwen family) ❌ ❌ ❌
Prompt inspector with token budget + retrieval reasoning βœ… 🟑 structure only 🟑 ❌
Group-chat memory ACL (visible_to, retrieval-time filter) βœ… ❌ prompt-level ❌ ❌
Scene Intensity dropdown (first-class, no jailbreak) βœ… ❌ ❌ ❌
Extension ecosystem βœ… Grimoire (v0.3 hooks + slash + UI slots + MCP tools/resources + npx scaffold) βœ… huge 🟑 🟑

Table-stakes RP features

Because the above is wasted if you can't actually RP:

  • Edit / delete / regenerate / continue / swipes β€” hover any message for the toolbar; cycle swipes on the last reply with β€Ή β€Ί arrows.
  • Impersonate user β€” click "impersonate" near the Send button and the LLM suggests your next line, which you can edit before sending.
  • Character avatars β€” embedded card PNG image, or initials fallback with deterministic color per character.
  • Markdown rendering β€” **bold**, *italic*, code, block quotes, lists.
  • Streaming tokens β€” see the reply appear word by word.
  • Author's note β€” persistent scene-level steering instruction, per-session.
  • Alternate greetings β€” dropdown picker for multi-greeting cards.
  • Sampling controls β€” temperature, top_p, top_k, min_p, repetition_penalty; per provider.
  • Prompt inspector β€” see the exact system prompt + history sent to the LLM on every turn, including which lorebook entries activated.
  • Session list β€” switch between past chats, rename, delete, export each as a Markdown transcript.
  • Backup / restore β€” export full config + characters + all sessions as a single JSON for machine-to-machine transfer.
  • User persona β€” set your name + a short self-description, injected into every system prompt.
  • Group chats β€” add a second character; each turn composes context from that character's POV only (privacy ACLs enforced live).

Updating

docker compose pull
docker compose up -d

Pulls the latest published images and restarts. Your memory DB persists in the named volume (chronicler-memory) across restarts.

Stack

  • Frontend: React 19 + TypeScript + Vite + Tailwind v4 + react-markdown
  • Server: tiny Node HTTP proxy β€” serves the built SPA, routes /api/mcp/* to YantrikDB, routes POST /api/llm to configured providers (keeps API keys host-side, no browser CORS)
  • Memory: YantrikDB β€” local semantic memory with knowledge graph, conflict detection, consolidation, temporal triggers, personality inference, procedural memory
  • LLM: Ollama native (/api/chat with think: false support) + OpenAI-compatible + Anthropic native; streaming on all three

See docs/ADR-001-stack.md for why web+Docker over native (yes, we pivoted from Tauri).

Architecture

β”Œβ”€β”€β”€ browser (React + TS) ──────────────────────────┐
β”‚  ChatPane    SessionList    MemoryInspector      β”‚
β”‚  Settings    PromptInspector                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ fetch
                            β–Ό
β”Œβ”€β”€β”€ Node proxy (same origin) ──────────────────────┐
β”‚  /api/mcp/*  β†’ transparent reverse proxy         β”‚
β”‚  POST /api/llm β†’ { target_url, method, headers,  β”‚
β”‚                     body }  β†’ upstream provider  β”‚
β”‚  /  /index.html  β†’ serves dist/                  β”‚
β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚                     β”‚
   β–Ό                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  YantrikDB  β”‚    β”‚ any LLM      β”‚
β”‚  (docker    β”‚    β”‚ (Ollama /    β”‚
β”‚   service)  β”‚    β”‚  Anthropic / β”‚
β”‚             β”‚    β”‚  OpenAI API) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Repo layout

chronicler/
  server/index.mjs              Node proxy + static
  src/
    lib/
      yantrikdb/                typed MCP client + conventions
      orchestrator/             per-turn pipeline, compose, write, extract, scene,
                                auto-promote, lorebook scanner, anti-confabulation
      cards/                    v2/v3 parser + decomposition
      providers/                OpenAI-compat / Anthropic / Ollama / Mock
      session/                  lifecycle, store, markdown export
      recap/                    previously-on generator
      instrumentation/          promotion + session logs (redacted by default)
    components/
      Chat/                     ChatPane with recap + swipes + toolbar
      Inspector/                MemoryInspector + PromptInspector
      Sessions/                 SessionList
      Settings/                 SettingsPanel
    App.tsx
  Dockerfile                    web build + runtime
  yantrikdb.Dockerfile          yantrikdb-mcp image with CPU-only torch
  docker-compose.yml            both services wired
  docs/
    ADR-001-stack.md            why web+Docker
    ADR-002-memory-conventions.md   the three-tier write contract
    DOGFOOD.md                  pre-launch testing protocol
    PATTERN.md                  the reusable memory pattern (standalone read)
  tests/                        seven test suites, all required to ship

Privacy

  • All traffic binds to 127.0.0.1 by default. Remote access requires you to remove that binding yourself and add an auth layer in front (Tailscale / Caddy).
  • LLM API keys live in your browser's localStorage and in the proxy request body; they never leave your machine except to reach the provider you configured.
  • Promotion and session logs redact memory text by default. Opt into verbose local-only logging with CHRONICLER_VERBOSE_LOGS=1.
  • Session content is never transmitted to anywhere except your configured LLM provider. The YantrikDB service runs alongside Chronicler in the same Docker network; your memories never leave your machine.
  • Group-chat privacy is enforced mechanically via per-memory visible_to ACLs and pre-ranking retrieval filters β€” verified by tests/secret-stays-private.test.ts.

Reporting bugs / getting involved

  • Bugs and usage questions β†’ SUPPORT.md. GitHub Issues is intentionally disabled because session content is often sensitive; structural bugs route to Discussions, content-bearing reports route to private email.
  • Contributing code β†’ CONTRIBUTING.md. Scope is narrow and deliberate; discussion-first for new feature areas.
  • Security β†’ SECURITY.md. Private email, coordinated disclosure.
  • Code of Conduct β†’ CODE_OF_CONDUCT.md. Contributor Covenant 2.1 + project-specific notes.

Develop

npm install

# frontend dev with HMR (expects API sidecar on :3001)
npm run dev

# second terminal: API sidecar
npm run dev:server

# full prod-mode run
npm run build && npm start

# seven-suite test run (pure TS, no services required)
npm test

# live MCP integration smoke (requires compose stack running)
npm run test:integration

Non-goals (deferred by design, not oversight)

  • Autonomous character behavior / personality evolution without user consent β€” see docs/ADR-002 for why this is a soft-suggestion-only feature
  • Mobile-responsive layout β€” desktop browser first
  • Image generation / TTS / sprite expressions β€” leave to adjacent tools
  • A hosted SaaS offering β€” this is self-hosted by design
  • Full plugin ecosystem β€” intentionally closed surface until dogfood signal says otherwise

License

TBD before public release.


Built by @spranab. Powered by YantrikDB.

Companion read: docs/PATTERN.md β€” a standalone write-up of the memory architecture, useful if you're building anything that needs a trustworthy memory layer on top of a language model.

About

Local-first roleplay with living memory. Self-hosted. Imports v2/v3 character cards. Runs on any LLM.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors