# DeepTutor shipped 15 releases in six weeks, and one of them broke the frontend

> DeepTutor is a self-hosted Python tutoring application with RAG retrieval, multi-agent collaboration, and a CLI described as agent-native. It is large and fast-moving, and its changelog is unusually candid about what broke. The fix-release notes are also the best argument for reading them before you upgrade.

**HKUDS/DeepTutor** — GitHub describes it as DeepTutor: Lifelong Personalized Tutoring. https://deeptutor.info/.. The repository metadata lists Python as its primary language. The metadata lists the Apache-2.0 license. This article stays within the project description and details documented in the GitHub repository README.

- Repository: https://github.com/HKUDS/DeepTutor
- Website: http://arxiv.org/abs/2604.26962
- Stars: 40,443 · Forks: 5,101
- Language: Python
- License: Apache-2.0
- Published: 2026-08-13 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/hkuds-deeptutor

## fifteen releases in six weeks, and 1.6.3 was a breaking refactor

The release list in the README is dense enough to date. v1.5.13 on 2026-08-17, then v1.5.14, v1.5.15, v1.5.16, v1.5.17, and v1.6.0 on 2026-08-27, then v1.6.1 through v1.6.12 by 2026-09-27. Fifteen tags in six weeks, and the patch numbers ran consecutively in the last stretch: v1.6.9 on 09-21, v1.6.10 on 09-22, v1.6.11 on 09-24, v1.6.12 on 09-27.

The consequence for anyone running this is that a pin has a short shelf life, and the version number does not tell you how much moved. v1.6.3, dated 2026-09-02, is described as a breaking front and back-end refactor with strict canonical routes and recoverable streams. A patch-numbered release that breaks the interface is the most important line in that list. Two smaller signals: the project keeps its changelog as prose inside the README, with anything older than a week collapsed into a details block, and the repository's homepage metadata points at an arXiv paper, `arxiv.org/abs/2604.26962`, while every README link goes to deeptutor.info.

## v1.6.7 shipped books with one empty chapter and quizzes that produced nothing

Two releases in that run are labelled as fix releases, and they list the symptoms rather than hiding them. v1.6.7 on 2026-09-11 covers books that arrived as one empty chapter, quizzes that produced nothing, the model's scratchpad appearing in the text, formulas printed raw, and cards you could not submit. v1.6.6 on 09-08 covers answers that could not submit, a copy button that lied, connected knowledge bases for partners, and Codex sign-in inside Docker. v1.6.8 then adds a sweep of fixes for quiet failures.

Read those together and you have the failure profile of a document-driven tutoring app at this stage. Parsing can return a structurally valid but empty chapter. Generation can return nothing at all. Reasoning can leak into the rendered answer. Math can arrive unrendered. Interactive elements can be inert. A copy control can report success while the clipboard holds something else.

None of that is a reason to skip the project, and naming it is to its credit. It is a reason to read the changelog for the two releases below whatever you pin, and to test a real book and a real quiz after upgrading rather than assuming a patch release is safe. The quiet-failure sweep in v1.6.8 should shape your test plan.

## docker compose is not the supported command; a Python wrapper is

There are five compose files in the repository root: `docker-compose.yml`, `docker-compose.dev.yml`, `docker-compose.ghcr.yml`, `compose.codex-oauth.yaml`, and `compose.yaml`, which is a separate Podman file. Picking the wrong one is easy. The documented commands are not.

The header of `docker-compose.yml` routes every invocation through a script:

```bash
python scripts/docker_compose.py up -d
python scripts/docker_compose.py -f docker-compose.yml -f docker-compose.dev.yml up
```

The reason is stated in the third prerequisite: use `scripts/docker_compose.py` so port mappings are rendered from `system.json`. Read that carefully. The compose file you are reading is not the configuration that runs, because the wrapper generates the port bindings from a separate settings file. Running `docker compose up` directly gives you a deployment whose published ports you did not choose.

