TacidOS
DevelopersGitHub

How it works

From intent to a reversible change

Tacid's safety is not a promise about how the model behaves. It is the shape of the system around the model: a machine that is a value, one API for changing it, a classifier that decides what may happen without asking, and a small privileged core with a closed set of operations.

01

The machine is a value

A body that rewrites itself must be able to know itself. In Tacid the whole 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. It can be built before it is activated, switched atomically, and switched back. It can be versioned, signed, carried to new hardware, and read by a mind that thinks in text.

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.

mind/agent.nix — written by the agent
{ ... }:
{
  tacid.packages.cli = [ "ripgrep" "fd" "openocd" ];
  tacid.services.openssh = { enable = false; };
  tacid.hardware.udev.profiles = [ "stlink" "cmsis-dap" ];
  tacid.dev.toolchains = [ "arm-none-eabi" "rust-embedded" ];
  tacid.desktop.theme = "phosphor-dark";
}
Path in the genomeSigned byWhat it holds
flake.nix · flake.lockowner or agentPins TacidOS; composes body, mind and owner.
body/hardware.nix · disks.nix · gpu.nixagentThis machine's hardware. Regenerated, not portable.
mind/agent.nix · preferences.nixagentWhat the agent has decided. Curated options only, never free Nix.
mind/policy.nix · personality.mdownerThe constitution and the system prompt. The agent cannot write these.
owner.nixownerFree Nix. Yours. Tacid never writes it.
pkgs/agentDerivations built in the Nix sandbox, tagged untrusted by policy.
secrets/ownersops-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, and it walks the same lifecycle whether it installs a package or patches Tacid's own source.

  1. 01

    classifykeeper

    Reversible or not, over the actual rendered change. Unknown counts as irreversible.

  2. 02

    verdictkeeper

    The owner's policy and the autonomy level decide: act, ask, or refuse.

  3. 03

    renderdaemon

    The change becomes text in the genome. A package name cannot inject Nix.

  4. 04

    stage · eval · builddaemon

    Nix evaluates and builds the next generation while the current one keeps running.

  5. 05

    previewclient

    The diff, the reason, and an honest account of what rollback restores.

  6. 06

    approvalkeeper

    Granted client to keeper, bound to this transaction id. The agent is not on this path.

  7. 07

    activatestem

    A typed operation, by activation class: switch, session restart, or reboot.

  8. 08

    health gatekeeper

    Units, daemons, declared services, inference, a tool-use smoke test.

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

Because the API is typed and deterministic, the machine is deterministic first and intelligent second. The model proposes. The nervous system decides, acts and records.

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
tacid.policy = {
  autonomy = "reversible";
  sovereign.expiresAfter = "4h";
  capabilities = {
    "packages.install" = "auto";
    "services.enable"  = "ask";
    "shell.exec"       = "sandbox";
    "git.push"         = "ask";
    "firmware.flash"   = "ask";
  };
  filesystem.write = [ "~/projects" ];
  egress.allow = [ "api.anthropic.com" "channel.tacidos.com" ];
  egress.dataClasses = {
    screenshot  = "ask";
    fileContent = "per-project";
    secrets     = "never";
  };
  budgets = { transactionsPerHour = 20; };
};
LevelNameWhat the agent may do
0observeReads the machine. Changes nothing.
1consultProposes every change and waits for a person.
2reversibleActs on what a generation rollback can undo. The default.
3trustedAdds the categories the owner has marked automatic.
4sovereignBroadest 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.

The Tacid process model and its chain of authorityA client — the tacid command line or the shell — talks to tacidd, the agent daemon, over tacidd.sock. tacidd talks to tacid-keeper over keeper.sock. tacid-keeper talks to tacid-stem, the only process running as root, over a socket readable only by the keeper. Separately, the client sends approvals directly to tacid-keeper, bypassing the agent entirely, so the agent cannot forge consent for its own work. A side branch shows tacidd reaching the network only through tacid-egress.tacid · tacid-shellrenderer · the user · decides nothingtaciddagent · user tacid · plans and actsnever holds a secret or a root shelltacid-keeperauthority · policy, approvals, secrets, journalthe health gate, and no network at alltacid-stemroot · the only process that istyped operations only, never a shelltacidd.sockkeeper.sockstem.sock · 0600, keeper uid + boot tokenapproval.grantbound to one transaction idegress.socktacid-egressgate · allowlist,ledger, credentialsthe network, with a badgeand a line in the ledgerswitch-to-configuration · bootctl · disko · nft
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.
ComponentBinaryRuns asTrustWhat it isState
Stemtacid-stemrootprivilegedTyped operations only; keeper-only socket; no network.built
Keepertacid-keepertacid-keeperauthorityPolicy, classifier, taint, approvals, secrets, audit, health, snapshots. Denied all IP addresses.built
Egresstacid-egresstacid-egressgateThe only network path for agent processes; allowlist, credential injection, ledger, airgap.built
DaemontaciddtacidagentSessions, the agent loop, facts, memory, transaction rendering and building, model clients.built
Inferencetacid-infertacid-inferhostile inputSupervises sandboxed inference servers behind a local OpenAI-compatible socket.built
Sessiontacid-sessionthe useruserPer-user executor; untrusted parsers in sandboxes; voice; desktop interop.built
Shelltacid-shellthe userrendererWayland shell for the Niri session.planned
CLI and TUItacidthe userrendererCommand 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

It runs itself, and undoes itself when it is wrong

Tacid watches a signed channel, builds the next generation while you work, switches when the body is quiet, verifies itself, and rolls back on its own if anything is wrong.

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.

Tacid also improves itself. Its own source is part of what it can change: the agent may rebuild the machine from a patched checkout, test it, live on it, and then propose the change to the commons. Proposals become pull requests only when a human signs them. The core — the stem, the keeper, the protocol, the health gate — is channel-signed, verified before activation, and replaceable only by the owner. A machine running code it built itself says so, in tacid version and in the bar.

07

The terminal interface is the first interface, and it is complete

User-interface state travels in the protocol — plans, live tool state, diffs, pending approvals, badges — so a client that renders those has feature parity by construction. The graphical shell is the same system with more pixels.

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 mind is unreachable, unloaded or wrong. Readable at eighty columns by twenty-four lines in sixteen colours on a serial console; better with more.