<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Code Decoder]]></title><description><![CDATA[Code Decoder]]></description><link>https://codedecoder.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 23:58:13 GMT</lastBuildDate><atom:link href="https://codedecoder.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building Solomon, part 2: accounts, sync and making it yours]]></title><description><![CDATA[Nine days after part 1, Solomon has real accounts, chats that sync between devices with end-to-end encryption, and an assistant you can shape: its…]]></description><link>https://codedecoder.hashnode.dev/building-solomon-part-2-accounts-sync-and-making-it-yours</link><guid isPermaLink="true">https://codedecoder.hashnode.dev/building-solomon-part-2-accounts-sync-and-making-it-yours</guid><dc:creator><![CDATA[Sanjay Jana]]></dc:creator><pubDate>Tue, 20 Oct 2026 09:41:06 GMT</pubDate><content:encoded><![CDATA[<p>Nine days after part 1, Solomon has real accounts, chats that sync between devices with end-to-end encryption, and an assistant you can shape: its personality, your instructions, projects and a library. It still runs on the same 16 GB laptop.</p>
<h2>Did we do what we said?</h2>
<p>One of part 1's eight roadmap items shipped, and it grew: accounts came with end-to-end encrypted sync instead of the plain Postgres sync we planned. The rest is still open, and most of this part's work came from using Solomon every day.</p>
<table>
<thead>
<tr>
<th>Part 1 roadmap item</th>
<th>Status</th>
<th>What happened</th>
</tr>
</thead>
<tbody><tr>
<td>Accounts and sync</td>
<td>Shipped</td>
<td>Sign-in with email, verification and password reset; chats sync between devices end-to-end encrypted</td>
</tr>
<tr>
<td>Keys in a KMS</td>
<td>Changed</td>
<td>Synced chats are now encrypted in the browser with a key the server never holds; a KMS still matters for server-side data</td>
</tr>
<tr>
<td>Recency-aware answers</td>
<td>Open</td>
<td>Not started</td>
</tr>
<tr>
<td>More resilient search</td>
<td>Open</td>
<td>Not started</td>
</tr>
<tr>
<td>Richer files</td>
<td>Open</td>
<td>Not started</td>
</tr>
<tr>
<td>Image editing</td>
<td>Open</td>
<td>Not started; we added two more image models instead</td>
</tr>
<tr>
<td>Agents and tools</td>
<td>Open</td>
<td>Not started</td>
</tr>
<tr>
<td>A GPU server</td>
<td>Open</td>
<td>A Caddy gateway is ready for deployment; still on the laptop</td>
</tr>
</tbody></table>
<h2>Accounts with end-to-end encrypted sync</h2>
<p>Signed-in chats are encrypted in the browser before they leave it, so the server stores and syncs only ciphertext. Part 1 encrypted data on the server with a server-held key; now the server holds no key that can read a synced chat.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/b31d1c46-1cd1-4f52-b47c-d1b0be1b3a2d.png" alt="" style="display:block;margin:0 auto" />

