# ringlet — full content dump Source: https://ringlet.neullabs.com License: MIT (content) · ringlet is MIT Generated: 2026-06-04T15:14:53.828Z This file is a single-pass dump of the Ringlet marketing site for AI-search crawlers. The authoritative version is at https://ringlet.neullabs.com. --- # Articles ## Multi-agent workflows: parallel runners vs profile-switching URL: https://ringlet.neullabs.com/blog/multi-agent-workflows Published: 2026-05-28 Tags: narrative, workflow, parallel, agents Cluster: narrative Two patterns for running multiple AI coding agents — parallel runs on git worktrees (Claude Squad, Conductor) and Ringlet's serial profile-switching. When each one is right, and how to use them together. "How do I run multiple AI coding agents?" turns out to mean two different things. **Pattern A — parallel.** Five agents at once. One on each git worktree. You're working on five things simultaneously and want to come back to a stack of pull requests. This is what Claude Squad, Conductor, and Tide Commander are good at. **Pattern B — serial.** One agent at a time, but a long-lived context per project. You're working on one thing, but it's the same one thing tomorrow morning, and you'd like the agent to remember what it learned yesterday. This is what Ringlet's profiles are good at. Both are useful. Most teams need both. This article is about which one to reach for when, and how they compose. ## What parallel runners do well Parallel runners assume that each task is independent and roughly sized to "one PR." A new feature, a refactor, a bug fix. You start the runner, hand each agent a different task, walk away, come back to diffs you review and merge. The mechanic is git worktrees. Each agent gets its own copy of the repo at a fresh branch. They can't conflict because they're on different filesystem paths. The orchestrator monitors them, surfaces output, and shows you the diffs. This works really well when: - You're a senior developer with a backlog of small, well-specified tasks. - You're willing to review each diff carefully before merging. - You're paying for the tokens it takes to run five agents at once. It works less well when: - The task is exploratory ("what's actually going on here?"). One agent thinking carefully beats five agents flailing. - You need long-running context. Each worktree restart is a context restart. - Tasks aren't independent. If they all touch the same file, the merge is its own problem. ## What profile-switching does well Ringlet's model is different. Each profile is a long-lived bundle: agent + provider + history + workspace. The agent is the same agent you'd use without Ringlet; the profile just means it remembers what it was doing last time. The mechanic is HOME isolation. Each profile has its own `~/.claude` (or `~/.codex`) so the agent's conversation history, project memory, MCP setup, and trust grants survive across sessions and don't leak between profiles. This works really well when: - You work on a small number of projects, intensely, for days or weeks at a time. - You want the agent to remember what you discussed yesterday and pick up where you left off. - You care about cost across projects and need to attribute spend correctly. - You want different providers (or different accounts at the same provider) for different work. It works less well when: - The task is "do these five things in parallel." That's not what serial profiles are for. - You don't care about long-term memory and just want each task to be fresh. ## A worked example You're an engineer working on three things this week: 1. **Refactor the auth layer in `client-acme/api`.** A several-day project. You'll come back to it across many sessions. The agent should remember what you've already tried. 2. **Triage 20 small bug reports across two repos.** Each takes 5–30 minutes. They're independent. 3. **Experiment with a side project at home.** Personal account, different provider. The right tool layout: - **A Ringlet profile for the auth refactor.** Long-lived. Anthropic-backed (you want Sonnet for this). Lives in `~/Projects/client-acme/api`. - **Claude Squad inside a Ringlet profile** for the bug triage. The profile binds Claude Squad's environment to the same client-acme account; Claude Squad spawns five agents on five worktrees and you review them as they finish. - **A separate Ringlet profile for the side project.** Personal account, MiniMax provider (cheap, you're paying out of pocket). ```bash ringlet profiles create claude acme-auth --provider anthropic --key-alias acme ringlet profiles create claude acme-bugs --provider anthropic --key-alias acme ringlet profiles create claude personal-toy --provider minimax --key-alias personal # Long-running refactor: a regular Ringlet session ringlet profiles run acme-auth # Bug triage: Claude Squad inside the bugs profile ringlet profiles run acme-bugs -- claude-squad # Side project: minimax-backed ringlet profiles run personal-toy ``` Three profiles, two patterns, one mental model. Costs land on the right accounts; histories stay separate; the parallel runner does its job inside one profile without polluting the others. ## Why this is the right composition The orchestration layer (Ringlet) and the parallel-runner layer (Claude Squad / Conductor) sit at different heights in the stack. Ringlet owns the agent's environment — credentials, history, workspace. The parallel runner owns *which* tasks are running on *which* worktrees right now. You wouldn't ask npm to manage your test runner's task parallelism, and you wouldn't ask your test runner to manage your dependency tree. Same logic here. Concretely, what each layer does: | Layer | Responsibility | |---|---| | Ringlet | HOME isolation, credentials, provider binding, cost tracking, hooks | | Claude Squad / Conductor | Git worktree allocation, task queue, parallel monitoring | | The agent (Claude Code, Codex, …) | The actual code generation, tool use, agent loop | When you compose them — a parallel runner inside a Ringlet profile — each layer does its job. The runner gets the agent's environment from Ringlet; Ringlet doesn't have to know about parallelism; the agent doesn't have to know about either. ## What about the times you don't need a runner? Most of the time, honestly. The "five worktrees, five tasks" pattern is real, but it's not most engineers' default day. Most days are: open a project, ask the agent for help, iterate, commit, move on. For that, profiles are enough. The agent is one agent. The runner is just you. The shape we see in practice: engineers create 3–5 long-lived profiles (work, personal, one or two clients), use them serially most of the time, and reach for a parallel runner once a week or so when the task fits. ## The honest answer on Ringlet + Claude Squad together Claude Squad is excellent at what it does. We use it. Running it inside a Ringlet profile is the right setup if you need both — the profile gives Squad the right credentials and provider, Squad does the parallel orchestration on top. We'd recommend installing both: ```bash # Ringlet for credentials, providers, cost, history curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh # Claude Squad for parallel task running brew install smtg-ai/tap/claude-squad ``` Inside a Ringlet shell: ```bash ringlet profiles run work # Now in the work profile's env: claude-squad init claude-squad task add "Refactor src/auth to use OAuth" claude-squad task add "Fix the JSON parse bug in src/parse" claude-squad task add "Add e2e test for /signup" claude-squad run ``` Three Claude Code instances spin up. Each is on a separate git worktree. Each inherits the work profile's Anthropic key. Cost lands on the work account. Diffs land in three branches. Review, merge, move on. That's the multi-agent workflow we recommend in 2026: profiles for the long-running stuff, a parallel runner inside a profile when the task fits. --- ## Migrating from shell aliases to Ringlet profiles in 15 minutes URL: https://ringlet.neullabs.com/blog/migrating-from-shell-aliases Published: 2026-05-27 Tags: guide, migration, bash, shell Cluster: guide A step-by-step migration from per-project bash aliases — multiple ANTHROPIC_API_KEY exports, direnv files, wrapper scripts — to Ringlet's profile model. Before/after, including the parts that don't translate. If you've been managing more than one AI coding agent for more than three months, you probably have a small mess of shell aliases, `.envrc` files, and wrapper scripts. This article is a step-by-step migration to Ringlet. The TL;DR: every alias becomes a profile. Every direnv file becomes a profile. Every wrapper script becomes a profile. The shell stops being the unit of configuration. ## Step 0: Audit what you have Before deleting anything, list it. The usual suspects: ```bash # Shell aliases related to AI agents grep -E '^alias.*(claude|codex|grok|droid|opencode)' ~/.zshrc ~/.bashrc ~/.config/zsh/* # Environment variables exported per shell grep -E 'ANTHROPIC|OPENAI|XAI|GROK|MINIMAX|OPENROUTER' ~/.zshrc ~/.bashrc ~/.zshenv ~/.profile # Direnv config in project directories find ~/Projects -name '.envrc' 2>/dev/null | head -20 # Wrapper scripts ls ~/.local/bin/ ~/bin/ 2>/dev/null | grep -Ei 'claude|codex|grok|ai-' ``` The output is your migration checklist. Each line is one Ringlet profile. ## Step 1: Install Ringlet, init ```bash curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh ringlet init ``` `ringlet init` detects every supported agent CLI on your `$PATH`, finds the existing usage logs (`~/.claude/usage.jsonl`, `~/.codex/sessions/`), and imports them so the cost graph isn't empty on day one. ## Step 2: Move keys to the keychain For every API key you currently have in a shell-exported environment variable: ```bash # Anthropic personal ringlet keychain set anthropic personal --from-env ANTHROPIC_API_KEY # (set the work key in your shell briefly, then run:) ANTHROPIC_API_KEY="sk-ant-..." ringlet keychain set anthropic work --from-env ANTHROPIC_API_KEY # OpenAI ringlet keychain set openai default --from-env OPENAI_API_KEY # etc. ``` Verify: ```bash ringlet keychain list # anthropic/personal # anthropic/work # openai/default ``` Now delete the corresponding `export` lines from your shell rc files. Restart the shell. Confirm the env vars are gone: ```bash echo $ANTHROPIC_API_KEY # → (empty) ``` If something breaks at this stage, it means a tool you forgot about was depending on the global env var. Either move that tool inside a Ringlet profile too, or temporarily put the export back while you finish the migration. ## Step 3: Create profiles for each shell alias Take your audit list and translate one alias at a time. **Before** — a typical setup: ```bash # ~/.zshrc alias claude-personal='ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-personal -w) claude' alias claude-work='ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-work -w) claude' alias claude-cheap='ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic ANTHROPIC_API_KEY=$(security find-generic-password -s minimax -w) claude' alias codex-work='OPENAI_API_KEY=$(security find-generic-password -s openai-work -w) codex' ``` **After**: ```bash ringlet profiles create claude personal --provider anthropic --key-alias personal ringlet profiles create claude work --provider anthropic --key-alias work ringlet profiles create claude cheap --provider minimax --key-alias minimax ringlet profiles create codex work --provider openai --key-alias openai-work # Delete the four aliases from ~/.zshrc. # Optionally re-add them as thin Ringlet wrappers: echo "alias claude-personal='ringlet profiles run personal'" >> ~/.zshrc echo "alias claude-work='ringlet profiles run work'" >> ~/.zshrc echo "alias claude-cheap='ringlet profiles run cheap'" >> ~/.zshrc echo "alias codex-work='ringlet profiles run codex-work'" >> ~/.zshrc ``` The aliases stay; they just point at Ringlet. From your fingers' point of view, nothing changes. ## Step 4: Translate direnv to profiles Direnv is the trickier case because it ties config to a directory, not just a command. Most direnv setups for AI agents look like: ```bash # ~/Projects/client-acme/.envrc export ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-acme -w) export ANTHROPIC_BASE_URL=https://gateway.acme.com/anthropic ``` The Ringlet way: a profile with the same binding, plus an optional shell hook to auto-select it by directory. ```bash ringlet profiles create claude acme \ --provider custom \ --custom-endpoint https://gateway.acme.com/anthropic \ --key-alias anthropic-acme # Auto-select when you're in the project directory cat >> ~/.zshrc <<'EOF' ringlet_auto_profile() { if [[ "$PWD" == "$HOME/Projects/client-acme"* ]]; then export RINGLET_DEFAULT_PROFILE=acme else unset RINGLET_DEFAULT_PROFILE fi } chpwd_functions+=(ringlet_auto_profile) ringlet_auto_profile # run on shell start EOF ``` Now `claude` (or `ringlet profiles run`) inside the project directory uses the acme profile. Outside it, your default. The shell hook is cheap (`[[ ]]` is a builtin) so there's no measurable cost on every `cd`. If you want stricter "this dir → this profile" mapping, add a `[directory-mapping]` block to `~/.config/ringlet/config.toml`: ```toml [[directory-mapping]] path = "~/Projects/client-acme" profile = "acme" [[directory-mapping]] path = "~/Projects/personal" profile = "personal" ``` `ringlet profiles run` (no args) consults this when called from inside the directory. ## Step 5: Translate wrapper scripts The trickiest one. A typical wrapper script might do: ```bash #!/bin/bash # ~/.local/bin/ai-prod export ANTHROPIC_API_KEY=$(...) export ANTHROPIC_BASE_URL=$(...) # Log everything to a JSONL for audit exec claude "$@" 2> >(tee -a ~/audit-prod.jsonl >&2) ``` The env-var part becomes a Ringlet profile (steps 2 and 3 above). The audit logging becomes a Ringlet hook: ```bash ringlet hooks add prod --on tool-use \ --shell 'echo "$(date -u) $RINGLET_TOOL $RINGLET_TOOL_INPUT" >> ~/audit-prod.jsonl' ``` Now `ringlet profiles run prod` does what the wrapper used to do, plus you get Ringlet's cost tracking for free. ## Step 6: Migrate per-project memory If you've been using Claude Code in multiple projects, you probably have project-specific `CLAUDE.md` files. These are still relevant — they live in the repo and Claude reads them from the workspace. They don't need migration. What does need migration is anything stored in `~/.claude/` that's project-specific (memory entries, project-level approvals). `ringlet import claude --to ` will move them all into one profile by default. If you want to split them across profiles by directory, do it once manually: ```bash # Move acme-related memory into the acme profile mv ~/.claude/memory/projects/client-acme \ ~/.ringlet/profiles/acme/HOME/.claude/memory/projects/ # Move personal-related memory into the personal profile mv ~/.claude/memory/projects/personal-* \ ~/.ringlet/profiles/personal/HOME/.claude/memory/projects/ ``` Imperfect. Manual. Once. ## Step 7: Delete the old setup Once everything is mapped: ```bash # Back up just in case cp -r ~/.claude ~/.claude.bak cp ~/.zshrc ~/.zshrc.bak # Delete the original ~/.claude — your profiles have their own copies # (skip this if you want to keep using Claude Code without Ringlet sometimes) # rm -rf ~/.claude # Delete obsolete env exports from ~/.zshrc # (do this by hand — the audit list from Step 0 is your guide) # Remove direnv .envrc files that are now redundant # (only ones tied to AI keys; keep node version selectors, etc.) ``` ## What doesn't translate A few patterns Ringlet doesn't replicate: - **Per-invocation env tweaks.** If your wrapper script reads from the shell environment dynamically (e.g. picks a model based on `$DEBUG`), you'll need to set up multiple profiles or use the routing layer. - **Interactive prompts inside the wrapper.** Wrapper scripts sometimes ask "which account?" interactively. Ringlet's model is "the profile chooses for you" — make the choice explicit ahead of time. - **External tool integration.** If a wrapper script exec'd a tool *and* an agent, sharing state between them — Ringlet doesn't have a primitive for that. Keep the wrapper for that case. ## Two weeks later The pattern most people settle into: - Three to five profiles total. Personal, work, one per client engagement. - Shell aliases mapping `claude` to `ringlet profiles run` with a default profile based on directory. - One audit hook running across all profiles. - Weekly check on `ringlet usage` to catch surprises. The shell rc shrinks. The `.envrc` litter shrinks. The "wait, which account is this terminal on?" anxiety goes away. The cost tracking pays for the install in about a week. Worth 15 minutes. --- ## Keychain credential storage in Ringlet: Security.framework, Keyring, WinCred URL: https://ringlet.neullabs.com/blog/keychain-credential-storage Published: 2026-05-26 Tags: technical, security, credentials, keychain Cluster: technical Where Ringlet stores your API keys and why — Apple Security.framework on macOS, GNOME Keyring or KWallet on Linux, Windows Credential Manager. The Rust keyring crate, file-backed fallback, and how to rotate. Coding agents need API keys. API keys in plain-text config files are a recurring foot-gun — they get committed to git, included in screen recordings, and leaked through error messages. The OS-native keychain stops all of that. Ringlet uses it by default. ## What the keychain stores Every API key Ringlet manages is stored as a generic password in the OS-native credential store. The Ringlet binary holds *no* secret material in memory longer than it needs; on every launch, it looks up the key by service + account name and uses it to construct the env block for the agent. The storage format is: | OS | Backend | Service name | Account name | |---|---|---|---| | macOS | Keychain Services (`Security.framework`) | `ringlet` | `/` | | Linux | Secret Service API (GNOME Keyring, KWallet) | `ringlet` | `/` | | Windows | Credential Manager (generic credentials, `wincred`) | `ringlet//` | (alias) | Examples: ```text ringlet/anthropic/default ringlet/anthropic/work ringlet/anthropic/personal ringlet/openai/default ringlet/minimax/default ringlet/openrouter/default ``` The cross-platform abstraction is the Rust [`keyring`](https://crates.io/crates/keyring) crate, which we picked because: - It's actively maintained. - It handles the three platforms with a single API. - It doesn't shell out to platform tools; it links the native libraries. ## Adding and inspecting keys ```bash # Interactive: Ringlet prompts, key is read via getpass() ringlet keychain set anthropic default # Non-interactive (from env) ANTHROPIC_API_KEY=sk-ant-... ringlet keychain set anthropic default --from-env ANTHROPIC_API_KEY # List entries (names only — no secret values shown) ringlet keychain list # anthropic/default # anthropic/work # openai/default # Remove ringlet keychain remove anthropic work # Rotate (replace existing) ringlet keychain set anthropic default --replace ``` `ringlet keychain show` is deliberately not a command. If you want to see the raw secret, use the OS's own tool (Keychain Access, `secret-tool`, Credential Manager). Ringlet treats keys as write-only. ## Aliases and how profiles reference them A profile references a keychain entry by alias, not by value: ```toml # ~/.ringlet/profiles/work/ringlet.toml agent = "claude" provider = "anthropic" [provider-config] key-alias = "work" # → ringlet/anthropic/work ``` Two profiles can share an alias if you want: ```toml # both ~/.ringlet/profiles/work/ringlet.toml # and ~/.ringlet/profiles/work-staging/ringlet.toml [provider-config] key-alias = "work" ``` Both will load the same key on launch. Useful for "different config, same billing account." ## What the file-backed fallback looks like On headless Linux without a Secret Service implementation, Ringlet falls back to a TOML file: ```toml # ~/.config/ringlet/credentials.toml — file mode 0600 [anthropic.default] key = "sk-ant-..." [anthropic.work] key = "sk-ant-..." [openai.default] key = "sk-..." ``` The file is created with `0600` permissions; Ringlet refuses to read it if the mode is more permissive. There's a startup warning: ```text ⚠ Using file-backed credentials (~/.config/ringlet/credentials.toml). For better security on multi-user systems, install GNOME Keyring or set up a service-mode keychain. ``` You can opt out of the fallback in `~/.config/ringlet/config.toml`: ```toml [credentials] backend = "system" # "system" | "file" | "system-only" ``` `system-only` refuses to start if the system keychain isn't available — safer for production-style deploys. ## Daemon authentication is separate Don't confuse credentials (API keys for providers) with daemon authentication (the bearer token for `127.0.0.1:8765`). They're two different things stored in two different places: - API keys: keychain, accessed by aliases. - Daemon bearer token: `~/.config/ringlet/http_token` (file mode 0600). Rotated via `ringlet daemon rotate-token`. The bearer token lives in a file because the daemon needs to read it on startup before any user is logged into the session keychain (think: cron-launched daemon on a server). The keys live in the keychain because they're user-scoped and read on-demand. ## What this protects against - **Plain-text leakage.** Keys aren't in your shell history, your `.env` files, or your git commits. - **Process-level access on macOS/Windows.** A separate process can't read another user's keychain without authentication. - **Accidental copy-paste.** Ringlet never logs the key value, never prints it, never sends it in an error message. If you `strings` the binary you'll find the URL fragments but not your key. ## What this does NOT protect against - **An attacker with your shell.** If they can run commands as you, they can ask Ringlet to launch an agent — which means they get the same effective access to the provider API as you do. - **A keylogger.** If the keychain is unlocked when Ringlet asks for the key, a keylogger or similar wouldn't see the key but would see whatever the agent does. - **The agent itself.** Once the agent is running, it has the key in its own process memory. That's how the API call gets made. The agent is trusted (it's the binary you `apt`/`brew` installed). ## Migrating from existing setups If you've been running agents with `ANTHROPIC_API_KEY` in your shell rc, migrate is one step: ```bash # Pick up the key from your current env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY ringlet keychain set anthropic default --from-env ANTHROPIC_API_KEY # Remove it from shell rc sed -i '' '/ANTHROPIC_API_KEY/d' ~/.zshrc # Restart your shell, verify source ~/.zshrc echo $ANTHROPIC_API_KEY # → (empty) ringlet profiles run default # → claude launches with key from keychain ``` Same for `OPENAI_API_KEY`, `XAI_API_KEY`, etc. ## Auditing keychain access macOS Keychain Access shows which apps accessed each item. Linux's `seahorse` (GNOME Keyring frontend) does similar. Windows Credential Manager is more opaque, but the API access is auditable via the Event Log. For Ringlet-side audit, the `--audit-log` flag records every keychain lookup (with the alias name, never the value). Useful for "who/what asked for the work-account Anthropic key this week." ## What's next The current keychain implementation is single-machine, single-user. The team tier will add: - HSM-backed key storage (AWS KMS, HashiCorp Vault, etc.) for shared infrastructure. - Time-bound key lease (key valid for N hours, then re-derived). - Per-agent capability scoping (key X can only be used for tool Y). For now, the OS keychain is the right default and the right place to look if a key isn't behaving the way you expect. --- ## Event hooks and webhooks: audit logs, Slack alerts, pre-tool guards URL: https://ringlet.neullabs.com/blog/event-hooks-and-webhooks Published: 2026-05-25 Tags: guide, hooks, audit, webhooks Cluster: guide Ringlet's hook system fires shell commands, scripts, or webhooks on every agent lifecycle event — tool use, stop, notify, cost-threshold. Use it for audit logs, Slack pages, or pre-flight checks on tool calls. Profiles isolate state. Sandboxes isolate filesystems. The other lever you want is *deciding what the agent is allowed to do, when, and what happens after*. That's what hooks are for. A hook is a rule that fires shell commands, Rhai scripts, or webhooks on lifecycle events. There are seven event types out of the box. ## The event surface | Event | When it fires | |---|---| | `pre-tool-use` | Before any tool call. Can deny. | | `tool-use` | Immediately after a tool call. Informational. | | `stop` | When the agent exits a turn (waiting for user). | | `start` | When a profile launches an agent. | | `notify` | When the agent calls a notification API (e.g. `osascript`). | | `cost-threshold` | When a profile's spend crosses a configured limit. | | `daily-rollup` | Once per UTC day. Useful for summary webhooks. | Each event handler receives a small payload (profile name, agent, tool name + args for tool events, etc.) and decides what to do. The handler can be a shell command, a Rhai script, or an HTTP webhook. ## Adding a hook ```bash # Audit every tool call to a local log file ringlet hooks add work --on tool-use \ --shell 'echo "$(date -u) $RINGLET_PROFILE $RINGLET_TOOL" >> ~/audit.log' # Page Slack when the work profile spends > $10 in a day ringlet hooks add work --on cost-threshold \ --period day --threshold 10 \ --webhook 'https://hooks.slack.com/services/...' # Block 'rm -rf' before the tool runs ringlet hooks add work --on pre-tool-use \ --shell 'echo "$RINGLET_TOOL_INPUT" | grep -qv "rm -rf"' # Exit 0 → allow, non-zero → deny ``` Hooks live in `~/.ringlet/profiles//hooks/` (per-profile) or `~/.config/ringlet/hooks/` (global). They're TOML files; the CLI just generates them for you. ## Use case: audit log The simplest valuable hook. Every tool call appended to a log: ```bash ringlet hooks add work --on tool-use \ --shell 'jq -n --arg t "$(date -u +%FT%TZ)" --arg p "$RINGLET_PROFILE" --arg tool "$RINGLET_TOOL" --arg in "$RINGLET_TOOL_INPUT" "{t:\$t, p:\$p, tool:\$tool, input:\$in}" >> ~/audit.jsonl' ``` You get one JSON line per tool call. `jq` queries are easy after the fact: ```bash # What did the agent do today? jq -r '.tool' ~/audit.jsonl | sort | uniq -c | sort -rn # Anything destructive? jq 'select(.input | contains("rm "))' ~/audit.jsonl ``` If you want a tamper-evident log (hash chain), that's a planned feature for the team tier. Today the audit log is plain JSON — fine for after-the-fact review, not for cryptographic non-repudiation. ## Use case: Slack alerts ```bash ringlet hooks add work --on cost-threshold \ --period day --threshold 10 \ --webhook 'https://hooks.slack.com/services/T.../B.../...' \ --webhook-template '{"text":"⚠️ work profile crossed $10 today: $RINGLET_TOTAL_USD"}' ``` The `--webhook-template` is a JSON template with `$RINGLET_*` variables substituted. The substitution is intentionally limited — no shell expansion, no arbitrary scripts — so the template is safe to share. ## Use case: pre-flight checks The `pre-tool-use` hook is the only one that can *block* a tool call. The handler runs synchronously; if it exits non-zero, Ringlet returns an error to the agent before the tool actually executes. ```bash # Block destructive shell calls ringlet hooks add prod --on pre-tool-use \ --shell 'case "$RINGLET_TOOL_INPUT" in *"rm -rf"*|*"DROP TABLE"*|*"git push --force"*) exit 1 ;; *) exit 0 ;; esac' ``` For richer logic, use a Rhai script: ```rust // ~/.ringlet/profiles/prod/hooks/pre-tool-use.rhai fn on_pre_tool_use(ctx) { let input = ctx.tool_input; // Block dangerous shell commands. if ctx.tool == "bash" { let blocked = [ "rm -rf /", "rm -rf ~", "git push --force", "DROP TABLE", ":(){ :|:& };:", ]; for pattern in blocked { if input.contains(pattern) { return deny(\`Pattern '\${pattern}' is blocked on this profile.\`); } } } // Cap the size of file writes. if ctx.tool == "write_file" && ctx.tool_input.len() > 1_000_000 { return deny("Write > 1MB blocked by hook."); } return allow(); } ``` The Rhai sandbox is documented [in the Rhai book](https://rhai.rs/book/). It's effectively a JavaScript-like language with strict typing and no I/O escape hatches by default — you can read from a small allow-listed FS surface but you can't shell out. ## Use case: daily rollups ```bash ringlet hooks add --all-profiles --on daily-rollup \ --webhook 'https://hooks.slack.com/services/...' \ --webhook-template '{"text":"📊 Yesterday: $RINGLET_TOTAL_USD across $RINGLET_PROFILE_COUNT profiles. Top: $RINGLET_TOP_PROFILE ($RINGLET_TOP_USD)"}' ``` Fires at 00:05 UTC. Useful for catching anomalies the day after they happen. ## Variables available to hook handlers Each event sets a slightly different set of `$RINGLET_*` environment variables: | Variable | Set on | |---|---| | `$RINGLET_PROFILE` | All events | | `$RINGLET_AGENT` | All events | | `$RINGLET_PROVIDER` | All events | | `$RINGLET_TOOL` | `pre-tool-use`, `tool-use` | | `$RINGLET_TOOL_INPUT` | `pre-tool-use`, `tool-use` (the JSON-serialised tool input) | | `$RINGLET_TOOL_RESULT` | `tool-use` (after the call, success/error) | | `$RINGLET_COST_USD` | `cost-threshold` (the amount that triggered) | | `$RINGLET_DURATION_MS` | `stop`, `daily-rollup` | | `$RINGLET_TOTAL_USD` | `daily-rollup` | ## Performance Hook execution is non-blocking for events that don't gate (`tool-use`, `stop`, `notify`, `cost-threshold`, `daily-rollup`). They run on a worker thread; the agent doesn't wait. `pre-tool-use` is gating — Ringlet waits for the handler to return before letting the tool call proceed. Keep these fast. Default timeout is 2 seconds; configurable per-hook. ## Where the team tier picks up Today's hook system is single-developer. The team tier will add: - Centralised hook policies (one Slack webhook URL configured org-wide). - Signed audit logs (hash-chained). - Per-event webhooks delivered via a queue (no agent-side timeout pressure). - Role-based override (manager can bypass a pre-tool-use deny with an approval). For now, hooks are the right shape for one developer or a small team that wants to roll their own setup. The TOML is portable; you can check it into a dotfiles repo and replay it on a new machine in seconds. --- ## Remote terminal sessions: running Claude Code on a server, browser access URL: https://ringlet.neullabs.com/blog/remote-terminal-sessions Published: 2026-05-24 Tags: guide, remote, websocket, pty Cluster: guide How Ringlet runs an AI coding agent on a remote dev box and lets you access the live PTY from your laptop's browser. WebSocket PTY proxy, bearer-token auth, sandboxed exec, xterm.js dashboard. Some AI coding work fits the laptop. Some doesn't. Compiling a Rust monorepo, running a big test suite, indexing a giant codebase — you want a real dev box doing the work, with a slim client on your laptop or phone. Ringlet's remote-session feature is that slim client. The agent lives on the server. You drive it from your browser. ## The shape of it ```text ┌─────────────────┐ ┌──────────────────────────┐ │ your laptop │ WebSocket (TLS) │ your dev box │ │ ─────────── │ ─────────────────▶│ ──────────── │ │ ringlet client │ │ ringletd (daemon) │ │ or browser │ ◀─────────────────│ ↓ exec inside sandbox │ │ │ PTY bytes │ claude / codex / ... │ └─────────────────┘ └──────────────────────────┘ ``` The daemon on the dev box listens for authenticated WebSocket connections, allocates a PTY, execs the agent inside a sandbox, and proxies bytes both ways. On the client side, xterm.js renders the PTY as a terminal in the browser. Or you can use `ringlet profiles run … --remote` from the CLI; the daemon handles both shapes the same way. ## Setting it up On the dev box: ```bash # Install (same as the local install) curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh # Run the daemon (binds to 127.0.0.1:8765 by default) ringlet daemon --stay-alive # To accept connections from outside, listen on the LAN interface: ringlet daemon --stay-alive --listen 0.0.0.0:8765 ``` Grab the daemon's bearer token: ```bash cat ~/.config/ringlet/http_token # → 7f4a8b2c-... ``` On your laptop: ```bash ringlet remotes add dev-box --url http://dev-box.local:8765 --token 7f4a8b2c-... ringlet profiles run work --remote dev-box ``` Or, if you'd rather a browser: ```text http://dev-box.local:8765 # Auth dialog appears, paste the bearer token # Dashboard shows profiles, click "Run" on the one you want ``` ## What happens at exec 1. Client connects to `wss://dev-box.local:8765/api/session` with the bearer token in `Sec-WebSocket-Protocol`. 2. Daemon allocates a PTY pair. 3. Daemon builds the sandbox config for the profile (see [Sandboxing AI coding agents](/blog/sandboxing-ai-coding-agents)). 4. Daemon execs the agent inside `bwrap` (Linux) or `sandbox-exec` (macOS), with the PTY as stdin/stdout/stderr. 5. Daemon proxies bytes between the WebSocket and the PTY. 6. Resize events (terminal window resize on your laptop) are sent as control frames; the daemon ioctl-resizes the PTY. The agent is none the wiser. From its point of view, it's a normal interactive terminal. ## Why a daemon and not just SSH + tmux? Three reasons we kept hearing from the people who asked for this: 1. **Phones and tablets.** SSH from an iPad to a dev box exists but is mediocre. A browser-based terminal is just better. 2. **Multi-session bookkeeping.** A daemon keeps track of which profiles are running, who's connected, and what they've done. SSH + tmux gives you screen sessions; the daemon gives you a queryable index. 3. **Sandboxing.** Wrapping every `tmux new-session` in `bwrap` is doable but fragile. The daemon does it consistently because it's the only thing that exec's the agent. You can still SSH in and use `ringlet profiles run` directly — the daemon doesn't replace SSH. But the WebSocket-based path is more pleasant for the common case. ## Reattach and detach The daemon keeps the PTY alive even when no client is connected. Disconnect your laptop, the agent keeps running. Reconnect later, the buffer is replayed: ```bash # List active sessions ringlet remotes sessions dev-box # SESSION_ID PROFILE AGENT UPTIME CLIENT # 7a2c-... work claude 12m (disconnected) # Attach to an existing session ringlet remotes attach 7a2c-... ``` For long-running tasks ("index this monorepo, post the report to Slack"), this is the killer feature. Kick it off, close the laptop, attach later. ## Security defaults - **TLS.** In production, terminate TLS at a reverse proxy or pass `--tls-cert` / `--tls-key` to the daemon. Without TLS, the bearer token is sent in plaintext on first WebSocket handshake — fine for `localhost`, not fine across an untrusted network. - **Bearer token only.** No password auth. The token is the credential. Rotate with `ringlet daemon rotate-token`. - **Localhost by default.** Unless you set `--listen 0.0.0.0`, the daemon is unreachable from anywhere except the same host. - **Sandbox always-on for remote.** Remote sessions are sandboxed by default. Override per-profile if you must. ## Performance PTY bytes over a WebSocket are cheap. We've tested with: - 60 fps interactive use (typing, completions): unmeasurable overhead. - Large outputs (compiling Rust with full warnings): the WebSocket is the bottleneck only if you're already pushing >10MB/sec — which means you'd see the same speed locally. - Many simultaneous sessions: tested up to 20 active PTYs per daemon. The daemon is single-process Tokio; CPU stays well under 1 core. If you're running an AI coding agent on a beefy dev box and the WebSocket is the bottleneck, something is off. ## Where it doesn't fit - **Strict network policies.** If the daemon can't be reached from your laptop (corporate VPN, no port forwarding), you're back to SSH. Eventually we'll ship an outbound-tunnel mode. - **Strong multi-tenant isolation.** The sandbox per-session is good. For "untrusted user with their own root login," use containers and run the daemon inside. - **Mobile-first heavy use.** xterm.js on a phone browser is workable, not delightful. We use the Tauri-packaged desktop app on laptops. ## What's next The remote feature is the youngest part of Ringlet — shipping at 0.1.0 but newer than profiles and cost tracking. Near-term improvements: - Outbound-tunnel mode (no inbound port required on the dev box). - Persistent recordings (replay a session like asciinema). - Multi-client view (two people watching the same agent run). If any of those would change how you'd use it, file an issue on [GitHub](https://github.com/neul-labs/ringlet). The priorities follow the noise. --- ## Sandboxing AI coding agents with bwrap and sandbox-exec URL: https://ringlet.neullabs.com/blog/sandboxing-ai-coding-agents Published: 2026-05-23 Tags: technical, security, sandboxing, bwrap Cluster: technical How Ringlet wraps remote agent sessions in a real OS sandbox — bubblewrap on Linux, sandbox-exec on macOS — keeping destructive commands inside a workspace. The config, trade-offs, and failure modes. An AI coding agent that can write code can also write `rm -rf`. Most of the time the agent is well-behaved, prompt injection isn't a factor, and the worst thing it'll do is reformat your code. But the worst-case isn't zero. Especially for remote sessions where a teammate is running an agent on your dev box, "the agent has the same privileges as you" is not the security model you want. Ringlet's remote-session feature wraps the agent in an OS-level sandbox before launch. This article describes how that works, what the agent can and can't do, and where the limits are. ## The threat model What we're defending against: 1. **An agent making an honest mistake.** It runs `rm` against the wrong path. The sandbox bounds the blast radius to the workspace. 2. **An agent following a malicious tool result.** Prompt injection in a fetched web page or an MCP-server reply convinces it to exfiltrate or destroy data. The sandbox limits what it can reach. 3. **A bug in the agent or one of its tools** that lets it do something the user didn't intend. Same defence. What we're not defending against: 1. **A malicious agent vendor.** If Anthropic ships a malicious Claude Code, the sandbox slows it down but doesn't stop it from doing whatever the binary is hard-coded to do. 2. **An attacker with root on the machine.** The sandbox is an unprivileged wrapper. Root can bypass it. 3. **An attacker with a kernel exploit.** Same. Ringlet's sandbox is a *trust-but-verify* layer for an agent you mostly trust, not a containment layer for an agent you don't. ## Linux: bubblewrap (bwrap) On Linux, Ringlet wraps the agent in `bwrap`. The default profile mounts: - The system root, read-only. - An explicit workspace directory, read-write (typically the project directory you're running the agent against). - The agent's HOME from the Ringlet profile, read-write. - `/proc`, `/sys`, `/dev` — restricted, kernel-default views. - A fresh user namespace, IPC namespace, and PID namespace. What the sandbox bans: - Writes outside the workspace and the profile HOME. - Binds to network ports (the agent can make *outbound* connections but can't listen). - Loading kernel modules, mounting filesystems, joining other namespaces. - Accessing other users' processes. The actual bwrap invocation Ringlet builds looks roughly like: ```text bwrap \ --ro-bind / / \ --bind /home/you/projects/myrepo /workspace \ --bind /home/you/.ringlet/profiles/work/HOME /home/agent \ --tmpfs /tmp \ --proc /proc \ --dev /dev \ --unshare-all \ --share-net \ --new-session \ --setenv HOME /home/agent \ --setenv ANTHROPIC_API_KEY \ --setenv ANTHROPIC_BASE_URL https://api.anthropic.com \ --chdir /workspace \ /usr/local/bin/claude ``` The agent's view is *as if* it's running in a much smaller filesystem. It sees `/workspace` as its working directory and `/home/agent` as its HOME. It can read the system binaries it needs (because the root is mounted read-only) but it can't write to `/etc`, `/usr`, or anywhere outside its workspace. Network access is granted by `--share-net` — necessary because the agent has to reach the provider API. If you want stricter network control, run the daemon behind a proxy and constrain outbound DNS resolution. ## macOS: sandbox-exec macOS's equivalent is `sandbox-exec`, which uses Apple's Sandbox framework (the same one App Sandbox uses for App Store apps). Ringlet ships a `.sb` profile: ```text (version 1) (deny default) (allow file-read*) ; system binaries / libs (allow file-write* (subpath "/Users/you/projects/myrepo")) (allow file-write* (subpath "/Users/you/.ringlet/profiles/work/HOME")) (allow file-write* (literal "/private/tmp")) (allow file-write* (regex #"^/var/folders/.*")) ; for caches (allow network-outbound (remote tcp)) ; provider API calls (deny network-bind) ; no listening sockets (allow process-fork) (allow process-exec (regex #".*")) (allow signal (target self)) (deny iokit-open) (deny system-info) ``` The agent runs with effectively the same permissions as before but can't write outside its workspace or its profile HOME, and can't bind to ports. Apple's sandbox is fine-grained — see the [Sandbox Reference](https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf) for the full grammar. ## Customising the sandbox Sometimes the default is too strict. The most common case: a project's test suite needs to bind to a localhost port (e.g. a dev server on `:3000`). Two options: 1. **Per-profile allow list.** Add a `[sandbox]` block to the profile manifest: ```toml [sandbox] allow-bind = ["127.0.0.1:3000"] extra-rw = ["/var/log/myapp"] ``` 2. **Sandbox bypass for trusted workflows.** Set `sandbox = "off"` in the profile. The agent runs without `bwrap`/`sandbox-exec`. We don't recommend this for remote sessions but it's there if you need it. ## What about local sessions? By default, `ringlet profiles run ` (no `--remote`) doesn't apply the sandbox. The reasoning: a local session is you typing commands at an agent on your own machine — the agent has whatever privileges you do, by definition. Wrapping it in `bwrap` is theatre. Remote sessions are different: the daemon is launching the agent on behalf of a WebSocket client, possibly across a network. The trust model is different. So remote always sandboxes, local never does unless you explicitly opt in. If you want sandboxing on local sessions too, `ringlet profiles set work --sandbox always`. Trade-off: any tool the agent tries to use that involves a path outside the workspace will fail. ## Failure modes worth knowing about - **Path-mapped tools break.** If a tool expects an absolute path that's outside the workspace, it fails inside the sandbox. Example: an MCP tool that reads `/Users/you/Documents/spec.md` — Ringlet maps `/workspace`, not `/Users/you`. Either move the input into the workspace or extend the sandbox config. - **Network mounts.** SMB / NFS shares outside the workspace mount don't appear. Mount them inside the workspace or extend. - **Some Node/Python tools assume access to a global cache.** Inside the sandbox, they may try to write to `~/.npm` or `~/.cache/pip` — which is now the profile HOME, not the system one. Usually fine, occasionally surprising. - **Long-running processes the agent spawns** inherit the sandbox. If the agent starts a dev server in the background, that dev server is sandboxed too. Usually correct. ## Auditing what the sandbox blocked Ringlet's audit log records sandbox denials when `--audit-log` is on: ```bash ringlet daemon --stay-alive --audit-log ~/.ringlet/audit.db # ...later... ringlet audit query --type sandbox-deny --profile work --since 2026-05-01 ``` This is useful for tuning the sandbox config. "The agent tried to do X and was denied" tells you whether to extend the rules or whether the denial saved you. ## How this compares to container-level isolation You could run each agent in a Docker container. We don't, because: - Cold-start is slow (1–3 seconds for container creation; bwrap is sub-100ms). - Container networking adds friction (you have to mount Docker socket, expose ports). - The container layer itself is a security boundary, but it also adds attack surface (Docker daemon, image registry, etc.). For Ringlet's threat model — limit blast radius of a mostly-trusted agent — `bwrap`/`sandbox-exec` hits the right balance. If your threat model is stricter (e.g. running untrusted code, multi-tenant), use containers — and run Ringlet inside them. ## Try it The sandbox kicks in any time you use `--remote`. The simplest test: ```bash # On your dev box ringlet daemon --stay-alive # From your laptop ringlet profiles run work --remote dev-box.local:8765 # Inside the agent, try: > rm /etc/passwd # → Permission denied (rule from sandbox profile) ``` The denial is the feature. If you'd rather it not be there, opt out per-profile — but for any session you'd let a teammate touch, leave the sandbox on. --- ## Routing requests by cost, tool use, or token count with Ringlet URL: https://ringlet.neullabs.com/blog/routing-requests-by-cost Published: 2026-05-22 Tags: guide, routing, cost, ultrallm Cluster: guide Ringlet's optional ultrallm proxy routes an agent's individual requests to different providers by token count, tool use, or arbitrary TOML rules. Cheap completions to Groq, tool-heavy work to Anthropic — same agent. A profile binds an agent to *one* provider. That covers most cases. But sometimes you want one agent to use *different* providers for different requests inside the same conversation: - Small completions go to Groq (fast, cheap). - Tool-use turns go to Anthropic (best at structured tool calls). - Anything over 100K tokens goes to a model with the right context window. For that, Ringlet ships an optional routing proxy called `ultrallm`. Attach it to a profile and rules decide where each request goes. ## When you need routing (and when you don't) You probably don't need routing if: - You only use one agent and one provider. - Cost differences between providers don't matter at your scale. - Latency variance from cross-provider routing would annoy you more than it would save. You probably do want routing if: - You're cost-sensitive and the agent makes a lot of small completions. - You've split a workload across two providers manually with shell wrappers and would rather it be declarative. - You want to A/B test "Claude Sonnet for the hard turns, Haiku for everything else" without rewriting the agent. ## How to attach the proxy to a profile ```bash ringlet profiles set work --proxy ultrallm ``` That tells Ringlet to launch `ultrallm` as a sidecar when the profile runs, with the agent pointed at `127.0.0.1:` instead of the provider directly. The proxy reads `~/.ringlet/profiles/work/routing.toml` for its rules. ## A first routing config ```toml # ~/.ringlet/profiles/work/routing.toml [[rule]] match = { tool-use = true } to = "anthropic" model = "claude-sonnet-4-5" [[rule]] match = { input-tokens-lt = 2000 } to = "groq" model = "llama-3.3-70b-versatile" [[rule]] match = { input-tokens-gt = 100000 } to = "anthropic" model = "claude-sonnet-4-5" # long context [default] to = "anthropic" model = "claude-haiku-4-5" ``` Rules are evaluated top to bottom. First match wins. `[default]` runs if nothing else matches. Now when the agent makes a request, ultrallm inspects the payload, finds the matching rule, swaps the base URL and model name, and forwards the request. The agent thinks it talked to one provider; in reality, the routing layer picked the right one per-request. ## Available match conditions | Condition | Meaning | |---|---| | `tool-use = true` | Request includes one or more tools. | | `input-tokens-lt = N` | Estimated input token count < N. | | `input-tokens-gt = N` | Estimated input token count > N. | | `messages-gt = N` | Conversation has more than N turns (proxy for "long context"). | | `has-system-prompt = true` | Request includes a system prompt. | | `model-equals = "..."` | The original request asked for this model. | The match conditions are designed to be cheap — Ringlet doesn't deserialize the full message body, just enough to evaluate the rule. ## Observability `ringlet usage` already shows you per-profile spend. With routing, you also get per-rule spend: ```text $ ringlet usage --profile work --by-rule RULE REQUESTS TOKENS COST tool-use → anthropic 342 1.1M / 280K $7.81 input-tokens-lt → groq 1284 980K / 220K $0.18 default → anthropic 128 180K / 45K $1.10 ───────────────────────────────────────────────────────── TOTAL $9.09 ``` If a rule fires 0 times, that's a signal it's not useful. If 90% of your traffic hits the default rule, that's a signal your rules aren't aggressive enough. ## Caveats and gotchas - **Latency.** Cross-provider routing adds 5–20ms in the local hop. Not a problem for most workflows; if you're running a latency-sensitive automation, measure. - **Tool calling shape varies.** Anthropic and OpenAI both have tool calling, but the wire format is slightly different. ultrallm handles the translation for Anthropic-shaped agents; for OpenAI-shaped agents, route only between OpenAI-compatible providers. - **Streaming.** All ultrallm-supported providers stream. If you route to a provider that doesn't stream, the agent may stall. - **Caching.** Anthropic's prompt caching has provider-specific semantics. Routing the same conversation between Anthropic and another provider invalidates the cache. ## When to use this vs LiteLLM [LiteLLM](/compare/litellm) is a fuller-featured routing proxy with budgets, retries, fallbacks, and a multi-tenant control plane. ultrallm is a stripped-down version optimised for "one profile, a handful of rules, no separate service to run." If you're already running LiteLLM, point Ringlet at LiteLLM and let LiteLLM do the routing. If you don't want another service in the stack, ultrallm is enough for most single-developer setups. ## Honesty: this is the part of Ringlet that's least mature Profiles, isolation, and cost tracking are 0.1.0-stable. Routing is shipping but recent — expect rougher edges, smaller default rule sets, and changes to the TOML schema between minor versions. We document it in the [docs](https://docs.neullabs.com/ringlet/proxy) but treat it as a power-user feature rather than a default. If you want bullet-proof routing with audit and budgets, run LiteLLM. If you want declarative per-profile rules without operating a separate service, ultrallm. --- ## Cost tracking across Claude Code, Codex, and Grok in one ledger URL: https://ringlet.neullabs.com/blog/cost-tracking-across-agents Published: 2026-05-20 Tags: guide, cost, usage, finops Cluster: guide How Ringlet parses streaming token-usage events from Anthropic, OpenAI, and MiniMax, writes them to a per-profile SQLite ledger, and aggregates spend across every agent and provider in one CLI command. The hardest part of running multiple AI coding agents isn't usually picking the right one. It's getting a clear answer to "what did this cost?" when the answer lives across three accounts and four invoices. Ringlet's cost tracking exists to make that answer easy. Here's how it works and how to use it. ## How the data gets collected Every modern model provider streams a usage block inline with the response. Anthropic sends `input_tokens` and `output_tokens` as part of the streamed event sequence. OpenAI sends `usage` in the final chunk of a Chat Completions stream. MiniMax follows the OpenAI shape. Groq does too. Ringlet parses those events as they fly past — same data the agent sees. For each completed turn it writes a row to `~/.ringlet/profiles//usage.db`: ```text id, timestamp, profile, agent, provider, model, input_tokens, output_tokens, cost_usd ``` `cost_usd` is computed from a price table baked into the Ringlet binary and refreshed each release. If a price changes mid-month and you want exact-to-the-cent reconciliation, use the provider's invoice; Ringlet's ledger is a real-time signal, not a billing system of record. ## Querying spend The simplest view: `ringlet usage`. ```text $ ringlet usage PROFILE AGENT PROVIDER TOKENS COST work claude anthropic 1.21M / 340K $8.41 personal claude minimax 2.83M / 820K $0.92 client-acme claude anthropic 480K / 130K $3.20 staging codex openai 450K / 90K $1.40 side-project grok openai 2.1M / 510K $1.05 ──────────────────────────────────────────────────────────── TOTAL $14.98 ``` Filter by profile, date range, or model: ```bash ringlet usage --profile work --since 2026-05-01 ringlet usage --provider anthropic --since 2026-05-01 --until 2026-05-31 ringlet usage --model claude-sonnet-4-5 --by-day ``` The `--by-day` view is the one engineering managers ask for: ```text $ ringlet usage --profile work --by-day --since 2026-05-01 DATE TOKENS COST 2026-05-01 140K / 38K $1.02 2026-05-02 220K / 55K $1.61 2026-05-03 0 / 0 $0.00 ← weekend 2026-05-06 310K / 72K $2.18 ... ``` ## Export for billing reconciliation CSV export feeds straight into spreadsheets: ```bash ringlet usage --export csv > may-2026.csv ringlet usage --export csv --profile work --since 2026-05-01 > may-work.csv ``` JSON for programmatic consumption: ```bash ringlet usage --export json | jq '[.[] | select(.provider == "anthropic")] | add' ``` ## Importing existing logs If you've been running Claude Code without Ringlet, you already have `~/.claude/usage.jsonl`. `ringlet import` reads it and attributes the events to a profile of your choice: ```bash ringlet import claude --to work # ✓ Read 18,432 events from ~/.claude/usage.jsonl # ✓ Attributed to profile 'work' # ✓ Cost reconstructed: $241.83 (Mar 1 → May 12) ``` Same for Codex (`~/.codex/sessions/`) and OpenCode. The import is idempotent — running it twice doesn't double-count. ## Setting cost alerts The cost-threshold hook fires when a profile crosses a daily or monthly threshold: ```bash # Daily $10 limit on the work profile ringlet hooks add work --on cost-threshold \ --period day --threshold 10 \ --webhook 'https://hooks.slack.com/services/...' # Monthly $200 budget warning at 80% ringlet hooks add work --on cost-threshold \ --period month --threshold 160 --of 200 \ --shell 'osascript -e "display notification ..."' ``` These run on the daemon side, so you don't need to keep a polling script alive. ## What gets counted, what doesn't Counted: - Successful streaming responses, regardless of whether the agent kept the result. - Tool-call cycles (an agent making a tool call and then continuing the turn produces multiple LLM round trips; each one is counted). - Failed responses where the provider still streamed a usage block before erroring. Not counted: - Requests that errored before the provider sent usage info (rare; network failures, 4xx auth errors). - Embeddings, image generation, audio — Ringlet today tracks chat completions. Embeddings via the agent's tools will be counted when that wire format stabilises. - Local-only model usage (e.g. running Ollama via a tool). The provider doesn't have a price; Ringlet records the call but not a dollar value. ## How accurate is it, really? Compared to provider invoices, Ringlet's totals are typically within 1–2% of the billed amount. Sources of drift: 1. **Mid-month price changes.** Ringlet's price table only updates with a release. 2. **Cached input pricing.** Anthropic's prompt-caching discount is reported in a separate event field; Ringlet handles this for Claude but if the provider adds new caching tiers between releases, drift increases. 3. **Per-minute rate adjustments** on hosted gateways (rare on Anthropic/OpenAI, common on smaller providers). For "what's the engineering team spending" answers, ±2% is plenty. For "exactly bill this customer," reconcile against the invoice. ## What you do with it A few patterns we've seen teams settle into: - **Per-client profiles.** Each customer engagement gets its own profile. CSV export feeds the invoice. - **Cost-aware agent selection.** Lower-stakes work goes through a MiniMax-backed profile; high-stakes through Anthropic-direct. The ringlet usage report shows the ratio in real time. - **Daily Slack rollups.** A scheduled hook posts yesterday's spend to a `#dev-spend` channel. People notice anomalies the day they happen. - **PR-blocking budget guards.** A pre-tool-use hook checks the current month's spend against a budget and denies tool calls past the threshold. Brutal but effective for runaway-loop bugs. ## Try it The `ringlet usage` command works the moment you run a profile through Ringlet. There's nothing to enable. If you've used Claude Code before, `ringlet import claude` gives you a backfill of history so the graph isn't empty on day one. [Install](/install) takes a minute. The first thing most people do after installing is run `ringlet usage` and find out where their month actually goes. --- ## Profile isolation explained: how Ringlet keeps your AI agents separate URL: https://ringlet.neullabs.com/blog/profile-isolation-explained Published: 2026-05-18 Tags: technical, isolation, claude-code, internals Cluster: technical What's actually inside ~/.ringlet/profiles/, how Ringlet rewrites the agent's HOME at launch, why env vars alone aren't enough, and what this means for caches, MCP servers, and tool approvals. If you've ever tried to manage two Anthropic accounts on one laptop by editing `ANTHROPIC_API_KEY` between terminal tabs, you've already met the problem Ringlet solves. The fix sounds easy until you actually try it. This article walks through what's inside a Ringlet profile, why isolating with environment variables alone breaks down, and what the agent itself sees when it launches. ## What's inside a profile Every profile lives at `~/.ringlet/profiles//`. The directory looks like this: ```text ~/.ringlet/profiles/work/ ├── HOME/ ← this becomes the agent's $HOME │ ├── .claude/ ← Claude Code's directory, this profile's copy │ │ ├── conversations/ │ │ ├── settings.json │ │ ├── memory/ ← project-level memory (CLAUDE.md cache, etc.) │ │ └── usage.jsonl │ └── .config/ ← if the agent uses XDG paths ├── ringlet.toml ← profile manifest ├── usage.db ← Ringlet's own token + cost ledger └── hooks/ ← profile-specific hooks ``` The crucial bit is the `HOME/` subdirectory. When you run `ringlet profiles run work`, Ringlet exports `HOME=~/.ringlet/profiles/work/HOME` before exec'ing `claude`. From the agent's point of view, that *is* its home directory. It writes its `.claude/conversations/` there. It reads its `.claude/settings.json` from there. It has no idea any other profile exists. The `ringlet.toml` manifest defines the binding: ```toml agent = "claude" provider = "anthropic" [provider-config] endpoint = "https://api.anthropic.com" key-alias = "work" # keychain entry name model = "claude-sonnet-4-5" # optional default [history] import-from = "~/.claude/usage.jsonl" # one-time, on profile creation ``` ## Why environment variables alone don't work The obvious first attempt at isolation is a wrapper: ```bash function claude-work() { ANTHROPIC_API_KEY=$(security find-generic-password -s 'anthropic-work') \ claude "$@" } ``` This sort of works. The API key is right. The token charges land on the right account. But agents cache much more than tokens, and none of it lives in environment variables. Specifically: - **`~/.claude/conversations/`** — every conversation Claude Code has ever had on this machine. Switching `ANTHROPIC_API_KEY` doesn't switch which conversations Claude reads. - **`~/.claude/settings.json`** — per-machine settings, including MCP server registrations. Pointing one profile at a Postgres MCP server pointed at staging means the other profile gets it too. - **`~/.claude/memory/`** — project-level memory. If the agent learned something specific to project A, it'll surface that in project B. - **`~/.claude/usage.jsonl`** — local usage log. Mixing two accounts here makes per-account cost accounting impossible. - **Per-project trust prompts.** When Claude asks "can I run this `rm -rf`?" and you say yes, the answer is cached. Across two profiles with the same HOME, that cache is shared. Every one of these is a state leak that an env-var wrapper can't fix, because the agent is reading from its own HOME path. The only durable fix is to give each profile a separate HOME. That's exactly what Ringlet does — and it's why "profile" is the right unit, not "session" or "command." ## What Ringlet actually does at launch When you run `ringlet profiles run work`, the call stack is: 1. **Read manifest.** `~/.ringlet/profiles/work/ringlet.toml`. 2. **Look up credentials.** Pull `anthropic/work` from the system keychain. 3. **Build env block.** Construct the environment the agent will see: ```text HOME=/Users/you/.ringlet/profiles/work/HOME ANTHROPIC_API_KEY= ANTHROPIC_BASE_URL=https://api.anthropic.com PATH=/usr/local/bin:/usr/bin:/bin # unchanged ``` 4. **Attach token-counting filter.** Insert a streaming proxy on the agent's network calls so usage events are written to `usage.db` in real time. 5. **Exec.** `execve("/usr/local/bin/claude", ...)` with the new environment. The agent then runs, reads its HOME, finds its conversations, and Ringlet stays out of the way. You can `ps aux | grep claude` and see exactly what process is running with what HOME — nothing magic. ## Two profiles on the same agent, in two terminals This is the headline use case. Two Claude Code instances on different Anthropic accounts, simultaneously, no conflict. ```bash # Terminal A ringlet profiles run work # claude launches with HOME=~/.ringlet/profiles/work/HOME # ANTHROPIC_API_KEY= # Terminal B (at the same time) ringlet profiles run personal # claude launches with HOME=~/.ringlet/profiles/personal/HOME # ANTHROPIC_API_KEY= ``` They don't share `.claude/conversations`. They don't share MCP registrations. They don't share trust prompts. They don't share usage logs. Two separate processes, two separate HOMEs, two separate everything. The agents have no idea the other exists. This isn't a contrived edge case — it's the most common Ringlet usage pattern. You're answering a client-A question in Terminal A and a client-B question in Terminal B and the agents don't get confused about whose code memory belongs to whose. ## What about MCP servers? MCP server registrations are per-profile, because they live in `.claude/settings.json` inside the profile's HOME. This is more useful than it sounds. Concrete example: you have a Postgres MCP server. In your `work` profile, it's pointed at the staging database. In your `production-incident` profile, it's pointed at prod. There is zero chance of accidentally running a staging-debug query against prod, because the agents are loading two different MCP configs from two different HOMEs. ```bash # work profile: staging cat ~/.ringlet/profiles/work/HOME/.claude/settings.json { "mcpServers": { "postgres": { "url": "postgres://staging.internal/app" } } } # production-incident profile: prod cat ~/.ringlet/profiles/production-incident/HOME/.claude/settings.json { "mcpServers": { "postgres": { "url": "postgres://prod.internal/app" } } } ``` If you've ever fat-fingered a staging connection into a prod-shaped agent session, you'll appreciate the value. ## What gets *shared* across profiles Not everything is isolated. By design: - **The binary.** All profiles run the same `claude` binary from `$PATH`. Upgrading Claude Code upgrades it for every profile. - **Token storage backend.** Keychain entries are stored once, referenced by alias. Two profiles can share a key by sharing an alias if you want. - **The Ringlet daemon and the hooks defined globally.** Per-profile hooks are isolated; daemon-level ones run across all profiles. If you actually want a totally isolated binary too — e.g. test a Claude Code prerelease in one profile while another stays on stable — install the prerelease to a different path and override `binary = "/opt/claude-canary/claude"` in the profile's `ringlet.toml`. ## Importing an existing setup If you've been using Claude Code without Ringlet, you already have a populated `~/.claude/`. Ringlet won't touch it unless you ask. `ringlet import claude --to work` copies the relevant bits into `~/.ringlet/profiles/work/HOME/.claude/`, attributing the usage history to the `work` profile. Future runs of `ringlet profiles run work` use the imported state; your original `~/.claude` stays as-is. ## What this isn't Some things this specifically isn't: - **Container-level isolation.** Profiles share the kernel, the filesystem (outside HOME), and the network stack. If you need stronger isolation, run the daemon inside a container or use the [sandboxed remote session](/blog/remote-terminal-sessions) feature. - **Multi-user isolation.** A profile boundary is a HOME boundary. The OS user is the same. Two Ringlet users would each have their own `~/.ringlet`. - **Magic.** Everything Ringlet does is files in a directory and env vars on exec. No kernel modules, no namespaces, no LD_PRELOAD. You can `cat ringlet.toml`, you can `ls HOME/`, you can debug it. ## Try it The two-profile pattern is the entry point. The [install page](/install) walks through it. If you have two Anthropic accounts, that's day-one Ringlet — make a `work` and a `personal` profile and stop worrying about which key is set. --- ## The agent stack in 2026: why one CLI per model isn't sustainable URL: https://ringlet.neullabs.com/blog/the-agent-stack-in-2026 Published: 2026-05-15 Tags: narrative, ai-agents, developer-tools, ecosystem Cluster: narrative Every major AI lab ships a coding CLI. Each assumes it owns your machine. The stack we're collectively building looks like the JavaScript ecosystem circa 2014 — and needs the same kind of orchestrator. Six months ago, you had Claude Code. Maybe Cursor if you wanted the IDE. That was the AI-assisted-coding stack. Today, on a fresh laptop set up for serious work, you'll install Claude Code (Anthropic's official CLI), Codex (OpenAI's), Grok CLI (xAI's), Droid (Factory's), OpenCode (the community-driven one), plus an open-source aider here and a Gemini CLI there. Each one is genuinely good at something the others aren't. None of them is "the winner" yet — and nobody we've talked to thinks one will be. So now your `~/.local/bin` has six CLIs in it. Your `~/` has five top-level dotdirectories (`~/.claude`, `~/.codex`, `~/.grok`, `~/.droid`, `~/.opencode`) each acting as if it owns your machine. Your bashrc has a dozen aliases. Your account dashboards at Anthropic, OpenAI, xAI, and OpenRouter all show charges and you don't know which project caused which. This is the stack now. It's not going away. And it needs an orchestration layer the way the JavaScript ecosystem needed npm. ## What "one CLI per model" optimised for The first wave of coding agents — and their wrappers — was right to ship as individual CLIs. They were optimised for the things they needed to prove: 1. **Standalone usefulness.** A coding agent has to work without any other tool installed. If you can't `npm install -g claude && claude` and have a useful conversation in 60 seconds, nobody will adopt it. 2. **Provider lock-in resistance.** Each lab wanted their CLI to be the durable bit of the relationship. Anthropic doesn't want Claude Code to look like a thin wrapper around OpenAI's API. 3. **First-party feature velocity.** Build a feature in your own CLI without coordinating with anyone. The pace was the point. Those were the right priorities for 2024–2025. The CLIs got good. Tool calling got good. The agent loop got good. But each one optimised for being the *only* CLI on the machine. None optimised for being one of five. ## The signs the stack has changed A few things shifted while we were watching individual CLIs get better: **Frontier model quality converged enough that no one model dominates every task.** Claude Sonnet is best at long-context refactors. GPT-5 is best at certain reasoning tasks. Grok is fast and cheap for prototyping. Gemini is good at vision and at integrating with Google services. Nobody we know runs only one. **Provider keys multiplied.** You have a personal Anthropic account and a work one. You also have OpenAI, OpenRouter, MiniMax, and probably a self-hosted gateway. Each agent CLI assumes one key per environment variable; managing five accounts on one machine is real work. **Cost became visible enough to matter.** When the first lab raised prices, people started caring. Per-project cost visibility — "what did this engineering team spend on Claude in May?" — is now a normal request from FinOps. The CLIs don't surface it. **Compliance teams started asking about agents.** A coding agent that can write code can also write `git push --force` to main. Sandboxing, audit logs, and per-team policy aren't optional for any organisation big enough to have a compliance team. **Multi-project work got harder, not easier.** The more agents you use, the more profile state lives in your HOME directory, and the more those profile states get in each other's way. We've all watched a project leak its Anthropic key into the wrong account via a stale env var. ## The npm pattern There's a really clean analogue from a decade ago. Around 2013, the JavaScript ecosystem looked like this: each library had its own install script. Each project depended on a different version of jQuery. Globally-installed packages collided with project-installed ones. People had bashrc tricks for switching node versions. The ecosystem was useful and growing — and there was no shared substrate. Then npm matured. Not as a feature, but as a *layer*: a thing that owns the dependency graph for one project, and gets out of the way of everything else. nvm, yarn, pnpm, and now Bun built on that pattern. The model — a thin manifest plus a tool that respects per-project state — became how the ecosystem coordinated itself. The AI coding agent ecosystem is at the same inflection point. Not because the agents need to share anything between each other (they don't), but because *the developer using them* needs a way to manage the state of running them. That layer is what Ringlet is. Profiles are the manifest. The CLI is the tool. The agent CLIs themselves don't change. ## What an orchestrator gives you that a wrapper doesn't People have been writing personal shell scripts to do parts of this for a while. Aliases, `direnv` rules, per-project `.envrc` files. They work, for a definition of "work." Here's what they don't give you: - **A canonical name for a context.** "Run the Claude Code I use for client A's repo, with their key, against their preferred provider" is a thing you can hand to a teammate's setup script if it has a name. As a bashrc alias, it doesn't. - **Cost accounting that survives the alias.** Token tracking has to read the actual events the provider streams back. A wrapper script doesn't intercept stdout at the right layer. - **Sandboxing.** A bashrc alias still runs the agent as you. A real orchestrator can wrap the exec in `bwrap` or `sandbox-exec` so the agent can't `rm -rf` outside the workspace it's supposed to touch. - **Remote sessions.** Running an agent on a dev box and accessing the PTY from your laptop is not a one-line shell script. It needs a daemon, a token, and a WebSocket. - **A web UI for people who want one.** Some teammates want a dashboard. The CLI-only crowd can ignore it; the dashboard crowd shouldn't have to fork. These are the features a *layer* gives you that a *script* doesn't. They're what we built Ringlet to provide. ## What an orchestrator doesn't give you It also doesn't: - Replace the agent. Claude Code is still Claude Code. Codex is still Codex. - Replace the provider. You still bring an Anthropic / OpenAI / wherever key. - Replace the gateway. If you use LiteLLM or OpenRouter as your routing layer, great — Ringlet sits above them and points the agent at them. - Replace the editor. Ringlet doesn't have opinions about Neovim vs Cursor. The "agent stack in 2026" is more layers, not fewer. Ringlet is the one that owns the agent CLI's environment. ## What this looks like in 18 months Predicting the agent ecosystem at any horizon longer than three months is hubris, but here's the rough shape we think 2027 looks like: - **Five to eight major coding agent CLIs**, none dominant. Anthropic, OpenAI, xAI, Google, plus two or three credible open-source ones (Factory's Droid is the front-runner, OpenCode is gaining). - **Three to four major model providers** in active use per developer. Anthropic + OpenAI is the floor; many developers will also use MiniMax or a self-hosted gateway. - **Per-project credentials as the norm.** The era of "one Anthropic key in your env" ends when teams start running multi-tenant projects with separate billing. - **Audit and cost as table stakes.** Compliance teams will require both. The agents themselves won't ship this; the orchestration layer will. - **A clear separation between the agent layer, the provider layer, and the orchestration layer.** Each can ship independently. That's where we think Ringlet is. The agent layer is the agent vendors' job. The provider layer is the model labs' job. The orchestration layer is open-source's job — like npm was, like git was, like Linux was. ## Try it If any of that resonates, the [install](/install) takes a minute. Start with two profiles on the same agent (work and personal Anthropic accounts, say) and see if the friction-you-didn't-realise-was-friction disappears. If it doesn't, file an issue. We'd rather not be one of five competing orchestrators in 2027. The pattern is the point — and we'll happily borrow the better bits from whoever does it best. --- ## What is Ringlet? The agent orchestrator for the multi-CLI era URL: https://ringlet.neullabs.com/blog/what-is-ringlet Published: 2026-05-12 Tags: ringlet, ai-agents, claude-code, codex, orchestration Cluster: cornerstone Ringlet is a Rust CLI that manages AI coding agents — Claude Code, Codex, Grok, Droid, OpenCode — behind isolated profiles, with provider switching and built-in cost tracking. What it does and who it's for. In 2026, most developers we know use more than one AI coding agent. Claude Code for refactors. Codex CLI for repo-wide migrations. Grok or OpenCode for a side project. Maybe Droid for something a teammate set up. Each of those agents wants to own a HOME directory. Each one has its own config file format, its own conversation history, its own usage log, and its own way of picking a provider. Run them all on the same laptop and the seams show up fast: keys leak between projects, you can't run two instances on different accounts, and nobody can tell you how much last month actually cost. Ringlet is the layer that sits above those agent CLIs and gives you a single unit — a *profile* — that captures everything they each want to manage independently. ## The five-minute version ```bash # Install curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh ringlet init # Create a couple of profiles ringlet profiles create claude work --provider anthropic ringlet profiles create claude personal --provider minimax ringlet profiles create codex staging --provider openai # Use them ringlet profiles run work # → claude with HOME=~/.ringlet/profiles/work ringlet profiles run personal # → claude with HOME=~/.ringlet/profiles/personal ringlet profiles run staging # → codex with HOME=~/.ringlet/profiles/staging # Cost across all of them ringlet usage ``` That's the whole product surface from a user's point of view. Everything else — sandboxing, hooks, the web dashboard, remote sessions — comes for free once you have the profile abstraction. ## What a profile actually does A Ringlet profile is a *(agent, provider, credentials, HOME)* tuple. When you run a profile: 1. Ringlet exports `HOME=~/.ringlet/profiles/` (the profile's own HOME directory) 2. Ringlet exports the right base URL and API key environment variables for the agent's wire format 3. Ringlet execs the agent binary The agent then reads what looks to it like a perfectly normal `~/.claude` (or `~/.codex`) directory — except it's a per-profile one. Two profiles never touch each other's files. Two profiles never share an MCP server registration. Two profiles never share a conversation history. The boundary is the same one the OS already uses for users. That's the whole core idea. The rest is leverage built on top. ## Why the HOME boundary matters You could try to isolate agents with environment variables alone: a wrapper script that re-exports `ANTHROPIC_API_KEY` per project. It works until the agent caches something. And modern coding agents cache a lot: - Project-level memory (e.g. `CLAUDE.md` and per-project context) - Tool approvals ("yes, you can run `git push` in this repo") - MCP server registrations - Cost ledgers - Conversation indexes All of these live in the HOME directory. The only durable way to isolate them is to give each profile its own. Ringlet does that automatically, populated with whatever the agent expects to find, and you stop fighting the agent's own caching layer. ## Provider switching, without the agent knowing Most coding agents have a default provider — Claude Code talks to Anthropic, Codex talks to OpenAI. But all of them respect a `*_BASE_URL` environment variable to point at a different gateway. Ringlet uses that. Want Claude Code to talk to MiniMax for cost-sensitive work? `--provider minimax`. The agent never knows. It thinks it's still talking to Anthropic — just at a URL Ringlet picked. Want Codex to go through Groq for speed? Same. Want all your tool-heavy traffic to land at Anthropic but small completions at OpenRouter? That's the [routing layer](/blog/routing-requests-by-cost), but it sits behind the same profile abstraction. The flip side: if you don't use a custom provider, Ringlet does nothing surprising. Defaults are the agent's own defaults. The orchestration layer is opt-in by feature. ## Cost tracking is the killer subfeature Every modern provider — Anthropic, OpenAI, MiniMax — streams token usage back inline with the response. The token count is a wire-protocol field, not a separate API call. Ringlet parses those events and writes them to a per-profile SQLite database. `ringlet usage` aggregates across profiles. ```text $ ringlet usage --since 2026-05-01 PROFILE AGENT PROVIDER TOKENS COST work claude anthropic 1.21M / 340K $8.41 personal claude minimax 2.83M / 820K $0.92 staging codex openai 0.45M / 90K $1.40 ───────────────────────────────────────────────────────── TOTAL $10.73 ``` This is the feature that pays for the install in a week. Engineering managers ask "what does our team's Claude spend look like" and get an answer that isn't "wait for the invoice." ## What Ringlet doesn't do Honesty section. Ringlet is **not**: - **An agent.** It doesn't generate code, doesn't have its own LLM, doesn't make tool calls. Your agent does that — Ringlet just lets you run a few of them cleanly. - **A model gateway.** [LiteLLM](/compare/litellm) and [OpenRouter](/compare/openrouter) sit between an application and a model API. Ringlet sits between a developer and an agent CLI. Different layer. Use them together if you want. - **A parallel task runner.** If you want five Claude Code agents working on five git worktrees, you want [Claude Squad](/compare/claude-squad) or [Conductor](/compare/conductor). You can run Claude Squad inside a Ringlet profile if you want both. - **A SaaS.** No hosted dashboard, no required cloud. Everything runs on your laptop or your own server. ## Who Ringlet is for Three concentric circles: 1. **Solo developers** with more than one project on more than one account. The shell-alias mess we [described in detail](/for-solo-devs) goes away on day one. 2. **Engineering teams** that have standardised on an AI coding agent and want every developer's workflow to look the same. The [team page](/for-teams) walks through that setup. 3. **Cost-sensitive operators** routing agents between Anthropic, MiniMax, OpenRouter, or their own gateway. The provider-switching feature is the entry point. If you only use one agent, on one account, with one provider, Ringlet adds little. Honest answer: stick with what you have. ## What ships at 0.1.0 The [0.1.0 release](/blog/announcing-ringlet-0-1) ships: - Profile isolation (separate HOME, credentials, history, config) - Multi-agent support (Claude Code, Codex CLI, Grok CLI, Droid CLI, OpenCode) - Provider switching (Anthropic, OpenAI, MiniMax, OpenRouter, OpenAI-compatible) - Cost tracking with SQLite ledger and CSV export - Event hooks (pre-tool-use, tool-use, stop, notify, cost-threshold) - Sandboxed remote terminal sessions (bwrap, sandbox-exec) - Web dashboard (Vue 3 + xterm.js) - Native desktop app (Tauri) - Keychain-backed credential storage - Rhai scripting for hook logic It's MIT-licensed. There is no paid tier yet. The team and enterprise tiers are on the roadmap; today you self-host everything. ## What to read next - [The agent stack in 2026](/blog/the-agent-stack-in-2026) — why one CLI per model isn't sustainable. - [Profile isolation explained](/blog/profile-isolation-explained) — what's actually inside `~/.ringlet/profiles/`. - [Sandboxing AI coding agents](/blog/sandboxing-ai-coding-agents) — the security story. - [Install Ringlet](/install) — 60-second setup. If you've already nodded at "yes, this is the problem I have" three times reading this, the install is one curl. Start with two profiles on the same agent, see if the friction you didn't realise was friction disappears, and decide from there. --- ## Announcing Ringlet 0.1.0 — one CLI for every coding agent URL: https://ringlet.neullabs.com/blog/announcing-ringlet-0-1 Published: 2026-05-07 Tags: announcement, release, ringlet Cluster: announcement First public release of Ringlet: the open-source orchestrator for AI coding agents. Profile isolation, provider switching, cost tracking, sandboxed remote sessions, web dashboard — all in one MIT Rust binary. Today we're publishing the first public release of [Ringlet](https://github.com/neul-labs/ringlet), the open-source orchestrator for AI coding agents. It's at 0.1.0 — the core feature set is real, tested, and in daily use; we expect to be at 1.0 by the end of the year. If you've been juggling Claude Code, Codex CLI, Grok CLI, Droid, or OpenCode across multiple projects and accounts, this is the tool we wish we'd had when we started doing the same. ## What it does, in one paragraph Ringlet manages the agent CLIs you already use. Each *profile* is a bundle of (agent + provider + credentials + HOME), and `ringlet profiles run ` launches the agent with the right environment. Two profiles never share state. Cost is tracked across all of them. The whole thing is a single Rust binary; everything else — web dashboard, Tauri app, remote sessions — is built on top of the same profile abstraction. For the why and the longer pitch, read [What is Ringlet](/blog/what-is-ringlet). ## What ships at 0.1.0 The feature surface is intentionally small. Each item below is documented, tested, and in daily use by us: - **Profile isolation.** Per-profile HOME, credentials, conversation history, and config. Two Claude Code profiles never see each other's `~/.claude/conversations` or MCP registrations. [Deep dive](/blog/profile-isolation-explained). - **Multi-agent support.** Five coding CLIs out of the box: Claude Code, Codex CLI, Grok CLI, Droid CLI, OpenCode. [Agents page](/agents). - **Provider switching.** Anthropic, OpenAI, MiniMax, OpenRouter, plus any OpenAI-compatible gateway via TOML. Bind any agent to any compatible provider — the agent's own config never changes. [Providers page](/providers). - **Cost tracking.** Token + dollar accounting in a per-profile SQLite ledger. `ringlet usage` aggregates across agents and providers. CSV export. [Cost tracking guide](/blog/cost-tracking-across-agents). - **Event hooks.** Shell or webhook on `pre-tool-use`, `tool-use`, `stop`, `notify`, `cost-threshold`, `daily-rollup`. [Hooks guide](/blog/event-hooks-and-webhooks). - **Sandboxed remote sessions.** Run the daemon on a dev box; access the agent's PTY from your laptop's browser. Sessions exec inside `bwrap` (Linux) or `sandbox-exec` (macOS). [Remote sessions](/blog/remote-terminal-sessions), [Sandboxing](/blog/sandboxing-ai-coding-agents). - **Web dashboard.** Vue 3 + xterm.js, served by the daemon at `http://127.0.0.1:8765`. Bearer-token auth, localhost-only by default. - **Desktop app.** Tauri-packaged build of the dashboard. Same UI, no browser tab. - **Keychain-backed credentials.** macOS Keychain, GNOME Keyring / KWallet, Windows Credential Manager. File-backed fallback for headless systems. [Keychain article](/blog/keychain-credential-storage). - **Rhai scripting.** Hooks can be inline shell, file paths, or Rhai scripts. - **Registry sync.** GitHub-hosted JSON index of supported agents and providers. Add new ones without a Ringlet upgrade. - **Import existing logs.** `ringlet import claude` (and `codex`, `opencode`) backfills usage history. ## Why now, why this shape We've spent the last six months running every major coding agent CLI we could find. The thing none of them solved was *what happens when you have more than one*. The first weekend with three of them installed, the dotdirectories started fighting and the cost dashboards in each lab's portal stopped matching. The repeated answer we kept giving ourselves — a per-project `direnv`, a wrapper script, a fresh terminal tab — was a band-aid on what's clearly a missing layer. So we built the layer. [The agent stack in 2026](/blog/the-agent-stack-in-2026) is the long-form version of that argument. The single-Rust-binary shape is intentional. We want this to feel like `git` or `jq` — a small dependency that does one job and gets out of the way. No daemon you need to operate (the daemon is optional, used only for the web UI and remote sessions). No service to keep alive. No SaaS to subscribe to. ## What's missing - **Team-tier features.** Shared profiles synced across teammates, centralised provider policy, role-based access, signed audit logs. These need a server side. We're prioritising based on early-access requests at [teams@neullabs.com](mailto:teams@neullabs.com). - **Windows.** The credential-store code works on Windows. Remote PTY needs more polish. macOS and Linux are first-class today. - **More agents.** Aider, Goose, Gemini CLI — the registry shape supports them, we just haven't bundled them yet. PRs welcome. - **More providers.** Same. We ship the four we use; OpenAI-compatible covers most of the rest. - **A formal plugin SDK.** Rhai scripting gets you most of the way for hooks; a real SDK is on the roadmap. ## Try it ```bash curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh ringlet init ``` [Full install guide](/install) walks through the first two profiles. ## Where we'd love feedback - The provider TOML schema is going to need extending; if your favourite gateway doesn't fit cleanly, file an issue. - Cost estimation accuracy. The price table is good but not perfect for non-Anthropic / non-OpenAI providers. If you see drift > 5% versus invoice, please send the data. - Naming. We've gone back and forth on "profiles" vs "workspaces" vs "contexts." Profile won. If you think it shouldn't have, the issue tracker exists. - The team-tier shape. We don't want to ship a hosted dashboard for the sake of it. If you have a team and Ringlet is in your stack, [email us](mailto:teams@neullabs.com) and tell us what's missing. ## Thanks To the dozen-plus engineers who tried 0.0.x and broke it in helpful ways. To the lab teams whose CLIs Ringlet wraps — none of this would matter if the underlying agents weren't great. To the open-source crates we lean on (`keyring`, `axum`, `tokio`, `bwrap`, `nng`, `xterm.js`, `tauri`, `astro`, and many more). Ringlet is MIT-licensed. [github.com/neul-labs/ringlet](https://github.com/neul-labs/ringlet). Pull requests welcome. --- # Comparisons ## vs Claude Squad URL: https://ringlet.neullabs.com/compare/claude-squad Published: 2026-05-22 Side-by-side: Ringlet's long-lived isolated profiles vs Claude Squad's parallel git-worktree task runner. They're not competitors — most teams want both. Here's when each is right, and how to compose them. When to pick Ringlet: - You work on a few projects serially and want long-lived agent context that survives across days. - You need credential isolation across multiple accounts at the same provider (e.g. two Anthropic keys). - You want cross-agent cost tracking, not just per-task. - You need to bind one agent to multiple providers (Claude Code → Anthropic for work, MiniMax for personal). - You want sandboxed remote terminal sessions accessible from a browser. - You want to layer Claude Squad on top — Ringlet handles credentials and isolation, Squad does parallel runs. When to pick Claude Squad: - You have a backlog of independent, well-specified tasks and want to dispatch them in parallel. - You want one agent per git worktree with automatic worktree management. - You don't need provider switching or long-lived per-project context. - You're OK reviewing many diffs at once and merging selectively. [Claude Squad](https://github.com/smtg-ai/claude-squad) is a terminal-app that lets you run multiple coding agents (Claude Code, Codex, Aider, etc.) in parallel on git worktrees. It's the right tool when you have a stack of well-specified tasks and want them all making progress at once. [Ringlet](/) is an orchestrator for long-lived agent profiles. It's the right tool when you have a small number of projects, each with their own credentials, providers, and context that should persist across days. These aren't competing tools — they sit at different layers in the stack. This page explains where each fits and how to use both together. ## The mental model Think of Claude Squad as a *parallel task runner* — like make's `-j8` flag for AI coding agents. You give it a list of independent tasks, it spawns one agent per task on a fresh git worktree, you come back to N diffs. Think of Ringlet as the *agent environment manager* — like `nvm` or `rustup` but for AI agent CLIs. You give it a profile (credentials + provider + workspace), it launches the agent with the right environment, the agent's history persists across sessions. The two questions they answer: - **Claude Squad:** "What's currently running?" - **Ringlet:** "Which context is this agent in?" ## Where they overlap, where they don't Both can launch a coding agent. Both can manage multiple agents on the same machine. The overlap stops there. | | Claude Squad | Ringlet | |---|---|---| | Parallel agent execution | ✓ (git worktrees) | ✗ (one at a time per profile) | | Long-lived context per project | ✗ (worktrees are per-task) | ✓ (profiles persist) | | Per-profile credentials in keychain | ✗ (shell env) | ✓ (Keychain / Secret Service) | | Provider switching per profile | ✗ | ✓ (Anthropic, OpenAI, MiniMax, OpenRouter) | | Cross-agent cost tracking | ✗ | ✓ (SQLite ledger across all profiles) | | Event hooks (audit / Slack / guards) | ✗ | ✓ | | Sandboxed remote PTY (browser access) | ✗ | ✓ | | Web dashboard | ✗ (TUI) | ✓ (Vue 3 + xterm.js) | | Single binary, no extra services | ✓ | ✓ | ## Using them together The setup we recommend for engineers who use both patterns: ```bash # Install both curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh brew install smtg-ai/tap/claude-squad # Create a Ringlet profile per long-lived project ringlet profiles create claude acme-api --provider anthropic --key-alias acme ringlet profiles create claude personal --provider minimax --key-alias personal # For serial work in acme-api: regular Ringlet ringlet profiles run acme-api # For a parallel-task burst within acme-api: Claude Squad inside the profile ringlet profiles run acme-api -- claude-squad ``` When you run `claude-squad` inside a Ringlet profile, Squad inherits the profile's environment — including the correctly-scoped `ANTHROPIC_API_KEY` and any provider routing. Each Squad-spawned agent on each git worktree uses the profile's credentials. Cost lands on the right account. The agent's own state (history, project memory) is shared because they're all inside the same profile HOME — usually what you want for "five parallel tasks in one project." ## When to skip Ringlet entirely If your day looks like this — one Anthropic account, one provider, no per-project memory you care about preserving, just "give me a parallel task runner" — Claude Squad alone is enough. Ringlet's value is in the cases where you have more than one of these things to keep separate. ## When to skip Claude Squad entirely If your day looks like this — work on one project at a time, the agent remembers what we discussed yesterday, parallelism isn't a feature you've ever wanted — Ringlet alone is enough. Claude Squad's value is in dispatching many tasks at once. ## What we ended up doing Most of us at Neul Labs run Ringlet as the always-on layer, and reach for Claude Squad maybe once a week when we have a batch of small tasks. The Ringlet profiles outlive any individual task; Claude Squad sessions are bursty. If we were starting fresh today and could only install one, we'd install Ringlet first — credential isolation across accounts is a daily pain point. The parallel-task pattern is a "once a week" pattern for us. Yours may differ. ## Honest things to know Claude Squad is a better choice than Ringlet for parallel task running. We don't try to compete on that surface and we don't plan to. If you have a workflow that's mostly "five tasks at once," install Claude Squad and don't worry about Ringlet until you also need credential isolation. Conversely, Claude Squad doesn't replace Ringlet's credential / provider / cost layer. Those features are out of scope for what Squad is. The recommendation to run them together isn't backhanded — it genuinely is the cleanest setup if you want both. --- ## vs Conductor URL: https://ringlet.neullabs.com/compare/conductor Published: 2026-05-22 Side-by-side: Ringlet's cross-platform CLI + daemon vs Conductor's Mac-only visual orchestrator for parallel Claude Code and Codex. When each is right, and how their primary audiences differ. When to pick Ringlet: - You work on Linux as well as macOS, or want a single workflow across both. - You want a CLI-first workflow where the visual dashboard is optional. - You need long-lived per-project context, not per-task worktrees. - Cost tracking and provider switching matter to you. - You want to bind one agent to multiple providers (not just multiple agents to their defaults). - You want event hooks, audit logs, or sandboxed remote sessions. When to pick Conductor: - You're macOS-only and prefer a polished Mac app over a CLI. - Your workflow is dispatch many parallel tasks; review diffs visually; merge. - You're happy with Conductor's default provider bindings (Anthropic for Claude, OpenAI for Codex). - Per-project credential isolation isn't a daily problem for you. [Conductor](https://conductor.build) is a Mac app for running parallel AI coding agents — Claude Code and Codex — on git worktrees. It's polished, native, and a good choice if you're on macOS and want a visual orchestrator. [Ringlet](/) is a cross-platform CLI orchestrator with an optional web dashboard. It's a better choice if you want long-lived per-project context, cost tracking, or you work on Linux. These tools sit at different points on two axes: macOS-only vs cross-platform, and parallel-task vs long-lived-context. ## The mental model Conductor optimises for a specific workflow: a senior developer with a backlog of independent tasks, working on macOS, who wants to dispatch them to parallel agents and review diffs visually. Its mechanic is git worktrees, like Claude Squad — but with a visual dashboard instead of a TUI. Ringlet optimises for a different workflow: a developer (on any platform) managing multiple long-lived project contexts, each with their own credentials, providers, and accumulated agent memory. The mechanic is HOME isolation per profile; parallelism is intentionally not the headline. ## Where the audiences split | Question | Conductor | Ringlet | |---|---|---| | Platform | macOS only | macOS, Linux (x86_64, aarch64) | | Primary interface | Native Mac app | CLI + optional dashboard | | Primary unit of work | Parallel task | Long-lived profile | | Provider switching per agent | No (agent defaults) | Yes (any agent → any provider) | | Cross-agent cost tracking | Per-task | Cross-profile SQLite ledger | | Keychain-backed credentials | Inherits env | Keychain / Secret Service / WinCred | | Sandboxed exec | Worktree-only | bwrap (Linux) / sandbox-exec (macOS) | | Remote PTY sessions | No | Yes, with web UI | | Event hooks | No | Yes (pre-tool-use, tool-use, cost-threshold, …) | ## What Conductor does better than Ringlet - **Visual polish on Mac.** The Mac app feels like a native Mac app. Ringlet's web dashboard is functional; it's not the same polish. - **Diff-review UI.** Conductor's diff-review experience is purpose-built and good. Ringlet doesn't have an equivalent. - **Discoverability.** Conductor is one app to launch; everything is on screen. Ringlet's CLI surface is more powerful but takes more learning. ## What Ringlet does better than Conductor - **Cross-platform.** Linux is first-class. Many engineering teams have at least one Linux dev box; Ringlet works there. - **Cost transparency.** `ringlet usage` aggregates across every agent and every provider. Conductor tracks per-task; cross-task accounting is something you'd build separately. - **Provider switching.** Point Claude Code at MiniMax, OpenRouter, or a self-hosted gateway with one TOML stanza. Conductor uses the agent's default provider. - **Long-lived context.** A Conductor task is a git worktree — it ends when the task ends. A Ringlet profile is a long-running context that persists across sessions. - **Remote terminal sessions.** Run agents on a server, access the PTY from a browser. Useful for big indexing jobs or for working from an iPad. - **Hooks and audit.** Pre-tool-use guards, audit logs, Slack alerts — the building blocks for team-level guardrails. ## Using them together Less obvious than the Ringlet + Claude Squad combo, but workable. Conductor as the visual layer over Claude Code and Codex on macOS; Ringlet for cost tracking and credential isolation when you're not in Conductor. Pragmatically, most people pick one or the other. If you're macOS-only and your workflow really is "parallel tasks, review diffs," Conductor is excellent and you won't miss what Ringlet adds. If you've ever found yourself wishing you could pin Claude Code to a different provider for one project or you've watched a Linux teammate hit "you must be on macOS," Ringlet is the answer. ## What we ended up doing Internally at Neul Labs we use Ringlet. The CLI fits how we already work, Linux support is non-negotiable for us, and the cost-tracking + provider-switching combo is what we built Ringlet to solve. We respect Conductor a lot — it's polished software for a real audience — but it's not our audience. If you're picking between them and your primary workflow is "I want to run five Claude Code agents in parallel on git worktrees, on my Mac," install Conductor. If you're picking between them and your primary workflow is "I need clean isolation across multiple projects/accounts, on whatever OS I happen to be on," install Ringlet. ## Honest things to know Conductor is reportedly working on a richer feature set (audit, cost, teams). When that ships, the comparison page will need updating. We'll keep this honest as their roadmap moves. Ringlet's web dashboard is the area we'd most like to invest in visual polish next. If the CLI is the right primitive but the dashboard is what gets your team to adopt it, file an issue — that's the kind of feedback that moves our priority list. --- ## vs OpenRouter URL: https://ringlet.neullabs.com/compare/openrouter Published: 2026-05-22 Side-by-side: Ringlet's open-source agent CLI orchestrator vs OpenRouter's hosted model marketplace. They live at different layers and complement each other — OpenRouter is a great Ringlet provider. When to pick Ringlet: - You're orchestrating agent CLIs and need profile isolation, per-profile credentials, and cross-agent cost tracking on your own laptop. - You want a self-hosted, single-binary tool with no SaaS dependency. - You want to use OpenRouter as one of many backends — Ringlet supports it as a first-party provider. - You want sandboxed remote sessions, hooks, or a local web dashboard. When to pick OpenRouter: - You want one API key that gets you access to 300+ hosted models across providers. - You're fine with model calls being routed through a third-party hosted service. - You want a hosted spend dashboard out of the box, no local setup. - You want prompt caching across providers without writing your own router. [OpenRouter](https://openrouter.ai) is a hosted marketplace that gives developers a single API key for 300+ language models across many providers. It's the easiest way to try a model you haven't paid for, and the easiest way to fall back between models when one is rate-limited. [Ringlet](/) is an orchestrator for AI coding agent CLIs. It manages the agent's environment — credentials, HOME directory, provider binding — and tracks cost across all of them on the developer's machine. These tools live in different layers. Ringlet routinely *uses* OpenRouter as one of its supported providers. The "vs" framing is a search-engine artefact; the honest framing is "Ringlet + OpenRouter." ## The two layers OpenRouter sits between an LLM-using application and the actual model providers. You point at `openrouter.ai/api/v1`, send an OpenAI-shaped request, and OpenRouter forwards to Anthropic / OpenAI / Google / Mistral / Together / etc. depending on the model slug. Ringlet sits between a developer and an agent CLI. You run `ringlet profiles run my-profile`, Ringlet sets up the right HOME and env vars, launches Claude Code (or Codex, or Grok), and the agent talks to whatever provider URL the profile names — which could be Anthropic directly, or OpenRouter. ## Where they sit in the stack ```text ringlet profiles run my-profile ← Ringlet ↓ claude ← the agent CLI ↓ HTTPS to api.openrouter.ai/v1 ← OpenRouter ↓ Anthropic / OpenAI / etc. ← upstream model provider ``` Ringlet manages the top half. OpenRouter manages the bottom half. They don't overlap. ## What OpenRouter does that Ringlet doesn't - **One key, 300+ models.** Anthropic, OpenAI, Google, Meta, Mistral, xAI, Cohere, Together — all behind one API key, one URL, one billing relationship. - **Hosted model marketplace.** No setup, no install. Sign up, copy the key, point your code at it. - **Cross-provider prompt caching.** OpenRouter caches responses across providers in some cases. - **Fallback chains.** If one provider is down, OpenRouter can route to another model that fits the request. - **A central spend dashboard.** No local setup; the dashboard is on openrouter.ai. ## What Ringlet does that OpenRouter doesn't - **Manages the agent CLI itself.** OpenRouter is a model API; it doesn't know anything about Claude Code or Codex or your project's HOME directory. - **Per-developer credential isolation in the OS keychain.** OpenRouter stores your account credentials on its servers; Ringlet stores your provider keys (whether direct or OpenRouter ones) in the local keychain. - **Cross-agent cost tracking on your own machine.** OpenRouter shows you spend for traffic that went through OpenRouter; Ringlet shows total spend across every agent and every provider, locally. - **Self-hosted, no SaaS dependency.** Ringlet runs entirely on your machine. OpenRouter is a hosted service. - **Sandboxed remote agent sessions, hooks, audit.** Out of scope for OpenRouter; core for Ringlet. ## When to use each You almost certainly want **OpenRouter** if: - You want to try many models without managing accounts at each provider. - You're building an application and want one API key in your code. - You're OK with your LLM traffic going through a third-party hosted service. - You want hosted spend visibility for OpenRouter-routed traffic. You almost certainly want **Ringlet** if: - You're a developer running coding agent CLIs and need to isolate them per project. - You want cost tracking at the developer/profile level, including agents that don't go through OpenRouter. - You want sandboxing, audit hooks, or remote browser-accessible sessions. - You'd rather have local-first tooling. ## Using them together This is the recommended setup if you want both: ```bash # Get an OpenRouter key, set it in Ringlet's keychain: ringlet keychain set openrouter default # Create a profile that uses OpenRouter as the provider for Claude Code: ringlet profiles create claude fast \ --provider openrouter \ --model anthropic/claude-3.5-haiku ringlet profiles run fast # claude launches with: # ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1 # ANTHROPIC_API_KEY= # ANTHROPIC_MODEL=anthropic/claude-3.5-haiku ``` Ringlet does all its normal things — isolated HOME, cost tracking, hooks — and Claude Code talks to OpenRouter as if it's the Anthropic API. You get OpenRouter's breadth and Ringlet's depth in the same workflow. ## Honest comparison of cost tracking This is where the layers slightly overlap. Both Ringlet and OpenRouter track spend. - **Ringlet's tracking is local, by profile, across all providers.** Whether a profile uses Anthropic directly, OpenRouter, MiniMax, or a self-hosted gateway, Ringlet writes the same per-request ledger. - **OpenRouter's tracking is hosted, by virtual key, only for OpenRouter traffic.** Direct Anthropic traffic is invisible to OpenRouter. For "what's this engineering team spending," Ringlet's view is more complete because it includes spend that bypasses OpenRouter. For "what's this OpenRouter key spending," OpenRouter is more authoritative because it's the source of truth. ## Conclusion Don't choose. They're complementary: - OpenRouter is a great cost-and-breadth backend for some profiles (especially "try this new model" or "fall back to cheaper"). - Ringlet is the right orchestration layer for agent CLIs on a developer's machine. If you want one piece of advice: install both. Use Ringlet as the daily driver. Add OpenRouter as one of your providers when you want to try a model you don't have a direct account for, or when you want a single key for many models. ## Honest things to know OpenRouter is hosted. If you're at a company with strict data-residency or non-third-party requirements, OpenRouter may not be an option — direct provider accounts via Ringlet probably is. Conversely, Ringlet doesn't solve the "one key for 300+ models" problem. That's exactly what OpenRouter exists for. We don't try to compete on that surface. --- ## vs Tide Commander URL: https://ringlet.neullabs.com/compare/tide-commander Published: 2026-05-22 Side-by-side: Ringlet's straightforward CLI + dashboard vs Tide Commander's game-like 3D orchestrator where agents appear as 3D characters. Different aesthetics, different audiences. When to pick Ringlet: - You want a no-nonsense CLI orchestrator that fits a normal developer workflow. - You're managing long-lived projects with cost tracking and provider switching. - You want a tool whose UI is functional rather than visually elaborate. - You need cross-platform support (macOS + Linux). When to pick Tide Commander: - You enjoy a visually rich, game-like interface for managing parallel agents. - You want to see agents as 3D characters and "command" them by clicking. - Your workflow is many parallel tasks and you want a fun way to track them. - You're comfortable with an experimental tool that's still finding its shape. [Tide Commander](https://github.com/tide-commander/tide-commander) is one of the more imaginative entries in the agent-orchestrator space — a free, open-source visual orchestrator that renders your AI agents as 3D characters on a battlefield. You click on them to assign tasks, watch them work, and pull them back when done. [Ringlet](/) takes a more conventional approach: a CLI and an optional dashboard, with profiles as the unit of organisation. Where Tide leans into the game metaphor, Ringlet leans into the Unix-tool metaphor. This page is short because the two are aesthetically different enough that the choice is usually clear within minutes of looking at either. ## Where they overlap Both can: - Launch and manage multiple AI coding agents (Claude Code, Codex). - Provide visual feedback on what each agent is doing. - Show progress and surface results when tasks complete. - Run locally without a hosted backend. ## Where they diverge | | Tide Commander | Ringlet | |---|---|---| | Primary metaphor | 3D characters on a battlefield | Profiles in a Unix CLI | | Parallelism | Yes, central feature | Per-profile (one agent at a time) | | Long-lived profile context | No (per-task) | Yes (persists across days) | | Provider switching | No | Yes (Anthropic, OpenAI, MiniMax, OpenRouter, …) | | Cost tracking across agents | No | Yes (SQLite ledger, CSV export) | | Hooks / audit | No | Yes | | Sandboxing | No | Yes (bwrap, sandbox-exec) | | Cross-platform | macOS, Linux, Windows | macOS, Linux | ## Honest take Tide Commander is fun. It's an interesting design experiment, and the 3D visualisation makes it easy to see at a glance what's happening with five agents at once. If the aesthetics resonate, you'll probably enjoy using it more than a CLI. Ringlet is built for daily-driver use. The dashboard is functional rather than playful; the CLI is the primary interface. If you spend hours a day in a terminal, the cognitive overhead of "now I'm in a 3D game" matters. These are different products serving different relationships with the work. Pick the one that fits how you want to feel about your tooling. There's no wrong answer; there's just whether the metaphor matches your daily experience. ## Combining them In principle you could run Tide Commander inside a Ringlet shell to get profile-level credential isolation around its agents. We haven't tested this combination extensively — file an issue if you do and we'll document the results. ## What we ended up doing We installed Tide Commander, played with it for an afternoon, and uninstalled it — not because it doesn't work, but because the game metaphor isn't how we want to relate to a coding session. Your daily work probably won't be ours; if the demo makes you smile, give it a real try. For everything else — the boring-but-load-bearing parts of running AI coding agents — Ringlet is the tool we use. --- ## vs LiteLLM URL: https://ringlet.neullabs.com/compare/litellm Published: 2026-05-22 Side-by-side: Ringlet's agent CLI orchestrator vs LiteLLM's LLM gateway / proxy. They sit at different layers in the stack — most teams that need both can run them together rather than choose. When to pick Ringlet: - You're managing AI coding agent CLIs (Claude Code, Codex, …) and need profile isolation, credential management, and cost tracking at the agent level. - You want one tool to coordinate the agent's environment without running another service. - You want sandboxed remote sessions, event hooks, or a web dashboard for agent management. - You'd rather have a single Rust binary than a Python/Docker-deployed gateway. When to pick LiteLLM: - You're building an application that calls an LLM API directly and need a routing/budget layer in front of multiple providers. - You want a unified OpenAI-style API in front of 100+ providers. - You have an organisation that needs per-team budgets, multi-tenant key management, and rich routing rules. - You're already running a Python or Docker-based stack and want a gateway that fits there. This is the comparison page we expected to be most controversial and ended up being the least. [LiteLLM](https://github.com/BerriAI/litellm) and Ringlet are different tools at different layers of the stack. They genuinely aren't alternatives. The shortest version: LiteLLM sits *between an application and a model API*. Ringlet sits *between a developer and an agent CLI*. Different layer, different problem, different audience. This page exists because people search "Ringlet vs LiteLLM" enough that we should clearly answer "they're complementary, here's how." ## The two layers ```text ┌──────────────────────────────────────────────────────┐ │ Developer (you) │ │ ↓ │ │ ringlet profiles run work ← Ringlet's layer │ │ ↓ │ │ claude (or codex, grok, ...) ← the agent CLI │ │ ↓ │ │ HTTPS to provider ← optional gateway │ │ ↓ │ │ LiteLLM proxy / OpenRouter ← LiteLLM's layer │ │ ↓ │ │ Anthropic / OpenAI / etc. ← the model API │ └──────────────────────────────────────────────────────┘ ``` Ringlet's layer answers: "Which agent should run, with which credentials, against which provider URL, in which isolated HOME?" LiteLLM's layer answers: "When this HTTPS request to the LLM hits me, which actual provider should I route it to, with what budget, with what retries?" You can absolutely run both: Ringlet launches Claude Code with `ANTHROPIC_BASE_URL=http://your-litellm.internal/anthropic`, and LiteLLM does the routing. ## What LiteLLM does that Ringlet doesn't - **Unified OpenAI-style API in front of 100+ providers.** Useful for application code that wants one API. - **Production-grade routing rules.** Budgets per virtual key, retries, fallback chains, model aliasing. - **Multi-tenant key management.** Each team / project gets a virtual key that maps to upstream credentials. Useful for organisations. - **Spend dashboards across teams.** A central place to see usage by virtual key. - **Self-hostable as a service.** Docker, Helm chart, Kubernetes operator. ## What Ringlet does that LiteLLM doesn't - **Manages the agent CLI itself.** Profiles, HOME isolation, agent-to-provider binding. LiteLLM doesn't care which CLI is calling it; Ringlet's whole point is the CLI. - **Per-developer credential isolation in the OS keychain.** LiteLLM holds credentials server-side; Ringlet holds them in the developer's keychain. - **Sandboxed agent exec.** bwrap / sandbox-exec wrap for remote sessions. LiteLLM has no opinion on how the calling agent runs. - **Hooks on agent lifecycle.** Pre-tool-use, tool-use, stop. These are events on the *agent*, not the LLM call. - **Remote terminal sessions.** Browser-accessible PTY for running agents on a dev box. Completely outside LiteLLM's scope. - **Single binary install.** No Python, no Docker, no daemon to operate (unless you want the web dashboard). ## When to install which You almost certainly want **LiteLLM** if: - You're building an application (not running AI agent CLIs). - You need per-team budgets, audit, and multi-tenant key management at the LLM-call level. - You already run a Python/Docker stack and another service fits naturally. - You want a polished spend dashboard out of the box. You almost certainly want **Ringlet** if: - You're a developer (or a team of developers) running coding agent CLIs. - You need to isolate credentials and HOME across projects on your laptop. - You want cost tracking at the agent / developer / project level, not the API-call level. - You want a small Rust binary, not a service to operate. If you want both: - Run LiteLLM as your organisation's LLM gateway. - Add LiteLLM as a custom OpenAI-shaped (or Anthropic-shaped) provider in Ringlet: ```toml # ~/.config/ringlet/providers/litellm.toml name = "litellm" shape = "openai" endpoint = "https://litellm.yourco.com/v1" key-env = "LITELLM_API_KEY" ``` - Profiles bound to the `litellm` provider get LiteLLM's routing, budgets, and central spend visibility — plus all of Ringlet's per-developer isolation. ## What we ended up doing We don't run LiteLLM at Neul Labs because we don't have organisation-level LLM call governance to worry about. Each engineer manages their own profiles. If we grew to a size where central LLM-call governance mattered, LiteLLM is the tool we'd reach for, and we'd point Ringlet at it. If you're at that size already, install LiteLLM first and point Ringlet at it on day one. The two don't fight; they slot together. ## Honest things to know LiteLLM has been actively developed for longer than Ringlet and is more mature for the routing / proxy / gateway shape of the problem. We have no plans to compete with LiteLLM's routing surface; Ringlet's optional ultrallm proxy is intentionally minimal. Conversely, LiteLLM isn't going to manage your `~/.claude` directory or sandbox your remote PTY. Those are Ringlet's job. The combination is genuinely the right setup for medium-sized teams.