Skip to content

// project

Candidai

A native, multiplatform AI career-intelligence engine, and the open-source project it's built on.

The brief

Candidai is a local-first AI career-intelligence engine: resume onboarding, reverse-ATS discovery, evidence-based fit scoring and tailored résumés, in one pipeline. The product idea and scoring model started on career-ops, an open-source Node.js job-search engine (⭐68k+) that I actively contribute to upstream. The native app is a from-scratch Kotlin Multiplatform rebuild: the same A-F fit-scoring engine, ported and verified line-for-line against the original, now running identically on Android, iOS, Desktop, Web and a Spring Boot server instead of a single Node process.

What shipped

  • 25-module Kotlin Multiplatform clean architecture (12 feature + 6 core modules) targeting Android, iOS, Desktop, Web and a Spring Boot 4 server from one shared engine.
  • core:engine is a no-IO module: A-F fit scoring, ATS search, SimHash fingerprinting, and funnel math ported 1:1 from career-ops and verified against its own test vectors.
  • 87 ATS & job-board provider integrations and a zero-token scan path (direct Greenhouse/Ashby/Lever APIs, no LLM cost) inherited from the open-source engine it's built on.
  • 24 merged PRs to the public career-ops project (⭐68k+): new ATS providers, an opt-in LLM re-ranker, an agent-inbox feature, and a run of correctness fixes, each with a reproduction and a regression test (full list below).
Kotlin MultiplatformCompose MultiplatformSpring Boot 4Room (KMP)Ktor87 ATS/board providers
Upstream (career-ops, ⭐68k+) Active · 24 PRs merged to public career-ops · member of the career-ops-hq org
built onkmp-build-logickmp-toolkit

// case study

The short version

The problem

career-ops's scoring engine lived in a single Node.js process. Proving the same logic could run identically on a phone, in a browser tab and on a server meant more than porting the syntax: it meant the scoring could not quietly drift from the original the moment it moved.

The decision

Port core:engine (A-F fit scoring, ATS search, SimHash fingerprinting, funnel math) as a no-IO module, then verify it against career-ops's own test vectors rather than trust a rewrite by inspection, the same zero-token discipline the upstream project already applies to keeping LLM cost off the hot path.

The result

One verified engine now drives five targets (Android, iOS, Desktop, Web and a Spring Boot 4 server) from 25 Kotlin Multiplatform modules, and 24 of the correctness fixes the port surfaced have already merged back upstream to the public career-ops project.

25 KMP modules · 5 targets

Evidence: One verified engine now drives five targets (Android, iOS, Desktop, Web and a Spring Boot 4 server) from 25 Kotlin Multiplatform modules, and 24 of the correctness fixes the port surfaced have already merged back upstream to the public career-ops project.

25

KMP modules · 5 targets

45k

lines of Kotlin · 543 files

87

ATS & job-board providers

24

PRs merged upstream

// multiplatform

One codebase, every surface

