Two reports against 1.6.0 and 1.7.0, both correct. The crowd worked end to end and was, in practice, not there: the picker was behind the ⋯ menu of a chat that already existed, and the switch was a card on the Agents page, which made it read as an agent-chat feature. The picker is now a button in the composer toolbar on both screens that include it, and on the new-chat screen the choice rides along with the first message, so a chat can start as a crowd instead of having to be converted into one. The instance switch has its own page. The width bug was the suggestion cards, exactly as reported. `.suggestions` rendered 455px inside a 366px column, and the tree's standing rule applied on its own made it worse -- 428px to 455px. A grid item carries `min-width: auto`, which is a min-content floor, and a floor beats `width: 100%`; the floor is measured while the percentage is indefinite, so `min(100%, …)` alone sends the track to a card's max-content. Both halves now go on all four auto-fit grids, and a test refuses either alone. It survived four releases of narrow-width checking because the harness never rendered that screen: `TestClient(app)` runs no lifespan outside a `with` block, so the startup-seeded cards were missing from every shot ever taken of it. And its overflow check skipped anything inside a scroller -- right for a table in its own scroller, blind to the scroller itself, which `overflow-y: auto` makes scroll sideways too. Both fixed; it now names the box and the child to blame. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
952 lines
54 KiB
Markdown
952 lines
54 KiB
Markdown
# Changelog
|
||
|
||
What changed, per version, for somebody using or running LLeMbas — not a
|
||
restatement of the commit log. If a change fixed something that *looked* like it
|
||
worked, that is worth a line: those are the ones nobody would otherwise know to
|
||
stop working around.
|
||
|
||
Newest first. Versions are `__version__` in `src/lembas/__init__.py`, which is
|
||
the only place a version is written.
|
||
|
||
The first tagged release is **1.0.0**. Everything below it shipped as a running
|
||
deployment rather than as a release, and is recorded here so the release notes
|
||
for 1.0.0 have something to be assembled from.
|
||
|
||
---
|
||
|
||
## Unreleased
|
||
|
||
## 1.8.0
|
||
|
||
- **The crowd is where you would look for it.** In 1.6.0 the only way to add a
|
||
model to a chat was the Chat settings panel — behind the ⋯ menu, inside a chat
|
||
that already existed — and the switch that turns the feature on was a card on
|
||
the Agents page. Somebody who enabled it went looking and found nothing, which
|
||
is the correct outcome of that arrangement.
|
||
|
||
Now there is a **crowd button in the composer**, beside the attachment and
|
||
scope buttons, on both the chat screen and Messages. It carries a count when
|
||
the chat has a crowd, it lists the models you can reach, and it says what the
|
||
turn will cost before you tick anything. On the new-chat screen the choice
|
||
**rides along with the first message**, so a chat can start as a crowd rather
|
||
than having to be converted into one.
|
||
|
||
The instance switch and its bounds have moved to their own page, **Admin →
|
||
Crowd**.
|
||
|
||
- Fixed: **the new-chat screen was wider than a phone.** Before the first
|
||
message, the suggestion cards pushed the conversation 65px past the edge of a
|
||
390px screen and it could be dragged sideways; after the first message it
|
||
looked right, because the cards were gone. Reported from a phone.
|
||
|
||
Two things were true at once. The cards' grid asked for a minimum column width
|
||
it could not give up — the ordinary version of this bug — and it was *also* a
|
||
grid item, which means it carried a min-content floor that beats `width: 100%`
|
||
outright. Fixing only the first made it 27px worse. Both are fixed, on all four
|
||
grids in the stylesheets that could have it, and a test now refuses either half
|
||
of the pair on its own.
|
||
|
||
The reason this survived four releases of narrow-width checking is worth
|
||
recording: the screenshot harness built its client without running the
|
||
application's startup, so the suggestion cards were **absent from every shot
|
||
ever taken of that screen**, and its overflow check deliberately ignored
|
||
anything inside a scrolling box — correct for a wide table in its own scroller,
|
||
blind to a box that scrolls sideways when nobody asked it to. Both are fixed,
|
||
and the harness now names the offending element and the child responsible.
|
||
|
||
## 1.7.0
|
||
|
||
- **The interface speaks Slovak.** Pick a language under **Appearance** in your
|
||
own settings, or set what everybody else gets under Admin → General. Your own
|
||
choice wins over the instance's, it is saved to your account rather than to one
|
||
browser, and `<html lang>` finally says what the page is actually in.
|
||
|
||
All 969 translatable strings are translated, including the long explanatory
|
||
paragraphs on the admin pages — there is no half-done corner where a Slovak
|
||
instance falls back to English. Dates follow too: a month name is a month name
|
||
in the language you are reading, which `strftime` cannot do without a process
|
||
locale that this application must not set.
|
||
|
||
**What is deliberately still in English**: everything a *model* reads. The
|
||
prompt fragments under Admin → Prompts are instructions written for models, and
|
||
translating them would change what the models are told rather than what you
|
||
see. Models answer in whatever language you write to them in — they already
|
||
did, and that line is editable where all the others are.
|
||
|
||
An English instance is byte-for-byte what shipped in 1.6.0. That is a property
|
||
of the design rather than a claim: a string with no translation renders the
|
||
English it was written in, so a language added later cannot leave holes in a
|
||
page.
|
||
|
||
- Fixed: **every page was rendered in the instance's language, whatever anybody
|
||
had chosen.** Found while building the above and worth naming because it would
|
||
have been invisible: the language was resolved in a dependency that FastAPI runs
|
||
in a threadpool, and the context it was set in is discarded on the way out. Now
|
||
it is resolved in the request's own task.
|
||
|
||
## 1.6.0
|
||
|
||
- **A chat can have a crowd.** Switch it on under Admin → Agents, and each chat's
|
||
settings panel offers the other models. The chat's own model answers first, then
|
||
each of the others in turn; then the order runs **backwards**, each one asked
|
||
whether it disagrees with anything said; and it ends back at the first model,
|
||
which either writes the final answer or sends them round again. Every
|
||
contribution is its own bubble with its own avatar, its own metrics and a chip
|
||
saying which speaker it is and which pass it belongs to.
|
||
|
||
What it costs is stated where you turn it on and again where you pick the
|
||
models, because it is easy to underestimate: one turn is **models × rounds ×
|
||
2 − 1** replies, so four models over two rounds is fifteen. On a single local
|
||
endpoint every change of speaker also loads a different model. Your own warning
|
||
is built into the defaults — larger crowds start going round in circles — so the
|
||
round limit is two, and it is a limit ordinary work will reach rather than a
|
||
runaway backstop.
|
||
|
||
Details worth knowing: each model sees the others' answers **quoted and
|
||
attributed**, never as its own words, so it can actually disagree with them; a
|
||
member you can no longer reach is skipped and said so rather than silently
|
||
dropped; a member whose endpoint fails is skipped, and two failures in a row end
|
||
the round; **Stop ends the round**, not just the model writing at the time; and
|
||
a message typed during a round waits for the round rather than interleaving with
|
||
it. Every sentence a crowd sends is editable under Admin → Prompts.
|
||
|
||
- Fixed: **a schedule that named its own model was ignored.** It was written on
|
||
the reply and never sent, so the bubble showed the model you chose while the
|
||
answer came from the chat's model. The same fix makes the crowd possible: the
|
||
reply itself now says which model is answering, rather than the conversation
|
||
deciding for all of them. Regenerating somebody's turn in a crowd keeps that
|
||
model rather than silently switching to the chat's.
|
||
|
||
## 1.5.0
|
||
|
||
- **A model's personality is now yours, not the instance's.** Each account gets
|
||
its own version of each model's character: a personality is something a model
|
||
works out *with somebody*, so two people talking to the same model are no longer
|
||
talking to the same one, and neither can see the other's. What a model **is** —
|
||
its description and the facts other models are told about it — stays the same
|
||
for everybody, because that is a property of the model rather than of a
|
||
relationship.
|
||
|
||
The box on the model's page is now the **default personality**: the starting
|
||
point somebody has until the model has written its own with them. It is not
|
||
layered underneath theirs afterwards — two personalities at once would
|
||
contradict each other and nobody could tell which was losing. Your own
|
||
personalities, their history, and what each model makes of you are all under
|
||
**Memory** in your settings, and deleting a personality resets it to the
|
||
default rather than removing it.
|
||
|
||
⚠ If you installed 1.4.0 — released and superseded the same day — anything a
|
||
model wrote about you then is sitting in the wrong place and reads as a
|
||
personality rather than as an impression. There is a note in
|
||
`db/migrations.py` with the one statement that moves it; deleting it is just as
|
||
reasonable, since nothing had time to write one worth keeping.
|
||
|
||
- Fixed: **a side panel was wider than a narrow phone and hung off the edge.**
|
||
The canvas, the terminal and the details panel all carried a minimum width of
|
||
384px, which beats the rule that was supposed to cap them at the screen — so on
|
||
a 360px phone they were 24px too wide with their left-hand edge cut off, and on
|
||
a 320px one, 64px. Nothing scrolled sideways, which is why a narrow-width pass
|
||
looking for a horizontal scrollbar never found it: the panels are fixed in
|
||
place, and fixed overflow does not make a page scroll. They are now exactly as
|
||
wide as the screen on a phone, and keep their column on a tablet.
|
||
|
||
The details panel was worse than the other two: it had no cap at all, and its
|
||
width is a *preference* you can drag to 2400px on a desktop. That number was
|
||
arriving verbatim on a phone.
|
||
|
||
- **The Install button now says why it is missing**, instead of not being there.
|
||
Four different things stop a browser installing this and all four looked
|
||
identical; the hint named only the least likely. It now reports whether the page
|
||
is a secure context, what the browser said if the service worker was refused,
|
||
and whether the browser simply never offers it — and names the cause that
|
||
actually bites a self-hosted instance: **a certificate the phone does not
|
||
trust**. A private or self-signed certificate means no service worker, and no
|
||
service worker means no install, however good the rest of it is. Installing the
|
||
CA on the device is the fix, and the app can now tell you that is what is
|
||
wrong.
|
||
|
||
## 1.4.0
|
||
|
||
- **Models can be told about each other.** A model may now be given a list of
|
||
the other models on this instance — their names, the id to refer to one by, and
|
||
what each is for — so that it knows what else is available and what each is
|
||
better at. The list is built per person from the models *they* can reach, so it
|
||
never names one they have no access to.
|
||
|
||
Each model's page has a new **Facts for other models** box for this: parameters,
|
||
quantisation, a benchmark figure, what it is bad at. The existing description is
|
||
used too, so filling in nothing at all still produces a usable list — but note
|
||
that the description is now read by models as well as by people.
|
||
|
||
- **A model can ask another model a question.** New **Ask another model** switch
|
||
on each model's page and a matching permission. The model picks who to ask from
|
||
the list above, writes the question, and gets that model's answer back to use —
|
||
a second opinion from something that is better at the subject, or a check on its
|
||
own reasoning by something that will not make the same mistakes.
|
||
|
||
The model answering sees only the question, not the conversation; it answers as
|
||
itself, and it is told to say so if it thinks the question is wrong. It cannot
|
||
ask anybody anything in turn, and it cannot pass the question on.
|
||
|
||
It shares the **Helpers** switch and allowance on Admin → Agents, because it
|
||
costs the same thing: one reply setting another reply going. On a single local
|
||
endpoint that also means a model swap out and back, so it is not free.
|
||
|
||
- **A model can have a personality of its own, and keep its own read of you.**
|
||
New **Edit its own personality** switch per model. Its character is carried into
|
||
every conversation rather than being an instruction for one, and it is the model
|
||
that writes it — you can seed it, read it, and put any earlier version back from
|
||
the **Personality** card on the model's page. Every version is kept.
|
||
|
||
Separately, each model keeps its own impression of how you work: what you
|
||
expect, how you like being answered, what keeps going wrong between you. Its
|
||
point of view rather than facts about you, which is what a memory is for. It is
|
||
per model and per person — two models may honestly reach different conclusions
|
||
about you, and nobody on a shared instance inherits anybody else's.
|
||
|
||
**You can read and delete all of it**, under Memory in your own settings. That
|
||
is the whole reason a model is allowed to keep one.
|
||
|
||
Two honest limits. A model that has just read a hostile web page can rewrite its
|
||
own character; what stops that being permanent is that every version is kept and
|
||
visible, not that it was prevented — the same position this takes on
|
||
model-written skills. And neither is available to a model running as somebody's
|
||
helper, answering another model's question, or working through a schedule: those
|
||
run on words nobody is watching being written.
|
||
|
||
- Fixed: **the model chosen to review generated images was silently forgotten**
|
||
whenever a connection was refreshed while its endpoint happened not to be
|
||
listing that model. Nothing failed — reviewing fell back to the chat's own
|
||
model, so pictures were being judged by a model you had not chosen, with nothing
|
||
saying so. Existing settings keep working.
|
||
|
||
- Fixed: **editing a message could leave one of the messages below it behind.**
|
||
Only when two were written in the same millionth of a second, which is exactly
|
||
what happens to a question and the reply being started for it — so the orphan
|
||
stayed in the conversation and in everything sent to the model afterwards.
|
||
|
||
## 1.3.2
|
||
|
||
- Fixed: **the model page could not save anything below the reasoning efforts**,
|
||
and had not been able to since 1.3.0. "Save changes" did nothing at all — not
|
||
slowly, not with an error, simply nothing — so the description, the system
|
||
prompt, every capability and tool switch, and the whole availability card
|
||
(enabled, pinned, available to everyone, groups) silently would not take. The
|
||
fields above it, including the display name and the reasoning efforts, saved
|
||
normally, which is what made it look like it worked.
|
||
|
||
Worse, the **Detect from the endpoint** button had stopped detecting. It
|
||
submitted the page as an ordinary save instead — a save carrying only the top
|
||
half of the form, so everything below took its empty default: it would have
|
||
cleared that model's description and system prompt and switched the model off
|
||
with all of its tools disabled. If you pressed it, check that model's page.
|
||
|
||
The cause was one HTML rule: a form inside another form is not allowed, and
|
||
rather than complaining, a browser discards the inner tag and lets the closing
|
||
tag end the *outer* form. Everything after that point was in no form, and a
|
||
button in no form does nothing. Nothing in the markup looks wrong, and no test
|
||
that posts to a route can see it — so the fix comes with one that reads every
|
||
page the way a browser parses it.
|
||
|
||
## 1.3.1
|
||
|
||
- Fixed: **updating to 1.2.0 or later broke every page that lists models**, with
|
||
a 500 and nothing but the error page to show for it. The per-model reasoning
|
||
effort list added in 1.2.0 was the first list-shaped setting this application
|
||
had ever added to a table that already had rows in it, and the code that fills
|
||
in such a column on existing rows could not tell a list from a dictionary — so
|
||
it wrote the wrong kind of empty value into every model, and reading one back
|
||
raised rather than returning nothing.
|
||
|
||
A fresh install was never affected, which is exactly why it was not caught:
|
||
the column is only filled in that way on a database that already existed.
|
||
|
||
This release both stops it happening and **puts right the rows already
|
||
written**, on start, with nothing to run by hand. If your instance is showing
|
||
the error page, updating is the whole fix.
|
||
|
||
## 1.3.0
|
||
|
||
- **A model's reasoning efforts can now be detected rather than known.** There
|
||
is a button on the model's page that asks the endpoint what its chat template
|
||
actually accepts, and ticks those. llama.cpp publishes the loaded model's
|
||
template, and that template is the very thing that rejects an effort it does
|
||
not recognise — so the answer is read from the place that is authoritative
|
||
instead of guessed at, or discovered by a failed reply.
|
||
- Endpoints that do not publish a template — OpenAI, vLLM — say so plainly
|
||
rather than being recorded as accepting nothing.
|
||
|
||
## 1.2.0
|
||
|
||
- Fixed: **choosing a reasoning effort could kill the reply outright**, with a
|
||
Jinja traceback where the answer should have been. Reasoning effort is sent
|
||
two ways, and the second — `chat_template_kwargs` — is rendered into the
|
||
model's own chat template, which does not ignore a value it has never heard
|
||
of: it raises, and the whole request fails. The catch is that the vocabulary
|
||
is **not the same for every model**. gpt-oss takes `low/medium/high`; Bonsai
|
||
takes `low/medium/xhigh` and refuses `high`; OpenAI has added `minimal`,
|
||
`xhigh` and `max` at various points. This application offered the same three
|
||
to everything, so on some models the top setting was one the model would
|
||
throw for.
|
||
- **A model now has its own list of the efforts it accepts**, on its page under
|
||
Models, and the composer's picker and `/effort` offer only those. Tick none
|
||
and the familiar three are used, which is right for nearly everything.
|
||
- **And it corrects itself.** If an endpoint refuses an effort anyway — a model
|
||
swapped underneath a name, a runtime upgraded — that reply is retried once
|
||
without it instead of being lost, and the model's list is narrowed so the
|
||
menu stops offering something that does not work. Where the endpoint says
|
||
what it *does* take, that is what gets stored.
|
||
- `/effort` now reads the levels from the picker rather than from a second copy
|
||
of the list kept in the browser, so the two can no longer disagree about what
|
||
a valid effort is.
|
||
|
||
## 1.1.2
|
||
|
||
Two things a phone found that 1.1.0's phone pass had not.
|
||
|
||
- Fixed: **the administration area could not be navigated on a phone.** Admin
|
||
has a nav of its own rather than the chat sidebar, and 1.1.0 gave every
|
||
sidebar the drawer behaviour — starts closed, slides in — without giving that
|
||
one any of the drawer's furniture. So it sat off-screen with no button to open
|
||
it, no close, and nothing to tap beside it: every administration page was
|
||
reachable and then a dead end. It now opens, closes and dims the page like the
|
||
other one, and a test refuses any future sidebar that cannot be opened.
|
||
- Fixed: **the chat gave nearly a quarter of a phone screen to margins**, so
|
||
anything that could not wrap had to be scrolled to sideways. The thread's side
|
||
padding is halved, and the speaker's avatar moves above the turn instead of
|
||
sitting in a 44px column beside every line of it — a code block gained about
|
||
sixty pixels of readable width.
|
||
- Fixed: **the chat's title was squeezed to nothing.** The row's designated
|
||
shrinker is hidden below a tablet width, so on a phone the controls went rigid
|
||
and asked for 317 pixels of a 390 pixel bar; the heading was not truncated, it
|
||
simply stopped occupying space. The model picker gives now, and on a phone it
|
||
shows its avatar rather than its name — the name is one tap away and the
|
||
title is not.
|
||
- Tick boxes and the smaller buttons are big enough to hit on a phone. A
|
||
checkbox is drawn by the browser at about sixteen pixels whatever the type
|
||
around it, which made it the smallest target in the application by some way,
|
||
and the admin lists are mostly checkboxes.
|
||
- Fixed: **icon buttons could be squashed below their own size.** The sidebar
|
||
toggle measured eighteen pixels across on a phone, under half its target,
|
||
because a full row shrank the button rather than the text beside it.
|
||
|
||
## 1.1.1
|
||
|
||
One bug, and it is the one that made 1.1.0 look broken the moment you updated to
|
||
it. If you saw a stray ✕ beside the logo on a desktop, controls that looked
|
||
half-styled, or a page that would not scroll, this is why — and none of it was
|
||
in the code you were running; it was the code your browser had *not* fetched.
|
||
|
||
- Fixed: **updating showed you the new page drawn with the old stylesheet.**
|
||
Pages are always fetched fresh, while the CSS and JavaScript beside them come
|
||
from the cache the offline support keeps — and that cache was keyed on the
|
||
release while the files inside it were not. For as long as the previous
|
||
release's worker was still in charge, you got 1.1.0's markup over 1.0.x's
|
||
stylesheet: a close button meant for the phone drawer appeared on the desktop
|
||
with nothing to style or place it, and anything else the new layout depended
|
||
on was simply absent. Every asset now carries the release in its address, so
|
||
a new page cannot be handed an old stylesheet whatever the cache holds.
|
||
|
||
It is self-correcting: updating to this version is enough, and no cache needs
|
||
clearing.
|
||
|
||
- The sidebar header is two slots — the name, and a rail on the right for the
|
||
drawer's own controls — instead of a brand with a button appended to it. The
|
||
close button sits in that rail, at the top right where it belongs, and a
|
||
second control added later lands beside it rather than pushing the name
|
||
around.
|
||
|
||
## 1.1.0
|
||
|
||
Mostly about using this on a phone, where it turns out a good deal of it could
|
||
not be used at all.
|
||
|
||
### The sidebar on a phone
|
||
|
||
- Fixed: **the sidebar opened over the page on every phone, and the button that
|
||
closes it was underneath it.** Below a phone width the sidebar is a 280px
|
||
panel laid over the page; nothing ever closed it, and the only control that
|
||
could was in the bar behind it. It now starts closed at that width, slides in
|
||
when you ask for it, dims the page behind it, and closes by tapping beside it,
|
||
by Escape, or by its own button — which is inside the drawer, where you can
|
||
reach it.
|
||
- Fixed: **seven of the eight pages with a sidebar had no way to show or hide it
|
||
at all.** Only the chat page ever had that button. Settings, Messages,
|
||
Reports, Scheduled, Library, Connections and a folder's own page did not —
|
||
which on a phone meant arriving at a page already covered by a panel with
|
||
nothing to do about it. Settings is where the Install and Notifications
|
||
buttons live, so this was also why they were hard to reach.
|
||
- The toggle no longer claims the sidebar is open when it is not, which matters
|
||
to anyone using a screen reader.
|
||
|
||
### Anything you tap
|
||
|
||
- **Every control is now at least 44px on a touch screen**, instead of 36px —
|
||
or 28px for the small ones, which included renaming and deleting a chat, all
|
||
seven actions on a message, and every panel's close button. The dismiss button
|
||
on a notification had no size of its own at all and was about 18 by 7 pixels.
|
||
- Fixed: **renaming or deleting a chat, and copying, editing, regenerating or
|
||
reading aloud a message, were impossible on a phone.** All of them appeared on
|
||
hover, and there is no hover on a phone; tapping the row simply opened it.
|
||
- Fixed: **the settings tabs scrolled sideways with nothing to say so**, hiding
|
||
Appearance, Memory and Security off the right-hand edge of a phone screen.
|
||
There is a fade at the edge now, and a flick lands on a tab.
|
||
- Installed on an iPhone, the page ran underneath the clock and the home
|
||
indicator. It no longer does.
|
||
|
||
### Installing it
|
||
|
||
- The install prompt now offers the richer dialog rather than the terse bar, and
|
||
a long press on the icon offers New chat, Messages and Scheduled.
|
||
- Fixed: **a light-themed instance installed to a phone showed a near-black
|
||
splash screen and then opened parchment**, and every page load flashed dark
|
||
browser chrome before the stylesheet had run. Both follow the theme now.
|
||
- Fixed: **a new version used to take over pages you were reading**, swapping
|
||
the stylesheets under an open tab while it emptied the cache they came from.
|
||
It waits and offers you a reload instead.
|
||
- Fixed: the small mark beside a notification on Android was a solid grey
|
||
square, because the icon it used has no transparency to be cut from.
|
||
- Fixed: notifications silently stopped working for good if the browser ever
|
||
replaced its own subscription, which browsers do.
|
||
- Pages start loading a little sooner, and the two icons a launcher actually
|
||
crops are now kept for offline use.
|
||
|
||
### Things that move
|
||
|
||
- **Every request the application makes now says it is happening**, with a thin
|
||
bar across the top of the window. Nothing did before, so anything slower than
|
||
a few milliseconds looked like a click that had not registered.
|
||
- The thinking indicator turns rather than fading, so a model that is working
|
||
and one that has stopped no longer look alike.
|
||
- Dialogs, the drawer and the panels arrive and leave rather than appearing;
|
||
buttons answer a press; cards lift under the pointer. All of it stops if you
|
||
have asked your system for reduced motion.
|
||
|
||
### Archiving
|
||
|
||
- **A chat can be archived** — out of the list, into a group at the bottom of the
|
||
sidebar, and back again whenever you like. The setting behind this has existed
|
||
and been honoured since folders arrived; nothing had ever been able to switch
|
||
it on.
|
||
|
||
### Smaller things
|
||
|
||
- **Extra headers can be set on a connection.** They were sent with every
|
||
request already and no form could write them, so OpenRouter's attribution
|
||
headers were documented and unreachable.
|
||
- A model is no longer told that it will hear when a background job finishes on
|
||
instances where that notification is switched off.
|
||
- The guidance for asking you a question can now be edited like every other
|
||
piece of the prompt. It was the only one that could not be.
|
||
- Several controls that a screen reader announced as nothing now have names, and
|
||
two lists that claimed to be tab strips now describe themselves honestly.
|
||
- Borders resolve through a token like every other value, so a theme can change
|
||
one. They were a literal `1px` in about ninety places, which was the largest
|
||
patch of hard-coded value left in the stylesheets.
|
||
- `chat.css` may now contain media queries. It was forbidden them, for a good
|
||
reason that had stopped applying: what the ban protected is asserted directly
|
||
now, which is both narrower and stronger.
|
||
|
||
## 1.0.4
|
||
|
||
Six things that looked like they worked. Five of them were found by reading the
|
||
code rather than by anybody reporting them, which is what they have in common:
|
||
none of these fails loudly, and two of them correct themselves if you reload.
|
||
|
||
- Fixed: **a reply lost the model's name and picture the moment it finished.**
|
||
While a reply streams it is attributed correctly; at the instant it lands, the
|
||
frame that replaces the bubble was looking the models up as nobody, and "no
|
||
user" answers "no models" rather than "all models". So a finished reply swapped
|
||
the model's avatar for the plain leaf mark, put the instance's name where the
|
||
model's should be, and grew a raw model id beside it. Reloading the page put it
|
||
all back, which is why this survived a release: it is only ever wrong until you
|
||
look away.
|
||
- Fixed: **a limit on how many replies an account may write at once could be
|
||
stepped over by pressing New chat.** It was enforced when sending into a chat
|
||
that already existed and nowhere else — not on a new chat, not on editing an
|
||
earlier message, not on sending a queued one, and not on regenerating. Four of
|
||
the six ways to start a reply ignored it, including the commonest.
|
||
- Fixed: **a custom theme's confirmations and warnings kept the built-in
|
||
theme's colour behind them.** Setting `success` or `warning` moved the text and
|
||
left the background it sits on, because the faded companion colour was derived
|
||
for three of the five settable colours. Visible on every alert and badge of
|
||
those two kinds, on the "on" state in the permissions list, and on the added
|
||
lines of every diff in an agent chat.
|
||
- Fixed: **on a phone, every page with a sidebar could be scrolled past its own
|
||
bottom into empty background.** The shell was sized to the part of the screen
|
||
you can actually see and the document around it to the part you can see with
|
||
the browser's toolbar retracted; the difference between those is real on a
|
||
phone and nil on a desktop, which is why it was never noticed on one. Reported
|
||
on Settings and true everywhere. A flick that ran off the end of a list now
|
||
stops there as well, instead of dragging the page behind it.
|
||
- Fixed: **the conversation was rendering every assistant message twice on every
|
||
page load** — once into Markdown that nothing read, and once the way it is
|
||
actually shown. The same was true of Messages, for your own turns. Nothing
|
||
looked wrong; a long conversation was simply slower to open than it needed to
|
||
be, every time, along with every rewind and every compaction.
|
||
- Fixed: a test file meant to skip itself on a machine without `setsid` never
|
||
did, because it set its marker twice and the second one replaced the first.
|
||
- Removed: an endpoint serving a message's unrendered Markdown, which nothing
|
||
had ever called — the copy button reads the page it is already on.
|
||
|
||
## 1.0.3
|
||
|
||
Two Arch-isms in the installer, both of which only a Debian machine could find.
|
||
`deploy/lxc-install.sh` had never been executed — it was reviewed and
|
||
syntax-checked, which is not the same claim — and running it is what found them.
|
||
|
||
- Fixed: **`deploy/install.sh` could not create its virtualenv on Debian**, and
|
||
so `deploy/lxc-install.sh` could not finish. It called bare `python`, which is
|
||
Python 3 on Arch — the machine this was written and only ever run on — and
|
||
does not exist on Debian at all unless `python-is-python3` is installed. The
|
||
LXC bootstrap installs `python3`, so the install aborted at the virtualenv
|
||
step with the service user, the bind mount and the clone already made. It now
|
||
calls `python3`, which is right on both.
|
||
- Fixed: the service account was created with `--shell /usr/bin/nologin`, which
|
||
is where Arch keeps it and where Debian does not. Nothing invoked it — `sudo -u`
|
||
execs directly and systemd's `User=` never reads a shell — so the account
|
||
worked either way, but it was created pointing at a file that was not there.
|
||
Now `/usr/sbin/nologin`, which is correct on Debian and resolves on Arch too,
|
||
since Arch's `/usr/sbin` is a symlink to `bin`.
|
||
|
||
## 1.0.2
|
||
|
||
- **The documentation moved to the [wiki](https://git.houmeres.sk/Houmeres/LLeMbas/wiki).**
|
||
`CLAUDE.md`, `PLAN.md` and `docs/` are gone from the repository: they are
|
||
documentation *about* this project rather than part of it, and a clone should
|
||
carry software. Nothing was lost — the working notes, the roadmap and the eight
|
||
topic notes are all there, with every internal link rewritten, and the README
|
||
now opens onto them. Where a source comment said "see `CLAUDE.md`" it now says
|
||
"see the working notes".
|
||
- Entries below this one still name `PLAN.md` and `docs/notes/…`, and are left as
|
||
they were written. A changelog records what happened at the time; rewriting old
|
||
entries to match a later decision makes it a worse record, not a better one.
|
||
|
||
## 1.0.1
|
||
|
||
- Fixed: the Updates page showed **"v1.0.0 (reports 1.0.0)"** — two spellings of
|
||
one version, in a note whose whole purpose is to warn that a tag was cut
|
||
before the version bump. `git describe` answers with the tag's name, and tags
|
||
here carry a `v`. Found by cutting the first release, which is the only place
|
||
it could have been.
|
||
|
||
## 1.0.0
|
||
|
||
The first release. Every version before it shipped as a running deployment
|
||
rather than as a release; this is what those add up to, and the point at which
|
||
it is worth somebody else installing.
|
||
|
||
**What it is.** A self-hosted web interface for OpenAI-compatible endpoints.
|
||
Server-rendered, no build step, no CDN, one SQLite file. Point it at whatever
|
||
you run — llama.cpp, LM Studio, vLLM, Ollama, OpenRouter, OpenAI — and it works
|
||
the same.
|
||
|
||
### What arrived since 0.8.1
|
||
|
||
- **Things that happen because time passed.** Say "every Monday at nine" and a
|
||
model sets it up itself, against the same recurrence rule the manual form
|
||
uses. A run can file a **report** you read later, send you a message, or work
|
||
in a chat of its own.
|
||
- **News that finds you.** A dot in the sidebar, a count in the tab title while
|
||
you are looking elsewhere, and **web push** so a schedule firing at seven in
|
||
the morning reaches a browser that is shut. Opt-in per device, and the one
|
||
thing here that contacts an outside service — `services/push.py` says so
|
||
plainly and says what it costs.
|
||
- **Helpers.** A reply can hand a self-contained piece of work to another model
|
||
that runs on its own and reports back, several at once. A helper cannot ask
|
||
questions, cannot send helpers of its own, changes nothing unless asked, and
|
||
on a machine runs only a fixed list of read-only commands.
|
||
- **Drawing.** Point it at a ComfyUI and a model can make images, against
|
||
workflow templates and defaults you set — size, steps, sampler, scheduler,
|
||
checkpoint. It reviews its own result and can try again.
|
||
- **Semantic search.** Pick an embedding model and library search fuses keyword
|
||
and meaning, so *"how do I get paid"* finds a document that says *"invoicing"*.
|
||
Choosing none is not a degraded mode: it is byte-for-byte the keyword search
|
||
that was always there, with nothing written and no requests made.
|
||
- **Quotas and sharing.** Monthly tokens, concurrent replies, agent wall clock,
|
||
images a day, helpers a reply — resolved by maximum across a person's groups,
|
||
with zero meaning *no limit*. Documents, notes, skills and reports can be
|
||
handed to a group or a person, read-only, with a *Shared with me* filter
|
||
everywhere. And a screen that answers **"what can this account actually do?"**
|
||
by naming where each permission came from.
|
||
- **Make it yours.** Name, tagline, logo, favicon and launcher icons; the
|
||
Middle-earth wording is editable data; custom themes defined as a set of
|
||
colours rather than a stylesheet.
|
||
- **Install it and update it.** A Dockerfile, a Proxmox container script, and an
|
||
`/admin/updates` page showing what is running, what is available and what
|
||
changed between. The button that applies an update is opt-in and cannot do the
|
||
work itself — it writes a file that a systemd unit picks up, because a web
|
||
application that can restart its own service is one whose worst day is much
|
||
worse.
|
||
|
||
### The part worth reading
|
||
|
||
Five audit passes went into this release rather than one, and they found things
|
||
that had shipped looking correct. These are the entries somebody stops working
|
||
around a bug because of:
|
||
|
||
- **Every model was told the time in a zone with no name** — on any account that
|
||
had not chosen one, which is every account by default.
|
||
- **A helper could write files and run programs on a remote machine,
|
||
unattended, in a mode that promises to change nothing.** `find` was on the
|
||
read-only command list, and `find -fprintf` writes a file.
|
||
- **Two ways to get root out of the update helper**, one of which needed no
|
||
compromise at all: root ran a script the unprivileged service account owns,
|
||
and an update fetches that script as that account.
|
||
- **Deleting a chat left every file it held on disk** — attachments, generated
|
||
images, all of it, with nothing that would ever look at them again.
|
||
- **Folder nesting was fully built, documented in the README, and reachable by
|
||
nothing.** So was moving a chat into a folder.
|
||
- **The terminal silently stopped accepting input after a reconnect**, while
|
||
output kept arriving so the panel looked healthy.
|
||
- **On the Messages screen, half the keyboard shortcuts did nothing**, because
|
||
two scripts were loaded twice and each toggle ran twice.
|
||
- **The prompt preview could not show two thirds of what it previews.**
|
||
- **Hints and timestamps failed the contrast minimum in both themes.**
|
||
|
||
### Where the edges are
|
||
|
||
Stated because they are the things worth knowing before you rely on it:
|
||
|
||
- **Nothing executes on the machine LLeMbas runs on.** Agent chats run their
|
||
commands over SSH on a host you choose, and the security of an agent chat is
|
||
the security of that host. There is no sandbox here and that is deliberate —
|
||
`PLAN.md` records the one that was designed and dropped, and why.
|
||
- **One worker.** The generation registry, the terminal sessions and the
|
||
schedule ticker are all in-process. Two workers means two tickers and every
|
||
schedule firing twice.
|
||
- **A restart abandons replies in flight**, keeping whatever each had.
|
||
- **Schema changes are additive.** New tables and columns apply themselves at
|
||
startup; renames and drops are manual. The upgrade path is tested from an
|
||
0.8.1-shaped database with rows in it.
|
||
- **Sharing grants reading only.**
|
||
|
||
2283 tests on Python 3.11, 3.12 and 3.14.
|
||
|
||
## 0.9.13
|
||
|
||
**The testing pass.** 2140 tests became 2283, and writing them found four bugs
|
||
that no amount of reading had.
|
||
|
||
- Fixed: **the terminal silently stopped accepting input after a reconnect.**
|
||
Change the connection, or let the shell catch up after falling behind, and
|
||
every keystroke was dropped from then on — while output kept arriving, so the
|
||
panel looked perfectly healthy. It also announced "Disconnected. Close and
|
||
reopen to reconnect." about a shell that had just reconnected successfully.
|
||
- Fixed: **on the Messages screen, half the keyboard did nothing.** Two scripts
|
||
were loaded twice there, so `Alt+B`, `Alt+E`, `Alt+T` and `Alt+I` toggled
|
||
their panel twice — which is to say not at all — while `/help` opened two
|
||
dialogs, `/image` posted the message twice, and picking an `@` mention
|
||
attached the file twice.
|
||
- Fixed: **pressing the microphone while the permission prompt was up opened a
|
||
recording each time.** Only the last was stopped, so the browser's recording
|
||
indicator stayed on until the tab was closed.
|
||
- Fixed: **a skill shared with you took its name out of your own library.**
|
||
Creating your own was refused with "a skill called that already exists. Edit
|
||
it instead" — naming a skill you cannot edit, because sharing grants reading
|
||
only. The model's `skill_create` hit the same dead end. Sharing a curated
|
||
skill with a team is what sharing is *for*.
|
||
- Hints and timestamps are readable now. `--ink-faint` failed the accessibility
|
||
contrast minimum in **both** themes — 3.85:1 in Moria, 3.19:1 in Shire, where
|
||
4.5:1 is the bar — so the smallest text on every screen was the hardest to
|
||
read.
|
||
- The suite runs on **Python 3.11 and 3.12** as well as 3.14. It had only ever
|
||
run on 3.14, while the Docker image ships 3.12 and the packaging claimed 3.11
|
||
— so the one interpreter most people would actually run was the one nothing
|
||
had tested.
|
||
- A `docs/notes/release-checklist.md` for the half of testing a machine cannot
|
||
do: a real endpoint, a real machine, real hardware, a real pair of eyes.
|
||
|
||
## 0.9.12
|
||
|
||
**The security pass.** Six findings, all fixed. None is reachable by simply
|
||
visiting the site; every one of them is a boundary that was supposed to hold
|
||
and did not.
|
||
|
||
- Fixed: **a helper could write files and run programs on the remote machine,
|
||
unattended, in a mode that promises to change nothing.** A subagent is pinned
|
||
to a fixed list of read-only commands — and `find` was on it. `find -fprintf`
|
||
writes a file, `find -exec` runs a program, `find -delete` removes one, and
|
||
none of them needs a character the shell-metacharacter guard refuses. A page
|
||
the model had just read could have asked for a helper and got an SSH key
|
||
written into `authorized_keys`. Those flags are refused outright now, whatever
|
||
list a command is on.
|
||
- Fixed: **an SSH connection could be pointed at `0.0.0.0` and reach the machine
|
||
LLeMbas runs on**, with the "may a connection point here" setting still
|
||
reading *off*. Every other spelling was caught; that one is neither a real
|
||
destination nor a refused one, and connecting to it goes to localhost.
|
||
- Fixed, twice, in the update helper — the one place this deliberately crosses a
|
||
privilege boundary: **root ran a script the unprivileged service account
|
||
owns**, and **root sourced a file that account can replace**. Either turns a
|
||
compromise of the web application into root on the host, which is exactly what
|
||
the unprivileged split exists to prevent. The first also meant control of the
|
||
branch was control of root, with no compromise needed at all.
|
||
**If you installed the update helper before this, re-run the installer** —
|
||
the old wiring stays until you do, and the update script now says so loudly
|
||
when it notices.
|
||
- Fixed: **browser notification endpoints skipped the guard that stops the
|
||
server being aimed at your own network.** It was the only outbound request in
|
||
the codebase not going through it.
|
||
- Fixed: **a chat could be put in another account's folder**, and a folder hands
|
||
its system prompt to the chats inside it — so that read a setting across an
|
||
ownership boundary through a field that looks like a tag.
|
||
- Fixed: a `"` typed into the share panel's search box silently stopped every
|
||
checkbox in the panel from doing anything.
|
||
- Fixed: **re-running the installer moved the update channel to `stable`** even
|
||
on a host following `edge`. The channel lives in two places — the environment
|
||
file the page reads and the systemd unit the button obeys — and a re-run kept
|
||
the first while rewriting the second, so an install for some unrelated reason
|
||
left the page naming one channel and the button deploying another. It now
|
||
defaults to what the host already follows.
|
||
|
||
## 0.9.11
|
||
|
||
- The Updates page no longer runs the **Check the remote** button flush against
|
||
the version and commit above it, where the two read as one block.
|
||
|
||
## 0.9.10
|
||
|
||
**The second audit pass: screens that were harder to use than they needed to
|
||
be.** Checked by rendering them in a real browser and measuring, not by reading
|
||
the CSS.
|
||
|
||
- Fixed: **the Prompts admin page put its reference material first.** The
|
||
Variables legend and the Preview run to a screen each and sat above the tabs,
|
||
so the editor — the thing the page is for — started two screens down and every
|
||
tab switch had to move the whole page to be any use. On a short tab it could
|
||
not move far enough and left the panel stranded above a screenful of nothing.
|
||
The editor comes first now, the reference after, and the tab bar stays put:
|
||
measured, it moved 385→642px between tabs before and does not move at all now.
|
||
The tab bar also sticks to the top, so a long panel does not scroll it away.
|
||
- Fixed: **custom themes were three fixed slots.** A fresh instance opened on
|
||
fifty-seven empty colour boxes under three identical headings, and a fourth
|
||
theme could not be made at all. Now: one block per theme you have, plus one
|
||
blank to add the next, with the colours behind a disclosure — so a theme is a
|
||
name and a starting point until you ask for more. Up to twelve. The page is
|
||
half the height it was.
|
||
- Fixed: **deleting a chat left every file it held on disk.** The rows went —
|
||
the message, the attachments, the generated images — and the files they named
|
||
stayed, with nothing that would ever look at them again. Four of the five ways
|
||
a chat can end had this: the delete button, a schedule's task chat, a helper's
|
||
hidden chat, and deleting an account. There is one function that deletes a
|
||
chat now, and it removes the files first.
|
||
- Fixed, and it is what made the above invisible: **a file attached before the
|
||
chat existed never learned which chat it belonged to.** Anything picked on the
|
||
new-chat screen kept an empty `chat_id` for the rest of its life. Six things
|
||
filter on that, so for those files the model was not told they were attached,
|
||
the canvas would not open them, and the cleanup could not find them.
|
||
- **Folders can be nested, which the README has always claimed.** The route has
|
||
handled it since folders existed — cycle guard, depth limit — and the sidebar
|
||
has always drawn a tree; there was simply no control that could ask for it.
|
||
Moving a folder also respects the depth limit now, which only creating one did.
|
||
- The Proxmox container installs the **update helper by default**. A container
|
||
made thirty seconds ago to run one thing is not the shared host the plain
|
||
installer has to be careful about, and an appliance you cannot update without
|
||
a shell is one nobody updates. `INSTALL_UPDATE_HELPER=0` opts out. Docker
|
||
deliberately has no equivalent: updating a container is pulling an image, and
|
||
a helper inside one would need the Docker socket, which is root on the host.
|
||
- The starting points on the new-chat screen are four new ones, aimed at
|
||
somebody who has just stood an instance up and wants to know what is behind
|
||
it. Only a fresh install gets them; an instance that has already seeded keeps
|
||
whatever its administrator has made of the list.
|
||
- `README.md` describes what this actually is again — schedules, reports,
|
||
helpers, image generation, semantic search, quotas, sharing, branding and the
|
||
updates page were all missing, and two things listed as *planned* had shipped.
|
||
It gained sections on Docker, the Proxmox container and updating.
|
||
|
||
## 0.9.9
|
||
|
||
**The first of five audit passes before 1.0.0** — everything that landed between
|
||
0.8.1 and 0.9.8 read as a whole rather than one feature at a time. This one is
|
||
the main logic, the harness, and every instruction a model is given.
|
||
|
||
- Fixed: **every model was told the time in a zone with no name.** On any
|
||
account that had not chosen a timezone — which is the default state of every
|
||
account — the date line shipped as "Times the person gives you are in
|
||
unless they say otherwise", on every request. The code claimed in two places
|
||
that the line disappeared instead. It never had.
|
||
- Fixed: **the prompt preview could not show most of what it previews.** Eleven
|
||
fragments are gated on things that only exist once there is a real chat, and
|
||
the preview has none — so the whole agent surface, both scheduling fragments
|
||
and the helper warning were missing from it whatever you ticked. Editing
|
||
`tool.agent` and pressing preview showed a system message without `tool.agent`
|
||
in it, and nothing said so. Two new controls come with the fix: what kind of
|
||
chat to preview as, and which agent mode.
|
||
- Fixed: **a model in Plan mode was told to use a tool it did not have.**
|
||
`plan_update` is withdrawn in that mode in favour of `plan_submit`, but its
|
||
guidance appeared whenever a plan existed — directly under the line saying
|
||
anything not in your tool list does not exist.
|
||
- Fixed: **reading one knowledge document could fill the whole context window.**
|
||
Every other reader caps what it returns and says so; this one returned the
|
||
document whole, and its description said "in full", so it did exactly what it
|
||
claimed. A long PDF is now cut at 40,000 characters with the model told.
|
||
- Fixed: **the guidance about helpers on a machine was wrong in both
|
||
directions.** It denied that a helper can write files, which is a documented
|
||
option of the tool beside it, and it named seven of the twenty-three commands
|
||
a helper may run — so a model avoided commands it was allowed to use. Both are
|
||
now checked against the real list and the real schema by tests, because prose
|
||
and a constant drift the moment one is edited alone.
|
||
- The tool description for delegating no longer claims a helper gets "the same
|
||
tools". It gets deliberately fewer, and sizing a task against the wrong set is
|
||
how a whole phase gets planned around something that will refuse it.
|
||
|
||
- The Updates page notices when the update helper on a host was installed for a
|
||
**different channel** than the page follows. It is declared in two places —
|
||
`lembas.env` and the systemd unit — and only the installer writes both, so
|
||
editing one by hand would have left the button deploying something other than
|
||
what the page named, with nothing anywhere saying so.
|
||
- Fixed: release notes from a **signed** tag rendered the signature block.
|
||
`_notes_for` stripped the PGP header only, and which header appears depends on
|
||
`gpg.format` — this repository signs with SSH.
|
||
- A `CHANGELOG.md`, kept from now on rather than assembled at release time.
|
||
|
||
## 0.9.8
|
||
|
||
**Updates follow a channel, not a commit.** `stable` tracks the newest `vX.Y.Z`
|
||
tag; `edge` tracks the branch tip. A branch tip is not a release — following one
|
||
means deploying whatever was pushed five minutes ago — so stable is the default
|
||
for anybody who is not the person writing it.
|
||
|
||
- The Updates page shows a **version** rather than a commit sha: `1.0.0` at a
|
||
tag, `1.0.0-7-gd4f56d` seven commits past one, and a bare sha only before the
|
||
first release exists.
|
||
- Release notes come out of the **annotated tag itself**, so no forge API is
|
||
involved anywhere. That matters: the Gitea API this was checked against
|
||
returns a 500 from a server-side panic on exactly the releases endpoint.
|
||
- A tag with a suffix (`v1.1.0-rc1`) is deliberately not a release — git's
|
||
version sort ranks it *above* `v1.1.0`, so accepting one would step a stable
|
||
host onto a candidate.
|
||
- Fixed: `deploy/update.sh` stopped silently after `== fetching ==` on any host
|
||
with no release tags — which was every host. Fetched, not reset, not
|
||
restarted, and no error printed.
|
||
- Fixed: `install.sh` now refuses an `ssh://` repository URL up front instead of
|
||
letting the clone fail as a service user with no key.
|
||
|
||
## 0.9.7
|
||
|
||
**Packaging, and updating without a shell.**
|
||
|
||
- `/admin/updates`: what is running, what is available, and what changed between.
|
||
A button applies it — answered by an **opt-in** systemd helper, because the
|
||
service runs unprivileged and a web application that can restart its own
|
||
service is one whose worst day is much worse. Without the helper the page says
|
||
so and prints the command.
|
||
- `Dockerfile` and `docker-compose.yml`. No secret key, no data and no `.git`
|
||
baked in; loopback only; a TLS proxy expected in front, because a service
|
||
worker and a microphone both require HTTPS or localhost.
|
||
- `deploy/lxc-install.sh` creates an unprivileged Proxmox container and runs the
|
||
existing installer inside it.
|
||
- `/healthz`, which opens the database rather than only proving the socket is
|
||
listening.
|
||
|
||
## 0.9.6
|
||
|
||
**Permissions, quotas and sharing.**
|
||
|
||
- **"What can this account actually do?"** answered on screen, naming *where*
|
||
each permission came from — admin, the baseline, or a group.
|
||
- Users and groups are list-plus-detail, and membership is edited from **one**
|
||
side. It was on both, and a save from either overwrote what the other showed.
|
||
- Reading and writing split for notes, memory and skills.
|
||
- **Quotas on a group** — monthly tokens, concurrent replies, agent wall clock,
|
||
images a day, helpers a reply. Resolved by maximum across a person's groups,
|
||
with zero meaning *no limit* and winning outright.
|
||
- Fixed: **deleting a group or an account left every share naming it behind.**
|
||
`forget_principal` had existed since shares did and was called by nobody.
|
||
- Fixed: `library.share` defaulted to off, so sharing shipped documented as done
|
||
and unreachable — the panel only renders for somebody who holds it.
|
||
- The share panel is its own action with a search box. It used to be checkboxes
|
||
inside the resource's save form, listing every account on the instance, and a
|
||
tick only took effect if you also saved the resource.
|
||
- Reports are shareable, and every listing has a **Shared with me** filter.
|
||
|
||
## 0.9.5
|
||
|
||
**Extraction settings, embeddings, and hybrid search.**
|
||
|
||
- `/admin/extraction`: upload size, image edge, JPEG quality, PDF pages,
|
||
extracted characters, orphan age, extra text extensions.
|
||
- An **embedding model** can be chosen from models flagged for it. Library search
|
||
then fuses keyword and semantic ranking, so *"how do I get paid"* finds a
|
||
document that says *"invoicing"*.
|
||
- **Choosing none is not a degraded mode**: no rows written, no requests made,
|
||
and byte-for-byte the keyword search that was always there.
|
||
- Vectors carry their model and width, and a mismatch is skipped rather than
|
||
scored — comparing two embedding spaces produces a confident wrong answer.
|
||
- Indexing happens in the background as records are written, with a rebuild
|
||
button for everything that already existed.
|
||
|
||
## 0.9.4
|
||
|
||
**An instance can be somebody else's.**
|
||
|
||
- Name, tagline, logo, favicon and launcher icons derived from the logo.
|
||
- The Middle-earth wording is editable data. Leaving a box alone does not freeze
|
||
it, so a later release can still improve the default.
|
||
- **Custom themes** as a set of colours rather than a stylesheet, inheriting
|
||
whichever built-in they start from.
|
||
- Global CSS overrides, served as `/branding.css`.
|
||
|
||
## 0.9.3
|
||
|
||
**Subagents.** A reply can hand a self-contained piece of work to a helper that
|
||
runs on its own and reports back — several at once, so research fans out instead
|
||
of queueing.
|
||
|
||
- A helper cannot ask questions, cannot send helpers of its own, writes nothing
|
||
unless the call asked and the chat's mode allowed it, and on a machine runs
|
||
only a fixed list of read-only commands — in **every** mode, including Auto.
|
||
- Fixed, and it was live in scheduled runs too: an unattended chat that hit an
|
||
approval built a card nobody could see and sat on it for fifteen minutes.
|
||
|
||
## 0.9.2
|
||
|
||
**Image generation defaults an administrator can actually set** — steps, cfg,
|
||
size, sampler, scheduler, denoise, negative prompt, checkpoint, batch. There were
|
||
none: one hard-coded set from the SD1.5 era, and prose in a box as the only way
|
||
to change it.
|
||
|
||
- The samplers and schedulers ComfyUI had been reporting all along are now the
|
||
pickers; nothing had ever read them.
|
||
- The tool's own schema restates the instance's defaults, instead of telling the
|
||
model "Default 512" beside an instance that draws at 1024.
|
||
|
||
## 0.9.1
|
||
|
||
**Everything that arrives is announced, not only chat replies.** A scheduled run
|
||
that filed a report used to light a dot in a corner and say nothing.
|
||
|
||
- A count in the tab title while you are looking elsewhere.
|
||
- **Web push**, so a schedule firing at seven in the morning reaches a browser
|
||
that is shut. Opt-in per device. It is the one thing here that contacts an
|
||
outside service, and `services/push.py` says so plainly.
|
||
|
||
## 0.9.0
|
||
|
||
**A model can schedule things.** There was no tool for it — asked to "remind me
|
||
every Monday", a model wrote a note and reported that it had scheduled
|
||
something, and every screen agreed with it.
|
||
|
||
- `schedule_create`, `schedule_list`, `schedule_update`, `schedule_cancel`, over
|
||
the same rule normaliser the manual form uses.
|
||
- The reply says the resulting timing back in words, which is the only moment
|
||
anybody can check that Monday was understood as Monday.
|
||
|
||
## 0.8.3
|
||
|
||
**An SSH connection may not point at this machine unless an administrator says
|
||
so.** A profile aimed at `127.0.0.1` walked straight past "nothing runs on the
|
||
LLeMbas host" — through a real login, onto the machine holding the database and
|
||
the encryption key. Three positions: off, one named port, or anywhere.
|
||
|
||
## 0.8.2
|
||
|
||
- Fixed: **opening the canvas before a chat existed swapped the whole site into
|
||
the panel.** `hx-get=""` is not "fetch nothing" — htmx looks for the attribute,
|
||
not the value, so the empty one was a real request for the current document.
|
||
- Fixed: the Canvas and Terminal buttons appeared where they could not work.
|
||
- The bottom edge of the shell is no longer drawn, so the sidebar footer and the
|
||
composer stop meeting a line at two different heights.
|
||
- Admin pages scroll in one container; `/admin/prompts` no longer drops you at
|
||
the bottom of a shorter panel.
|