{"slug": "horolog-a-self-hosted-open-source-alternative-to-reclaim-ai", "title": "Horolog – a self-hosted, open-source alternative to Reclaim.ai", "summary": "Horolog, a self-hosted, open-source AI calendar alternative to Reclaim.ai, was released on GitHub by developer Ujjwal Reddy, offering automatic scheduling of focus time, habits, and tasks with re-plan latency of 0.6–97 ms compared to Reclaim's ~15 seconds. The tool runs entirely on the user's machine, supports multiple language models including Ollama and OpenAI, and guarantees minimal disruption by moving only affected blocks when meetings change, as verified by tests.", "body_md": "**An open-source, self-hosted AI calendar that defends your time.**\n\nHorolog places focus time, habits and tasks around the meetings you actually have - then keeps them there. It runs entirely on your own machine, against your own calendar, with whichever language model you already trust.\n\nIt is a working alternative to [Reclaim.ai](https://reclaim.ai) (acquired by\nDropbox in 2024). At the time of writing no open-source equivalent existed:\nCal.com covers booking links, but the auto-scheduling half - focus time, habits,\npriority preemption, automatic rescheduling - had not been built in the open.\n\n```\ngit clone https://github.com/ujjwalredd/horolog.git && cd horolog\ncp .env.example .env\ndocker compose -f infra/docker-compose.yml up\n# → http://localhost:3000\n```\n\nNo account. No telemetry. Nothing leaves the machine.\n\nMost \"AI calendar\" tools re-plan your entire week whenever anything changes, so you stop trusting what you see. Horolog optimises for the opposite:\n\nRe-plan latency |\n0.6–97 ms (Reclaim documents ~15 s) |\nBlocks moved when a meeting lands |\nonly the ones it hit - verified by test, not asserted |\nRe-plan with nothing changed |\na provable no-op, to the slot |\nWhere your calendar lives |\nyour machine |\nModel |\nOllama, vLLM, SGLang, llama.cpp, Anthropic, OpenAI - your choice |\n\nThe stability guarantee is the point. Measured on 20 blocks with an eight-hour\nmeeting dropped into the middle of the week: **4 blocks collided, 4 blocks\nmoved, 16 untouched.** An earlier single-pass placer moved 16 of 20 - the\nregression test that pins this is\n[ test_a_busy_week_does_not_cascade](/ujjwalredd/horolog/blob/main/services/api/tests/test_solver.py).\n\nEach is the same scheduling primitive wearing a different hat - one engine, not\nfive subsystems. See [ARCHITECTURE.md](/ujjwalredd/horolog/blob/main/docs/ARCHITECTURE.md) for why that matters.\n\n| Agent | What it does |\n|---|---|\nSmart Task Scheduler |\nDeadline-aware tasks, split across sittings and placed around real meetings. Priority P1–P4 decides who wins a contested slot. |\nHabit & Routine Manager |\n\"Gym three times a week between 10 and 4.\" Recurrence, time-of-day windows, per-day caps, automatic relocation. |\nDynamic Calendar Sync |\nICS feeds and CalDAV servers in, an ICS feed of your plan back out. Recurring events expanded; free/transparent events ignored. |\nSmart Meetings |\nMulti-attendee scheduling that intersects everyone's availability - without letting a colleague's calendar block your own solo work. |\nProductivity Analytics |\nDeep-work hours, meeting load, fragmentation, longest free run per day, after-hours load, unmet demand. |\n\nThree more, built on the same engine:\n\n| Feature | What it does |\n|---|---|\nDecompression buffers |\nOff by default (`HOROLOG_AUTO_BUFFER_ENABLED` ). Holds recovery time after every meeting between 30 minutes and 4 hours, from any source — ICS, CalDAV or typed in. A run of back-to-back meetings gets one buffer, at the end. It spends real capacity, which is why it is opt-in. |\nBooking links |\n`/book/<name>` offers true free time from `GET /api/availability` : hours holding flexible focus work are still bookable, because accepting one moves that work rather than colliding with it. Only real commitments close a slot. `POST /api/book` writes the accepted meeting to the calendar mirror and re-solves around it. |\nMulti-Provider Integrations |\nNative sync modules for Linear (`POST /api/sync/linear` ), Todoist (`todoist.py` ), and GitHub Issues (`github.py` ). Issues automatically schedule as fluid tasks around your real-life calendar. |\n\nBooking links have no authentication.`/book/<name>`\n\nis a display label, not an identity — this is a single-user, self-hosted app. Put the route behind your reverse proxy before exposing it to the internet.\n\nReal captures, from `npm run seed:demo`\n\n's sample week - not mockups.\n\n**Planner** — month, week, day or list. Priority by accent weight, kind by\nglyph, movability by rule style. Live over SSE.\n\n**Task inbox** — every intent and where it actually landed. Anything that did\nnot fit is called out, not hidden.\n\n**Habits & Focus Time** — builds routines and weekly focus goals in the units\npeople speak: \"3× a week, an hour each, between 10 and 4\" or \"10 hours a week,\n≥90-minute sittings\".\n\n**Meetings** — a Smart Meeting only lands where every attendee's busy time\nallows, without ever blocking your own solo work.\n\n**Analytics** — stat tiles plus a per-day load chart. Palette validated for\ncolour-vision deficiency, not eyeballed.\n\n**Calendars** — connect Google or Outlook, an ICS feed, or a CalDAV server;\nexport your plan as a subscribable feed.\n\n**Time** — a live \"today\" view: what's happening right now, what's next, and\na moving now-line on the day's timeline.\n\n`⌘K`\n\nanywhere opens natural-language capture: *\"write the design doc, about\nthree hours, by Friday\"*.\n\nThe model only ever fills in a form. It reads your sentence and produces a schema-checked request; it never picks a time and it cannot write to your calendar. The scheduler does that, from your real availability. Output is constrained at decode time, so malformed answers are not caught - they are impossible.\n\n```\n# Local, zero cost (default)\nHOROLOG_LLM_PROVIDER=openai\nHOROLOG_LLM_BASE_URL=http://localhost:11434/v1\nHOROLOG_LLM_MODEL=qwen3:8b\n\n# Anthropic\nHOROLOG_LLM_PROVIDER=anthropic\nHOROLOG_LLM_MODEL=claude-opus-5\nHOROLOG_LLM_API_KEY=sk-ant-...\n\n# OpenAI, or anything OpenAI-compatible (vLLM, SGLang, Groq, Together)\nHOROLOG_LLM_PROVIDER=openai\nHOROLOG_LLM_BASE_URL=https://api.openai.com/v1\nHOROLOG_LLM_MODEL=gpt-4.1\nHOROLOG_LLM_API_KEY=sk-...\n```\n\nAnthropic needs the optional extra: `pip install 'horolog[anthropic]'`\n\n(already included in the Docker image).\n\n**Requirements:** Python 3.12+, Node 20+. No database server needed - SQLite by\ndefault.\n\n```\n# once\ncd services/api && uv venv --python 3.12 && uv pip install -e \".[dev]\" && cd -\ncd apps/web && npm install && cd -\n\n# every time - starts the API and the web app together\nnpm run dev\napi: 8000 is in use, using 8001\n  Horolog\n  api  http://localhost:8001\n  web  http://localhost:3000\n```\n\n`npm run dev`\n\npicks free ports rather than assuming 8000/3000, wires the web\napp's proxy to whichever port the API got, clears any `.next`\n\nleft behind by a\nproduction build, and shuts both halves down together. Running the two by hand\nworks too (`npm run dev:api`\n\n, `npm run dev:web`\n\n) - just don't start two\n`next dev`\n\nprocesses against the same checkout, because they share `.next`\n\nand\noverwrite each other's chunks.\n\nThe API loads the repository-root `.env`\n\ncreated by the quickstart. The launcher\nalso keeps OAuth callback URLs on the actual ports it selected, so Google and\nMicrosoft callbacks continue to work when 8000 or 3000 was already occupied.\n\nThe web app proxies `/api/*`\n\nto the API, so the browser sees one origin and\nthere is no CORS to configure. Override with `HOROLOG_API_URL`\n\n.\n\n```\nnpm test       # 45 tests, ~1s\nnpm run bench  # solve-time + quality benchmark\nnpm run check  # ruff + ruff format + mypy strict + pytest, then tsc + next build\n```\n\nThe suite is fast on purpose - the scheduling engine has no I/O, so its properties are checked directly rather than through a server.\n\n``` bash\n$ python -m horolog.bench\nhorizon 2016 slots (21 days), 9:00-18:00 workday\ndemand held at 85% of open capacity | 7 runs each\n\n intents   reqs   load  blocks  cold p50  cold p95  warm p50  warm p95  unmet\n      30     70   84%     107      3.8ms     4.6ms     3.0ms     5.3ms      5\n     100    232   86%     224     13.6ms    14.8ms     8.9ms    16.9ms      8\n     300    700  185%     335     59.6ms    92.2ms    54.2ms    64.2ms    365\n```\n\nThe 300-intent row is deliberately over-subscribed (185% of capacity) - the\n`unmet`\n\ncolumn is correct behaviour there, not a failure.\n\nEvery setting is read from the environment with a `HOROLOG_`\n\nprefix and\nvalidated at boot, so a bad value fails the process rather than the first\nrequest that needs it. Full list in [ .env.example](/ujjwalredd/horolog/blob/main/.env.example).\n\nThe one worth knowing: `HOROLOG_TIMEZONE`\n\ndefaults to the host's own zone. Left\nat UTC on a machine that isn't, a 9-to-5 goal silently lands at 5am.\n\n```\ndocker compose -f infra/docker-compose.yml up -d\n```\n\nBrings up the API, the web app, Postgres, and Ollama with a small model pulled\non first boot. For SQLite instead of Postgres, drop the `db`\n\nservice and set\n`HOROLOG_DATABASE_URL=sqlite+aiosqlite:///./data/horolog.db`\n\n.\n\n**Authentication:** this build is single-user and unauthenticated by design - it\nruns on your machine against your calendar. For a team instance, put it behind\nan SSO proxy (oauth2-proxy, Authelia, Tailscale, Cloudflare Access) rather than\nexposing it directly. Horolog trusts the identity your proxy asserts instead of\nrolling its own credential store.\n\n**Backups:** see [docs/BACKUP.md](/ujjwalredd/horolog/blob/main/docs/BACKUP.md) - one command either way,\nSQLite or Postgres. There's no schema-migration tooling, so back up before\npulling a new version.\n\n```\nservices/api/horolog/\n  domain/       Pydantic contracts - the single source of truth\n  solver/       expand → greedy placement → score\n  providers.py  ICS + CalDAV\n  llm.py        multi-provider structured extraction\n  capture.py    natural language → validated intent\n  analytics.py  derived metrics\n  api.py        HTTP surface + SSE\napps/web/app/   Next.js 15, App Router, Tailwind v4\ndocs/           ARCHITECTURE.md\ninfra/          docker-compose + Dockerfiles\n```\n\nWe welcome contributions! Please read our [Contributing Guide](/ujjwalredd/horolog/blob/main/CONTRIBUTING.md) and [Code of Conduct](/ujjwalredd/horolog/blob/main/CODE_OF_CONDUCT.md) before submitting pull requests.\n\nThe engine is the product; everything else is I/O around it. If you change\nanything under `solver/`\n\n, the property tests in\n[ services/api/tests/test_solver.py](/ujjwalredd/horolog/blob/main/services/api/tests/test_solver.py) are the contract - they encode behaviour\n(no overlap, bounded churn, honest shortfall) rather than implementation, so a\nbetter algorithm should keep them all passing.\n\nRun `python -m horolog.bench`\n\nbefore and after any placement change and put the\nnumbers in the PR.\n\nPlease see our [Security Policy](/ujjwalredd/horolog/blob/main/SECURITY.md) for vulnerability reporting guidelines.\n\n[AGPL-3.0-or-later](/ujjwalredd/horolog/blob/main/LICENSE). If you run a modified version as a network service, publish your\nchanges.\n\n**Not affiliated with Reclaim.ai or Dropbox.** \"Reclaim.ai\" is referenced only\nto describe the category this project reimplements.", "url": "https://wpnews.pro/news/horolog-a-self-hosted-open-source-alternative-to-reclaim-ai", "canonical_source": "https://github.com/ujjwalredd/horolog", "published_at": "2026-08-10 03:51:31+00:00", "updated_at": "2026-08-10 04:05:41.404983+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-products", "ai-tools", "ai-agents", "developer-tools"], "entities": ["Horolog", "Reclaim.ai", "Dropbox", "Cal.com", "Ujjwal Reddy", "Ollama", "OpenAI", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/horolog-a-self-hosted-open-source-alternative-to-reclaim-ai", "markdown": "https://wpnews.pro/news/horolog-a-self-hosted-open-source-alternative-to-reclaim-ai.md", "text": "https://wpnews.pro/news/horolog-a-self-hosted-open-source-alternative-to-reclaim-ai.txt", "jsonld": "https://wpnews.pro/news/horolog-a-self-hosted-open-source-alternative-to-reclaim-ai.jsonld"}}