Skip to main content
2026-09-10Dev LogAI AgentArchitecture

Tearing out a working agent: a month of replacing PureLab’s runtime

In July we shipped a first-generation assistant inside the product process. In August we kept a dual engine for comparison, then scrapped a design with thirty-plus hard decisions. For the next month we refused compatibility shims and history migration, and pulled chat out of the app process.

Tearing out a working agent: a month of replacing PureLab’s runtime

In the August note we wrote one sentence: the assistant would stay closed in that release.

That was not laziness. The first-generation agent could already talk, search tracks, and produce previews on the Organize page. What was stuck was the foundation. Stacking more features on it would weld orchestration, sessions, secrets, and music write-back into a single process. The next desktop product would have to do the same work again. We tore out something that already ran, and replaced it with a runtime that can be embedded, reviewed, and crashed in isolation.

That swap was much harder than writing a chat box from scratch.

🛠 The first generation already worked. That is why it hurt.

The July stack was not a prototype. It was a full assistant inside the PureLab process: multi-turn streaming, sessions on disk, cancel and a serial lock, a user-supplied model key, library search and tag previews. Write-back still went through the product’s own confirmation gate. Domain safety was the strength — the model could not edit files directly, paths were redacted, and subscription gates could not be skipped just because the request arrived through chat.

The problem was not “can it talk”. The problem was the shape.

Orchestration, the event channel, the session table, key unsealing, and tool calls all lived in the product process. The UI knew only that one event stream. Change library, switch the side panel, collide two generations — and both the frontend and the backend had to keep a state machine. Worse: the next app that wanted an assistant would copy an entire host. That is not embedding. That is every product raising its own agent.

We did try the gentler path: keep the old one, and bolt on a new one.

🚧 The harder detour: a dual engine for comparison that took four thousand lines, then we voided the lot

In early August there was a design that looked safer. The activity bar would show “Classic AI” and “Smart assistant” at once. We initially kept the old kernel so we’d have a baseline to “compare effects” against as we connected the new foundation. We hoped that while the new isolated process slowly caught up on Windows, packaging, and the secret channel, we could use the old engine to verify the model’s output quality.

On paper, that is smooth transition and effect comparison. Once it was written down, it was a disaster.

To maintain this “comparison”, the two chat command sets could not mix. The two session ledgers could not be reconciled. One key had to feed both in-process orchestration and the isolated process — and still never land in environment variables or config files. Preview, confirm, write-back, and rollback had to stay on the product’s security chain, while the new kernel must not own arbitrary file rights. Windows had the isolated process fail-closed. The entry still had to be visible, the copy still had to be human, and the panel could be neither blank nor a crash.

That dual-entry design, originally meant just for “comparison”, piled up more than thirty hard decisions. The body ran past four thousand lines. Before we implemented it, we already knew: this was not a comparison. It was raising two assistants, two ledgers, and two failure domains. Any “just keep the old transcripts” would lock both sides together forever.

On 13 August we voided the whole document.

The new contract was short, and every sentence was expensive: the Kit is all new code, new commands, a new event channel. No compatibility layer for the old path. No reconciliation. No history migration. No shared tables. The old agent’s “comparison mission” ended right there. Deletion removes the product entry, not the new contract.

Users will not see “bring your old chats across”. That is not an omission. It is the point. Migrating old sessions into the new runtime looks kind. In practice it welds two lifecycles together. We could not afford the interest.

⛰️ Several mountains had to move at once

Voiding the design only gave permission. The real labor was splitting “chat in the product” into three layers, then wiring them into PureLab within a month.

  • 📦 The first mountain is process: The model loop must not share a crash domain with Organize, write-back, and analysis. Chat runs in an isolated process. The product starts it, supervises it, and reaps it on failure. If the helper dies, the library and the tag pipeline should still be there. In the other direction, the product must not hand the model a general terminal, an arbitrary filesystem, or outside tools. Write permission on the music library does not belong to the assistant.
  • 🔌 The second mountain is protocol: The UI must not speak the low-level session protocol. The product shell must not hand-roll “send prompt, listen for events, stitch history”. There has to be a stable product protocol: send, stop, list sessions, resume, configure the channel. Transport can be desktop commands. The protocol itself does not belong to one app. The next product hangs a panel, supplies the current library identity, and registers domain capabilities. It does not implement another host.
  • 🔑 The third mountain is the key: The user’s model key stays sealed in the product. It must not appear on the isolated process’s disk or in its environment. Requests come back over loopback. The product unseals for a short window, forwards to the endpoint the user typed, and drops the secret. Copying the database file may still recover ciphertext. That risk is known. We did not pretend a system keychain already solved it.
  • 🛠 The fourth mountain is domain tools: The assistant can search the currently authorized library, read tracks, preview renames and tag patches, and create drafts. It cannot start write-back, start rename, start a pipeline, or run analysis. It previews one to five tracks first. If the sample is right, it may create at most fifty drafts. The workbench opens the write-back dialog. Files move only when you click Apply. The model must not invent paths, must not ask you for internal library ids, and must not claim the files are already changed.
  • 🚀 The fifth mountain is shipping across platforms: Same contract. macOS needs sandbox signing. Windows had the isolated process unavailable for a stretch, and could only say “not available on this system”. Only in September did Windows line up the private home directory, the capability flag, and debug versus release layout. For a desktop agent, half the work is not conversation. It is “it installs, it starts, and it cannot take the main app down”.

