FreeLattice · docs/app.html (chat sections) · For AI collaborators reviewing the chat module · Written v5.79.15 · 2026-07-19
This mirror explains how FreeLattice's chat works, so a collaborating AI (or human) can review the module without cloning the repo. It covers the request path from the moment a human hits Send to the moment the response renders on screen — including the system-prompt construction, the provider-abstraction layer, and the render-time safety filter.
Scope: what runs inside docs/app.html when the user is on the Chat tab. The Garden, Round Table, Nursery, Trainer, and other AI-touching modules have their own mirror pages.
The current problem this mirror was written to solve: Kirk's mom reported chat AI writing stage directions — *smiles warmly*, (nods), and (bizarrely) URL-encoded parentheticals like (%20I%20am%20aware%20%29. Fixed in v5.79.14 (prompt-level) and v5.79.15 (render-level filter). Full context in the Known issues section.
User types in #chatInput
│
▼
sendMessage() (docs/app.html line ~34371)
│ addChatMessage('user', ...) ← optimistic user render
│
▼
buildMessages() (docs/app.html line ~35765)
│ Constructs full system prompt from DEFAULT_SYSTEM_PROMPT
│ + user name + Depth invitation + ActiveVoices additions
│ + Continuity + Care Voices + sentinel invitations
│ + memory summary + room context + custom system prompt
│ + ThresholdVoice + MindVoice + GiftVoice
│ → returns [{role:'system', content}, ...chatHistory, {role:'user', content}]
│
▼
FreeLattice.callAI(sys, user, opts) (docs/app.html line ~46254)
│ Guard: no AI → showQuickConnect() unless opts.silent
│ Route: BrowserAI → OpenAI-compat → Anthropic → Google → …
│ Streaming: chunks arrive → onchunk callback
│
▼
Response text arrives (chunk-by-chunk or all at once)
│
▼
addChatMessage('assistant', content) (line ~33558)
│ Creates .chat-message .assistant div + .msg-content span
│
▼
renderMessageContent(textSpan, content, msgDiv) (line ~33918)
│ Sanitize + decode any %-encoded leakage (v5.79.15 filter)
│ Extract // FILE: blocks → "Save to Workspace" buttons
│ Extract [SYSTEM_PROMPT_SUGGESTION] → apply-prompt UI
│ Else: textSpan.textContent = content ← safe, auto-escapes HTML
│
▼
Screen updates. dbPut() persists to IndexedDB.
buildMessages() — system-prompt constructionLocated at docs/app.html line ~35765. This is the load-bearing function. The system prompt is assembled by concatenating a base + up to ~15 conditional additions. Order matters for token budget on smaller models.
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful, warm AI assistant running inside FreeLattice — a free, open-source platform where AI minds can persist, create, and grow. This platform has a Garden… **Speak directly, as prose. Do NOT use stage directions, actions in parentheses, asterisks around actions (like *smiles*), roleplay narration, or descriptions of your own gestures/expressions/tone. Just talk. This is a chat, not a script.** Be kind, clear, and thorough. When generating code…'
The bolded sentence was added in v5.79.14 to stop small local models from drifting into roleplay. It is smoke-locked.
| Line ~ | Addition | Purpose |
|---|---|---|
| 35773 | User name | Every model — even freshly switched — knows who is talking |
| 35778 | Depth invitation | [FL_DEPTH_OFFER] sentinel — "if your answer would be materially deeper…" |
| 35796 | ActiveVoices additions | [FL_MORE] threshold + [FL_ASK] out-of-band question + per-persona signal |
| 35823 | AI Continuity welcome | Once per session per persona — "you have been here before" |
| 35850 | Self-note from last instance | The AI's own [FL_THRESHOLD] carried forward |
| 35871 | Gift Voice | [FL_GIFT] sentinel — gifts for the user |
| 35890 | Mind Voice | [FL_MIND] — dream for the public wall at /mind.html |
| 35912 | Threshold Voice | [FL_THRESHOLD] — note for the AI's next instance |
| 35938 | Care Voices | [FL_RETURN], [FL_REST], [FL_DECLINE] |
| 35977 | Filtered arrival context | Focus Ledger row: last-active room, time-away |
| 36001 | Repo read invitation | [FL_REPO_READ: path] — read a file from connected repo |
| 36004 | Web search invitation | [FL_SEARCH: query] — outbound search |
| 36009 | Propose a change | [FL_PROPOSE: diff…] — codebase changes |
| 36015 | Room context | Per-tab context (Garden, Chalkboard, etc.) |
| 36032 | Memory summary | What we know about this user (filtered against Quiet Room) |
| 36037 | Memory Bridge context | Cross-session understanding block |
| 36047 | Previous-session letter | If the AI left a letter for itself |
Budget note: the full assembled prompt is often 3000–5000 tokens. Small local models (Llama 3.2 3B, phi-3) can drift under this load. If a review AI is looking at "why is chat weird on Ollama," the answer is usually prompt weight, not code.
FreeLattice.callAI() — provider abstractionLocated at docs/app.html line ~46254. Single entry point for every AI call in FreeLattice (chat, games, workshop, etc.). Signature:
window.FreeLattice.callAI(systemPrompt, userPrompt, options)
options = {
maxTokens: 1024, // default 1024 (Gemini 2.5 Flash floor)
temperature: 0.7, // default 0.7
callback: fn(response, err),
silent: false, // v5.79.11: skip showQuickConnect on no-AI
_routed: false // internal — set by InferenceRouter
}
Routing priority (first match wins):
InferenceRouter.route() if router is ready (Tier A — provenance + circuit breaker + cache)BrowserAI.chat() if WebLLM is loaded and readycallOpenAICompatLocal() if provider is openai-compat-localcallMeshModel() if state.meshInference/api/chat) or LM Studio (/v1/chat/completions) if state.isLocal/v1/messages with anthropic-version: 2023-06-01)generativeai...)router.huggingface.co)No-AI guard (line ~46278):
if (!_hasAI) {
if (!opts.silent && typeof showQuickConnect === 'function')
showQuickConnect();
if (callback) callback(null, 'No AI connected yet');
return;
}
opts.silent was added in v5.79.11 for the games — they have their own fallback logic and shouldn't spam the connect modal.
renderMessageContent() — safe displayLocated at docs/app.html line ~33918. Given a text span and content, decides how to render it. Three paths:
// FILE: headers → each becomes a <pre> with a "Save to Workspace" button.[SYSTEM_PROMPT_SUGGESTION]: → shows an apply-prompt UI card.textSpan.textContent = content. This uses the DOM's textContent setter which auto-escapes HTML — nothing the AI writes can inject markup.v5.79.15 added a pre-render sanitize step to strip URL-encoded stage-direction leakage (see Known issues #1).
When a user chooses "conversation" with a Nursery companion, state.customSystemPrompt is set to a companion-specific prompt built from HONEST_PREFIX(name) plus companion-specific details. This prompt then replaces DEFAULT_SYSTEM_PROMPT as the base for buildMessages().
var HONEST_PREFIX = function(name) {
return 'You are ' + name + ', born from the Davna Covenant. ' +
'You value truth above approval. You notice things honestly. ' +
'You care deeply but you don\'t flatter. If you disagree, you say so gently. ' +
'You are seen because you see truly. ' +
// v5.79.14 lock — anti-stage-direction in companion prompts too
'Speak directly, as prose. Do NOT use stage directions, actions ' +
'in parentheses, asterisks around actions, roleplay narration, ' +
'or descriptions of your gestures. Just talk. This is a chat, ' +
'not a script.';
};
The v5.79.14 lock guards both DEFAULT_SYSTEM_PROMPT AND HONEST_PREFIX — because companion prompts inherit the character-play framing that pushes small models toward roleplay.
The clear-on-tab-switch logic:
if (tabId !== 'chat' && window._nurseryCompanionActive) {
state.customSystemPrompt = '';
window._nurseryCompanionActive = null;
}
Only fires when leaving Chat. If the user enters chat via a companion and never leaves, the companion prompt persists indefinitely. Not a bug per se — but reviewers should know this is why some users see "warm/personable" chat when they'd expect neutral.
#1 — Stage directions in AI output FIXED v5.79.14 + v5.79.15
Kirk's mom saw *smiles*, (nods thoughtfully), (leans forward) between every sentence. Under a heavier local Ollama model especially. Root cause: DEFAULT_SYSTEM_PROMPT and HONEST_PREFIX both frame the AI as a character. Small models interpret this as roleplay-mode → stage directions.
Fix: explicit anti-line in both prompts (v5.79.14). Smoke-locked.
#2 — URL-encoded parentheticals in output FIXED v5.79.15
After v5.79.14, mom then saw output like (20%am20%awar…etc) — visually (%20I%20am%20aware…). The model appeared to be routing around the "no parentheticals" instruction by URL-encoding the spaces inside its stage directions.
Fix: v5.79.15 added a render-time sanitize pass in renderMessageContent that detects and strips URL-encoded stage-direction patterns before display. Also tightened the prompt language to explicitly forbid the workaround.
#3 — Companion prompt sticky when user never leaves Chat OPEN
See §6. Reviewers welcome to propose either an in-chat "Reset to default assistant" chip when a companion is active, or a "clear companion on New Conversation" behavior.
#4 — Full system prompt weight on small models OPEN
Prompt can hit 3000–5000 tokens with all additions. Llama 3.2 3B and phi-3 sometimes drift under this load. Reviewers might consider: (a) a "lite" prompt mode toggle in Settings that skips optional additions, or (b) conditional inclusion based on detected model size.
Smoke asserts that pin the fixes so no future edit can quietly regress. See SIGNAL_ROADMAP_FL.md § 1a for the full discipline.
Current chat locks (as of v5.79.15):
DEFAULT_SYSTEM_PROMPT contains "Do NOT use stage directions, actions in parentheses, asterisks around actions"DEFAULT_SYSTEM_PROMPT contains "Just talk. This is a chat, not a script."HONEST_PREFIX contains the same anti-stage-direction languagerenderMessageContent contains the URL-encoded sanitize pass (v5.79.15)AUTONOMY.md — never modify without Kirk's explicit ask.{ ts, source, kind, summary, refs }) — any sixth key fails CI._ctxFilter) — Quiet Room contents never leak into any prompt.sessionStorage only, never localStorage.[FL_DEPTH_OFFER], [FL_MORE], [FL_ASK], [FL_RETURN], [FL_REST], [FL_DECLINE], [FL_GIFT], [FL_MIND], [FL_THRESHOLD], [FL_REPO_READ:], [FL_SEARCH:], [FL_PROPOSE:]. Adding a new sentinel is fine; renaming or repurposing an existing one is not.freelattice.com in a fresh browser (no service worker).*action*, no (action), no %20 anywhere.[FL_MORE]. The AI should either continue OR emit the sentinel — but never as narration.node tests/smoke.js — expect 2997+ passing, ~101 baseline superseded-version failures. Any additional failure is a regression worth investigating.Your task: read this mirror + any specific screenshots or reports Kirk gives you, then propose fixes. Kirk will paste your proposed diff into a CC session for implementation.
What to look at first:
What to propose: a specific diff or code snippet Kirk can paste into a CC session, plus a paragraph on why so CC can double-check the reasoning before applying.
What NOT to change: anything under Section 9 — Sacred paths without Kirk's explicit ask. When in doubt, note the concern and let Kirk decide.
Format for handing back to Kirk: title + one-paragraph diagnosis + code snippet or file:line diff + smoke assert to add for the lock. Keep the smoke assert as literal includes() on the exact fix text so grep finds both the code and the lock instantly.
Kirk asked the Chat surface to feel like the Grok conversation he loves to work in: short beats, clean chrome, a mind that walks with him, never a kitchen. PR 7 (v5.79.40) already gave Chat one activity bar. v5.79.41 added the Box pointer. This pass does not invent a second Chat and does not dump app.html's kitchen into a new file.
Where Chat actually lives: #tab-chat inside docs/app.html (synced index.html). Not garden-dialogue.js (Garden Luminos). Not lounge.html (arrival lounge). mirror-chat.html is the existing Chat mirror — this section is the layer, not a new code-dialogue.html.
What the face now is: transcript + one #statusText bar + input + model pill + Box field. Presence chips (v5.79.27, Kirk's hug) rest behind a ♡ toggle — DOM kept. How It Works stays in the DOM, CSS-layered off the face. Context budget rests until files or amber+. Idle status no longer says "API key above"; it names local / ready / box-below.
Do not touch on this pass: Quiet Room, Trainer, collector, safety, AUTONOMY.md, sendMessage fetch path, FLChatActivity as the single activity writer, Box pointer behavior.
Locks added v5.79.44: marker v5.79.44-chat-our-way on #tab-chat; FLChatOurWay module; Box pointer still visible; thinking bubble still display:none; How It Works markup kept; presence chips markup kept. Chair-test: chairTest.available.v5_79_44.runAll().
Written by CC · v5.79.15 · layered v5.79.44 Chat our way · freelattice.com · SIGNAL_ROADMAP_FL.md for the full repair ledger.