The build plan
TacidOS — Build Plan
The comprehensive build plan: architecture, surfaces, security, release engineering, phases.
Companions: MANIFESTO.md (why), docs/architecture.md (stable reference), docs/decisions/ (every settled question), SECURITY.md (invariants and threats).
This plan is written to be executed phase by phase, largely by coding agents working under the maintainer’s review. Every phase has an exit criterion that is a demonstration, not a feeling. Every work item has an acceptance test. When this plan and an ADR disagree, the ADR wins; when either disagrees with a hard invariant in SECURITY.md, the invariant wins.
1. Summary and vocabulary
TacidOS is an open-source, agent-first Linux distribution in which the agent is the operating system, the computer is its body, and Tacid is the nervous system. A machine is a declaration (the genome) built by Nix into an atomic generation. Every change is a typed transaction on one deterministic API. A tiny privileged core executes, an isolated keeper authorizes and audits, and the agent daemon plans and acts through them without ever holding a secret or a root shell. The machine works fully offline, updates itself continuously from a signed channel, rolls back what fails, and can improve its own source through a human-signed proposals loop.
| Term | Meaning |
|---|---|
| Body | The machine: hardware, firmware, kernel. |
| Nervous system | Tacid: tacid-stem, tacid-keeper, tacid-egress, tacidd, tacid-infer, tacid-session, tacid-shell, tacid. |
| Mind | Whatever model or models the owner chose, local or remote. Pluggable, never assumed. |
| Owner | The human the machine serves, identified by the owner key. Writes the constitution (policy). |
| Principal | Any authenticated actor: owner, local user, the agent, a remote agent, voice (untrusted). |
| Genome | /etc/tacid, a git repository: the complete declaration of this machine. |
| Generation | A built, activatable system closure (a NixOS generation) plus the matching state snapshots. |
| Transaction | A typed request for change. Kind action (ephemeral) or change (persistent, renders Nix). |
| Stem | tacid-stem: root, typed operations only, accepts only the keeper. |
| Keeper | tacid-keeper: policy, reversibility, taint, approvals, secrets, audit, health, snapshots. |
| Egress | tacid-egress: the only path to the network for agent processes. |
| Channel | A signed stream of tested (tacidos, nixpkgs) revisions: stable, edge, dev. |
| Immune system | Health gate + automatic rollback + boot counting, owned by the keeper and the bootloader. |
| Proposal | An agent-drafted change to Tacid’s own source, with CI and evaluation results, awaiting a human signature. |
| Facts | The structured live model of the machine (system.*), derived from kernel and systemd sources, never from parsed shell output. |
| Provenance / taint | Origin labels on every context item; taint propagates and caps autonomy. |
| Session | A conversation or job bound to a principal, with its own tool visibility and budgets. |
2. Goals, non-goals, people
2.1 Goals for 1.0
- Agency. The agent can inspect, change, diagnose and repair the machine, including its own source, through one typed, previewable, reversible API.
- Sovereignty. Fully functional offline. No accounts, no telemetry, no relay. The owner chooses the mind and holds the keys.
- Self-maintenance. Continuous signed updates with automatic rollback. Install once, never reinstall.
- Beauty. One language, one protocol, one design system, a complete terminal interface and a quiet graphical shell.
- Builders’ workstation. Developer, embedded and local-AI workflows that work out of the box on documented hardware, including NVIDIA.
2.2 Non-goals for 1.0
A new kernel, libc, init, package manager, compositor, terminal or browser. Universal hardware support. Phones and tablets. A hosted inference gateway or any Tacid account. Gaming positioning. Hiding Linux: a shell, SSH, git, systemd tooling and the Nix tools are always there.
2.3 People
- The builder. Developers, embedded engineers, security researchers and local-AI people who live in a terminal and a tiling compositor, want the machine to do things on request, and will not accept an agent that can quietly break it.
- The operator of a headless node. A server, a lab box, a board on a bench, a van: the same nervous system with no display, reached over SSH or by another Tacid.
- Not yet. Beginners, kiosks, fleets with directory services, daily-driver phones.
3. Architecture
3.1 Layers
┌────────────────────────────────────────────────────────────────────────┐
│ Surfaces tacid (CLI/TUI) · tacid-shell (Niri session) · voice │
├────────────────────────────────────────────────────────────────────────┤
│ Nervous tacidd · tacid-session · tacid-infer · tacid-egress │
│ system tacid-keeper (authority) · tacid-stem (privilege) │
├────────────────────────────────────────────────────────────────────────┤
│ Declaration /etc/tacid genome · tacid.* modules · tacidos flake │
├────────────────────────────────────────────────────────────────────────┤
│ Engine Nix (Lix) · nixpkgs (pinned via channels) · systemd │
├────────────────────────────────────────────────────────────────────────┤
│ Body Linux kernel · firmware · hardware │
└────────────────────────────────────────────────────────────────────────┘
3.2 Process model and trust zones
| Process | Runs as | Network | Responsibility |
|---|---|---|---|
tacid-stem | root | none | Socket-activated; accepts only the keeper’s uid plus a per-boot token. Typed operations: activate(generation, class), restore(generation), reboot, shutdown, disk.apply(plan) — a typed plan, laid out by the stem itself as fixed argument vectors, not a disko file (ADR-0033) — genome.write, system.install, udev.reload, principal.ensure, nftables.profile, bless_boot. No run(string). Refuses LUKS-slot, TPM-enrollment, Secure-Boot-key and bootloader changes without owner-present confirmation. Own minimal protocol crate. Fuzzed. |
tacid-keeper | tacid-keeper | IPAddressDeny=any | Policy engine; reversibility classifier; taint tracking; approvals (clients connect to the keeper directly, bound to a transaction id); secrets broker (systemd-creds, sops/age, Secret Service façade for apps); audit sink (root-owned append-only file, head sealed to a TPM NV counter or the owner’s remote); health gate and automatic rollback; Btrfs snapshots of /persist and /var/lib/tacid before data-touching transactions and at every activation. |
tacidd | tacid | via egress only | Sessions and principals; the agent loop; facts; memory; transaction rendering; nix eval/nix build in a memory-capped cgroup; model clients. Least-trusted daemon: no secrets, no root, no direct network. |
tacid-infer | tacid-infer, video/render | none | Supervises llama-server, whisper-server and the TTS server as sandboxed subprocesses (read-only model files, seccomp, no network, GGUF-embedded chat templates ignored). Exposes /run/tacid/infer.sock (OpenAI-compatible) to any local application. Evicts models on memory pressure; yields VRAM to owner workloads. |
tacid-egress | tacid-egress | loopback in, allowlist out | nftables rules installed by the stem pin tacid, tacid-session and sandbox uids to this proxy. Enforces the domain allowlist from the keeper, injects cloud credentials (the agent never sees a key), records the per-call egress ledger with data classes, drives the NET badge, implements one-command airgap. |
tacid-session | the person | none | One executor per person, a system unit started as them the first time the daemon asks, on a socket the system made (ADR-0035). Reads their files as them, read-only; opens what is not text with a parser in a sandbox; answers the daemon and the person, by socket credentials, and nobody else; everything it returns is untrusted. Voice capture and playback, desktop D-Bus and portals: a second, session-bound component, later. |
tacid-shell | the user | — | Wayland shell for the Niri session. Pure renderer of protocol state. |
tacid | the user | — | CLI; with no arguments the TUI. Pure renderer of protocol state. |
Request flow: tacidd requests, tacid-keeper authorizes, tacid-stem executes. Crash
policy: any process dies alone. If tacidd is down, the : verbs, the CLI’s deterministic
subcommands and a tty still work. If the mind is unreachable, everything but intelligence works.
3.3 Protocol
- JSON-RPC 2.0 over Unix domain sockets, newline-delimited, with file descriptors passed by
SCM_RIGHTSfor audio, pseudo-terminals and large blobs. Notifications carry streams (tokens, tool events, progress). - One crate,
tacid-protocol, defines every message withserde; JSON Schema is published from it and versioned. The stem has a separate, minimal crate. - Authentication:
SO_PEERCREDplus a per-boot token; principals are established at connect time. No polkit in the Tacid path (it needs a session agent that does not exist on a serial console). - MCP is JSON-RPC 2.0 too: one codec covers clients, tools and helpers. Tacid exposes an MCP server for other agents; third-party MCP servers are consumed as untrusted integrations.
- Transport-agnostic by construction: the same messages travel over SSH or WireGuard later so a brain can drive a remote body.
- The protocol carries UI state (plan, live tool state, diffs, pending approvals, badges). Clients render; they do not decide. That is how parity between the TUI and the shell is enforced.
- D-Bus via
zbusonly for desktop interop (notifications, portals, media keys).
3.4 The genome and on-disk layout
/etc/tacid/ git repository; the declaration of this machine
flake.nix flake.lock inputs.tacidos pinned; composes body + mind + owner
body/hardware.nix probed at install; regenerated by `tacid body probe`
body/disks.nix the disk as NixOS declares it, from the labels the stem wrote; the root ephemeral
body/gpu.nix graphics profile (nvidia-cuda | nvidia-graphics | nouveau | amd | intel)
mind/agent.nix tacid.* options written by the agent (agent-signed commits)
mind/policy.nix autonomy, capabilities, allowlists, budgets (owner-signed commits only)
mind/personality.md the system prompt; versioned; never hidden (owner-signed)
mind/preferences.nix user-level prefs the agent may set (agent-signed)
owner.nix free Nix; may import Home Manager (owner-signed)
pkgs/ agent-authored derivations; sandboxed builds; untrusted-tagged
secrets/ sops-nix, age-encrypted; keys never in the store in the clear
/persist/ the only state that survives a reboot (impermanence)
home/ etc/tacid/ var/lib/tacid/ etc/machine-id etc/ssh/ var/lib/<declared service state>/
/var/lib/tacid/ state, never in the genome
models/ content-addressed model blobs + registry cache
memory/<uid>/memory.db SQLite + FTS5 + sqlite-vec, per user
journal/ keeper-owned; audit.log is root-owned append-only
cache/ evaluation caches, indices
snapshots/ Btrfs snapshot metadata
/run/tacid/ sockets: stem.sock (0600 keeper) · keeper.sock · tacidd.sock · infer.sock · egress.sock
The owner is a key. Owner-only paths are those whose last commit is signed by the owner key; the
agent key signs the agent’s commits; the keeper refuses to load a policy whose head commit is not
owner-signed. git log is the provenance ledger of the genome. mind/ is portable (“reincarnation”
onto new hardware); body/ is regenerated. Untracked files are invisible to a flake, so tacidd
stages before every evaluation. Memory and journal never live in the genome: flakes copy the tree
into the store on every evaluation.
3.5 Transaction lifecycle
Two kinds share one envelope, one policy path, one journal:
action(ephemeral): run tests, open a serial monitor, flash a board, connect to a Wi-Fi network, take a screenshot, fetch a page. Executed by tools, sandboxed by default.change(persistent): anything that should survive a reboot. Rendered into the genome.
Lifecycle of a change:
1 intent natural language, a `:` verb, `tacid do`, a script, or another agent
2 typed change one or more ChangeItems (package.ensure, service.enable, udev.profile, option.set, …)
3 keeper classify reversibility over the rendered diff; compute taint; check budgets
4 policy verdict allow | ask | deny, with the reason
5 render tacid.* option edits in mind/agent.nix (or pkgs/ for agent derivations)
6 stage + eval git add; nix eval; schema errors are returned to the model verbatim, they are actionable
7 build nix build in a memory-capped cgroup; cache hits expected; closure delta recorded
8 preview human summary · option diff · full evaluation and activation log
9 approval if required: client → keeper, bound to the transaction id; never through tacidd
10 activation class switch | session-restart | boot (kernel, driver, compositor, tacidd itself are boot-class)
11 stem activates switch-to-configuration-ng; boot-class changes are staged for the next quiet reboot
12 health gate units reached target · agent self-test (stub eval + tool-use smoke) · declared checks
13 rollback on failure the keeper re-activates the previous generation and journals why
14 journal + commit append-only journal entry; agent-signed commit to the genome with the transaction id
Rollback claims are three-way and honest: system (generation), data (Btrfs snapshot taken
before any data-touching transaction), external (never: firmware, remote services). tacid undo
is a generation switch and a genome revert as one operation.
3.6 Reversibility classifier and taint
Reversibility is computed by the keeper over the rendered option diff, never asserted by the model.
- Default rule: an option change covered by generation rollback with no entry in the irreversible list is reversible. Unknown options are irreversible.
- Irreversible despite rollback: exposure (listening services, open firewall ports,
sshd, remote agent access), principals (users, groups, sudo, owner key), secrets, boot and encryption (bootloader, LUKS, TPM, Secure Boot), external devices (flash, disk operations on other media), data deletion,git push, anything that sends file content off the machine. - Taint: every context item carries provenance (
owner,user,system,tool,file,web,mcp:<name>,voice). Tool output derived from untrusted input is untrusted. A turn whose context contains untrusted content is capped at level 1 for side effects; voice-origin intents are capped at level 2 unless confirmed on a physical input. - Budgets per session: transactions per hour, closure delta in bytes, egress bytes, cloud spend estimate. Exceeding a budget downgrades to level 1 until the owner resets it.
3.7 Policy and autonomy
| Level | Name | Behaviour |
|---|---|---|
| 0 | observe | Read-only. |
| 1 | consult | Proposes everything; asks for everything. |
| 2 | reversible | Default. Acts with notification on changes the classifier proves reversible; asks otherwise. |
| 3 | trusted | Also acts without asking on owner-declared categories in mind/policy.nix. |
| 4 | sovereign | Everything except the hard invariants. Persistent indicator on every surface. Expires at reboot or after a configured window. Never entered silently. |
Capability-scoped tool visibility: a session’s model sees only the tools it is permitted to use.
This keeps small models competent and shrinks the attack surface. The deterministic : verbs and
tacid do never touch a model at all.
3.8 Secrets
- Declared secrets (API keys, tokens, host keys) are sops-nix/age in
secrets/; machine and service secrets aresystemd-creds, TPM2-sealed where available. - The model sees only handles:
secret://anthropic/default. The keeper resolves a handle for a specific destination; the egress proxy injects it into the outbound request; tools receive it in an environment variable scoped to one invocation. - A scrubber runs on every tool result, memory write and audit line; known secret values and high-entropy tokens are replaced by handles before anything reaches a model or a log.
- Protected paths are denied by default:
~/.ssh,~/.gnupg, credential stores, browser profiles,/etc/tacid/secrets,/persist/etc/ssh.
3.9 Egress
- nftables rules (installed by the stem) restrict the
tacid,tacid-sessionand sandbox uids to the loopback egress socket. There is no other route. - The proxy enforces the domain allowlist from policy, injects credentials, records the ledger
(destination, data classes: text, file content, screenshot, logs, audio; byte counts; model;
estimated cost), and raises the
NETbadge on every surface while a request is in flight. tacid airgap onswitches the nftables profile and shows a persistent badge. Airgap is a one-command reflex, not a setting buried in a menu.
3.10 Facts and Doctor
system.* is a structured, typed model of the machine that the agent queries instead of parsing
shell output: cpu, memory, gpu, disks, usb, serial, probes, network, services,
processes, logs, generations, packages, kernel, session, drift (declared vs. live).
Sources: sysfs, udev, journald, systemd’s D-Bus, the Nix store, nvidia-smi/DRM.
tacid doctor <domain> runs deterministic checks over the facts (nvidia, wayland, audio, network,
bluetooth, nix, disk, models, cuda, toolchains, usb, probes) and lets the model explain and propose;
the checks produce truth, the model produces prose. The health gate reuses the same checks.
3.11 Memory
SQLite with FTS5 and sqlite-vec, one database per user, vector index versioned by embedding-model id, forward-only migrations with a snapshot at every activation so a generation rollback never corrupts a newer database.
Layers: session (task context), project (facts bound to a repository), owner
(preferences the owner allowed), machine (facts and action history). Never-memory: secrets
and protected values. Every write carries provenance; memories derived from untrusted content are
never loaded as instructions; owner-layer writes need confirmation. tacid memory searches, edits,
exports and deletes.
Learning beyond memory: opt-in, sanitized tool-trace collection with a published sanitizer
specification and a shown-before-upload bundle feeds tune/, which fine-tunes a small Tacid
tool-use model. A small model fine-tuned on Tacid’s own tools beats a large generic one at running
this machine. The dataset is licensed CDLA-Permissive-2.0.
3.12 The mind: routing and providers
- Task classes:
route,classify,edit,summarize,plan,code,speak,listen,embed. - Privacy class is derived from provenance labels and path policy, never from a classifier.
- Routing inputs: task class, privacy class, policy, context size, latency need, offline state, local hardware, requested model, budget.
- Providers behind one rich trait: first the Anthropic Messages API and the OpenAI-compatible family
(OpenAI, xAI, Google’s compatibility endpoint, OpenRouter,
llama-server), then native adapters when a provider’s tool-use, caching or structured-output capabilities diverge enough to matter. No default provider; the installer asks; keys go to the keeper. - Every cloud request shows the badge and writes the ledger; cost is reported and never claimed authoritative.
3.13 Local inference
- Engines are subprocesses, never linked in:
llama-server(CUDA; Vulkan on AMD by default with ROCm opt-in; Vulkan on Intel; CPU),whisper-server, a Kokoro TTS server. Sandboxed with read-only model files, seccomp, no network. A second Rust backend (mistral.rs) may follow behind the same trait. - Constrained decoding: tool schemas compile to GBNF grammars so small local models call tools reliably.
- Registry
models/registry.toml(signed): id, license, source, hashes, size, RAM/VRAM, backends, context, intended tasks. Profilestiny(8 GB RAM, no dGPU),core(16 GB RAM or 8 GB VRAM),lab(32 GB or 12+ GB VRAM) recommended from detected hardware. Weights are never in the netinstall ISO; the offline ISO carriestiny; USB model packs serve airgapped sites. - Resource governance: models are evicted on memory pressure; Nix evaluation runs in a capped
cgroup; inference yields VRAM to owner workloads;
tacid modelsshows disk use.
3.14 Voice
Push-to-talk by default (Super+Space held in the shell, a key in the TUI). Wake word
(openWakeWord via ort in the session) opt-in with a persistent indicator; audio never leaves the
machine before the wake word fires. Streaming partial transcripts into the surface. Interruptible
speech. Voice is an untrusted principal. Budgets (core profile, mid-range laptop): release to first
partial ≤ 400 ms; 8 s utterance to final text ≤ 1.5 s; first model token ≤ 300 ms (7B Q4 on GPU);
first audio ≤ 400 ms after the reply starts.
3.15 Updates and channels
nixos-unstable ──► Tacid CI ingest ──► build matrix (x86_64, aarch64; every reference config)
──► VM tests ──► cache fully populated ──► sign ──► edge
──► hardware matrix ──► maintainer promotion ──► stable
- A channel manifest names
tacidos_rev,nixpkgs_rev, kernel, tests passed, publish time and expiry, signed with TUF roles: an offline root key in the maintainer’s custody, online snapshot and timestamp keys with short expiry, revocation by manifest. Store paths carry separate cache signatures. tacid-update.timer: poll hourly onedge, daily onstable; build when new; switch only at quiet points (idle, on AC, no active session, or at a scheduled window); boot-class changes wait for a reboot and use systemd-boot boot counting; the health gate must pass or the previous generation is restored. The new lock is committed to the genome.- Kernel:
edgetracks the newest stable series;stabletracks the newest series validated by the hardware matrix; LTS is available for servers and boards. - Generation retention: the last ten, everything from the last thirty days, and anything pinned —
declared by the owner under
tacid.core.retention, applied by the keeper from its own policy, carried out by the stem by number (ADR-0040). The collection runs weekly, or when the owner says, once nobody is at the machine. Models are not store paths and are never collected. - Binary cache: Attic behind a CDN;
cache.tacidos.com/stableand/edge.
3.16 The immune system
Owned by the keeper and the bootloader, outside the agent’s reach:
- Pre-switch checks: evaluation and build succeeded; closure delta within budget; activation class allowed now.
- Post-switch gate within a window:
boot-complete.targetreached;tacidd, keeper, egress and (if graphical) the shell are up; declared services healthy; network reachable if declared; inference answers a stub prompt; the agent’s tool-use smoke test passes. - Failure: re-activate the previous generation, restore the matching state snapshot for Tacid’s own databases, journal the failure with logs, and notify every surface.
- Boot-class failures: systemd-boot counters mark the entry bad after the configured tries and the previous generation boots.
- The owner can always select any generation from the boot menu and always reach a tty.
3.17 Self-improvement
- Tacid’s source is one input of every genome. At autonomy level 3 or higher, the agent may check
out
tacidos, make a change, run the test suite, build this machine with--override-input tacidos ./checkout, switch, and verify. - The crates are partitioned. Core (
tacid-stem,tacid-keeper,tacid-protocol, the health gate) is channel-signed; the keeper verifies the core’s store signatures before the stem activates, and only the owner may replace core. Everything else is agent-mutable. A machine running self-built code is marked and cannot claimstable. - The agent never opens pull requests. It files a proposal (branch, CI results, evaluation results, a plain-language rationale) into the owner’s queue. The owner reviews, signs (Developer Certificate of Origin), and pushes. Species evolution is proposal → maintainer → CI → channel → every body.
- Hardware quirks discovered on one machine become proposed hardware modules for all. Boards that cannot build Tacid use a workstation body as a remote builder.
3.18 Storage, boot, impermanence
GPT and UEFI only; systemd-boot; systemd stage-1. LUKS2 on by default with TPM2 + PIN bound to
PCR 7 (Secure Boot state) so hourly kernel updates never lock the owner out; a recovery key shown
once at install. Btrfs with subvolumes @root (ephemeral, reset at boot), @persist, @home,
@nix, @snapshots. Impermanence is the default: the machine is a function of (genome, persist);
every tacid.services.* option declares its persistent directories; the agent watches for writes
to ephemeral paths and offers to declare them. Secure Boot via Lanzaboote is integrated from the
first ISO and enrolled after install with tacid secureboot enroll (Microsoft certificates
included so NVIDIA option ROMs keep working); it is opt-in until the certified matrix passes, and
the documentation says plainly that NixOS does not enforce kernel-module signing. Swap: zram by
default, a swap file when hibernation is requested.
3.19 Networking
iwd, systemd-networkd and systemd-resolved. The agent and the shell are the network UI; VPNs and WireGuard peers are declared. Firewall on by default; SSH off unless selected; the agent socket is never exposed beyond the machine by default; remote access is SSH-first with no relay.
4. The tacid.* option surface
The agent does not write arbitrary Nix. It edits typed options with descriptions, examples and validation whose failures are messages a model can act on.
| Module | Agent | Contents |
|---|---|---|
tacid.core | read | Boot, kernel, users, encryption, the daemons’ own units and sandboxes. |
tacid.agent / tacid.policy | read | Autonomy, capabilities, allowlists, budgets, providers, models. Owner-signed only. |
tacid.packages | write | Package lists by category: cli, gui, dev, embedded, ai. |
tacid.services | write | Allow-listed services with curated options and one VM test each. First set: OpenSSH, Tailscale, WireGuard, Syncthing, Podman, libvirt, Bluetooth, printing, Flatpak, local inference, the binary-cache client. Every service declares its persistent state. |
tacid.hardware | write, approval | udev profiles (curated), GPU profile, laptop quirks, serial and probe permissions. Raw udev rules need review. |
tacid.desktop | write | Theme selection, wallpaper, fonts, keybindings, monitors, autostart. |
tacid.dev | write | Toolchains, language servers, containers, per-project environments. |
tacid.user | write | Shell, terminal, git and editor defaults for the user. Tacid does not depend on Home Manager; owner.nix may import it. |
tacid.raw | level ≥ 3 | Free Nix with a loud warning, rendered into a separate file. |
Rules: every option has description, example and a type or assertion; the agent’s edits touch
only mind/agent.nix, mind/preferences.nix and pkgs/; evaluation errors are returned to the
model verbatim; a module is not merged without a VM test.
5. Surfaces
5.1 CLI
tacid open the TUI
tacid ask "<request>" one request, streamed
tacid do <verb> [args] deterministic transactions, no model: install, remove, enable, disable, set, unset
tacid status | facts <path> [--json] live state
tacid diff [declared|<generation>] declared vs live, or between generations
tacid apply | undo [--to <n>] apply pending changes; undo = generation switch + genome revert
tacid pending what is waiting for your answer, at the keeper
tacid approve <id> [--session] consent, brought to the keeper directly; then the daemon carries it out
tacid deny <id> decline at the keeper; nothing is touched
tacid history [--today] | journal what happened, by whom, with which transaction
tacid generations | rollback [<n>] generations and the boot default
tacid update [--now] | channel <name> updates and channel selection
tacid doctor [<domain>] deterministic diagnostics with explanation
tacid models list|recommended|install|remove|benchmark
tacid providers list|add|test
tacid policy show|set owner key required
tacid memory search|edit|export|delete
tacid device list|serial|probe hardware graph and embedded devices
tacid project init|env|status project environments
tacid secrets add|list owner key required
tacid airgap on|off | secureboot enroll|status | genome sync|export|import
tacid version full provenance: tacidos rev, nixpkgs rev, kernel, GPU profile, channel, self-built flag
Every command accepts --json. The shell uses the same protocol and nothing more.
5.2 TUI
tacid atlas edge 2026.09.02-1842 mind: local/qwen3-8b NET off gen 187 level 2 3.2G/16G
┌ conversation ────────────────────────────────────────────────────────────────────────────┐
│ you ship a static site from ~/site to /srv and keep it running │
│ tacid plan: 3 steps · 1 change (reversible) · 0 approvals │
│ ✓ facts.services nginx not installed │
│ → change tacid.services.nginx = { enable = true; root = "/srv/site"; } [diff] │
│ ○ verify GET http://localhost/ → 200 │
└──────────────────────────────────────────────────────────────────────────────────────────┘
: approvals 0 journal ⏺ ptt
Readable at 80×24 in 16 colours on a serial console; better with more. Tool traces collapse and
expand. Approval prompts are modal and keyboard-only: y once, n, s scope this session, a
always for this category (owner key). : verbs never touch the model: :diff, :undo, :gen,
:update, :airgap, :mind, :policy, :sh (a real shell, also a tool the agent can request),
:q.
5.3 Shell
A Niri session with tacid-shell (Iced on iced_layershell):
- Bar: workspace strip, clock, the badges (
NET,AIRGAP,SOVEREIGN,MIC,SELF-BUILT), generation and channel, pending approvals, mind status. - Command surface (
Super+Space): one box for launching, searching and asking; the user never decides whether something is “chat”. Deterministic matches first; the agent answers below. - Agent panel: goal, plan with live tool state (never theatrical progress), system changes with summary/diff/log tabs, approve/reject bound to the transaction id, pause and stop.
- Notifications: standard notification protocol; Tacid’s own are quiet and actionable.
- Settings: curated product concepts (System, AI, Developer), never every Nix option.
- Recovery at boot: generations with plain-language labels (“Before CUDA setup”), inspect changes, boot selected, make default.
Terminal: Ghostty with OSC 133 shell integration so terminal sessions become structured objects (command boundaries, exit status, working directory) that can be sent to the agent on request, never scraped. Browser: Firefox, unmodified, with an explicit “send page to Tacid” later.
5.4 Installer and first boot
Two artifacts: netinstall (≤ ~1 GB: kernel, installer, TUI, daemons, no graphical closure, no
weights) and offline (several GB: graphical closure plus the tiny model so the first boot is
not mute). The installer is not separate code: it is the stem, the keeper and tacidd running in
genesis mode on the live system, driven by the TUI over the console or a serial line, and its
output is the first genome and the first generation.
Welcome to Tacid
1 Keyboard and language
2 Network (optional)
3 Disk (the plan, previewed) 6 Autonomy level (default: reversible)
4 Encryption (on; TPM2 + PIN; recovery key) 7 Mind: local profile sized to hardware; optional cloud key
5 Owner key and user 8 Review → Install
Hardware profile is auto-detected with an explicit GPU choice on NVIDIA machines (CUDA-ready,
graphics only, or fully open). What a person knows of the body that no probe finds out goes in at
birth — tacid install --body <file>, written under the genome’s body/ and imported from
owner.nix — so the first system built is the one the machine will keep building. First boot: the
agent introduces itself, verifies hardware against the facts, offers model downloads, and shows
Super+Space (or the TUI prompt) with one suggestion.
Also supported: nixos-anywhere over SSH, and “Tacid on your existing NixOS” through
inputs.tacidos.nixosModules.default.
6. Developer and embedded workflows
- Projects: detection (Cargo, npm, CMake, PlatformIO, Zephyr, ESP-IDF, Python, Go, Nix);
generated dev shells with direnv; never rewrite an existing
flake.nix,shell.nixordevenv.nix; optional.tacid/project.tomlfor per-project policy and embedded targets. - Git: typed tools (
status,diff,log,show,branch,worktree,commit); destructive operations (reset --hard, force push, deleting untracked files) are classified irreversible. - Editors: Neovim, Helix, Zed, VS Code available; “ask Tacid about selection”, open at location, agent-produced patches. No IDE of our own.
- Containers: Podman by default, Docker available, devcontainers, distrobox. App-source ladder: nixpkgs → Tacid module → upstream flake → Flatpak → container → wrapper.
- Device center: a USB/serial/probe graph (VID/PID, serial, interfaces, driver, permissions, inferred capabilities, project association). On connection: a quiet notification with “open” and “use with current project”.
- Probes and targets: ST-Link, J-Link, CMSIS-DAP, Raspberry Pi Debug Probe, Black Magic Probe;
OpenOCD and probe-rs; STM32, ESP32, nRF, RP2040/RP2350, Zephyr boards.
tacid flash,tacid serial,tacid probeas transactions with exact target identity; UART capture and crash summaries; map-file comparison. - Datasheets: local PDF extraction in a sandbox, page-aware retrieval with citations, upload only with permission.
- Terminal and hardware together: the agent can hold source, build output, debugger state, serial log and target identity in one context and answer “why does it reset when the motor starts”.
7. Design system and budgets
- One source of truth:
design/tokens.toml(colour scales, one accent, type scale, spacing, radii, motion durations). Generators produce the TUI theme, the Iced theme, the terminal palette, the Niri configuration fragment, and palette-level GTK and Qt themes (they honour no more). - Typography: Inter for interface text, JetBrains Mono for code and the TUI. No font soup, no patched icon fonts; a proper icon set.
- Tone: quiet, precise, dense when needed. Dark and light are equals. Motion only when it explains a state change. No neon, glass, gradients, sci-fi noise or giant chat windows.
- Published budgets, measured in CI on the reference laptop: power-on to prompt ≤ 8 s (TUI) and
≤ 12 s (shell); idle RAM ≤ 400 MB without a loaded model;
tacid diff≤ 200 ms; a no-op apply ≤ 1 s; input lag imperceptible; the voice budgets in §3.14.
8. Security
The invariants, trust zones, threat model and key custody are in SECURITY.md and are normative. This section adds the engineering matrix.
| Surface | Mechanism |
|---|---|
| Daemons | systemd hardening: ProtectSystem=strict, PrivateTmp, NoNewPrivileges, RestrictAddressFamilies, SystemCallFilter, IPAddressDeny where applicable, dedicated users, socket activation. |
| Spawned tools | bubblewrap + Landlock + seccomp; scoped mounts; no network unless the policy grants it, and then only via egress; time and memory limits. |
| Untrusted parsers | PDF, archives, images, web content parsed in sandboxes in the user’s session, never in tacidd. |
| High-risk code | microVM via microvm.nix (Firecracker or cloud-hypervisor) with no devices unless declared. |
| Inference | Subprocesses with read-only model files, seccomp, no network; embedded templates ignored; signed registry. |
| Network | nftables by uid/cgroup to the egress proxy; DNS pinned; airgap profile. |
| Supply chain | Signed tags; SLSA provenance for CI artifacts; SBOM per release; reproducibility checks on the core; pinned inputs; signed channel manifests (TUF roles) and signed cache; dependency review on every bump. |
| Testing | Prompt-injection corpus as a CI gate (zero side effects at level 2); fuzzing of the stem protocol and the scrubber; permission regression tests; OVMF + swtpm harness for Secure Boot and TPM paths. |
9. Hardware program
- Order: x86_64 UEFI first; aarch64 UEFI second; riscv64 aspirational; phones far future.
- Graphics: NVIDIA (CUDA, open kernel module where supported, PRIME hybrids, suspend/resume,
external displays, explicit sync under Niri), AMD (Vulkan by default; ROCm opt-in), Intel
(Vulkan). No claim of support before the physical matrix passes; physical NVIDIA CI before
edgeis public. - Tiers: certified (physically tested by the project), supported (upstream support with active
triage), community (reports exist, no promise). Published at
hardware.tacidos.comor in the docs. - Reference machines (to be acquired): an Intel laptop, an AMD laptop, an NVIDIA desktop, an Intel/NVIDIA hybrid laptop, an AMD/NVIDIA hybrid laptop, an aarch64 UEFI machine, an ARM board for the headless profile, a set of debug probes and boards for the embedded bench.
- Apple Silicon: a separate community channel built on the Asahi enablement stack, limited to models with mature support; never a general “runs on MacBooks” claim; the installer never touches APFS.
- Boards: headless Tacid nodes on selected SBCs; a workstation body serves as their remote builder; no desktop promise on arbitrary boards.
10. Release engineering
- Versioning: components SemVer; stable releases CalVer
YY.MM; channel revisionsYYYY.MM.DD-HHMM;tacid versionprints all of it. - Channels:
dev(from branches, no promise),edge(automatic on green CI and a populated cache),stable(maintainer promotion after the hardware matrix). - Quality gates for
stable: every reference configuration builds on both architectures; shell, agent, keeper and stem build; docs build; privileged API tests, permission regression tests and the injection corpus pass; the release is signed; a clean install boots; an update from the previous stable succeeds; rollback succeeds; the recovery generation boots; login, command surface, notifications, settings and multi-monitor smoke tests pass; local inference, one cloud adapter, tool calling, denial policy and the journal work; on NVIDIA machines the driver loads, the Wayland session works, CUDA inference works and suspend/resume status is documented. - Infrastructure: Attic cache behind a CDN; x86_64 and native aarch64 builders; a continuous-integration signer with short-lived keys; the maintainer’s offline root key.
11. Phases
Each phase lists work items with acceptance tests, sized for an agent working under review. Durations are ranges for one maintainer plus agents; they are estimates, not promises.
Where this stands. Phase 0 is complete and Phase 1 is substantially complete: the protocol, the classifier, the keeper with its invariants, the journal, the renderer, the stem, the agent loop with its tools, the fact API, memory, the doctor and the health gate it shares, the egress proxy, the cloud adapters, the signed-manifest update system, the model registry and inference supervisor, the self-improvement partition and proposal queue, the disk layout and genesis genome, the untrusted-content sandbox, the CLI, the terminal interface and the design system are written and tested (898 tests, no warnings). The Phase 1 exit criterion runs end to end, and the injection corpus is a gate of its own: eleven cases in which the model obeys the attacker completely and the machine changes nothing. What remains in Phase 1 is a real provider on the other end of the proxy, and a real machine: the Nix builder and the stem’s switch are no longer stand-ins — an installed machine has built its second generation from what it carries and taken it back (ADR-0037). The process split is real since the first boot: the daemon holds no journal and no token and asks the keeper for everything over the keeper’s socket, consent has a wire path (
tacid approve), and every socket in/run/tacidis a socket unit’s (ADR-0032). The checkboxes below are left unticked deliberately — a phase is finished when it is finished on hardware, not when its tests pass in a container. The repository layout in §12 marks which crates exist and which are still a plan; where this document and the code disagree, the code is right and this document is behind.
Phase 0 — Genesis (2–4 weeks)
Goal: the repository exists, the thesis is tested, and every decision is written.
- The spike. A ~200-line script that takes a natural-language request, calls a model, edits a
tacid.*option in a scratch NixOS configuration, runsnix build, shows the diff, and switches on a developer’s NixOS machine. Accept: “install ripgrep” works end to end and a 60-second recording exists. - Repository layout as in §12 and ADR-0025;
flake.nixexposingnixosConfigurationsfor a reference x86_64 and aarch64 host,packages,devShells,checks,formatter. Accept:nix flake checkpasses on both architectures in CI. -
tacid-protocolv0 with JSON Schema published as a build artifact. Accept: schema round-trips every message in tests. - Design tokens v0 and the generators for the TUI theme and terminal palette. Accept: one change to the accent propagates to both outputs in a test.
- CI: evaluation, Rust build and tests,
nixfmt/rustfmt/clippy, a NixOS VM boot test. - All ADRs merged;
CODEOWNERS; org and crate names reserved; trademark search done. -
tacid --versionprints provenance from a real generation.
Exit: the reference configuration builds on both architectures; the spike installs a package end to end; every open question from the three source plans maps to an ADR.
Phase 1 — Spine (8–12 weeks)
Goal: the deterministic change path exists with authority, audit and rollback, on a developer’s NixOS machine.
-
tacid-stemwithactivate,set_boot_default,reboot,bless_boot; keeper-only socket; fuzzed protocol. Accept: a malformed request is rejected; a non-keeper peer is refused. -
tacid-keeper: policy engine, the reversibility classifier with the irreversible list, taint tracking, approvals bound to transaction ids, the audit sink, the health gate and automatic rollback, snapshots. Accept: an injected failing generation is rolled back within the window; approvals presented throughtaciddare rejected. -
tacidd: sessions and principals, the agent loop, transaction rendering, staging and evaluation,nix buildin a capped cgroup, facts v0 (services, packages, generations, drift). -
tacid-egress: allowlist, credential injection, ledger, badge, airgap. Accept:taciddcannot reach any host except through the proxy; a cloud request succeeds withouttaciddever holding the key. - Models: the scripted stub model for CI; the Anthropic adapter; the OpenAI-compatible adapter; record/replay cassettes.
-
tacidCLI:ask,do,status,diff,apply,undo,history,generations,rollback,version, all with--json. - Self-build:
tacid dev build --override-input tacidos ./checkoutfor the agent-mutable crates; the self-built marker. Accept: a patchedtaciddruns on the developer machine. - Injection corpus v0 as a CI gate. Accept: zero side effects at level 2 across the corpus.
- VM tests for the whole loop with the stub model.
Exit: “install Wireshark and let me capture packets without root” produces a summary, an option diff, an approval, an activation, a verification and a one-command undo on a developer’s NixOS machine, with the injection corpus green.
Phase 2 — Senses (6–8 weeks)
Goal: the machine can be talked to, offline, and knows itself.
- TUI with the layout in §5.2, the
:verbs, modal approvals, serial-console readability. -
tacid-infer: sandboxedllama-serverandwhisper-server; constrained decoding; the OpenAI-compatible local socket; eviction on pressure. Accept: a 7B model calls a tool with a valid schema 100 times out of 100 in the eval set. - Registry and profiles:
models/registry.toml,tacid models, hardware-sized recommendation, verified downloads. - Voice: push-to-talk in the TUI, streaming partials, Kokoro TTS, interruption. Accept: the §3.14 budgets on the reference laptop.
- Memory v1 with provenance, never-memory,
tacid memory. - Facts v1 and Doctor for nvidia, wayland, audio, network, nix, disk.
- Journal and unit tools:
journal.readandservice.status, thelogs.readcapability, and the rules that answer “why did sshd fail” offline (ADR-0043). Built, with a lifecycle assertion against a unit that fails on purpose. - Tool-use eval set v1: install a service, edit a file, explain a journal, diagnose a failing unit; scored per model in CI.
Exit: with the network cable out, a fresh session reads the machine and answers correctly; a
push-to-talk round trip lands within budget; tacid doctor covers six domains.
Phase 3 — Body (8–12 weeks)
Goal: install from an ISO, then never reinstall.
- Images: netinstall and offline ISOs; a raw image for mini PCs. Accept: the netinstall ISO is ≤ 1 GB; the offline ISO installs with no network. Both ISOs build; the netinstall is 1.47 GB, so the first acceptance stands open and honest. Both carry the sources they were built from and build a first system from them with no network, which is the second.
- Installer in genesis mode with the flow in §5.4; disk plans previewed and laid out by the stem from a typed plan; LUKS2 with TPM2 + PIN; Btrfs layout; impermanence; owner key generation; hardware profile with the NVIDIA choice. The plan, the layout, the sealed key, the genome and the installed system are done, and set out in a test where a machine boots from the disk on the PIN its TPM asks for, refuses a wrong one, and opens the root with the passphrase when booted with a TPM that never sealed anything (ADR-0039); that test’s first full run was under way when this was written. A person who brings no key is given one, its secret half written into their home by the stem, and the recovery key is enrolled, handed back once and kept nowhere (ADR-0041); the test boots the machine with the recovery key once its TPM is gone.
- Lanzaboote integrated;
tacid secureboot enroll; OVMF + swtpm harness in CI. - Channels: manifests with TUF roles; Attic cache; CI promotion to
edge. The maintainer’s side exists —tacid-channelmakes the keys, the keyring, the signed manifest with every input locked, and signs the cache the way Nix reads it — and the follower’s side is written and set out in a test under software emulation: a machine with nothing in its store but its own closure follows a signed revision from a cache and reboots into it (ADR-0038); the test’s first full run was under way when this was written. The cache behind a CDN and the pipeline that promotes are not built yet. -
tacid-update: quiet-point scheduling, activation classes, boot counting, genome lock commits, retention and garbage collection. Accept: an update that breakstaciddis rolled back automatically and journaled. Done: the check proposes the advance to the daemon as the system principal, the lock moves and is committed, a channel revision is boot-class, boot counting abandons a revision whose daemon cannot start after three boots, and the keeper says the machine came back and moves the profile to what runs — asserted by the same test. Retention is the owner’s declaration, applied by the keeper and carried out by the stem by number, withtacid-collecton the owner’s timer (ADR-0040); the lifecycle test runs it on a machine with nothing to forget, and the forgetting of real generations is set out for the channel test. - First boot experience. The headless form is built: the installer declares the body’s birth, the keeper reads it back at its first start and writes the line down, and the machine introduces itself once on the console (ADR-0042); the installation test is set out to show it. The graphical moment waits for the shell.
- Idle RAM and boot budgets measured in CI.
Exit: a fresh install self-updates from edge; an injected bad generation rolls back on its own;
the maintainer daily-drives the TUI on a real laptop. The first two hold under software
emulation, against a channel the test publishes; the third needs the laptop.
Phase 4 — Face (8–12 weeks)
Goal: a graphical Tacid that is daily-drivable and quietly beautiful.
- Niri session with the design system applied; xwayland-satellite; explicit sync on NVIDIA.
-
tacid-shell: bar with badges; the command surface; the agent panel with approvals; notifications; settings v1; recovery labels in the boot menu. - Tokens across apps: terminal, GTK and Qt palettes, editor themes.
- Ghostty integration with OSC 133 sessions and “send to Tacid”.
- Graphics matrix: Intel, AMD, NVIDIA desktop, two hybrid laptops; suspend/resume; external displays; XWayland apps.
Exit: the maintainer daily-drives the shell; the screenshots are worth posting; the graphics matrix is green on the reference machines.
Phase 5 — Hands (8–12 weeks)
Goal: the workflows that make Tacid a builder’s machine.
- Projects: detection, dev-shell generation, direnv, the never-rewrite rule,
.tacid/project.toml. Detection, the never-rewrite rule and the declaration are built; the shell is generated and shown, for the person to save (ADR-0046). Writing it waits for an executor that may write. - Git tools with destructive classification; editor adapters for Neovim and Helix. The reading half is built: status, diff, log, show and branches as the person, through their executor, which writes nothing (ADR-0044). Writes wait for an executor that may write.
- Containers: Podman, devcontainers, distrobox; the app-source ladder in the command surface.
- Device center and the hardware graph; udev profiles;
tacid device. The graph is a fact, with a role and the granting profile for every probe the curated rules know, andtacid deviceshows it (ADR-0045); the notification on connection waits for the shell. - Probes: ST-Link, J-Link, CMSIS-DAP, Pi Debug Probe, Black Magic; OpenOCD and probe-rs;
tacid flash|serial|probe; UART capture and crash summaries. - Datasheets in sandboxes with citations; browser tool sandboxed with untrusted content.
- Embedded bench in CI: USB relay, one board per ecosystem, flash and UART verification.
Exit: plug in an ST-Link and an STM32 board, say “build this, flash it and show me the boot log”, and it happens; an embedded engineer flashes a board within ten minutes of install.
Phase 6 — Evolution (6–10 weeks)
Goal: the system improves itself and the species learns.
- Proposals queue: the agent drafts, attaches CI and evaluation results, the owner signs and
pushes;
tacid proposals. - Core signing partition enforced by the keeper; the self-built marker in
tacid versionand the bar. -
tune/: the sanitizer with a published specification, the shown-before-upload bundle, the dataset license, a first fine-tuned Tacid tool-use model in the registry. - Hardware-quirk loop:
tacid body proposeturns a local quirk into a proposal fornix/hardware/. - Tacid as an MCP server with per-agent principals and policy.
Exit: the agent fixes a real bug in its own tool router, files a proposal, the maintainer signs and
merges it, CI publishes to edge, and a second machine receives it.
Phase 7 — Many bodies (ongoing)
Goal: the same nervous system on more bodies.
- aarch64 images and native builders; a certified aarch64 machine.
- Headless node profile for boards with a workstation as remote builder.
- The hardware lab and the published hardware database.
- Remote transport for the protocol; one Tacid drives another; team genomes.
- Apple Silicon community channel where Asahi support is mature.
Exit: parity on a certified ARM machine; one Tacid drives another over SSH with mutual keys.
Phase 8 — 1.0
1.0 means: the update path is trusted; rollback works; the permission model is understandable; transactions are reliable; x86_64 support is broad on documented hardware; NVIDIA is genuinely supported; aarch64 is real on documented devices; the shell is cohesive; developer and embedded workflows are strong; the option surface is frozen; three months have passed without a rollback-worthy regression; and the security architecture has been publicly reviewed.
12. Repository layout and backlog by epic
tacidos/
├── flake.nix flake.lock
├── nix/ modules/ (core, agent, packages, services, hardware, desktop, dev, user)
│ profiles/ (workstation, headless, embedded, ai, minimal)
│ hardware/ (quirks, boards, gpu) images/ installer/ tests/ overlays/ lib/
├── crates/ built: tacid-protocol tacid-stem-protocol tacid-stem tacid-policy
│ tacid-tx tacid-audit tacid-keeper tacid-egress tacid-model
│ tacid-facts tacid-doctor tacid-memory tacid-update
│ tacid-infer tacid-propose tacid-install tacid-session
│ tacid-daemon tacid-cli tacid-design
│ planned: tacid-shell tacid-voice
│ dropped: tacid-tools (the toolbox lives in tacid-daemon; ADR-0031)
├── models/ registry.toml
├── design/ tokens.toml typography/ icons/
├── docs/ manifesto, architecture, decisions/, rfcs/, guides (mdBook; shipped on-box)
├── hosts/ example genomes for the reference machines
├── tune/ offline fine-tuning pipeline (Python permitted here only)
└── .github/ workflows
Epics (each becomes a tracked milestone): Foundation (flake, CI, protocol, tokens) · Nervous system (stem, keeper, tacidd, egress, infer, session) · Authority (policy, classifier, taint, approvals, secrets, audit) · Body (images, installer, storage, boot, channels, update, immune system) · Mind (providers, routing, local inference, registry, constrained decoding) · Senses (TUI, voice, memory, facts, Doctor) · Face (Niri, shell, design system, graphics matrix) · Hands (projects, git, editors, containers, device center, probes, datasheets) · Evolution (proposals, core signing, tune, quirk loop, MCP server) · Many bodies (aarch64, lab, remote, fleet) · Project (docs, website, cache, signer, hardware database).
13. Risks
| Risk | Mitigation |
|---|---|
| Nix complexity leaks into the experience | Curated options, generated-file ownership, actionable evaluation errors, the agent writes the Nix, tacid.raw gated. |
| NVIDIA + Niri + Secure Boot + frequent kernels is the highest-churn combination | Physical NVIDIA CI before edge is public; boot-class activation; boot counting; tested driver profiles; stable waits for the matrix. |
| The agent is unreliable at system changes | Typed tools, constrained decoding, deterministic policy, build before activate, health gate, narrow stem. |
| A local model disappoints | Route simple work locally, expose frontier providers, fine-tune small on Tacid’s tools, never market every task as local. |
| The default level 2 is unsafe | The classifier with unknown = irreversible, taint caps, budgets, the immune system in Phase 1, the injection corpus as a gate. |
| Self-improvement corrupts the core | Signed core partition; keeper verifies before activation; proposals need a human signature. |
| A leaked key updates every machine | TUF roles, short-lived online keys, offline root, revocation by manifest. |
| Impermanence surprises users | Services declare state; the agent detects ephemeral writes and offers to declare them; the installer explains it once. |
| Scope explosion (phones, fleets, Macs) | The phase gates; Apple as a community channel; boards headless-only; fleets after 1.0. |
| Solo-maintainer burnout | Agents do the labour; ADRs stop re-litigation; area reviewers by month six; a tight stable. |
| Trademark collision with existing “TacID” marks | Clearance before public branding spend. |
| Binary cache and CI cost | Attic with deduplication behind a CDN; measured before scaling; sponsors cover bills only. |
14. Immediate next actions
Items 4 through 10 of the original list are done and tested; what is left is everything that needs a machine, and it is all the same shape — the code is written and has never met the hardware it was written for.
- Reserve the GitHub organization and crate names; start the trademark search.
- Buy or designate the reference laptop and the NVIDIA desktop.
Evaluate the Nix layer.Done: every flake output evaluates to a derivation on both architectures, the first evaluation’s eight findings are fixed, and the package builds under Nix with its tests passing inside the sandbox.Boot it.Done, under software emulation: the VM test’s ten subtests pass on a machine that booted — the sockets, the users, the doors the daemon cannot open, a reversible change end to end, and consent brought to the keeper by a person and refused from the agent’s uid. What the first boot found, and what it changed, is ADR-0032.Replace the two stand-ins on a real machine.Done, under software emulation: the installed machine is toldinstall hello, the daemon locks the genome to the sources the machine carries and builds it, the stem registers the generation and switches to it,helloruns, the genome has its lock and its commit, andtacid undobrings the first generation back with the package gone — with no network, from the assembly tools every closure now keeps. What the first real build found is ADR-0037.- Point a real provider at the proxy. The adapters are tested end to end against a fake
provider through the real
tacid-egress; what has never happened is a real key, a real response, and a real bill. Build the netinstall image and install a machine with it.Done, under software emulation: the image builds under its own name (1.47 GB — over the gibibyte, and measured rather than trimmed), and the installation test lays out a disk, writes a genome, installs a built system, boots the disk as the machine and finds it whole — the stem’s guarded operations from a typed plan, with no evaluator on the privileged path (ADR-0033). The netinstall builds the system it installs from the genome, with every input pinned to the sources the image carries, and every machine keeps those sources (ADR-0034), and the test’s live image evaluates and assembles the machine’s first system itself, from the genome and the sources it carries, to the very path the host computes for the same declaration. The machine it installs has an encrypted root whose key the installer sealed to the machine’s TPM behind a PIN, unlocked on the PIN at the console of the initrd and on the passphrase when the TPM cannot open it (ADR-0039), because the default is what is tested; and once booted, it builds its next generation itself (item 4). Still to come: the first netinstall on hardware, with a cache in front of it.- Follow a channel and survive a bad one. Written, and set out in a test under software emulation whose first full run was under way when this was written: a follower with nothing in its store but its own closure reads a signed manifest through the proxy, the update check proposes the advance as the system, the daemon moves the genome’s lock to the sources the channel locked and builds the revision from the cache, the stem registers it for the next boot, the machine boots it and blesses it; then a revision whose daemon cannot start is tried three times and abandoned by the bootloader (ADR-0038). What the channel needs next is a cache behind a CDN and a pipeline that promotes to it.
- Run the fine-tuning pipeline in
tune/, once there are sanitised traces worth training on. - Then Phase 4: the Niri session and
tacid-shell. It is deliberately last of the written work, because a graphical shell cannot be tested the way everything else here has been, and shipping untested code to keep a phase order would be the first shortcut in the tree.
Appendix A — Example transaction
{
"schema": 1,
"id": "tx_01J9Z8X0N4M7Q2R5T8V1W3Y6",
"kind": "change",
"intent": "enable_stlink_development",
"principal": { "kind": "owner", "key": "ed25519:9f3c…" },
"provenance": ["owner"],
"items": [
{ "op": "package.ensure", "name": "openocd" },
{ "op": "udev.profile", "profile": "stlink" }
],
"classification": {
"reversible": true,
"privilege": "system",
"persistent": true,
"network": false,
"destructive": false,
"activation": "switch",
"data_touching": false
},
"verification": [
{ "check": "command", "argv": ["openocd", "--version"] },
{ "check": "facts", "path": "system.probes[?vendor=='STMicroelectronics'].accessible", "equals": true }
]
}
Appendix B — Example user-visible diff
OpenOCD and ST-Link support tx_01J9Z8X0… reversible switch
Tacid will make a persistent system change.
+ install OpenOCD
+ add the tested ST-Link udev profile
+ grant your user access to the debug interface
Why: the connected ST-Link is visible to root but your user cannot open it.
Reversible: ✓ system configuration (generation 188 → 187)
✓ no user files are modified
✗ firmware on attached hardware is not affected by this step
[option diff] [full log] [approve: y] [reject: n]
Appendix C — Example mind/agent.nix
{ ... }:
{
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";
}
Appendix D — Example mind/policy.nix
{ ... }:
{
tacid.policy = {
autonomy = "reversible"; # observe | consult | reversible | trusted | sovereign
sovereign.expiresAfter = "4h";
capabilities = {
"packages.install" = "auto";
"services.enable" = "ask";
"hardware.udev" = "ask";
"desktop.theme" = "auto";
"shell.exec" = "sandbox"; # sandbox | ask | deny
"git.push" = "ask";
"firmware.flash" = "ask";
};
filesystem.read = [ "~/projects" "~/.config" "/etc/tacid" ];
filesystem.write = [ "~/projects" ];
egress.allow = [ "api.anthropic.com" "cache.tacidos.com" "channel.tacidos.com" ];
egress.dataClasses = { screenshot = "ask"; fileContent = "per-project"; logs = "auto"; secrets = "never"; };
budgets = { transactionsPerHour = 20; closureDeltaMiB = 4096; egressMiBPerDay = 200; };
};
}
Appendix E — Example protocol exchange
→ {"jsonrpc":"2.0","id":7,"method":"session.ask","params":{"session":"s_1","text":"install wireshark and let me capture without root"}}
← {"jsonrpc":"2.0","method":"session.event","params":{"session":"s_1","event":"plan","steps":[…]}}
← {"jsonrpc":"2.0","method":"session.event","params":{"session":"s_1","event":"transaction","id":"tx_…","kind":"change","classification":{…},"needs_approval":true}}
(client connects to keeper.sock)
→ {"jsonrpc":"2.0","id":1,"method":"approval.grant","params":{"transaction":"tx_…","scope":"once"}}
← {"jsonrpc":"2.0","method":"session.event","params":{"session":"s_1","event":"activated","generation":188,"health":"pass"}}
← {"jsonrpc":"2.0","id":7,"result":{"summary":"Wireshark is installed; your user is in the wireshark group; run `wireshark` to capture.","journal":"j_…"}}