<p>Here is a sentence that has been on the Sjá page since monitors shipped:</p><blockquote><p>Re-scan URLs on a schedule — hourly, 6-hourly, daily, weekly.</p></blockquote><p>And here is what it delivered: daily, if the window happened to be open.</p><p>Those are not the same sentence, and the gap between them is the worst shape a bug can take, because a monitor that didn't run looks exactly like a monitor with nothing to report. Green bell, no alerts, everything fine. You only find out when you go looking for a history and discover the history has holes shaped like the days you had the app closed.</p><p>0.7.0 is the release that closes that gap — and then does the same thing again for the citation probe, which had been a button you had to remember to press.</p><h2>Why a background runner, and why it's off</h2><p>The honest way to make "daily" true is for something to run when the app isn't running. On macOS that means a <code>launchd</code> agent, and <code>launchd</code> agents are a thing you should be asked about rather than given.</p><p>So it's off. Turn it on in <strong>Monitors</strong> or in <strong>Settings</strong>, and Sjá installs an agent for your user that runs a new subcommand every fifteen minutes:</p><pre><code class="language-bash">elyra-sja tick
</code></pre><p>It opens the database, runs every monitor that's due, notifies you about regressions and errors, records that it ran, and exits. Turn the setting off and the agent is removed. Its output goes to:</p><pre><code class="language-text">~/Library/Logs/Elyra Sja/tick.log
</code></pre><p>That's the whole feature, and it would be a two-line changelog entry if the interesting part weren't underneath it.</p><h2>The four things a background runner has to get right</h2><p>A second process touching the same database as your open app is exactly the kind of thing that works for a month and then eats a week. Four guarantees, each of which is a decision:</p><ol><li><p><strong>A monitor runs exactly once, even with the app open.</strong> Both the app and the agent ask "what's due?" and both can get the same answer at the same moment. So claiming is a compare-and-set: <code>monitors::claim</code> writes <code>last_run_at</code> only if the row still holds the value it just read. The loser of the race gets nothing back and moves on. No locks, no leader election, no "please don't open the app while the agent runs".</p></li><li><p><code>tick</code><strong> never creates or migrates the database.</strong> The app is the only migrator. If <code>tick</code> finds a pending migration it logs <code>open the app once</code> and skips — it does not guess, and it does not helpfully run a migration you haven't seen. A background process that upgrades your schema unattended is a background process that can lose your data unattended.</p></li><li><p><strong>It prints to </strong><code>stderr</code><strong> only</strong>, which is what <code>launchd</code> routes to that log file. Nothing invents its own logging path.</p></li><li><p><strong>The app re-registers the agent when the binary has moved.</strong> An app update relocates the executable; an agent pointing at the old path silently does nothing, forever, while the setting still says on. So on every launch, the app checks and rewrites. An update must not be able to quietly break a schedule.</p></li></ol><p>And the test for all this drives the real binary's <code>tick</code> against an empty <code>HOME</code>, then asserts it exits 0 and created nothing. Not a mock. The thing that will run on your machine at 03:15.</p><pre><code class="language-text">$ tail -f ~/Library/Logs/Elyra\ Sja/tick.log
[09:15:02] 3 monitors due
[09:15:04] elyracode.com/eterm — score 85 → 85, no change
[09:15:06] elyracode.com/sql — score 82 → 74, alert raised
[09:15:06] done in 4.1s
</code></pre><h2>The second promise: a probe you had to remember</h2><p>0.6.0 added the citation probe: type the question a user would ask, and Sjá puts it to the answer engines with your own keys and tells you whether you're cited, at what position, and who was cited instead.</p><p>The card that announced it said something slightly awkward in the last line: what to read is the trend and the competitor list, not one answer. Which is true — a single probe is one sample of a non-deterministic system — but it quietly asked you to build that trend by hand, by remembering to press a button on a Tuesday.</p><p>Now you don't. <strong>Schedule</strong> repeats the question daily or weekly. Both the open app and the background runner pick up due schedules, a few per pass, because unlike a monitor a probe costs real money. And every scheduled run compares each engine's verdict with its previous one:</p><pre><code class="language-text">Citation alert · "fastest way to run Laravel without PHP-FPM"

  ⚠ citation_lost — Perplexity
    Cited on 8 Sep, not cited on 12 Sep.
    Cited instead: frankenphp.dev, roadrunner.dev, laravel.com

  ✓ citation_gained — Gemini
    Not cited on 8 Sep, cited on 12 Sep at position 2.
</code></pre><p>That's the shape of the thing you actually wanted: not "are we cited today", but the day it changed, and who replaced us.</p><h2>A fourth engine, and the one everybody actually uses</h2><p>Gemini joins ChatGPT, Claude and Perplexity — through the Gemini API's <code>Interactions</code> surface with the <code>google_search</code> tool, which per Google's own grounding documentation is the closest API path to what AI Overviews and AI Mode do.</p><p>We added it because a competitor review found the obvious hole: Google's answer surface is the one most of your traffic will meet, and we weren't asking it. The key, the model (<code>gemini-3.8-flash</code>) and the endpoint are settings, because these surfaces move; the parser reads both the <code>Interactions</code> shape and the classic <code>generateContent</code> grounding shape, in case your endpoint points at the older one.</p><p>Gemini needs its own key under <strong>Citation probe</strong> in <strong>Settings</strong>. Like the others, it bills a few cents per probe to your account, not ours.</p><h2>"They mentioned us but didn't link"</h2><p>This one came from using the thing.</p><p>An answer engine can talk about you by name and never link to you. "For PHP in-process, Askr is one option…" — no URL, no citation. Under the old model that counted as not cited, which is wrong in a way that matters: being talked about and being ignored are not the same result, and treating them alike hides the middle of your funnel.</p><p>So each probe now records whether the answer used your project name, the <code>Organization</code> your page declares, or the domain's own label — whole words, any case, any script:</p><pre><code class="language-text">ChatGPT      cited              position 2 of 5
Claude       mentioned, no link
Perplexity   not cited          cited instead: frankenphp.dev, roadrunner.dev
Gemini       cited              position 1 of 3
</code></pre><p>Shown in cyan in <strong>Trends</strong>, and — the part we were careful about — kept out of both counts. A mention is not a citation, and it is not nothing. Folding it into either number would have made a metric that quietly lies in one direction or the other.</p><h2>What it costs, said where you decide</h2><p>We'd rather be blunt about this than have you discover it on a card statement:</p><blockquote><p>A daily probe on four engines is four API calls a day, every day, until you pause or delete it under <strong>Monitors</strong>.</p></blockquote><p>A weekly schedule on one engine is a rounding error. A daily schedule on four engines across a dozen pages is a real line item. The interface says so at the point where you press <strong>Schedule</strong>, and everything is listed under <strong>Monitors</strong> where you can pause it.</p><h2>Upgrading</h2><p>It installs itself if you're on 0.4.2 or later. Two migrations run on first launch (<code>probe_schedules</code>, and <code>mentioned</code>/<code>mentions</code> on <code>citations</code>). Then:</p><ol><li><p>Turn on <strong>Run in the background</strong> if you want your monitors to mean what they say.</p></li><li><p>Add a Gemini key if you want the fourth engine.</p></li><li><p>Schedule the one question you'd most like to be the answer to — weekly is plenty — and leave it alone for a month.</p></li></ol><p>Then, one Tuesday, you'll get a notification telling you that Perplexity stopped citing you and started citing somebody else. That notification is the whole product.</p><p>Close the window. It keeps working now.</p>