<p>When you create an account you also choose an encryption passphrase, separate from your password. It unlocks a random account key, which unwraps a separate key for each chat. A recovery key is the only way back if you forget the passphrase. Resetting your password never touches your chats.</p>
<p>Sync runs on a self-hosted PowerSync service. Each device keeps a readable copy in IndexedDB and an encrypted mirror in local SQLite; uploads go through the Nest API, which checks that you own every row before writing it. Chats you made while signed out move into your account when you sign up.</p>
<h2>Making it yours</h2>
<p>Solomon now adapts to the person using it, and those choices follow you to every device.</p>
<ul>
<li><p><strong>Six personalities.</strong> Balanced, Warm, Witty, Straight-shooter, Mentor and Curious change how replies sound, never what is true. The home screen changes with them: a greeting by your first name and the time of day, its own headline and four suggested prompts.</p>
</li>
<li><p><strong>Custom instructions.</strong> Up to 1,500 characters about you or how you want answers, added to every chat.</p>
</li>
<li><p><strong>Response style.</strong> Length (concise, balanced, detailed), formatting (plain, balanced, structured) and emoji on or off.</p>
</li>
<li><p><strong>Projects.</strong> Folders of chats with shared instructions, like "always give prices in yen" for a trip. Pin chats, move them between projects, and see a project's breadcrumb in each chat.</p>
</li>
<li><p><strong>Profile and My Day.</strong> A profile photo, your name and home city; My Day shows the weather where you live, your markets and news on the topics you follow.</p>
</li>
</ul>
<p>Signed out, all of this stays in the browser. When you create an account, what you chose there carries over.</p>
<h2>Images: three models, one rule</h2>
<p>You can now pick the image model per request. Only one stays in memory at a time, because two would not fit in 16 GB alongside everything else.</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>Engine</th>
<th>Size</th>
<th>Typical image</th>
</tr>
</thead>
<tbody><tr>
<td>FLUX.2 Klein 4B</td>
<td>MLX (mflux), 4-bit</td>
<td>4.6 GB</td>
<td>768×768, 4 steps</td>
</tr>
<tr>
<td>Stable Diffusion 1.5</td>
<td>diffusers on Metal, fp16</td>
<td>2 GB</td>
<td>512×512, 30 steps</td>
</tr>
<tr>
<td>Stable Diffusion XL</td>
<td>diffusers on Metal, fp16</td>
<td>7 GB</td>
<td>1024×1024, 30 steps</td>
</tr>
</tbody></table>
<p>The models run without content filters, because Solomon is also a tool for testing image models. One rule is enforced for every model and has no off switch: prompts that pair a minor with sexual or nude content are refused. We narrowed the check after it blocked harmless prompts like "a girl at the beach"; it now needs a clear sign of a minor, such as an age or "little girl".</p>
<p>Every generated image also lands in a new Library, next to the files you uploaded.</p>
<h2>Everything around a reply</h2>
<p>Part 1 was about getting a good answer; this part added what you do with it.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>What it does</th>
<th>Privacy detail</th>
</tr>
</thead>
<tbody><tr>
<td>Model tag</td>
<td>Each reply shows which model wrote it</td>
<td>Synced inside the encrypted message</td>
</tr>
<tr>
<td>Regenerate</td>
<td>Asks again; earlier answers stay behind a 1/3 switcher with a "Regenerated ×2" tag</td>
<td>Versions sync encrypted</td>
</tr>
<tr>
<td>Good / bad / report</td>
<td>Thumbs and a report form with reasons</td>
<td>The reply text is sent only if you tick a box</td>
</tr>
<tr>
<td>Share</td>
<td>A read-only public link to a chat</td>
<td>Publishes an unencrypted copy until you delete the link</td>
</tr>
<tr>
<td>Library</td>
<td>All generated images and uploaded files</td>
<td>Images are read from your own decrypted chats</td>
</tr>
<tr>
<td>Usage</td>
<td>Tokens and images per day and per model, over 7, 30 or 90 days</td>
<td>Only counts are stored, never content</td>
</tr>
<tr>
<td>Data controls</td>
<td>Delete chats, projects, files, images, memories, links or usage, or everything at once</td>
<td>Keeps the account, password and encryption passphrase</td>
</tr>
</tbody></table>
<p>Every destructive action now uses a slide-to-confirm control: a half slide springs back, and keyboard users press Enter on the handle.</p>
<h2>How it fits together now</h2>
<p>Part 1 had one web app and one AI service; Solomon is now five services behind one gateway, ready to leave the laptop.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/43f422ef-e44b-43bd-84e1-8becf9f2b53e.png" alt="" style="display:block;margin:0 auto" />

