---
title: "Architecture"
description: "System layers, package DAG, CLI commands, and key abstractions"
---

# Architecture

## System Overview

Clawker is a CLI tool plus two long-lived daemons — `clawker-cp` (the control plane, one per host) and `clawkerd` (one per agent container) — that together manage isolated Docker containers for AI coding agents. The CLI is the root of trust; the daemons hold the security boundary at runtime.

```
┌────────────────────────────────────────────────────────────────────────────┐
│  CLI Layer                                                                  │
│  cmd/clawker → internal/clawker → internal/cmd/root                         │
│  12 command groups, 50+ subcommands (Cobra, Factory DI)                    │
└──────┬──────────────────────────┬──────────────────────┬────────────────────┘
       │                          │                      │
       ▼                          ▼                      ▼
┌──────────────┐    ┌─────────────────────┐    ┌────────────────────────────┐
│ Container    │    │ Configuration       │    │ Control Plane              │
│ Subsystem    │    │ Subsystem           │    │ (cmd/clawker-cp, PID 1     │
│              │    │                     │    │  of clawker-controlplane)  │
│ docker/      │    │ storage/ (engine)   │    │                            │
│ workspace/   │    │ config/ (project)   │    │ AdminService gRPC (mTLS    │
│ containerfs/ │    │ config/ (settings) │    │  + OAuth2): 13 firewall    │
│ bundler/     │    │ project/ (registry) │    │  RPCs + ListAgents         │
│              │    │ storeui/ (TUI edit) │    │ AgentService gRPC (mTLS):  │
│ pkg/whail    │    │                     │    │  clawkerd Register +       │
│ (engine lib) │    │                     │    │  Session command channel   │
└──────┬───────┘    └─────────────────────┘    │ Ory auth stack (Hydra,     │
       │                                       │  Kratos, Oathkeeper)       │
       ▼                                       │ Agent sqlite registry      │
  moby/moby (Docker SDK)                       │ Overseer event bus         │
                                               │ Owns: firewall (Envoy +    │
                                               │  CoreDNS) + eBPF cgroup    │
                                               │  programs + dns_cache map  │
                                               └──────────┬─────────────────┘
                                                          │ mTLS Session
                                                          ▼
                                               ┌────────────────────────────┐
                                               │ clawkerd                   │
                                               │ (PID 1 of every agent      │
                                               │  container; ENTRYPOINT)    │
                                               │                            │
                                               │ Inbound mTLS gRPC :7700    │
                                               │ Forks user CMD with kernel-│
                                               │  side privilege drop       │
                                               │ Two-phase orphan reaper    │
                                               │ Signal forwarding to child │
                                               │  pgroup                    │
                                               └────────────────────────────┘
```

The control plane is **not** the firewall — it is the supervisor that, among other things, owns the firewall lifecycle. See [Control Plane](/control-plane) for the user-facing CP guide and [Firewall](/firewall) for the firewall guide.

## CLI Command Structure

Commands are organized under `internal/cmd/` with subpackages per subcommand:

| Command Group | Subcommands | Purpose |
|---------------|-------------|---------|
| `container` | list, run, start, stop, kill, exec, attach, logs, inspect, cp, pause, unpause, restart, rename, remove, stats, top, update, wait, create | Docker container management |
| `image` | list, build, inspect, remove, prune | Image lifecycle |
| `volume` | list, create, inspect, remove, prune | Volume management |
| `network` | list, create, inspect, remove, prune | Network management |
| `project` | init, register, list, info, edit, remove | Project registration and config |
| `worktree` | add, list, prune, remove | Git worktree management |
| `firewall` | status, list, add, remove, reload, up, down, enable, disable, bypass, rotate-ca | Egress firewall control (all RPCs route through `f.AdminClient` to the CP daemon) |
| `controlplane` | up, down, status, agents | Break-glass control plane lifecycle + registered agent listing |
| `auth` | rotate | CLI-side auth material (CA, server certs, OAuth2 signing key) |
| `monitor` | init, up, down, status | Observability stack (OTel Collector, OpenSearch, OpenSearch Dashboards, Prometheus) |
| `settings` | edit | User settings TUI editor |
| `skill` | install, show, remove | Manage the clawker-support Claude Code plugin |

**Top-level shortcuts**: `init` → `project init`, `build` → `image build`, `run`/`start` → `container run`/`start`, `generate`, `version`

Shared domain logic (container creation) lives in `shared/` subpackages within each command group — not in library packages.

## Package Dependency Graph

Packages follow a strict DAG with no cycles. Verified via `goda`.

### Leaf Packages (zero internal imports)

Importable by anyone. Depend only on stdlib or external libraries.

| Package | Purpose |
|---------|---------|
| `storage` | Generic layered YAML store engine (`Store[T]`): discovery, merge, provenance, atomic write |
| `git` | Git operations, worktree management (go-git) |
| `logger` | Struct-based zerolog (file rotation + optional OTEL bridge) |
| `text` | Pure ANSI-aware text utilities (Truncate, PadRight, StripANSI) |
| `term` | Terminal capabilities + raw mode (sole `x/term` gateway) |
| `signals` | OS signal utilities (SetupSignalContext, ResizeHandler) |
| `build` | Build-time metadata (version, date via ldflags) |
| `update` | Background GitHub release checker (24h cached) |
| `keyring` | Credential storage service |
| `pkg/whail` | Reusable Docker engine library with label-based isolation |

