Skip to content

Releases

1.7.0 — one door per subject, honest errors, a self-aware agent

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 23:12

One door per subject, honest errors, and an agent that knows its own setup.

Changed

  • One place to connect anything. + Connecter un MCP left 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/model refuses 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.md with Mermaid diagrams; INSTALL.md gains "Where to connect things" and the pip ceilings.

Full changelog: CHANGELOG.md

1.6.1 — Python 3.10 imports again

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 14:07

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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 13:57

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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 01:17

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 skills lists what is installed (yours and the built-in ones).
  • uh skills --compress groups 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 a Merged from <name> heading.
  • It is a dry run unless --write, and the absorbed files move to
    skills/.merged instead of being deleted: a bad merge is one mv away 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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 00:59

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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 14 Nov 00:25

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.cli and uh.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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 13 Nov 23:56

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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 13 Nov 23:26

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/status is 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-position is 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 web when it predates --app: the
    .lnk points at a script we generate, and an upgraded installation used to keep
    one that ran uh 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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 13 Nov 22:46

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/run starts 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, and uh dream --now exits 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 --app verified 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

Choose a tag to compare

@Antoine-Thomas Antoine-Thomas released this 13 Nov 21:31

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