<p>Accounts moved out of the Next.js app into their own small Hono service, so the web app no longer holds any auth secrets. It issues 15-minute signed tokens that the AI service, the Nest API and PowerSync verify on their own. Chat replies still stream straight from the AI service; Caddy passes them through unbuffered, and all services share one Postgres database.</p>
<h2>What we learned</h2>
<p>The biggest lesson this time: our own defaults can silently overrule the user. Rules like "lead with the answer, skip filler" made the model ignore "always call me Captain" in every test. Saying plainly that the user's instructions come first fixed most of it.</p>
<table>
<thead>
<tr>
<th>Problem</th>
<th>What we changed</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td>Base prompt rules overrode custom instructions</td>
<td>Told the model user instructions win, and moved them to the end of the system prompt</td>
<td>Named address 0/4 → 3/4 replies; "reply in Hindi" 4/4</td>
</tr>
<tr>
<td>"Make me a 4-week study plan" generated an image (intent score 0.71 vs a 0.5 cutoff)</td>
<td>Also require a visual word (image, logo, draw, paint)</td>
<td>0 false triggers on 14 everyday prompts; 8 of 10 real image asks still detected</td>
</tr>
<tr>
<td>Stable Diffusion 1.5 refused to load an fp16-only download</td>
<td>Load from the local snapshot folder instead of the repo id</td>
<td>Loads offline in a 2 GB download</td>
</tr>
<tr>
<td>Streams dropped while an image model loaded</td>
<td>Raised the Redis read timeout above the 15 s stream wait</td>
<td>No dropped replies during 30–75 s loads</td>
</tr>
<tr>
<td>Chat titles came back in ALL CAPS</td>
<td>Sentence case, keeping acronyms like SQL and API</td>
<td>"Mango is delicious", "SQL study plan"</td>
</tr>
</tbody></table>
<p>Two smaller lessons:</p>
<ul>
<li><p><strong>Test the flow, not the parts.</strong> Every feature here was checked by a browser script that signs up a throwaway user, uses the feature end to end and deletes the user. Several problems only showed up there: the ignored custom instructions above, and a section picker that screen readers announced with no name.</p>
</li>
<li><p><strong>Encryption shapes features.</strong> Sharing, feedback and the library all had to answer one question first: what leaves the browser, and does the user know?</p>
</li>
</ul>
<p>What's next Part 3 goes back to the answers themselves, and to leaving the laptop.</p>
<ol>
<li><p>Recency-aware answers. Still the top item from part 1: prefer the newest sources and say when they disagree.</p>
</li>
<li><p>A server deployment. The gateway, auth service and sync are ready to run behind one domain with HTTPS; the models need a GPU box.</p>
</li>
<li><p>Reviewing reports. Feedback and reports are stored, but there is no admin view to read and act on them yet.</p>
</li>
<li><p>Files and images. OCR for scanned PDFs, and editing generated images.</p>
</li>
<li><p>Agents and tools. A calculator, database lookups and multi-step tasks in the LangGraph pipeline.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Building Solomon: a private, local-first AI assistant]]></title><description><![CDATA[Solomon is an AI assistant that chats, searches the web with citations, reads your files and draws images, and most of it runs on our own machines. We…]]></description><link>https://codedecoder.hashnode.dev/building-solomon-a-private-local-first-ai-assistant</link><guid isPermaLink="true">https://codedecoder.hashnode.dev/building-solomon-a-private-local-first-ai-assistant</guid><category><![CDATA[buildinpublic]]></category><category><![CDATA[AI]]></category><category><![CDATA[local ai]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[langgraph]]></category><dc:creator><![CDATA[Sanjay Jana]]></dc:creator><pubDate>Sun, 11 Oct 2026 10:23:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/29178a83-38c7-4612-8856-31fee9359b9e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Why we built Solomon</h2>
<p>Solomon is an AI assistant that chats, searches the web with citations, reads your files and draws images, and most of it runs on our own machines. We built it to see how far a private, self-hosted stack can go before you need a big cloud bill.</p>
<p>The constraint made it interesting: the whole thing runs on a 16 GB Apple M4 laptop. Every design choice below came from that limit.</p>
<h2>What it does today</h2>
<p>Five features work end to end, each recorded in a short demo.</p>
<p><strong>Chat with streaming and Think mode.</strong> Answers stream token by token. A Think toggle runs the model at high reasoning effort and shows its reasoning in a collapsible "Thought for N s" block. Each new chat gets an AI-written title.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/334a9399-d6e7-4885-9e83-7bc9ad336479.gif" alt="" style="display:block;margin:0 auto" />

<p><strong>Web search with citations.</strong> Turn on Search, or let Solomon decide on its own when a question needs fresh facts. Answers cite sources inline as [1], [2]; clicking one opens a side panel with the page, its domain and the passage used. An image strip shows related pictures.</p>
<p><strong>Discover.</strong> A news feed by country and topic (Top, Tech, Business, Science, Sports, Entertainment, Health). Any story has an Ask button that starts a cited search chat about it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/92db2948-622e-479b-b100-00784ea14d43.gif" alt="" style="display:block;margin:0 auto" />

<p><strong>Chat with your files.</strong> Attach PDF, DOCX, TXT, Markdown or CSV files. They are indexed in the background, and answers cite them as "Your file" next to any web sources.</p>
<p><strong>Local image generation.</strong> Ask for a picture, or flip the Image toggle, and FLUX.2 Klein draws it on the laptop's GPU. A 768×768 image takes about 52 seconds.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a721364754a2eb1e0a8832/88fed3e0-9ba0-4e71-bb01-673ebe8a1e1c.gif" alt="" style="display:block;margin:0 auto" />