### Foundation Packages (import leaves only)

Universally imported infrastructure. Their imports are leaf-only.

| Package | Imports | Purpose |
|---------|---------|---------|
| `config` | storage | Config loading/validation, schema types, path resolution. Composes `Store[Project]` + `Store[Settings]` |
| `iostreams` | term, text | I/O streams, TTY detection, colors, styles, spinners, progress |
| `ebpf` | logger | eBPF program loader/manager (cilium/ebpf) — pins maps and programs, attaches to container cgroups, `SyncRoutes` replaces the global `route_map` atomically |

### Domain Packages (import leaves + foundation)

Core business logic. Import leaves and foundation packages only.

| Package | Imports | Purpose |
|---------|---------|---------|
| `project` | config, git, logger, storage, text | Project registration, worktree lifecycle, registry CRUD |
| `bundler` | config + own subpkgs | Dockerfile generation, content hashing, semver, npm registry |
| `tui` | iostreams, text | BubbleTea models, viewports, panels, progress display |
| `prompter` | iostreams | Interactive prompts with TTY/CI awareness |
| `storeui` | iostreams, storage, tui | Generic TUI editor for `Store[T]` instances |
| `controlplane` | config, docker, logger, ory subprocess managers | CP daemon core: AdminService composition, Ory auth stack, startup orchestrator, agent watcher |
| `controlplane/agent` | auth, consts, dockerevents, overseer, logger | CP-side agent surface: Dialer, sqlite Registry, Register handler, IdentityInterceptor, session/agent events |
| `controlplane/overseer` | logger | Typed event bus + in-memory `State` worldview (containers + agents) |
| `controlplane/dockerevents` | overseer, logger | Docker events feeder (reconnecting stream → typed events on overseer bus) |
| `controlplane/cpboot` | config, docker, logger (+ embedded clawker-cp + ebpf-manager binaries) | Host-side CP lifecycle: `EnsureRunning`/`Stop`/`CPRunning` |
| `controlplane/adminclient` | auth, consts | CLI-side AdminService gRPC dial (mTLS + auto-refreshing OAuth2 bearer) |
| `controlplane/firewall` | config, docker, logger, storage, controlplane/firewall/ebpf | Firewall `Handler` (13 RPCs), `Stack` (Envoy+CoreDNS lifecycle), `ActionQueue`, config generators, certificate PKI, rules store |
| `controlplane/firewall/ebpf` | logger | eBPF program loader/manager, `SyncRoutes` replaces global `route_map` atomically; break-glass `ebpf-manager` CLI under `cmd/` |
| `dnsbpf` | controlplane/firewall/ebpf | CoreDNS plugin that writes DNS resolutions to the BPF `dns_cache` map in real time (embedded in `cmd/coredns-clawker`) |
| `clawkerd` | (embed only) | `//go:embed assets/clawkerd` — exports the per-container daemon binary; bundler drops it into every per-project image at `/usr/local/bin/clawkerd` |
| `hostproxy` | config, logger | Host proxy for container-to-host communication |
| `socketbridge` | config, logger | SSH/GPG agent forwarding via muxrpc |
| `containerfs` | config, keyring, logger | Host config preparation for container init |
| `monitor` | config | Observability stack templates (OTel Collector, OpenSearch, OpenSearch Dashboards, Prometheus) |
| `docs` | config, storage | CLI documentation generation |

### Composite Packages (import domain packages)

Higher-level subsystems that compose domain packages.

| Package | Key Imports | Purpose |
|---------|-------------|---------|
| `docker` | bundler, config, pkg/whail, pkg/whail/buildkit | Clawker middleware wrapping whail with labels/naming |
| `workspace` | config, docker, logger | Bind vs Snapshot strategies |
| `cmdutil` | config, docker, iostreams, tui, + type imports for Factory | Factory struct (DI container), error types, arg validators |

### Import Rules

```
  OK:  foundation → leaf           (config → storage)
  OK:  domain → leaf               (firewall → storage)
  OK:  domain → foundation         (project → config)
  OK:  composite → domain          (docker → bundler)

  BAD: leaf → anything internal    (storage must never import config)
  BAD: foundation ↔ foundation     (config must never import iostreams)
  BAD: any cycle                   (A → B → A is always wrong)
```

## Configuration and Storage

Three packages form the configuration subsystem:

- **`storage`** (leaf) — Generic `Store[T]` engine. Handles file discovery (static paths + walk-up), YAML loading with migrations, N-way merge with provenance tracking, and atomic writes. Zero domain knowledge.
- **`config`** — Thin domain wrapper composing `Store[Project]` + `Store[Settings]`. Exposes the `Config` interface with schema types, path helpers, and ~40 accessor methods.
- **`project`** — Project domain layer. Owns `projects.yaml` registry via its own `Store[ProjectRegistry]`. Handles registration CRUD, path resolution, worktree lifecycle.

