TacidOS
Developers
Documentation

Decision 0051

51. What leaves is named by the conversation, not by the adapter

Architecture decision record 0051: 51. What leaves is named by the conversation, not by the adapter

Status
accepted
Date
2026-09-06

Context

Every request out of a Tacid machine carries a list of data classes. The egress proxy writes them into the ledger and the badge is drawn from them, so the classes are how an owner learns that their journal went to a provider rather than only a question. An owner may be perfectly willing to send a README to a model and unwilling to send their systemd journal; the list is where that distinction lives.

The list was computed in one adapter, and it was wrong in two ways.

It looked for a tool named logs.*. Tacid’s journal tool is called journal.read. So the class logs was never once emitted, and a machine that sent its journal out told its owner it had sent text.

And it was computed in anthropic.rs. The OpenAI-family adapter — which covers OpenAI, xAI, Google’s compatibility endpoint, OpenRouter and a local llama-server — wrote vec!["text"] on every request it ever made, including the ones carrying a person’s files. Everything reached through that adapter was invisible to the badge.

Beyond those two, git.read, project.look and memory.recall all carry a person’s content out and were named by nothing.

Decision

Classification is a property of the conversation, so it lives on Prompt. Prompt::classes() walks the messages and names what is in them; both adapters call it, and a third one gets it for free. An adapter that decided for itself would drift, and one of them had.

A tool’s family names its class, through provider::class_of: files is file content, journal is logs, git and project are file content, memory is memory, source is source. text is always present, because a prompt is text.

This machine’s own words about itself are not a data class. facts.get, service.status and generations.list return the kernel’s account of this machine, systemd’s account of a unit, and Tacid’s list of its own generations. Calling those “file content” would make the badge say less by saying more: a badge that lights for a kernel fact is a badge its owner learns to ignore.

The list is held against the real toolbox by a test in the daemon. class_of lives in the model crate, which does not know what tools exist; the daemon is the only place that does. A tool added without a class fails there, rather than leaving silently under the word “text”.

Consequences

The badge and the ledger now name journal output, repository content, project files and memory, on every provider rather than on one.

class_of ends in an arm returning None, which is the wrong default for a new tool that carries something out. The daemon’s test is what makes that safe, and it is the reason the test exists rather than being a nicety: without it the failure is silent and on the wrong side.

The classes are still declared by the client rather than derived from the bytes. A compromised tacidd can lie about them. That is not what they are for — they are how an honest machine tells its owner what it did — and the control against a dishonest one is that the proxy holds the credentials and the allowlist, not the labels.