Get started
Browse docs
On this page
  1. What QuoxSignal is
  2. Sources
  3. Stage gates and cost
  4. The CLI
  5. Triage lenses
  6. Proposals and HITL
  7. Autonomy and budgets
  8. The morning briefing
  9. The API
  10. Stable, beta and broken

QuoxSignal

QuoxSignal watches social accounts, feeds and local media, transcribes what was said, and turns it into typed proposals scored against the live Quox roadmap (QuoxPlan) and codebase (RepoBrain). The output is a draft you can act on, not a summary you skim. It proposes; it never publishes.

Part of QuoxCORE · Store tier B · $39 one time, licence key · docs compiled 2026-07-31 from source

What QuoxSignal is #

QuoxSignal (codenamed SCOUT internally; the agent you talk to inside it is also called SCOUT) follows sources, records what they posted, transcribes the audio, and runs an evidence-gated analysis that can end in a typed proposal: a stream, a substream, a skill, a plugin feature, an alert, or an FYI, each backed by verbatim quotes from the transcript.

Two ways to run it: the CLI (bin/signal.js, talking to the SQLite-backed server on :3108) and the dashboard surface at Home > Developers > QuoxSignal. Both talk to the same server and the same data; neither is a demo of the other.

It proposes, it never publishesApproving a proposal records a decision, nothing more: no build step is wired to approval, by design. Handing an approved proposal to QuoxPlan is a second, separate, explicit act, and there is deliberately no autonomy level that reaches QuoxPlan on its own.

QuoxSignal is a tier B plugin on the store: $39, one time, licence key.

Sources #

A source is one of four kinds: account (a platform handle polled periodically), feed (an RSS, podcast or YouTube-channel feed URL), file (a local media file, no follow needed, just signal ingest <path>), or json (a JSON API followed with a mapping file). Platform support is measured against live accounts, not read off a feature list:

SourceFollow an accountSingle URLCookies
TikTokyesyesno
Instagramno (broken upstream)yesno
YouTubeyes, via channel RSSyesno
RSS / podcastyesyesno
X / Twitteruntesteduntestedlikely needed
LinkedInnonon/a
Local filen/ayesno
JSON APIn/ayes (--kind json --map)no

Instagram account following does not workyt-dlp's instagram:user extractor is marked broken upstream. Following a single reel URL works fine; following an account never returns posts. Do not plan around Instagram account polling until yt-dlp fixes their extractor.

signal cookies shows which platforms need a session cookie jar and where to put it (~/.quox-signal/cookies/<platform>.txt, Netscape format, mode 700, never committed). Single video URLs mostly work without a cookie; account follows usually need one on any platform that gates its listing behind a login wall.

JSON sources

signal follow <url> --kind json --map <file.json> follows a JSON API instead of a feed. This exists because the authoritative security sources publish JSON, not RSS: shipped mappings cover CISA KEV (lib/sourcemaps/cisa-kev.json) and the NVD API (lib/sourcemaps/nvd.json).

A JSON item has no audio, so it lands at transcribed directly, with engine: "json:source" recorded honestly instead of naming an ASR engine that never ran. The NVD mapping ships a rolling 8-day query window because NVD's default sort is oldest first: a naive poller would ingest Sendmail bugs from the 1980s forever and report a healthy poll.

Failure modes worth knowing

  • TikTok answers with one stale post rather than an error. An account with little public content returns a single old video for a request of eight and looks healthy while never firing. Only the extractor's own error text is treated as evidence of a login wall, so poll cannot distinguish a gated account from a small but fine one.
  • TikTok advertises audio it does not ship. Its h265 variants report acodec: aac while containing no audio track at all. Every download is probed with ffprobe before it reaches the transcriber, because the near-miss version of this failure produces an empty transcript that reads like a boring post.

Stage gates and cost #

Every item moves through exactly one lifecycle, one step at a time, never backward, never skipping:

lifecycle
discovered -> transcribed -> analysed

discovered is metadata only, and free: signal poll lists what a source posted without downloading anything. transcribed is the first stage that costs real money (or real CPU minutes on the local model): audio downloaded, ASR run, transcript saved.

analysed means triage has run on a cheap model, and a lens has run on anything that survived. The server enforces the order with a 409 on any attempt to skip or reverse a step.

The gate exists so spend is always an explicit, visible act. poll never transcribes, transcribe never analyses, and there is no "just do everything" command, on purpose. signal transcribe always prints the estimated audio minutes and USD cost before doing any work.

