Skip to content

oracular roadmap ​

Oracular is an on-device intent engine: your app learns when the user is ready, and nothing leaves the device. This roadmap says what we are building next and why, in order. It is a statement of priorities, not a schedule; items move when real integrations show they should.

Principles that decide what gets in ​

  • Nothing leaves the device. No feature may require a network call, a vendor, a device fingerprint, or cross-user data. Requests that need any of those get a polite no, however reasonable they sound.
  • Pure core, thin helpers. The engine stays (state, event) → state. Anything with I/O lives outside the core, in documented patterns or small optional helpers.
  • Earn features from integrations. A feature enters the roadmap when a real app needed it, not when it sounds useful. Two production integrations exist today; their common shape became the store behind @oracular/react, and the Vue composable follows the same store.
  • Honest about the model. The docs say what the engine can and cannot learn, and every number in a tuning table is generated from the engine's own math.

Done: 0.3 ​

The theme was proof. Both integrations had to invent the same thing, a shadow mode that records what the engine would have done; 0.3.0 made it a first-class workflow, so a team can run oracular silently, export a log, measure it offline, tune, and only then switch a reveal on. Everything below shipped in 0.3.0:

  1. Recorder format and helpers. createRecord and createEndRecord produce the lines of a versioned NDJSON log. Every record carries an install id (random, generated by the app, never derived from the device), keeps the raw event apart from the recorded decision, and says whether reveals were shadow or live at the time. The Shadow mode guide has the format.
  2. npx oracular replay. Replays a log through the engine under any config and reports, per intent and threshold, precision, recall, lead time from reveal to outcome, reveals per user, and a reliability table with a Brier score. Shadow periods only: a mixed log gets a warning, a live-only log is refused unless told otherwise.
  3. Per-episode saturation. The n-th repeat of a signal in one episode adds w × log2(1 + 1/n); saturate: false opts an intent out. State version 2, with its built-in migration.
  4. State robustness. A fixture from every published version that must keep loading, and a fuzz test that feeds deserialize corrupt, truncated and tampered states.
  5. Packaging checks in CI. publint, @arethetypeswrong/cli, size-limit with a hard budget, and the exact tarball file list, all inside npm run check; the Node matrix stays.
  6. Docs: What oracular can and cannot learn.

Done: 0.4 to 0.7 ​

The theme was learning that matches how people behave. Everything below shipped between 0.4.0 and 0.7.0 (see the changelog), in priority order: the small ergonomics first because nothing depends on them, and attribution after tune, so that a real log could be replayed with and without it.

  1. Cross-episode attribution. Done. IntentConfig.attribution holds a closed episode for the outcome instead of recording a miss; an outcome inside the window credits each held episode by 2^(−age / halfLife) with the complementary miss, so every episode still counts once and nothing is ever reversed; the window's end makes it a miss at that moment. Off unless set. State version 3 with its migration. See Episodes & learning; whether a default is justified is a question for tune on real logs before 1.0.
  2. npx oracular tune. Done. From a recorder log across users, grouped by install id: base rate → prior, per-signal lift → initial weights, half-life → the lowest Brier score over a grid scored by replay, spread of per-install base rates → prior strength. Emits a ready-to-paste config with sample sizes and a 90% interval per weight. The counts come from the engine itself. Population-seeded config, per-user correction on device; see Shadow mode → Tune.
  3. Presets. Done: oracular/presets exports willBuy, upgrade, stuck and churn as plain config objects to spread and override, with every knob explained and presetSources saying where each number comes from. willBuy is tuned on the public clickstream (181,080 sessions); the other three are hand-written and labelled so until a real log replaces them. See Presets.
  4. Small ergonomics. Done: validateConfig() exported on its own; ms.days(7) and friends, a zero-cost units helper; a JSON Schema for the config, shipped as oracular/config.schema.json.

Next: 0.8 ​

