The spec only checks that warmupBatch returns once every request ends;
errcheck flags the dropped error value.
Assisted-by: Claude:claude-sonnet-5-5 golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Report failed warmup requests without failing model load. Document the
warmup limits and use the reported LocalAI benchmark measurements.
Assisted-by: Codex:gpt-6
(cherry picked from commit ec50871dd6)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
qwentts builds its code-predictor and codec-stream graphs lazily per batch
width. With max_batch > 1 the first request that hits a new width (for
example two requests finishing in the same step) stalled the whole batch
for ~5 s on an RTX 5060 Ti; afterwards the same pattern took ~1 s.
When max_batch > 1, Load now synthesizes N short utterances of increasing
length concurrently through the streaming path and discards the audio, so
the slots finish one by one and every width from N down to 1 is built
before the first real request. `max_batch_warmup:false` skips it.
Signed-off-by: nexxtmobile.de <git@nexxtmobile.de>
Assisted-by: Claude:claude-opus-5-5
(cherry picked from commit 0b7d0b40ed)
qwentts.cpp batches concurrent qt_synthesize calls on one context when it
is initialized with max_batch > 1 (its compute worker coalesces them into
batched decode steps; qt_synthesize stays blocking and thread safe). The
LocalAI wrapper never set max_batch and serialized every request with
base.SingleThread, so concurrent /v1/audio/speech requests always ran one
after another.
- new model option `max_batch:N` (alias `parallel:N`, default 1 = current
behavior), passed to qt_init_params.max_batch via qt3_load
- Locking() keeps LocalAI's per-backend request lock only for max_batch 1;
with batching TTS/TTSStream reach the native worker concurrently
- TTSStream routes chunks per request: a fresh id travels as the C
callback's user_data (one shared purego callback, as before), each request
has its own queue drained into its channel, so the native worker never
blocks on a slow consumer and concurrent streams cannot mix
- TTSLive rejects max_batch > 1 (qwentts incremental mode requires a
max_batch=1 context, docs/INCREMENTAL.md)
- README: option, measurements, TTSLive limit, ROCm/gfx1201 hipBLASLt note
- unit tests: option parsing, Locking, concurrent stream routing (fails if
requests were serialized), TTSLive rejection
Measured with qwentts' own tts-server and the 1.7B Base Q4_K_M talker,
four concurrent streaming requests, time to first PCM: RTX 5060 Ti (CUDA)
0.02/0.36/0.70/1.04 s at max_batch 1 vs 0.06 s for all four at 4;
Radeon AI PRO R9700 (ROCm, with ROCBLAS_USE_HIPBLASLT=0) 0.03/0.71/1.36/
2.02 s vs <= 0.11 s.
Signed-off-by: nexxtmobile.de <git@nexxtmobile.de>
Assisted-by: Claude:claude-opus-5-5
(cherry picked from commit da0c5435b5)
A model can cite a plausible deep link it never opened, written from
memory. The client cannot tell it from a link a search tool returned,
because server-side MCP tool results never reach it.
evidence_check.urls compares every http(s) link of the final answer,
bare or inside a Markdown link, with the URLs of the request (system
prompt, system, user and tool messages) and of the request's successful
MCP tool results. Links are compared without scheme, fragment, "www."
and trailing slash; a link counts as retrieved when it equals an evidence
URL or is a shorter form of one at a path or query boundary. Unbacked
links (at most five) are listed in an appended note; urls_note replaces
the default note, with {urls} as the placeholder for the list.
Assistant messages are not evidence, so an invented link is marked again
when the client sends it back on the next turn.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 523e8cd3c4)
ChatEndpoint gained a compressor parameter after the spec was written, so
the openai test package no longer compiled.
Assisted-by: Claude:claude-sonnet-5-5 go golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The comment in sourceURLs named ExecuteMCPToolCall, which the tool
consumer matrix test (core/services/addons/tools) reads as a marker of a
file that discovers or executes tools. evidence.go only reads tool
output, so reword the comment instead of classifying the file.
Docs-Not-Needed: comment-only change, no behaviour change
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 1043d3cbc4)
Keep balanced parentheses in source URLs and decode MCP text arrays before
extracting links. This preserves Wikipedia paths and escaped query
separators instead of appending truncated citations.
Assisted-by: Codex:gpt-6
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 9b81cb20ab)
A system prompt that tells a model to look things up first does not stop
it from answering from its own knowledge, for example when the question
comes with a picture.
profile.require_tool makes the first model call of a chat request that
offers the profile's server-side MCP tools use tool_choice "required".
Every follow-up call of the server-side tool loop is made with the
client's tool_choice again: a "required" that stayed in place would make
the model call a tool after every tool result and never answer until the
iteration limit.
With a tokenizer template the backend enforces "required" with a grammar
built from the chat template's tool-call format. Where LocalAI builds the
tool-call grammar itself, the first call's grammar leaves out the
no-action function instead.
The first call is not forced when no server-side tool was offered, when
the client set tool_choice or function_call itself, or when the request
does not end with a user message, so a request returning a client tool's
result cannot bounce tool calls between client and server.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 2801938aac)
A research profile asks the model in its system prompt to cite its
sources, but a model can answer from a search result without naming a
single link, and the client never sees the server-side tool results that
held them.
evidence_check.sources appends one line with the distinct http(s) URLs
returned by the request's successful MCP tool calls (at most five, in
order of appearance) when the final answer names no URL itself. An answer
that already links something is left unchanged, and nothing is added when
no tool result holds a URL, so a link is never made up. The label is
configurable through evidence_check.sources_label (default "Sources") for
profiles that answer in another language.
It runs in the existing evidence gate, on the same streaming and
non-streaming paths as the number note.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
(cherry picked from commit 4ee6ff1395)
Four recent changes added English strings without the other locales, so
the locale completeness gate failed in the UI unit tests. Add the keys
to the eight locales. The baseline stays as it is.
Assisted-by: Claude:claude-sonnet-5-5 node
The package already registers a suite in p2p_suite_test.go. A second
RunSpecs call made the whole package test binary fail with "RunSpecs
called more than once" even though every spec passed.
Assisted-by: Claude:claude-sonnet-5-5 go golangci-lint
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Reserve capacity when selecting a worker and release it when forwarding
ends. Completed connections no longer bias routing toward new workers.
Keep active counts through offline periods and choose randomly on ties.
Add regression tests and document per-instance TCP connection counts.
Fixes#12992
Assisted-by: Codex:gpt-6
(cherry picked from commit 66cf106490)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The connector specs broke when bindings began to need a kind, to hold
no capability until the owner enables it, and to move file bytes by
handle only. The Telegram specs now name the kind. The Slack file spec
enables files.upload and pairs the sender, since a sender without a
role needs a pairing to send files. A test transport that can move
files gets the transfer table of the application, so its add-ons
declare the files ability as installed ones do.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
A trigger run or an agent job has nobody to approve a step, and so far
the approval mode auto_readonly and the act.* grants were only recorded.
The prompt was the only thing that kept such a run from acting.
Every tool call of an unattended run now goes through one check at the
call. A tool that only reads runs. Any other tool needs the grant of its
class, and the check fails closed:
- act.files for write_file, edit_file, move_file, delete_file and the
built-in bash;
- act.addons, or act.addons:<id>, for add-on tools whose declared kind is
not read, plus act.spend when the add-on gates the tool with a
permission;
- act.spend for MCP tools.
A trigger holds its new acts list within the grants of its agent. A
trigger that declared read only runs, or that a binding in auto_readonly
mode made without asking, is refused every non-read tool whatever it
holds. A binding that cannot be read counts as auto_readonly. A job
holds the act grants of its agent. Sub-agents follow their parent, and a
saved agent cannot be started from an unattended run.
A refused call answers with a plain sentence the model can pass on, not
an error, and writes one audit log line. Triggers set acts through the
REST API; a model never can. A trigger whose plan names a file it writes
keeps act.files.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo swag
The Tools list gains a group, "What the agent may change", with one
row per builtin grant: its title and a plain-words description, and a
mark on the sensitive ones. The agent form switches them through
useAgentTools; a chat of a saved agent lists the grants the agent
holds, read only. Both draw the group with the same useToolGroups and
SwitchPanel code.
The group has no group setter, so Select all and Disable all, and the
"N of M on" count, leave it alone: a bulk click must not grant a
right.
Assisted-by: Claude:claude-sonnet-5-5 node
The Skills page asks for the skills of the saved agents with
agents=true and shows them with the agent's name. A skill of the
agent layer says who proposed it, can be removed (to the Trash) and
is not offered to other agents. The list of a plain chat or of the
agent form never carries them.
Assisted-by: Claude:claude-sonnet-5-5 go node
A saved agent keeps its own skills in LocalAI/agents/<agent>/skills.
Only a chat that runs that agent sees them. They sit between the folder
and personal layers, so an agent's version of a skill wins for that
agent, and they count as granted without being listed in the agent's
skills.
An agent writes one by a skill change proposal, as before; the new
layer only reads them. A proposal for an agent skill cannot take the
name of a core skill or of a document slot (read from slots.json).
When writing-skills loads in a chat of a saved agent it starts with
"Do not write files. Call propose_change with kind skill.", and the
skills.write grant makes it visible to the agent.
The Skills list gains the agent layer, with the agent's ID and name,
and a skill can be read or removed (to the Trash) with the agent
query parameter.
Assisted-by: Claude:claude-sonnet-5-5 go swag
Add the pinned Q8 preview for llama.cpp and document installation.
Keep the HTDemucs source checksum aligned with the fix in #12919.
Assisted-by: Codex:gpt-6
(cherry picked from commit 66b4228310, mudler/LocalAI#12922)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add AD-Q4_K_M and Q8_0 builds with pinned URLs and verified checksums.
Group the builds and document installation with a 32K default context.
Assisted-by: Codex:gpt-6
(cherry picked from commit 1f41de1611, mudler/LocalAI#12926)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The nightly checksum job writes the LFS sha256 of main into each
entry, so a commit-pinned uri cannot follow it (see #12994). The files
on main are identical to the previously pinned revisions, so no sha256
changes.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Dorian Magasic <dorian.magasic@turintech.ai>
(cherry picked from commit 32ccb7dcc5)
Add eight TurinTech Artemis GGUF builds of Qwen3.5 2B, 4B and 9B, each
with the repo's F16 vision projector, pinned by revision and SHA-256.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Dorian Magasic <dorian.magasic@turintech.ai>
(cherry picked from commit 622ac48d0f)
The nightly checksum job writes the LFS sha256 of main into each
entry, so a commit-pinned uri cannot follow it (see #12994). The files
on main are identical to the previously pinned revisions, so no sha256
changes.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Dorian Magasic <dorian.magasic@turintech.ai>
(cherry picked from commit 45a766b3bf)
Add seven TurinTech Artemis GGUF builds of Gemma 4 E2B and E4B, each
with the repo's F16 vision projector, pinned by revision and SHA-256.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Dorian Magasic <dorian.magasic@turintech.ai>
(cherry picked from commit 5e08aa4958)
Add OpenBMB's 1.3B Chinese/English document parser to the gallery as
three llama.cpp entries: the recommended Q8_0 build, a smaller Q4_K_M
build for constrained hosts, and an optional DSpark build that pairs the
Q8_0 target with the official speculative draft.
All entries install the required F16 4x vision projector, default to an
8,192-token single-slot context, and keep thinking disabled so
transcriptions land in the response content. Q8_0 is the declaring entry;
the other two are selectable variants.
Tested text, table, and mixed Chinese/English page OCR through LocalAI
on an RTX 4090; the DSpark build reached about 0.96 draft acceptance. The
model's projector type is new in llama.cpp, so the entries need a backend
built from the current master pin; the latest release backend predates it.
Assisted-by: claude code:opus5.5
Signed-off-by: Caldalis <zyk_computer@163.com>
(cherry picked from commit 91ff3f06cc)
EnableTransfers rewrote the add-ons and the gateway while the sync loop
of the connector manager already read them through Kinds, so the
race-detector run of the multi-frontend job failed. A read-write lock
now guards both. The runtime is copied out before a call, so the lock is
not held across it.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo
The Files section showed only the limit in effect. It now also says,
for receive and send, whether the add-on declares a largest file, no
limit, or no file support, shows the host ceiling, and gives help text
on each field. A direction the add-on does not support is disabled.
Assisted-by: Claude:claude-sonnet-5-5 node
The GitHub add-on description gained a clause about writing, but the
committed development index kept the old text, so catalog.sh --check
failed in CI. Regenerate it with catalog.sh.
Assisted-by: Claude:claude-sonnet-5-5 sh
The draft tools and add-on lists, and the tools list of a stored chat,
read the execution status without an execution scope. The host logged
an error and reported the status as not selected. They now run in a
scope; a draft has no conversation, so its status says "draft".
In the Home composer, switching a built-in tool group on or off reset
the commands state to the loading value and never read it again. The
composer then showed the unknown warning for good. The Hide button also
did nothing for that state, because it needed a binding identity that
the loading value does not have. A draft now keeps the state the list
gave it, and Hide works for any notice that is shown.
Assisted-by: Claude:claude-sonnet-5-5 go ginkgo node
The notice came from navigation state, which survives a refresh, so it
came back after add-ons were on again. A frontend also reports add-ons
as off when the owner does not answer for a moment, which looked the
same as the instance switch being off.
Home now reads /api/features on mount, drops the notice and clears the
history state when add-ons are on, and clears the state on dismiss.
/api/features gains addons_enabled (the instance switch alone), so the
notice can say "not answering right now" with a retry button when the
switch is on and the owner is silent.
Assisted-by: Claude:claude-sonnet-5-5 eslint node go-test
A binding needs a kind now, so the route specs name the platform, and
the spec for an offline binding checks that nothing is kept while no
add-on serves the kind.
Assisted-by: Claude:claude-sonnet-5-5 go test
The add-on guide names one manifest api, one way to move a file and no
migration path, and drops the section on arguments of older tool
versions. The Swagger files are generated again, which also brings in
fields that the last generation missed, and the test of the apps view
now emits every field it documents. The inventory of the add-on suites
lists the specs by their new names.
Assisted-by: Claude:claude-sonnet-5-5 go swag
A connector add-on no longer carries file bytes in the connector
envelope. The ops media and send_file need a transfer handle, and the
gateway refuses a call to them without one, or for a kind that declares
no files.
- The manifest field files.transfer, the inline file of a request and
of an answer (ConnectorFile) and the 1 MiB cap
(MaxConnectorFileBytes) are removed. The 2 MiB limit of a connector
call between hosts drops to 1 MiB, because it holds text and no file.
- A kind that declares no files moves no file: the host answers a sender
who attaches one with a plain sentence and does not offer attach_file.
A direction left out of a declared files block still gets the host
ceiling.
- The inline layer is gone from the file limit, from the API
(inline_bytes) and from the Files section of the connector page.
- The Telegram and Slack add-ons keep only the streaming paths. The
tests moved to the handle path, and the studio test rig gives every
connector add-on a table of handles.
Assisted-by: Claude:claude-sonnet-5-5 go test node
No release carried add-ons, so nothing reads older add-on data or
manifests. Delete the code that did.
- Connectors: the kind and the add-on of the Telegram connectors that
core once carried are gone (LegacyAddon, KindTelegram, the stored
binding normalization, the platform titles). A binding needs a kind,
and its add-on is the one that serves the kind. The conversion of the
old agent pool lists every connector as dropped instead of making a
Telegram binding, and its notes say so.
- The library index reads only the id field of an entry, not addonId.
- A host control that has a capability block (the network policy) is
attached by that block alone, not by a tool that replaces bash.
- The Office create tools no longer answer for the argument names of an
earlier schema, and the identity file note about old addresses is
gone.
- The known debt of the provider-name guard drops the entries of the
conversion and of the resource listing shim, and its ceiling goes
from 13 to 5.
Assisted-by: Claude:claude-sonnet-5-5 go test
No release ever shipped add-ons, so there is no installed base on api 1.
The manifest must now declare api {"min": 2, "max": 2}; a manifest with
api 1, no api or a wider range is refused with a message that names the
range.
Every field that was gated on api 2 is now part of the contract, so the
Min*API constants and their gates are gone, and so are HostAPIMin and
the apiMin host field. Three older-manifest shims go with them: the
resource listing the host implied for one add-on, the legacyArgs hint
for arguments an older version of a tool took, and the tests that
checked the gates. All shipped manifests and the test fixtures declare
api 2.
Assisted-by: Claude:claude-sonnet-5-5 go test
For a kind that declares files.transfer, the connector manager gives the
add-on a handle and not the bytes, in both directions.
Inbound, the add-on streams the file to the host. The chat upload then
reads it from the blob store, so a 6 MB PDF reaches the chat. Outbound,
the manager checks the access of the chat to the file, gives the
process that runs the add-on only the hash and the size of the content,
and the add-on pulls the chunks. The file limit of such a kind has no
inline cap: it is the smallest of the host ceiling, the ability the kind
declares and the limit the owner set for the bot, and the sentence names
the number and the layer. A kind without transfer keeps the 1 MiB inline
cap, and its sentence says so.
Checks: the files of one message may add up to three times the limit;
the media type that the add-on states must be among the types the kind
declares; an outbound file must have a type that the kind declares and
must not start with an HTML page. Every transfer writes one audit line
with direction, binding, size, result, layer and time, and no name or
content. The binding page no longer says "larger files need streaming"
for a kind that streams.
The specs run the real Telegram and Slack add-ons, the real gateway and
the real file operations over scripted platform APIs, with a 6 MB file in
each direction.
Not in this change: a daily byte counter per binding, and a virus scan
(the table has a hook that accepts everything).
Assisted-by: Claude:claude-sonnet-5-5 go test
Both add-ons implement the streaming side of the connector route: they
read a file from the platform and put it to the host chunk by chunk, and
they pull the file to send from the host and stream it into the
platform upload. Neither holds more than one chunk in memory.
Slack uploads with the external upload flow and tells Slack the length
first. Telegram sends a multipart body of known length, and a picture
over 10 MB goes as a document, because sendPhoto refuses more. The
client of a streamed file has no total timeout; the context carries the
bound.
The manifests declare the limits that the platforms state, and
files.transfer. Telegram moves to manifest API 2 for it. The limits are:
Slack: 1 GB per file, as in the help center article
https://slack.com/help/articles/201330736-Add-files-to-Slack
The upload API names no per-file cap
https://docs.slack.dev/reference/methods/files.getUploadURLExternal
Telegram, public Bot API: bots download up to 20 MB with getFile and
send up to 50 MB (10 MB for photos)
https://core.telegram.org/bots/faq
A local Bot API server lifts both (no download limit, 2000 MB upload)
https://core.telegram.org/bots/api#using-a-local-bot-api-server
The add-on has no setting for another server, so it declares the
limits of the public one.
Slack gets the host operation prefix connector. in the official host
policy, as Telegram has, so it can call the two file operations.
Assisted-by: Claude:claude-sonnet-5-5 go test
A connector add-on that declares files.transfer no longer carries file
bytes in the connector envelope. The host puts a random handle in the
request of the op media (write) or send_file (read), and the add-on
streams the file with two new host operations, connector.file.put and
connector.file.get, in chunks of at most 256 KiB.
The process that runs the add-on registers the handle when the call
arrives and ends it when the call returns. A handle is bound to the
add-on, so another add-on gets the same answer as for an unknown
handle. An unfinished inbound file is dropped with its partial bytes,
and a handle with no call for two minutes expires. Inbound bytes go
to the blob store of the workspace, outbound bytes come from it, so a
cluster needs the shared object store and no shared disk. The file is
never held in memory. The host overwrites the size and hash of an
inbound file with its own record.
The change is additive: the field files.transfer needs manifest API 2,
a kind without it keeps the inline path and its 1 MiB cap, and the host
never sends it a handle. A call that carries a handle may run up to 15
minutes. The ceiling of a file also stays under the largest file of a
workspace.
Assisted-by: Claude:claude-sonnet-5-5 go test
Echo's RealIP trusted X-Forwarded-For and X-Real-Ip from any peer
when no IPExtractor is set. That fed the login and registration rate
limits and the API trace client address, so a client could dodge a
limit by rotating a forged header.
Add pkg/clientip, one resolver for the whole server. With
--trusted-proxies / LOCALAI_TRUSTED_PROXIES (IPs and CIDRs, empty by
default), the forwarded chain is read only when the TCP peer is
listed. The client is the right-most X-Forwarded-For entry that is
not a trusted proxy; a malformed entry stops the walk. With the flag
unset every request resolves to its TCP peer.
The resolver is set as Echo's IPExtractor and used by the access log
(--log-client), so all consumers agree.
Assisted-by: Claude:claude-sonnet-5-5 go gofmt
The grants of an agent were stored and listed, but only the proposal kinds
read them. The connector gate now reads them too, so a tool exists when
the sender holds the capability on the binding, the agent holds the grant
and the chat did not switch the tool off.
It covers triggers.propose, triggers.run, talkback.channels,
talkback.new_channel and files.upload. Every question that goes through
the gate asks the grants: the capabilities of a turn, the check of a
feature that has a sender, and a run without a sender, so a trigger that
fires from a message pauses when the grant goes.
An agent with no grant list keeps the capabilities that are not
sensitive, as before. A sensitive one (talkback.new_channel) needs the
explicit grant. Once any grant is on the list, the list is the whole
answer. The act.* grants are not enforced: the action policy of unattended
runs that uses them does not exist yet.
Assisted-by: Claude:claude-sonnet-5-5 go test
A write tool of an add-on that holds a credential was protected only by
the write mode of the connection and the confirmation of the proposal.
In a connector chat it now also needs the capability tools.write for the
sender of the turn, through the same Authorize path as the other
capabilities: the tool is not offered without it, a call is checked again
and every decision is audited. The default is no.
The rule is by shape, not by name. The manifest says an add-on holds a
credential, and contract.ToolPermission maps its non-read tools to
tools.write. The chat reads the permission from the tool definition, so
the core names no add-on.
Also close a hole in local-owner mode, where a chat turn and the browser
share one credential: a proposal that waits for a person could be
confirmed from a chat turn. The call of a chat tool now carries a mark,
and the owner refuses to confirm such a proposal under it. A remote owner
does not see the mark; local-owner mode has no remote owner.
Assisted-by: Claude:claude-sonnet-5-5 go test
One manifest-driven guard replaces the Go-only provider guard. It reads
addons/*/manifest.json and fails when the Go strings of core and pkg,
the React UI source, the locale files or the core docs hold the id of an
add-on with a role (as a whole string or a tool-name prefix) or one of
its declared titles as a whole word.
Third-party mentions (OAuth login, release sources, token formats) and
ordinary words sit in a commented allow-list. The special cases that
remain (propose_change memory kind, the Account memory tab, the Telegram
conversion, the Tables listing shim) sit in knownDebt, each with the item
that removes it, and a ceiling that only goes down.
Reword the sandbox sentences in the container docs and use neutral
sample names in the UI catalog page.
Assisted-by: Claude:claude-sonnet-5-5 go test
Bindings saved before permissions were enforced have no capabilities, so
they deny files from senders, triggers, memory writes and talk-back, and
the owner could only fix that through the REST API.
The shared connector page now has a section for it, so every connector
add-on gets it: a switch and a description for each capability with the
sensitive ones marked, the role matrix (never a sensitive capability, never
above the ceiling), pairing with a code shown once, the approved channels
for talk-back, and a read-only view of the access check with the latest
decisions. A binding with no capability shows a notice that says why the
features are off, with an "Allow the usual" button that turns on the
non-sensitive everyday set.
The kinds endpoint returns the closed list of capabilities with a title,
a description and the sensitive and usual flags. The field is additive.
Assisted-by: Claude:claude-sonnet-5-5 go node playwright swag