Releases: HKUDS/DeepTutor
Release list
v1.5.16
DeepTutor v1.5.16 Release Notes
Release Date: 2026.08.22
v1.5.15 added a capture inbox under the Book reader and, in doing so, stopped its pages from turning. That is fixed here, alongside a new connected knowledge source — a MarginNote 4 library that its own add-on fills — and a run of failures that were only reachable through a particular gateway: tool-call ids that grew until the provider refused them, embeddings rejected for a parameter we deliberately omit, and a temperature limit that only applied if you had named the right binding. Drop-in — no migrations, nothing to re-index.
What's New
MarginNote 4 as a knowledge base
Connect a library and the MN4 add-on pushes your notes, excerpts, cards and mindmap nodes into it — no upload, no index, nothing copied out of MarginNote. Pair a device from the library's Devices tab, paste the one-time token into the add-on, and syncs arrive incrementally into a store DeepTutor owns.
A turn on that library runs on seven tools of its own rather than on rag: search across excerpt and note text, read a single object, list by type, walk a document's children, follow links, filter by tag, and read mindmap cards. The chat loop hands the turn to them the way it does for an Obsidian vault, because a library that holds no index has nothing for retrieval to chunk.
Contributed as #931 by @evan188199-tech. This is phase one — read-only navigation; write-back is planned.
Book pages turn again
The capture inbox landed as a second child of the reader's flex row without making that row a flex column, so PageReader's h-full collapsed to auto: the body stopped scrolling and the page-turn footer was clipped out of view entirely. A one-line layout fix; every gate we run was green through the regression, since nothing here is visible to types or tests.
Streamed tool-call ids stop growing
A router in front of an OpenAI-compatible provider may re-send the whole tool-call id on every delta chunk. Three core sites appended it instead of assigning it, so the id grew one copy per chunk — #937 saw 47,241 characters — and past the provider's 64-character ceiling every round died with Invalid 'messages[i].tool_calls[0].id': string too long, surfacing as "I could not produce a useful response from the model output." name had the identical defect at both sites, latent on that gateway and fatal on any that repeats the name. The three sites now share one accumulator: id and name arrive whole and are assigned, arguments arrives in fragments and is concatenated.
Model limits follow the model, not the route
Kimi models lock temperature server-side and reject an explicit value with HTTP 400. The override for that has existed since before v1.5.5, but it was resolved from the configured binding's spec — so it never fired for #938, who did the ordinary thing and pointed binding="openai" at Moonshot. The route is not what enforces the limit, so the route no longer decides: the configured spec wins when it says something about the model, otherwise the model's own vendor spec answers. Every model-intrinsic override was being lost the same way behind a generic binding.
Embeddings recover from a gateway that demands encoding_format
ModelScope reads a missing encoding_format as '' and refuses the request (#934); SiliconFlow refuses it when the field is present, which is why we omit it. The same Qwen3-Embedding weights are served on both sides of that disagreement, so no default and no model-family rule can satisfy both. The happy path is byte-identical — the field is still omitted — and a 400 that names both the parameter and the value we are about to send earns exactly one retry with encoding_format="float".
Images in an index describe in parallel
The LlamaIndex pipeline described extracted images one at a time. They now run concurrently with a per-image timeout and a progress line, tunable via image_description_concurrency (default 4) and image_description_timeout_seconds (default 60). A model that turns out not to accept images is no longer retried without them — the image is the question there, and a description invented from the text around it would be indexed as fact. Contributed as #933.
LightRAG converges instead of hanging
Transport failures against a LightRAG base are now classified before they are retried: retryable HTTP statuses get up to three bounded attempts with a capped Retry-After, anything else fails immediately, and the two retry layers no longer multiply. Cancellation cleans up rather than leaking, and a Codex transport failure propagates instead of being swallowed. Contributed as #940.
Assorted
- Indexing progress lines are translatable. All 22 producers sent hardcoded English to the log box. The wire now carries the template plus its values and the frontend renders it in your language; consumers without i18n — logs, snapshots, the CLI — read exactly what they read before.
- A deleted knowledge base stops haunting your sessions. Its name lingered in each session's context chips (#936).
- The development Docker image has a frontend again. Its supervisor ran
node scripts/dev.mjsagainst a production standalone bundle that contains no sources, no configs and noscripts/, sodocker compose -f docker-compose.dev.yml upcame up dead (#906). The stage now takes the builder's wholeweb/tree. The production image was never affected. - A provider's rejection body is capped before it reaches the log. That log exists so you can attach
deeptutor.jsonlto a bug report, and some providers echo the rejected request — schemas, sometimes messages — back into it (#930). - Two MarginNote libraries can no longer claim one store. "My Lib" and "My/Lib" both derive
My_Lib.db; registration now rejects the collision by name, and deleting a library removes the store we created for it. - The MN4 device bridge checks the credential first.
/syncand/heartbeatresolved their store before validating the token, so an unauthenticated POST created a database per distinct header value — and pairing wrote into the caller's workspace while the device path read the default one, which could 403 every sync forever.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- A MarginNote 4 library starts empty. Connect it, pair a device from the Devices tab, then paste the one-time token into the MN4 add-on — the token is shown once and only its hash is kept. The store lives at
data/user/marginnote4/<name>.dband is deleted with the library; nothing in MarginNote itself is touched. - MarginNote libraries are not swept by
rag. Like an Obsidian vault, they are reached only through their own tools, so Book andrag_searchskip them instead of reporting an empty result. - The new image-description knobs apply to the next index build, not the next question, and live under Settings → Knowledge Base.
Full Changelog: v1.5.15...v1.5.16
v1.5.15
DeepTutor v1.5.15 Release Notes
Release Date: 2026.08.20
v1.5.14 put the document next to the conversation. This one is mostly about what sits behind it: PageIndex becomes something you can host yourself and retrieves by reasoning over a document tree, the question bank stops being a list you cannot file into, and third-party packages can now register their own tools and capabilities. Nearly all of it arrived as community pull requests. Drop-in — no migrations, nothing to re-index.
What's New
PageIndex you can host yourself, and retrieval that reasons
PageIndex OSS joins the engine list as its own knowledge-base type: the same interface as the hosted service, bound to the base's local library, driven by the LLM you already configured instead of a PageIndex API key.
Retrieval changed underneath both variants. A PageIndex base used to be read through a built-in MCP server; that path is gone, replaced by a small reading loop that walks the document tree — structure first, then only the pages it needs. Fewer moving parts, and the same page-level citations come back.
The question bank stops being write-only
addEntryToCategory shipped in v1.3.6 and was never called from the frontend, so for eleven releases a learner could create categories and never put anything in them. The quiz card's bespoke dropdown now uses the shared category menu that does call it.
The page is rebuilt around the three things a flat list could not do — search, batch selection, and an inbox for questions nobody has filed yet. Graded Mastery Path questions carry their explanation and difficulty in with them.
And the tutor can finally file for you. Asking it to group your wrong answers had no writable handle on the bank, so it reached for write_note and your request quietly became a notebook record — the Chinese write_note hint even listed 错题分析 as a reason to append. The new question_bank tool is name-addressed: organize takes a category name and creates it if missing, so filing is one call after one listing. It mounts only when the bank has entries.
Third-party tools and capabilities
Two entry-point groups let an installed package extend DeepTutor without touching the tree. deeptutor.plugins was already imported by the capability registry and /plugins/list behind a try/except — but the module never existed, so plugin discovery was a dead path; it is now real. deeptutor.loop_capabilities registers chat-loop capabilities alongside the built-ins, which win on name collisions. Broken entry points are skipped rather than fatal, and discovery is cached per process so plugin capabilities stay singletons.
Apache Tika parsing
A seventh document-parsing engine, selectable in Settings → Knowledge Base. Tika is remote-only: no Python package, no model download — it points at an Apache Tika server (TIKA_SERVER_URL) and converts a broad set of formats to Markdown.
LightRAG's indexing knobs, in the UI
max_concurrent_files, llm_model_max_async, and entity_extract_max_gleaning reached runtime settings and the API in #877 but had no inputs, so they were reachable only by hand-editing config. They now sit under their own Indexing divider — a different axis from the query knobs above, because they shape how a base is built, so a change applies to the next build rather than the next question. The wiring asks the installed RAG-Anything what it accepts and drops the rest with a warning, so an older install keeps running on library defaults instead of raising.
Rebuilding a document tree in a knowledge base
Three fixes finally close #866. A linked folder's subdirectories stopped collapsing into raw/'s top level; same-named documents from different subtrees stopped silently dropping one another (a clash between different bytes stages under a free sibling name, identical bytes still dedup); and uploads now take an optional destination folder.
That last one was the actual blocker. A browser folder pick reports each file's path relative to the directory you chose, so adding 应用开发/钉钉CLI/ one subtree at a time arrived as 钉钉CLI/... with no way to say where it belonged — every batch piled up at the root, and the missing ancestor was never in the payload to begin with. The picker now offers the base's existing folders once files are staged.
Book: a learning-capture inbox
Reading segments you select are kept per book with an explicit status lifecycle, so captures can be reviewed before they go anywhere. Separately, drift warnings got honest: stale-page detection is scoped to the KB documents that actually changed, and a warning will not clear while flagged pages are still awaiting recompilation.
Whisper — a community contribution
/whisper is a two-seat counselling-practice room contributed by @alanguan73: the tutor plays the visitor, you take the trainee seat, and a supervisor whispers coaching only the trainee sees, with an end-of-session debrief. It lives under Learning Space → More Projects and needs its own capability package installed — the surface ships, the counselling capability is not part of DeepTutor.
The generally useful half of that work is in the runtime: a capability can now publish a body and its own declared extras on CAPABILITY_COMPLETE, while capability, session_id, and turn_id stay unspoofable. Only the explicitly declared sub-dict goes on the event bus — turn metadata is a scratchpad holding live callables and your ask_user answers, and it was never a wire format.
Assorted
- The model knows what day it is. Without the real date it resolved "today" from its training cutoff when composing web searches. The injected block is day-granular, so the system prompt stays byte-stable within a day and keeps prompt-cache hits.
- Deleting a turn no longer blanks the session. Descendants pointed at deleted rows, the session lost its root, and the page rendered empty (#912). Deleted rows are spliced out of the parent-pointer tree, and already-corrupted sessions are recovered from their oldest orphan.
- A stale CA bundle no longer breaks every LLM call. A leftover
SSL_CERT_FILE/SSL_CERT_DIR— common after cloning a conda env withoutca-certificates— made httpx raise mid-construction; broken paths are dropped so TLS falls back to the default bundle. - Thinking tags are filtered on non-SSE local streams too. That branch passed reasoning blocks straight through; both branches now share one incremental parser, so a tag split across chunks or left unclosed behaves the same either way.
- Unhandled server errors return JSON instead of Starlette's plain-text 500, and the handler stays inside the middleware stack so the response still carries CORS headers.
- Obsidian vaults survive real-world files — undecodable bytes and date-typed frontmatter are tolerated, and a vault that fails to open says why instead of failing opaquely.
- A stuck session load ends in a retryable state rather than spinning, and token usage emitted as plain dicts by native adapters is recorded instead of lost.
- Mastery reads stop inventing paths,
end_loopis honored after anask_userresume, and thecodebuddyextra resolves again. - The frontend is linted in CI. ESLint now runs on
web/on every build.
The docs at deeptutor.info were refreshed alongside this release.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- PageIndex OSS needs an API-key or local LLM profile. An OAuth-only provider (OpenAI Codex, GitHub Copilot, CodeBuddy) cannot drive indexing and is rejected with an explanation. OSS bases are PDF-only, and at most one may be selected per request.
- PageIndex's built-in MCP server is gone. Nothing to configure — hosted bases keep working through the new reading loop. If you had pinned that MCP server anywhere yourself, drop it.
- Tika installs nothing. Run an Apache Tika server and point
TIKA_SERVER_URLat it (defaulthttp://localhost:9998); there is noparse-tikaextra because there is no Python package. - The LightRAG extra's floor moved to
raganything>=1.2.5, the first release carrying the keyword passthrough. Older installs are not broken — unsupported knobs are dropped with a warning — but the three new ones need that floor to take effect. The knobs apply to the next build, not the next question. - Book drift warnings re-baseline once. Fingerprints now carry a scheme tag, so changing the formula re-baselines existing books instead of marking every one of them stale.
- Whisper is not a primary nav entry. It sits under Learning Space → More Projects, and the room needs its own capability package — a stock install has the page but not the counselling capability behind it.
Full Changelog: v1.5.14...v1.5.15
v1.5.14
DeepTutor v1.5.14 Release Notes
Release Date: 2026.08.19
v1.5.13 made a book something you read. This one puts the assistant next to what you are reading: a document opens beside the thread, and every claim comes back with the page it came from. Three other shifts sit around it — DeepTutor can configure itself from a chat turn, a Tencent IMA library becomes something you browse and write to rather than only search, and notebooks get a console of their own. Drop-in — no migrations, nothing to re-index.
What's New
Immersive Reading — the document open beside the thread
Pick Immersive Reading in the capability menu and open a PDF, a slide deck, or any document the knowledge base already accepts. The reader takes about two thirds of the workspace, the conversation keeps the rest, and the seam between them drags.
The assistant reads the same units you see — PDF pages, slides, or roughly page-sized sections for formats that have no pages of their own — so an answer cites [p.12] and the reader scrolls there and highlights the sentence behind the claim. Select a passage and ask about it, highlight as you go and attach notes, then take your marks with you: export a real annotated PDF that opens with the highlights intact in Preview or Acrobat, or Markdown to paste into your own notes.
Two decisions make it grounded rather than merely confident. A reading turn keeps the whole chat surface — web search, code execution, your other knowledge bases — because the five reading tools are added to it, never substituted for it. And before the model runs at all, DeepTutor runs your own question against the open document and hands it the top hits, so grounding happens even with models that would never have called a read tool on their own. It is a plain search, not a second LLM pass: no extra tokens, no latency before the first word.
Ask DeepTutor to configure itself
"Switch the interface to Chinese and use a better PDF parser" is now something you say rather than something you go and click. A turn can inspect the install, apply a setting, install a parsing engine or fetch its model weights — following the log live inside the single call instead of polling for it — and ask you for a credential.
Three rules keep that from being reckless. A new model or provider is probed before it is committed, against a candidate configuration that never reaches disk, so the assistant cannot switch itself onto something unreachable and cut off its own power supply mid-conversation. API keys never enter the model's context: it opens the matching form and your browser posts the key straight to the server. And every knob it can touch is one row in a single table carrying its own scope and effect, so you are told whether a change is instant, needs a restart, or invalidates your embeddings — instead of finding out later.
Tencent IMA: a library you browse and write to
An IMA library was searchable and nothing else. It now also answers "what is actually in here" — the knowledge-base file list reads the library's own browse API instead of reporting a connected base as non-enumerable, and a listing cut short by its request budget is labelled a lower bound rather than passed off as a total.
From a chat turn you can list the library, read a whole source rather than the retrieved fragments, search your notes by recency, add URLs for IMA to ingest, and write a note back. These tools are additive: rag still serves an IMA library, because it is genuinely searchable over HTTP, and attaching one no longer costs you web search or your other bases.
Connected knowledge bases are searchable again
v1.5.13 stopped Book from sweeping connected knowledge bases: the sweep returned nothing, and "nothing" was indistinguishable from a source with no relevant content. That fix was drawn too wide. Only an Obsidian vault (no index at all) and a connected subagent (not a document collection) are genuinely unreachable — a linked folder mounts an index built elsewhere, and LightRAG Server and IMA offload retrieval over HTTP. All three were being set aside, so every book quietly dropped them. Retrieval now asks whether a base can be retrieved from, not whether it is connected.
Notebooks get a console
Notebooks moved out of the Space document layout into their own full-height console at /notebook (the old link redirects, deep links included). Records can be copied or moved between notebooks, and a whole notebook exports as one Markdown document.
Three defects went with it. Concurrent saves could clobber one another; each notebook now has its own lock and every write is atomic, so a crash leaves the previous file rather than a truncated one. A notebook whose file is damaged is surfaced as a flagged row instead of vanishing from the list. And renaming a record used to clear its knowledge-base link, because the API forwarded every field whether or not you sent it.
Settings writes stop overwriting each other
Two writers shared interface.json and neither's lock covered the other. Measured before the fix, six preference saves racing six writes from a chat turn lost every one of the saves. Both now go through one lock and one atomic replace. Separately, the settings endpoints stopped writing back the defaults-merged view of your settings: changing your theme used to freeze that day's defaults into your file as explicit choices, so you silently stopped following later changes to any of them.
Assorted
- The capability menu was regrouped. Chat, Quiz, Visualize, Mastery Path, and Immersive Reading are one click away; Research and Solve moved under More Capabilities. The grouping is now purely about menu order — it used to key off which engine a capability ran on, which meant the menu could not be reordered without lying about the engine.
write_notereports a failure it used to hide. A record id came back even when no notebook accepted the write, and that was read as success.- A damaged notebook no longer takes a chat turn down while resolving notebook references, and
/notebook/healthis reachable again — it was declared after/{notebook_id}and had been shadowed by it.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- Immersive Reading stores materials per user under a new
readingworkspace directory, created on first use. Nothing is written until you open a document. - The capability picker looks different. Research and Solve are now under More Capabilities; nothing was removed, and a session that was using either keeps working.
- Reading needs no new Python dependency (PyMuPDF is already core), and the PDF viewer ships with the frontend bundle — source installs pick it up on the next
npm install. - A scanned, image-only document is rejected with an explanation rather than opening as an empty reader. Run it through OCR first, or index it in a knowledge base with a parsing engine that does OCR.
Full Changelog: v1.5.13...v1.5.14
v1.5.13
DeepTutor v1.5.13 Release Notes
Release Date: 2026.08.17
v1.5.12 was about where an answer's material comes from. This one is mostly about Book — the surface that turns that material into something you read rather than something you chat with. A book could be generated but not really used: the page froze while it was being written, the progress it claimed to track was never written down, and what you got could not leave the app. That gap is the bulk of this release. The home screen also stops asking you to start from a blank composer. Drop-in — no migrations, nothing to re-index.
What's New
Watch a book being written, then finish it and take it with you
Compilation outlives the request that started it: confirm_spine returns as soon as the page shells exist, while the work it queued runs for minutes. The event bus was owned by that request and closed with it, so every subsequent event was dropped and "watch your book being written" degraded into a frozen page. The stream is now owned per book by the process, so it survives the request, a refresh, and a reconnect.
Reading progress is now actually recorded. visited_page_ids and bookmarked_page_ids had been declared but never written for the life of the feature; visited pages, bookmarks, and quiz attempts now roll up into a completion score and a list of weak chapters.
And a book can leave the app: export to a self-contained Markdown document. Every block type gets a text projection, and inherently visual blocks (a rendered animation, an interactive widget) degrade to their description plus a pointer instead of vanishing silently. Non-ASCII titles survive the download — a Chinese or accented book name used to fail the request outright.
Know what a book costs before you approve it — and don't pay twice
Confirming a spine kicks off dozens of LLM calls and many minutes of work. The editor now shows a live per-chapter estimate derived from the Section Architect's own templates, so the number cannot drift from what actually gets generated, and it stays accurate while you add, remove, or retype chapters.
Two scheduling defects that were invisible in production are fixed: the same page could be compiled twice concurrently — billed twice, one result silently discarded — and a book would grind on through a provider outage until every chapter was half-generated. The source sweep is bounded too. Ungated, eight knowledge bases against a dozen queries fired roughly a hundred concurrent provider calls; the cap is now allocated round-robin, so it isn't spent entirely on the first few bases.
Connected knowledge bases stop silently contributing nothing
A connected KB — an Obsidian vault, a remote LightRAG or IMA library, a subagent CLI — has no local index, so rag_search returns nothing for it. Book generation swept them anyway, and the empty result was indistinguishable from a source with no relevant content: attach your vault, and nothing ever told you it contributed zero. Retrieval now tells indexed bases and connected pointers apart and routes each accordingly.
Three places to start, drawn from what you were actually doing
The home composer now offers three starter suggestions built from your memory — the L3 synthesis plus your last several activities across surfaces. They propose a specific thing to understand rather than a way to revisit history, and they're sized per language so they don't overflow. A new recall memory read backs them, and snapshot stamps are read without loading entity content.
Mastery paths follow the conversation
A path outlives any one chat, and a chat may work several paths in sequence. Moving a live turn between paths now happens in one place — lease, session preference, and live turn changed together, release before acquire — so a rejected handoff leaves the learner where they were instead of half-moved. Path state and turn recovery are hardened, teaching content stays visible, qualitative reviews are scheduled, and deleting a session cleans up its path.
Tencent IMA credentials move up to the account
IMA credentials used to live only on each KB. They now also resolve at the account level from Knowledge → the IMA engine page, the way PageIndex's key does, with the per-KB pair still winning when it is complete. Existing bindings keep working untouched, and rotating the account key updates every KB that relies on it. Read-only IMA connections and source-text retrieval are fixed alongside.
Assorted
- EPUB is a supported knowledge-base format, and Docling can parse remotely against a Docling Serve server (
mode=remote) with no local install or model downloads. - Generated code is parsed before it reaches you. A truncated snippet used to ship with nothing marking it unchecked.
- The Overview chapter stays singular. Re-confirming a spine grew a second one each time, and the page was rebuilt wholesale — the one path that bypassed
edited_by_userprotection. Its wording is also no longer hard-coded to Chinese-or-English while the picker offers eleven languages. - Quiz blocks read the question pipeline directly instead of a legacy facade that discarded progress from the slowest block.
- Knowledge: large uploads stream through the web layer, document parsing runs off the event loop, docstore probes are cached and deduped in
kb list, and GraphRAG model/embedding compatibility is hardened. - Partners: data is isolated per user, an invalid channel config no longer takes the others down, the tool list follows the global chat toggles, and a partner reconnects after its tab goes inactive.
- Models & providers: the model catalog and provider transport recover instead of staying broken, catalog secrets are protected in the settings API, GPT-5 agentic tools use the Responses API, provider stream-failure boundaries survive, and completed turn message IDs persist across reconnects.
- Runtime: the active virtualenv is visible inside the bwrap sandbox, invalid homes and placeholder OpenAI keys are rejected, Windows
.cmdshims resolve before a CLI is spawned, console exception tracebacks are preserved, andmcpinstalls by default so the PageIndex MCP server connects.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- Books compiled before this release have no recorded progress. Completion scores and weak chapters start accumulating from your next read; nothing is backfilled and no existing page is rewritten.
- A book that was mid-compile when you upgrade should be resumed, not left. The event stream is now owned per book, so reopening it reattaches to the live compile rather than showing a frozen page.
settings/ima.jsonis new and optional. Existing per-KB IMA credentials keep working exactly as before — the account-level pair only fills in for KBs that don't carry their own.
Full Changelog: v1.5.12...v1.5.13
v1.5.12
DeepTutor v1.5.12 Release Notes
Release Date: 2026.08.13
v1.5.11 was about what happens while an answer is being written. This one is about where its material comes from. The web-search layer was rebuilt onto a single table — which is how three defects that had been quietly costing you results got found — and six providers were added on top of it, most of them reachable from mainland China. A sixth document-parsing engine lands, and an MCP server whose credential you rotated now reconnects instead of answering with the one explanation that rules out the real cause. Drop-in — no migrations, nothing to re-index.
What's New
Web search rebuilt, with six new providers
The provider list had been copied into seven places — the backend registry, runtime config, the settings router, the CLI wizard, the frontend catalog, i18n, and the tests — and they had drifted apart; the frontend marked Serper deprecated while the backend still offered it. All seven now derive from one SEARCH_PROVIDERS spec table. Consolidating it surfaced three silent defects: Serper's num never reached the API, so max_results did nothing; Serper dropped its proxy configuration; and Jina had no result-count limit at all.
Six providers join on that table — Doubao, Bocha, Zhipu, Firecrawl, Baidu Qianfan, and Aliyun IQS. They appear in Settings → Catalog → Search with their credential fields derived from the spec, and deeptutor init offers them too. Doubao returns an answer rather than only links; Firecrawl adds full-page extraction. Baidu returns as qianfan on the official Qianfan API — the old baidu key stays deprecated rather than revived.
A search failure no longer fails the turn
A rate limit, timeout, or unreachable host used to end the whole turn. The request now walks your other credentialed search profiles and finally DuckDuckGo, recording what it tried under a top-level search_fallback key so the trace shows the downgrade instead of hiding it. The proxy travels with the request, so a fallback still works from behind one.
A missing API key is treated as the opposite kind of problem and still fails immediately for the six new providers. Five of them are China-hosted, and DuckDuckGo is unreachable on those networks — silently downgrading would turn "you did not configure a key" into an unexplained timeout somewhere else.
LiteParse joins the parsing engines
A sixth document-parsing engine, selectable in Settings → Knowledge Base alongside Text-only, MinerU, Docling, markitdown, and PyMuPDF4LLM. Lightweight, with no local model downloads. Install with pip install deeptutor[parse-liteparse].
MCP servers reconnect when a credential changes
Rotating a stored credential left the live connection untouched: the stored config still held a ${secret:...} reference, so its fingerprint was byte-identical and the reload diff concluded nothing had changed. Connections are now fingerprinted against the resolved config, so the account reconnects with the key it just set. Separately, a transport failure during a tool call — most often auth — was raised inside the SDK's own task group and left the caller waiting for the full tool timeout, reporting the one cause that had been ruled out. The connection is now watched during the call, so the real error is what the model and the user are told, in the second it took. URLs are redacted from those messages, since a server can carry its credential in a query parameter.
CodeBuddy and OrcaRouter
Two more model providers: CodeBuddy/WorkBuddy, which validates CodeBuddy SDK auth and starts a browser sign-in when needed, and OrcaRouter, a named OpenAI-compatible gateway available for both chat and embeddings.
Codex keeps its reasoning effort, and can see
A managed reasoning-effort override now persists instead of resetting between sessions, and OpenAI Codex is marked vision-capable, so images reach it rather than being dropped. Gemini 3 and 2.5 Pro, which reject reasoning_effort="none", now default to minimal — the lowest level those families accept.
Assorted
- RAG citations read the structured payload. Citation extraction prefers
ToolResult.metadata["sources"]over parsing the textual answer, which usually yielded no sources at all; parsing remains the fallback for older traces. - Responses tool calls no longer cross-dispatch. A missing output-item id was filled with a literal
fc_0and registered as an identity, so the next call that omitted its id resolved to the previous call's buffer and ran under its name and arguments. The placeholder is no longer an alias. - Mastery paths are independent of chat sessions, so opening a path no longer disturbs the session you were in.
- Chat history settles at the bottom after loading, instead of landing mid-scroll.
- Assistant output renders
**bold**correctly when the model emits stray spacing inside the markers. - The obsolete Typer extra is gone from both packaging manifests.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
GET /api/v1/settings/provider-choiceschanged shape. Each entry in thesearcharray gainedrequires_api_key,requires_base_url,soft_fallback, andstatus, and the array now also returnsexa,baidu, andopenrouterwithstatus: "deprecated"— deliberately, so a client can tell a stale configuration from a misspelled name. DeepTutor's own UI filters onstatus; any other consumer that renders the array directly will show three deprecated options until it does the same.get_providers_info()entries gainedrequires_base_url, which reachesdeeptutor config showand the/api/v1/systemdiagnostics.- An unrecognized search provider name now falls back to DuckDuckGo rather than raising. Previously only
exa,baidu, andopenrouterwere downgraded and any other unknown name raised. An explicitnonestill raises, because that means search is switched off. get_available_providers()returns more than it used to. It previously instantiated each provider and swallowed the credential error, so it effectively reported only the active provider plus the two that need no key; it now reports every profile whose credentials are complete.- Brave is labelled
Brave, notBrave Search, in CLI diagnostics — the UI already showed the shorter name.
Full Changelog: v1.5.11...v1.5.12
v1.5.11
DeepTutor v1.5.11 Release Notes
Release Date: 2026.08.10
v1.5.10 was about the paths underneath a request holding up; this one is about what happens while the answer is still being written. Prose that shared a round with a tool call used to vanish into the trace, a truncated generation was read as the model deciding it had finished, and local indexing sat on the event loop everything else needed. The settings strip also stops restating what the Models page already tells you and starts showing the one number nothing else did: what DeepTutor is actually costing in memory. Drop-in — no migrations, nothing to re-index.
What's New
Text around a tool call is no longer lost
DeepSeek's Anthropic-compatible endpoint interleaves user-facing prose and DSML tool-call markup in a single content stream. The old handling was all-or-nothing: once a round's content channel showed DSML markup, the rest of that round was diverted to the thinking channel, so any explanation the model wrote around its call disappeared from the answer. A streaming filter now removes only the DSML envelope and the invoke blocks, incrementally — text before, between, and after a call streams and persists as part of the reply. An incomplete call is released verbatim on flush rather than swallowed, and a 512-character ceiling means a stray < in ordinary prose cannot make the live stream wait forever for a closing bracket.
DSML array and object arguments decode correctly
DeepSeek marks essentially every DSML parameter string="true", JSON arrays and objects included, so a container argument arrived at the tool as a string of JSON. Parameter decoding now consults the declared tool schema: a parameter the schema types as array or object is parsed when the payload actually matches, and the provider's string contract still wins everywhere else. The schema catalog is kept even when a provider rejects native tools and the loop falls back to DSML, which is exactly when the parser needs it.
A truncated generation asks for a continuation
A length finish is an incomplete generation, not the model choosing to stop — but it was treated as a finish, so the reply ended mid-sentence. The loop now keeps the visible prefix and asks for a continuation, and the assembled answer carries the prefix through to persistence and the SDK. The round budget was restructured to make this safe: exploration rounds, then a small bounded settlement window, then a single tool-less hard finish, so repeated truncation and malformed tool cycles share one absolute upper bound of exploration + 4.
Live memory usage in Settings
The settings status strip carried Backend, LLM, Embedding, and Search — three of which restated what the Models and Chat pages already own. It now carries the two runtime facts no settings page shows: whether the backend is up, and resident memory across the whole DeepTutor process tree — backend, the Next.js server, and whatever sandboxes and subagent CLIs are alive — with a per-process split inline and a pressure dot that turns amber past two thirds of the limit. It is served by a new GET /api/v1/system/memory; psutil reads the tree and is imported lazily with a /proc fallback, so a missing wheel costs the strip and not the app. Non-admins and platforms where the tree cannot be read get {available: false} and no strip.
Local LightRAG indexing stops stalling everything else
RAG-Anything's local storage backends do synchronous graph merging and JSON serialization from inside async methods, so indexing a document on the service event loop froze unrelated API and LLM work for its duration. Indexing now runs on a worker thread's own event loop, with a narrow bridge that forwards LLM, vision, and embedding calls back to the request's loop, carrying a copy of the caller's context so request-local model and user configuration stays visible. Remote LightRAG servers are unaffected.
Mastery works from the persisted question, not the model's retelling
A posed question now projects into a small public contract — prompt, type, and the option label/body map — shared by registration, presentation, and grading, with the expected answer staying server-side. ask_user renders that persisted state instead of display data the model re-authored, so the choices a learner sees are the choices that will be graded. Registration validates the quiz shape rather than silently accepting a contradictory one: a missing type is inferred from the payload, short/open questions reject options, and a choice question must carry real option bodies instead of bare A/B/C labels.
Assorted
- Connected knowledge bases answer honestly about local files. A linked external resource has no DeepTutor-managed
raw/directory; listing its files returns an empty collection instead of inventing one, and endpoints that require a local file return an explicit409. - Generated output artifacts are served request-scoped. A new
/api/outputsendpoint resolves and authorizes a path in one operation and fails closed with a404when there is no request user, so an auth-context regression cannot hand an administrator's artifact to an ordinary request. - Visualize iframes shrink as well as grow — the height bridge measures body content instead of a root height the host had already stretched, and coalesces reports into one animation frame.
- The chat page's model list is fetched once. Option loading moved into a hook with single-flight de-duplication, so concurrent mounts and a tab-focus refresh no longer stack up identical requests.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
psutilis a new dependency and installs with the upgrade. It is imported lazily behind a/procfallback — if your environment cannot provide it, the memory strip stays hidden and nothing else changes.
Full Changelog: v1.5.10...v1.5.11
v1.5.10
DeepTutor v1.5.10 Release Notes
Release Date: 2026.08.07
v1.5.9 was about providers; this one is about the paths underneath them holding up — a batch upload that stalled every other request, a knowledge-base list that rescanned every index, a tool call dispatched with no arguments and retried until the budget ran out. Two things that were quietly missing also land: an account can sign in to its own Codex, and the model's reply language is finally its own setting. Drop-in — no migrations, nothing to re-index.
What's New
Every account can sign in to its own Codex
A Codex token authorizes one person's ChatGPT plan, so the profile was correctly never grantable — but the OAuth endpoints were admin-only too, which left ordinary users with no route to Codex at all. They can now sign in for themselves: the card sits under Models → LLM, and the profile it creates is written to that user's own catalog (data/users/<uid>/settings/model_catalog.json), never the shared admin one. Grants resolve personal rows alongside assigned ones in a single place, so the option list, the capability gate, and selection validation cannot disagree — and an administrator inspecting someone's grants still sees only what they assigned. Partners are refused: a partner is a synthetic user, and admitting one would mean acting on its owner's login.
Model output language is its own setting
Settings → Appearance now carries two independent toggles. Interface language covers navigation, settings, and status text; Model output language sets the default language of chat and capability replies, which until now you could only steer from the prompt. An interface.json written before the split inherits its single language into both fields, so nothing changes until you pick. The pre-session bootstrap that reads these is also public now — /login and /register stayed English before, because the anonymous read hit the gated router and 401'd; it returns theme and language only, and a choice made in this browser still wins.
A tool call with no arguments is rejected, not run
Under a large tool surface and long history, providers intermittently emit a native function call with empty arguments even where the schema marks fields required. The call was dispatched anyway, so the tool did the rejecting — exec raised "requires a non-empty command", write_note answered "Unknown mode ''" — and neither reads as "an argument is missing", so the model re-emitted the same malformed call until the loop budget ran out. A guard in front of dispatch now names the missing arguments and their accepted values (enums included), turning that into one self-correcting round. The rules stay narrow: a parameter with a default is not required, empty collections are left alone, server-injected kwargs count as supplied.
Uploads and knowledge-base listing stop holding the loop
Batch-uploading files stalled every other request — chat WebSockets included — for the whole duration of the batch: the chunked write, zip extraction, and per-file PocketBase upload all ran inline in an async context, and the follow-up staging pass sat in an async def background task, which Starlette awaits on the loop rather than in its threadpool. Both now run in a worker thread. Separately, building one KB list response reloaded configuration and rescanned every index per item; it now reuses one reconciled snapshot — a 58-KB read-only listing finishes in ~2s instead of timing out the proxy.
Deep Research reads a connected Obsidian vault directly
When the selected knowledge base is a linked Obsidian vault, the research loop no longer auto-mounts rag against an index that does not exist. It mounts the three read-only vault tools instead — obsidian_search / obsidian_read / obsidian_list — injects the vault path server-side (overwriting any model-supplied value), treats their results as citable so evidence reaches the citation manager and the final report, and swaps in an Obsidian-specific system note. Indexed KBs and no-KB runs are untouched.
The server-side backend address is IPv4 loopback everywhere
localhost resolves to ::1 first on a dual-stack host while uvicorn binds 0.0.0.0 — IPv4 only — so every rewritten /api/* request failed to connect. The launcher was fixed first; the Docker entrypoint's render_environment and web/proxy.ts's last-resort default kept the hostname and have now been brought back in sync.
Assorted
- Escape stops a streaming Partner reply, and only that: the listener bails while any dialog-role overlay is mounted, so dismissing a modal or picker no longer kills the answer behind it.
- Vision capability overrides now match
claude-by vendor prefix (nothing ever began withclaude-4, so that key matched nothing), and recognize Kimi K3 and Qwen3.8-Max. - Codex context-window metadata survives the catalog round-trip instead of being dropped.
- Editing a message no longer races the refresh that follows it, so the optimistic bubble stops flickering back.
- The
deeptutor-cliwheel ships the agents' prompt YAML again — it had none, so prompt loading failed and the empty result was cached for the life of the process. Misses are no longer cached either, so a resource that appears after startup is picked up on retry.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- Model output language defaults to your existing interface language. The split is inherited, not reset — set it explicitly under Settings → Appearance if you want replies in a different language than the UI.
- Non-admin Codex sign-in is on by default. Each account's profile stays in its own catalog and is invisible to the administrator and to every other user; nothing an ordinary user signs into can appear in the shared model list or the grant editor.
Full Changelog: v1.5.9...v1.5.10
v1.5.9
DeepTutor v1.5.9 Release Notes
Release Date: 2026.08.04
Where v1.5.8 was about memory, this one is about providers — Gemini's native Embedding 2 endpoint, a per-model reasoning-effort control, a Novita AI gateway — and about the paths that were quietly wrong underneath them: queries embedded as if they were documents, none turning thinking on, a capability crash arriving as a blank message. Drop-in for most installs, but docker-compose.ghcr.yml users have a one-time data migration to run before their next up.
What's New
Gemini Embedding 2 over the native API
A Gemini embedding profile now defaults to Google's native …/models/<model>:batchEmbedContents endpoint with gemini-embedding-2, authenticated with x-goog-api-key, and the endpoint follows the model you pick — including through a gateway prefix. Existing gemini-embedding-001 profiles deliberately stay on the OpenAI-compatible path: the native route sends a taskType and L2-normalizes what comes back, so moving them would change their document vectors and silently invalidate the index built from them. Point base_url at the native URL yourself to opt any model in.
Queries stop being embedded as documents
Retrieval embeddings now carry their role. LlamaIndex tags queries search_query and passages search_document, LightRAG maps its own context hint the same way, and adapters opt in explicitly (SUPPORTS_INPUT_TYPE) so a backend that never sent a role keeps not sending one. Only Cohere — which requires the field and already defaulted to search_document — changes today: document vectors are untouched, queries finally get the right role. No re-index.
A reasoning-effort selector per model
Each LLM model in the catalog gets an optional default reasoning depth next to its context window, offered only where the provider actually supports one and labelled in that provider's own vocabulary (minimal/low/medium/high/xhigh for OpenAI-style families, adaptive where that is the only on-mode, a plain on/off for the rest). Provider default (Auto) leaves the choice to the provider. Two Anthropic bugs fell out of wiring it up: none was turning thinking on with a 4096-token budget, and a stored adaptive sent to an older family drew a 400 instead of the default budget.
Novita AI, and MiniMax on the right platform
Novita AI joins the LLM gateways (novita, https://api.novita.ai/openai). MiniMax runs two separate platforms whose keys are not interchangeable, and the default now points at the global one (api.minimax.io) instead of the mainland-China one; minimax-m3 and minimax-m2.7 context windows are recorded, and detection also reads a context_size field.
Compose deployments keep all of data/
docker-compose.ghcr.yml bind-mounted only data/user, data/memory and data/knowledge_bases, so data/system (the JWT signing secret, accounts, grants, per-owner Codex tokens), data/users, data/partners and data/cli-apps lived in the container's writable layer and were discarded on every recreate. It now mounts the whole tree, matching the other Compose files. Named-volume and source-build deployments were never affected.
Codex sign-in inside a local container
Default local Docker and Podman deployments put the browser and the server on separate loopback networks, so the OAuth callback never arrived. There is now a temporary compose.codex-oauth.yaml overlay (and the equivalent docker/podman commands) that publishes ports 1455/1457 only while you sign in — with teardown, so those ports go back to your other Codex clients. See CONTAINERIZATION.md.
Failures that used to arrive as nothing
A capability that raised — or a request for one that does not exist — ended the stream without a verdict, and the turn was persisted as completed with an empty answer. The error is now marked terminal and carried through a DONE event, so the turn is stored as failed with its message and the UI shows what went wrong instead of an empty bubble.
Assorted
- The interface language you saved is honored on first load. It lived in the backend's
uisettings, which only the Settings route ever read, so every other page started in English until you re-picked it in that browser. A local choice still wins. - Visualize no longer aborts when the analysis model invents a
render_typeorvisual_genreoutside the enum — the off-enum value is dropped and the field falls back to its default. - Credential-like query parameters (
key,api_key,token,access_token) are redacted in every endpoint the wizard, the client, and error messages print.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, and nothing to re-index.
docker-compose.ghcr.ymlusers: copy your state out before the nextup -d. The new whole-tree mount means empty host directories would shadow what is in the container's writable layer, regenerating the auth secret (logging everyone out) and starting with no non-admin accounts. The onedocker cploop is in CONTAINERIZATION.md → One-time migration.- MiniMax on the mainland-China platform: the default base URL is now the global endpoint. Set
base_urltohttps://api.minimaxi.com/v1(orhttps://api.minimaxi.com/anthropic) and use a China-platform key — keys are issued per platform. Saved profiles keep whatever URL they already have. - Gemini embeddings: existing
gemini-embedding-001profiles are left where they are on purpose. New profiles andgemini-embedding-2use the native endpoint.
Full Changelog: v1.5.8...v1.5.9
v1.5.8
DeepTutor v1.5.8 Release Notes
Release Date: 2026.08.02
A maintenance release with one target: memory that stays put instead of climbing. A v1.5.7 source deployment grew past 14 GB before V8 gave up mid-session, and the causes turned out to be independent — a frontend dev server whose heap ceiling never reached the process holding the memory, unbounded in-process caches, and a fresh HTTP client built for every turn. Drop-in — no migrations, but a source checkout now starts differently.
What's New
The dev server was never actually capped
next dev renders in a worker it spawns itself, and V8 flags in argv are not inherited by child processes — so the long-standing node --max-old-space-size=4096 …/next dev never reached the process that holds the memory. Finding no ceiling in NODE_OPTIONS, Next substituted 50% of total RAM: a 12 GB heap on a 24 GB machine, which V8 genuinely tried to fill before dying in Ineffective mark-compacts near heap limit after 63 seconds inside a single mark-compact. web/scripts/dev.mjs now passes the ceiling through NODE_OPTIONS, which the worker does inherit and Next honors — clamped to 4 GB, and sized against the cgroup limit inside a container rather than the host's RAM.
Source installs serve a production build
deeptutor start from a checkout now builds the frontend once into .next-deeptutor and serves the standalone output, reusing that build until an input actually changes (a fingerprint over the web/ tree, the version file, and the build-time public settings). Hot reload moves to deeptutor start --dev, which keeps using .next — separate directories, so neither mode invalidates the other's output while it is running. A long-running deployment no longer pays for a dev compiler's caches.
Bounded LLM clients instead of one per turn
The chat, research and question pipelines each built a fresh OpenAI-compatible client — and with it a new HTTP connection pool — on every turn. Both the agentic client and the services-layer provider now come from a small event-loop-local LRU pool of 2, keyed on binding, model, credentials and base URL, closed on shutdown and retired when settings change. Only the selected backend's SDK is imported.
Caches with a ceiling
Knowledge-task log streams now cap events and bytes per task, expire an hour after a task ends, and leave a small completion tombstone so a late subscriber still learns the task finished. The completed-task table keeps 256 entries. The LlamaIndex index cache drops from 8 entries to 2 and evicts anything idle for 10 minutes. A book runtime is released once its queue drains instead of living for the process.
Reclaiming what was already freed
After a turn, a finished book job, or a completed knowledge-base task, DeepTutor collects cycles and — on Linux — calls malloc_trim so glibc hands free heap pages back instead of holding arenas that make RSS look permanently pinned. Containers and deeptutor start also set MALLOC_ARENA_MAX=2 and a lower trim threshold, both overridable by an operator who has tuned their own.
A keep-alive race that surfaced as 500s
web/proxy.ts forwards to the backend over Node's http.globalAgent, which reaps idle sockets on a 5s timer — identical to uvicorn's default. Both ends armed the same timer on the same socket and raced to close it; when the server's FIN landed on a socket the pool was simultaneously handing to a new request, that request died with ECONNRESET and reached the UI as "Failed to load sessions". Every uvicorn launch now sets --timeout-keep-alive 300, leaving the client as the only side that retires an idle connection.
Assorted
deeptutor.agents,deeptutor.core.agentic, the provider-core package, the document extractor's parser libraries (PyMuPDF, pypdf, python-docx, openpyxl, python-pptx) and the LLM error-mapping rules all resolve on first use rather than at import time.python -m deeptutor.api.run_serverno longer starts with--reload; exportDEEPTUTOR_DEV_RELOAD=1for it. Reload runs a supervisor plus a worker and retains file-watcher state, which is development machinery.deeptutor serve --reloadis unchanged.
Upgrade Notes
Drop-in from v1.5.7: pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema or config changes.
- Source checkouts build before they serve. The first
deeptutor startafter upgrading runsnpm run buildand takes correspondingly longer; later launches reuse it until the source changes. Usedeeptutor start --devfor the previous hot-reload loop. The newweb/.next-deeptutor/build directory is git-ignored. python -m deeptutor.api.run_serverno longer reloads by default. ExportDEEPTUTOR_DEV_RELOAD=1to restore it.
Full Changelog: v1.5.7...v1.5.8
v1.5.7
DeepTutor v1.5.7 Release Notes
Release Date: 2026.07.31
A follow-up to v1.5.6 that opens the tutor up to tools DeepTutor never shipped: a curated store of hosted MCP services each person installs for themselves, and 101 command-line apps from the CLI-Anything catalog the chat agent can call directly. Both arrive with the authorisation and isolation a shared deployment actually needs — installing is a privileged action, running is not, and nothing that authorises a person stays anywhere the code sandbox can read. Drop-in — no migrations.
What's New
An MCP store you install from yourself
Learning Space → MCP Services is a store of 45 curated hosted servers you add for yourself in one click, plus any remote server you configure by URL. Every catalog endpoint was verified by sending it a real MCP initialize, and no logo CDN is contacted — rendering the store would otherwise ship your installed-service list to a third party on every paint. Servers that answer 401 (Notion, Linear, Sentry, Asana, monday, Canva, Prisma) sign in over OAuth 2.1 with PKCE and dynamic client registration; a background reconnect never blocks on a consent screen, it just reports needs_auth so the UI can offer a Connect button. Static keys are stored by reference and resolved in memory at connect time, so a token never lands in a config file that gets displayed, returned by an API, or copied into a log. stdio entries are refused for self-service: a command is host execution as the app user.
MCP is no longer configured under Settings — /settings/mcp redirects to the store — and the tool pickers now fold hundreds of tools into one row per service.
CLI apps the tutor can run
Learning Space → CLI Apps catalogs 101 tools snapshotted from the two CLI-Anything registries, and the chat agent calls an installed one directly, with that app's own usage guide loaded only when it is needed. Installing is administrator-only, because pip install runs the package's own setup.py in the application container — no amount of care makes that self-service. The registry's install_cmd is never executed: it is parsed into a plan whose argv DeepTutor builds (the registries already contain curl … | bash), npm install scripts are forced off, and an update that fails to build rolls the working environment back. 66 first-party harness entries are pinned to one reviewed CLI-Anything commit; anything pointing at a different upstream is labelled third-party so installing it is an informed choice.
Apps land in data/cli-apps, one environment each, mounted read-only into the sandbox runner. That split is the point: an escaped command cannot overwrite an app's entry point and have every later turn, for every account, run the replacement.
Credentials the sandbox cannot reach
Everything that authorises a person moved to data/system — the one branch of the data tree that is never bind-mounted into the runner. Codex OAuth tokens now live in data/system/user-secrets/<owner>/private/openai-codex/, per-user MCP configs in data/system/user-mcp/<owner>.json, and MCP tokens and API keys beside them. Existing Codex logins relocate themselves on first use, and the move refuses to run through a symlinked directory rather than risk relocating another account's store.
This closes a real exposure rather than a theoretical one: the per-user mount the runner receives is a whole user root, so a credential kept under data/users/<uid>/ was readable — and writable — from every other account's sandboxed shell.
Plugged-in tools, governed per person
Grants gain a cli_apps whitelist with the same deny-by-default posture MCP tools already had: an installed app is third-party code running in the sandbox, and a deployment installing one is not the same decision as every account being allowed to run it. Behind that, a per-turn registry view keeps one user's provider tools out of the process-global registry entirely — two tenants whose servers share a tool name can no longer clobber each other — and refuses an unauthorised tool at execute, not just in the manifest, because a text-protocol fallback can synthesise a name that was never offered.
A composer that tells you how full the window is
A chip in the composer reports what this turn actually put in the model's context window and what is filling it, measured from the already-assembled prompt blocks, tool schemas, and message list rather than re-derived — so the readout cannot drift from what was sent. Long conversations also get a rail of ticks in the transcript's left gutter, one per question you asked, for jumping back through the history.
DeepTutor on a phone
The workspace and utility layouts now collapse to a single-column shell with a slide-over sidebar on small screens instead of rendering a desktop three-pane layout off the edge of the viewport.
Assorted fixes
- The composer keeps focus after you send, and gets it back when you switch tabs away and return (#720).
- Released container images carry provenance and SBOM attestations (#722).
gitis installed in the application image and the Node runtime in the runner image, both required by the app installer and by npm-packaged apps' console scripts.
Upgrade Notes
Drop-in from v1.5.6: pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema or config changes, and an existing Codex login carries over — its credentials relocate themselves the first time they are resolved.
- docker-compose deployments should re-up, not just restart: the runner service gains a read-only
./data/cli-appsmount, and without it installed CLI apps are catalogued but cannot execute. - MCP servers configured under Settings stay where they are — the deployment-wide
mcp.jsonis unchanged and still admin-owned. The new store is a parallel, per-account one;/settings/mcpnow redirects to it. - Installing a CLI app is administrator-only, and assigning it is a second step. A non-admin account sees no installed app until a grant names it, the same way MCP tools and skills already work.
Full Changelog: v1.5.6...v1.5.7