Skip to content

Releases: lumizone/local-waifu

Local Waifu 0.1.21

25 May 17:48

Choose a tag to compare

Small polish + safety release shipped the same day as v0.1.20. Adds the
standard macOS menu bar that was missing, gives you a manual escape
hatch when auto-update breaks, and lets you dismiss any notification
with a click.

Added

  • Native macOS app menu. The menu bar now has App / Edit / View /
    Window / Help submenus like every other Mac app. "Check for
    Updates…"
    lives in both the app submenu (under About) and the Help
    submenu so it's wherever your muscle memory expects. Standard
    shortcuts (Cmd+C, Cmd+V, Cmd+W, Cmd+Q, etc.) now work from the menu.
    Polish + English labels follow the existing language pref.
  • Manual update fallback. When the auto-updater can't reach GitHub
    Releases (network down, repo briefly unavailable, signature mismatch),
    Settings → Advanced now shows a "Download latest manually from
    GitHub →" link so you're never stuck on an old version with a cryptic
    error. The same link surfaces as an action on update-failed toasts.
  • Close button on every notification. Toasts in the top-right now
    show an X on hover, so you can dismiss any notification without
    waiting for it to time out.

Fixed

  • License deactivate device no longer POSTs blank credentials to
    Dodo on a second call. Previously, after one successful deactivate,
    pressing the button again would surface an "invalid_key" error from
    Dodo instead of a clean no-op.
  • OAuth token storage (currently inert in the shipped UI, but live
    in the codebase) is now a single atomic JSON blob instead of 7
    sequential writes. A crash mid-sequence can no longer leave a
    partially-saved bundle that drops account_id on the next read.
  • Secret store recovery is fail-closed. If your secrets.json
    ever gets corrupt AND the app can't move it aside, the app now
    refuses to overwrite it rather than silently destroying anything
    recoverable. You'll see a clear error message instead.
  • License key masking is UTF-8 safe. A defensive fix; current Dodo
    keys are all ASCII, but the underlying slice is no longer panic-prone
    if that ever changes.

Mostly a payments + UX cleanup release. License processing moves from
Dodo test to Dodo live (real cards now). The paywall got loading-state
polish so the Buy buttons no longer look dead during the first second
of cold start.

Changed

  • License processing is now live. New purchases hit the real Dodo
    Payments live environment, $25 lifetime / $35 renewal, license keys
    arrive by email from Dodo. Existing license keys you redeemed during
    the test/beta window keep working on this build, no re-activation
    required.
  • Paywall buttons show a loading state. "Buy lifetime" and "Buy
    renewal" now render a small spinner while the app fetches the
    checkout URLs from the backend, instead of looking disabled. Fixes
    the "is this broken?" first-click moment.
  • Lifetime price copy simplified to "$25". Previous "$25 launch ·
    $35 standard" framing implied a discount code that no longer applies
    on live checkout. The actual charge has always been $25, and that's
    what the button now says.
  • Refund + revocation copy is honest about timing. Terms,
    withdrawal form, and privacy policy no longer promise an automatic
    "Locked within ~12 hours" after a refund. The current build doesn't
    poll Dodo on a schedule, so the app will pick up a revoked license
    when you next re-redeem or manage devices in Settings → License.

Marketing site

  • Polar → Dodo across all legal pages. The Polar Software Inc.
    Merchant of Record references in /terms, /privacy, /thank-you,
    /withdrawal-form, and /contact have been updated to Dodo Payments,
    Inc. The post-purchase thank-you page now links to the Dodo customer
    portal instead of polar.sh. GDPR/CRD-aligned.

Fixed

  • License deactivation error path now scrubs response bodies. The
    /licenses/deactivate call previously surfaced a raw 400-character
    slice of any future error format Dodo returns. Now routes through
    the same sanitiser as validate/activate (strips sk- / Bearer /
    AIza / x-goog-api-key patterns), so a future Dodo error shape
    that echoes user data or tokens can't leak via this path.
  • Cancellation guards on license-tab and paywall fetches. Closes a
    hard-rule-66 violation where a user switching tabs mid-fetch could
    briefly see stale state from the prior async resolve.
  • dev.sh sanity check now matches the active provider. Was
    validating Polar env vars only; now picks Dodo or Polar based on
    LW_LICENSE_PROVIDER and warns on missing values for the active set.

