Engineering Platform › Shared module · See it on the architecture map

AI Worker Boundary (Spark Worker Kit)

When Runway, GetSeen, or Tourney hand off AI work to a separate helper process - a "worker," running on FOMX.ai's dedicated GPU machine, the "FOMX.ai Spark" - each of them needs the same question answered: is this response actually valid, or is it garbage, oversized, stalled, or trying to hand back a final answer it was never allowed to produce? This package is the shared code that answers that question the same way, every time, for all three apps - instead of three separately hand-written copies of the same rules quietly drifting apart. It was originally written once for Tourney, then copied by hand into Runway - and that copy started drifting out of sync with the original, which is the exact failure this package now exists to prevent.

Why it matters
A worker on separate hardware is an untrusted party, even when I own the hardware. If three applications each validate its output their own way, the rules drift and the weakest copy is the real security posture.
What I designed
One shared validator and one direction of trust: the worker checks in with each app and can only report progress; no app ever reaches into the worker, and the worker can never hand back a final answer on the app's behalf.
Used by
Tourney, Runway and GetSeen.
3 real callers
Tourney, Runway, and GetSeen all lean on this same package - Tourney's migration is deliberately partial, under a signed-off spec, not an oversight
Never trusted blind
the worker can report "I'm still working," but it can never hand back a final result on the app's behalf
Bad input is refused, not patched
a broken worker ID stops the request outright instead of being quietly trimmed to fit
One-way only
the worker checks in with each app; no app ever reaches into the worker

Why one shared package instead of three separate copies

This exists because of a real mistake that already happened once: Runway needed the exact same worker rules Tourney had already built, so instead of reusing Tourney's real code, that logic got rewritten by hand from a written description of it. Two hand-written copies of the same rule don't stay identical for long - the moment one gets a bug fix, the other quietly falls behind and nobody notices until something breaks. The fix is the boring, obvious one: one real package that every app imports, so a fix made once is a fix made everywhere.

The rules themselves come from a written contract Tourney had already specified in detail (specs/SPARK_QUEUE_V2_CONTRACT.md). That document is still the official description of the rules; this package is simply that description turned into real, running code, instead of a second, separate description that could drift from the first.

The request, step by step

Every app that uses this follows the same five steps to get an AI task done - none of them ever reach the FOMX.ai Spark directly:

  1. The app adds a new task to its own queue.
  2. The worker checks in on its own schedule and claims that task.
  3. The worker calls an AI model - either a free one running locally on the FOMX.ai Spark, or a paid one reached over the internet.
  4. The worker sends the result back to the app.
  5. The app saves that result and marks the task done.

The queue is what makes this safe to run this way at all: the FOMX.ai Spark itself never has to accept an incoming connection from anything. It only ever reaches out, on its own schedule, to ask an app "anything for me?" - the same way a phone checks in with an app's servers for updates, rather than the servers reaching into the phone. Nothing outside the FOMX.ai Spark can ever initiate a connection into it, because there's nothing there listening for one.

The app’s own box App Job queue a row in the app’s own database FOMX.ai Spark - private hardware worker AI model runs locally, never leaves this box 1 5 2 3 4 solid = direct call dashed = the worker polling, never the app calling in
Every dashed arrow starts from the worker, never the app - the FOMX.ai Spark accepts no incoming connections, so the only way anything reaches it is by asking, on its own schedule. (This is Tourney's real request flow, shown in detail on Tourney's own page; Runway and GetSeen follow the identical pattern through this same shared package.)

What the worker is - and isn't - allowed to say

Picture the worker as a contractor who calls the office to report on a job, but is never handed the keys. It's allowed to send back exactly four kinds of update: "here's a piece of the answer," "still working," "something went wrong," or "I need to use this tool." What it can never do is post a message pretending to be the tool's official result, or pretending to be the app itself talking - those two message types are reserved for the app alone, and if a worker tries to send one, it's thrown out and never recorded. That distinction is what keeps the permanent record of "what happened" trustworthy: it reflects what the app itself actually did, never just what a worker claims happened.

That protects the shared worker once a job's already in the queue. GetSeen adds a complementary safeguard one step earlier, on the app's own side: a cap on how many jobs a single bulk action - tailoring resumes for dozens of postings at once, say - is allowed to add to the queue at all, so one user's bulk request can't flood it and delay everyone else's jobs before the worker ever gets involved.

Where it actually runs

Runway and GetSeen use the whole package - the loop that checks for new jobs, the logic that claims one and hands it off, and the two ways it can actually talk to an AI model (a local model running on the FOMX.ai Spark, or Claude itself, called through a temporary background process with its own time limit). Tourney reuses the rate limiter, the local-model connection, and its own contract validation from this package - but its polling loop, job claiming, and dispatch logic deliberately stay local, under a signed-off internal spec, not because anyone forgot to finish the job.

The reason is a real, specific one: Tourney's existing test suite checks the AI worker's behavior by substituting fake versions of exact internal function names while a test runs. Moving that logic onto the shared package's own objects would mean those tests silently stop actually checking anything - they'd still pass, but only because they'd no longer be testing the real code path, which is worse than not testing it at all. Finishing the migration the right way means rewriting that test coverage deliberately, file by file, under its own reviewed and signed-off change - not a quick edit made in passing.

Every worker reaches out to its own app, never the other way around - similar to how your phone checks in with an app's servers for updates, rather than the servers reaching into your phone. The exact method differs per app (a client certificate for Tourney, a secret token for Runway and GetSeen, an extra secure tunnel for GetSeen), but the direction is always the same: the worker calls out, and the FOMX.ai Spark itself is never the one being called.