by Intelliverse X

Operator Reads — the industry-insights agent

Operator Reads is a curation agent for operators: a shortlist of the most recent articles, blog posts, and news that actually matter to your operation — ranked by relevance to the product verticals your machines stock and the places your machines sit, newest first. It ships in the Operator X app as its own tab, in the AI assistant as the get_operator_reads tool, and over the API under /api/v1/operator-reads/*.

It is wired the same way as the Location Scout agent: a per-user-id subscription record activates the agent, and an n8n workflow powered by Firecrawl does the web discovery.

sequenceDiagram participant App as Operator X app participant API as api.kiosk-x.ai participant N8N as n8n "Operator Reads" workflow participant FC as Firecrawl App->>API: POST /operator-reads/subscription (activate) Note over N8N: schedule (every 6h) or webhook N8N->>API: GET /operator-reads/subscribers (admin key) API-->>N8N: active subscribers + fleet-derived profiles N8N->>FC: /v1/search per keyword/city query FC-->>N8N: recent articles (title, url, date, snippet) N8N->>N8N: tag relevance evidence, dedupe, keep top N N8N->>API: POST /operator-reads/ingest (admin key) App->>API: GET /operator-reads/feed API-->>App: ranked shortlist w/ relevance reasons App->>API: POST /operator-reads/refresh (pull-to-refresh) API->>N8N: webhook operator-reads-refresh {userId, profile}

Activation is a subscription (per user-id)

The agent is off by default. Feed, item actions, and refresh all return 403 until the operator activates:

curl -X POST https://api.kiosk-x.ai/api/v1/operator-reads/subscription \
  -H "X-API-Key: $KEY"
# → {"active": true, "operatorEmail": "...", "activatedAt": "..."}

POST /subscription/cancel turns the agent off; the shortlist (and its read/saved state) is kept so re-activating later doesn't start from a blank feed. GET /subscription reports the activation state plus unread/saved counts for badges.

Subscription records live in the operator_reads_subs store, FLEET-tier persisted (app/persistence.py) — replica-shared over Postgres LISTEN/NOTIFY and rehydrated on boot, so an activation on pod A gates the feed served by pod B and survives the ~20 redeploys/day. Both Operator Reads stores are additionally reset-proof (_RESET_PROOF_DICT_STORES): the demo environment's routine /sandbox/reset re-seeds machines and orders but leaves agent activations and reading state alone — the same reasoning that puts Location Scout's subscriptions in the money tier.

Convergence note: Location Scout keeps its paid entitlements in its own scout_subscriptions (money-tier) store. Operator Reads activation is a free feature toggle, so it lives in its own fleet-tier store rather than a shared entitlements table; if per-agent entitlements ever become paid SKUs, fold both into one store.

The operator profile is derived, never declared

Relevance is computed from what the fleet actually is (GET /operator-reads/profile):

How ranking works

Each ingested item carries relevance evidence — a list of {kind, reason} pairs the workflow attaches, e.g. {"kind": "vertical", "reason": "matches your 'vape' vertical"} or {"kind": "location", "reason": "near your Airport T2 machine"}. Those reasons are shown verbatim as chips in the app, so ranking stays explainable.

Dedupe across refreshes hashes the normalized URL (scheme/www/trailing slash/fragment-insensitive) per operator. Re-ingesting a known URL refreshes its metadata but never clobbers the operator's read/saved/dismissed state. The feed is capped (KIOSKX_OPERATOR_READS_MAX_ITEMS, default 200) by pruning the oldest unsaved items; saved items are never pruned.

The n8n workflow + Firecrawl

Discovery runs in the "Operator Reads" n8n workflow (integrations/n8n/operator-reads.json, importable):

  1. Triggers — a 6-hour schedule (all active subscribers) and a webhook POST /webhook/operator-reads-refresh (one operator, fired by the backend's /refresh endpoint on pull-to-refresh; cooldown-limited because every run spends Firecrawl credits).
  2. SubscribersGET /api/v1/operator-reads/subscribers with the fleet-admin key returns each active subscriber with their derived profile.
  3. Firecrawl search — for each profile it builds queries from the keyword pack + city-scoped queries and calls Firecrawl https://api.firecrawl.dev/v1/search (the API key lives in n8n as an HTTP-header-auth credential named "Firecrawl API (header auth)" — Authorization: Bearer fc-…; the backend holds no Firecrawl key).
  4. Score & tag — a Code node tags each hit with its relevance evidence (which vertical/city/industry query found it), extracts the published date, dedupes by URL, and keeps the top ~30 per operator.
  5. CallbackPOST /api/v1/operator-reads/ingest with the admin key: {operatorEmail, runId, items: [{url, title, source, publishedAt, summary, relevance[]}]}. Non-admin credentials can only ingest into their own feed, and ingest into a non-activated operator is refused.

If n8n is unreachable, /refresh answers honestly ({"queued": false, "note": "...workflow is unreachable..."}) and the scheduled run picks the operator up on its next pass.

API surface

Endpoint What
GET /api/v1/operator-reads/subscription Activation state + unread/saved counts
POST /api/v1/operator-reads/subscription Activate the agent for this user-id
POST /api/v1/operator-reads/subscription/cancel Deactivate (shortlist kept)
GET /api/v1/operator-reads/profile The fleet-derived curation profile
GET /api/v1/operator-reads/feed?view=inbox\|saved\|dismissed\|all Ranked shortlist, paginated
POST /api/v1/operator-reads/feed/{itemId}/read\|save\|dismiss\|restore Item state
POST /api/v1/operator-reads/refresh Trigger a curation run now (cooldown-limited)
GET /api/v1/operator-reads/subscribers Admin: active subscribers + profiles (workflow input)
POST /api/v1/operator-reads/ingest Authenticated curation callback (workflow output)

Admin credentials pass ?operatorEmail= (or the body field) to act on a specific operator; operator credentials always act on themselves.

Configuration

Env var Default What
KIOSKX_OPERATOR_READS_N8N_WEBHOOK https://n8n.intelli-verse-x.ai/webhook/operator-reads-refresh Manual-refresh trigger; empty disables
KIOSKX_OPERATOR_READS_COOLDOWN 60 Seconds between manual refreshes per operator
KIOSKX_OPERATOR_READS_MAX_ITEMS 200 Per-operator feed retention cap
KIOSKX_FIRECRAWL_API_KEY (empty) Optional direct-Firecrawl fallback (n8n normally owns discovery)

Tests: tests/test_operator_reads.py — subscription gating, lifecycle, profile derivation, ranking/recency, dedupe, callback auth, refresh cooldown, assistant tool, and fleet-sync peer-apply/restart survival.