mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-30 01:54:31 -04:00
agent.<name>.cancel was the last family on a message bus, and the only reason an agent worker dialled one. Its subscriber is the worker running the execution, and a worker has no database, so the family could not move to the PostgreSQL fan-out carrier: a cancel published there would reach no worker while reporting that it had been sent. It is a control verb now. An agent worker mounts workerctl.PathAgentCancel on the loopback control plane behind its tunnel and applies the cancel to the same registry the executor registers a run on. The frontend issues it through nodes.AgentControlClient.CancelAgentRun. That call is a FAN-OUT and not a pick, because nothing records which worker holds a given execution: the claim row names the claiming replica, and it is deleted when the run ends. Every agent worker a live replica can reach is asked over its own tunnel, relayed by the peer mesh when a peer holds it, and each worker answers only for itself. The answers stay apart, which is why this family was held back. A cancel a worker made is nil. A cancel some worker could not be asked is ErrAgentCancelUndelivered, which is neither a refusal nor a missing run. A cancel every reachable worker declined to own is ErrAgentRunNotOnAnyWorker. A deployment with no agent worker is ErrNoAgentWorker. Neither new sentinel wraps ErrWorkerUnroutable and neither is a worker answer, so nothing is reaped, demoted or evicted because of a cancel. A worker in the ABSENT CONNECTION condition, one whose tunnel was lost inside the reconnect grace, counts as undelivered. It is not retried in the call and not queued: a retry would spend a budget the caller did not choose, and a queue would need durable state whose only consumer is a run whose control stream went with the tunnel. A worker whose departure has outlived the grace is the one routing fact a caller may act on and is excluded, or a single retired agent node would make every cancel undelivered for ever. The fan-out reads a different node set from the pick. A draining worker takes no new work but is still finishing what it holds, so it is offered the cancel; a pending one is refused by the tunnel route on every dial and is not. With that, nothing in LocalAI connects to NATS. The agent worker's dial, its credential ladder and its refresh loop are gone, and so is the frontend's cancel carrier. LOCALAI_NATS_URL is accepted and ignored everywhere, and distributed mode no longer requires it. Assisted-by: Claude Opus 5 [claude-code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
47 lines
2.3 KiB
Go
47 lines
2.3 KiB
Go
// SPDX-License-Identifier: MIT
|
|
|
|
package application
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mudler/LocalAI/core/config"
|
|
"github.com/mudler/LocalAI/core/services/nodes"
|
|
)
|
|
|
|
// newAgentControl builds the frontend's agent control client: the SELECTION
|
|
// that decides which agent worker answers a verb, and the control client that
|
|
// carries the verb to it.
|
|
//
|
|
// A named function rather than two more lines in initDistributed, for the
|
|
// reason distributedSchedulerOptions is one: initDistributed opens a database
|
|
// and a bus, so no unit spec reaches it, and the argument that matters here has
|
|
// no symptom when it is wrong. An empty instance id makes every connection read
|
|
// report nothing as held by this replica, so every MCP call takes a relay hop
|
|
// through a peer even when this replica holds the worker's tunnel itself, and
|
|
// nothing anywhere says so: the calls all succeed, just through one more
|
|
// process than they need. It is refused here instead of shipped as latency.
|
|
//
|
|
// The other two refusals are the ordinary kind. A selector with no registry has
|
|
// nothing to select from and a client with no transport reaches nobody, and
|
|
// both would present as MCP being quietly unavailable in a deployment that
|
|
// looks healthy.
|
|
//
|
|
// This client is also the deployment's agent CANCELLER, which is why the agent
|
|
// event bridge takes it: a cancel is a control RPC on the tunnels the workers
|
|
// hold, and the reconnect grace it is built with is what decides whether a
|
|
// worker that is not connected makes a cancel undelivered or is simply gone.
|
|
func newAgentControl(cfg config.DistributedConfig, registry *nodes.NodeRegistry,
|
|
conns nodes.AgentConnectionReader, control *nodes.ControlClient) (*nodes.AgentControlClient, error) {
|
|
if cfg.InstanceID == "" {
|
|
return nil, fmt.Errorf("the agent control client was built with no instance id: every MCP call would relay through a peer even for a worker whose tunnel this replica holds")
|
|
}
|
|
if registry == nil || conns == nil {
|
|
return nil, fmt.Errorf("the agent control client was built with no way to find a connected agent worker")
|
|
}
|
|
if control == nil {
|
|
return nil, fmt.Errorf("the agent control client was built with no control transport to reach an agent worker over")
|
|
}
|
|
return nodes.NewAgentControlClient(nodes.NewAgentSelector(registry, conns, cfg.InstanceID, cfg.WorkerReconnectGrace), control), nil
|
|
}
|