An AI assistant does not become trustworthy because it knows more facts. It becomes trustworthy when it knows which facts matter, where they came from, when to use them, and how to prove the answer was grounded in something real.
More Information Was Not the Real Goal
Most of my work this week centered on improving how Cypher handles information.
That included importing years of health records and archived conversations, reading links instead of guessing from them, selecting better context for meaningful personal moments, cleaning up web sources, and giving me direct control over the model and reasoning level used in each conversation.
I also built Local Places into Bonumark Stream, which created a different version of the same challenge. The feature needed enough location information to be useful without depending on a paid service, exposing private coordinates, weakening security controls, or turning a quick post into a database form.
The work looked different on the surface, but it kept returning to the same standard:
More capability is not automatically better.
The information has to be structured correctly. The source has to be visible. The feature has to activate at the right time. The interface has to stay usable. The system has to behave correctly outside the test environment.
Cypher is my private personal assistant. It is not a public product, and it does not have public releases. I upgrade it as I improve how it remembers, reasons, responds, publishes, understands my schedule, uses context, and supports my daily life.
This week was about making those parts more dependable.
Giving Cypher Access to My History
The largest job was giving Cypher access to three major archives:
My Garmin data.
My Fitbit data.
My full ChatGPT history.
The simple request was to give Cypher all of it.
The real problem was deciding what “all of it” should mean.
I could have dumped thousands of files into a semantic memory system and declared that Cypher now knew more about me. That would have created the appearance of progress while making the system harder to trust.
Health records would have become text fragments. Duplicate sleep sessions could have inflated totals. Fitbit and Garmin could have disagreed without a source rule. Old assistant responses could have returned later as if they were proven facts about me.
I did not want a larger pile of context.
I wanted a personal data system that could answer exact questions and show where the answer came from.
The first decision was to keep the data separated by type.
Garmin and Fitbit contained structured health information. That data needed to support calculations involving activities, distance, sleep, heart rate, HRV, steps, weight, and daily measurements.
The ChatGPT archive contained conversations. It needed search and historical context, but it also required stronger evidence boundaries. Something I said could support a fact, decision, or part of my history. Something an assistant said could help explain the conversation, but it could not independently prove something about me.
Those archives did not belong in one undifferentiated memory pool.
I created a Personal Data Library with two separate systems.
Garmin and Fitbit feed a structured health ledger.
ChatGPT history remains inside a role-aware conversation archive.
Cypher can use both when a question requires them, but their records, evidence rules, and retrieval paths remain distinct.
Establishing Source Authority
The original archives gave me a clean source transition.
Fitbit covered the older period through April 29, 2024.
Garmin began on April 30, 2024, and became the stronger source for newer health records.
That allowed me to define an authority rule instead of blending values silently.
Fitbit serves as the historical source.
Garmin serves as the primary source for the newer period.
Any overlap stays labeled by source so I can inspect it instead of letting Cypher average two systems together and pretend the result is exact.
The ChatGPT archive created a different scale problem. It contained 31 conversation files, 3,041 conversations, and more than 51,000 visible user and assistant messages.
Cypher’s existing memory importer was not built for that.
It accepted smaller files, converted them into plain text, and sent chunks through semantic indexing. Using that path would have stripped away message roles, conversation relationships, branches, timestamps, and evidence boundaries.
I rejected that approach and built storage specifically for archived conversations and messages.
The archive remains separate from Cypher’s native chat history. Old ChatGPT conversations do not appear as if they happened inside Cypher. User and assistant roles stay attached to every message.
That distinction matters because an assistant becomes less trustworthy the moment its old guesses begin returning as personal truth.
Importing More Than 64,000 Records
The original exports were too large for one browser request.
The Fitbit archive alone contained roughly 22,000 files and about seven gigabytes of uncompressed data. Garmin included more than 12,000 original FIT files along with activity summaries, sleep records, daily wellness data, heart-rate information, HRV, training metrics, and body measurements.
I required the importer to be resumable.
The prepared data was divided into 133 verified parts. Cypher committed each completed section before moving to the next one. If the browser closed or the server interrupted the process, the import could continue without starting over.
Checksums prevented the same bundle from being imported twice. Original source identifiers prevented duplicate records inside the archives from becoming duplicate records inside Cypher.
The final import created:
2,526 activities.
3,097 unique sleep sessions.
3,635 daily health records.
158 body measurements.
3,041 archived ChatGPT conversations.
51,631 archived messages.
The total was 64,088 unique records.
The sleep count gave me an immediate way to verify the duplicate system.
The raw data contained 3,174 sleep rows. Seventy-seven were repeated Fitbit records. Cypher identified them through their original Fitbit IDs and retained 3,097 unique sessions.
The duplicate protection was not just present in the code.
The final count proved it was operating.
Testing the Data Instead of Admiring the Totals
A successful import did not prove that the data was useful.
I tested Cypher with questions that required exact calculations.
I checked running totals across specific date ranges. I checked yearly activity history. I tested the transition between the final Fitbit day and the first Garmin day. I asked about sleep averages, resting heart rate, HRV, steps, body weight, and daily distance.
One sleep result initially appeared wrong.
I asked for my average sleep in 2023. Cypher returned 5 hours and 52 minutes across 370 recorded sessions. The original expected count was 382.
Further inspection showed that the 382 raw entries contained 12 exact duplicates.
Cypher was correct.
The expectation was wrong.
That matters because verification is not about forcing the system to match the first number written into a test. Sometimes the test exposes a bad assumption instead of a bad implementation.
Another test found a real defect.
Fitbit stored daily-distance samples in centimeters. During preparation, those values had been labeled as meters without being converted. That made Fitbit’s all-day distance totals approximately 100 times too large.
Workout and running mileage remained correct because they came through a different data path. The defect affected only Fitbit’s daily-distance field.
I directed a targeted correction.
Affected Fitbit values were divided by 100. Garmin values and recorded workout distances were left untouched. The repair also recorded that it had run so the same conversion could not be applied twice.
After the correction, Cypher reported an average daily distance of 6.18 miles in 2023, with 2,254.47 miles across the year.
That result was realistic, internally consistent, and mathematically correct.
The repair passed.
Searching My Old Conversations Without Rewriting My History
I also tested whether Cypher could retrieve historical ChatGPT conversations without weakening its evidence standards.
One test asked Cypher to find a specific statement I had made about being a builder. It located the user message and identified the conversation where I said it.
Another test asked about an older project decision involving the final phase of Cypher’s article knowledge and WordPress roadmap.
Cypher found the decision that the roadmap ended after Phase 5. It also retrieved the instruction that future work should be treated as maintenance, fixes, refinements, and upgrades rather than additional roadmap phases.
That required more than locating an exact quote.
Cypher had to retrieve an old decision, understand its meaning, and apply it to the current question.
I tested the evidence boundary directly, too.
Cypher correctly stated that archived assistant messages could not independently prove personal facts about me.
Finally, I tested ordinary conversation. I wanted to make sure the new archive did not activate every time a current message resembled something I had discussed before.
A casual statement still received a normal reply. Cypher did not search more than 51,000 archived messages just because historical data was available.
That proved the archive could remain accessible without taking over the conversation.
Making the Personal Data Interface Match the System
The first Personal Data admin page worked, but it looked unfinished.
Summary totals collapsed into cramped text. Import details were technically present, but the page had weak hierarchy. Beside the more polished Memory, Settings, Backups, Help, and Usage pages, it looked like a functional first pass.
I directed another refinement.
The page received proper summary cards, verified-import status, separate counts for imported records and duplicates, a cleaner archive search, a compact empty-inbox state, and better spacing across desktop and mobile layouts.
That mattered because this was not a temporary import script.
The Personal Data Library is now part of how I inspect, verify, and manage Cypher’s information foundation. The interface needed to reflect that.
Teaching Cypher to Read the Actual Link
The next problem appeared during ordinary use.
I gave Cypher a link to a new article on JimLunsford.com. It responded as if it understood the argument inside the article.
The answer sounded reasonable.
It was also unsupported.
When I asked whether Cypher could actually see the page, it admitted that it could not. It had used the URL, domain, and words in the slug to infer what the article probably said.
That exposed a credibility problem.
A readable URL is not evidence that a page was opened.
A plausible interpretation is not the same as knowing what the source contains.
I defined a direct requirement.
When I intentionally give Cypher a link, it must inspect that exact page before describing, summarizing, evaluating, or reacting to the content.
If the page cannot be opened, Cypher has to say that plainly.
It cannot fill the missing evidence with a confident guess.
I added OpenAI’s hosted web-search capability to Cypher’s response pipeline. During normal conversation, web access remains available when current or external information is needed. When a message contains an HTTP or HTTPS address, tool use becomes required.
I also added support for structured URL citations. Those citations are stored with the assistant response and displayed as clickable web sources inside the conversation.
The evidence remains available after the page reloads.
The upgrade was tested with URL detection, forced web-tool use, citation extraction, message storage, history restoration, and source-card rendering.
The package passed 39 PHP regression tests, eight browser-side tests, syntax checks across 107 PHP files, release-manifest verification, and ZIP-integrity checks.
The packaged test environment did not contain my live API credentials, so the final verification happened after I installed the upgrade.
I used the exact article link that had exposed the original problem.
This time, Cypher opened the page, summarized the actual argument, connected the article to the projects discussed inside it, and displayed the JimLunsford.com page as a clickable source.
It did not infer the article from the title.
It read it.
Keeping Web Evidence Without Burying the Conversation
Adding working web sources created another problem.
A normal legal question produced an answer that was only a few paragraphs long, followed by a source area taller than the answer itself. The same government domain appeared repeatedly. Raw Markdown links and long tracking addresses appeared inside the reply instead of clean clickable text.
The web search worked.
The chat experience did not.
Removing sources was not an option. Current and external information needs evidence. The problem was how that evidence was being selected and displayed.
I required Cypher to distinguish between pages actually cited in the final answer and the larger set of pages returned during the search.
The visible source list now prioritizes citations attached to the answer. Duplicate URLs are removed. Common tracking parameters are stripped. When citation data is unavailable, the broader search results remain available as a fallback.
The large open source card was replaced by a compact collapsed control showing the number of available references. I can expand it when I want to inspect the evidence, but the sources no longer dominate every web-grounded answer.
The renderer was also upgraded to recognize Markdown links and plain web addresses and convert them into safe clickable links.
The same behavior applies to new responses and old messages restored from conversation history.
This was not just a CSS change.
Making the source box smaller would have hidden part of the symptom without fixing which sources deserved to appear. The better solution was to improve source selection, normalize the addresses, render the links correctly, and give the interface a quieter default state.
Trust and usability are connected.
Evidence should be available.
It should not bury the conversation.
Fixing a Routing Failure
Another context failure showed up after I accidentally pasted a Builder Receipt prompt into a Cypher conversation.
I told Cypher to disregard it, then gave a short update about work I had completed on Local Places in Bonumark Stream.
Instead of responding to the project update, Cypher told me what my first published article was.
I tried the message again.
It returned the same unrelated answer.
The response was too specific and too consistent to be random. I traced the problem to Cypher’s deterministic writing-inventory detector.
That feature exists for questions such as:
“What was my first published post?”
“How many Builder Receipts have I written?”
“List every Recovery Standard.”
Those questions should not depend on a model estimating the answer. Cypher should identify the request and query the synchronized writing catalog directly.
The detector was doing the right job too aggressively.
It matched isolated words anywhere in a message. My Local Places update contained the word “post” and the phrase “The first build worked.” That was enough for the router to classify the entire message as a request for my first published post.
The model was not ignoring me.
The message never reached normal conversation.
Cypher’s routing had already decided what I meant.
I tightened the detector so it now looks for actual question and command structure instead of isolated vocabulary.
Real inventory requests still activate the deterministic path.
Ordinary statements containing words such as “first,” “post,” “article,” or “Builder Receipt” remain normal conversation.
I added the exact messages that exposed the failure as regression cases. That preserved the intended inventory behavior while preventing it from hijacking unrelated messages.
The answer had been accurate.
The failure was deciding that I had asked for it.
Teaching Cypher Why a Moment Matters
The second context problem was more subtle because the original response was technically correct.
I told Cypher it was my birthday. I also told ChatGPT the same thing.
Cypher knew more current facts. It knew my age, family, work schedule, and that I had limited time before a shift. It used those details, but the response sounded like a calendar assistant reminding me to enjoy some time before work.
ChatGPT used less immediate context but understood the larger meaning. It connected the birthday to recovery, rebuilding my life, family, physical transformation, and reaching a milestone that carried more weight than another date on the calendar.
The comparison exposed the real weakness.
Cypher had more information, but it chose the wrong information.
It reached for recent logistics because they were concrete and easy to retrieve. It did not reach deeply enough into identity, recovery, relationships, accomplishments, or the life I had rebuilt.
I did not immediately replace the model.
Cypher was using GPT-5.6 Terra. ChatGPT was using GPT-5.6 Sol. Sol might have produced stronger language, but model choice did not explain why the retrieval system had selected schedule details instead of meaningful history.
I kept Terra at medium reasoning and fixed the system around it first.
The next upgrade added milestone-aware context.
Cypher now recognizes direct statements involving birthdays, sobriety anniversaries, relationship anniversaries, major completions, and significant transitions.
When that route activates, retrieval broadens beyond recent activity and logistics. Cypher looks toward identity, recovery history, family, important relationships, values, accomplishments, and major life changes.
The response instruction also changed.
Cypher should prefer the meaning of the moment over ordinary logistics. It should make one or two relevant connections, not unload everything it knows or turn the answer into a biography.
The goal is not maximum personalization.
The goal is relevant personalization.
I added milestone information to the answer audit and diagnostics so I can verify whether the route activated, which milestone was detected, and what response focus was selected.
Then I repeated the birthday test.
Cypher connected turning 50 to the life I had rebuilt, my family, my physical condition, meaningful work, and the systems and projects I continue to create.
It did not just congratulate me.
It understood why the birthday carried weight.
Testing Meaningful Context
I ran another test involving recovery.
I told Cypher that I had been sober for almost eleven years and sometimes forgot how much my life had changed.
Cypher connected sobriety to the life built on the other side of addiction. It used family, work, fitness, and my projects without listing them mechanically.
The strongest part of the response was recognizing that massive change can begin to feel ordinary when someone lives inside the result every day.
That was interpretation, not retrieval.
The timeline also stayed accurate. It used “almost eleven years” instead of incorrectly treating the anniversary as already completed.
I tested a family recovery milestone too.
Cypher kept the focus on the family member instead of turning the moment entirely back toward me. It used the family’s broader history to support the answer without dumping private details or turning the response into a speech.
That mattered.
Personal context should support the subject of the conversation.
It should not make every meaningful moment about the person whose memory system is being used.
These tests proved that having more memory is not enough. Personalization comes from selecting the right context, understanding what the facts mean together, and knowing when they matter.
Terra was capable of producing the better response once the surrounding system gave it better material and clearer direction.
Giving Myself Better Model Control
Even after fixing milestone context, I still wanted a clean way to compare Terra and Sol inside Cypher.
The easy option would have been to move everything to Sol.
That would have been wasteful.
Cypher handles schedule questions, health calculations, memory retrieval, writing searches, publishing commands, project updates, and ordinary conversation. Many of those tasks do not require the most expensive model.
I needed control where the conversation actually happened.
I added a compact model selector inside the composer beside the existing Writing control. The first version offered Terra and Sol and stored the choice per conversation.
Terra remained the default.
Sol could be selected when I needed deeper interpretation, stronger writing, or a more thoughtful personal conversation.
Then I caught a missing requirement.
The selector changed the model but did not let me control reasoning effort.
That meant any comparison would be incomplete. Sol at one reasoning level and Sol at another are not the same configuration. Without a reasoning control, I would not know whether the response difference came from the model, the reasoning setting, or Cypher’s global default.
The first implementation looked finished.
It solved only half the problem.
I expanded the control to display both settings:
Model: Terra · Medium
The menu now provides separate model and reasoning sections. Reasoning options include None, Low, Medium, High, XHigh, and Max.
Both values are stored per conversation.
One conversation can remain on Terra at Medium. Another can use Sol at High. Stronger reasoning can be applied where it earns its cost without raising the cost of every Cypher interaction.
The upgrade was verified through PHP linting, the existing regression suite, browser testing, release-manifest checks, request handling, per-conversation persistence, reasoning-effort handling, and compatibility with older conversations using the global default.
The upgrade did not prove that Sol solves every conversational weakness.
It gave me the controls required to test that honestly.
Building Local Places Without Renting the Feature
Cypher was not the only system I worked on.
I also built Local Places into Bonumark Stream.
The idea came from the part of Swarm I liked: attaching a place to a short post.
The easiest first version would have used Google, Foursquare, OpenStreetMap servers, or another commercial places provider. That would have produced quick search results, but it would also have placed a basic publishing feature behind someone else’s API, pricing, and availability.
That did not fit Bonumark Stream.
The decision was to build the local system first.
Each Bonumark Stream instance keeps its own private directory of saved places. The browser can use location permission to determine which saved places are nearby, but place records, coordinates, and post information stay on the local instance.
The owner creates a place once and can select it later.
Raw coordinates do not appear on the public post. The public display can use the place name, an approximate area, or only the city.
Posts keep a location snapshot so an older post does not break if the saved place is later edited or removed.
The feature works without a paid API and without a future central Bonumark Places service.
That was the product boundary.
When the First Working Build Was Still Wrong
The first version worked, but the composer exposed a design problem immediately.
It placed most of the saved-place record inside the publishing interface. The user could choose a place, select a display mode, set the category, enter the approximate area, city, state, country, and other details.
Technically, it offered control.
Practically, it turned a quick post into a database form.
I reduced the composer to three actions:
Choose a saved place.
Find nearby.
Add a new place.
Creating a place now requires only a name and an optional public location label. Full place management remains available in the admin area.
Once selected, the location collapses into a compact row with controls to change or remove it.
The composer handles publishing.
The admin area handles record management.
That was the right separation.
Browser Testing Found the Security Conflict
Static checks passed, but the real browser test exposed another failure.
The Use Current Location button did nothing.
The problem was not the user’s browser permission. Bonumark Stream was blocking geolocation through two separate security controls.
The site’s Permissions Policy disabled geolocation for every page. The admin control also depended on inline JavaScript, which the Content Security Policy correctly refused to execute.
The protections were working exactly as configured.
The new feature had been built in conflict with them.
I changed the implementation without weakening the security model broadly.
Geolocation became available only from the Bonumark Stream instance itself. The inline handler was moved into an approved external JavaScript file. Error handling now explains whether HTTPS is unavailable, permission was denied, the location could not be determined, or the request timed out.
I also changed the service-worker cache version so the browser would not continue serving the broken script after the upgrade.
A syntax check could confirm that the JavaScript was valid.
It could not prove that the browser was allowed to run it under the application’s real security headers.
That required using the feature.
Making the Feature Belong to the Product
Once the behavior worked, screenshots exposed visual problems.
The Local Places panel used a brown surface and tan border that did not match the existing Schedule panel. It looked like a feature imported from another application.
I changed the panel to use the same dark surface, slate border, spacing, field treatment, and corner radius as the rest of the composer. Orange remained limited to active controls and actions.
The default Midnight Ledger theme received the new styles first. My live site uses a custom Microblog Stream theme, so I updated that theme separately.
That was another reminder that core markup is not enough when Bonumark supports independent themes.
A feature is not complete until the active theme knows how to present it.
The saved-place directory needed another pass, too.
The first version looked like a raw database table. A single record stretched across the page. Actions were cramped together, and the privacy explanation carried too much visual weight.
I replaced the table with a card-based directory.
The place name and location received priority. The screen gained a saved-place count, a quieter coordinate notice, cleaner metadata, and separation between normal actions and Delete.
Testing exposed one more visual issue. Category appeared as a floating badge on the far side of the card and looked disconnected from the place it described.
I moved Category beside Default Public Display as ordinary labeled metadata.
The card then read in a logical order:
Place and location.
Supporting details.
Actions.
The feature was tested through PHP syntax, JavaScript syntax, JSON parsing, CSS checks, migration loading, release-manifest verification, ZIP integrity, and clean-extraction smoke tests.
Those checks found structural and packaging problems.
Real use found the product problems.
The composer was too crowded.
Geolocation was blocked by security policy.
The panel did not match the interface.
The custom theme lacked presentation support.
The admin directory looked unfinished.
The category badge broke the card hierarchy.
Both forms of testing mattered.
What the Week Proved
This week was not about adding information, tools, and controls and then assuming Cypher and Bonumark Stream had improved.
The work was in deciding how those capabilities should behave.
Garmin and Fitbit needed source authority, structured records, duplicate protection, and unit verification.
Archived ChatGPT conversations needed role boundaries so old assistant claims could not become personal facts.
Links needed to be opened before Cypher claimed to understand them.
Sources needed to remain visible without taking over the conversation.
Deterministic routing needed narrow intent boundaries.
Meaningful moments needed deeper context than recent logistics.
Model choice needed reasoning control before I could compare configurations honestly.
Local Places needed independence, privacy, security compatibility, usable composer behavior, and theme-level integration.
The failures were different, but the pattern was consistent.
The first working version was rarely the finished version.
An import could succeed while carrying a bad unit.
A correct inventory system could answer the wrong question.
A personalized response could contain accurate facts and still miss the meaning of the moment.
A web-search feature could find good information and still make the interface worse.
A model selector could look complete while controlling only half of the request.
A location feature could pass syntax checks while the browser refused to run it.
That is why I test these systems through actual use.
I do not only ask whether the code exists.
I ask whether the behavior is correct.
I inspect the records.
I challenge the totals.
I compare the sources.
I repeat the message that caused the failure.
I test the page after reload.
I use the live API.
I open the feature in the browser.
I look at the screen and decide whether it belongs.
That is directed AI-assisted development.
The work is not finished when the system produces an answer or the interface displays a control.
The work is finished when I can explain the requirement, reproduce the failure, verify the correction, and trust the behavior that remains.
New Here?
Read Next:
- Builder Receipt: Making Cypher More Reliable
- Cypher Is Starting to Keep Up
- Why I Built Bonumark Stream