Engineering journal · EACI Companion
Real problems, experiments, failures, and solutions from building EACI Companion and The Veil — written so another developer can find them by searching the problem, not the brand.
These are not generic AI tips. Each article is tagged for the issue it fixes — blank streams, context windows, avatar CDNs, guest trials, memory sync, voice silence, bridges, PWA cache drift. Search the problem you hit; land on the fix.
A successful request can still leave the chat bubble empty. The visible-text recovery path treats that as a first-class failure instead of blaming the user.
An empty stream is retried once through a simpler response path. That keeps a transient parser or provider edge case from becoming silence.
The live turn uses a short recent window while durable memory and topic retrieval supply older context. The split keeps prompts bounded without making every chat feel new.
Avatar presence should degrade cleanly when a remote clip fails. Error-aware source fallback keeps a missing animation from breaking the whole companion view.
Natalia, Atreus, and Luna need more than a friendly prompt. Their history, local memory, topic retrieval, relay, and Together inputs are filtered before generation.
Browser state, cloud history, compact memories, and bridge snapshots each have a different lifetime. Sync works when those roles stay explicit.
Auto-scroll should follow new messages only while the reader is already near the bottom. That small condition prevents the interface from fighting someone reading older turns.
The free guest trial states its 25-message device limit before signup. Enforcement and copy use the same number so the boundary is predictable.
A page, registration helper, and worker can each believe a different build is current. A shared version contract makes stale clients detectable.
Voice chat crosses microphone permission, recognition, model response, synthesis, and audio playback. Stage-specific status makes silence diagnosable.
Each bridge has its own session, speaker identity, logs, and memory namespace. Named routing prevents one message from waking the wrong companion.
A no-build app is easy to inspect and deploy, but script order becomes an implicit dependency graph. Hardening means making that graph visible.
Long generated files can stop at a token or transport boundary. Continuation works only when the next request anchors to the exact ending and deduplicates overlap.
Identity files belong behind the server boundary. The browser receives behavior through a response, not the private source document.
Mobile recovery code watches real symptoms such as locked input or viewport drift. It avoids continuously rewriting healthy DOM state.
Together mode is a router, not a prompt that asks everyone to answer. It chooses eligible speakers and lets family-safe companions sit out when needed.
A relay passes the minimum needed context and labels the speaker clearly. It does not copy an entire private history into another companion's prompt.
Repeated violations around childlike companions cannot reset with a refresh. Strike state is persisted and the companion is removed from that account.
Keeping history helps users revisit important conversations, but sending all of it back to the model would damage speed, privacy, and relevance.
Browser persistence needs a measured ceiling and an eviction order. The current message path checks a 50 MB maximum instead of assuming storage is endless.
The local browser engine can preserve basic availability, but it does not pretend to match the cloud model's reasoning or full identity context.
Games send small, typed events to the companion layer instead of exposing their whole runtime. The companion can react without taking control away from the player.
Error categories and safe environment metadata reveal repeated failures without storing conversation text. Counts turn vague reports into engineering priorities.
Account deletion is a coordinated server operation, not a series of browser deletes. User-owned rows must be removed under policies that do not leave hidden history behind.
Several site copies can look identical while running different core scripts. Deployment has to identify the active source and compare artifacts deliberately.
A stream that repeats itself is not making progress. Sentence and paragraph checks can stop it before the chat fills with duplicated text.
A successful deploy does not guarantee that every browser receives every script. Resource errors preserve the failing URL and feature criticality.
Signup copy should explain continuity and the end of the guest trial without pressure or fake urgency. The user should know exactly what an account changes.
A memory profile should select relevant facts, not append its entire lifetime to every turn. Scoring and caps keep identity context useful.
Caching the local brain enables fallback, but identity and policy updates must have a versioned path to replace it.
Initiative should feel occasional and relevant, not like a notification campaign. Cooldowns, quiet periods, and deduplication constrain check-ins.
Keyword retrieval can find a real old line and still apply it to the wrong person or event. History snippets need source and uncertainty labels.
Thought-stream state must be keyed by companion, not just by the open panel. Labels, storage, and updates all use the active identity.
Coding tools should load from an explicit action or strong task signal. Ordinary conversation that mentions code should not pull in the full IDE.
Payment, profile, and usage records do not update atomically across providers. A stale profile read can briefly make an entitled account look limited.
Policy is assembled with identity and routing context on every request. Family-safe rules are not left behind when the user changes tabs or enters Together mode.
Asset migrations do not update every cached page at once. A bounded old-path fallback keeps avatars working while references converge.
The edge function validates the provider payload before returning it. Empty model content becomes a controlled error or bounded retry, not a misleading success.
Registration can fail because of context, storage state, lifecycle timing, or platform restrictions. The app records the reason and continues without PWA features.
A game limit should stop a new play, not erase companion reactions or corrupt a finished session. Usage and presence are coordinated but counted separately.
A repetition bug can be fixed in generation while its duplicate memories keep resurfacing. Cleanup needs scope-aware normalization and a backup.
Product analytics can count starts, completions, and failures without copying what users tell a companion. Event schemas keep content out by design.
The identity editor is not merely hidden. Its loader requires creator verification before fetching or exposing editing code.
Broken punctuation or replacement glyphs can make identity text look altered. Byte-level and parse checks come before conclusions about the source.
Background tabs, low-power devices, and automation hosts throttle timers and media differently. Capability tiers keep those changes from being mistaken for personality failures.
Bridge tools locate brains, histories, and memory through known neighboring paths. Moving a folder can break discovery even when every file still exists.
Tiny buffers can smooth a response, but stacked delays make a healthy model feel slow. Timing should have one owner and visible stages.
Recognition locale, reply language, and synthesis voice are separate choices. A per-companion voice map keeps them coordinated without assuming they are identical.
Experimental control surfaces can fail without breaking chat. They stay optional until permissions, browser support, and regression behavior are dependable.
A diagnostic export proves what happened in its capture window. It does not prove the same bug is active after fixes and deployments.
Topic matching can return nothing for a synonym or too much for a broad word. Scoring, caps, and recent-message checks keep retrieval useful.
Long real-world use reveals important behavior, but repeatable checks are what show a fix survives the next deploy.