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
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.