System Architecture¶
Deep dive into how Clown is designed and how its components interact.
Overview¶
Clown is a Rust-native workspace built around a central background daemon (ringletd) that orchestrates CLI coding agents. The daemon owns profile persistence, agent discovery, telemetry collection, and real-time event distribution.
┌─────────────────────────────────────────────────────────────────┐
│ User / UI │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ringlet CLI │
│ (Thin client - auto-starts daemon, forwards commands) │
└──────────────────────────────┬──────────────────────────────────┘
│ async-nng / HTTP
▼
┌─────────────────────────────────────────────────────────────────┐
│ ringletd (Daemon) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Profile │ │ Agent │ │ Registry │ │
│ │ Manager │ │ Registry │ │ Client │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Proxy │ │ Usage │ │ Scripting │ │
│ │ Manager │ │ Tracking │ │ Engine │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Claude Code │ │ Codex CLI │ │ ultrallm │
└─────────────┘ └─────────────┘ │ Proxy │
└─────────────┘
Design Goals¶
- Single entry point - Detect any supported CLI coding agent installed on the system
- Profile isolation - Each profile has completely separate configuration and state
- Multi-provider support - Run the same agent against different API backends
- Centralized orchestration - Daemon ensures single source of truth
- Usage tracking - Monitor token consumption and costs across all profiles
- Extensibility - Add new agents through manifests and scripts, not code
Core Components¶
ringlet CLI¶
A thin client that:
- Parses commands like
agents list,profiles create,profiles run - Auto-starts the daemon if not running
- Forwards requests over
async-nngsockets - Renders responses as tables or JSON
The CLI never performs stateful operations directly - everything goes through the daemon.
ringletd (Daemon)¶
The heart of Clown. Runs as a long-lived background process and owns:
| Responsibility | Description |
|---|---|
| Profile persistence | Stores and retrieves profile configurations |
| Agent discovery | Detects installed agents and their versions |
| Telemetry collection | Tracks usage, sessions, and costs |
| Event distribution | Publishes changes via WebSocket/pub-sub |
| Proxy management | Spawns and monitors ultrallm instances |
| HTTP API | Serves REST endpoints and Web UI |
The daemon auto-starts on first CLI use and exits after idle timeout unless pinned with --stay-alive.
Profile Manager¶
Handles profile lifecycle:
- Creation - Validates agent+provider pairing, prompts for API key, runs Rhai script
- Storage - Writes profile JSON to
~/.config/ringlet/profiles/ - Execution - Creates isolated environment, injects variables, spawns agent
- Tracking - Updates
last_usedtimestamps
Agent Registry¶
Loads and manages agent manifests:
- Built-in manifests compiled into binary
- User manifests from
~/.config/ringlet/agents.d/ - Registry manifests from GitHub
Detection probes run in parallel to capture versions and paths.
Provider Registry¶
Manages API backend definitions:
- Built-in providers (Anthropic, MiniMax, OpenAI, OpenRouter)
- User providers from
~/.config/ringlet/providers.d/
Providers define endpoints, authentication, and available models.
Scripting Engine¶
Embeds Rhai for configuration generation:
- Receives context (provider, profile, preferences)
- Outputs configuration files and environment variables
- Scripts resolved: user override → registry → built-in
Registry Client¶
Synchronizes GitHub-hosted metadata:
- Downloads manifests, templates, and model catalog
- Verifies checksums/signatures
- Caches under
~/.config/ringlet/registry/ - Downloads LiteLLM pricing for cost calculation
Proxy Manager¶
Manages ultrallm proxy instances:
- Spawns processes on dedicated ports (8080-8180 range)
- Generates routing configuration from profile rules
- Monitors health and handles graceful shutdown
- Auto-starts proxies when profiles with proxy enabled run
Usage Tracking¶
Collects and aggregates token usage:
- Per-session records in
sessions.jsonl - Rolled-up stats in
aggregates.json - Cost calculation using LiteLLM pricing
- Queryable via CLI, HTTP API, and Web UI
Communication¶
CLI ↔ Daemon Transport¶
The CLI and daemon communicate through async-nng:
- Request/Reply: Commands serialized via
serde_json, sent over IPC - Pub/Sub: Change notifications for watch modes and UI clients
- Endpoint:
/tmp/ringletd.sock(Unix) or%LOCALAPPDATA%/ringlet/ringletd.ipc(Windows)
HTTP API¶
For UI integrations that can't speak NNG:
- REST endpoints at
http://127.0.0.1:8765 - WebSocket at
ws://127.0.0.1:8765/ws - Serves embedded Web UI
Profile Isolation¶
Home Wrapper Strategy¶
When a profile runs, Clown creates an isolated environment:
Real HOME: /home/user
Profile HOME: /home/user/.claude-profiles/my-project
Environment:
HOME=/home/user/.claude-profiles/my-project
ANTHROPIC_BASE_URL=https://api.anthropic.com
ANTHROPIC_AUTH_TOKEN=sk-...
The agent reads configuration from the profile HOME, ensuring:
- Configuration files are isolated
- Conversation history is separate
- Settings don't leak between profiles
What Gets Isolated¶
| Isolated | Not Isolated |
|---|---|
| Agent config files | System binaries |
| API credentials | Shell configuration |
| Conversation history | Network access |
| Agent settings | File system access |
Data Flow¶
Profile Run Workflow¶
1. User: ringlet profiles run my-project
2. CLI:
- Connects to daemon (or starts it)
- Sends ProfileRunRequest
3. Daemon:
- Loads profile configuration
- Retrieves API key from keychain
- Creates/validates profile home
- If proxy enabled, starts ultrallm
- Spawns agent process with:
- HOME set to profile home
- Environment variables injected
- CLI arguments forwarded
4. Agent runs with isolated configuration
5. Daemon tracks:
- Session start/end times
- Token usage (from agent files)
- Runtime duration
Registry Sync Workflow¶
1. CLI sends RegistrySyncRequest to daemon
2. Daemon:
- Acquires per-channel lock
- Checks registry.lock for cached data
- If force or cache stale:
- Downloads registry.json
- Verifies checksums
- Fetches missing artifacts
- Stages under commits/<sha>/
- Updates registry.lock
- Publishes RegistryUpdated event
3. CLI receives response with:
- Resolved commit
- Channel
- Downloaded artifact count
- Cache/network indicator
Persistence Layout¶
~/.config/ringlet/
├── config.toml # User preferences
├── daemon-endpoint # Active daemon endpoint
├── agents.d/ # Custom agent manifests
├── providers.d/ # Custom provider manifests
├── scripts/ # Custom Rhai scripts
├── profiles/ # Profile definitions
│ └── my-project.json
├── registry/ # Cached registry data
│ ├── current -> commits/f4a12c3
│ ├── registry.lock
│ ├── litellm-pricing.json
│ └── commits/
│ └── f4a12c3/
├── cache/
│ └── agent-detections.json
├── telemetry/
│ ├── sessions.jsonl
│ └── aggregates.json
└── logs/
└── ringletd.log
Cross-Platform Support¶
| Platform | Config Path | IPC Endpoint |
|---|---|---|
| macOS | ~/.config/ringlet/ | /tmp/ringletd.sock |
| Linux | ~/.config/ringlet/ or XDG | /tmp/ringletd.sock |
| Windows | %APPDATA%\ringlet\ | %LOCALAPPDATA%\ringlet\ringletd.ipc |
Clown uses the dirs crate to resolve paths and keeps launcher scripts optional.
Future Directions¶
- UI Layer - Desktop/web frontend connecting to daemon
- Delta syncs - Download only changed registry entries
- Prometheus/OpenTelemetry - Enterprise observability endpoints
- Plugin system - User-defined hooks and extensions