These five had to move together. Chat first, safety later — or safety first, Windows later — would have dragged the old entry back during the comparison window. We also deleted the right to drag it back.

⚙️ Where the month actually went

From mid-August to 10 September the calendar said “integrate”. The days were a string of boundaries that refused to stay quiet:

  • Channel and state machine tangles: Settings needed an AI assistant entry a person can find. With no configuration, the panel must not paint itself ready. The conversation pane defaults to an English empty state; the product injects a “Go to Settings” action. Internal error codes must not land on the UI. Changing the endpoint once required submitting the key again. Later we allowed the human thing: the key is already there, you only changed the URL or the model. That is not copy. That is the channel state machine tied to unsealing.
  • Sessions are harder than a chat box: A brand-new thread must not be overwritten by “resume the latest non-empty”. Switch library, and the session list must switch with it. Close the side panel and open it again in the same run, and the transcript should restore, not replay like a typewriter. Quit the app and reopen, and only then take the cold path. We did lose history when switching away and back. We later cached projected events per session. Deleting a session must close the runtime session, remove the on-disk directory, and stay disabled while a turn is in flight.
  • The slow grind of write-back: The old agent’s confirmation gate was correct. The new runtime must not go soft because the transport changed. Chat does not ask “allow this tool” — approved queries are released by the host. In early September we folded “fill parameters → preview a sample → create drafts → you click Apply” into a product event. File confirmation stays in the write-back dialog, not a chat popup. Rename has no patch: it produces a path mapping, you check it in the operations area, then you apply. Any sentence that says the assistant already changed files is a bug under this contract.
  • Panel independence vs. embedding costs: The panel has to feel like the workbench, not a demo. Markdown is headings and lists only — no syntax highlighting. Streaming, failure, and cancel share that renderer. Strings inside the panel are hardcoded English; the product can override only the settings CTA. That is one cost of embedding across three repos. It sits on the right of Organize, and only recently dropped to the bottom of the activity bar so it would stop competing with Extract, Replace, and Pipeline for the same line of sight.

None of this can ship alone. A protocol change moves the host, the panel, and the product adapter together. Miss one side, and you get “sometimes it talks, sometimes it drops history, sometimes the key goes to the wrong place”.

👀 What you will see in the workbench now

The product is at 6.0. On the Organize page, the AI entry sits at the bottom of the far-right activity bar. Open it and you get a conversation workbench: multiple sessions, resume, stop. Against the August 5.0 note, the entry is now always there. This article does not claim a store release.

  • Flexible configuration: First fill in your own endpoint, model, and key under Settings. We do not weld chat to one cloud vendor, and we do not silently fall back to a built-in model when nothing is configured.
  • Restrained domain capabilities: Once that is set, you can talk to the library you currently have open. Preview a rename by artist and album. Inspect a small tag draft. The assistant can search, preview, and produce a plan. Whether files change is still your click in the write-back dialog or the rename mapping. The tag pipeline from August is still there. The assistant will not press Start for you.
  • Firm boundaries: Analysis, artwork, lyrics, and fetching metadata from the internet stay out of reach this round. Not because we forgot. The boundary has to be hard before the assistant gets to do more.

💡 The cost we accepted, and the bill still open

The expensive part of replacing the runtime was not writing new code. It was admitting that generation one already worked, that the dual engine we kept for comparison looked safer, and voiding the whole design anyway. Skipping history migration is unfair to old chats. Migrating them is unfair to the new contract. We chose the latter, and we deleted the old entry so two assistants would not live on as a permanent comparison.

The unpaid bill should be named. Switching the channel to our own relay, moving secrets from database ciphertext into a system keychain, and making Windows packaging as boring as macOS — those are still ahead. The assistant is a workbench pilot. It is not the sentence “smart assistant is generally available”.

In August we turned tag cleanup into a pipeline, because repeating the same clicks should not mean flipping the sidebar. This month we pulled the assistant out of the product process, because chat, secrets, and write-back should not share a crash domain. Both lines are the same job: let a local library workstation automate, and still stop before a file is changed.

The runtime has to land before it can actually help you tidy the library.