Local Waifu 0.1.20

25 May 08:51

Choose a tag to compare

Mostly a payments + UX cleanup release. License processing moves from
Dodo test to Dodo live (real cards now). The paywall got loading-state
polish so the Buy buttons no longer look dead during the first second
of cold start.

Changed

  • License processing is now live. New purchases hit the real Dodo
    Payments live environment, $25 lifetime / $35 renewal, license keys
    arrive by email from Dodo. Existing license keys you redeemed during
    the test/beta window keep working on this build, no re-activation
    required.
  • Paywall buttons show a loading state. "Buy lifetime" and "Buy
    renewal" now render a small spinner while the app fetches the
    checkout URLs from the backend, instead of looking disabled. Fixes
    the "is this broken?" first-click moment.
  • Lifetime price copy simplified to "$25". Previous "$25 launch ·
    $35 standard" framing implied a discount code that no longer applies
    on live checkout. The actual charge has always been $25, and that's
    what the button now says.
  • Refund + revocation copy is honest about timing. Terms,
    withdrawal form, and privacy policy no longer promise an automatic
    "Locked within ~12 hours" after a refund. The current build doesn't
    poll Dodo on a schedule, so the app will pick up a revoked license
    when you next re-redeem or manage devices in Settings → License.

Marketing site

  • Polar → Dodo across all legal pages. The Polar Software Inc.
    Merchant of Record references in /terms, /privacy, /thank-you,
    /withdrawal-form, and /contact have been updated to Dodo Payments,
    Inc. The post-purchase thank-you page now links to the Dodo customer
    portal instead of polar.sh. GDPR/CRD-aligned.

Fixed

  • License deactivation error path now scrubs response bodies. The
    /licenses/deactivate call previously surfaced a raw 400-character
    slice of any future error format Dodo returns. Now routes through
    the same sanitiser as validate/activate (strips sk- / Bearer /
    AIza / x-goog-api-key patterns), so a future Dodo error shape
    that echoes user data or tokens can't leak via this path.
  • Cancellation guards on license-tab and paywall fetches. Closes a
    hard-rule-66 violation where a user switching tabs mid-fetch could
    briefly see stale state from the prior async resolve.
  • dev.sh sanity check now matches the active provider. Was
    validating Polar env vars only; now picks Dodo or Polar based on
    LW_LICENSE_PROVIDER and warns on missing values for the active set.

Local Waifu 0.1.19

22 May 19:35

Choose a tag to compare

The prior release was about how the character learns about YOU. This
one is about her interior life — modes she can be in, things she
likes, things she's afraid of, who she is when she's with you. Three
layers landed together.

New (named behaviour modes)

  • Mode catalog. 13 named modes the character can be in for a
    turn: neutral, playful, naughty, tender, comforting,
    excited, sleepy, focused, melancholic, grumpy,
    protective, curious, quiet. Each ships with a one-line
    behavioural cue so the LLM has a discrete posture to organise the
    reply around (instead of trying to translate 6 analog mood
    numbers into tone).
  • Mode selector. Each turn picks a mode from mood + user-message
    signals (sad-keyword lexicon, threat keywords, new-topic openers)
    • stage + time-of-day. Strong overrides (distress → comforting,
      threat → protective) bypass mood entirely. naughty is gated by
      relationship stage (≥ Close) AND the absence of an NSFW
      hard-boundary.
  • Sticky modes. Modes hold for at least 3 turns once chosen so
    the character doesn't whiplash between postures when mood values
    jiggle by 0.05. Strong overrides break stickiness instantly.
  • LLM-driven shifts. New [[MODE shift <name> | <reason>]]
    marker lets the LLM consciously override the heuristic when it
    senses something the lexicon missed.
  • Mode telemetry. Last 50 transitions persisted per character
    in app_meta, ready for future Growth Dashboard surfacing.

New (character self-content blocks)

Four new Letta-style working-memory blocks every character now
ships with, alongside the existing persona / user / session
/ scratch:

  • likes — things SHE enjoys, discovered through your
    conversations.
  • dislikes — things that bother her.
  • quirks — verbal tics and mannerisms she's noticed in
    herself.
  • fears — soft worries she carries, even with you.