Engine66-second reelCost
OpenAI whisper-1 (hosted)9 seconds$0.0066
local faster-whisper (small, CPU)5 min 50 sec$0

Local transcription runs roughly 5x slower than realtime on the reference dev box: useless interactively, fine for an unattended overnight batch (SIGNAL_ASR=local). SIGNAL_MAX_MINUTES (default 20) is checked before any download starts, so a long item is skipped rather than silently burning tokens; this caught two lectures totalling 342 minutes on the first live poll.

A vocabulary hint (SIGNAL_VOCAB) fixes most mangled proper nouns but not all of them: it cannot invent a phoneme the audio does not contain, so do not treat it as an accuracy guarantee.

The CLI #

Every verb below is taken from node bin/signal.js's own help output, nothing invented. Alias the binary as signal or invoke it directly.

VerbWhat it does
follow / unfollowFollow a handle, feed URL or JSON API; stop following
sourcesList sources with health and triage lens; a budget-paused source is flagged, never silent
tag / untagAdd or remove watchlist tags on a source
cookiesShow which platforms need a session and where the cookie jar goes
pollDiscover new posts, metadata only, free
transcribeAdvance discovered items; prints the cost estimate first
ingestOne shot: fetch a URL or local file and transcribe immediately
analyseTriage, then propose on survivors
brain-ingestPush transcripts into QuoxBrain (optional, non-blocking)
show / itemsPrint one transcript; list ingested items (--tag filters)
proposalsReview proposals (--pending / --approved)
approve / rejectThe HITL decision; approval records a decision only
lensList, assign, create, edit or remove triage lenses (see below)
hitlhitl send posts pending proposals to Telegram as short handles; hitl poll reads an exact "approve P1" reply, records it, and on approval runs the real QuoxPlan handoff
briefingDecisions needed, then what arrived, then what it cost
autonomyShow or set a source's autonomy level, with caps and spend shown first
budgetShow every cap and the spend against it; set per-source daily and org monthly caps
example
# follow a creator, discover, transcribe, analyse
signal follow @natebjones --on tiktok
signal poll --limit 5
signal transcribe --limit 5
signal analyse

# review the proposals and decide
signal proposals --pending
signal approve <proposal-id>

Triage lenses #

Every source is judged through a triage lens: the question triage asks about it, not just "will this make a useful thing for Quox" (that is one lens, quox-roadmap, the default). signal lens list shows every built-in preset plus any custom lens defined for this org; signal lens set <source> <lens-id> assigns one; signal lens create defines your own, no repo edit required.

The propose stage runs an evidence-gated pass following the Lens Invocation Contract (see the QuoxLens docs). It runs locally by default (provider: "builtin"); setting SIGNAL_LENS_PROVIDER_URL points it at a real QuoxLens provider instead, a config change rather than a rewrite.

Every finding a lens returns is checked back against the source transcript before it is kept: a finding whose evidence quote cannot be found verbatim in the transcript is dropped, never shown with an invented quote.

This is enforced in code, not by prompt instruction alone. Transcripts are treated as untrusted, third-party data throughout: a literal instruction aimed at an AI inside a transcript is reported in an injection_attempt field, never followed.

Proposals and HITL #

Every individual proposal carries a verbatim quote and timestamp for every claim (no citation, no proposal), a novel / novel_why judgement against what is already on the roadmap, and the injection_attempt field described above.

