Skip to main content
All insights

Build log: CopernicIQ on the Spark

aioslocal-aiagents

Part of Build Blog

We updated the Spark runner to have an always-on Perplexity decision agent we are calling CopernicIQ.

That was the work of October 23 to 26, 2026. Three other changes landed alongside it: the AI Control Panel, the agent harness and my voice skill. Each one is a part below, with what changed, the hard question it raised, and the questions I had to answer before it shipped. My answers are as I gave them, cut only where they named something private.

1. CopernicIQ: an always-on decision agent on the Spark

What changed

CopernicIQ runs Perplexity's open-weight decision model and embedding model locally on the DGX Spark. Give it a claim and the text the claim is supposed to rest on, and it answers grounded, contradicted or not in the sources, with probabilities and the passages it used.

It also answers questions in a format compatible with TypeSafe's System One decision API, so tools built for that API can use it by changing one address.

The small pair stays loaded all the time: a 5B decider and a 0.6B embedder, about 15 GB together. The 27B decider loads only when asked for, and only if there is room.

The decider cannot run under Ollama or vLLM. It needs Perplexity's own package, so CopernicIQ runs in its own locked-down container and is reachable only from the machine itself. A 3-claim check takes a few seconds.

The hard question

Memory. The Spark has one pool of memory shared by every model server. On October 16 an Ollama load of a 51.7 GB model next to vLLM ran it out of memory. The box thrashed for 30 minutes and the kernel killed both Ollama and vLLM. So what happens when CopernicIQ needs room that another model already holds?

What we decided, and why

The newcomer yields. CopernicIQ loads a model only when free memory covers what it needs plus a 12 GiB margin. Otherwise the caller gets "no room". It never stops vLLM or Ollama, and it never quietly answers with the small model when the 27B was asked for. The 27B takes 4.5 to 6 minutes to load, so a request for it starts the load and gets "try again in N seconds" instead of waiting.

On October 26 the same rule went onto Ollama. A small guard watches for new Ollama model loads and stops one that will not fit. It never touches a model or service that is already running.

Before building any of it, a hand-run test put ten claims with known answers through three models, then twenty harder ones: the 5B decider, the 27B decider and the Qwen model already on the box with a prompt. The 5B got 10 of 10 and 18 of 20. The 27B got 10 and 17, and Qwen got 10 and 17. The stop rule fired, because Qwen did as well as the 5B. I saw the table and kept the plan, with the 27B on demand.

Questions I had to answer

What does the validator tell the harness about a claim, and what does it not tell it? Why is a hand-run test the first thing built?

The validator tells the harness whether a claim is supported by the supplied text, contradicted by it, or not found in it. It returns probabilities and the passages it used. It does not establish whether the claim is true. A wrong source can support a wrong claim, so the harness must say 'grounded in sources.' I start with a hand-run spike because we have not measured whether these models are useful for this job or whether the required runtime works on the Spark. Building the service first could leave us maintaining something that offers no benefit.

Another model holds most of the memory and a request for the 27B arrives. What happens?

Before loading the full tier, the service checks available memory against what the 27B model needs, plus a 12 GiB margin. [...] there is not enough room for the roughly 52 GB full model. It refuses the load and returns 'no room' [...] to the harness. It does not stop vLLM or Ollama, and it does not silently substitute the light tier. The guard only controls loads made by the validator. It cannot stop Ollama from loading another 40 GB model after the validator is already running. That could repeat the 10/16 memory problem from the other side.

The Ollama guard of October 26 closes that gap, with the same rule and the same 12 GiB margin.

2. The AI Control Panel updates itself and reads CopernicIQ

What changed

One command, the harness update I already run, now redeploys the panel too, on every PC including the laptop I travel with. It redeploys only when the panel's main branch moved and its checkout is clean, and it restarts the panel only if it was open.

It builds the new version into a staging folder before stopping anything, so a failed build leaves the running panel alone. The old build is kept until the next run.

The Perplexity search count on the Usage page was fixed. It showed 8 searches for a day when 2 were billed, because it counted every search command as written: a help call, three runs with no query and two refused keys. Now a call without a query or with a reported error is not counted, and the page says "at most".

The panel now reads CopernicIQ's call records: which endpoint, the outcome, the verdicts and the time per call. It never keeps the text of a claim, a source or a question. The figures are computed now. How they are drawn comes with the next UI pass.

The hard question

When does an automatic redeploy count as approved? And how honest can a usage count be when the panel never reads a tool's output?

What we decided, and why

Running the update is the approval to deploy the reviewed code on main. Any other deploy still needs my word, and the change that created this path waited for my word to merge. The search count is an upper bound and says so.

Questions I had to answer

From running the update to a current panel, with the panel open: why does the build come before any stop?