All four start mostly empty and grow organically — from in-chat
[[BLOCK append <label>]] markers the LLM emits AND from the new
weekly self-reflection pass.

New (weekly self-reflection sub-agent)

  • character/self_reflection.rs — runs once per 7 days per
    character from the daily background pass. Reads the last 50
    chat turns plus the current self-content blocks, asks the LLM
    to reflect in FIRST person ("I notice I enjoy…"), and appends
    up to 3 entries to each block. Also produces:
    • Mode preference weightsmode → weight map blended into
      the existing weights (smoothing 0.7 × existing + 0.3 × new) so
      "she has learned that playful works with you" emerges over
      months without a single week's outlier flipping it.
    • Insight of the week — 1-2 sentence honest self-observation
      stored as a self_insight memory, ready for the Growth
      Dashboard.
  • License-gated like the other background loops.

Removed / cleaned up

  • The pre-v0.1.19 mood-only prompt line ("Current mood: warm
    (happiness=…)") is now LABELLED as "Underlying mood" because
    the discrete mode + cue sits above it. The mood numbers still
    feed the model — they just no longer carry the whole tonal
    responsibility alone.

Fixed (caught by new gemma4:e4b integration test)

  • System-prompt block dump no longer leaks into the chat.
    Reasoning models (gemma4:e4b especially) sometimes echoed the
    rendered working-memory block dump — --- persona (188/500 — Your self-image) --- … --- end blocks --- — verbatim into the
    visible reply, exposing internal state. The pipeline now strips
    any block-dump-shaped region from the assistant text right
    before persisting and surfacing it, so even when the model
    mimics the system-prompt format, only her actual words reach
    you. (character::blocks::strip_leaked_block_dump, wired in
    chat_pipeline.rs after marker parsing; 5 unit tests covering
    with/without terminator, markdown horizontal rules, unknown
    labels, and clean text.)
  • Polish distress lexicon now catches reversed word order.
    The distress cue list only had "fatalnie się czuję" but
    Polish lets you reorder freely — "Czuję się fatalnie." was
    slipping through and Comforting mode wasn't firing. Added 12
    new cues: both word orders for czuję się fatalnie /
    fatalnie się czuję / źle się czuję / kiepsko się czuję,
    plus kiepsko mi, ciężko mi, rozpadam się,
    przejebany dzień/tydzień, and ASCII-fold variants for users
    who skip Polish diacritics. Threat-mode (boję się) still
    takes precedence when both signals fire, by design.
  • Multi-turn self-learning integration test.
    Added tests/v019_self_learning_demo.rs — drives a 6-turn
    Polish conversation against gemma4:e4b, parses every marker
    the character emits ([[MEM]] / [[BLOCK]] / [[THREAD]] /
    [[MODE shift]] / [[MILESTONE]] / <thought>), folds the
    resulting state back into the next prompt, and asserts the

Local Waifu 0.1.17

21 May 09:43

Choose a tag to compare

Second internal-quality release in 24 hours. A 5-agent bug hunt
swept the chat hot path, security surfaces, lifecycle / sidecars,
frontend, and external integrations. Every fixable finding landed.

Fixed (privacy / security)

  • Offline-mode bypass. When offlineMode=true was set and the
    local Ollama crashed mid-conversation, the cloud-fallback path
    would silently forward your message to BYOK Anthropic / OpenAI /
    DeepSeek anyway — directly contradicting your privacy opt-out.
    The offline check is now re-validated before any fallback.
  • Soul-file tmp 0600. The encrypted tmp file the soul saver
    writes (before atomic-rename) now has 0600 perms from creation
    rather than briefly landing at the umask default of 0644.
  • Error-body sanitisation gaps. Three cloud / BYOK error paths
    (Anthropic mid-stream SSE error, Dodo deactivate failures, the
    byok_test detail field) were passing raw HTTP body text
    through to the UI. Each now runs through the same token-masking
    sanitiser the other cloud error paths use.

Fixed (chat correctness)

  • Surprisal gate firing every turn. The "is this user message
    novel enough to trigger KG extraction?" check compared a
    weighted-by-importance-and-recency similarity against a 0.78
    threshold designed for raw cosine. Result: aged memories always
    scored under threshold, and KG extraction fired on every chat
    turn for users with any history. Now compares raw cosine — only
    fires when the user is bringing up something genuinely new.
  • Orphan user messages. The chat pipeline writes the user turn
    to the DB before the LLM call so it survives crashes mid-stream.
    Downside: an actual crash (or ?-propagated error) between the
    user write and the assistant write left a dangling user row
    with no reply, rendering as a lonely user bubble forever. A
    startup sweep now deletes any user row older than 1 hour that
    has no character row following it — preserves recent legit
    messages, cleans up genuine orphans.
  • Empty model routing. route("openai:") (and the other
    cloud prefixes) used to send an empty model field to the
    provider and get a 400. Empty model after a prefix now falls
    back to the Ollama path so the standard error surface kicks in.

Fixed (lifecycle / data integrity)

  • Backup integrity. backup_app_data now runs
    PRAGMA wal_checkpoint(TRUNCATE) before the file copy — the
    previous version copied the live WAL alongside the main DB
    with no synchronisation, so a write committing mid-copy could
    produce a backup that refused to open.
  • Cmd+Q sidecar shutdown. Cmd+Q on the macOS menu bar used
    to bypass both the WindowEvent::Destroyed handler (intercepted
    by hideToTray) and the tray Quit handler — leaving Ollama and
    the TTS sidecar as zombie child processes. A new
    RunEvent::Exit handler now catches every exit path and
    shuts down sidecars with a 5 s deadline.
  • post_update_finalize warning loop. A crash between the
    version-marker write and the soul-file check used to hide any
    warning forever. The check now runs before the marker, and the
    marker is only set when the check passes — or after 2 retries
    with a separate counter, so a genuinely missing soul doesn't
    loop the warning forever either.

Fixed (resource exhaustion)

  • Swift script timeouts. Calendar, Reminders, and OCR all
    shelled out to /usr/bin/swift with no deadline. A hung Swift
    process (TCC dialog blocked, missing dylib, signal-stopped)
    would pin a Tokio blocking-pool thread until the OS reaped the
    process — and enough hung calls could starve every other
    blocking caller. All three paths now route through a shared
    watchdog with a 15 s deadline (kills the child + clear error).
  • Image download cap. The fal.ai and OpenAI image-URL
    download paths called .bytes().await with no content-length
    check, so a misbehaving CDN streaming gigabytes would OOM the
    app. Both now cap at 20 MB.

Fixed (frontend stream race)

  • Stop button leaving markers visible. Hitting Stop mid-stream
    after the assistant bubble appeared but before done arrived
    used to leave the partial tokens (with any [[MEM ...]]
    bookkeeping markers) frozen in the bubble. The bubble now still
    receives the cleaned text on done even when stopped — so the
    final state matches what the server actually committed.
  • Character switch race. Switching characters while a stream
    was in flight relied on bumpStreamGeneration to drop later
    tokens, but a done event arriving in the microtask window
    before clearMessages could still write into the old
    character's array. Now uses stopStreamingMessages for an
    atomic bump-and-finalise.

Audit deferrals

  • Soul-file double-PBKDF2. Cocoon::new(key) runs its own
    100k-iteration PBKDF2 on top of our already-PBKDF2'd master key,
    for 200k iterations total per soul read/write. Switching to
    MiniCocoon::from_key would skip cocoon's KDF but the wire
    format is incompatible — would brick every existing user's
    soul files. The performance cost (~50 ms per character switch,
    never on the chat hot path) is acceptable; a format-version
    byte for future migration is tracked as v0.2 work.

Local Waifu 0.1.16

21 May 09:11

Choose a tag to compare

Internal-quality release driven by a 4-agent backend audit. No
new user-facing features — just hardening every production-risk
finding the audit surfaced.

Fixed (BLOCKER class)

  • Migration runner crash window. The DB migration tracker row
    used to be inserted after the migration body's COMMIT. A crash
    in the millisecond between those two writes would leave the
    schema modified but the migration unrecorded; the next launch
    would re-attempt the same ALTER TABLE ... ADD COLUMN, fail
    (SQLite has no IF NOT EXISTS for ADD COLUMN), and brick the
    database. The body + bookkeeping now commit atomically.
  • Fire-and-forget tasks. commands/feedback.rs was using raw
    tokio::spawn instead of tauri::async_runtime::spawn for the
    background consolidation pass — broke the codebase convention.
    Swapped. (Telegram dispatcher keeps tokio::spawn because its
    is_finished() API powers status reporting — panic visibility
    is already covered by the global panic hook.)

Fixed (production hardening)

  • Marker parser robustness. [[MEM ...]], [[THREAD ...]],
    [[BLOCK ...]] parsers used to terminate on the first ]]
    encountered — if the model mirrored user text containing ]]
    (markdown footnotes, nested quotes), the marker would fire on
    the wrong delimiter and either drop a real call or swallow
    user-visible content. Now any [[ inside the body is treated
    as nested content and the marker is preserved verbatim.
  • Lorebook write validation. lorebook_upsert had no length
    caps; a user could push a 50 MB entry into SQLite. Now bounded
    at 200 chars (title), 4,000 chars (content), 32 keywords ×
    80 chars each. Update path also now correctly returns the
    original created_at instead of now, and rejects updates
    against an ID that doesn't belong to the active character.
  • Anticipations table runaway. The pruning DELETE used
    LIMIT = MAX_UNCONSUMED - parsed.len() which could go negative
    if those constants ever drifted. Clamped to >= 0.
  • DB lock starvation. anticipations::build_context used to
    hold the SQLite mutex across two prepared-statement iterations.
    Concurrent chat turns now don't block on the background
    reflection pass.
  • Ollama non-stream timeout. The shared client default 120 s
    was killing background drift detection / KG extraction /
    consolidation calls on Power-tier 27B models (real responses
    take 2-5 min). Per-request override raised to 10 min on the
    non-streaming path.
  • License gate for background loops. Daily consolidation +
    sleep-time anticipations now check license::gate::current_status
    and skip the LLM call entirely for Locked / TrialExpired /
    NotStarted users. They can't chat with the app anyway — no
    reason to burn their local CPU/GPU on reflection passes for a
    product they haven't paid for.

Audit deferrals

  • Retention sweep on memories / chat_messages / entities /
    open_threads / relationship_milestones.
    All grow unboundedly.
    Realistic ceiling for a daily-use install: ~100 MB after a year.
    Acceptable for v0.1.x; will land as a configurable retention
    policy in v0.2.

Local Waifu 0.1.15

21 May 08:43

Choose a tag to compare

Added

  • Lorebook (per-character). Settings → Lorebook. Pre-author
    backstory snippets with trigger keywords; each one only injects
    into her prompt when the keywords appear in your message. Means
    you can pour thousands of words of canon into her without paying
    the token cost every turn. Empty-keywords mode (always active)
    available for the rare entries that must always be in context.
    SillyTavern-inspired keyword-triggered injection.
  • Sleep-time anticipations. Once every ~6 h of idle, a small
    background pass asks a fast model "looking at the recent chat
    and her open threads, what would she naturally bring up next
    time?" Stores 1-3 short anticipations per pass. When the
    spontaneous-callback trigger fires after long silence, she picks
    the freshest anticipation first ("hey, was thinking about your
    interview nerves") and falls back to the bare open-thread topic
    only if nothing's queued. Letta-inspired.

Local Waifu 0.1.14

20 May 19:49

Choose a tag to compare

Added

  • Time-aware. Every reply now knows what day and part of day it
    is (Monday morning, Friday evening, late night Sunday). She'll
    open with something appropriate to the wall clock instead of the
    same neutral greeting at 3 AM and 3 PM. Pulled from the system
    clock — matches what you actually see.
  • She reads before she replies. A short reading pause kicks in
    between the moment you hit Enter and the moment her bubble
    appears. Length-tied: "ok" feels instant, a paragraph buys her a
    beat to "take it in". AIRI-inspired typing-simulation; capped at
    1.5 s so long pastes still feel responsive.
  • Inner thoughts (💭). She can now keep a private thought
    separate from her spoken reply — "you sound stressed today"
    while saying "tell me more". Tap the 💭 badge under her message
    to peek; the user only sees thoughts they choose to. AIRI-style
    <thought> blocks parsed server-side.
  • Open threads. She tracks what you're mid-conversation about
    ("the job interview Friday", "the row with your mum") so she
    can circle back instead of treating every turn as a cold start.
    Resolves them when wrapped up. Letta-inspired.
  • Working memory blocks. Four labelled scratchpads she
    maintains across turns: persona (her own mood), user (running
    notes), session (top-of-mind now), scratch (notepad). She
    can compact them when full. Letta-style multi-block memory.
  • Spontaneous callbacks on idle. Instead of a generic "you
    there?" after long silence, she now pings about a specific open
    thread: "hey, how did the interview go?". Falls back to the
    generic nudge when there's no open thread.

Local Waifu 0.1.13

20 May 19:01

Choose a tag to compare

Added

  • She knows the difference between what you said and what she
    guessed.
    Memories now split into two kinds: deductive (direct
    quotes from things you explicitly told her) and inductive
    (patterns she noticed over multiple turns but were never said).
    The digest in her system prompt labels inductive entries as
    (impression) so she hedges them ("I get the sense that…")
    instead of asserting them as fact ("you told me…"). Inspired by
    Honcho's deductive/inductive observation split.
  • Memory provenance. Every memory she creates now records which
    chat message produced it (source_message_ids column). Future
    work can wire citations into her replies — "Last Tuesday you
    mentioned X" — instead of asserting context in a vacuum.
  • Smarter knowledge extraction. Instead of running the KG
    extraction LLM every 5 turns regardless of what you said, she
    now checks if your latest message is semantically novel vs what
    she already remembers. If you're repeating a topic she's heard,
    she skips the extraction call (saves Ollama). If you bring up
    something new, she extracts immediately. Force-fallback every 20
    turns so slow patterns still get logged. Net result: fewer
    background LLM calls + faster reactions when the topic IS new.

Inspired by

  • plastic-labs/honcho
    their peer-representation work + deductive/inductive split + dream
    cycle. AGPL-3.0; we ported only patterns, no code. Honcho remains
    the right pick if you want managed cross-session user modeling.

Local Waifu 0.1.12

20 May 18:36

Choose a tag to compare

Added

  • Persona notes per character. Settings → Characters now has a
    free-text "Persona notes" editor for the active character. Type
    quirks, tone hints, mannerisms ("she's grumpy in the morning",
    "she calls me by my name often") and they slot into the system
    prompt above the slider-derived personality every reply. 2,000-char
    cap; leave empty to use defaults from onboarding.
  • /compress slash command. Power-user escape hatch for when the
    memory digest fills up — type /compress in the chat input and
    the app programmatically trims low-importance memories that haven't
    been surfaced recently. Shows stats in a toast ("Trimmed 12 entries
    → 8 kept, freed 340 chars"). No LLM call; instant.
  • Auto-fallback to cloud BYOK when Ollama is unreachable. If you
    picked a local model but Ollama has crashed / isn't running, and
    you've configured a cloud BYOK key (Anthropic / OpenAI / DeepSeek),
    the next chat turn quietly retries via that cloud provider with a
    cheap default model. You stay in conversation instead of staring
    at an "offline" error. Offline Mode in Network settings still
    disables the cloud path entirely — your privacy choice wins.

Local Waifu 0.1.11

20 May 18:11

Choose a tag to compare

Added

  • She remembers you better. Every chat turn now starts with a
    bounded plain-text snapshot of what she knows about you (USER
    digest, ~500 tokens) and the relationship so far (MEMORY digest,
    ~800 tokens), alongside the existing semantic recall. The snapshot
    includes a usage % header — so when memory fills up she
    proactively consolidates instead of forgetting things at random.
  • She can explicitly write to memory. When you reveal something
    durable (preferences, important people, hard limits, big news),
    she emits an inline marker that the app picks up and stores. You
    never see the marker — it's stripped before display — but she'll
    remember next session. Same mechanism for correcting outdated
    facts (replace) or asking her to forget (remove).
  • Capacity-aware consolidation. When either store crosses 80%
    capacity, the background consolidation pass kicks in automatically
    so the next turn has room.

Inspired by

  • Hermes Agent's
    MEMORY.md / USER.md pattern + memory tool grammar. Their plain-
    text bounded approach + substring matcher are ported here; the
    underlying storage stays our existing encrypted soul files +
    SQLite memories table (embeddings preserved, plain text is
    additive).