Proposals land as pending. signal approve / signal reject (or the dashboard's Proposals tab) records the decision. For an owner away from the terminal, signal hitl send posts pending proposals to Telegram as short handles (P1, P2, ...) and signal hitl poll reads an exact "approve P1" or "reject P1" reply and records it.

Handing an approved proposal to QuoxPlan is a second, explicit act. The dashboard shows a dry-run preview first (the exact stream payload, drafted deterministically with no model call), and only a second click submits it.

The write always goes through the collector's governed /api/v1/quoxplan/* proxy, never directly to QuoxPlan's port, because that proxy is where the mutation becomes an AEE envelope and gets WARD-witnessed. Without a service key in the server's environment, the handoff refuses outright rather than falling back to the unwitnessed direct path.

Each proposal shows its own evidence trail: SOURCE (the post URL), TRANSCRIPT (a content hash), AEE (the mutation envelope), WARD (the witness receipt). See the honest limits for the current state of that trail.

Autonomy and budgets #

Each source carries an autonomy_level: how far the background worker may advance that source's items without a human clicking anything. signal autonomy <source> <level> raises or lowers it, and prints what the level does and the current caps first when raising above manual.

LevelWhat runs unattended
manualNothing. The default, and the only level a source starts at
auto-transcribediscovered -> transcribed
auto-extract... -> analysed, triage only, no lens call
auto-lens... -> lens runs, verdict saved, no proposal created
auto-propose... -> full chain; proposals land pending for a human

There is deliberately no level that reaches QuoxPlan: that handoff always requires a human. Every autonomous advance reserves its estimated cost against the source's daily cap and the org's monthly cap before the work starts, in a transaction that stops two concurrent workers slipping past the same cap.

signal budget shows every cap and the spend against it; signal budget --source <label> --daily <usd> and signal budget --monthly <usd> set them. A budget-paused source is flagged in signal sources and in the briefing, never silent.

The worker is not installed by defaultAutonomy is executed by a separate process, bin/worker.js, the only process that actually calls yt-dlp, ffmpeg or a model in response to queued work. Its systemd unit (deploy/quox-signal-worker.service) ships but is not installed or started by default. Until a human starts it, every source behaves as manual regardless of its autonomy level.

The morning briefing #

example
signal briefing --since 24h [--json] [--write <path>] [--telegram]

Decisions needed first, then what arrived, then what it cost: the order matches what someone actually wants over coffee, not the order the data was generated in. The "needs attention" section (pending proposals, failed items, unhealthy sources, any budget-paused source) is deliberately never window-filtered, so a problem stays visible for as long as it stays true.

--write and --telegram let it run unattended; a matching daily systemd timer exists but, like the worker, is not installed by default.

The API #

Two access paths, and they behave differently:

  • The direct dev server on :3108: plain Express + SQLite, no authentication, the same posture as QuoxPlan's own dev server. The CLI and worker talk to this path (SIGNAL_API_URL). It is a dev harness: do not put it behind a public port unfronted.
  • The governed collector proxy, /api/v1/signal/*: what the dashboard uses. Auth-gated, traversal-guarded, and it emits a signal.* AEE envelope for every successful mutating request (signal.source.followed, signal.item.discovered, signal.proposal.created, signal.proposal.decided, signal.proposal.handed_off, with a generic signal.mutation fallback so no real write goes unrecorded).

Proposal creation and handoff use a deterministic correlation id (SIGNAL_PROPOSAL_<id>) so a fresh page load can rediscover a proposal's evidence thread from the proposal id alone, plus a follow-on signal.evidence.bundled envelope carrying the full evidence bundle: source URL, transcript hash, ASR engine, every evidence quote.

Stable, beta and broken #

Compiled from quox-dashboard/docs/STATUS_AND_LIMITS_QUOXSIGNAL.md and verified against the CLI's own help output. The point of this section is that nothing above is softened by omission.

Broken Not working, named plainly

  • Instagram account following does not work. yt-dlp's instagram:user extractor is broken upstream, not something this codebase can fix locally. Single reel URLs work fine.

Known gap Documented limitations, not softened

  • No proposal carries a complete end to end evidence chain today. Proposals created via the CLI carry no AEE envelope, because the CLI talks to the bare :3108 server directly, below the governed proxy where envelopes are emitted. And WARD receipts are not surfaced on the evidence trail, so even a proposal whose mutation was witnessed at write time cannot show its receipt. The SOURCE and TRANSCRIPT stages are real on every proposal; the chain is not complete until both gaps close.
  • It proposes, it never publishes. No build step is wired to approval, by design. The gap between "approved" and "something happened" is a second, separate human act.
  • TikTok account follows can look healthy and never fire: an account with little content returns one stale post, never an error.
  • Triage score variance is roughly ±25 points on identical input, which is why the triage gate defaults low: it favours recall, because a dropped item is never revisited.
  • Local transcription is roughly 5x slower than realtime on the reference box: overnight batches only.
  • Neither background process for the unattended story is installed by default: the worker and the daily discover/briefing timer both ship as systemd units that a human must install and start.
  • Qortex ingest redaction is a first pass, not a guarantee: it catches emails and separator-delimited phone numbers only. A transcript is not safe to treat as scrubbed because it passed through it.
  • The :3108 server has no authentication by design; the sanctioned access path is the governed collector proxy, which itself has no per-org licence gate yet.
  • X/Twitter is listed as a platform but has never been exercised against a live account. Untested is not "works".
Compiled from the quox-signal repository (docs/USER_GUIDE.md, docs/CLI.md, docs/API.md, docs/SOURCES.md, the CLI's own help output) and quox-dashboard's STATUS_AND_LIMITS_QUOXSIGNAL.md, 2026-07-31← Back to docs