DeepTutor v1.5.17 Release Notes
Release Date: 2026.08.25
v1.5.16 connected a MarginNote library and put a run of gateway failures to rest. This one changes who a Partner belongs to: they stop being a deployment-wide resource only an admin can touch and become something each member builds, owns, and holds their own private conversations with — an admin still lends theirs out through grants. Around that sit a knowledge base that tracks GitHub repositories, Antigravity CLI under My Agents, a WeChat QR code drawn in the browser instead of a log file, and a container that keeps the optional extras you declared. Existing partners keep working and migrate themselves; the one behavior change is scoped to non-admin users, who can now see a Partners page that used to be closed to them.
What's New
Partners belong to people, not to the deployment
Every /api/v1/partners route used to sit behind a blanket admin gate, so a member could talk to a partner an admin had assigned them and nothing else. Three rights now nest instead: manage (its creator, plus any admin) covers soul, channels, models, assets, lifecycle and deletion; use adds anyone an admin assigned it to, and means holding a conversation; anyone else cannot see that the partner exists — an unowned partner and an invisible one answer alike, so ids cannot be enumerated.
Conversations follow the human, not the partner. Two people talking to the same companion never see each other's threads, and each side reads back in the web app under its own account. Grants still exist and are unchanged in shape, but they now only offer admin-managed partners: one someone built for themselves is theirs to share or not, since its SOUL.md is usually written personally.
A chat account can carry your DeepTutor identity
A partner reached over QQ or Telegram used to know only a channel-local sender id, so every inbound message was anonymous — it landed in the shared thread pool, and the partner answered out of the admin workspace rather than the sender's own library. Ask your partner for a code in the web app, send /link <code> to it from the chat account you want connected, and from then on that sender is you: the conversation is private to your account, readable back in the browser, and the partner reads your knowledge, notebooks and memory the way it would in a web turn.
Two partners can share a channel
Channel runtime state — bot tokens, long-poll cursors, conversation references, E2EE device stores — lived in one directory per channel, not per partner, so two partners configured on the same channel read each other's credentials and overwrote each other's cursor. Only one of them could ever be online. State now lives under data/partners/<id>/channels/<channel>/, and the old tree is copied across on first start when its owner is certain — the saved token matches one partner's configured token, or only one partner has that channel enabled at all. A genuine tie re-authenticates once rather than being handed someone else's account.
Personal WeChat signs in from the browser
The QR-code exchange has always worked, but it only ran from the channel's own start() and drew the code on the server's stdout — which on any container deployment is a supervisord log the person configuring the partner cannot reach (#951). The same exchange now runs for the web app: press Scan to connect on the partner's channel page, scan, and the bot token is written into the channel config server-side. A status reply says whether the login succeeded, never what it produced. The terminal still prints a code as a fallback.
A knowledge base can track GitHub repositories
Register a repo, branch and glob as a GitHub source and its Markdown is pulled into the KB and re-synced on demand, so documentation you follow stays current without re-uploading it. Contributed as #903; download destinations are contained inside the KB's raw/ directory, since a .. segment or an absolute path in a tree entry would otherwise have written — and, on the removal branch, deleted — outside the base.
Antigravity CLI joins My Agents
Google retired Gemini CLI in June and pointed its users at Antigravity CLI, so anyone on a Google AI Pro/Ultra or Gemini Code Assist plan was left with a gemini backend that had no CLI to detect (#828). agy is now a backend of its own — connect it, consult it mid-turn, resume its conversations. The Gemini CLI backend stays: it still runs for anyone holding a Gemini API key, and a working connection must not break on upgrade.
deeptutor doctor
One command that answers "is this workspace ready to start a session?" — settings, providers, dependencies, ports. --online also sends a small request to the configured model provider; --format json prints the same report for a script or an agent. Contributed as #959.
Containers keep the extras you declared
A container is disposable, so docker exec … pip install ".[partners]" survives exactly until the next compose down (#762). Declare them on the deployment instead — DEEPTUTOR_EXTRAS: "math-animator,partners", and DEEPTUTOR_APT_PACKAGES for system libraries — and every container started from it re-applies them. A warm container pays one metadata lookup per requirement, the pip cache lives on the data volume, and a missing wheel leaves that one feature unavailable rather than taking the deployment down.
Embeddings survive a rate-limited provider
Reindexing a large library against a low-RPM provider used to burn hours and then die on a 429. Three things changed: a key pool rotates across provider × model with per-key cooldown so one throttled key no longer poisons the run; a retry budget waits out Retry-After instead of exhausting itself on a sliding window; and a process-wide throttle serializes the interval-plus-post across concurrent reindexes, which is what made a request that succeeded by hand fail inside the app. Contributed as #962.
Windows
MinerU parsing decoded its subprocess output with the platform default — GBK on a zh-CN install — and died on the first non-ASCII byte; its multi-threaded PDF renderer separately corrupted the heap. Both are fixed, the render guard scoped to Windows only (#844). Atomic writes now retry a transient PermissionError from an antivirus or indexer holding the target, and code execution works on a Windows host (#964).
Assorted
- Reasoning effort is per conversation. The model-level control from v1.5.9 can now be overridden for a single chat (#641, contributed as #958).
- Indexing fails bounded instead of hanging. A provider that accepts an embedding request and never completes it stalled the executor future forever; a heartbeat guard now raises after a bounded window of no progress (#946).
- MinerU page-layout blocks stop being indexed as content. Headers, footers and page numbers are filtered before ingest; an unrecognized block type is indexed rather than failing the whole run, so a MinerU release that adds one cannot break indexing (#949).
- Voice transcription accepts what Chrome records.
audio/webm;codecs=opusis normalized once, at the wire boundary, instead of being rejected as an unknown format (#945). - Mastery quiz cards no longer land below the fold. The viewer mounts after the post-stream scroll window closed (#960), and a quiz whose options repeat a label or an answer is rejected at registration rather than shown as an unanswerable card (#956).
- A learning path keeps its name. Three surfaces each derived it from the first module, so rebuilding a map renamed the path and the three could disagree; one function answers now, and derivation is the fallback, never the record.
- A tool that changes what the round operates on runs first. Switching mastery path mid-round left the other calls bound to the state the round started with; the dispatcher now runs rebinding tools, re-binds everything else, then runs pausing tools last.
- Local LightRAG answers cite their sources (#754), and the local LlamaIndex pipeline no longer freezes its progress-bar decision at import time (#864).
- Azure OpenAI accepts the endpoint the portal hands you. A classic
…/openai/deployments/<name>/chat/completionsURL is reduced to the/openai/v1surface, so chat stops answering404 Resource not foundwhile the settings probe passes. - Gemini embeds images where the model supports it, and refuses
contentsfor one that does not rather than posting parts the endpoint will reject (#814). - A page can ask whether a capability is installed. Plugin-served capabilities such as the Whisper practice room were offered by a stock install that could not honour them, and the turn came back
Unknown capability(#963). - The standalone CLI package declares what it actually imports (#954).
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No re-index and no manual migration.
- Non-admin users gain a Partners page. On a multi-user deployment members can now create and manage their own partners, and their conversations with any partner are private to their account. Existing partners have no
owner_idand therefore read as admin-managed — exactly what they were — so nothing you have configured changes hands. - Channel state moves per partner, by copy. On first start, state under
data/partners/<channel>/is copied todata/partners/<id>/channels/<channel>/when its owner is unambiguous. The legacy tree is left untouched, so a bad outcome is recoverable by hand; a partner whose ownership was ambiguous simply re-authenticates once. - Grants only list admin-managed partners now. A partner a member built for themselves will not appear in the assignable pool.
- Container extras are opt-in. Nothing changes until you set
DEEPTUTOR_EXTRASin your Compose file. Preview one withpython scripts/install_extras.py --dry-run "<name>". - Gemini CLI connections keep working. Antigravity is an additional backend, not a replacement.
Full Changelog: v1.5.16...v1.5.17