TacidOS
Developers

How it works

From intent to a reversible change

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.

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. The same lifecycle is designed to cover a package install today and, in a later phase, a patch to 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.

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
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
Scribetacid-session --writethe userwriterKeeper-only writer in a person's home: git, files, firmware flash, serial capture.built
Shelltacid-shellthe userrendererWayland shell on Hyprland: bar, launcher, calendar, calculator, timer, notepad.built
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

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.