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):
/etc/cowboy/config.json— optional hand-written system defaults (not emitted by the NixOS module)~/.config/cowboy/config.json— user overrides.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 configurationservices.cowboy.secretsProxy— the credential-injecting proxyservices.cowboy.pubsub— Redis pub/sub backendservices.cowboy.bridges.<name>— message bridge declarationsservices.cowboy.sheepdog— seccomp sandboxservices.cowboy.camoufox— headless browser
There is no flat services.cowboy.enable / provider / model; agents are
enabled individually under services.cowboy.agents.<name>.