Commands access config through the `Config` interface and `ProjectManager` interface — never `storage` directly.

## Dependency Injection: The Factory Pattern

Follows the GitHub CLI's three-layer pattern:

1. **Wiring** (`internal/cmd/factory/`) — Creates `*cmdutil.Factory` with all deps as `sync.Once` closures. Called once at entry point. Tests never import this.

2. **Contract** (`internal/cmdutil/`) — `Factory` is a pure struct with closure fields. Eager: `Version`, `IOStreams`, `TUI`. Lazy: `Config`, `Client`, `ProjectManager`, `GitManager`, `HostProxy`, `SocketBridge`, `Firewall`, `Logger`, `Prompter`.

3. **Consumers** (`internal/cmd/*/`) — Cherry-pick Factory closures into per-command `Options` structs. Run functions accept `*Options` only.

```go
func NewCmdStop(f *cmdutil.Factory, runF func(*StopOptions) error) *cobra.Command {
    opts := &StopOptions{
        IOStreams: f.IOStreams,
        Client:   f.Client,  // closure, not call
    }
}

func stopRun(ctx context.Context, opts *StopOptions) error {
    client, err := opts.Client(ctx)  // resolved lazily here
}
```

## Key Abstractions

| Abstraction | Package | Purpose |
|-------------|---------|---------|
| `storage.Store[T]` | `internal/storage` | Generic layered YAML store with merge, provenance, and atomic write |
| `config.Config` | `internal/config` | Typed config interface composing `Store[Project]` + `Store[Settings]` |
| `project.ProjectManager` | `internal/project` | Project identity, registration CRUD, worktree orchestration |
| `whail.Engine` | `pkg/whail` | Reusable Docker engine with label-based resource isolation |
| `docker.Client` | `internal/docker` | Clawker middleware (labels, naming) wrapping whail |
| `cmdutil.Factory` | `internal/cmdutil` | DI struct (closure fields); constructor in `cmd/factory` |
| `CreateContainer()` | `internal/cmd/container/shared` | Single entry point for container creation |
| `adminclient.Dial` | `internal/controlplane/adminclient` | CLI-side AdminService gRPC dial (mTLS + OAuth2) |
| `firewall.Handler` | `internal/controlplane/firewall` | 13-method AdminService firewall surface inside the CP daemon |
| `firewall.Stack` | `internal/controlplane/firewall` | Envoy + CoreDNS container lifecycle |
| `dnsbpf.Handler` | `internal/dnsbpf` | CoreDNS plugin populating the BPF `dns_cache` map in real time |
| `ebpf.Manager` | `internal/controlplane/firewall/ebpf` | BPF program loader; `SyncRoutes` replaces the global `route_map` atomically |
| `cpboot.Manager` | `internal/controlplane/cpboot` | Host-side CP container lifecycle (`EnsureRunning`/`Stop`/`CPRunning`) |
| `agent.Registry` | `internal/controlplane/agent` | sqlite-backed agent identity (CP is sole writer) |
| `overseer.Overseer` | `internal/controlplane/overseer` | Typed event bus + worldview state inside CP |
| `cmd/clawker-cp` | `cmd/clawker-cp` | CP daemon entry point — PID 1 of the `clawker-controlplane` container |
| `cmd/clawkerd` | `cmd/clawkerd` | Per-container agent daemon — PID 1 of every agent container |
| `cmd/coredns-clawker` | `cmd/coredns-clawker` | Custom CoreDNS entrypoint embedding `dnsbpf`; built into `clawker-coredns:latest` on demand |
| `tui.TUI` | `internal/tui` | Factory noun for presentation layer |

## Container Naming and Labels

**Container names**: `clawker.project.agent` (3-segment) or `clawker.agent` (2-segment when project is empty)

**Volume names**: `clawker.project.agent-purpose` (purposes: `workspace`, `config`, `history`)

**Labels** (all under `dev.clawker.*`):

| Label | Purpose |
|-------|---------|
| `managed` | `true` — authoritative ownership marker |
| `project` | Project name (omitted when empty) |
| `agent` | Agent name |
| `version` | Clawker version |
| `image` | Source image reference |

Labels are authoritative for resource ownership. Names are for human readability. Clawker refuses to operate on resources without `dev.clawker.managed=true`.

## Presentation Layer

Commands follow a 4-scenario output model:

| Scenario | Description | Packages |
|----------|-------------|----------|
| Static | Print and done | `iostreams` + `fmt` |
| Static-interactive | Output with y/n prompts | `iostreams` + `prompter` |
| Live-display | Continuous rendering, no input | `iostreams` + `tui` |
| Live-interactive | Full keyboard input, navigation | `iostreams` + `tui` |

**Import boundaries** (enforced):
- Only `iostreams` imports `lipgloss`
- Only `tui` imports `bubbletea`/`bubbles`
- Only `term` imports `golang.org/x/term`
