Repository navigation
feat(addons): installable add-ons foundation and the Tables add-on - #12702
Open
mudler-agent wants to merge 264 commits into
Open
mudler-agent wants to merge 264 commits into
mudler-agent wants to merge 264 commits into
Conversation
There was a problem hiding this comment.
gosec found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
mudler-agent
force-pushed
the
feat/addons-tables
branch
from
October 15, 2026 14:05
cfae1ff to
2be58a9
Compare
mudler
marked this pull request as ready for review
October 16, 2026 16:48
| ) | ||
|
|
||
| // HostCredentialHeader carries the host credential of the incarnation. | ||
| const HostCredentialHeader = "X-Addon-Host-Credential" |
| if path == "" { | ||
| return s, nil | ||
| } | ||
| b, err := os.ReadFile(path) |
Forward typed addon operations from any frontend to the designated owner over pinned mutual TLS with signed, body-bound, expiring envelopes and replay protection. The owner rechecks current credential and account status and fails closed when identity or resource policy is unavailable. Relay JSON, coarse lifecycle progress, attachment bytes and immutable UI assets through the receiving frontend with bounded streams and cancellation. An uncertain mutation dispatch reports the original operation and is never retried as a new one. Add actor-bound read-only lifecycle status lookup, feature and admin gated routes with CSRF checks, API discovery entries and docs. Verify gateway, runtime, lifecycle, packages, catalog, contracts and prerequisites. Run auth application and config tests and race tests. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The owner handler turned every Relay error into an aborted connection, so a denial made before the first byte (invalid kind, missing resource policy, refused or revoked resource) reached the receiving frontend as "owner unavailable" instead of forbidden. The same decision therefore produced different errors depending on the frontend that served it. Track whether the owner committed any response byte. Before the first byte, return a typed status through one shared mapping (403 for a denial, 503 for unavailability) that the frontend already decodes back into ErrForbidden or ErrUnavailable. After the first byte the status can no longer change, so the stream is still aborted and the frontend sees a truncated stream, never a clean end. The receiving frontend now also bounds each relay kind by its own limit instead of the file limit: progress and status use ProgressBytes, asset uses AssetBytes, json is capped at 2 MiB and file keeps Bytes. An unknown kind is denied before the owner is contacted. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add behavioural coverage for the owner forwarding gateway and replace assertions that could not fail against an always-forbidden or empty implementation. - Owner denials before the first byte reach every frontend as forbidden; a revocation after bytes were written still aborts, and an owner that cannot serve reports unavailable. - Per-kind frontend budgets: file, progress, asset and an unknown kind. - A CA signed peer that is not pinned is refused by the owner handler, and a CA signed owner that is not the pinned owner is refused by the frontend before any request is sent. A pinned control proves the pin, not the CA, decides the outcome. - Gateway.asset against a real installed package: generation, operation ownership, asset size limit, media types, undeclared files and traversal. - The registered /api/addons routes through real authentication: forged forwarding headers, feature gate, administration gate and same-origin requirement for cookie mutations. - Replace the construction, admission and typed-ingress tests with assertions that fail on an always-forbidden implementation: complete pin and address validation, admission held while the output is described, the original operation ID of an unknown mutation outcome, and unknown lifecycle fields leaving the journal untouched next to an accepted request in the same setup. - Keep the suite inventory in sync. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The dispatch and relay handlers carried only @tags and @router, so the generated API reference had no addon operations, parameters or responses. Add full annotations (summary, description, content types, request body, success and failure responses) and regenerate the Swagger artifacts with the repository's generator settings. Raw JSON fields of the typed request, result and preview use the existing swaggertype:"object" convention so the schemas resolve. The HTTP endpoints now derive 403 versus 503 from the same gateway.StatusFor mapping the owner uses, so a local error and a forwarded error of the same kind produce the same status. The two routes are deliberately not wired into the LocalAI Assistant tool catalog: lifecycle mutation tools are deferred until the brokered tool integration defines confirmation semantics, and the route map test only requires an entry for each registered tool. Record that decision in the addon documentation instead of weakening a test. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Gateway.Relay arms a goroutine that expires the response write deadline when the relay context ends. The deferred cancel fires right after the relay returns, so the goroutine could win the race against the done signal and expire the deadline just before the caller wrote its final bytes. A typed denial status written by the owner handler, or the final flush of a buffered response, was then dropped and the receiving frontend reported the owner as unavailable. The failure showed up under the race detector. Only expire the deadline while the relay is still running. A regression test arms 200 relays and checks that no deadline is ever moved to the past after Relay returns. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Carrier.Relay returned success when reading the owner response ended with a clean end of stream, even if the relay context had been canceled or had expired at the same moment. A disconnected or timed out relay could therefore look like a finished one. About one run in ten of the interrupted progress cancellation spec failed under load for this reason. Return the context error when the stream ends while the context is no longer live. The spec now passes 120 of 120 runs under parallel load. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The owner mapped every failure other than a few unavailability errors to 403, so a runtime that was not installed, not serving, in recovery or busy, an output that ended before its first byte, and any unclassified error were reported to users as a policy denial. Only explicit authorization, policy and request rejections are 403 now. Every other error, including an unclassified one, is 503, so an unknown failure is never reported as a decision or as success. The owner marks a refusal that happened before anything ran, and the receiving frontend decodes it to plain unavailability instead of an unknown mutation outcome, so both frontends report the same error for the same cause. The receiving frontend also copied the owner's attachment headers before the first body read. A failed first read then returned an error response that still carried Content-Disposition. The headers are now copied only when the first byte is committed or the stream ends cleanly. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A previous change let the owner mark a typed 503 with a refusal header so a frontend reported a refused mutation as plain unavailability. The owner chose the marker by error identity, not by whether it had already run anything. The lifecycle manager can return a runtime or recovery error after it journaled and applied the operation, so the marker made an uncertain mutation look as if it had not run. A caller could then submit it again under a new identifier. Remove the marker. A mutation that gets a 503 or a transport failure after dispatch stays an unknown outcome with its original operation identifier, and the frontend does not dispatch it again. The 403 and 503 taxonomy, the explicit denial allowlist and the fail-closed default stay. An error that wraps both a denial and an unavailable cause is 503, and a test pins that order. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A mutation that failed as unavailable on the owner's own frontend was returned as a plain 503 with no operation identifier. The owner can fail after it recorded or applied the mutation, so a caller could submit a fresh mutation for work that already ran. Classify the local dispatch result with the same status taxonomy the forwarding carrier uses, in one shared function. A mutation that is unavailable now returns an unknown outcome with the original operation identifier from every frontend. Reads, status polls and denials keep their status. Add endpoint tests that drive the dispatch route and check the status, error type and operation identifier for an unknown outcome, an unavailable read, a denial and the owner-local mutation. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The addon endpoints annotated their failures with the generic OpenAI error schema. That schema has no operationId, so the outcome_unknown body was not described, and the type of code did not match. Define the error body in the addons endpoint package, emit every failure through it, and reference it from the annotations. Regenerate the Swagger files from the annotations. A test now checks that every field a 503 response emits is documented. Describe the body in the addons guide. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Classify only the read and operation-status actions as non-mutating, so an action added later reports an unknown outcome with its original operation identifier until it is explicitly classified. Relay requests remain read-only by kind. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add an owner-local policy store that decides who may touch an addon resource, separate from replaceable packages. Resources are opaque host IDs bound to the trusted addon identity and an immutable owner. Roles are Viewer, Editor and Owner on whole resources. Installation administration confers no data access, named-user mode rejects anonymous and legacy principals, and explicit local-owner mode turns sharing off. Switching the mode of populated data needs a stopped, backed-up migration; shared data cannot return to local-owner mode. Admission rechecks the account and credential, serializes writes per resource and shares one lock with grant changes. Revocation is durable first, invalidates pending proposals through a seam, cancels the user's admitted work and returns only after it ended or the bound expired. New resources are registered pending and reconciled after a crash, so an orphan never becomes reachable. The owner installs the policy as the gateway admission and resolver; frontends and hosts without it keep denying. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The gateway mapped every failed host account check to a denial, so a broken account store looked like a revoked user. A check that wraps the unavailable cause now returns 503 from dispatch, relay and the forwarded-actor verifier; any other refusal stays 403. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A downgrade that failed to drain left the lowered grant durable, so the identical retry saw no change and was acknowledged while the old write still ran. A drain mark is now stored with the change and cleared only after admitted work ended and proposals were invalidated; any retry waits for it, also across a restart. Admission checks the revocation state under the gate right before it runs the work, so a host check that ignores cancellation cannot start work for a revoked user. Account checks that report an outage give 503 instead of 403. Registrations are capped per user (pending and total) and in the store with finite defaults. The store rejects unknown roles, grants to the owner and trailing bytes, removes stale temporary files, and rolls memory forward and refuses writes when the directory sync fails after the rename. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Add AddonConfig.OwnershipMode. The default stays named-user and local-owner is never inferred. In local-owner mode every caller of the addon routes acts as one reserved synthetic owner, sharing is denied and the owner logs a startup warning that unauthenticated callers share one authority. Named-user mode rejects that principal. Account lookups now separate "not found or disabled" (403) from a store failure (503). Creation authority requires the addon to be installed, enabled and not changing on the owner, and the owner reconciles pending registrations at startup. Task 13 supplies the real resource check; the default drops pending registrations and keeps committed ones. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Cover the owed-drain mark, the registration limits, store validation, the creation authority and the explicit local-owner mode, including its behaviour without an account database. Add the new cases to the suite inventory. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The policy and the gateway mapped any error from the host account check or the creation authority that was not explicitly unavailable to a denial. An unexpected failure was then reported as a refusal of a valid user (403) instead of failing closed as unavailable (503). Only an error that wraps the shared forbidden sentinel is a denial now. Everything else, including a cancelled check, is unavailable. One helper, gateway.ClassifyCheck, serves the policy, the gateway dispatch and relay paths and the forwarding verifier. The auth layer's definite refusals now wrap the sentinel so they keep answering 403. Tests that used a plain error to stand for a disabled account now wrap the forbidden sentinel, since a plain error no longer means a denial. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
While a drain was owed, every retry of the same grant change stopped and waited for all admitted work of that user. Work admitted after the grant became durable again was cancelled too, because the mark did not say which authority it was owed for. Each resource now keeps an authority epoch that a lowering advances, and every admission records the epoch it was admitted under. The owed mark stores the cutoff of its lowering, separately for writes and reads. A retry invalidates proposals and stops and waits only for work older than the cutoff. A new lowering starts a new epoch and covers newer work. The epoch and the cutoff are persisted with the grant, so a restart keeps both. A call also clears only the mark it drained, so a lowering that ran while the call waited keeps its own owed drain. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
…covery
Every data or schema mutation from chat, an agent or an API client is now
a stored proposal. The owner previews it on the addon, stores the
canonical arguments, generation, revisions, exact effect count, a
bounded sample and an expiry, and applies it only on an explicit
confirmation of that stored operation.
- Broker (policy): propose, confirm, cancel and reconcile with the
pending, cancelled, expired, executing, applied, rejected and
outcome-unknown states. Each transition is written atomically and
fsynced; executing is durable before the addon sees the call. The
broker is the policy's proposal invalidator, so a revocation cancels
pending proposals before it is acknowledged.
- A lost or crashed apply is outcome-unknown with the original
operation ID and is resolved by receipt lookup, never by a new apply.
An unresolved outcome blocks other writes on the resource.
- Gateway: proposal calls travel as their own carrier call, so a typed
dispatch can never confirm, and other frontends reach the owner's
state through the signed forwarding path.
- HTTP: POST /api/addons/proposals, GET /api/addons/proposals/{id} and
POST .../confirm, .../cancel and .../reconcile. Confirm accepts only
the proposer's browser session from a same-origin fetch that names the
proposal and its digest, and refuses API keys and tool, agent or MCP
markers. No MCP tool maps to it.
- Resource IDs now start with a letter. The envelope contract refuses a
bare hex value that starts with a digit, which would have made most
new resources unusable at preview time.
The suites inventory, Swagger, the instruction registry, the route
feature registry and the addons guide are updated with the code.
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The digest did not cover the proposing user, and the owner only compared the digest a caller named with the stored one. A stored record whose arguments, actor, resource, generation or operation ID had been changed would still run under the digest it was created with. The digest now includes the proposing user and mode. Confirmation and the execution step recompute it from the stored record and refuse a mismatch, with or without a digest from the caller. Tests cover each bound field in the digest and a record edited on disk for each field. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
… store A pending proposal past its expiry was never moved to expired or dropped. Expiry ran only when the proposal was read or confirmed, the per-user count skipped expired entries, and nothing collected settled records except when the store was already full. One user making a proposal every few minutes could fill the 1024 records and deny every user for good, across restarts. - Expiry is now recorded by the clock, on each insertion, on start and on recovery, durably and without any read. - Settled proposals are dropped after a retention window (24 hours by default, configurable, never disabled). - The per-user bound counts live proposals and is checked before any store-wide bound. The store makes room by dropping settled records only, the proposer's own first, and never another user's live proposal; it then refuses with a typed quota error. - The canonical arguments are bounded per user and in total, with a typed quota error. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The job services reach the progress and stop operations through adapters beside the existing job adapters. The application registers them with the same broker as the job operations, and only the owner of an add-on host has a broker. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The host registers the model through the existing export and import services, as the calling user, and returns only the model name. The operation takes no path. The name is optional and limited to a fixed pattern, and the export format is limited to the values the fine-tuning docs list. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The fine-tuning and quantization services are adapted to the model registration operations. A job the user does not hold is not found, and a job that has not completed, or whose export or import is running, is a conflict, since the services report these as plain errors. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The export and import services refused a name only when its config file existed. A folder or a file of another extension with the same name was used as the output, so a registration could write into an existing model. The check now refuses any entry with the name directly under the models folder. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
finetune.export and quantize.import need the administrator role on top of the feature. The broker checks the role with the rule of the admin routes; a local-owner install counts its owner as administrator. A feature-holding user who is not an administrator gets 403. finetune.export works from the job's latest checkpoint, the one with the highest step. A job with no checkpoint is a conflict that names it. The model registration adapters take small interfaces, so their 404 and 409 mapping is tested with fakes: a missing job is not found, and a job that has not completed or whose export or import is running is a conflict. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Changelog line for the 2026-10-18 owner ruling on finetune.export and quantize.import. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The admin check of model registration now decides on the actor. With an account database the role of the active account decides, including in local-owner mode; the local owner counts as administrator only when no account database exists. A name the collision check cannot match is treated as taken, so the guard refuses it. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The 2026-10-18 changelog line now says that local-owner counts as administrator only when there is no account database. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A host operation stores an inline text dataset of up to 900 KiB through the existing dataset service and returns a reference. A start operation resolves a reference only for the user it was issued to. Assisted-by: Claude:claude-haiku-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
… add-ons One source directory holds the stage and status helpers, an SSE reader and the step, check, stage and log views. Each add-on bundles what it imports. The SDK does not carry them. Assisted-by: Claude:claude-sonnet-5-5 node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The add-on builder refuses a bundle that imports a core UI file, so tools.js, tools.css and formatBytes are copies. A node test now fails when a copy differs from its core original. The spec changelog records the exception. Also tighten the step-line assertion and give the plan the glob form of the test command. Assisted-by: Claude:claude-sonnet-5-5 node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The fine-tuning page becomes an add-on page that calls the core routes with the user's session. The add-on also offers tools to start, stop, inspect and export jobs through the host operations. A model controls only a job that a tool started. A running job whose progress reads 0 is shown as not reported, and the export controls are shown to administrators only. Assisted-by: Claude:claude-sonnet-5-5 go node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
A read tool may leave out resourceId when it answers about the calling user. The host runs it without a resource, bound to the caller's envelope, only when the verified manifest declares the tool that way. A write or schema tool, and a read tool that offers resourceId, still must require it. The argument limit of a tool call rises to the payload limit so a tool can take a dataset of up to 900 KiB. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
fine-tune-list shows the jobs of the calling user, those started in the web page included. fine-tune-dataset sends a small inline dataset after a person confirms it and keeps the reference the host returns, which fine-tune-start then uses as its datasetSource. Assisted-by: Claude:claude-sonnet-5-5 go node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The argument limit of a tool call is 1 MiB and one byte more is refused. A second user's job list does not show the first user's jobs. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Policy.AdmitAddon is tested for a stale account, a held add-on, an empty add-on ID and a missing function, and the gateway provider is checked against the interface. The 1 MiB argument limit now counts the tool key the host adds, so an accepted call always fits the payload limit; a call at the limit is tested through the adapter and the envelope check. Assisted-by: Claude:claude-sonnet-5-5 go node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The quantization page becomes an add-on page that calls the core routes with the user's session, and the add-on offers tools to list, start, stop and import jobs through the host operations. Assisted-by: Claude:claude-sonnet-5-5 go node Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The old routes open the add-on while it is installed and running and the core page otherwise. The two Build hub tabs go; the Build overview keeps its cards. Users find the add-ons in Browse add-ons and, once installed, in the Add-ons drawer. Assisted-by: Claude:claude-sonnet-5-5 node playwright Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
…-ons The auth specs pin that a user without the fine_tuning or quantization feature gets 403 from the job routes the add-on pages call. The docs describe both pages as add-ons, the old links, the unchanged API and the chat tools. The e2e spec covers the core fallback of the old quantize link. The suite inventory of verify.sh lacked cases that earlier commits added (read tools without a resource, SDK refusals), so the contracts and apps suites failed on a mismatch. List them. Assisted-by: Claude:claude-sonnet-5-5 go playwright Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
package.sh and the release workflow read the add-on from its platform file instead of naming Tables. The workflow runs one set of legs for each add-on, and each add-on other than Tables publishes to its own repository. Tables builds as before; the pure Go add-ons build without cgo. The platform files of the four other add-ons list every platform as disabled, so no leg builds until a platform is enabled with its native check. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The publish and promote jobs derived the repository of an add-on from ADDON_OCI_REPOSITORY, but the documented value gave no form that the add-on tools accept. Document the variable as the Tables repository in the add-on namespace, and test that the derivation rule yields an accepted repository for every add-on by running the workflow step. Make the plan step fail on a publishing event when an add-on has no enabled platform, and tolerate only that answer on other events. A missing or malformed platforms.yaml now fails the plan on every event. Check in the workflow policy that Tables keeps the configured repository, that publish and promote do not stop each other with fail-fast, that publish downloads only the layouts of its own add-on, and that promote checks the digest format. Cover each with a mutation test and run the plan step against stub platform files. Stop asserting that platforms are disabled: a later change enables them. State in the docs that publishing events fail closed for an add-on until one of its platforms is enabled. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Voice, Face, fine-tuning and quantization each list linux and darwin on amd64 and arm64 with the runner that checks them natively. Every platform stays disabled until its native check passes. A test fails when a platform is enabled without a recorded check. Each package gets a catalog template so Browse add-ons can list it. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The native check passes on linux/amd64 for Voice, Face, fine-tuning and quantization, so the platform is enabled for the four packages. The release workflow legs on ubuntu-24.04 run the same check. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The packaging checks compile Go packages that import the generated gRPC stubs. The stubs are not in the tree, so the validate job failed at setup on every run and the package legs, which wait for it, never ran. Generate them with the pinned tools the test workflow uses. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The native check passes on ubuntu-24.04-arm for Voice, Face, fine-tuning and quantization, so the platform is enabled for the four packages. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The previous commit left a stray line in each platform file, which made the files unreadable. Enable linux/arm64 with a plain edit. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The native check passes on macos-14 for Voice, Face, fine-tuning and quantization, so the platform is enabled for the four packages. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
The package workflow runs the native check on macos-15-intel for Voice, Face, fine-tuning and quantization, so the platform is enabled for the four packages. Assisted-by: Claude:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
mudler
force-pushed
the
feat/addons-tables
branch
from
October 20, 2026 07:28
05f89f0 to
0d71c38
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status: ready for review. The whole plan is implemented and tested: the
foundation, Tables, recovery, packaging, update detection and an end-to-end
Tables acceptance suite. A few residual items are listed under "Open items".
What
An add-on is an installable extension that runs as a managed process next to
LocalAI, and can bring tools for models, services, and UI. This PR adds:
journal and a single designated owner that supervises add-on processes.
assets through the owner over mutual TLS. If the owner is down, requests
fail closed; nothing runs locally.
host-owned admission and revocation that waits for admitted work.
Models, agents and tool calls can propose but never approve.
download.
channels,
development(master only) andlatest(published release).Upgrade.
and an administrator view to browse, install, activate and manage add-ons.
slots (chat composer actions, per-message actions, navigation, settings
sections), plus approval-card and tool-result renderers.
operations, structured queries and row changes (no raw SQL), whole-database
sharing, a native UI, a "Select database" chat action, tools for models, and
private CSV export.
Why
LocalAI users want to extend it with tools and small apps without rebuilding
the host or giving every extension access to everything. The design keeps
authority in the host (identity, roles, approval, quotas) and keeps the add-on
code narrow.
How to try it
make build; the React UI is built by the normal UI build).go build ./addons/tables/cmd/tablesand build theUI package in
addons/tables/ui(node build.mjs).the web UI, add a library that lists the package, install Tables and
activate it.
with
/addons, pick a database with the Select database action and askthe model to read or change rows. Changes appear as a proposal card that you
confirm in the UI.
Documentation:
docs/content/features/addons.md,docs/content/features/tables.mdanddocs/content/development/addon-testing.md.Tests
Every suite runs offline through
sh scripts/addons/verify.sh <suite>, whichenforces an exact case inventory and zero skips. Pass counts at the last full
run (each includes the readiness checks the runner adds):
sh scripts/addons/foundation_gate.shandsh scripts/addons/tables_gate.shrun the foundation and Tables suites in order, reap leaked processes and print
an inventory. Go race tests for
core/services/addons/...,core/http/endpoints/addonsandpkg/addons/...pass, as dogo vetand theexisting application, config, auth and endpoint tests.
tables-acceptanceruns the real packaged Tables on an owner and twoforwarding frontends over mutual TLS with real sessions, API keys and SQLite,
and writes an evidence manifest (cases, platform, artifact digests). Only
linux/amd64 is covered natively.
The release workflow cannot be executed offline: registry login, signing and
verification are covered by workflow-policy assertions and local OCI layouts,
not by a live run.
Known limits
and is not sandboxed. Controls are install-time trust, server-side
authorization on every call and a host-owned approval frame.
reach the port, including confirmation of proposals. It is explicit opt-in and
warns at startup.
as model tools; this is a deliberate deferral and documented.
deletion and sharing are UI-only.
(
enabled: false) until each has a native runtime check; the publishingworkflow fails closed without a registry namespace and an enabled platform.
includes the release ref; supporting an identity pattern needs a catalog
change and is documented.
pending row proposal stale.
and the recovery panel shows the phases.
unavailable (an unknown outcome for mutations, recovered by repeating the
same operation ID).
Open items
fixes, the acceptance burst change) were verified by their tests and the full
gate but did not get a second independent review pass.
core/http/endpoints/localaiwebsocket-logs spec is an existing flakytest that fails occasionally under load; it is unrelated to this change.
listed as pending until it expires.
enabled platform and an environment with signing permission; none are
configured here.
🤖 Generated with Claude Code