What the case study asked for.

  1. tune fits learning.priorEpisodes and learning.gain. With one signal in the config the reveal threshold cannot reach a stateful rule: a higher threshold only demands more cart adds, so it costs recall and buys no precision. priorEpisodes and gain move the whole frontier. The setting that scored best on the fit half is, on the holdout, more precise and catches more orders than the printed config at thresholds 0.3 and 0.4 alike, and it sat at the edge of the grid searched, so the right values are not obvious. tune should grid-search both against the Brier score replay computes, exactly as it does the half-life. The defaults stay 10 and 2 because fast trust is high variance. After two cart sessions without an order, an order in a session without a cart add, and one session with neither, cart.added falls from 2.32 to 1.90 at the defaults, to 1.16 at priorEpisodes 3 and gain 2, to 0.87 at gain 3, and to −2.32 at the case study's best setting. That is correct inference on four episodes, but a weight that swings so far on so little history has to be fit per app, not changed globally. See the case study.

Before 1.0 ​

The theme is promises we can keep. 1.0 means the API and the state format are commitments.

  1. Stability policy, written down. Done: Stability and 1.0 says what is public API (every export, the serialized state, the recorder format, the schema, the error types and the CLI's JSON reports), the deprecation rule (one minor marked @deprecated before removal), the STATE_VERSION guarantee (every version ever written loads, no migration changes an estimate), and three rules for the state's version: a bump only adds fields, a release reads the next state version so a rollback by one release keeps every user's history, and from 1.0 the state format changes only in a major, with states moving between any two releases in both directions within one.
  2. Property-based tests. Done: tests/property.test.ts generates configs and event traces with fast-check and checks purity, monotone decay, the α/β invariant, serialize/deserialize identity and shouldReveal ⇔ no cooldown and p ≥ threshold and lower ≥ threshold, plus the ranges of every prediction field, the shape of explain, and that reads leave no trace. The fixtures and the deserialize fuzz from 0.3 keep running against every release in between.
  3. A debug overlay. Done: inspect in the main entry returns every intent's prediction, gate and explanation as one object, and oracular/inspect renders it as a framework-free panel (mountInspector, renderInspector), no DOM API or timer inside. See Inspect.
  4. One public case study with real numbers, produced by replay on a production shadow log, and at least two production users referenced. A reproducible first one, on a public clickstream rather than a shadow log, is Case study: a public clickstream; the production one is still to come.
  5. Housekeeping. Done in 0.3: GitHub Actions pinned by SHA, CodeQL and an OpenSSF Scorecard workflow, the publish-only-from-CI policy written down in SECURITY.md. What is left is keeping them green.

After 1.0 ​

  • @oracular/vue, a composable over the framework-free store that @oracular/react ships (createIntentStore). React shipped first because both production integrations are React apps. The rule the store embodies holds for every binding: never end an episode on background, pagehide or visibilitychange; a short app switch is not a session boundary. Flush persistence and let episodeTimeout close idle episodes.
  • Persistence adapters for localStorage, AsyncStorage and MMKV ship inside @oracular/react; an IndexedDB adapter, or a framework-free adapter package, if an integration asks for one.
  • Playground upgrade. Paste a recorder log, scrub a timeline, drag sliders for weights, half-life and threshold, and watch p and the lower bound move.
  • Project structure. A GitHub organization and a second maintainer when a contributor shows up; GOVERNANCE.md says how.

Considered and declined, for now ​

  • A population benchmark against a logistic model on public clickstream data. A model trained on data oracular refuses to collect will win on precision. The meaningful comparison is against the rules a team would otherwise hand-write, on their own shadow log, which replay provides.
  • Performance benchmarks. observe() runs in microseconds. Bundle size is the number that matters, and size-limit guards it.
  • Anything that phones home, including opt-in telemetry about the engine itself.

How this changes ​

Open a Discussion in the "Modelling" category with the integration you are doing and what the engine could not express. Items on this roadmap came from exactly that.

Released under the MIT License.