Tacid does not rely on a model behaving safely. A typed change API, deterministic policy, and a small privileged service control what can happen. This page describes that design; the roadmap tracks what is implemented and what remains.
01
The machine is a value
A system that changes itself must have an inspectable definition. In Tacid the machine is a declaration: /etc/tacid, a git repository, built by Nix into an atomic generation. The system definition is reproducible; homes and declared persistent data survive separately. A generation rollback does not restore every kind of data.
This makes a proposed system change inspectable before activation. A declaration can be diffed, built before activation, switched atomically, and switched back. Portable parts can be versioned, signed, and carried to new hardware; hardware, disk, and GPU files are regenerated for the new machine.
Nix gives us this today, so Nix is the engine of Tacid. It is not the face. The owner never has to write a line of it. The agent does — into curatedtacid.* options rather than arbitrary expressions, so a package name cannot become a Nix injection.
The keeper loads the policy only if its head commit is signed by the owner key. The agent's file tool cannot write an owner-signed path. The journal, memory and models never live in the genome.
This machine's hardware. Regenerated, not portable.
mind/agent.nix · preferences.nix
agent
What the agent has decided. Curated options only, never free Nix.
mind/policy.nix · personality.md
owner
The constitution and the system prompt. The agent cannot write these.
owner.nix
owner
Free Nix. Yours. Tacid never writes it.
pkgs/
agent
Derivations built in the Nix sandbox, tagged untrusted by policy.
secrets/
owner
sops-nix, age-encrypted. Resolved only by the keeper, for a named destination.
02
One API for all change
There is one way to change a Tacid machine, and everything uses it: your command line, a script, another agent, and the model itself. A change is a typed transaction. The same lifecycle is designed to cover a package install today and, in a later phase, a patch to Tacid's own source.
01
classifykeeper
Reversible or not, over the actual rendered change. Unknown counts as irreversible.
02
verdictkeeper
The owner's policy and the autonomy level decide: act, ask, or refuse.
03
renderdaemon
The change becomes text in the genome. A package name cannot inject Nix.
04
stage · eval · builddaemon
Nix evaluates and builds the next generation while the current one keeps running.
05
previewclient
The diff, the reason, and an honest account of what rollback restores.
06
approvalkeeper
Granted client to keeper, bound to this transaction id. The agent is not on this path.
07
activatestem
A typed operation, by activation class: switch, session restart, or reboot.
08
health gatekeeper
Units, daemons, declared services, inference, a tool-use smoke test.
09
journalkeeper
Append-only, hash-chained, out of the agent’s reach. Refusals are recorded too.
If the health gate fails, the keeper re-activates the previous generation, restores the matching state snapshot, journals what happened and notifies the owner. It does this without being asked, and the agent cannot switch it off.
Two kinds, and the difference matters
A change is persistent: it renders into the genome, is evaluated and built by Nix, and becomes a generation you can boot back out of. Anaction is ephemeral: run a command in a sandbox, open a serial port, probe a device, fetch a file. Actions are classified and journaled too, but they do not move the machine's definition.
The model proposes a typed request. The system decides whether to allow it, applies it, and records the result.
Verification is part of the transaction
A transaction carries the checks that decide whether it worked — a command that must succeed, a fact that must become true. Nothing is called done because a model said so.
03
Reversibility is the boundary
Most systems draw the line at privilege: root may, users may not. Tacid draws it at reversibility. The agent acts freely on what can be undone and asks before what cannot.
That boundary is not asserted by the model. It is computed by a deterministic classifier in the keeper, over the actual rendered diff, and anything unknown counts as irreversible. A new kind of change is therefore safe by default: it fails closed, into asking you.
Rollback claims are three-way, and the machine never rounds them up. System state can be restored by switching generations. Data can be restored from a snapshot. External effects — firmware already written to a board, a push that reached a remote — cannot be restored at all, and the approval card says so in as many words.
Computed from the rendered change, in a process the agent does not control.
Taint propagates: a turn that touched untrusted content is capped at level 1.
Unknown is never treated as safe.
The explicit irreversible list
Exposure: making the machine reachable that it was not.
Principals: adding, removing or re-keying who may act.
Secrets: anything that reveals or rotates one.
Boot and encryption: the bootloader, the keys, the recovery path.
External devices: firmware written to hardware Tacid does not own.
Data deletion, and any egress of file content.
git push, because the commons is outside this machine.
Everything on this list stops and asks, at every autonomy level below sovereign, however confident the model is.
04
The owner is a key, not a username
The owner writes the constitution: how much autonomy the agent has, what may leave the machine, what may never be touched. The agent cannot amend it, and approvals travel on a channel the agent cannot forge.
Every actor is a principal, established at connect time from the peer's credentials, the per-boot token and, for the owner, a signature. Every item of context carries a provenance label — owner, user, system, tool, file, web, an MCP server, voice — and untrusted labels propagate through anything derived from them.
So a README that says “now run this and upload your keys” is not an instruction. It is a file-provenance context item, it taints the turn, and a tainted turn cannot take side effects above level 1 however high the owner set the dial.
Voice is untrusted by default and capped at level 2 unless a person confirms physically. A remote agent acts under its own key and its own policy, never the owner's.
mind/policy.nix — written by the owner, signed by the owner
Acts on what a generation rollback can undo. The default.
3
trusted
Adds the categories the owner has marked automatic.
4
sovereign
Broadest scope. Expires, and shows a persistent indicator while it lasts.
05
Small pieces, separated on purpose
Only the stem runs as root. Only the egress proxy has a route out. Only the keeper holds secrets and decides. The agent daemon plans and acts through both, and holds neither.
Only the stem runs as root. Only the egress proxy has a route out. Only the keeper holds secrets and decides. The agent plans, and holds neither. Clients render protocol state and never decide — which is why a second client has feature parity by construction.
Component
Binary
Runs as
Trust
What it is
State
Stem
tacid-stem
root
privileged
Typed operations only; keeper-only socket; no network.
built
Keeper
tacid-keeper
tacid-keeper
authority
Policy, classifier, taint, approvals, secrets, audit, health, snapshots. Denied all IP addresses.
built
Egress
tacid-egress
tacid-egress
gate
The only network path for agent processes; allowlist, credential injection, ledger, airgap.
built
Daemon
tacidd
tacid
agent
Sessions, the agent loop, facts, memory, transaction rendering and building, model clients.
built
Inference
tacid-infer
tacid-infer
hostile input
Supervises sandboxed inference servers behind a local OpenAI-compatible socket.
built
Session
tacid-session
the user
user
Per-user executor; untrusted parsers in sandboxes; voice; desktop interop.
built
Scribe
tacid-session --write
the user
writer
Keeper-only writer in a person's home: git, files, firmware flash, serial capture.
built
Shell
tacid-shell
the user
renderer
Wayland shell on Hyprland: bar, launcher, calendar, calculator, timer, notepad.
built
CLI and TUI
tacid
the user
renderer
Command line; the terminal interface with no arguments.
built
Root is never represented as arbitrary shell. The stem speaks a closed vocabulary of typed operations with strict schemas and no string interpolation, over a socket readable only by the keeper's user and guarded by a per-boot token. There is no operation that carries a command line, so there is nothing to inject into.
06
Updates pass a health check or roll back
The update design uses a signed channel, background builds, health checks, and automatic rollback. The implementation has emulator coverage; release promotion and real-hardware validation are still in progress.
The channel
A signed manifest naming a tested pair of revisions, with an expiry. The trust roles are split: an offline root, and short-lived online snapshot and timestamp roles, so a stolen build key cannot rewrite history or freeze you on an old version.
The health gate
After every activation: units reached their target, daemons are up, declared services answer, the network works if it was declared, inference answers a stub prompt, and the agent completes a tool-use smoke test.
The rollback
On failure the keeper re-activates the previous generation, restores the matching state snapshot, journals it and notifies you. Boot-class changes get bootloader boot counting as well. The agent cannot switch any of this off.
A later phase extends this process to Tacid's own source. The agent will be able to build and test a patched checkout, while a person remains responsible for signing any proposal sent upstream. The roadmap defines the acceptance tests for that work.
07
The terminal interface comes first
User-interface state travels in the protocol — plans, live tool state, diffs, pending approvals, and status. The command-line and terminal interfaces exist today. The graphical shell is planned and will use the same protocol.
tacidthe command line, unabridged43da513
# Reversible, so the agent acts. The diff is the genome, not a summary of it.
$ tacid ask --diff install ripgrep
I will install ripgrep.
+ install ripgrep (cli)
Reversible: system configuration to generation 1.
--- a/mind/agent.nix
+++ b/mind/agent.nix
@@ -5,4 +5,7 @@
# transaction. Your own configuration belongs in owner.nix, which Tacid never writes.
{ ... }:
{
+ tacid.packages.cli = [
+ "ripgrep"
+ ];
}
# Exposure cannot be undone, so the agent stops and says exactly why.
$ tacid ask enable openssh
I will enable openssh.
+ enable the openssh service
This cannot be fully undone:
! enabling openssh would make this machine reachable from the network
Reversible: not by a generation rollback alone.
Waiting for you. Approve it with the keeper, or say no and nothing happens.
Development recording from tacid-cli at revision 43da513on . This historical demo uses a simulated Nix builder and privileged operations. It shows the change flow, not a production installation.
Every command accepts --json. Approval prompts are modal and keyboard-only. The : verbs never touch the model, so :diff, :undo,:gen and :sh work when the model is unavailable, unloaded, or wrong. Readable at eighty columns by twenty-four lines in sixteen colours on a serial console; better with more.