TacidOS
DevelopersGitHub

Developers

Build on a system
you can inspect.

The architecture, interfaces, and decisions behind TacidOS. Start with the change lifecycle, understand the trust boundaries, and find the part you want to work on.

Crates
21
Reported tests
898
Decisions
46
Status
Pre-release

Source snapshot · dcb3832. Test totals are reported by the upstream project; they are not a hardware certification. Source access is currently limited to collaborators.

Start here

The protocol

JSON-RPC 2.0, newline-delimited, over Unix domain sockets, with file descriptors passed for audio, pseudo-terminals and blobs, and notifications for streams. One crate defines every message and publishes a versioned JSON Schema. The same messages travel over SSH or a tunnel, with the remote peer as its own principal.

NamespaceWhat it carries
session.*ask, cancel, history, events — a conversation or a job bound to a principal.
tx.*propose, preview, apply, undo, list. The whole change API.
facts.*The typed live model of the machine, derived from the kernel and systemd rather than parsed shell output.
memory.*Per-user memory with provenance on every row.
models.* · providers.*Which minds are available, and which the owner has configured.
policy.* · approval.* · journal.* · health.*The keeper. Approvals reach it directly from the client.
update.* · device.* · project.* · voice.*Channels, the hardware graph, project environments, speech.
stem.*Keeper to stem only. A closed vocabulary, no command lines, no interpolation.
Illustrative protocol exchange
→ session.ask
  { "session": "s_1",
    "text": "install wireshark and let me
             capture without root" }

← session.event  plan
← session.event  transaction
  { "id": "tx_…", "kind": "change",
    "needs_approval": true }

  (the client connects to keeper.sock)

→ approval.grant
  { "transaction": "tx_…",
    "scope": "once" }

← session.event  activated
  { "generation": 188, "health": "pass" }

← result
  { "summary": "Wireshark is installed;
      your user is in the wireshark group.",
    "journal": "j_…" }

Note where the approval goes. It is granted by the client to the keeper, bound to that transaction id, so the daemon that proposed the change is not on the path that consents to it. That single arrow is most of the security model.

The workspace

21 Rust crates form the current implementation. Open a component for its responsibilities and the test coverage described in the source snapshot.

01tacid-protocol

The vocabulary and the wire: principals, provenance, transactions, verdicts, events, JSON-RPC with newline framing.

Coverage described in the source

A hostile string cannot break out of a Nix literal; an oversized frame is refused rather than buffered.

02tacid-policy

The deterministic authority: path safety, the reversibility classifier, taint, budgets, the engine and its hard invariants.

Coverage described in the source

A sovereign machine still refuses ~/.ssh; a README cannot install software; a lookalike host does not pass the allow-list.

03tacid-tx

The genome renderer and a unified diff.

Coverage described in the source

Rendering is deterministic and idempotent; a package name cannot inject Nix; every diff reconstructs its target.

04tacid-audit

The hash-chained journal and the secret scrubber.

Coverage described in the source

Editing, removing and reordering records are each detected by name; a store path survives scrubbing untouched.

05tacid-stem-protocol · tacid-stem

The only process that runs as root, and the closed vocabulary it speaks — including the six operations that make a machine: lay out a disk from a typed plan, seal its key to the TPM behind a PIN, enrol the recovery key it hands back once, write the first genome, write the owner's key into their home, install a built system.

Coverage described in the source

No operation carries a command line; a stranger is refused over the real wire; guarded operations need a human; the disk is laid out in the order a machine mounts it, and the passphrase reaches cryptsetup on standard input and appears in no argument, no report and no {:?}.

06tacid-listen

How a service receives its socket: from systemd when it was started for one, otherwise by making it.

Coverage described in the source

The one adoption safe Rust cannot express lives outside every Tacid crate; a stale socket is replaced, not refused; without a manager nothing is inherited.

07tacid-keeper

Policy, approvals, the journal, the health gate and automatic rollback, in their own process.

Coverage described in the source

The agent cannot approve its own work; consent for one transaction cannot apply another; a failed health gate restores the previous generation unasked.

08tacid-model

The provider trait, tool schemas, two models that need no weights, and two that reach a real one.

Coverage described in the source

A completion cannot express a shell command; the rules model still installs a package with no network; a proposal the protocol cannot express is refused in the adapter, not downstream.

09tacid-egress

The only way out: allow-list, credential injection, airgap, and a hash-chained ledger of every byte.

Coverage described in the source

A certificate for the wrong name is refused; the ledger holds the handle and never the key; raising a flag stops the machine talking mid-conversation.

10tacid-facts

Proprioception: what this machine is, read from /proc, /sys and /run.

Coverage described in the source

Loop devices are not disks; an unknown Secure Boot state is not reported as disabled; a machine with no systemd says so rather than reporting nothing running.

11tacid-doctor

What is wrong with the machine, in the one place both the report and the keeper's gate read from.

Coverage described in the source

A skipped check is never counted as a pass; the gate and the report are asserted to agree about the same machine; a machine that cannot be measured is not rolled back.

12tacid-memory

What the machine remembers: four layers, full-text search, and a provenance label on every note.

Coverage described in the source

A secret is not stored safely, it is not stored; the agent cannot write the owner's layer; a forgotten memory leaves the search index too; FTS5 syntax cannot be smuggled through a search.

13tacid-update

Channels, signed manifests, and the rules deciding when a machine may change itself.

Coverage described in the source

Rollback, freeze and cross-channel attacks each have a test named after them; a signature is checked before anything in the document is believed; a reboot is offered, never taken.

14tacid-infer

Local minds: a registry of weights, profiles sized to the hardware, and a supervisor that treats an inference server as hostile.

Coverage described in the source

No hash, no install; a model that does not fit is not offered; the weights' own chat template is never honoured; a server that keeps dying is reported rather than resurrected.

15tacid-session

A person's executor: their files, read as them and never as the agent, and untrusted content opened in a sandbox with no network, nothing writable, and one readable file.

Coverage described in the source

A caller that is neither the daemon nor the person is refused by its socket credentials before any method is looked at; a link out of the home is followed to where it goes and refused there; the executor refuses a protected file a talked-around daemon would have allowed; a file is what its bytes say, not what its name says; a file called --bind is a file.

16tacid-install

Genesis: a device and a few answers become the stem's plan for a disk and the machine's first declaration, from one layout.

Coverage described in the source

Only a stable whole-disk path is ever written to; the plan and the declaration name the same root; encryption without a passphrase is refused rather than skipped; a hostile answer cannot break out of the Nix it lands in; the rendered configuration balances at every combination of options.

17tacid-propose

How the machine changes its own source: a partition it cannot cross, and a queue only a person empties.

Coverage described in the source

A change to the keeper cannot be *filed*, not merely rejected; an unclassified crate is core; a signature does not survive the diff being swapped.

18tacid-daemon

The agent loop with real tools, the genome as a git repository, and the service every client shares.

Coverage described in the source

The thirteen end-to-end tests, including the Wireshark scenario; and an eleven-case injection corpus in which the model obeys the attacker every time and the machine still refuses.

19tacid-cli

The tacid command, the terminal interface, and a development server.

Coverage described in the source

It is what the session above is driving; the interface is rendered into a buffer and asserted at 60, 80, 100 and 132 columns.

20tacid-design

One token file, every surface.

Coverage described in the source

The palette's own contrast requirements are tests, and they failed until the palette was fixed.

The graphical shell and voice interface remain planned. Some crates provide libraries as well as services; an implemented component does not by itself mean a supported release.

Process model

ComponentBinaryUserTrustConstraintState
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

Extension points

The architecture defines these extension points. Check the current source and decision records before building an integration; interfaces are still evolving during development.

Extend the option surface

Adding a service means a tacid.* module with curated options, declared persistent directories, and a virtual-machine test. The module surface is the agent’s vocabulary: if it is not an option, the agent cannot ask for it.

Add a tool

A typed Rust tool with a schema, a provenance rule and a policy category. Output derived from untrusted input inherits the untrusted label automatically, so a new tool cannot quietly widen what a tainted turn may do.

Add a doctor check

A function over facts that returns a proposed fix as a transaction. Diagnostics and repairs are the same object, so a check can never suggest something the change API would refuse.

Add a model

A registry entry, signed. Providers implement one trait covering tool use, streaming, caching, structured output and multimodal input; local engines run as sandboxed subprocesses.

Add hardware

A quirk module under nix/hardware/ with the facts that identify the machine. An agent that finds a quirk on its own body can file it as a proposal for everyone else’s.

Add a theme

A token file, and only a token file. There is nowhere else to put a colour, which is the point.

Tacid also exposes an MCP server of its own. Third-party servers register as untrusted principals with their own policy, and cannot escalate through the agent that called them.

Run the loop in a terminal

The development demo does not require Nix or model weights once the source and dependencies are available. Start with the setup guide for requirements and commands.

View the historical CLI recording
tacidthe founding demonstration43da513
# 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.

# The hash-chained journal. The refusal is a record too.
$ tacid history
History:
     6  asked     the owner's policy asks before services.enable
     5  proposed  enable openssh (enable the openssh service)
     4  concluded applied as generation 2
     3  health    passed
     2  activated generation 2 (switch, from 1)
     1  allowed   every effect of this change can be undone
     0  proposed  install ripgrep (install ripgrep (cli))

# One command back to the previous generation.
$ tacid undo
I have undone the last change and switched the machine back.

# Provenance, including whether this machine built its own code.
$ tacid version
TacidOS 0.0.1 (dev)
  protocol   0.1
  autonomy   reversible
  generation 3
  self-built this machine is running code it built itself
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.