The local sandbox is dropped before it was built. Every hard problem in it
came from running on the machine that holds the database and the encryption
key: the service user cannot traverse /home, granting it needs ACLs,
RLIMIT_NPROC is counted per uid so a fork bomb starves the server too,
--size only applies to tmpfs so there is no disk quota, and the bind list
is a standing invitation to widen until the sandbox is decoration.
Over SSH, isolation is somebody's considered choice of host -- a throwaway
container with one project mounted into it -- using tools far better at it
than anything that could be built here. It is also the only version that is
honestly multi-user: each person brings their own credentials and their own
machine, and picks a project directory on it.
So ProtectKernelTunables goes back. It was removed for exactly one reason,
that bubblewrap cannot mount /proc without it, and that reason is gone. The
agents settings group loses everything bwrap-shaped with it.
What this costs, and the admin copy has to say so: there was a network:False
switch that made exfiltration from a compromised reply impossible, and over
SSH there is no equivalent, because the network belongs to the far side.
The security of an agent chat is now the security of the host behind its
profile, and LLeMbas cannot tell a scratch container from a live server.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three features turn out to be one mechanism: a command waiting to be
approved, a question the model wants answered, and "this reply is waiting
for you" are all — stop the generation, put an interactive block in the
bubble, wait for a POST, carry on. So there is one primitive, and the only
thing using it so far is `ask_user`: a model can offer you a few answers
and a box to write your own.
The shell executor is not here yet. This lands first on purpose, because
it is the riskiest machinery in the feature and it is worth having working
before any subprocess exists to complicate it.
Two things about where the pause sits. It pauses a round, not a call: a
round's calls run together under a semaphore, and parking four coroutines
on four separate answers inside that gather would queue them behind each
other invisibly. And Stop had to be taught about it — `cancel` is read
between streamed chunks and there are no chunks while paused, so the
button did nothing at all until `request_stop` learned to resolve the
pause itself.
Also here: a risk class on every tool (read, write, execute), which is
what the four permission modes will be a table over, and the systemd unit
loses ProtectKernelTunables. That last one is not tidying — it
bind-mounts /proc/sys read-only, which stops bubblewrap mounting /proc at
all, and the obvious workaround would expose this process's environment
and with it the encryption key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things the running instance needed.
**Registration toggle.** Admin -> General, backed by a new settings table
group rather than the environment. LEMBAS_ALLOW_SIGNUP now seeds only the
initial value: once an administrator saves the setting, the stored value
wins. The alternative -- environment always winning -- means a toggle in
the UI silently reverts on the next restart, which is worse than not
offering one. Closing registration also removes the "Create one" link
from the sign-in page, so the link never leads somewhere that refuses.
**Password change**, on the user settings page. Changing a password
revokes every other session and immediately re-issues a cookie for the
current one: if the reason for the change is that somebody else knows
the password, leaving their session alive defeats the point, but signing
the user out of the tab they are standing in is merely rude.
**deploy/ is now host-agnostic.** This repository is public, so the unit
and vhost became templates with __PREFIX__ / __SITE_HOST__ / __APP_PORT__
substituted at install time, and every path, hostname and port moved to
environment variables. REPO_URL defaults to the checkout's own origin so
a fork deploys itself. Machine-specific values belong in private notes,
not here -- CLAUDE.md now says so.
83 tests, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LLeMbas now runs end to end. Register, add an OpenAI-compatible
connection, and hold a real streaming conversation organised into
folders. Verified against the local llama-swap instance.
Streaming is the one genuinely tricky part. Sending a message returns
two HTML fragments -- the user bubble and an empty assistant bubble
carrying an sse-connect -- and that attribute is the ONLY thing that
starts a generation. Rendering an incomplete assistant message as a
streaming shell falls out of the same template, which means loading a
page whose last reply never finished simply picks it up again.
Details worth knowing about, each commented where it matters:
- SSE payloads are split across several data: lines. A raw newline in
one data: line truncates the event, which shows up the first time a
model emits a code block.
- Markdown is rendered server-side by the same helper for both the page
and the final streamed frame, so the two cannot disagree. The fence
renderer is replaced outright rather than using markdown-it's
highlight option, which re-wraps output in a second <pre>.
- escape_text is html.escape, not nh3.clean_text: it escapes character
by character, so escaping stream chunks separately equals escaping
the whole string.
- The stream opens its own session via session_scope(); it outlives the
request handler and the dependency-scoped session may be closed.
- Deleting a folder keeps the chats inside it (FK is SET NULL). Losing
a conversation to a mis-clicked folder delete is unforgivable.
- Login failures use one message for "no such account" and "wrong
password" so the form cannot enumerate registered addresses.
Also adds deploy/ for the gamebox install at https://chat.lan: system
unit, nginx vhost with buffering off (buffering on turns streaming into
one lump at the end), and install/update scripts following the same
service-user and /srv bind-mount conventions as llama-swap and comfyui.
70 tests, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>