The real screens (and, where it's live, the running build) per platform — not a mockup.

Android screen (1 of 3)

Real Roborazzi captures: first screenshots off the actual Compose UI, not mockups.

// design notes

How it works

One engine, five targets

A 25-module clean-architecture split, 12 feature modules and 6 core modules, targets Android, iOS, Desktop, Web (wasmJs) and a Spring Boot 4 server from one shared Kotlin codebase: 543 files, ~45,000 lines. core:designsystem, core:protocol, core:engine, core:data, core:network and core:ai sit underneath feature modules for dashboard, pipeline, explore, intel, ops, profile, auth, assistant and more.

A no-IO engine, ported and verified

core:engine holds the A-F fit-scoring rubric, ATS search, SimHash fingerprinting for duplicate-listing detection, a liveness classifier, and the funnel math. None of it touches the network or disk. It's ported 1:1 from career-ops's original JavaScript implementation and checked against that implementation's own test vectors, so the scoring behaves identically whether it's running on Android, in a browser tab, or on the server.

Offline-first, agent-reachable

Room (KMP) plus DataStore caches everything locally over a Ktor REST + NDJSON/SSE sync layer, so the dashboard stays usable offline and catches up when connectivity returns. An agent-interop surface lets other agents, and the OS itself, drive the app without going through the UI. It covers Android AppFunctions, iOS App Intents/Shortcuts, candidai:// deep links, and a documented OpenAPI contract.

On-device AI, with a fallback that always works

Where an LLM adds real value, it runs on-device first: ML Kit GenAI / Gemini Nano on Android, Apple Foundation Models on iOS. Every AI-assisted step has a deterministic-heuristic fallback, so fit scoring and résumé tailoring keep working with zero model available, the same discipline career-ops applies with its zero-token scan path.

Zero tokens until an LLM is actually needed

The engine's scan path hits Greenhouse, Ashby and Lever APIs plus per-company local parsers directly, at zero LLM cost, falling back to an agent-driven search only for companies with no structured source. Every scanned posting passes through one shared trust-validator that scores and flags it before it reaches the tracker. 87 ATS & job-board provider modules plug into that one contract instead of reinventing trust scoring each time.

One engine, many candidates

career-ops's multi-profile architecture is a profiles.yml registry mapping each candidate to a private data root while sharing one engine install. That is the same shape the native app's per-candidate routing follows: one server, N profiles, a strict User/System data contract between them.

Bring your existing career-ops data

Already running the career-ops CLI? Point Candidai at the existing checkout instead of starting over: the first-run wizard's Profiles screen has a Bring your data step, a doctor gate checks it's a usable career-ops root and says exactly what's missing if not, then imports it as an active profile. Reference mode leaves the folder where it is so the CLI can keep reading and writing it, guarded by a cross-process tracker lock; copy mode clones it onto the server volume for a host with no shared filesystem. The same path is exposed over HTTP for a server deploy.

The agent-interop contract, not just an OpenAPI label

Any mutating request may carry an Idempotency-Key header; a retry with the same key on the same method and path is acknowledged without re-executing the write, the exact contract the mobile offline outbox itself relies on. Mutating requests to the API are rate-limited per client IP, with read-only GETs left unlimited. The scan and explore endpoints stream progress over SSE (text/event-stream) rather than blocking until a scan completes, so a caller sees offers arrive live instead of waiting on one long response.

Genuine upstream contribution, not a personal fork

24 merged pull requests against the public career-ops repository (⭐68k+, independently verifiable): two new ATS providers (BambooHR, Breezy HR), a dashboard rendering fix that rewrites only the changed Status cell instead of the whole row, an agent-inbox feature for queuing requests across sessions, an opt-in LLM relevance re-ranker for the pipeline, and a long run of correctness fixes. Most target one class of defect: code that reports success while doing the wrong thing. Distinct non-Latin company names collapsed to one key and silently deleted a tracked application; a `$` sequence in CV text spliced the template into the résumé while the build exited 0; a date filter was ignored in its `--flag=value` form, so a bounded scan silently ran unbounded; concurrent adds to the agent inbox dropped queued requests with no error; an unlocked append to shared scan history could interleave and corrupt it; and a `k`/`M`/`B` magnitude suffix walked an inflated claim straight past the fact-checker that exists to stop exactly that. Each shipped with a runnable reproduction and a regression test proving the fix.

// architecture

How it's built

One engine, five targets

Zero tokens until an LLM is actually needed

// under the hood

Tech stack

Native app

  • Kotlin Multiplatform
  • Compose Multiplatform
  • Spring Boot 4 server
  • Room (KMP) + DataStore
  • Ktor REST + NDJSON/SSE

On-device AI

  • ML Kit GenAI / Gemini Nano (Android)
  • Apple Foundation Models (iOS)
  • deterministic-heuristic fallback

Agent interop

  • Android AppFunctions
  • iOS App Intents / Shortcuts
  • candidai:// deep links
  • OpenAPI contract

Open-source engine (career-ops)

  • Node.js
  • 87 ATS/job-board providers
  • zero-token Greenhouse/Ashby/Lever scanning
  • A-F fit rubric

// directions

Same screen, 3 treatments

Pick a treatment to see it whole. Where one ships a dark counterpart, drag the divider to compare the two faces of it — same layout, same content, only the light changes.

theme — dashboard, light
theme — dashboard, dark
lightdark
dashboarddrag or arrow-key the divider

// gallery

Screens (4)

Swipe or use the arrows · tap a screen to enlarge

See how everything connects →

frequently asked

What does he do at Dice.tech?

SDE-2, Android & Product Owner at Dice.tech (June 2023 - Present), platform owner of the app behind 50k+ monthly active users (22k+ daily, platform owner at Dice.tech). See the source.

What did he do to improve GPS accuracy?

Took GPS accuracy to 95%: up from 50%, by predictive dead reckoning. See the source.

How did he reduce production crashes?

80% crash reduction: Crashlytics + structured concurrency fixes. See the source.

How much of the app is Jetpack Compose?

~87% of the UI layer: 455k of 523k UI-layer LOC, verified screen by screen against the legacy XML. See the source.

Where did he study?

B.Tech, Computer Science & Engineering, NIT Bhopal (MANIT) (2017 - 2021). See the source.

What is the Neev Consulting role?

Consulting Engineer, Platform & AI at Neev Consulting (April 2026 - Present). Built the LLM assistant layer of an ERPNext/Frappe consulting ERP: business-context resolution, capability discovery, and an AI capability gate that defaults OFF with a test proving it. Models client to project to PO to milestone to GST invoice to payment end to end. See the source.

What is Doori?

Doori: Offline-first mileage, travel & expense tracker on one Kotlin codebase across Android, iOS, Wear OS, watchOS & Desktop. See the source.

What is Gaddi?

Gaddi: A Hinglish social-deduction bluffing game of power, satire & second chances. Gaddi ke liye kuch bhi karega. See the source.

What is PaymentsLab-KMP?

PaymentsLab-KMP: An Integration Lab for the Android payments ecosystem: every gateway behind one abstraction, with a live look at what actually happens on each transaction. See the source.

What is Candidai?

Candidai: A native, multiplatform AI career-intelligence engine, and the open-source project it's built on. See the source.

Does he write, outside of code?

Yes: The Loopdown, his field-notes writing hub, where the recurring bug characters are named after real production incidents. See the source.

What's he like to work with?

Answered by his own teammates, not by him: see the EB Profiles, one question per member, in their own words. See the source.

Is he available, and how do I reach him?

Open to remote (worldwide / India) and hybrid in Pune / Bengaluru. Email siddharthpandalai990@gmail.com. See the source.

What has he contributed outside employer work?

Shared Kotlin Multiplatform libraries used across his own apps, plus merged upstream pull requests on career-ops: see the repos and the running count. See the source.

Has anything he's built shipped for real?

Yes: see the Play Store shelf for the apps that are actually live and installable, not just source. See the source.

What can I try on this site besides reading it?

Every route on the site, as a tile you can open: 3D builds, labs and canvases, not screenshots. See the source.