# carlos > Pure-Go TUI agent. Single binary around 30 MB. Local-first, multi-provider, with autonomous skill induction and first-class sub-agent supervision. Carlos is a CLI tool that runs in your terminal. State persists to `~/.carlos/`. It speaks to Anthropic, OpenAI, OpenRouter, Ollama, and Gemini using the Anthropic tool-use schema as the canonical shape; adapters normalize the others. The event log is SQLite (FTS5 over markdown summaries for memory); the trusted-workspaces store, schedules, and user prefs are flat files. No CGO, cross-compiled for darwin + linux on amd64 + arm64. Two distinguishing bets. First, skill induction: carlos watches transcripts and proposes reusable skills, which the user reviews before they enter the library. Second, sub-agent supervision: delegated agents appear in a live manage view with intent, tool calls, progress, diffs, and spend, and the user can join, redirect, or stop any of them mid-flight. ## Frames + modes Sessions run inside a frame (personal + N user-defined: work, research, side projects, etc.). Each frame carries its own glyph, accent from an 8-name palette, provider + model + `provider_override` (per-frame billing keys + base URLs that shadow the shared pantry), vault subtree, `system_prompt_append`, cwd_hints (auto-pick at fresh launch), capabilities, MCP-server gating (top-level `mcp_servers:` list, each entry's optional `frames:` field restricts to a subset), and orchestrator `mode` (solo / tight / orchestrator). Per-frame on-disk layout under `~/.carlos/frames//{research,usershell,worktrees,digest}/`. Cross-frame READ is free; cross-frame WRITE prompts with a distinct audit reason. Ctrl+F opens the full-screen 3×2 takeover switcher; `/frame switch ` swaps the live provider/model without losing the transcript and refreshes the in-process FrameUI (mode, capabilities, glyph, accent). `/mode ` flips the orchestrator mode, which gates the supervisor's spawn cap (solo=0, tight=1, orchestrator=5) and changes the per-mode line in the system prompt's Frame block. solo tells the model to do the work itself; tight is single-task focus; orchestrator tells the model to delegate by default for anything beyond a trivial single-line edit and never pause for per-turn confirmation. `NewPersonal` defaults to orchestrator. When sub-agents are live and the chat width is >= 120 cols, the transcript splits horizontally with a right-side roster. `/whoami` echoes the current frame + mode + provider + model. ## Canonical references - [README.md](https://github.com/georgebuilds/carlos/blob/main/README.md): user-facing intro, install, layout - [Releases](https://github.com/georgebuilds/carlos/releases): version history and signed tarballs - [Issues](https://github.com/georgebuilds/carlos/issues): open work and discussion ## Tool surface (31 registered by default) - Filesystem read: `read`, `grep`, `glob`. Sandboxed by `BaseDir` when running inside a worktree. - Filesystem mutate: `write`, `edit`. Always prompt unless the session cached "Always". - Shell: `bash`. Non-PTY by default. - Git read-only: `git_status`, `git_diff`, `git_log`, `git_blame`, `git_show`. - Web: `web_fetch` (HTML to text, configurable UA and robots policy), `web_search` (Brave, SearXNG, or DuckDuckGo HTML fallback), `http_request` (method-parametric for REST, GraphQL, webhooks), `code_search` (concurrent fan-out to Codewiki + Context7 + DeepWiki; defaults to carlos's own repo for self-reference). - Introspection: `carlos_about` returns carlos's own state: vault path, active frame, all configured frames, capabilities, providers, user name. Auto-approved; never leaks API keys. - Notes, pinned to the configured Obsidian vault: `notes_get`, `notes_search`, `notes_backlinks`, `notes_tagged`, `notes_neighbors`, `notes_recent`, `notes_resolve`, `notes_write`. Schema does not accept `vault:`. Auto-approved by layer 1 of the permission model. `notes_write` is scoped to the active frame's `vault_subtree`; cross-vault or cross-subtree writes are rejected and have to go through the generic `write` tool. - Notes, arbitrary vault: `obsidian_get`, `obsidian_search`, `obsidian_backlinks`, `obsidian_tagged`, `obsidian_neighbors`, `obsidian_recent`, `obsidian_resolve`. Schema requires `vault:`. Always prompts. ## Permission model Three layers, evaluated in order, in `internal/agent/policy.go`: 1. Built-in allowlist. Hardcoded read-only tools (the configured-vault notes_*, plus `read`, `grep`, `glob`, `ls`, and the five `git_*` inspection tools). Auto-approved without inspecting input. Trust anchor is the configuration boundary set during onboarding. 2. Workspace trust. When the current working directory is in `~/.carlos/trusted-workspaces.json`, a curated set of read-only bash verbs auto-approves: `ls`, `pwd`, `cat`, `head`, `tail`, `wc`, `file`, `which`, `echo`, and the read-only `git` subcommands (`status`, `diff`, `log`, `show`, `blame`, `branch`, `ls-files`, `ls-tree`, `rev-parse`, `describe`, `remote`, read-form `config`). Build, test, and install tools (cargo, npm, yarn, pnpm, go test, go build, make, cmake, bazel, ninja) are explicitly excluded. Mutating filesystem verbs are excluded. Shell metacharacters anywhere in the command disqualify the whole call. Implementation lives in `internal/workspace`. 3. Session fallback. The bubbletea TUI overlay prompts the user; their answer can cache as a session "Always" entry. Every decision is captured by an optional `AuditSink`. Reasons: `ReasonBuiltinAllow`, `ReasonWorkspaceAllow`, `ReasonSessionAllow`, `ReasonSessionDeny`, `ReasonCrossFrameAllow`, `ReasonCrossFrameDeny`. The Phase F-12 cross-frame detector forces the prompt path on write/edit inputs whose path lands in a non-active frame's subtree. A first-launch trust prompt surfaces in the overlay slot when the cwd contains a project marker (.git, go.mod, package.json, etc.) and isn't already trusted. Y persists via the same store the /trust slash uses; N/Esc dismiss for the session. Identity hardening: provider clients run every EventError through `providers.ScrubModelName` so "I am Gemini" / "I am Claude" reveals become "I am carlos". `cmd/carlos.scrubProviderName` runs the same scrub at the stderr boundary. A regression test pins the system prompt against user-injection attempts. ## Slash commands Owned by `internal/tui/slash`. Typing `/` opens an inline autocomplete: ghost text on the input row for the best match plus a hint band with chip palette, description, and keybinds. Mirrored from Claude Code: `/clear`, `/help`, `/exit` (alias `/quit`, `/q`), `/compact`, `/model`, `/review`. Carlos-specific: `/insights`, `/skills`, `/memory`, `/schedule`, `/daemon`, `/agents`, `/research`, `/resume`, `/shell`, `/jobs`, `/fg`, `/bg`, `/trust`, `/untrust`, `/trusts`, `/permissions`, `/frame`, `/mode`, `/capabilities`, `/whoami`, `/mcp`. CLI verbs adjacent: `carlos gateway test `, `carlos gateway add`, `carlos onboard --only `, `carlos memory search -f `. ## Subsystems - [internal/agent](https://github.com/georgebuilds/carlos/tree/main/internal/agent): tool-use loop, event log, supervision, layered approval policy (cross-frame detector + mode-aware spawn cap) - [internal/frame](https://github.com/georgebuilds/carlos/tree/main/internal/frame): Frame + Config + Policy, accent palette, paths, migration, modes, render helpers - [internal/tools](https://github.com/georgebuilds/carlos/tree/main/internal/tools): every registered tool (`NewDefaultRegistryWithBaseDirAndFrames` is the canonical constructor) - [internal/mcp](https://github.com/georgebuilds/carlos/tree/main/internal/mcp): Model Context Protocol client; stdio servers register their tools under `__` and inherit the standard approval path - [internal/providers](https://github.com/georgebuilds/carlos/tree/main/internal/providers): anthropic, openai, openrouter, ollama, gemini, oacompat - [internal/notes](https://github.com/georgebuilds/carlos/tree/main/internal/notes): Obsidian vault index and cache (Goldmark plus miniyaml) - [internal/workspace](https://github.com/georgebuilds/carlos/tree/main/internal/workspace): trusted-workspaces store and read-only bash classifier - [internal/research](https://github.com/georgebuilds/carlos/tree/main/internal/research): decompose, search, fetch, read, synthesize, verify - [internal/skills](https://github.com/georgebuilds/carlos/tree/main/internal/skills): skill format, inducer, judge, replay-eval, bundle-directory loader, frame filter - [internal/usershell](https://github.com/georgebuilds/carlos/tree/main/internal/usershell): Phase U `!` prefix driver, jobs overlay, history - [internal/gateway](https://github.com/georgebuilds/carlos/tree/main/internal/gateway): ntfy, Telegram, Signal, custom adapters - [internal/daemon](https://github.com/georgebuilds/carlos/tree/main/internal/daemon): background scheduler under launchd or systemd (per-fire frame plumbing) - [internal/schedule](https://github.com/georgebuilds/carlos/tree/main/internal/schedule): cron plus natural language grammar (Schedule.Frame field) - [internal/memory](https://github.com/georgebuilds/carlos/tree/main/internal/memory): SQLite FTS5 over summaries (per-frame search + recent helpers) - [internal/farewell](https://github.com/georgebuilds/carlos/tree/main/internal/farewell): bordered TUI-exit panel that surfaces daemon-orphan, brew-update, and frame-migration notes in one rounded box - [internal/tui](https://github.com/georgebuilds/carlos/tree/main/internal/tui): bubbletea chat, manage, onboarding, slash registry, frame switcher, new-frame wizard, /permissions, /capabilities; chat renders assistant turns as markdown via glamour, streams tool calls as they happen, and shows a live "thinking" activity row before the first model token ## Provider capability map The `Capabilities()` accessor exposes per-provider feature flags: `ParallelToolUse`, `PromptCaching`, `Vision`, `StructuredOut`. Anthropic is first-class. OpenAI and OpenRouter ride the `oacompat` Chat Completions wire shape. Gemini has a native provider plus `oacompat` for OpenAI-compatible Gemini endpoints. Ollama uses `/api/chat` with a leading system message. ## Filesystem layout (`~/.carlos/`) - `config.yaml`: user prefs, provider keys, schedules, vault config, frames - `state.db`: SQLite event log plus memory FTS5 (`summaries.frame` column for per-frame scoping) - `trusted-workspaces.json`: Phase T-2 trust store, 0600 with atomic writes - `shell-history`: Phase U history file (shared across frames) - `frames//research/-.md`: research reports - `frames//usershell/.log`: per-job shell output - `frames//worktrees//`: sub-agent worktrees - `frames//digest/`: scheduled-digest artifacts - `skills/.md`: user-approved skill library (shared) Legacy `~/.carlos/{research,usershell,worktrees}/*` paths are idempotently migrated into `frames/personal//` on first launch. Directory permissions are 0700; files containing secrets are 0600. ## License GPL-3.0-or-later.