Add a manifest credentials declaration, host-enforced per-binding
resource lists, Request.Credential and ResourceKey set by the host, and
a typed ErrRateLimited that reaches the model as rate_limited.
Assisted-by: Claude:claude-sonnet-5-5 [go]
POST and PUT on /api/chats/agents take folders, a list of path and mode
rows, up to eight. The old folder field still works and is read as a
list with one entry. Responses carry folders, the effective mode of each
row and a notice with the count of shared folders a chat does not mount.
A folder inside another folder of the same agent takes the mode of the
outer one, because the file store gives a node the highest mode among
the roots above it and cannot hold a read only folder inside a write
folder. The editor data shows that mode.
An agent with one folder still exports as version 2. An agent with
several exports as version 3 with folders. A server that reads version 2
refuses version 3. Import keeps a folder that is not found as a row
that is not chosen.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
The fence of a chat now holds every folder of the agent in one layer.
Where a folder is inside another one of the same agent, the outer mode
caps the inner one, and a shared folder is read only. A missing or
unchosen row stops the chat and the error names the row; it never
falls back to the whole home.
The fence budget no longer refuses a chat. Skill layers and the agent
folders are always kept, a shared folder inside a root already in the
layer is skipped, and the rest are kept by node ID until the layer is
full. Trigger tools, trigger runs, sub-agent checks and the agent list
take the whole list. Save and import take a list of folder rows.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
A view can now mount further folders next to the first one, at
folder-<name> with a numeric suffix when two names collide. The store
still caps each mount by the fence. A fenced view counts the shared
folders it leaves out, and the prompt says how many are not mounted.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
An agent may now have up to eight folders, each with its own mode. The
list is stored in a new table for GORM and in a folders field for the
JSON store. The old single-folder pair stays and mirrors the first entry
for one release, so a rollback keeps working. At start the GORM store
copies a single folder into the list; the copy is safe to repeat.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
A chat that does not exist yet has no execution status to read, so its
isolation is unknown by construction. Three places treated that as a
failure:
- Switching an add-on in a draft reset the commands state and never
refilled it, so the "isolation is unknown" notice stayed. The draft
now keeps the state its list gave it.
- The skills list of a draft hid the document skills of the selected
commands provider, because they need isolated commands. A draft list
now offers them; the turn of the real chat still gates them on the
status it reads. The draft add-on switch also reads the skills again.
- The shell row of the Tools menu printed the generic unknown text for
the draft state. It now says nothing about isolation there.
In a real chat, switching an add-on or a tool group now reads the
chat's network view again, so the network controls appear once the
provider serves commands.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt playwright
A connector chat can now propose a trigger, the person answers it in the
chat, and the run reports to the chat.
The trigger tools propose_trigger and list_triggers are offered in a
connector chat when a capability gate allows triggers.propose for the
binding. The gate is a small interface on the service with a deny-all
default, so nothing changes until the application sets one. Change and
delete stay in Studio.
A proposal from a chat stores the binding, chat and thread, a four
character code and a 24 hour expiry. The host writes the proposal and the
code in that chat. "confirm CODE" or "cancel CODE", as a whole message in
the same chat and thread, is handled before any model turn and needs
proposals.confirm. A binding may make 3 proposals an hour. Proposals that
lapse are marked expired, and the proposal card shows that state.
A trigger carries a delivery: the Inbox (default) or the chat of the
proposal, with the modes always, on_change and on_problem. The host writes
the last message of the run, cut and with broadcast mentions broken; a
message of exactly [SILENT] writes nothing; three failures in a row raise
one alert per 6 hours. A failed delivery falls back to the Inbox. Host
messages of a binding go through an outbox of 20 a minute that queues the
rest. While triggers.run is off, runs of such a trigger are skipped, not
replayed, and the record is unchanged. The audit records each step with a
hash and no text.
Assisted-by: Claude:claude-sonnet-5-5 go test ginkgo eslint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The tool that reads the details of a context file topic was called
memory_read. The loader is generic: any component or add-on that declares
a context file uses it, and nothing in it knows which one. Rename the tool
to context_read so the host does not name a feature of one add-on.
Behaviour and limits stay the same.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A turn now asks the add-on broker which running add-ons declare context
files, and loads their index files with the loader that already reads
registered sources. The same rules as for add-on skills apply: a stopped
add-on, one the chat switched off, one the chat's agent does not grant,
and one that cannot work for the user bring nothing. memory_read serves
the topics of those sources. The Memory page lists the files of any
installed add-on, running or not, because the files belong to the person.
The channel notes folder of a chat is now the one propose_change writes
for the same conversation: the binding and a hash of the chat. A note the
person confirmed is read back by the conversation that proposed it. Before,
the loader built the folder from the binding alone, so the two never met.
The prompt names the save tool only in a chat that has it, so a chat that
switched propose_change off is not told to call it.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A connector kind can now declare the roles it gives to a sender and the
capabilities it can grant to a role. The owner of a binding enables a
ceiling of capabilities and maps roles to them. The host stores these
values and returns them with the binding and the kind list, so an add-on
settings page can show them.
Capability names are a closed list owned by the host. Sensitive names
may be listed by a kind but are never granted to a role; only a paired
sender can use them.
Pairing proves that the owner controls a platform account. The owner
asks for a one-use code (valid ten minutes) and sends it as a direct
message to the bot from an allowlisted account. The host checks the code
before any model turn and never logs it. A binding holds three paired
senders, a pairing ends after 90 days, and five wrong codes in an hour
lock the sender out for an hour.
Contract: connector kinds gain optional roles and permissions, and a
message gains optional roles. They need manifest api 2, which the host
already accepts; protocol is unchanged and manifests that declare
nothing new behave as before. The host drops roles a kind does not
declare.
Enforcement of the capabilities by feature is not part of this change.
Assisted-by: Claude:claude-sonnet-5-5 go swag
A connector chat kept the model its agent had when the chat was made, so
changing the agent's model did not reach senders with an existing chat.
Move the kept chat to the agent's current model when a message arrives.
A turn that failed because the tool definitions or the conversation did
not fit the context window showed only the generic error sentence. Name
the cause and the sizes, and give the same sentence in the web chat and
in the bots. A refused tool definition now has its own code.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The thread_context binding setting is removed. When the kind declares
the history ability and the bot is mentioned in a thread, the host
reads the thread once for a new chat, as before. The other guards stay:
the ability check, the bound, the untrusted block, and a failure never
blocks the answer.
A binding stored by an earlier build still loads. The JSON store
ignores the unknown field. The GORM row no longer maps the
thread_context_off column; AutoMigrate does not drop it and it accepts
NULL, so reads and writes keep working. No data is migrated or removed.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Settings page states the ceiling in effect from this field.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The connector list shows, for each binding and each kind, the limit in
effect in both directions, the layer that sets it, the ability the kind
declares and the most an owner may set. A binding accepts
file_receive_max_bytes and file_send_max_bytes; a value above the limit
of the kind is a 400.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The limit of a file is the smallest of the host ceiling, the ability the
connector kind declares, the owner's setting for the binding and the cap
of the inline path. One function decides it and names the layer that
won. The sentences to the sender and to the model are built from that
number and layer, in KiB or MiB, instead of a fixed 1 MB. A guard spec
fails when these files carry a size or a byte count of their own.
The owner can lower the limit of a binding; a value above the limit of
the kind is refused.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A binding keeps an optional limit for the files it reads and the files it
sends. Zero means the owner sets none. Both stores keep the values and the
validation bounds them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
GET /api/chats takes an optional origin parameter: person (chats a
person started), automated (every other chat) or one origin kind such as
trigger or connector. The limit counts the chats kept, so a flood of
chats that a connector or a trigger started cannot push the chats of a
person off a page, and the other way round. Without the parameter the
list is unchanged.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A chat that is not created yet has no add-on bound to it, so its
execution status cannot be read. The host now says so with the reason
draft, and the Home composer shows no commands warning for it.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A harness may call a tool with a context that carries nothing of the
turn. Lend the values of the turn to the tool, so a status read in the
tool shares the turn's observation instead of starting without one.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
load_skill runs with the context the harness gives it, which has no
execution scope, so the status of the commands read as not selected and
the document skills were dropped. Make the descriptor and skill
resolution open a scope when the context has none, and report a scope-less
status read as a programming error: logged once in production, a failure
of the spec in tests.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The spec builds the turn tools under the product scope and then calls
load_skill with a context of its own, as the harness does. It fails
with the not_selected text on master.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The option followThreads says which threads the bot follows without a new
mention: own (default) follows the threads that a top-level mention
started, any also follows a thread that other people started once the bot
is mentioned in it, never needs a mention each time. A mention inside a
thread that people started is answered and, in own mode, not followed.
The followed threads are kept in the add-on's data directory, written
atomically, and expire after a day without a message, so a restart or a
reconnect does not make the bot forget them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A kind can declare options with a few values and a default (manifest
api 2). The host checks the owner's choice against the declaration,
stores it on the binding, shows it in the REST API and sends the value in
effect with every poll. The add-on decides what the option means.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
When a message in a thread makes a new chat, the host asks the add-on of
a kind that declares history for the earlier messages, once, and hands
them to the first turn as a delimited block of untrusted content. The
block is bounded in messages and bytes, keeps the newest, is flattened
and escaped, and is not kept in the transcript. A failure or a missing
ability leaves the turn as it was.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A binding reads the earlier messages of a thread by default. The new
thread_context option turns that off, and the kind list says which kinds
can give a thread's messages. It is stored like send_files: the zero
value keeps the default.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Fail when a file that builds the text of the command network, the
built-in bash or the host-exec setting writes the name of an add-on.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The chat's network view, the administrator view and the line the model
reads about the network of its commands now take their name from the
capabilities.network an add-on declares, or from the title of its
commands tool when it declares none. The sentences no longer say
"Sandbox" or "the sandbox". The API gains a network object with the
title and the preset titles next to the existing addon field.
The text of the built-in bash and of the host-exec setting names an
add-on that provides bash instead of one add-on.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Read addons/*/manifest.json at run time and find the add-ons that shadow a
built-in tool or skill, serve as a connector or declare the execution
provider. One spec scans the string literals of core and pkg for their
names; another builds the command prompts and the network text for every
state and checks that none names a shipped provider. A short allow-list
holds the strings that name a platform or a token format as content.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The prompt, the Tools menu reasons, the network error texts, the CLI help,
the API instructions and the assistant tool descriptions said "Sandbox" for
the add-on that runs commands. Core must not know that name.
The reason a chat has no commands now uses the title the provider declares
("the Zeta add-on is off for this chat") or, when none is installed, says
"the add-on that runs commands". Text that has no provider in hand speaks of
the network of commands or of an add-on that isolates them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Union of both case lists in the add-on suite inventory; the swagger
files are regenerated from the merged code.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Document the optional body of the confirm route, the context-files route,
the card the thread draws for any kind and source, and the Memory tab. The
context-file listing is a person's own action, so the actor scan allows it.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Sandbox add-on states in its manifest that its bash tool runs
commands, under which title, with which note for the model, and that it
supplies the runtime of the document skills. The manifest accepts API 1
to 2. A spec checks that the manifest matches the descriptor in code.
A guard spec fails when the studio package or the add-on tool adapter
compares a string with the ID of the Sandbox again, or when the files
that build the commands text name it.
The contracts suite inventory lists the new specs, and two specs of the
network-policy status that it was missing.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Document skills, the Commands block of the prompt and load_skill no
longer compare an add-on ID or provider with "sandbox". The host asks
whether the add-on that ships a skill serves the chat's commands and
whether the typed execution status reports them isolated. Skills that
need that are listed by the broker from the add-on's manifest, and the
text names the provider by the title its manifest declares.
An add-on whose commands tool declares no descriptor behaves as a
provider of commands under its own title and keeps the isolation check
for its document skills.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The proposal card lets the owner correct the text before confirming.
POST /api/chats/changes/{id}/confirm now accepts an optional JSON body
{"content": "..."}. The text has the same size limit and text check as a
proposal, the file and the audit hash hold what was written, and a
refused text leaves the proposal waiting.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Memory settings page needs to know which index and topic files the
host loads into the prompt. GET /api/chats/context-files walks the
registered context sources, expands the agent and channel folders that
exist, adds the agent profiles and reports each file with its size, the
line count of the index, and the caps. It only reads; the page edits and
deletes through the Files API.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Memory page reads the context-file sources that master added.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Open the proposal store beside the chats (auth database, file or memory)
and document the mechanism for add-on authors: the wire record, the
limits, the gate, the audit, the REST routes and how a cluster shares
it. Update the registry and actor-scan specs for the new tools.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The turn block of a connector chat said that files made in a turn are
attached, but not how to give a file that already exists. Name the
attach_file tool in the block. The block stays under 700 characters.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The host sent only the files a turn made or changed. A person who asked
in a messaging app for a file that already existed got nothing.
Add the attach_file tool. It is offered only in a chat that a connector
started whose binding sends files, so a chat in Studio has no such tool.
It takes a path as the file tools show it, checks the file against the
chat's access (a scoped agent reaches only its folders), the type
allowlist, the size limit and the count, and marks it. When it refuses, the
result says why in plain words so the model can tell the person.
sendFiles now sends the files the turn made, then the attached ones, each
path once and at most five in all. It reads them with the chat's access
resolved at send time. Delivery is the existing send_file op, so every
connector add-on that implements it gets the feature with no code of its
own. A failed send says "I could not send X" and adds the reason the add-on
reports, cut to one short line.
The marks are kept in the memory of the process, like the other
per-turn state of the connector manager.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
read_file and edit_file answered "X.docx is not a text file (43963
bytes)" and gave the model nothing to go on.
Make the error typed and let the chat's tool list add the hint. If the
chat has a skill for the file type (docx, xlsx, pptx, pdf), the error
names it. Otherwise it suggests a script run with the commands tool, with
a library for office types, when the chat can run commands. A chat with
neither is told that no tool opens the file. The skills are resolved only
when a read fails.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A model asks to change the person's memory or an agent profile with
propose_change. The host stores a proposal and writes nothing until the
owner confirms; the write is one workspace change set, so revert_change
and the Files history can undo it. A proposal expires after 24 hours,
is refused when the target changed meanwhile, and is capped at 10
pending per person and 5 per hour per agent.
The host holds no platform code. The origin of the chat is recorded as
the source, so a Studio chat and the connector chats of any platform
use the same path. A gate decides if a chat may propose; the default
refuses a chat that runs a saved agent until grants fill it. Every step
is audited with a content hash, not the content.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Each turn the chat prompt gets the agent profile and the indexes of the
registered context sources, after the persona and before the skill index.
The scope of a source is derived from the chat record: the saved agent, and
the binding when a connector started the chat. The text is the same at each
turn, so the transcript does not grow, and it feeds the refresh key so the
harness restarts only when a file changes.
memory_read reads a topic next to an index as the user, outside the chat
fence. A scoped agent reads its own notes and the user notes and gains no
access to the files. The service exposes ContextSources and
SetContextWriter so other parts of the host can register a source or plug in
the save path.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A source declares a scope (user, agent or channel), a home-relative index
path and a byte cap. The loader reads the indexes that apply to a chat and
returns one bounded block under a fixed header, plus a key that changes with
it. Details of a topic are read next to the index; the path is built from
the registry and the chat, so the model can only name a topic.
The package only reads. A Writer interface lets a later change plug in the
save path, and CheckIndex and CheckProfile return the current entries when a
write would pass a cap, so the model merges them.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Memory and profile changes write to fixed places in the owner's home;
the target a model names is checked against a small pattern so it
cannot leave Memory/. Other features register their kinds. The gate is
the hook the agent grants fill; by default it refuses a chat that runs
a saved agent.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The host must write a confirmed change as the owner, in one titled
change set whose revision it can undo later. Add ReadHome and
WriteHome, which resolve the path inside the home and refuse a path
that leaves it.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Keep what a model asked to change, until the person answers, in a
store with memory, JSON file and GORM implementations and one shared
conformance suite. Create checks the pending cap and the hourly rate in
the same step as the insert, Settle is a compare-and-set so two
frontends cannot both answer, and the audit holds hashes, never
content.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A chat started from a messaging app has its answer read in that app, and
the host attaches the files a turn makes to it. The model did not know
this: it said it could not send files and quoted a sandbox path.
Add a short block to the turn prompt of connector chats. It names the
platform, says that files saved in the chat folder are attached after the
answer, gives the limits the host enforces, and says that files are not
sent when the binding turns them off. If the binding cannot be read the
file part is left out. Other chats are unchanged.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The Network row of the Tools menu could never show the Off, Registries and
All switcher: the host stopped setting `applies` because isolation alone does
not prove network-policy enforcement, and nothing else set it.
Let the provider report enforcement in its typed execution status
(`networkPolicyEnforced`). The host sets `applies` only when that report is
true and the status is available and isolated. The Sandbox reports it for the
gVisor and KVM backends, whose guests reach the network only through the proxy
that asks the host. Providers that do not report it keep the "not enforced"
text.
When the host cannot read the status, give the unknown state a reason
(`unknown_reason`) in the API and the Tools menu: owner unreachable, not
reported, stale, invalid, not selected, no answer or reported unknown. Log the
class of each failure, rate limited. After a failure that a restart of the
add-on owner can cause, let a long turn bind and read again, at most once every
five seconds, instead of keeping the first failure for the whole turn.
The Sandbox now reports "unavailable" with a reason when it started without
settings, and explains a backend it does not know.
When the Sandbox document skills are hidden because the Sandbox is not known to
be isolated, say so in the skill list, in the turn note and in the
`load_skill` error, instead of "This chat has no skills now".
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>