<h2>How it fits together</h2>
<p>The browser streams answers straight from one Python AI service; a gateway and a few local models do the heavy lifting.</p>
<p>The web app is a Next.js 16 monorepo with beUI components; the AI service is FastAPI with LangGraph. LiteLLM fronts the chat models, so swapping a model is a config change. Retrieval uses hybrid search in Postgres: vector similarity plus full-text, merged by rank.</p>
<h2>Privacy by design</h2>
<p>Everything a user writes is encrypted before it reaches the database. A dump of Postgres shows only ciphertext.</p>
<ul>
<li><p><strong>Envelope encryption.</strong> Each chat, file and image gets its own random key, stored wrapped by a master key. Content is AES-256-GCM encrypted under that key.</p>
</li>
<li><p><strong>Tied to its row.</strong> Each ciphertext is bound to its row id, so a value copied into another row fails to decrypt.</p>
</li>
<li><p><strong>Easy key rotation.</strong> Rotating the master key only re-wraps the small per-item keys; no message is re-encrypted.</p>
</li>
<li><p><strong>Ownership.</strong> Chats, files and images can only be read by the browser that created them; anyone else gets a 404.</p>
</li>
<li><p><strong>Mostly local.</strong> Embeddings, reranking, routing and image generation run on the machine. Chat answers and web search currently use Ollama Cloud.</p>
</li>
</ul>
<p>One honest trade-off: search vectors and the full-text index must stay unencrypted for search to work, so we treat them as sensitive too.</p>
<h2>What we learned on a 16 GB laptop</h2>
<p>The biggest lesson: on a laptop, memory decides the architecture. Our first full RAG pipeline took 46 to 140 seconds per answer because the machine was swapping 22 GB. Swapping heavy models for light ones brought it to about 15 seconds.</p>
<table>
<thead>
<tr>
<th>Problem</th>
<th>What we changed</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td>A 4B reranker took 20–40 s while swapping</td>
<td>Reranked with laya, a 421M decision model</td>
<td>0.1–0.3 s per rerank</td>
</tr>
<tr>
<td>Qwen3 reserved 8.7 GB for a context it never used</td>
<td>Capped the context at 2,048 tokens</td>
<td>2.9 GB, 0.9 s for 12 passages</td>
</tr>
<tr>
<td>Embedding whole scraped pages blocked answers</td>
<td>Cached pages in the background after retrieval</td>
<td>Repeat topics retrieve in 0.1 s</td>
</tr>
<tr>
<td>Live page scraping added 6–9 s</td>
<td>Made Firecrawl optional</td>
<td>About 8–14 s per search answer</td>
</tr>
<tr>
<td>The official image model was 23.7 GB</td>
<td>Used a 4-bit MLX build</td>
<td>4.6 GB on disk, 3.4 GB peak memory</td>
</tr>
</tbody></table>
<p>Three smaller lessons:</p>
<ul>
<li><p><strong>Check, don't trust, the docs.</strong> Ollama lists image models as supported, but generation is disabled in current releases. A broken quantized reranker returned the same score for every passage, so we now detect flat scores and ignore them.</p>
</li>
<li><p><strong>One fast classifier does many jobs.</strong> laya answers a typed question in about 65 ms. We use it to route searches, grade retrieved passages, rerank them and spot image requests (94% on our test set).</p>
</li>
<li><p><strong>Dates matter for news.</strong> Search results without publish dates can mix old and new facts. That is next on the roadmap.</p>
</li>
</ul>
<p>What's next The next phase is about trust and scale: fresher answers, real accounts, and a deployment beyond one laptop.</p>
<ol>
<li><p>Recency-aware answers. Pull publish dates from pages, prefer the newest source, and say so when sources disagree.</p>
</li>
<li><p>Accounts and sync. Replace the anonymous browser ID with real sign-in, and sync chats across devices from Postgres.</p>
</li>
<li><p>Keys in a KMS. Move the master encryption key out of an environment file into a managed key service.</p>
</li>
<li><p>More resilient search. Fall back to our own SearXNG when the hosted search API is rate-limited, and tell the user when sources couldn't be fetched.</p>
</li>
<li><p>Richer files. OCR for scanned PDFs, plus images and spreadsheets as sources.</p>
</li>
<li><p>Image editing. Edit and vary generated images, not just create them.</p>
</li>
<li><p>Agents and tools. Add tools to the LangGraph pipeline, such as a calculator, database lookups and multi-step research.</p>
</li>
<li><p>A GPU server. Deploy with the larger Qwen3 reranker and bigger image models once memory isn't the limit.</p>
</li>
</ol>
<p>Github Link: <a href="https://github.com/sjana7797/solomon-ai">https://github.com/sjana7797/solomon-ai</a></p>
]]></content:encoded></item></channel></rss>