Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


title: Design Overview tags: [overview, architecture]

Design Overview

Cowboy is an AI agent harness built on Zellij and WebAssembly. The agent runtime is a Zellij plugin compiled to wasm32-wasip1; everything around it (network isolation, credential injection, message bridges) is configured declaratively through NixOS modules.

Repository layout

The project is a single monorepo:

cowboy/
  crates/core/      # host-agnostic agent runtime — state machine, tools, memory,
                    #   dispatch, provider clients (Rust; has a `lite` feature)
  crates/harness/   # Zellij WASM plugin front end over core (wasm32-wasip1)
  crates/component/ # the agent built as a wasip2 component (cowboy:agent WIT world)
  crates/montana/   # headless wasmtime embedder + ndjson socket host (Linux-only)
  crates/runtime/   # the `cowboy` OCI runtime (docker --runtime=cowboy; Linux-only)
  crates/sheepdog/  # seccomp syscall sandbox (Rust, native binary)
  crates/a2a/       # protocol-neutral A2A mapping over the WIT describe() export
  crates/contracts/ # native wire contracts (Intent/Approval/Addressed, Descriptor)
  crates/ui/        # shared TUI widgets/atoms (cowboy-ui)
  crates/bootstrap/ # interactive first-boot setup wizard (a Zellij plugin)
  specs/            # spec root — WIT world (specs/wit/), CDDL records, prose
  cowboy/           # Python CLI launcher (PyPI: get-cowboy, binary: cowboy)
  proxy/            # mitmproxy addon for credential injection
  pkgs/bridge/      # Python framework for message bridges
  pkgs/discord/     # Discord bridge service
  pkgs/email/       # Email bridge service
  pkgs/matrix/      # Matrix bridge service
  modules/          # NixOS / home-manager modules (entry: modules/default.nix)

The harness has a lite feature for portable builds without NixOS dependencies. The sheepdog sandbox is compiled to a native binary and baked into the harness at build time via the SENTRY_BINARY environment variable.

The imageless resolver (external)

Rootfs materialization is not in this repo. Imageless — the flake-native resolver that turns a digest-addressed release reference into a realised rootfs — lives in its own repository and is consumed here as a first-class dependency: a flake input supplying its NixOS module and runtime package, plus a Cargo git dependency (pinned to the same rev) supplying the materializer library that crates/runtime links.

The seam is the OCI bundle boundary, not a Cowboy-internal API. cowboy-runtime receives a bundle whose config.json names a release, and asks imageless to materialize it — over the resolver daemon’s socket when the node runs one (IMAGELESS_RESOLVER_SOCKET, exported onto containerd by the module), or in-process for montana/dev where there is no socket. modules/imageless.nix carries only Cowboy’s glue over the external module: it registers the packaged, digest-addressed agent release as a node issuer and enables the resolver by default. The module’s fail-closed cache_only default stands — the agent is a release from cache.nixos.org and the node never evaluates workload Nix.

How the pieces fit

+-----------------------------------------------------------------+
|                         Zellij session                          |
|  +-----------------------------------------------------------+  |
|  |              Cowboy harness (WASM plugin)                  |  |
|  |   agent loop  ·  provider clients  ·  tool execution      |  |
|  |   context/compaction  ·  pubsub source polling            |  |
|  +-----------------------------------------------------------+  |
+-----------------------------------------------------------------+
        | web_request() (HTTP)        | shell tools (fork/exec)
        v                             v
  +------------+              +-----------------+
  | proxy      |              | sheepdog        |
  | (mitmproxy |              | (seccomp        |
  |  addon)    |              |  sandbox)       |
  +------------+              +-----------------+
        |
        v
     LLM / web APIs

The harness issues all HTTP through Zellij’s web_request() host function — no curl, no sidecar daemon. On Linux that traffic leaves through a network namespace and is DNAT’d to the proxy, which injects API credentials (see Security). Shell tool calls are mediated by the sheepdog seccomp sandbox.

Providers

The harness defines an LlmProvider trait (crates/core/src/provider/traits.rs) with implementations for Anthropic, OpenAI, OpenRouter, Ollama, and Codex. A provider produces the request URL, headers, and body, then parses the response; requests are dispatched asynchronously and results arrive as Zellij WebRequestResult events.

The model catalog lives in crates/core/data/models.json. Provider and model are selected by config key (provider, model); the default is anthropic:claude-sonnet-4-20250514. The catalog also includes Codex models (gpt-5.6-luna and gpt-5.6-sol) for ChatGPT subscriptions. In full NixOS deployments, the credential proxy injects the rotating Codex subscription token outside the agent.

Pub/sub bridges

External platforms (Discord, email, Matrix) connect to the agent through a Redis-backed pub/sub layer. Bridge services run as the broker (a human-operated trust domain), publishing inbound messages to Redis streams and consuming outbound replies. Inside the harness, SourceManager (crates/core/src/pubsub/manager.rs) polls those streams via generated shell commands (MessageSource trait) and routes replies back.

External platform        Bridge (broker)            Harness (agent)
  Discord  ----------->  ingest  --> Redis stream --> SourceManager polls
  reply    <-----------  outbox  <-- Redis stream <-- SourceManager writes

Outbound messages can require manual approval before the broker sends them; see Approvals & Outbox.

Configuration

Runtime configuration is JSON. The Python launcher (cowboy/cli.py) reads it and generates a Zellij KDL layout that passes config keys to the WASM plugin as a BTreeMap<String, String>, which the plugin parses in Config::from_configuration() (crates/core/src/config/mod.rs).

Precedence (later overrides earlier):

  1. /etc/cowboy/config.json — optional hand-written system defaults (not emitted by the NixOS module)
  2. ~/.config/cowboy/config.json — user overrides
  3. .cowboy/config.json — project-level overrides (read by the lite init)

In a full NixOS deployment the services.cowboy module tree does not write config.json; it emits per-agent runtime config instead (/etc/cowboy/agents/<name>.json, /etc/cowboy/plugins.json, /etc/cowboy/cowboy/sources.json).

NixOS module tree

The module entry point is inputs.cowboy.nixosModules.default. All options live under services.cowboy:

  • services.cowboy.agents.<name> — per-agent configuration
  • services.cowboy.secretsProxy — the credential-injecting proxy
  • services.cowboy.pubsub — Redis pub/sub backend
  • services.cowboy.bridges.<name> — message bridge declarations
  • services.cowboy.sheepdog — seccomp sandbox
  • services.cowboy.camoufox — headless browser

There is no flat services.cowboy.enable / provider / model; agents are enabled individually under services.cowboy.agents.<name>.

References