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

%%{init: {'theme': 'dark', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"20px", "darkMode":true}}}%%

flowchart TD
    Q[/"your question<br>selected service"/]
    Notes[("admin-task notes")]
    Cmd[["Chat panel"]]
    Local[["local model<br>via ollama"]]
    Hosted[["hosted model"]]
    Act["start, stop, restart"]

    Q --> Cmd
    Notes --> Cmd
    Cmd ==>|"default:<br>stays on this host"| Local
    Cmd -.->|"opted in:<br>leaves this host"| Hosted
    Cmd --x|"never"| Act

    style Q fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
    style Notes fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
    style Cmd fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
    style Local fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
    style Hosted fill:#FFFFFF,color:#000000,stroke:#333,stroke-width:1px,rx:5,ry:5
    style Act fill:#FFFFFF,color:#000000,stroke:#333,stroke-width:1px,rx:5,ry:5
    

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 sitting right there. Wiring it up would take one line.

It stays unwired, and the four tools offered are all read-only: get_status, get_logs, get_service_info, and 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. 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.

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.

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.