The build happens before stopping anything so a failed build leaves the open panel running. After a successful build, it [...] stops only the published panel by its exe path, swaps the folders, and restarts it because it was open before the update. [...] On a feature branch, Update-Panel exits 2 and touches nothing. It does not switch branches, stash or pull.

Which way can the search count be wrong, and why is that acceptable?

The remaining error is over-counting. The page says "at most," and the dollar figure is a list-price reference kept out of costs and totals. An upper bound is honest there. Presenting it as actual cost would overstate spending.

What does a 40% "disagreed" share from CopernicIQ mean?

A 40% disagreed share says 40% of the recorded validation verdicts were contradicted or not_in_sources. It shows where CopernicIQ pushed back. It does not prove those verdicts were correct, that anyone acted on them, or that they improved a result. not_in_sources could also mean the supplied sources were incomplete. I would need evidence of a changed action and a better outcome to call that value.

3. Pruning the agents, and one harness for every platform

What changed

Six specialist agents were added, and each passed a real run. Three generic agents were archived, and the deploy now removes archived agents from the machine. Every specialty agent was kept. The older ones are being sorted into skill, agent or both.

The harness cleanup started. It is meant to make the rules, skills and agents built over two years work on Claude Code, Codex, Grok and Antigravity, and in a lean form on the Spark, without losing anything.

First came safety. An old setup script that held secrets in plain text was retired, and every value it held now loads from Azure Key Vault. Once, a test command printed a key into the session. No value is shown here, but it happened, and it is the honest part of this story. Since I started building with AI I have had to rotate or cycle keys left and right, because an agent that can run commands can also print what they return. How the Key Vault is being used to curb that gets its own Build Blog entry.

Then the rules. There is now one shared core that every platform reads. Claude-only parts sit in their own layer, and long reference sections moved into files the sessions open when that work comes up. One deploy writes each platform's copy, and a drift check fails when any copy differs. Claude, Codex and Grok passed real sessions that read a core rule.

The hard question

Why not switch Grok's Claude compatibility back on? And what is given up by building a shared core?

What we decided, and why

Grok gets the shared core and a chosen list of skills, with compatibility left off. The core stays under 48 KB because Codex cuts off its rules file past 64 KB, and a check proves every rule sentence of the old file survived the move.

Questions I had to answer

Why not turn Grok compatibility back on, and what is the strongest case against the shared core?

Turning compatibility back on would restore the setup that hung Grok on 10-04: 173 skills, 13 MCP servers, 12 hooks and the full global rules. That would bring back the startup problem. [...] The strongest argument against a shared core is that we are adding a build and deployment system to maintain. Different platforms interpret instructions and expose tools differently. Matching files does not prove matching behavior. [...] Keeping Claude as the only source would be simpler for Claude. The cost is that the other platforms keep depending on compatibility or hand copies, which already left Codex stale and Grok unable to use the harness.

Why 48 KB, and how do you prove nothing was dropped?

Crossing 64 KB risks Codex silently cutting off the end, so rules can disappear from the session even though they remain in the file. [...] Moving text is allowed. Losing it is not. That proves the text survived, not that every platform follows it.

4. My voice skill: the ask comes first

What changed

The voice skill that writes messages under my name now puts the ask first. The greeting and a short intro come first where the form has one, then the ask, then the explanation. A review comment opens directly on the ask.

It is backed by BLUF (bottom line up front), Minto's Pyramid Principle and the US federal plain-language guidelines.

Writing with no ask, such as time logs and status notes, keeps its shape. Two checks now warn when a message ends on its only question. They warn. They never block.

The hard question

The skill was built by measuring how I actually write, and I end on the ask. How do you change the rule without erasing the evidence?

What we decided, and why

Keep both. The measured habit stays recorded as evidence, and ask-first is marked as my own choice, so a later re-measure does not "correct" it back.

Questions I had to answer

Why does the skill keep the measured habit, and what goes wrong if it is deleted or the new rule is left unmarked?

The measurement records how I actually wrote. The new order records how I want to write now. I keep both and mark ask-first as my explicit choice. Deleting the measurement would erase valid evidence. Leaving the new rule unmarked would make it look like a claim about my existing habits, so a later re-measure could 'correct' it back to ending on the ask.

Why are the new checks warnings and never failures?

The checks use question placement as a clue, but an ask can be a statement like 'Please add this check.' Making that a failure would block correctly written comments. Writing with no ask should not be forced to invent one either. If a check fires on my [...] 'after' example, we cut that check. It is an accepted ask-first example, so warning on it would teach people to ignore the warnings.

The next step

The next step is the Key Vault entry: how keys are being kept out of AI sessions, the leaks that wrote the rules, and what is still not solved. After that, CopernicIQ gets drawn in the AI Control Panel, and the next harness unit takes on settings, skills per platform and a secret scanner. The laptop picks up the new rules and the panel the next time the harness update runs there.

Have a Project in Mind?

Every project starts with understanding the problem. Tell us yours.