Repository navigation
Releases
1.7.0 — one door per subject, honest errors, a self-aware agent
One door per subject, honest errors, and an agent that knows its own setup.
Changed
- One place to connect anything.
+ Connecter un MCPleft the Données tab: Connexions is the only door to a cloud storage, an MCP server or an IDE. Données is the local maintenance room (backup, skills, installed tools, repair). - The header switches a pair. The menu lists local models and every keyed API provider (💻 / ☁️) and sends
{provider, model}— choosing an API model no longer leaves the provider behind, and an Ollama id can no longer be sent to an API. - No "Utiliser" buttons. The row selects the model; Providers merged into Fournisseurs.
- SYSTEM_MAP.md gained an Architecture section (UH cockpit, UHB backend on 8101, Ollama on 11434), with one line in the brief the model reads every turn.
- The prompt names the doors: a UI question is answered with a tab and
/?panel=conn, never with a file edit; a "no session" tool error says to open the CONSOLE.
Fixed
- Ollama failures are sentences (
non chargé : VRAM ou délai), an uninstalled model is a 400 that says so, a read-only config.toml is a 409 naming the cause. POST /api/modelrefuses a contradictory pair instead of caching it.- The dev environment no longer shadows the repository: an editable install plus
tests/test_dev_env.py(the gate could test code that was not being pushed).
Docs
- New
docs/ARCHITECTURE.mdwith Mermaid diagrams;INSTALL.mdgains "Where to connect things" and the pip ceilings.
Full changelog: CHANGELOG.md
1.6.1 — Python 3.10 imports again
1.6.0 does not import on Python 3.10 — fixed
uh/mcp_cloud.py imported tomllib directly. tomllib joined the standard
library in 3.11; UH supports 3.10 to 3.13. The 3.10 jobs of the CI matrix
caught it on the release commit itself: ModuleNotFoundError: No module named 'tomllib' on ubuntu, macOS and windows, at collection time.
The module now goes through uh._compat, which falls back to tomli (already a
conditional dependency) exactly like config.py, keys.py and the catalogue
reader.
Verified with the 3.10 interpreter on the release machine:
python3.10 -c "import uh.cli, uh.web.app, uh.mcp_cloud, uh.fixes"
→ 3.10: tous les modules importent
The wheel published as 1.6.0 is unusable on 3.10; install 1.6.1 there. Nothing
else changed: 1180 tests, 1 skipped, ruff and mypy clean.
1.6.0 — No Ollama required, and it can patch itself
No Ollama required — and it can patch itself.
It runs on Hugging Face
uh/providers/huggingface.py speaks POST /v1/chat/completions and picks its base
URL in this order: UH_HUGGINGFACE_BASE_URL, a local TGI on 127.0.0.1:8080
(probed with a socket — no token, no account), then the router
https://router.huggingface.co/v1 with a token. The legacy
api-inference.huggingface.co per-task API is never used: it has no chat roles, and
a chat loop pointed at it fails on the first turn.
Proven end to end: uh chat --provider huggingface answering through a TGI-shaped
server, blocking and streaming, with Ollama nowhere in the chain.
It proposes a patch on its own code — and proves it first
uh fix asks for a unified diff and then refuses to trust it: the answer must
start with diff --git and must pass git apply --check. --apply works on a
fix/auto-<stamp> branch and reverts the patch when a --check command fails.
Nothing is ever committed or pushed by the harness.
In the cockpit: Données → Réparer UH, describe what is broken, and the diff
opens in a modal with Appliquer sur une branche / Rejeter
(capture).
Cloud storage in two clicks
AWS S3 (put_object, 7 tools) or Google Drive (read-only) from the Data panel, plus
a self-repair filesystem template that can only read the checkout. Secrets go to
mcp-secrets.toml (0600) and are merged into the child's environment at spawn —
never into config.toml, the file people paste into issues.
Fixed on the way
The Data panel threw data is not defined whenever /api/tools/status succeeded
(the MCP block read data.mcp outside the try that declared it), so the whole
panel was replaced by an error line. Also: uh fix could not build a configured
provider, the file list read /__init__.py, and a diff written from Python was
translated to CRLF — a patch whose lines end in \r does not apply.
1180 tests, 1 skipped; CI green on ubuntu, macOS and Windows across Python 3.10 to
3.13.
Full changelog: CHANGELOG.md
1.5.1 — Skills: merge the twins, keep the originals
Skills that say the same thing, merged without eating them
Asked for "compress the skills if they look alike". Detecting similarity is
mechanical; merging prose is a judgement call — so the tool is deliberately
conservative.
uh skillslists what is installed (yours and the built-in ones).uh skills --compressgroups user skills whose titles or descriptions are
close (difflib, 0.82 by default) and merges each group into its longest
member, appending the others under aMerged from <name>heading.- It is a dry run unless
--write, and the absorbed files move to
skills/.mergedinstead of being deleted: a bad merge is onemvaway from
undone. - Two skills with empty descriptions are never grouped: difflib calls them
"identical", which would merge every undocumented skill into one.
Also fixed: the 11 MB MCP test borrowed a 15 s timeout to read a line through a
pipe, measured at ~20 s on a loaded machine. The test is about refused, not
buffered, so the budget is 60 s with the reason written next to it.
1154 tests, 1 skipped; CI green on ubuntu, macOS and Windows across Python 3.10 to
3.13.
Full changelog: CHANGELOG.md
1.5.0 — The harness knows where it lives
The harness stops guessing about itself.
Asked "where are my dreams stored?", the agent used to invent a path - and then
could not be asked to build on it. The harness knew; the agent did not.
uh map — the map is generated, not written
Every path comes from the function that owns it (uh_home, default_store_path,
dream_store, skills), so the answer cannot drift from the machine the way a
hand-written document does. uh map --write drops $UH_HOME/SYSTEM_MAP.md, and a
compact nine-line version rides on the system prompt of every turn — before the
skill's instructions, because the task must stay the closest thing to the user
message.
outputs_dir — somewhere for a realisation to land
config.toml, default $UH_HOME/outputs; on a machine that has a Desktop, that is
where it should point. "Exaucer" now delivers its plan there as well as keeping
the record in dreams/actions: a plan the user cannot find is a plan that never
happened.
The night pass reads the chats
A new sessions source: the last 20 session titles and their message counts —
titles, not bodies. It runs unattended at 03:00, and a transcript is the most
private file in the harness.
Measured with a real lfm3:latest turn: asked where its dreams and chats live, it
answered with C:\Users\searc\.uh\uh.db and the dream folders — which it could not
name an hour earlier.
1150 tests, 1 skipped; CI green on ubuntu, macOS and Windows across Python 3.10 to
3.13.
Full changelog: CHANGELOG.md
1.4.4 — An upgrade that finishes
An upgrade that finishes.
Reported from a real laptop: pip answered WinError 32 because a uh web
from earlier still held uh.exe — and the user had to open PowerShell to
taskkill it. That is not an install path, it is a workaround.
uh upgrade does the whole pass
- stops the other UH processes (matched on
-m uh.clianduh.exe, never on
"python.exe", which is somebody else's program); - purges pip's cache and installs with
--force-reinstall --no-cache-dir, so a
stale wheel or a half-replaced install cannot survive the upgrade; - refreshes the Desktop shortcut, which is a file we generate and used to keep
the old launcher without--app; - and, on Windows, when it is
uh.exe— the file pip has to replace — it hands
the install to a detached console and leaves first, because Windows will not let
a running executable be overwritten.
--no-clean restores the old plain pip install --upgrade; --dry-run touches
nothing at all (it no longer purges the cache).
The launcher stops a previous uh web
That process owns the port and the executable, and a second start used to die on
bind. The generated .ps1 now stops it first, then opens web --app.
DeepSeek is listed before it holds a key
The DeepSeek routes existed while the catalogue did not name the provider, so the
list you add a key from had no row for it: the provider appeared only once a key
already existed. A catalogue entry fixes the order.
1141 tests, 1 skipped on Windows and on Linux; CI green on ubuntu, macOS and
Windows across Python 3.10 to 3.13.
Full changelog: CHANGELOG.md
1.4.3 — Fewer choices, clearer status
Fewer choices, clearer status.
Feedback after the first hour with the cockpit: five models in the header when
one was wanted, and a paragraph to say the second brain was connected.
One model in the header
The selector shows the current model; a ⋯ button reveals the rest, and the
choice is remembered per browser. Which one is current comes from default_model
in config.toml — the code names no model, because a hardcoded name is right on
one machine and wrong on every other.
The Second brain says it once
Connected is one line: green dot, v0.1.2, the mode. Harness, providers and modes
are behind "Détails", and the long explanation now only appears when something is
actually wrong — the only moment it is worth reading.
And a real bug
loadModels attached a change listener on every call: changing the model three
times fired three POSTs. It is an onchange now.
1138 tests, 1 skipped on Windows and on Linux; CI green on ubuntu, macOS and
Windows across Python 3.10 to 3.13.
Full changelog: CHANGELOG.md
1.4.2 — The window opens like a program
The patch for the first hour of real testing.
A busy port is no longer a dead end
A uh web from an earlier session was still holding 127.0.0.1:8765, so every new
launch died on bind — uvicorn's exit code 3, and a popup with no reason. Worse:
because the interface is read from disk on every request, that stale server served
the new app.js with an old backend. The panel called /api/uhb/start,
got a 404, and said "Not Found" on a button that exists in the code.
uh web now probes the port and, on the default one, moves to the next free port
and says so (8765 is already in use (another 'uh web'?); using 8766). With an
explicit --port it refuses with one clear line, because a requested port is a
contract.
Two versions can no longer hide from each other
- the page is stamped with its build (
app.js?v=1.4.2,data-uh-version), so
a cached script cannot outlive a restart; /api/statusis compared at boot: a mismatch shows a banner naming both versions
and telling the user to relaunch;- a 404 on an API route now reads "ce serveur UH ne connaît pas cette route : une
ancienne instance occupe encore le port" instead of "Not Found".
The window opens like a program
--window-positionis computed at launch from the primary screen, so the app
window opens centred instead of in a corner;- the Desktop launcher is refreshed by
uh webwhen it predates--app: the
.lnkpoints at a script we generate, and an upgraded installation used to keep
one that ranuh web(a browser tab) forever. A hand-edited launcher is left
alone.
Measured after regenerating the launcher: one msedge process, --app=…,
--window-size=1280,880, --window-position=640,280, no browser tab, cockpit
stamped v1.4.1, second brain connected.
1138 tests, 1 skipped on Windows and on Linux; CI green on ubuntu, macOS and
Windows across Python 3.10 to 3.13.
Full changelog: CHANGELOG.md
1.4.1 — The second brain starts
The patch after the first evening of real use.
The second brain starts again
uh web spawned UHB with the interpreter running UH. On Python 3.10 — the one the
Desktop shortcut uses — the child died inside import (datetime.UTC is 3.11+),
and the cockpit could only say "uhb unreachable". Measured in ~/.uh/uhb.log:
ImportError: cannot import name 'UTC' from 'datetime'.
uhb_spawn now picks an interpreter it has asked two questions of — version
3.11+, and import httpx, fastapi — preferring the UHB checkout's own .venv,
then UH_UHB_PYTHON, then the running interpreter. A failed start quotes the
child's last lines instead of pointing at a log.
And it is a button, not a shell command
The panel used to answer "UHB not connected. Run 'uh web' (it starts UHB) or set
UH_UHB_PATH" — a shell instruction, in a browser, for a feature the user can see.
- Connecter le second cerveau →
POST /api/uhb/start, off the event loop, 25 s
budget, tried once automatically when the panel opens; - Détails → the reason, the checkout that was probed, the last lines of the log;
- Arrêter le second cerveau →
POST /api/uhb/stop, and the chat keeps working.
That decoupling is the whole reason UHB is a separate process.
A dream in one click, and it knows the tooling
POST /api/dreams/runstarts one pass in its own process (409 while one runs),
logged to$UH_HOME/dreams/manual-run.log; the Dreams panel gets an accent
button, the header button is drawn in the accent colour.- The pass reads four sources now: SiYuan, the RAG, the installed skills and
the configured MCP servers — listing each server's tools
(initialize+tools/list, never a call) so a proposed workflow names a tool
that exists. Empty sources are not failures; unreadable ones make the night
partial, anduh dream --nowexits non-zero when UHB was offline.
Twelve native coding skills, shipped in the wheel
Python, JavaScript, TypeScript, HTML/CSS, Rust, C++/OpenGL, Java, PHP, Ruby,
Pascal, React, Angular. Each written for a senior review — the traps, the security
rules, a checklist — and listed in the Data panel with the "Natif" badge.
Also
- the splash stays 900 ms minimum (it was dismissed in ~120 ms, a white flash);
- no window opens before the server answers, so a cold start is never a blank
window; uh web --appverified to open exactly one window and no browser tab.
1133 tests, 1 skipped on Windows and on Linux; CI green on ubuntu, macOS and
Windows across Python 3.10 to 3.13.
Full changelog: CHANGELOG.md
1.4.0 — The window and the protocol
A window, a protocol, and a night pass that knows what the harness can do.
Your cockpit is an application now
uh web --app opens the cockpit in Edge or Chrome with --app=<url>: no tab
bar, no address bar, one window in Alt-Tab, its own profile under
$UH_HOME/web-app so a browser you already have open cannot swallow the URL and
drop the flag. uh install-shortcut passes it, so the Desktop shortcut is an
application and not a bookmark. No Chromium is not an error: the console says so
and a normal tab opens.
It cost one flag instead of a Rust toolchain (Tauri is ~1.5 GB plus WebView2 plus
code signing before Windows stops calling the installer a virus), which is the
whole argument for doing it this way first.
UH speaks MCP
[[mcp.servers]] in config.toml attaches any Model Context Protocol server
over stdio — filesystem, a database, a browser — and its tools reach the agent
loop as mcp__<server>__<tool>:
[[mcp.servers]]
name = "filesystem"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "C:/Users/me/notes"]
allow = ["read_file", "list_directory"]One process per server, reused across turns. A per-server allow list hides what
it does not name. 10 MB per message, 200 000 characters per result, a 30 s
backoff after a failed start, and every failure — hang, crash, JSON-RPC error —
comes back as a tool result with a kind, never a crashed turn. The Data panel
lists each server with its state and a test button that asks it now.
Measured, not asserted: @modelcontextprotocol/server-filesystem 2.3.0 through
npx, 16 tools, and the screenshot in the README is a real turn where
qwen3:8b called mcp__atelier__list_directory and answered from the result.
The 03:00 report is no longer blind to your tooling
The night pass read SiYuan and the RAG. It now also reads the installed skills
and the configured MCP servers, and asks the model for workflows that name
real tools — filtered against the inventory, so a hallucinated tool name is
dropped instead of dangling in the report.
It still never calls one. A job that runs at 03:00 with nobody awake, through a
server that can write anywhere it is allowed, is a data-loss story with a cron
entry. The report proposes; the person decides.
Also
- README rewritten around 1.4.0, with a real screenshot (
docs/assets/cockpit.png),
CI/licence/Python badges and the test count corrected from 638 to 1108. - 1108 tests, 1 skipped on Windows and on Linux; CI green on ubuntu, macOS and
Windows across Python 3.10 to 3.13.
Full changelog: CHANGELOG.md