The other two prerequisites matter just as much. Runtime settings are stored in `./data/user/settings` and created automatically, which means your first launch writes a settings tree before you have configured anything, and model providers are configured from the web Settings page or from `model_catalog.json` rather than from environment variables in the compose file.

## requirements.txt calls itself a mirror, and two wheels ship from one repo

The header of `requirements.txt` is unusually candid. It states that the single source of truth is the `optional-dependencies` table in `pyproject.toml`, and that files under `requirements/` mirror those extras for Docker and CI installs that do not yet have access to pyproject.toml and the source code. So there are two dependency declarations to keep in step, and the file tells you which one wins and why the other exists.

The install lines it lists are worth reading side by side:

```bash
pip install deeptutor
pip install deeptutor-cli
pip install -e ".[partners]"
pip install -e ".[all]"
```

Two public wheels come out of this repository. `deeptutor` is the full app and `deeptutor-cli` is the command line only, so choosing by name is the difference between a server you did not want and the tool you did. The extras are separate again: `partners` for partner channel SDKs, `matrix` for a Matrix channel, `math-animator` for a Manim animation engine, `dev`, and `all`.

The Python floor has a documented reason. `requires-python = ">=3.11,<3.15"`, and the comment above it says 3.14 is supported now that compiled dependencies such as FAISS publish compatible wheels, with a forward cap kept so a future interpreter is not advertised before the compiled stack is ready. The ceiling is a dependency-readiness decision, recorded in the file.

## host.docker.internal is the difference between a local LLM working and not

If you point DeepTutor at LM Studio, Ollama, or vLLM, the compose file tells you the fix before you hit the error: use `host.docker.internal` instead of `localhost` in the provider `base_url` fields. Inside a container, `localhost` is the container, so a provider on your machine looks like a refused connection rather than a misconfiguration, and the settings screen gives you no clue which of the two you have.

Endpoints are configured in `data/user/settings/model_catalog.json` or through the UI, which means provider configuration is a file in your data directory and not part of the compose definition. That is convenient for switching providers and awkward for version-controlled infrastructure, since the file lives under `./data`.

The workspace follows the same pattern. Setting `DEEPTUTOR_WORKSPACE_HOST` to an absolute host folder maps it to the stable in-container path `/workspace`, and if you omit the variable the default is `./data/user/workspace`. The failure mode is quiet in a way that matters: you point the app at a folder of material, forget the variable, and the container uses its own empty default while reporting a workspace that exists. There is no error for pointing at the wrong directory.

## pyte is a core dependency so the project can scrape another product's TUI

One line in the core dependency list explains itself better than the rest. `pyte>=0.8.1` is annotated as an in-memory terminal emulator used to scrape Claude Code's `/model` TUI on sync. DeepTutor is emulating a terminal, running another product's interface inside it, and reading the screen.

That is a coherent way to pick up model state from a tool with no API for it, and also an integration whose failure modes sit outside this project's control. A change to that TUI's rendering is a change to DeepTutor's partner sync, and the dependency will not fail, it will read the wrong thing. If Claude Code sync matters to you, that is the piece to retest after every upgrade.

Next to it, `mcp>=1.26.0,<2.0.0` sits in the core list rather than an extra, and the comment explains why with a reference to issue 792: the default chat and tool surface includes configurable MCP servers, so a plain install has to be able to connect them without an optional extra. The same reasoning is applied to the partner channel QR onboarding for WeChat, WeCom, and Feishu. Both choices are deliberate, and both make the default install larger than a tutoring app would suggest.

## the published index records what built it, and refuses a silent swap

v1.6.10, dated 2026-09-22, added native LightRAG role models and a published index that records and enforces what built it. That phrase describes a retrieval index carrying metadata about the models and configuration that produced it, and enforcing it means a later run with different role models does not quietly overwrite the earlier one.

