# Chatbot Scope

Background and reasoning behind design decisions in `splam`. This page won't tell you how to do something. See the How-To Guides for that.


# Why a chatbot at all

The **Tasks** tab already answers "what does this service do and what should I check." It answers it the same way every time, because it's a static document. What it can't do is answer "why is *this* status output saying *that*," which is the question an admin actually has at 2am with a failed unit in front of them.

That's the gap. A model holding the deployment's own admin-task notes can read a specific `journalctl` dump and say something useful about it. Everything else on this page is about keeping that capability inside a boundary the deployment can live with.


# Why a panel rather than a terminal

A chat panel and a terminal chatbot cost about the same to build, and the panel was still the right one to build first.

Dependencies don't separate them. The chat widget is `shinychat`, which `shiny` already requires, so the panel adds `chatlas` and nothing else. A console adds `chatlas` and nothing else. Neither is meaningfully more code than the other, because `shinychat`'s `Chat` accepts a `chatlas` client directly and wires up streaming and history itself.

What separates them is what each one already knows. The panel runs inside a session that has authenticated a user, resolved their role, and has a service selected in the sidebar. A console starts with none of that and can't get it: there's no login step, so it's bounded by the file permissions of whoever ran it. In a deployment that bothered to distinguish `admin` from `auditor`, a second entry point that ignores the distinction is a gap rather than a feature.

The console remains worth shipping as a secondary interface, for a host with no browser or an admin who lives in a terminal. It just isn't the one to design around.


# Why it's off by default

`chatlas` is an optional dependency and `splam` never imports it unless you installed it. That isn't tidiness. It's the only control a validated environment can verify cheaply: if the package isn't in the environment, there's no code path from `splam` to a model provider, and confirming that takes one `pip list` rather than a code review.

Every other guard on this page depends on the app behaving as designed. This one doesn't.

Installing the extra and configuring a provider are separate steps, and the panel distinguishes them. With `chatlas` absent there's no tab, because the deployment chose not to have the feature. With `chatlas` present but no provider reachable, the tab appears and explains what's missing. An earlier version hid the tab in both cases, which was wrong: someone who had just run the install had no way to tell a deliberate omission from a broken setup, and a missing tab reads as a bug rather than as a message.


# What leaves the machine


*\[Rich HTML output -- view on the documentation site\]*


Whatever the panel sends the model, the model's operator receives. With `SPLAM_CHAT_PROVIDER=ollama` that operator is the host itself and the question is closed. With a hosted provider it isn't, and the material at stake is worse than it first looks: `journalctl` output carries hostnames, usernames, internal IPs, file paths, batch identifiers, and occasionally a secret that got logged by accident. Sending a hundred lines of it to a hosted model is a disclosure of all of that, made by an admin at a text box, without a ticket.

So `ollama` is the default. Not because local models answer better, they often don't, but because the default should be the setting that can't surprise you. Choosing a hosted provider takes an explicit environment variable and an API key, which is roughly the friction the decision deserves.

Putting the chat inside the app raises the stakes here rather than lowering them. A panel sitting next to the **Logs** tab makes pasting log output the obvious next gesture, and with `SPLAM_CHAT_TOOLS` set the model fetches it without being asked. That's the point of the feature and also its main risk, which is why tools are off unless someone deliberately turns them on, and why that switch belongs to whoever starts the app rather than to whoever is typing in the panel.

Per-call approval was considered and dropped. Approving a tool call from the UI means blocking mid-stream on a round trip to the browser, and the honest version of that prompt would appear several times in a row for a question that needs status and logs together. Operators would learn to click through it, which is worse than no prompt at all because it looks like a control. Enabling tools at startup is one decision, made once, by the person who also chose the provider.


# Why it reads but never acts

`chatlas` can register any Python function as a tool, and `splam` already has [systemctl_action](../reference/systemctl_action.md#splam.systemctl_action) sitting right there. Wiring it up would take one line.

It stays unwired, and the four tools offered are all read-only: [get_status](../reference/get_status.md#splam.get_status), [get_logs](../reference/get_logs.md#splam.get_logs), [get_service_info](../reference/get_service_info.md#splam.get_service_info), and [read_audit_log](../reference/read_audit_log.md#splam.read_audit_log).

The reason isn't that models are unreliable, though they are. It's that every service action in this app carries a **Reason** typed by a person, and that requirement is the point of the audit trail rather than a form validation quirk. See [Explanation: Reason Required](02.02-explanation-reason-required.md). A model-issued restart would produce an audit entry whose reason was written by the thing being audited. The record would look complete and mean nothing, which is worse than an obvious gap.

The panel makes this line easy to hold, because **Actions** is a separate tab with its own buttons and its own required field. Nothing about a chat box invites you to expect a restart from it.


# Why a chat session isn't audited

Using the **Chat** panel writes nothing to `audit_log.jsonl`. Reading isn't an audited event anywhere else in this app either: viewing the **Logs** tab leaves no entry, and neither does opening the audit trail itself. The log records attempts to change something, and a conversation changes nothing.

**Export conversation** exists for the other case, when the conversation informed a change you'll have to justify later. That's a transcript you chose to keep, filed wherever your change control lives. It's deliberately not the same artifact as the audit trail, which is the app's own record of what it did. See [Explanation: Audit Trail Scope](02.03-explanation-audit-trail-scope.md).


# What the panel inherits

Building inside the session means the chat gets four things for free that a console would have to invent: the authenticated username, the role, the selected service, and the idle timeout. The last one matters more than it sounds. Sessions are ended server-side after an idle period, and a chat reply that takes ninety seconds to stream shouldn't count as idleness, so typing in the panel marks the session active the same way changing the **Service** dropdown does. See [Explanation: Login Sessions](02.01-explanation-login-sessions.md).

That inheritance is the whole argument for the panel. It's also the reason the panel can be shown to `auditor` accounts without a second thought: the tools are read-only, the role is already known, and an auditor asking questions about logs is the job rather than a loophole.
