Skip to content

Build from source

./build.sh        # docker: go build + fetch+pin sops/rtk + nfpm  →  ./dist/*.deb

Everything runs in throwaway docker run --rm containers (Go build, checksum-verified sops/rtk downloads, nfpm). Go is never installed on the build host. The vendored payload is static (packaging/payload/), so what is in the tree is what ships.

What the .deb installs

Path What
/usr/bin/agentctl the CLI, the only surface anyone sees
/usr/bin/agentd the root daemon: socket + authz boundary + reconcile (enabled by provision)
/usr/lib/agentctl/bin/sops vendored, pinned sops (not apt-installable on the target distro; arch-dependent → /usr/lib)
/usr/lib/agentctl/bin/rtk vendored, pinned CLI-output token-optimizer; host-base puts it on PATH
/usr/lib/agentctl/bin/mcptools vendored, pinned generic MCP-over-stdio CLI the turnless data plane (mcp read / mcp call, the cockpit mini-apps) shells out to; host-base symlinks it onto PATH
/lib/systemd/system/agentd.service daemon unit (Type=notify; enabled by provision)
/usr/share/agentctl/helpers/ vendored login/pairing helpers (PKCE + OAuth flows, prime-bot) the converge and login paths shell out to
/usr/share/agentctl/units/ systemd unit files the converge reconciles onto the host (units/agent/ per-agent templates, units/host/ host units + slices)
/usr/share/agentctl/templates/ the claude-agent systemd override template the converge renders
/usr/share/agentctl/hooks/ Claude Code lifecycle hooks (forward-to-plugin, session-start-memory, session-event, kb-reminder, compact-notify, narrate-cron-skip, subsession-skip, turn-review, ws-publish) + hooks/registry.yaml
/usr/share/agentctl/scripts/ agent runtime scripts (start-agent, watcher handlers, narrate, agent-notify)
/usr/share/agentctl/mcp/ the MCP catalog: one self-contained, relocatable dir per MCP (mcp.yaml + bridge/server + vendored deps); the one operational catalog (pinned/vendored). Runtime-added items live under /var/lib/agentctl/catalog
/usr/share/agentctl/cron/ per-user cron scripts + a cron-run.sh wrapper; cron/units/ holds the agentctl-<job>@.{service,timer} templates (host-scope cron units ship under the host-base role's host-units/)
/usr/share/agentctl/skills/, /usr/share/agentctl/agents/ shipped skills + subagents (drop-to-add; symlinked per agent, not a catalog, no pinning)
/usr/share/agentctl/defaults/ canonical default file set for a fresh agent (CLAUDE.md, settings.json, the ~/workspace zk notebook scaffold); installed non-destructively (scaffold-once) by agent-add
/usr/share/agentctl/docs/ reference docs consumed by skills (steering-model.md drives the housekeeper + dream-cycle convergence rules)
/usr/share/agentctl/tools/ the host CLI tool catalog: one self-contained tools/<tool>/ dir (tool.yaml + cli.sh + SKILL.md + vendored deps), all kind: native, overlaid repo-first. tool catalog add authors into the repo overlay; a catalog tool has no per-agent wiring verb
/usr/share/agentctl/runtime/pi/ the pi runtime payload: the telegram↔pi bridge (TS + package.json), pi-run.sh, pi-cron-run, and the lifecycle extensions (extensions/*.ts); staged per-agent and bun installed when a pi agent is provisioned
/lib/systemd/system/pi-agent@.service the pi agent template unit (no PTY; runs the SDK-embedding bridge)

Install is non-destructive: no users created, no units enabled, no reconcile run. The first state-changing step is always an explicit agentctl provision.

apt dependencies

The depends: block in packaging/nfpm.yaml is the authoritative list and the only place base packages are declared, so apt installs all of them at package install time. Four are worth knowing about because their absence is a hard failure rather than a missing feature:

  • nftables — the per-agent port guard. agentctl apply exits 1 without it rather than reporting complete on an unguarded host.
  • tmux — the PTY behind both interactive shells, so a shell survives a page reload.
  • ffmpeg — the voice-note transcode, and the media path generally.
  • libreoffice-* + pandoc + ocrmypdf — the Office and document-intake paths.

sops is deliberately not an apt dependency: the target distro does not package it, so it is vendored on-disk and pinned. The same goes for rtk and mcptools.

Repository layout

cmd/agentctl/      CLI entrypoint (thin client → socket for mutations)
cmd/agentd/        root daemon entrypoint (socket, peercred authz, serialized reconcile)
internal/cli/      stdlib-only command tree (provision/agents/mcp/secret/login/apply/status/…)
internal/daemon/   agentd core: listener, fifo lock, job registry
internal/proto/    socket wire protocol (Request + streamed Frames)
internal/client/   CLI-side dispatch: route mutations through the socket, run reads locally
packaging/
  nfpm.yaml        .deb spec (deps, file map, maint scripts)
  agentd.service   systemd unit (disabled)
  postinst.sh      daemon-reload only (non-destructive)
  prerm.sh         stop/disable on removal (safety net)
  payload/         the /usr/share/agentctl/ payload (helpers, units, templates, hooks
                   + hooks/registry.yaml, scripts, mcp, cron, skills, agents, tools)
  repo/            the distribution server image (signed apt + yum repos, mac binaries)
  docs/            the documentation site image (this site)
  build-image.sh   build + tag + push one release container image
build.sh           containerized build (binaries + vendored sops/rtk + .deb)
ci-local.sh        the gate; .github/workflows/ci.yml runs this same script
test/              end-to-end validation
docs/              this site (mkdocs.yml at the repo root)

Runtime layout, after agentctl init

/opt/agentctl/         the GitOps repo (desired state) — git, push/clone-able
  agents.yaml          fleet ledger: host block + host keys
  agents.d/<name>.yaml per-agent shards (one file per agent — concurrency-safe)
  secrets.d/*.sops.yaml sops/age-encrypted per-owner store (committed; OAuth excluded)
  mcp/ skills/ agents/ hooks/ extensions/ tools/   catalog overlays (overlay the shipped defaults)
  hooks/registry.yaml  hook overlay registry
/etc/agentctl/         host-local only — age.key (identity) + env/ (rendered, plaintext)
/usr/share/agentctl/   shipped payload (read-only defaults)

Building the docs site

./packaging/docs/build-docs.sh        # → dist/docs-site
uvx --with mkdocs-material mkdocs serve   # live preview on :8000

build-docs.sh pins the mkdocs versions and passes --strict, so a broken internal link fails the build. It is what ci-local.sh, the CI workflow and the container's build stage all run.