For most RAG systems the index is an opaque artifact. Change the embedding or summarisation model and the old vectors meet the new queries with no event to point at. An index carrying its own provenance turns that into something you can see, and something that pushes back.

This is also where the alternatives question gets an honest answer. DeepTutor is not one closed product with a fixed retrieval stack. The release history names LightRAG as first-party, PageIndex OSS that you host yourself with reasoning retrieval, IMA libraries you browse and write to, MarginNote 4 libraries you connect along with its add-on filling them, WeKnora, and Serply alongside native search. The realistic choice for an evaluator is often which retrieval component to plug in, not which tutor product to buy.

## Redis holds coordination state, SQLite and PocketBase hold your data

The first service in `docker-compose.yml` carries a comment that answers a question you would otherwise have to guess at. Redis is internal-only runtime coordination state, with AOF enabled through `--appendonly yes --appendfsync everysec` so unflushed event and command streams survive a sidecar restart, and business data remains in SQLite and PocketBase. Redis is pinned to the `redis:7.4-alpine` image, named `deeptutor-redis`, restarted `unless-stopped`, with its state on `./data/redis` and a healthcheck that pings every five seconds.

So there are two datastores with different jobs, and a backup that copies one of them is not a backup. Redis is throwaway coordination; SQLite and PocketBase are what you would be upset to lose.

`compose.yaml` is the hardened path, and it is a different file with different rules: Podman 4.1 or newer, rootless, every service running with `read_only: true` where `tmpfs` mounts are the only writable surface, `userns_mode: keep-id` with a `:U` suffix on every volume, and host port bindings restricted to loopback. Bringing it up is two commands:

```bash
cp .env.example .env
podman compose -f compose.yaml up -d
```

Note the destructive line in the same comment block, where `podman compose -f compose.yaml down -v` is labelled as wiping data. Volumes carry the state, so a teardown command that looks routine is the one to read twice.

## Conclusion

DeepTutor fits a self-hosted deployment where the retrieval stack is something you want to control, and where MCP servers, IMA libraries, MarginNote 4 add-ons, and a self-hostable PageIndex OSS in the dependency list read as capability rather than as surface area. Do not adopt it expecting a stable 1.x contract, because v1.6.3 shipped a breaking front and back-end refactor inside a run of fifteen releases between 2026-08-17 and 2026-09-27. Verify three things before your first install. Which wheel you actually want, `deeptutor` for the full app or `deeptutor-cli` for the command line alone. Whether you will drive Docker through `scripts/docker_compose.py`, since the compose file you read is not the compose file that runs. And what the changelog says broke in the two releases before the one you are pinning.

## FAQ

### How do I install DeepTutor?

Two public wheels are published: `pip install deeptutor` for the full app and `pip install deeptutor-cli` for the CLI only, with `pip install -e .` for a source checkout and extras such as `partners`, `matrix`, `math-animator`, `dev`, and `all`. For containers, start it with `python scripts/docker_compose.py up -d` rather than `docker compose up`, so port mappings are rendered from `system.json`. Python 3.11 through 3.14 is supported.

### how to use deeptutor

Install the full app or the CLI, launch it, and configure model providers from the web Settings page or from `model_catalog.json` under `data/user/settings`, which is created on first run. For a local model on LM Studio, Ollama, or vLLM, the provider `base_url` must use `host.docker.internal` rather than `localhost`.

### deeptutor alternative

The release history treats retrieval as a swappable part rather than naming a single rival product. It records first-party LightRAG, PageIndex OSS that you host yourself with reasoning retrieval, IMA libraries you browse and write to, MarginNote 4 libraries with its add-on filling them, WeKnora, and Serply alongside native search.

## Sources

- [Official documentation](http://arxiv.org/abs/2604.26962)
- [Official README](https://github.com/HKUDS/DeepTutor#readme)
- [Project repository](https://github.com/HKUDS/DeepTutor)
- [Release notes](https://github.com/HKUDS/DeepTutor/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hkuds-deeptutor
