# YottaCode — Documentation Full text of the documentation documentation, concatenated for one-shot ingestion by an LLM or coding agent. Generated 2026-06-04 03:30:10 UTC · https://yottacode.ai/ ================================================================================ # Installation Source: https://yottacode.ai/docs/installation/ This page covers every install path plus post-install configuration. For the fastest path (pre-built binary + setup wizard), see the [Quick start](/docs/quickstart/) on the project README. > [!TIP] > New here? Run the installer script below, then `yottacode setup`. No `sudo` required. ## Requirements - Go 1.25+ for building from source - A modern terminal for the interactive TUI - A model provider: Ollama, OpenAI, Anthropic, Gemini, xAI, ChatGPT OAuth through `openai-auth`, GitHub Copilot through `copilot-auth`, or any OpenAI-compatible `/v1` API ## Installer script (recommended) ```bash curl -fsSL https://raw.githubusercontent.com/yottadynamics/yottacode/main/install.sh | bash ``` The script detects your OS/arch (Linux + macOS, amd64 + arm64), resolves the latest GitHub release (or honors `VERSION=0.3.0` if set), verifies the archive against `SHA256SUMS`, and installs to `$HOME/.yottacode/bin/yottacode`. No `sudo` required. It then offers to append a `PATH` export to your shell rc file (zsh / bash / fish / sh detected from `$SHELL`), backing up the rc first. Useful flags and env: - `VERSION=0.3.0` — pin a version instead of "latest". - `INSTALL_DIR=/custom/path` — override the install location. - `--no-modify-rc` — skip the rc-file edit (useful when you manage `PATH` yourself). - `--yes` / `-y` — non-interactive: assume "yes" to prompts (required in CI). - `NO_COLOR=1` — disable ANSI colors and animations. Re-running the installer upgrades in place: same flow, the rc edit is detected and skipped via sentinel comments. ## Updating `yottacode` checks GitHub for a newer release once per day on startup. The check is asynchronous, cached at `~/.yottacode/cache/update-check.json`, and runs **only** when the root interactive command launches into a real terminal — `yottacode run`, `yottacode --version`, scripts, and pipes never trigger it. When a newer release exists, you'll see a one-line prompt before the TUI starts: ``` yottacode 0.3.0 is available (you have 0.2.0). Release notes: https://github.com/yottadynamics/yottacode/releases/tag/v0.3.0 Install now? [y/N]: ``` Answer `y` and the installer runs in the foreground; yottacode exits cleanly once it's done so you can re-launch on the new binary. Answer anything else and the TUI starts as normal. To disable the check entirely (CI, privacy, sandboxes): `export YOTTACODE_NO_UPDATE_CHECK=1`. To force a refresh: `rm ~/.yottacode/cache/update-check.json`. ## Manual binary install {{< details title="Pinned download + manual extract" closed="true" >}} ```bash VERSION=0.2.0 # Swap linux/darwin and amd64/arm64 to match your machine curl -fsSL https://github.com/yottadynamics/yottacode/releases/download/v${VERSION}/yottacode_${VERSION}_linux_amd64.tar.gz \ | tar -xz install -m 0755 ./yottacode "$HOME/.yottacode/bin/yottacode" ``` Archive matrix: `yottacode_${VERSION}_{linux,darwin}_{amd64,arm64}.tar.gz`. Each release also publishes a `SHA256SUMS` file for verification. {{< /details >}} ## Build from source ```bash git clone https://github.com/yottadynamics/yottacode.git cd yottacode go build -o yottacode ./cmd/yottacode ``` Run it from the repo: ```bash ./yottacode --help ``` Install it onto your `PATH`: ```bash sudo install -m 0755 ./yottacode /usr/local/bin/yottacode ``` Or install into a user-local bin directory: ```bash mkdir -p ~/.local/bin install -m 0755 ./yottacode ~/.local/bin/yottacode ``` Make sure `~/.local/bin` is on your `PATH`. ## Go install from a local checkout From the repository root: ```bash go install ./cmd/yottacode ``` This writes the binary to `$(go env GOPATH)/bin`. ## Cross-compile ```bash GOOS=darwin GOARCH=arm64 go build -o yottacode-darwin-arm64 ./cmd/yottacode GOOS=darwin GOARCH=amd64 go build -o yottacode-darwin-amd64 ./cmd/yottacode GOOS=linux GOARCH=arm64 go build -o yottacode-linux-arm64 ./cmd/yottacode GOOS=linux GOARCH=amd64 go build -o yottacode-linux-amd64 ./cmd/yottacode ``` ## Supported platforms - Linux: supported by source builds and release binaries - macOS: supported by source builds and release binaries - Windows: not a release target; run yottacode under WSL ## Run the setup wizard (recommended) > [!IMPORTANT] > yottacode has no default model — you must set a model and base URL before the first run. The wizard is the easiest way. yottacode does not guess a default model or endpoint. The fastest post-install path is the interactive wizard: ```bash yottacode setup ``` The wizard writes `~/.yottacode/config.toml` and `~/.yottacode/.env`, probes providers where possible, and can be rerun later with `/setup` from inside the TUI. ## Or configure manually {{< tabs items="Local Ollama,ChatGPT OAuth,GitHub Copilot" >}} {{< tab >}} No API key — run a model locally with Ollama: ```bash ollama serve ollama pull export YOTTACODE_PROVIDER=ollama export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=http://localhost:11434/v1 yottacode ``` {{< /tab >}} {{< tab >}} Use your ChatGPT account via OAuth: ```bash yottacode openai-auth login export YOTTACODE_PROVIDER=openai-auth export YOTTACODE_MODEL= # /model list shows what your account allows export YOTTACODE_BASE_URL=https://chatgpt.com/backend-api/codex yottacode ``` `openai-auth` stores tokens under `~/.yottacode/auth/` with restrictive permissions; that directory is blocked from model reads and writes. {{< /tab >}} {{< tab >}} Bill against your GitHub Copilot subscription: ```bash yottacode copilot-auth login export YOTTACODE_PROVIDER=copilot export YOTTACODE_MODEL=claude-haiku-4.5 export YOTTACODE_BASE_URL=https://api.githubcopilot.com yottacode ``` `copilot` uses GitHub's device code flow and bills against your Copilot subscription. Tokens and model cache live under `~/.yottacode/auth/`. {{< /tab >}} {{< /tabs >}} Other OpenAI-compatible endpoints (NVIDIA NIM's free tier, Groq, vLLM, ...) work the same way — see [Configuring providers](/docs/providers/). ================================================================================ # Quickstart Source: https://yottacode.ai/docs/quickstart/ This guide gets yottacode running against a model provider and starts your first coding session. {{% steps %}} ### Install yottacode One-liner (Linux + macOS, amd64 + arm64): ```bash curl -fsSL https://raw.githubusercontent.com/yottadynamics/yottacode/main/install.sh | bash ``` The installer drops `yottacode` into `~/.yottacode/bin/` (no `sudo`), verifies the release archive against published SHA256 sums, and appends a `PATH` export to your shell rc — making a timestamped backup of the rc first. Skip the rc edit (you manage `PATH` yourself): ```bash curl -fsSL https://raw.githubusercontent.com/yottadynamics/yottacode/main/install.sh \ | bash -s -- --no-modify-rc ``` Pin a specific version instead of "latest": ```bash curl -fsSL https://raw.githubusercontent.com/yottadynamics/yottacode/main/install.sh \ | VERSION=0.2.0 bash ``` Verify the install: ```bash yottacode --version ``` > [!NOTE] > yottacode checks GitHub for a newer release once a day on startup and offers to upgrade before the TUI starts. Set `YOTTACODE_NO_UPDATE_CHECK=1` to disable. Windows users should run yottacode under WSL. For build-from-source and pinned-archive paths, see [Installation](/docs/installation/). ### Configure a provider yottacode does not guess a default model or endpoint. The fastest way to configure one is the wizard: ```bash yottacode setup ``` The wizard writes `~/.yottacode/config.toml` and `~/.yottacode/.env`, probes providers where possible, and can be rerun later with `/setup` from inside the TUI. Or configure manually — you need a model id, a base URL, and (for some providers) an API key. Pick one: {{< tabs items="Ollama,OpenAI,ChatGPT OAuth,GitHub Copilot,xAI" >}} {{< tab >}} ```bash ollama serve ollama pull export YOTTACODE_PROVIDER=ollama export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=http://localhost:11434/v1 yottacode ``` Ollama ignores API keys; yottacode handles that internally. {{< /tab >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=openai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.openai.com/v1 export YOTTACODE_API_KEY=sk-... yottacode ``` {{< /tab >}} {{< tab >}} ```bash yottacode openai-auth login export YOTTACODE_PROVIDER=openai-auth export YOTTACODE_MODEL= # /model list shows what your account allows export YOTTACODE_BASE_URL=https://chatgpt.com/backend-api/codex yottacode ``` `openai-auth` uses a browser "Sign in with ChatGPT" flow instead of an API key. Saved tokens live under `~/.yottacode/auth/`, which yottacode blocks from model reads and writes. {{< /tab >}} {{< tab >}} ```bash yottacode copilot-auth login export YOTTACODE_PROVIDER=copilot export YOTTACODE_MODEL=claude-haiku-4.5 export YOTTACODE_BASE_URL=https://api.githubcopilot.com yottacode ``` `copilot` uses GitHub's device code flow. Model calls bill against your GitHub Copilot subscription. Available models depend on the subscription tier (Free, Pro, Pro+); the model picker marks plan-gated models. {{< /tab >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=xai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.x.ai/v1 export YOTTACODE_API_KEY=xai-... yottacode ``` {{< /tab >}} {{< /tabs >}} See [Providers](/docs/providers/) for the full list. ### Ask for help in the TUI Launch yottacode from a project directory: ```bash cd ~/src/my-project yottacode ``` Try prompts like: ```text summarize this repository ``` ```text find the tests for the session package and explain how persistence works ``` ```text add a regression test for the bug described in this issue ``` Useful interactive commands: - `/help` — list slash commands - `/provider` — add, remove, or switch providers - `/model` — open model picker or `/model ` to switch directly - `/plan` — toggle plan mode (also Shift+Tab) for research before implementation - `/theme` — change the color theme - `/skills` — menu to enable/disable, install, uninstall, check, and update skills - `/sessions` — resume, rename, or export sessions - `/memory` — edit USER.md / YOTTACODE.md or browse agent-managed memories - `/doctor` — actively probe the configured endpoint - `/init` — draft or refresh `.yottacode/YOTTACODE.md` ### Use one-shot mode for scripts ```bash yottacode run "summarize the public API of this repo" ``` `yottacode run` prints the final answer to stdout and sends status/tool progress to stderr, so it composes cleanly with pipes, redirects, and CI logs. ### Understand approvals yottacode reads and inspects your repo without prompting; mutating actions usually ask first. You will see approval modals for file writes, shell commands, destructive git operations, and similar changes. > [!CAUTION] > yottacode does not provide an in-process sandbox. For real isolation, run it inside a container or devcontainer. {{% /steps %}} ## Next steps - [CLI usage](/docs/cli/) - [TUI slash commands](/docs/tui-slash-commands/) - [Configuring providers](/docs/providers/) - [Security and allow lists](/docs/security-and-allow-lists/) - [Memory](/docs/memory/) ================================================================================ # CLI usage Source: https://yottacode.ai/docs/cli/ `yottacode` has two primary entry points: - `yottacode` — launch the interactive TUI - `yottacode run ""` — run one prompt non-interactively Both use the same agent, tools, provider configuration, memory, and sessions. ## Global provider flags These work for the TUI and `run` mode. | Flag | Env var | Required | Purpose | |---|---|---:|---| | `--model`, `-m` | `YOTTACODE_MODEL` | yes | Model id to use | | `--base-url` | `YOTTACODE_BASE_URL` | yes | Provider base URL | | `--api-key` | `YOTTACODE_API_KEY` | no | Bearer token | | `--provider` | `YOTTACODE_PROVIDER` | no | Provider override | | `--reasoning-effort` | `YOTTACODE_REASONING_EFFORT` | no | Reasoning hint: `low`, `medium`, `high` | | `--enable-web-search` | `YOTTACODE_ENABLE_WEB_SEARCH` | no | Enable hosted web search when supported | | `--disable-web-search` | `YOTTACODE_DISABLE_WEB_SEARCH` | no | Disable default hosted web search | | `--enable-x-search` | `YOTTACODE_ENABLE_X_SEARCH` | no | Enable xAI `x_search` | | `--enable-code-interpreter` | `YOTTACODE_ENABLE_CODE_INTERPRETER` | no | Enable hosted code interpreter when supported | | `--allow-paths` | `YOTTACODE_ALLOW_PATHS` | no | Extra write roots | | `--yolo` | — | no | Dangerous: skip approval prompts and remove iteration cap (permissions-bypass overlay). Explicit deny rules still apply. Launch-only. | | `--max-iterations` | — | no | Tool-call cap per turn; default `50` (auto raises to `200`; `--yolo` removes the cap) | | `--permission-mode` | — | no | TUI only — startup mode: `default` \| `plan` \| `auto`. Mirrors Claude Code's `--permission-mode`. | | `--plan-resume` | — | no | TUI only — resume a saved plan by slug/substring (implies `--permission-mode plan`) | | `--resume` | — | no | Resume a session by id or name | | `--continue` / `-c` | — | no | Resume the most recent session in the current directory (mirrors `claude --continue`). Mutually exclusive with `--resume`. | > [!NOTE] > Precedence is flags → environment variables → config file (where supported) → a clean error for any missing required value. ## Two ways to run {{< tabs items="Interactive TUI,One-shot (run)" >}} {{< tab >}} ```bash yottacode ``` The TUI is best for multi-turn coding work. It provides streaming output, slash commands, approval modals, session controls, and inline scrollback. Useful keys: - `Enter` — submit - `Ctrl+J` — insert newline - `Ctrl+C` — cancel an in-flight turn - `Ctrl+D` — quit when input is empty - `?` — show cheatsheet when input is empty - `/` — open slash-command palette See [TUI slash commands](/docs/tui-slash-commands/). {{< /tab >}} {{< tab >}} ```bash yottacode run "summarize this repo" ``` Use `run` for scripts, CI jobs, or shell pipelines. stdout contains the final assistant response. stderr contains reasoning, progress, and tool status. Examples: ```bash yottacode run "write a changelog entry for the current git diff" ``` ```bash yottacode run --max-iterations 100 "implement step 3 of the plan we drafted yesterday" ``` {{< /tab >}} {{< /tabs >}} ## Setup ```bash yottacode setup ``` Runs the first-run configuration wizard. It can write `~/.yottacode/config.toml` and help configure provider profiles. ## Provider commands ```bash yottacode provider list yottacode provider add openai yottacode provider use openai yottacode provider remove old-provider yottacode provider refresh yottacode provider refresh openai ``` Provider profiles live in `~/.yottacode/config.toml` as `[[providers]]` blocks. ## Model commands ```bash yottacode model list yottacode model list --all yottacode model use yottacode model fetch yottacode model fetch openai ``` `model use` updates the active `default_model` in config. In the TUI, `/model ` switches only the running session. ## Diagnostics ```bash yottacode doctor yottacode doctor --json ``` `doctor` probes the provider `/models` endpoint and reports reachability, auth status, model visibility, provider capabilities, and configuration warnings. The JSON form is intended for scripts. ## ChatGPT OAuth ```bash yottacode openai-auth login yottacode openai-auth status yottacode openai-auth status --json yottacode openai-auth logout ``` These commands manage the `openai-auth` provider's browser OAuth credentials and account-specific model list. ## GitHub Copilot auth ```bash yottacode copilot-auth login yottacode copilot-auth models yottacode copilot-auth models --raw yottacode copilot-auth status yottacode copilot-auth status --json yottacode copilot-auth logout ``` These commands manage the `copilot` provider's GitHub device-code OAuth credentials and model cache. `login` runs the device code flow and caches available models. `models` lists cached models and marks plan-gated ones with `[upgrade plan]`. ## Sessions ```bash yottacode sessions list yottacode sessions list --json yottacode sessions resume yottacode sessions resume --summarized yottacode sessions rename yottacode sessions export yottacode sessions export path.md --force ``` Sessions are saved automatically after completed turns in `~/.yottacode/sessions/`. ## Memory ```bash yottacode memory list # default scope: project yottacode memory list --scope user yottacode memory forget --scope ``` Use the TUI `/memory` picker to edit `USER.md` / `YOTTACODE.md` and browse agent-managed memories in `vim`. See [Memory](/docs/memory/) for the full layout and the `memory_save` / `memory_forget` tools the agent uses to curate this layer. ## Worktrees ```bash yottacode --worktree # start a session in a git worktree yottacode -w # short form yottacode run --worktree # one-shot in a worktree yottacode worktree list # list active worktrees yottacode worktree status # show worktree state yottacode worktree remove # remove a worktree yottacode worktree prune # clean up stale worktrees ``` See [Worktrees](/docs/worktrees/) for the full workflow and `.worktreeinclude` format. ## GitHub setup ```bash yottacode setup github # interactive PAT setup (or use $GITHUB_TOKEN / gh auth) ``` See [GitHub integration](/docs/github/) for the full auth chain and available slash commands. ## Version ```bash yottacode version ``` ================================================================================ # Configuration Source: https://yottacode.ai/docs/configuration/ `yottacode` does not ship with built-in defaults for the required provider settings. If `--model` or `--base-url` is missing, startup fails immediately with a clear error. ## Flags And Environment Variables Every flag below works for both `yottacode` and `yottacode run`. The same provider flags also apply to `yottacode doctor`. | Flag | Env var | Required | Notes | |---|---|---|---| | `--model`, `-m` | `YOTTACODE_MODEL` | yes | Provider-specific model id (run `/model list` to see what your account allows) | | `--base-url` | `YOTTACODE_BASE_URL` | yes | OpenAI-compatible base URL such as `http://localhost:11434/v1` or `https://api.openai.com/v1` | | `--api-key` | `YOTTACODE_API_KEY` | no | Bearer token for authenticated providers | | `--provider` | `YOTTACODE_PROVIDER` | no | Provider profile name or provider kind hint | | `--reasoning-effort` | `YOTTACODE_REASONING_EFFORT` | no | Reasoning effort for providers that support it: `low`, `medium`, or `high` (unset = provider default). Applies to OpenAI, Anthropic, Gemini, and xAI via each one's native knob — see [providers.md](/docs/providers/#reasoning-effort). Change it mid-session with [`/effort`](/docs/tui-slash-commands/). | | `--enable-web-search` | `YOTTACODE_ENABLE_WEB_SEARCH` | no | Enable provider-native web search when supported | | `--disable-web-search` | `YOTTACODE_DISABLE_WEB_SEARCH` | no | Disable provider-native web search even when OpenAI/xAI would enable it by default | | `--enable-x-search` | `YOTTACODE_ENABLE_X_SEARCH` | no | Enable xAI `x_search` when supported | | `--enable-code-interpreter` | `YOTTACODE_ENABLE_CODE_INTERPRETER` | no | Enable provider-native code interpreter when supported | | `--search-allowed-domains` | `YOTTACODE_SEARCH_ALLOWED_DOMAINS` | no | Comma-separated allowlist for provider-native web search | | `--search-excluded-domains` | `YOTTACODE_SEARCH_EXCLUDED_DOMAINS` | no | Comma-separated blocklist for provider-native web search | | `--x-search-allowed-handles` | `YOTTACODE_X_SEARCH_ALLOWED_HANDLES` | no | Comma-separated X handle allowlist for xAI `x_search` | | `--x-search-excluded-handles` | `YOTTACODE_X_SEARCH_EXCLUDED_HANDLES` | no | Comma-separated X handle blocklist for xAI `x_search` | | `--x-search-from-date` | `YOTTACODE_X_SEARCH_FROM_DATE` | no | Inclusive lower bound for xAI `x_search` in `YYYY-MM-DD` form | | `--x-search-to-date` | `YOTTACODE_X_SEARCH_TO_DATE` | no | Inclusive upper bound for xAI `x_search` in `YYYY-MM-DD` form | | `--system` | — | no | Override the default system prompt | | `--resume` | — | no | Resume a session by id or name | | `--continue` / `-c` | — | no | Resume the most recent session whose cwd matches the current directory. Mirrors Claude Code's `--continue`. Mutually exclusive with `--resume`. | | `--yolo` | — | no | DANGEROUS: auto-approve every tool call without prompting and remove the iteration cap (`deny` rules in `permissions.json` still apply). Launch-only; cannot be toggled mid-run — restart yottacode without the flag to recover. | | `--max-iterations` | — | no | Tool-call cap per turn; defaults to `50`. Auto mode raises the effective cap to 4× (200). `--yolo` removes it entirely. | | `--allow-paths` | `YOTTACODE_ALLOW_PATHS` | no | Comma-separated extra write roots in addition to the current working directory | | `--permission-mode` | — | no | Startup permission mode: `default` (no startup mode), `plan` (read-only research; describe the task as your first message), or `auto` (edits auto-allow; bash & commits still prompt). Mirrors Claude Code's `--permission-mode`. No-op for `yottacode run`. | | `--plan-resume` | — | no | Resume an existing plan by slug or substring (matched against `~/.yottacode/plans/`, newest-first). Implies `--permission-mode plan`. No-op for `yottacode run`. | Precedence is: 1. Explicit flags 2. Environment variables 3. Matching provider profile in `~/.yottacode/config.toml` 4. Error for missing required values Resolution lives in [`internal/cli/options.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/cli/options.go). ## Examples ```bash # Local Ollama (no API key) — flags form yottacode --provider ollama --model --base-url http://localhost:11434/v1 # Same thing through env vars export YOTTACODE_PROVIDER=ollama export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=http://localhost:11434/v1 yottacode # OpenAI API key export YOTTACODE_PROVIDER=openai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.openai.com/v1 export YOTTACODE_API_KEY=sk-... yottacode # ChatGPT OAuth (first run: yottacode openai-auth login) export YOTTACODE_PROVIDER=openai-auth export YOTTACODE_MODEL= # /model list shows what your account allows export YOTTACODE_BASE_URL=https://chatgpt.com/backend-api/codex yottacode # GitHub Copilot (first run: yottacode copilot-auth login) export YOTTACODE_PROVIDER=copilot export YOTTACODE_MODEL=claude-haiku-4.5 export YOTTACODE_BASE_URL=https://api.githubcopilot.com yottacode # Anthropic export YOTTACODE_PROVIDER=anthropic export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.anthropic.com export YOTTACODE_API_KEY=sk-ant-... yottacode # xAI with default web_search + explicit x_search export YOTTACODE_PROVIDER=xai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.x.ai/v1 export YOTTACODE_API_KEY=xai-... export YOTTACODE_SEARCH_ALLOWED_DOMAINS=docs.x.ai,arxiv.org export YOTTACODE_ENABLE_X_SEARCH=1 export YOTTACODE_X_SEARCH_ALLOWED_HANDLES=xai yottacode # Opt out of default hosted web search export YOTTACODE_DISABLE_WEB_SEARCH=1 yottacode # Allow writes into sibling repos too export YOTTACODE_ALLOW_PATHS=/home/me/shared-configs,/home/me/other-repo yottacode ``` ## Isolation There is no in-process sandbox, and there will not be one. yottacode keeps its core small and does not ship bwrap/firejail/landlock backends. `run_bash` and every other tool run directly on the host. **For real isolation, run yottacode inside a container or devcontainer** — that protects every tool (not just shell commands) and is portable across host distros. ## Provider Diagnostics At startup, `yottacode` resolves a provider profile from the configured endpoint, model, and feature flags. That profile drives: - adapter routing (`chat.completions` vs Responses API) - which provider-native tools are actually enabled - static diagnostics shown in the startup card, footer, and `/provider` Examples of diagnostics: - unsupported built-in tool flags on the selected provider - `x_search` filters used on a non-xAI endpoint - empty API keys for remote providers - suspicious provider/model mismatches such as `grok-*` on `openai` Use `/provider` to inspect the resolved static state, or `/doctor` to run an active `/models` probe against the configured endpoint. ### Default Hosted Web Search `yottacode` enables provider-native `web_search` by default for: - OpenAI - xAI That default can be disabled with `--disable-web-search` or `YOTTACODE_DISABLE_WEB_SEARCH=1`. For Ollama and generic OpenAI-compatible endpoints, hosted provider tools stay off by default. Those models can use the local `fetch_url` tool instead. For shell and CI usage: ```bash yottacode doctor yottacode doctor --json ``` `yottacode doctor` exits non-zero when issues are found. `--json` emits a stable machine-readable payload. ### `doctor --json` shape Top-level fields: - `profile` - `base_url` - `model` - `http_status` - `endpoint_reachable` - `auth_ok` - `model_visible` - `available_models` - `issues` - `warnings` The nested `profile` object includes: - `provider` - `uses_responses_api` - `supports_reasoning` - `supports_web_search` - `supports_x_search` - `supports_code_interpreter` - `enabled_builtin_tools` - `issues` - `warnings` Example: ```json { "profile": { "provider": "openai", "uses_responses_api": true, "supports_reasoning": true, "supports_web_search": true, "supports_x_search": false, "supports_code_interpreter": true, "enabled_builtin_tools": ["web_search"], "warnings": ["API key is empty for a remote provider"] }, "base_url": "https://api.openai.com/v1", "model": "", "http_status": 200, "endpoint_reachable": true, "auth_ok": true, "model_visible": true, "available_models": ["", ""], "warnings": ["API key is empty for a remote provider"] } ``` ## On-Disk State Most state lives under `~/.yottacode/`: {{< filetree/container >}} {{< filetree/folder name="~/.yottacode" >}} {{< filetree/folder name="auth" >}} {{< filetree/file name="openai-auth.json" >}} {{< filetree/file name="openai-auth-models.json" >}} {{< filetree/file name="copilot.json" >}} {{< filetree/file name="copilot-models.json" >}} {{< /filetree/folder >}} {{< filetree/folder name="sessions" >}} {{< filetree/file name="<id>.json" >}} {{< /filetree/folder >}} {{< filetree/folder name="checkpoints" >}} {{< filetree/folder name="<session>" >}}{{< /filetree/folder >}} {{< /filetree/folder >}} {{< filetree/folder name="memory" >}} {{< filetree/file name="<name>.md" >}} {{< filetree/file name="MEMORY.md" >}} {{< /filetree/folder >}} {{< filetree/folder name="projects" >}} {{< filetree/folder name="<slug>" >}} {{< filetree/folder name="memory" >}}{{< /filetree/folder >}} {{< /filetree/folder >}} {{< /filetree/folder >}} {{< filetree/file name="index.sqlite" >}} {{< filetree/file name="USER.md" >}} {{< filetree/file name="config.toml" >}} {{< /filetree/folder >}} {{< /filetree/container >}} - **`auth/`** — provider OAuth token stores plus per-account model caches (`*-models.json`) for `openai-auth` and `copilot` (`0600`; denied to model tools). - **`sessions/`** — saved conversations; **`index.sqlite`** is the FTS5 index that powers `/recall`. - **`checkpoints/`** — `/checkpoints` + `Esc Esc` snapshot store. - **`memory/`** — agent-managed user-scope memories (`MEMORY.md` is the auto-generated index). - **`projects//memory/`** — agent-managed project-scope memories (per-user). - **`USER.md`** — optional global user memory (human-only). **`config.toml`** — tunables (context watermarks, retrieval, checkpoints). ### Checkpoints retention `/checkpoints` and `Esc Esc` capture a per-prompt snapshot under `~/.yottacode/checkpoints/`. By default, snapshots expire 30 days after creation; the sweep runs opportunistically when a session opens. Override the window in `~/.yottacode/config.toml`: ```toml [checkpoints] retention_days = 30 # set to 0 to fall back to the 30-day default; smaller values prune more aggressively ``` See [`tui-slash-commands.md`](/docs/tui-slash-commands/#checkpoints---checkpoints--esc-esc) for the full feature. ### Theme `/theme` switches the TUI color palette and persists the choice. Pin a non-default theme in `~/.yottacode/config.toml`: ```toml [theme] name = "catppuccin" # one of: terminal, catppuccin, dimmed, gruvbox, high-contrast, # low-contrast, no-color, nord, one-dark, solarized-dark, # tokyo-night ``` Omit the section to ride the default (`terminal`). Unknown names are rejected at load time — see [themes.md](/docs/themes/) for the full palette catalog. Per-repo state lives under `/.yottacode/`: ```text /.yottacode/ YOTTACODE.md optional per-repo project memory (human-seeded; the agent keeps it fresh through approval-gated writes) permissions.json committable team-shared permission rules permissions.local.json gitignored personal additions (where the modal's [a]lways-allow path writes to) ``` See [`memory.md`](/docs/memory/) for how the agent-managed memory layer works and how to inspect or prune it. `USER.md`, `YOTTACODE.md`, and both `permissions*.json` files are safe to edit directly — opening either memory file via `/memory` (yottacode suspends to `vim`) reloads on exit, and external edits to either `permissions*.json` are picked up automatically on the next tool call (run `/permissions` to print the two paths). Session files and the FTS index are application-managed. `USER.md` is in the agent's write-deny list (global preferences are out of scope for a project-scoped agent to curate). `YOTTACODE.md` is **not** in the deny list: the agent updates it through `edit_file` / `write_file` with the standard approval modal gating each change. To make `YOTTACODE.md` human-only on a specific project, add a deny rule to `/.yottacode/permissions.json`: ```json { "permissions": { "deny": ["Edit(.yottacode/YOTTACODE.md)", "Write(.yottacode/YOTTACODE.md)"] } } ``` Add `**/.yottacode/permissions.local.json` to your `.gitignore` so personal allow rules don't leak into the team-shared file. A missing, empty, or whitespace-only `permissions.json` / `permissions.local.json` is treated as "no rules" — yottacode no longer fails to start when either file exists but has no content. Opening either path from `/permissions` seeds the file with the full `{allow, ask, deny}` skeleton before vim launches, so you always edit a fully-shaped file instead of an empty buffer. Files that already have content are never overwritten. ### Rule Prefixes Each rule has the shape `()`. Supported tool prefixes: | Prefix | Applies to | Pattern matches against | |---|---|---| | `Bash` | `run_bash` | Full shell command text | | `Read` | `read_file`, `read_many_files` | cwd-relative path (doublestar) | | `Write` | `write_file` | cwd-relative path (doublestar) | | `Edit` | `edit_file`, `apply_diff` | cwd-relative path (doublestar) | | `Mkdir` | `mkdir` | cwd-relative path (doublestar) | | `Copy` / `Move` / `Delete` | the same-named tools | path or `src -> dst` (string) | | `List` | `list_dir`, `list_project_structure` | cwd-relative path (doublestar) | | `Glob` / `Grep` | the same-named tools | pattern string | | `Fetch` | `fetch_url` | URL (string) | | `Git` | unified `git` + discrete `git_*` helpers | joined args (string) | | `Github` | every `gh_*` tool (PR + issue surface) | canonical verb name (string) | | `Memory` | `memory_save` / `memory_forget` | `op scope:name` (string) | | `Tests` / `Rollback` | the same-named tools | empty descriptor (binary allow/deny) | `Github(...)` descriptors are the canonical verb name extracted from the tool name (independent of the resource-first tool naming so the roadmap's `Github(read_*)` style works): | Tool | Verb | |---|---| | `gh_pr_read` | `read_pr` | | `gh_pr_review_context` | `read_pr_review_context` | | `gh_pr_create` | `create_pr` | | `gh_pr_update` | `update_pr` | | `gh_pr_add_comment` | `add_pr_comment` | | `gh_issue_read` | `read_issue` | | `gh_issue_list` | `list_open_issues` | Wildcards work as in any other rule, so: - `Github(read_*)` covers every read verb - `Github(*_pr)` covers every PR-targeting verb - `Github(*)` is the catch-all (use sparingly — `Allow` it and writes auto-approve) Owner/repo scoping (`Github(create_pr owner/repo)`) is not yet implemented — every call currently resolves against the cwd's git remote. The roadmap tracks per-repo scoping for the cloud bot work (SaaS Phase 2). ### Starter Rule Set The default skeleton ships with empty arrays — yottacode is unopinionated about which rules a project wants. The set below is a curated starting point you can paste into `/.yottacode/permissions.json` and prune to taste. Decision precedence is `Deny > Allow > Ask > Default`, so the `deny` block always wins even if a broader `allow` is added later. ```json { "permissions": { "allow": [ "Bash(git status)", "Bash(git status *)", "Bash(git diff)", "Bash(git diff *)", "Bash(git log)", "Bash(git log *)", "Bash(git show *)", "Bash(git branch)", "Bash(git branch -*)", "Bash(git remote -v)", "Bash(ls)", "Bash(ls *)", "Bash(pwd)", "Bash(echo *)", "Bash(which *)", "Bash(head *)", "Bash(tail *)", "Bash(wc *)", "Github(read_*)", "Github(list_open_issues)" ], "ask": [ "Bash(git push *)", "Bash(git reset --hard *)", "Bash(git rebase *)", "Bash(gh pr create *)", "Bash(gh pr merge *)", "Bash(gh pr close *)", "Github(create_pr)", "Github(update_pr)", "Github(add_pr_comment)", "Bash(gh release *)", "Bash(npm publish*)", "Bash(cargo publish*)", "Bash(docker push *)", "Read(**/.env)", "Read(**/.env.*)", "Read(**/*.pem)", "Read(**/id_rsa)", "Read(**/credentials*)" ], "deny": [ "Bash(rm -rf /*)", "Bash(rm -rf ~*)", "Bash(sudo rm -rf *)", "Bash(curl * | sh)", "Bash(curl * | bash)", "Bash(wget * | sh)", "Bash(wget * | bash)", "Bash(* | sudo sh)", "Bash(* | sudo bash)", "Bash(dd if=* of=/dev/*)", "Bash(mkfs.*)", "Bash(chmod -R 777 /)", "Bash(chmod -R 777 /*)", "Edit(/etc/**)", "Edit(/usr/**)", "Edit(/bin/**)", "Edit(/sbin/**)", "Edit(/boot/**)", "Write(/etc/**)", "Write(/usr/**)", "Delete(/etc/**)", "Delete(/usr/**)" ] } } ``` Pattern semantics that catch new authors out: - `*` matches the empty sequence too, so `Bash(rm -rf /*)` covers both `rm -rf /` and `rm -rf /home/user` with one rule — you don't need a separate `rm -rf /` entry. - The space before `*` is literal: `Bash(git status *)` matches `git status -s` but not the bare `git status`. Add both forms when you want to cover the command with and without arguments. - Path-typed rules (`Read`, `Write`, `Edit`, `Delete`, …) use doublestar; `Read(**/.env)` matches both top-level `.env` and nested `services/api/.env`. Personal additions go in `permissions.local.json` (gitignored). A common starter for a Go project: ```json { "permissions": { "allow": [ "Bash(go *)", "Bash(make *)", "Bash(gofmt *)", "Bash(goimports *)" ], "ask": [], "deny": [] } } ``` ## MCP servers yottacode is a client for Anthropic's Model Context Protocol. Each `[[mcp_servers]]` block in `~/.yottacode/config.toml` launches a subprocess at session start and registers its tools under the `mcp//` namespace. ```toml [[mcp_servers]] name = "filesystem" command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/workspace"] [[mcp_servers]] name = "github" command = "npx" args = ["-y", "@modelcontextprotocol/server-github"] env = { GITHUB_PERSONAL_ACCESS_TOKEN = "$GITHUB_PAT" } ``` `env` values support `$VAR` substitution from yottacode's process environment so secrets stay out of the config file. v1 supports stdio transport only. See [`mcp.md`](/docs/mcp/) for the full reference, including permission rules (`MCP(...)`), the `/mcp` slash command, and a curated server list. ## Model routing The `[router]` block hosts two independent, opt-in features. **Cache-safe task routing** runs isolated work (subagents, history compaction) on a cheap model while your main conversation stays on your chosen model — a pure cost saving with no prompt-cache churn: ```toml [router] mode = "auto" # off | manual | auto (default off) fast_model = "anthropic:claude-haiku-4-5" smart_model = "anthropic:claude-opus-4-6" ``` - `mode = "off"` (or absent) — disabled; fully backward compatible. - `mode = "manual"` — only routes subagents that declare an explicit `model:`. - `mode = "auto"` — also routes read-only/search subagents and summarization to `fast_model`. `fast_model` / `smart_model` are required when `mode` is not `off` and use the `""` or `":"` grammar; the model must exist in that provider's `models`. See [`models.md`](/docs/models/#cache-safe-task-routing) for the cost rationale and the auto heuristic. **Multi-provider failover** (separate feature, same block) dispatches each main-thread turn across an ordered candidate list, falling through on early failure: ```toml [router] enabled = true policy = "fallback-chain" # fallback-chain | cheap-first candidates = ["anthropic:claude-haiku-4-5", "openai:gpt-4o"] health_window_seconds = 60 health_failure_threshold = 3 ``` The two are orthogonal: `enabled`/`candidates` control failover across providers; `mode`/`fast_model`/`smart_model` control task routing. You can set either, both, or neither. ## Runtime Reconfiguration The TUI supports changing the active session configuration without restarting: - `/model ` - `/provider` (use `/provider use ` to swap endpoint + key in one step) - `/doctor` - `/permissions` (prints the shared + local rule file paths; edit either file directly) - `/memory` (edit `USER.md` / `YOTTACODE.md`, browse user-scope and project-scope memories) These changes apply to the current session only. They do not rewrite your shell configuration or future launch defaults. ================================================================================ # Configuring providers Source: https://yottacode.ai/docs/providers/ yottacode can use native provider adapters where useful and OpenAI-compatible endpoints everywhere else. ## Required settings At startup, yottacode needs: - `model` - `base_url` - `api_key` for remote providers that require API-key auth The `openai-auth` and `copilot` providers are exceptions: they use OAuth flows and store tokens under `~/.yottacode/auth/`. You can provide them through flags, environment variables, or `~/.yottacode/config.toml`. ## Environment variables ```bash export YOTTACODE_PROVIDER=openai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.openai.com/v1 export YOTTACODE_API_KEY=sk-... ``` Flags override environment variables: ```bash yottacode --model --base-url https://api.openai.com/v1 --api-key sk-... ``` ## Setup wizard ```bash yottacode setup ``` The wizard writes provider profiles to `~/.yottacode/config.toml`. ## Provider profiles Provider profiles live in `~/.yottacode/config.toml`: ```toml [active] provider = "openai" default_model = "" [[providers]] name = "openai" kind = "openai" base_url = "https://api.openai.com/v1" api_key_env = "OPENAI_API_KEY" default_model = "" ``` > [!WARNING] > Don't put raw API keys in `config.toml`. Use `api_key_env` and set the secret in your shell environment or in `~/.yottacode/.env` (the setup wizard does this for you). ## Provider configuration {{< tabs items="OpenAI,ChatGPT OAuth,GitHub Copilot,Ollama,Anthropic,Gemini,xAI,OpenAI-compatible" >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=openai export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.openai.com/v1 export YOTTACODE_API_KEY=sk-... ``` OpenAI reasoning models such as `o1*`, `o3*`, `o4*`, and `gpt-5*` are routed to the Responses API automatically when appropriate. Hosted tools: - `web_search` is enabled by default - `code_interpreter` can be enabled with `YOTTACODE_ENABLE_CODE_INTERPRETER=1` Disable default hosted web search: ```bash export YOTTACODE_DISABLE_WEB_SEARCH=1 ``` {{< /tab >}} {{< tab >}} ```bash yottacode openai-auth login export YOTTACODE_PROVIDER=openai-auth export YOTTACODE_MODEL= # /model list shows what your account allows export YOTTACODE_BASE_URL=https://chatgpt.com/backend-api/codex ``` `openai-auth` signs in with a ChatGPT account through browser OAuth. Model calls use the ChatGPT-authenticated backend instead of OpenAI API keys. Available models are account-dependent; after login, yottacode probes candidate models and stores the accepted list next to the token store. Lifecycle commands: ```bash yottacode openai-auth login yottacode openai-auth status yottacode openai-auth status --json yottacode openai-auth logout ``` Tokens and scanned model lists live in `~/.yottacode/auth/` with restrictive file permissions. That directory is denied to model read and write tools. {{< /tab >}} {{< tab >}} ```bash yottacode copilot-auth login export YOTTACODE_PROVIDER=copilot export YOTTACODE_MODEL=claude-haiku-4.5 export YOTTACODE_BASE_URL=https://api.githubcopilot.com ``` `copilot` uses GitHub's device code flow to authenticate. Model calls bill against the user's GitHub Copilot subscription. Available models depend on the subscription tier (Free, Pro, Pro+); the model picker marks plan-gated models with "upgrade plan". Lifecycle commands: ```bash yottacode copilot-auth login # device code flow, saves token + caches models yottacode copilot-auth models # list available models (updates cache) yottacode copilot-auth models --raw # full API response for debugging yottacode copilot-auth status yottacode copilot-auth status --json yottacode copilot-auth logout ``` In the TUI, `/provider add` with the `copilot-auth` entry runs the device code flow inline — no separate CLI step needed. Tokens and cached model lists live in `~/.yottacode/auth/` with restrictive file permissions. That directory is denied to model read and write tools. {{< /tab >}} {{< tab >}} ```bash ollama serve ollama pull qwen3.5:latest export YOTTACODE_PROVIDER=ollama export YOTTACODE_MODEL=qwen3.5:latest export YOTTACODE_BASE_URL=http://localhost:11434/v1 ``` Ollama does not require an API key. Provider-native hosted tools are not available; the model can use yottacode local tools such as `fetch_url` for concrete URLs. {{< /tab >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=anthropic export YOTTACODE_MODEL=claude-sonnet-4-6 export YOTTACODE_BASE_URL=https://api.anthropic.com export YOTTACODE_API_KEY=sk-ant-... ``` The native Anthropic adapter uses the Messages API rather than an OpenAI-compatible shim. {{< /tab >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=gemini export YOTTACODE_MODEL=gemini-2.5-pro export YOTTACODE_BASE_URL=https://generativelanguage.googleapis.com export YOTTACODE_API_KEY=... ``` The native Gemini adapter uses Google's HTTP API. {{< /tab >}} {{< tab >}} ```bash export YOTTACODE_PROVIDER=xai export YOTTACODE_MODEL=grok-4 export YOTTACODE_BASE_URL=https://api.x.ai/v1 export YOTTACODE_API_KEY=xai-... ``` Hosted tools: - `web_search` is enabled by default - `x_search` can be enabled with `YOTTACODE_ENABLE_X_SEARCH=1` - `code_interpreter` can be enabled when supported Optional filters: ```bash export YOTTACODE_SEARCH_ALLOWED_DOMAINS=docs.x.ai,arxiv.org export YOTTACODE_X_SEARCH_ALLOWED_HANDLES=xai export YOTTACODE_X_SEARCH_FROM_DATE=2026-01-01 export YOTTACODE_X_SEARCH_TO_DATE=2026-12-31 ``` {{< /tab >}} {{< tab >}} Use provider kind `openai-compatible` or pass the base URL directly: ```bash export YOTTACODE_PROVIDER=openai-compatible export YOTTACODE_MODEL=llama-3.1-70b export YOTTACODE_BASE_URL=https://example.com/v1 export YOTTACODE_API_KEY=... ``` This works with many gateways and self-hosted runtimes that expose `/v1/chat/completions` and `/v1/models`. Tested examples include NVIDIA NIM, Groq, vLLM, and Llama Stack. Other gateways that speak the same wire protocol should work but are not formally validated. {{< /tab >}} {{< /tabs >}} `/usage` reports token usage for every provider and links the billing dashboard for the paid cloud ones; it does not compute a dollar figure (no provider exposes per-model pricing on the inference key). Ollama and NVIDIA NIM (`openai-compatible` pointed at `integrate.api.nvidia.com`) have no billing dashboard — token counts only. See [cost.md](/docs/cost/). ## Diagnostics Inside the TUI: ```text /provider /doctor ``` From the shell: ```bash yottacode doctor yottacode doctor --json ``` `/provider` shows static resolved config. `/doctor` performs an active `/models` probe for endpoint reachability, auth, and model visibility. ## Switch providers ```bash yottacode provider list yottacode provider use openai ``` In the TUI, use the provider picker or: ```text /provider use openai ``` Switching provider in an active session rebuilds the adapter while preserving the session history. ## Image support Image support varies by provider. Two capabilities matter: | Provider | Pasted images | `read_file` images | |---|---|---| | Anthropic | yes | yes | | OpenAI | yes | no | | ChatGPT OAuth (`openai-auth`) | yes | no | | GitHub Copilot | yes | no | | Gemini | yes | no | | xAI | yes | no | | Ollama | no | no | | OpenAI-compatible (NVIDIA NIM, etc.) | no | no | > [!NOTE] > **Pasted images** — paste a screenshot path or `file:///` URL in the input; the image is sent as a native content block the model can see. Providers marked "no" receive only the text marker (no image data), avoiding API errors from text-only models. **`read_file` images** — `read_file("photo.png")` returns the image as a visual content block; only Anthropic supports image blocks in tool results today, so other providers receive a text label with file metadata. ## Reasoning effort Set how hard a reasoning-capable model thinks with [`--reasoning-effort`](/docs/configuration/) (or `YOTTACODE_REASONING_EFFORT`) at launch, or [`/effort`](/docs/tui-slash-commands/) mid-session. The surface is uniform — `default · low · medium · high` — but each provider has a different underlying knob, so yottacode translates the level per provider: | Provider | Underlying knob | Notes | |---|---|---| | OpenAI (`gpt-5*`, `o1`/`o3`/`o4`) | `reasoning.effort` enum | `low`/`medium`/`high` map 1:1. Non-reasoning models (e.g. `gpt-4o`) ignore it. | | ChatGPT OAuth (`openai-auth`) | `reasoning.effort` enum | Same as OpenAI, on the Codex backend. | | Anthropic (Claude) | extended-thinking token budget | Enables thinking with a budget sized as a fraction of the model's max-output tokens (low ≈ 25%, high ≈ 75%); `max_tokens` is raised to leave room for the answer. A model the catalog doesn't know falls back to a conservative budget so effort still engages — refresh the catalog (`yotta-models refresh`) for the full model-scaled budget. | | Gemini (2.5) | `thinkingConfig.thinkingBudget` | Enables thinking with a budget scaled per level, capped to the Gemini 2.5 family's valid range. | | xAI (Grok) | `reasoning_effort` enum | Only `grok-*-mini` accepts it (`low`/`high`; `medium` folds to `high`). `grok-4` reasons unconditionally and is left untouched. | **Default is unchanged.** When effort is unset, yottacode injects no reasoning parameter at all — every provider behaves exactly as it does without the setting. In particular, Anthropic and Gemini do **not** get extended thinking unless you ask for it (it costs extra tokens). `/effort default` (or `off`/`none`) returns to this state mid-session. **No per-model table to maintain.** Whether a model supports thinking, and how big a thinking budget to allow, come from the model catalog (`Capabilities.Thinking` and `MaxOutput`, fetched from each provider's list-models endpoint via `yotta-models refresh`) — not a hand-maintained list. A model the catalog doesn't describe still works: enum providers (OpenAI/xAI) are gated on model-name prefixes, and Anthropic/Gemini fall back to a conservative thinking budget. If the catalog explicitly marks a model as non-thinking, the effort is a no-op rather than an error (surfaced as a `/effort` hint). ================================================================================ # Usage and cost Source: https://yottacode.ai/docs/cost/ `/usage` shows how many tokens the current session has spent — per model, plus a rolling total across every session created today — along with live rate-limit headroom and a provider-aware account block. **It does not show a dollar estimate.** Token *counts* are reported by the provider and are exact, but a dollar *figure* would require a price per model, and no provider exposes per-model pricing through the inference API. The only way to price tokens is a hand-maintained table that drifts the moment a provider changes rates or ships a new model — so rather than print a number we can't stand behind, `/usage` links each provider's billing dashboard, the authoritative source for spend. ## What `/usage` shows ``` session 20260530-153012.482917 usage by model: claude-opus-4-7: 265 input, 103,432 output, 22,503,118 cache read, 457,012 cache write claude-haiku-4-5: 1,200 input, 10,712 output, 1,310,484 cache read, 117,933 cache write total tokens 24,504,156 rate limits (live, from last response) tokens 1,824,000 / 2,000,000 remaining · resets in 41s requests 3,998 / 4,000 remaining · resets in 41s today total tokens 213,891 account provider: openai (pay-per-use API key) billing dashboard: https://platform.openai.com/usage — the source of truth for spend ``` The block renders in an inline overlay below the cmdline (the same surface the cheatsheet and the pickers use), not in chat scrollback — token tallies are transient inspection, not part of the conversation, so they never bloat the history the model re-reads. Press any key to dismiss it. The panel is read-only and safe to invoke mid-turn — it doesn't cancel a streaming response. The per-model breakdown is sorted by total tokens (highest first) and reuses the session's `ModelUsage` map. Sessions that mixed providers or models (Claude for code review, Gemini for grep) show each model's tokens separately. ### Live rate limits OpenAI, Anthropic, and xAI return per-minute rate-limit headers on **every** successful response — no admin key, no extra request. A client middleware (`internal/adapter/ratelimit.go`) snapshots them off each turn and `/usage` surfaces the latest as a "rate limits (live)" block: remaining/limit token and request headroom for the current window, with a reset countdown. The snapshot is in-memory and reflects the most recent response, so the block only appears after the first turn of a session and disappears on restart until the next turn. This is the one quota signal the providers *do* return on the inference key. The per-account **cost / spend** APIs (OpenAI's `/v1/organization/costs`, Anthropic's `/v1/organizations/cost_report`) need a separate **admin/org key** and report org-wide month-to-date totals with a lag — they can't give per-session cost — so `/usage` doesn't call them; the dashboard link covers that need. ## Why no dollar figure Pricing the tokens we count would mean shipping a per-model rate table and maintaining it by hand: - **No pricing API.** OpenAI, Anthropic, Gemini, and xAI publish rates on web pages, not through an endpoint the inference key can read. - **It drifts.** A bundled table is stale the moment a price changes, and it has no entry at all for a model released after the last update (and new models ship constantly). - **The list price isn't the invoice anyway.** Promotional credits, committed-use and enterprise discounts, batch pricing, and context-length tiers all move the real number in ways the inference API can't see. So a computed "≈$X" would be an unverifiable guess wearing the costume of an exact figure. The honest surface is exact token counts (which we have) plus a link to where the real dollars live. ## Per-provider behavior | Provider | What `/usage` shows | |---|---| | `anthropic`, `openai`, `gemini`, `xai`, `openai-compatible` (OpenRouter, Groq, …) | Per-model token counts + billing-dashboard link | | `openai-auth` (ChatGPT subscription) | Per-model token counts + plan/reset (best-effort `/backend-api/me` probe, 429-memo fallback) | | `copilot` (GitHub Copilot subscription) | Per-model token counts; no public quota endpoint | | `ollama` (local) | Token counts when the runtime reports them; no billing dashboard | | `openai-compatible` → NVIDIA NIM (`integrate.api.nvidia.com`) | Token counts only — local / credit-based | ### Billing dashboards `/usage`'s account block links each provider's public billing surface — the authoritative answer to "what did this cost": | Provider | Billing dashboard | |---|---| | Anthropic | `https://console.anthropic.com/settings/billing` | | OpenAI API | `https://platform.openai.com/usage` | | ChatGPT (`openai-auth`) | `https://chatgpt.com/account` | | Copilot | `https://github.com/settings/billing/summary` | | Gemini | `https://aistudio.google.com/app/billing` | | xAI | `https://console.x.ai/team` | For ChatGPT subscription accounts the `/backend-api/me` probe adds plan + email when the endpoint cooperates. It's undocumented and may change without notice; we cache the result for 5 minutes per process and silently fall back if a subsequent probe fails. Account identity is sourced from each provider's own API, never from config. Only `openai-auth` exposes one today (email + plan, via the probe above); API-key providers don't surface the key holder's name/email on the inference key, so `/usage` shows none for them. ## Where the data comes from - Each cloud adapter parses the provider's usage field on its final stream event (`message_delta` for Anthropic, `response.completed` for the OpenAI Responses APIs, the empty-`choices` chunk for Chat Completions with `stream_options.include_usage: true`, and `usageMetadata` for Gemini). - The neutral `adapter.Message.Usage` field carries normalized counts: `input_tokens`, `output_tokens`, `cache_creation_tokens`, `cache_read_tokens`, `reasoning_tokens`. - `session.Session.AddUsage(model, u)` sums each turn into `TotalUsage` plus a per-model breakdown. Sessions persist these alongside the message log in `~/.yottacode/sessions/.json`. - The `/usage` daily rollup scans the sessions directory and decodes only the metadata + usage fields (Messages stay on disk) so the command stays cheap to run. - `internal/cost/dashboards.go` maps each provider to its billing dashboard URL — the only piece of the former price catalog still in the tree. ## Backward compatibility The `Usage` field on `adapter.Message` is a pointer with `omitempty`; `Session.TotalUsage` uses `omitzero` and `Session.ModelUsage` uses `omitempty`. Session files written before the usage fields landed continue to load unchanged, and sessions that haven't recorded a turn yet stay byte-identical to the old shape on disk. ================================================================================ # Configuring models Source: https://yottacode.ai/docs/models/ A model id tells yottacode which model to send each turn to. Model names are provider-specific. ## Set the startup model Environment variable: ```bash export YOTTACODE_MODEL= ``` Flag: ```bash yottacode --model --base-url https://api.openai.com/v1 --api-key sk-... ``` Config file: ```toml [active] provider = "openai" default_model = "" ``` ## Switch models in the TUI ```text /model ``` This changes the active model for the current session and rebuilds the adapter. It does not necessarily rewrite your shell environment. ## Manage default models from the CLI ```bash yottacode model list yottacode model list --all yottacode model use yottacode model fetch yottacode model fetch openai ``` `model use` updates the configured active `default_model`. ## Fetch live models ```bash yottacode model fetch openai ``` This calls the provider `/models` endpoint and prints the merged model list. It is useful when checking auth, endpoint shape, or whether a newly released model is visible to your account. ## Reasoning models Some models expose reasoning streams or summaries: | Provider/model family | Behavior | |---|---| | OpenAI `o1*`, `o3*`, `o4*`, `gpt-5*` | Routed to Responses API when appropriate | | `openai-auth` account models | Use the ChatGPT-authenticated backend and surface reasoning summaries where available | | `copilot` account models | Use the GitHub Copilot backend; available models depend on subscription tier (Free/Pro/Pro+) | | xAI Grok reasoning models | Reasoning content is surfaced when present | | Ollama thinking models | Reasoning fields are surfaced when the OpenAI shim provides them | | Standard chat models | Stream final content only | Use `--reasoning-effort low|medium|high` when the selected provider/model supports it. ## Hosted tools by model/provider Hosted provider tools depend on provider support, not just the model name. - OpenAI: `web_search` default-on; `code_interpreter` optional - xAI: `web_search` default-on; `x_search` and `code_interpreter` optional - Ollama/custom OpenAI-compatible: no hosted provider tools; local yottacode tools still work ## Cache-safe task routing Routing lets yottacode run **isolated, throwaway work** (subagents and history compaction) on a cheap **fast** model while your main conversation stays on your chosen **smart** model. It is opt-in via the `[router]` block in `~/.yottacode/config.toml`: ```toml [router] mode = "auto" # off | manual | auto fast_model = "anthropic:claude-haiku-4-5" smart_model = "anthropic:claude-opus-4-6" ``` `fast_model` / `smart_model` use the same `""` or `":"` grammar as the multi-provider router's `candidates`. Both are required when `mode` is not `off`. Provider names refer to your `[[providers]]` blocks, and the model must be listed in that provider's `models` (typos are rejected at load time). ### Why this saves money (and never costs more) In an agentic loop the dominant cost is **re-sending the full context** (system prompt + files + history) on every turn. Prompt caching makes repeat turns on the *same* model cheap — cache reads are a fraction of the input price. Switching the **main-thread** model mid-conversation would throw that cache away on *both* models and cost *more*, so yottacode never does it. Routing only ever targets contexts that **never shared the main thread's cache** in the first place: - **Subagents** each build a fresh, isolated context window. - **Summarization / compaction** is a single isolated call. Running those on the fast model is a pure saving with zero cache churn. Your interactive turns are untouched. ### Modes | `mode` | Behavior | |---|---| | `off` (default) | Routing disabled. Everything runs on your active model. Fully backward compatible. | | `manual` | Resolves `fast_model` / `smart_model`, but only routes a subagent when its definition declares an explicit `model:` (see [subagents.md](/docs/subagents/)). Non-annotated agents inherit your active model, exactly as with routing off. | | `auto` | Routes by the agent's nature: **read-only / search subagents** (the `Explore` and `Plan` built-ins) and **summarization** → `fast_model`; **everything else** (the `general-purpose` and `verification` built-ins, or any agent that can mutate/run) → `smart_model`. | The `auto` heuristic is **deterministic and free** — it inspects each agent's declared tool allowlist, with no extra model call to classify the task. An agent restricted to read-only tools (read_file, grep, glob, git read subcommands, etc.) routes to `fast_model`; an agent that can mutate the workspace or run commands (`run_bash`, `write_file`, …) routes to `smart_model`. An explicit `model:` on an agent definition always wins over the heuristic. (Your **main conversation** is never affected either way — only subagents and summarization.) ### Seeing what ran where The model a subagent ran on is shown in the `/subagents` picker and on each subagent's completion card (`… · on claude-haiku-4-5`), so you can confirm at a glance that a search subagent used the fast model and a heavier one used the smart model. > Note: yottacode does not yet aggregate per-model token totals or cost > across a session — token figures shown are per-subagent estimates. ### Relationship to the multi-provider router The same `[router]` block also hosts the **multi-provider failover router** (`enabled`, `candidates`, `policy`, health knobs), which dispatches each *main-thread* turn across candidates with fallback. That is a separate, orthogonal feature: failover is about resilience across providers; task routing (`mode` / `fast_model` / `smart_model`) is about spending less on isolated work. They can be configured independently. ## No silent fallback If the model or base URL is missing, yottacode exits with a clear error. It does not silently default to localhost or a paid cloud provider. ## Choosing a model Practical starting points: - Local/privacy-first: Ollama with Qwen, Llama, or DeepSeek models - General coding: a strong OpenAI-compatible coding model - Deep planning: a reasoning model, with higher latency/cost - Scripting/CI: cheaper fast model plus low `--max-iterations` Use `/doctor` or `yottacode doctor` when a model is configured but not visible to the provider. ================================================================================ # Built-in tools Source: https://yottacode.ai/docs/tools/ Thirty-nine tools ship in `internal/agent` (thirty-seven always-on plus `todo_write` and `exit_plan_mode`). The model sees their JSON-schema parameters via the OpenAI tools API; the TUI renders each invocation as a bordered card with a verb-style header (see [How tool calls render in the TUI](#how-tool-calls-render-in-the-tui)). All paths are resolved against the agent's working directory (absolute paths are also accepted). In addition to the built-ins, **MCP tools** register dynamically when an `[[mcp_servers]]` block is present in `~/.yottacode/config.toml`. They appear as `mcp//` and flow through the same approval modal and permission rules — see [`mcp.md`](/docs/mcp/) and the `MCP(...)` rule shape in [`security-and-allow-lists.md`](/docs/security-and-allow-lists/#rule-shape). MCP tools default to approval-required unless the server explicitly hints them as read-only. | Tool | Approval | One-line summary | |---|---|---| | [`read_file`](#read_file) | none | Read a text or image file (png/jpg/gif/webp) with optional line offset/limit | | [`read_many_files`](#read_many_files) | none | Read multiple UTF-8 files in one call | | [`write_file`](#write_file) | required | Overwrite or create a file | | [`edit_file`](#edit_file) | required | Surgical `old_string`→`new_string` replacement | | [`apply_diff`](#apply_diff) | required | Apply a unified diff patch | | [`mkdir`](#mkdir) | required | Create a directory and missing parents | | [`copy_file`](#copy_file) | required | Copy a file to a new path | | [`move_file`](#move_file) | required | Move or rename a file or directory | | [`delete_file`](#delete_file) | required | Delete a file or empty directory | | [`list_git_changed_files`](#list_git_changed_files) | none | List changed files in the current repo | | [`git_branch_status`](#git_branch_status) | none | Show branch/upstream/dirty state | | [`git_show_file_at_rev`](#git_show_file_at_rev) | none | Read a file from a past revision | | [`git_diff_files`](#git_diff_files) | none | Show a diff for refs and/or files | | [`git_stage_files`](#git_stage_files) | required | Stage specific files or all changes | | [`git_unstage_files`](#git_unstage_files) | required | Unstage specific files | | [`git_create_branch`](#git_create_branch) | required | Create and switch to a new branch | | [`git_commit`](#git_commit) | required | Commit staged changes | | [`git_commit_context`](#git_commit_context) | none | Typed snapshot for drafting a commit message (paired with `git_commit_apply`) | | [`git_commit_apply`](#git_commit_apply) | required | Validate a one-line subject and run `git commit` with structured result envelope | | [`gh_pr_context`](#gh_pr_context) | none | Typed snapshot for opening a PR (base resolution, ahead-count, push state, gh availability, PR template) | | [`gh_pr_create`](#gh_pr_create) | required | Validate title and open a PR via the `internal/github.Interface` adapter with typed result envelope | | [`gh_pr_read`](#gh_pr_read) | none | Fetch PR metadata only via one API call (use instead of `run_bash gh pr view --json …`) | | [`gh_pr_review_context`](#gh_pr_review_context) | none | Fetch PR metadata + diff + check rollup via the `internal/github.Interface` adapter for review | | [`gh_pr_update`](#gh_pr_update) | required | Rewrite an existing PR's title and body via the `internal/github.Interface` adapter; title validation + non-empty-body guard | | [`gh_pr_add_comment`](#gh_pr_add_comment) | required | Post a top-level conversation comment on a PR; body capped, approval-gated | | [`gh_issue_read`](#gh_issue_read) | none | Fetch issue metadata + comments (use instead of `run_bash gh issue view --json …`) | | [`gh_issue_list`](#gh_issue_list) | none | List open issues matching label/assignee/milestone filters | | [`git_push`](#git_push) | required | Push the current branch to origin with deterministic upstream detection; surfaces the PR URL when one exists | | [`git_log_file`](#git_log_file) | none | Show history for one file | | [`git_blame_lines`](#git_blame_lines) | none | Blame a line range in a file | | [`git_merge_base`](#git_merge_base) | none | Find merge base between two refs | | [`git_checkpoint`](#git_checkpoint) | required | Create a local checkpoint commit | | [`rollback`](#rollback) | required | Reset the repo to an earlier commit | | [`run_tests`](#run_tests) | none | Run the repo's test command | | [`list_dir`](#list_dir) | none | One-line-per-entry directory listing | | [`glob`](#glob) | none | Doublestar pattern match | | [`grep`](#grep) | none | Ripgrep (or GNU grep fallback) | | [`fetch_url`](#fetch_url) | none | Fetch a single HTTP(S) URL and return capped textual content | | [`run_bash`](#run_bash) | required | Shell command via `/bin/sh -c` | | [`git`](#git) | varies | Unified git invocation; read-only auto-runs, mutations prompt | | [`todo_write`](#todo_write) | none | Maintain the agent's working task plan, rendered as a card | | [`exit_plan_mode`](#exit_plan_mode) | required | Only callable in `/plan` mode; presents the plan for user approval | | [`Agent`](#agent) | none | Dispatch a typed subagent that runs in its own context window; see [subagents.md](/docs/subagents/) | "Approval = required" means the tool always pauses for a `y` / `a` / `N` from the user, unless an `allow` rule in `/.yottacode/permissions.json` (or its gitignored `.local.json` sibling) matches the call, or `--yolo` is set (DANGEROUS). See [architecture.md](/docs/architecture/) for the approval round-trip and the permissions schema. ## How tool calls render in the TUI ### Every tool call renders as a bordered card with three regions: ``` go test ./internal/tui/ -run TestDemo_CardOutput -v go test ./internal/tui/ -run TestDemo_CardOutput -v | sed -n '/^─── /,/^--- PASS/p' | sed '/^--- PASS/d' ``` ```text ╭ Verb(arg) │ ╰ ``` The gutter glyphs (`╭ │ ╰`) render dim; the header is bold; the footer is dim, with `exit 0` in green and `exit N≠0` / `✗ ` in bold red. Body rows are indented three columns under the gutter so the shape reads as "header, indented content, footer." **Header verbs.** The raw tool name still appears in the agent's tool-call log; the TUI renames it for readability. Mapping: | Tool | Header | |---|---| | `run_bash` | `Bash()` | | `read_file` | `Read()` or `Read( @ L+)` (images: `Read()`) | | `read_many_files` | `Read(N files)` | | `write_file` | `Write()` | | `edit_file` | `Edit(, single\|all)` | | `apply_diff` | `Patch(apply)` | | `mkdir` | `Mkdir()` | | `copy_file` | `Copy()` | | `move_file` | `Move()` | | `delete_file` | `Delete()` | | `list_dir` | `List()` | | `glob` | `Glob()` or `Glob( in )` | | `grep` | `Grep("" in )` | | `fetch_url` | `Fetch()` | | `run_tests` | `Test()` | | `rollback` | `Rollback()` | | `git` | `Git( )` | | `git_branch_status` | `Git(branch status)` | | `git_show_file_at_rev` | `Git(show @ )` | | `git_diff_files` | `Git(diff ..)` | | `git_stage_files` / `git_unstage_files` | `Git(stage N files)` or `Git(stage all)` / `Git(unstage N files)` | | `git_create_branch` | `Git(create branch )` or `Git(create branch from )` | | `git_commit` | `Git(commit)` | | `git_log_file` | `Git(log )` | | `git_blame_lines` | `Git(blame :L-L)` | | `git_merge_base` | `Git(merge-base ..)` | | `git_checkpoint` | `Git(checkpoint)` | | `list_git_changed_files` | `Git(list changed)` | ASCII control characters inside an arg (a stray `\n` in a path, a tab in a filename, etc.) are stripped before the header renders, so a malformed arg can never break the card's box shape. Long bash commands are clipped to fit the terminal width with a `…)` tail. **Body.** Carries the tool's interesting output: directory entries, grep matches, command stdout, diff hunks. Capped at 10 visible lines with a trailing `…N more line(s)` notice — the model still receives the full output via the agent's tool-result event. A few tools have card-specific body shapes: - **`run_bash` / `run_tests` / `git`** split their output into stdout, a `── stderr ──` separator, and stderr. The footer carries the process exit code (green when zero, red otherwise). - **`edit_file`** renders a syntax-highlighted `-` (red) / `+` (green) diff in the body instead of the textual confirmation. - **`git`** with a destructive flag (`--force`, `--hard`, `-D`, `--delete`, …) prepends a bold-red `⚠ DESTRUCTIVE FLAG(S): ` row to the body so it's hard to `y` past by reflex. - **`fetch_url`** drops the raw HTTP body and shows only the response metadata (`Status`, `Content-Type`, optional truncation note). The footer reports the response body size. The model still receives the full content; the user is spared 64+ KiB of minified markup. - **`read_file` / `write_file`** show no body — the footer's `N lines · M bytes` / `wrote N bytes` carries the entire signal. **Footer.** Summarizes the call: `N entries`, `wrote N bytes`, `N lines · M bytes [(truncated)]`, `N matches`, `exit N` (colored), or the tool's confirmation message. When the call errored, the footer renders `╰ ✗ ` in bold red and the body shows the raw error verbatim (per-tool body shaping is bypassed for errors). --- ## read_file Read a text or image file. For text files, output is `cat -n` style (1-indexed line numbers). For image files (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`), the image data is returned as a native visual content block that vision-capable models can see directly. | Param | Type | Default | Notes | |---|---|---|---| | `path` | string | — | Absolute or cwd-relative | | `offset` | int | `1` | 1-indexed start line (text files only) | | `limit` | int | `2000` | Max lines to return (text files only) | **Image support.** When the path points to a recognized image file and the provider supports images in tool results (currently Anthropic only), the tool reads the raw bytes (up to 20 MiB) and returns them as an image content block alongside a text label like `[image: photo.png, image/png, 12345 bytes]`. On providers that don't support images in tool results, only the text label is returned. The same deny list applies to images. No approval — the model legitimately needs to read dotfiles, USER.md, `/etc/os-release`, etc. A narrow deny list still applies: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.netrc`, `~/.yottacode/.env`, `~/.kube/config`, `~/.docker/config.json`, `~/.config/gh/hosts.yml`, `~/.config/gcloud`, `/.env`, `/.env.local`. Reading those returns an error — closes the silent prompt-injection exfiltration vector. Use `run_bash` (which prompts) if you really need them. ## read_many_files Read multiple UTF-8 text files in one call. Useful when the model needs context from a handful of related files without paying for many separate round-trips. | Param | Type | Default | Notes | |---|---|---|---| | `paths` | []string | — | Required; max 20 files | | `offset` | int | `0` | Bytes; negatives clamped to 0 | | `limit` | int | `524288` | Per-file cap | Returns sections in the form: ```text ==> path/to/file <== ``` Each file gets its own `[truncated]` marker if needed. ## write_file Full overwrite. Creates parent directories as needed. | Param | Type | Default | Notes | |---|---|---|---| | `path` | string | — | Absolute or cwd-relative | | `content` | string | — | Full new file contents | Always prompts for approval. The preview shows the path and a 200-char content snippet. ## edit_file Surgical string replacement. Fails when `old_string` matches zero or more-than-one place (uniqueness check), unless `replace_all=true`. | Param | Type | Default | Notes | |---|---|---|---| | `path` | string | — | Must exist | | `old_string` | string | — | Must be non-empty and != `new_string` | | `new_string` | string | — | The replacement | | `replace_all` | bool | `false` | Disable uniqueness check | Always prompts for approval. The TUI's approval modal renders a colored diff (red `−` / green `+`) so you see exactly what's about to change. ## apply_diff Apply a unified diff patch using `git apply`. This is better than `edit_file` for multi-hunk changes across one or more files. | Param | Type | Default | |---|---|---| | `diff` | string | — | Always prompts for approval. The diff header is parsed and each touched file is run through the same write-path validator `write_file` / `edit_file` use — yottacode-managed state, `.git` internals, paths outside cwd, and symlinks are refused before `git apply` runs. A `Deny(Edit())` rule applies if any target path matches; an `Allow(Edit())` rule auto-approves only when every target path matches (mixed-path diffs still prompt). ## mkdir Create a directory and any missing parents. | Param | Type | Default | |---|---|---| | `path` | string | — | Always prompts for approval. ## copy_file Copy a file from `src` to `dst`. Creates destination parent directories if needed. | Param | Type | Default | |---|---|---| | `src` | string | — | | `dst` | string | — | Always prompts for approval. ## move_file Move or rename a file or directory. Creates destination parent directories if needed. | Param | Type | Default | |---|---|---| | `src` | string | — | | `dst` | string | — | Always prompts for approval. ## delete_file Delete a file or an empty directory. | Param | Type | Default | |---|---|---| | `path` | string | — | Always prompts for approval. ## list_git_changed_files List changed files in the current git repo, combining staged, unstaged, and optionally untracked files. | Param | Type | Default | |---|---|---| | `staged` | bool | `true` | | `unstaged` | bool | `true` | | `untracked` | bool | `true` | No approval. ## git_branch_status Show the current branch, upstream, ahead/behind counts, and whether the working tree is dirty. This is a compact status helper for coding sessions where the model wants repo state without parsing full `git status` output. No parameters. No approval. ## git_show_file_at_rev Read a file from a specific git revision without changing the working tree. Useful for regressions, comparisons, and historical inspection. | Param | Type | Default | |---|---|---| | `path` | string | — | | `rev` | string | `HEAD` | No approval. ## git_diff_files Show a diff for specific refs and/or file paths. | Param | Type | Default | Notes | |---|---|---|---| | `base` | string | current working tree | Optional base revision | | `head` | string | — | Optional head revision | | `paths` | []string | — | Restrict diff to one or more files | Examples: - diff current working tree: omit both `base` and `head` - diff one revision vs working tree: set `base` - diff two revisions: set both `base` and `head` No approval. ## git_stage_files Stage specific files (`git add -- ...`) or all changes (`git add -A`). | Param | Type | Default | Description | |---|---|---|---| | `paths` | []string | — | Paths to stage. Mutually exclusive with `all`. | | `all` | bool | `false` | Stage all tracked, untracked, and deleted files (`git add -A`). Mutually exclusive with `paths`. | Provide either `paths` or `all`, not both. Returns `staged N file(s)` for path mode or `staged all changes` for the bulk mode. Always prompts for approval. ## git_unstage_files Unstage specific files with `git reset HEAD -- ...`. | Param | Type | Default | |---|---|---| | `paths` | []string | — | Always prompts for approval. ## git_create_branch Create a new local branch and switch HEAD to it (`git switch -c []`). The branch name is validated via `git check-ref-format --branch` before any switch happens, and the tool refuses with a `branch_exists` error if a local branch by that name already exists — it never overwrites or fast-forwards. The working tree is left as-is (no precheck against dirty state); git itself will refuse the switch if local changes would be clobbered. | Param | Type | Default | Description | |---|---|---|---| | `name` | string | — | Branch name to create (required). | | `start_point` | string | `HEAD` | Optional starting ref (commit / branch / tag). | Returns `created=true branch= from=` on success. Always prompts for approval. ## git_commit Create a commit from the currently staged changes. | Param | Type | Default | |---|---|---| | `message` | string | — | Always prompts for approval. ## git_commit_context Composite read-only snapshot used to draft a one-line commit message without parsing bash heredoc output. Returns labeled sections under `## state`, `## staged.name-status`, `## staged.diff`, `## recent.subjects`, `## branch.commits`, `## prose`, `## unstaged`, and `## untracked`. The `## state` block carries the deterministic `staged_empty=`, `detected_style=` (one of `conventional` / `ticket-prefix` / `plain`, chosen by majority over the last 15 subjects), and `branch=` fields so callers branch on typed flags rather than text inference. Pair with [`git_commit_apply`](#git_commit_apply) — context tool gathers state, apply tool validates and commits. | Param | Type | Default | |---|---|---| | _(none)_ | | | No approval. Parallel-safe. ## git_commit_apply Composite mutator that validates a one-line subject and runs `git commit -F -` against the currently staged changes. The following rejections fire **before** invoking git (deterministic Go, not model judgment): - empty staging (`committed=false reason=staged_empty`) - empty / whitespace-only message - multi-line message (no body or footer accepted) - subject longer than 72 characters - subject ending in a period Returns a typed envelope. On success: `committed=true sha=` plus post-commit `## unstaged` / `## untracked` sections. On pre-commit hook failure: `committed=false reason=hook_error` followed by the hook's verbatim output. The tool never auto-retries, auto-stages, or amends. | Param | Type | Default | |---|---|---| | `message` | string | — | Always prompts for approval. ## gh_pr_context Composite read-only snapshot used to open a pull request without parsing multi-step bash output. Returns labeled sections under `## state`, `## diff.stat`, `## commits.log`, and `## pr.template`. The `## state` block carries deterministic flags callers branch on: `resolved_base=`, `base_resolution=` (one of `explicit`, `origin-head`, `fallback:`, `unresolved`), `current_branch=`, `base_equals_current=`, `ahead_count=`, `pushed_to_origin=`, and `gh_available=`. Base resolution priority: explicit `base` argument → `origin/HEAD` symbolic ref → first of `main` / `master` / `develop` that exists locally. The `pushed_to_origin` check uses `git ls-remote --exit-code --heads origin ` so empty-remote repos correctly report `false` without crashing. Pair with [`gh_pr_create`](#gh_pr_create) — context tool gathers state, create tool validates the title and opens the PR. | Param | Type | Default | |---|---|---| | `base` | string | (auto-resolved from `origin/HEAD` then fallback chain) | No approval. ## gh_pr_create Composite mutator that validates a PR title and opens the pull request through the typed `internal/github.Interface` adapter. The following rejections fire **before** dialing the adapter (deterministic Go, not model judgment): - empty title / body / base (`created=false reason=validation`) - multi-line title - title longer than 72 characters - title ending in a period Returns a typed envelope. On success: `created=true url= number=`. On a missing or unauthenticated `gh` CLI: `created=false reason=gh_unavailable` so the procedural `/git-create-pr` can fall through to draft-only output without surfacing an opaque exec failure. On other gh errors: `created=false reason=gh_error` followed by the gh output verbatim. The tool never auto-retries, auto-edits, or auto-merges. The adapter behind this tool is `internal/github.TypedClient`, backed by the `go-github/v66` REST client. Auth resolves through a three-tier precedence chain: `$GITHUB_TOKEN` env var → `gh auth token` shell-out (one-shot, cached for the session) → `~/.yottacode/github.json` (yottacode-native PAT, written by a future `yottacode setup github` flow). The `gh` CLI is no longer required for API calls — only optionally used to source the token when `$GITHUB_TOKEN` isn't set. | Param | Type | Default | |---|---|---| | `base` | string | — | | `title` | string | — | | `body` | string | — | | `draft` | bool | `false` | Always prompts for approval. ## gh_pr_review_context Composite read-only fetcher for the procedural `/git-review-pr` flow. Calls the three Interface read methods (`ReadPR`, `ListPRChecks`, `ReadPRDiff`) and folds their results into one typed snapshot under `## state`, `## pr`, `## checks.summary`, `## checks`, and `## diff` headers. The `## state` block carries deterministic flags callers branch on: `ref=`, `not_found=` (the gh CLI couldn't resolve the ref to an existing PR), `gh_unavailable=` (gh missing or unauthenticated), and `failing_checks=` (comma-separated names of check runs whose conclusion was FAILURE, CANCELLED, TIMED_OUT, or ACTION_REQUIRED). When `not_found` or `gh_unavailable` are true the snapshot short-circuits — the pr/checks/diff sections are omitted to keep the model's branching clean. Failing-check classification covers both the GraphQL check-run shape (Conclusion populated) and the legacy status-context shape (State="FAILURE"/"ERROR" with empty Conclusion). The diff is capped at 64 KiB with a truncation marker pointing at `gh pr diff ` for the full content. | Param | Type | Default | |---|---|---| | `ref` | string | (uses current branch's PR) | No approval. Touches the network (not parallel-safe). ## gh_pr_update Composite mutator that rewrites an existing PR's title and body. Paired with the procedural `/git-update-pr` slash command for the "follow-up commits made the original description stale" workflow. Deterministic guarantees: - **Title validation** (reuses `validatePRTitle` from `gh_pr_create`): rejects empty, multi-line, oversize (>72 chars), and trailing-period titles before dialing the adapter. - **Non-empty body guard:** empty body would clobber the existing PR description, which is almost never intended. Caught in Go with `updated=false reason=validation`. - **Scope-pinned:** only edits title and body. Labels, base, reviewers, draft state, milestone, and projects are not accepted — `/git-update-pr` enforces the same scope at the prompt level. Returns a typed envelope. On success: `updated=true url= number=`. On a missing PR: `updated=false reason=not_found` with a hint pointing at `/git-create-pr`. On gh-unavailable: `updated=false reason=gh_unavailable`. On other gh errors: `updated=false reason=gh_error` with the gh output verbatim. The tool never auto-retries, auto-edits other fields, or auto-merges. | Param | Type | Default | |---|---|---| | `ref` | string | (uses current branch's PR) | | `title` | string | — | | `body` | string | — | Always prompts for approval. ## git_push Composite mutator that pushes the current branch to origin. Deterministic guarantees: - **Upstream-aware:** detects whether the current branch already tracks an upstream and adds `-u origin HEAD` only on first push. - **Detached-HEAD early exit:** rejects the push *before* invoking git when HEAD isn't on a branch — returns `pushed=false reason=detached_head`. - **No force-push surface:** `--force` / `--force-with-lease` are intentionally not accepted. Use the unified `git` tool when you actually need force-push. Returns a typed envelope. On success: `pushed=true branch= set_upstream=` plus a best-effort PR-URL lookup via `internal/github.Interface.ReadPR`. When a PR exists for the branch, `pr_number=` and `pr_url=` are populated so callers (and `/git-push`) can surface a "PR updated" footer. When no PR exists, the envelope hints at `/git-create-pr` as the next step. Git errors (exit non-zero) populate `pushed=false reason=git_error` with the verbatim git output. The tool never auto-retries, auto-force-pushes, or rebases to "fix" a rejection. | Param | Type | Default | |---|---|---| | _(none)_ | | | Always prompts for approval. ## git_log_file Show history for a single file. | Param | Type | Default | |---|---|---| | `path` | string | — | | `limit` | int | `10` | No approval. ## git_blame_lines Show blame output for a line range in a file. | Param | Type | Default | |---|---|---| | `path` | string | — | | `start` | int | — | | `end` | int | — | No approval. ## git_merge_base Find the merge base between two refs. | Param | Type | Default | |---|---|---| | `base` | string | — | | `head` | string | — | No approval. ## git_checkpoint Create a local checkpoint commit from all current changes. | Param | Type | Default | |---|---|---| | `message` | string | `checkpoint` | Always prompts for approval. ## rollback Reset the git working tree to a target commit. Defaults to `HEAD~1`. This is destructive and discards uncommitted changes. | Param | Type | Default | |---|---|---| | `target` | string | `HEAD~1` | Always prompts for approval. ## run_tests Run a test command in the repo. Defaults to `go test ./...`. | Param | Type | Default | |---|---|---| | `command` | string | `go test ./...` | | `path` | string | `.` | No approval. ## list_dir One line per entry: `\t` where `` is `d` (dir), `f` (file), or `l` (symlink). Capped at 100 entries. | Param | Type | Default | |---|---|---| | `path` | string | `.` | No approval. ## glob Doublestar pattern match (`**` recursive). Returns paths relative to the cwd. Capped at 200 results. | Param | Type | Default | Notes | |---|---|---|---| | `pattern` | string | — | e.g. `**/*.go`, `internal/agent/*.go` | | `cwd` | string | tool's cwd | Roots the search | No approval. ## grep Prefers `rg` (ripgrep); falls back to GNU `grep -E`/`-F`. Pattern arguments are passed via argv — no shell, no injection. | Param | Type | Default | Notes | |---|---|---|---| | `pattern` | string | — | Required | | `path` | string | `.` | File or directory | | `regex` | bool | `false` | When false, treats pattern as a fixed string | | `ignore_case` | bool | `false` | | | `max_results` | int | `50` | Hard cap | Output is capped at 256 KiB. Exit code 1 (no matches) is treated as "no results", not as an error. No approval. ## fetch_url Fetch a single HTTP or HTTPS URL and return capped textual content. This is the local-network fallback for models that do not have provider-native hosted web search. | Param | Type | Default | Notes | |---|---|---|---| | `url` | string | — | Required; must start with `http://` or `https://` | | `max_bytes` | int | `65536` | Hard cap is `262144`; larger values clamp to the default | The tool only returns textual content types such as HTML, plain text, JSON, XML, and JavaScript responses. Binary content types are rejected. ## run_bash Run a shell command via `/bin/sh -c` in the session's cwd. | Param | Type | Default | Notes | |---|---|---|---| | `command` | string | — | Passed verbatim to `/bin/sh -c` | Always prompts for approval. Output is `exit=\n--- stdout ---\n…\n--- stderr ---\n…`, each stream capped at 1 MiB; truncation is announced in the result. There is no in-process sandbox, and there will not be one — yottacode keeps its core small and does not ship bwrap/firejail/landlock backends. For real isolation, run yottacode inside a container or devcontainer. ## git Unified git tool. Args are passed as a JSON string array, never via the shell, so there's nothing to escape and nothing to inject. | Param | Type | Default | Notes | |---|---|---|---| | `args` | []string | — | e.g. `["status"]`, `["log", "--oneline", "-n", "5"]` | Approval policy is **based on the first arg**: - **Auto-execute** (read-only): `status`, `diff`, `log`, `show`, `blame`, `grep`, `ls-files`, `rev-parse`, `branch --show-current`, etc. - **Prompt for approval** for everything else (`commit`, `push`, `pull`, `branch -D`, `checkout`, `reset`, `rebase`, `merge`, …). Destructive flags (`--force`, `-f`, `-D`, `--hard`, `--delete`, …) are called out in the approval preview with a `⚠ DESTRUCTIVE FLAG(S):` prefix so you don't `y` past them by reflex. Stdout is capped at 1 MiB; stderr at 64 KiB. ## todo_write Maintain the agent's working task plan. The list is owned by the session and rendered as a live card in the TUI's in-flight area — updates in place on every `todo_write` call, so the user sees status flips without each call stacking a new card. At turn end one final-state snapshot lands in scrollback as the historical receipt for that turn. | Param | Type | Default | Notes | |---|---|---|---| | `todos` | []object | — | Complete plan; previous list is replaced wholesale | Each item has `content` (short human-readable description) and `status` (`pending` / `in_progress` / `completed`, at most one `in_progress`). No filesystem or network side effects — purely a visibility primitive, so it never prompts for approval. The model is instructed to call `todo_write` proactively for any task with three or more distinct steps and to update it as soon as each step finishes. When work has both a design/research phase and an execution phase, the todo list must end at the design-presentation step — implementation todos are only added in a follow-up `todo_write` call after the user has explicitly agreed to the design, so the plan never pre-stages unapproved work. Pass an empty list to clear the plan; the live card disappears and no end-of-turn snapshot is emitted. ## exit_plan_mode Only callable while `/plan` mode is active (the registry hides it from the adapter schema otherwise). Takes no arguments — the TUI reads the plan body from the resolved plan file on disk and renders it in the approval card. Matches Claude Code's `ExitPlanMode` shape exactly. | Param | Type | Default | Notes | |---|---|---|---| | _(none)_ | — | — | The plan content comes from `~/.yottacode/plans/.md` | If the plan file doesn't exist or is empty when `exit_plan_mode` is called, the TUI auto-denies the call with a console notice — the model is expected to write the plan to the file first, then call this tool. The TUI renders the plan inside an approval card with four hotkeys: - `[A] auto-approval` — exits plan mode AND turns on auto mode for the implementation, so mutating tools auto-allow without per-call prompts (safety floor still applies: `run_bash`, `git_commit`, `git_checkpoint`, `rollback`). - `[M] manual approval` — exits plan mode and the agent resumes execution, but per-tool prompts continue as normal so you can review each step. - `[L] later` — exits plan mode but ends the turn; the plan stays on disk for resume via `/plan list` or `--plan-resume`. - `[K] keep planning` — stays in plan mode; model receives refinement guidance. `--yolo` does NOT skip this approval — that approval is the user-visible signal, not a safety gate. While `/plan` is active, every other mutating tool is blocked except writes to the resolved plan file under `~/.yottacode/plans/.md`. Read-only tools auto-allow as usual; writes to the plan file auto-allow too (no per-edit prompt — the plan file is the model's only legitimate mutation surface during planning). See [tui-slash-commands.md#plan-mode](/docs/tui-slash-commands/#plan-mode) for the full plan-mode flow. ## Agent Dispatch a typed subagent. The subagent runs `agent.Turn` in its own message history, with a filtered tool registry and its own iteration budget. The parent's adapter context never sees the subagent's intermediate reasoning or tool calls — only the child's final reply is returned as the tool result. Mirrors Claude Code's `Agent` / `Task` tool surface. | Param | Type | Default | Notes | |---|---|---|---| | `subagent_type` | string (required) | — | The name of a registered agent definition (e.g. `general-purpose`, `Explore`, `Plan`, or a custom entry under `.yottacode/agents/`). | | `prompt` | string (required) | — | The task for the subagent. The subagent has no access to the parent conversation, so be self-contained. | | `description` | string | "" | A 3-5 word label shown to the user while the subagent runs. | | `run_in_background` | boolean | `false` | If true (TUI only), return immediately with a task id; the subagent runs to completion in the background. `oneshot` rejects this with a recoverable error so the model can retry without the flag. | The full documentation — file format for custom agents, the `/subagents` command, transcript layout, and the recursion + iteration safeguards — is in [subagents.md](/docs/subagents/). ================================================================================ # MCP servers Source: https://yottacode.ai/docs/mcp/ yottacode is a client for Anthropic's [Model Context Protocol](https://modelcontextprotocol.io/). Configure an MCP server in `~/.yottacode/config.toml` and its tools register alongside yottacode's native tools — same approval modal, same permission rules, same dispatch path. The model doesn't know (or care) which tools are native vs MCP. > [!NOTE] > v1 supports **stdio transport only** — yottacode launches each server as a local subprocess. Remote HTTP/SSE servers aren't supported yet. ## What v1 ships v1 covers the part of the MCP ecosystem most users actually need: - **Stdio transport only.** yottacode launches each MCP server as a subprocess and talks JSON-RPC over stdin/stdout. Covers every server published as `@modelcontextprotocol/server-*` and most community servers. Remote servers that only expose HTTP/SSE or Streamable HTTP endpoints (e.g. `mcp.excalidraw.com`) are not supported yet — you must run a local subprocess. - **Non-blocking startup.** MCP servers initialize in the background after the TUI renders. A slow server (e.g. `npx -y` downloading a package for the first time) does not block the prompt. `/mcp` shows "starting..." for servers still initializing; tools register automatically once they come up. - **Tools only.** MCP's `resources` and `prompts` primitives are deferred — they're rare in practice and add real surface. Most servers ship only tools anyway. - **Per-tool approval.** Each MCP tool defaults to the approval modal. Servers can hint a tool as read-only (`annotations.readOnlyHint: true`) and yottacode honors that — read-only tools auto-execute. Users can still elevate trust with explicit permission rules. - **Subprocess sandboxing inherited from your OS.** yottacode does not run MCP servers in an isolated container. Each subprocess inherits yottacode's environment and filesystem permissions. Treat MCP servers as you'd treat any binary you choose to run. Deferred to follow-ups: - **HTTP/SSE transport** — needed for remote MCP servers that don't run as local subprocesses. This is a transport-level addition; many remote servers (like Excalidraw) don't require auth, so transport alone would unblock them. - **OAuth 2.1 device-flow auth** — needed on top of HTTP transport for remote servers that require authentication. - **MCP resources and prompts** — rarely used in practice; most servers only expose tools. ## Configuration Add one `[[mcp_servers]]` block per server in `~/.yottacode/config.toml`. Each entry needs at minimum a `name` and a `command`: ```toml [[mcp_servers]] name = "filesystem" command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/workspace"] [[mcp_servers]] name = "memory" command = "npx" args = ["-y", "@modelcontextprotocol/server-memory"] env = { MEMORY_FILE = "$HOME/.yottacode/mcp-memory.json" } [[mcp_servers]] name = "github" command = "npx" args = ["-y", "@modelcontextprotocol/server-github"] env = { GITHUB_PERSONAL_ACCESS_TOKEN = "$GITHUB_PAT" } disabled = false ``` Fields: | Field | Required | Notes | |---|---|---| | `name` | yes | Unique. Lowercase letters, digits, `-`, `_`. Must start with a letter. Used in tool names and `/mcp`. | | `command` | yes | Executable (resolved via `PATH` at session start). | | `args` | no | Forwarded verbatim. | | `env` | no | Per-server env vars layered on top of yottacode's inherited environment. Values support `$VAR` substitution from the process env — keep secrets out of the config file. | | `disabled` | no | `true` skips this entry at session start. | Unknown keys are rejected at load time so typos like `trnsport` surface immediately instead of silently disabling a feature. ## Tool namespacing Every MCP tool registers as `mcp//`. So the GitHub server's `create_pull_request` becomes `mcp/github/create_pull_request` — distinct from yottacode's native `gh_pr_create`. The namespacing also makes permission rules predictable; see below. ## Approval and permissions Default policy: the approval modal fires on every MCP tool call. If the server explicitly hinted a tool as read-only, yottacode skips the modal — that's a deliberate trust delegation to the server, not a security guarantee. Treat server-declared annotations as suggestions, not contracts. Elevate trust (or restrict it) with `MCP(/)` rules in `.yottacode/permissions.json` or `.yottacode/permissions.local.json`: ```json { "permissions": { "allow": ["MCP(filesystem/read_*)", "MCP(memory/*)"], "ask": ["MCP(github/*)"], "deny": ["MCP(*/delete_*)"] } } ``` Precedence is the same as native tools: `deny` > `ask` > `allow` > default. `MCP(*)` is a valid pattern (covers everything) but you almost never want it — narrow rules per server are cheap to maintain and minimize blast radius. ## `/mcp` slash command | Command | Effect | |---|---| | `/mcp` | List configured servers, their start status, and tool count. | | `/mcp add --command [args...]` | Add a server to `config.toml`, start it immediately, and register its tools — no restart needed. Everything after `--command` is the executable + arguments. | | `/mcp remove ` | Remove a server from `config.toml`. Requires a restart to take effect. | | `/mcp logs ` | Show the last ~200 lines of the server's stderr (helpful when a server crashes during init or under load). | | `/mcp restart ` | Stop the named subprocess, respawn it from the stored config, and swap the registered tools to the fresh client. The tool surface may shrink or grow if the server's catalog changed between generations. | Examples: ``` /mcp add podman --command npx -y podman-mcp-server@latest /mcp add filesystem --command npx -y @modelcontextprotocol/server-filesystem /home/me/workspace /mcp add excalidraw --command node /path/to/excalidraw-mcp/dist/index.js --stdio ``` ## Non-text tool results MCP servers can return image, audio, resource-link, or embedded-resource content alongside (or instead of) text. yottacode's v1 bridge passes **text content through verbatim**; non-text blocks are replaced with explicit placeholder markers like `[image omitted: image/png, 1024 bytes — yottacode v1 tools-only bridge passes text only]`. The model sees the marker and learns the call succeeded but the data wasn't text — far safer than receiving a silent empty string and retrying or hallucinating. If a server uses MCP's `structuredContent` field (typed JSON output) without populating `content`, the bridge JSON-marshals the structured payload as a fallback so it isn't lost. Full multi-modal passthrough (image / audio bytes forwarded to a vision-capable model adapter) is deferred to a follow-up wedge. ## What yottacode does NOT track > [!CAUTION] > yottacode's checkpoints (`/checkpoints`, `Esc Esc`) can't snapshot or restore MCP-driven changes (databases, external APIs, filesystem-via-server) — same limitation as `run_bash`. Use the underlying system's own rollback (transactions, git, etc.). MCP tools mutate state outside yottacode's file model (databases, external APIs, filesystem-via-server). yottacode's checkpoint system (`/checkpoints`, `Esc Esc`) cannot snapshot or restore MCP-driven changes — same limitation as `run_bash`. If you need rollback, use the underlying system's own mechanisms (database transactions, git, etc.). ## Running a server {{< tabs items="npm package,Build from source" >}} {{< tab >}} Most servers are published to npm — run them with `npx` (no install step): ```toml [[mcp_servers]] name = "filesystem" command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/workspace"] ``` {{< /tab >}} {{< tab >}} Some servers aren't on npm and must be cloned and built locally. Build steps vary per server — check its README. Typical pattern for Node.js servers: ```bash git clone https://github.com//.git cd pnpm install && pnpm run build ``` Then use `node` (or the appropriate runtime) as the `command` and point `args` at the built entrypoint (most stdio servers accept a `--stdio` flag): ```toml [[mcp_servers]] name = "excalidraw" command = "node" args = ["/home/me/excalidraw-mcp/dist/index.js", "--stdio"] ``` {{< /tab >}} {{< /tabs >}} ## Curated test servers Useful starting points; each is published as an `npx`-runnable npm package. | Server | Args | Exercises | |---|---|---| | `@modelcontextprotocol/server-filesystem` | `-y ... /path/to/workspace` | Stdio baseline, mixed read/write, approval modal on writes. | | `@modelcontextprotocol/server-memory` | `-y ...` | Stateful subprocess for the session lifetime. | | `@modelcontextprotocol/server-fetch` | `-y ...` | One read-only tool, minimal-server conformance. | | `@modelcontextprotocol/server-sqlite` | `-y ... /path/to/test.db` | Mutation approval against a local file resource. | ## Subprocess cleanup on hard exit On a graceful yottacode shutdown (TUI quits, Ctrl+C handled), MCP subprocesses receive a `shutdown` notification, then SIGTERM with a 3s grace, then SIGKILL. On Linux, yottacode also sets `PR_SET_PDEATHSIG = SIGTERM` so the kernel signals the children automatically if yottacode itself dies ungracefully (SIGKILL, panic, crash). On macOS and BSD there's no equivalent prctl in the Go stdlib — an ungraceful yottacode exit on those platforms can leave MCP subprocesses parented to launchd / init. `ps`/`pgrep` + `kill` is the manual cleanup path there. ## Troubleshooting - **`command not found` at session start.** yottacode resolves `command` via `PATH`. Use an absolute path if the binary lives elsewhere. - **Server crashes during init.** Check `/mcp logs ` for the stderr — npm install errors, missing env vars, or bad args show up there. - **Tool appears in `/mcp` but the model never calls it.** The schema may be malformed; yottacode passes it through verbatim, and some models refuse tools they can't parse. Re-check the server's `tools/list` output. - **Stale tools after editing config.** Use `/mcp restart ` to reload from the originally-loaded config, or restart the yottacode session to pick up `config.toml` changes from disk. ================================================================================ # Subagents Source: https://yottacode.ai/docs/subagents/ A **subagent** is a typed agent that yottacode dispatches in its own context window. The parent session sees only the subagent's final answer — the child's reasoning, tool calls, and tool outputs never enter the parent model's adapter context. Use subagents to delegate work that would otherwise blow up the main conversation: deep codebase searches, planning explorations, regression test runs, multi-file surveys. This mirrors Claude Code's `Agent` / `Task` tool surface. The `Agent` tool is exposed to the parent model; the model dispatches by `subagent_type`, and yottacode handles the rest. ## Built-in agents Four agent types ship with the binary: | Name | Tools | Purpose | | --- | --- | --- | | `general-purpose` | all parent tools (except `Agent` itself) | Answer open-ended questions. Falls back to writing if the task demands it. | | `Explore` | read-only (read_file, grep, glob, list_*, git read subcommands, fetch_url) | Fast code search and location lookup. | | `Plan` | Explore's tools + `todo_write` | Produce a written plan for a coding task. Ends with a `### Critical Files for Implementation` trailer. | | `verification` | Explore's tools + `run_bash` | Adversarially verify a change: run builds / tests / probes, try to break it, end with a `VERDICT: PASS\|FAIL\|PARTIAL` line. Background-by-default. | The built-in definitions live in `internal/subagents/builtins/*.md` and are embedded in the binary — they ship without any setup. ### `verification` in depth Dispatch after non-trivial work (3+ file edits, backend / API changes, infra changes). Pass the agent the original user task description, the files changed, and the approach taken — the prompt is structured around that input. The agent: 1. Reads project conventions (`PROJECT.md`, `README`, `AGENTS.md`). 2. Runs build / tests / linters as the universal baseline. A broken build is automatic FAIL. 3. Applies a strategy specific to the change type (frontend, backend, CLI, infra, library, bug fix, data pipeline, migration, refactor). 4. Runs at least one adversarial probe (concurrency, boundary, idempotency, orphan operation) before issuing PASS. 5. Returns a structured report with `### Check:` blocks (each with the exact command run and the actual output observed), terminated by a single parseable line: ``` VERDICT: PASS ``` or `FAIL` / `PARTIAL`. Use `PARTIAL` only for environmental limitations (no test framework, server can't start) — never as a hedge against "I'm unsure." The agent is read-only against the project directory: it can write ephemeral test scripts to `/tmp` via `run_bash` but cannot edit, create, or delete project files, install dependencies, or run git write operations. **Background-by-default**: the agent's frontmatter declares `background: true`, so dispatches default to a detached background task even when the caller omits `run_in_background`. The parent gets a task id back and can keep working; the verdict surfaces via the `SubagentBackgroundDone` event. In oneshot mode (where background isn't available), the dispatch silently falls back to foreground so the verdict still lands inline. ## Custom agents Drop a markdown file under either directory: - `.yottacode/agents/*.md` (per-project; commit alongside the repo) - `~/.yottacode/agents/*.md` (per-user; available in every project) Project definitions win on name collision over user definitions, which win over the built-ins. ### File format ```markdown --- name: ReleaseScout description: Audits the branch's release readiness — tests, gates, dirty checkouts. tools: [read_file, read_many_files, grep, glob, list_dir, git_branch_status, git_diff_files] model: claude-haiku-4-5 --- You are ReleaseScout. The parent has asked you to assess this branch's release readiness. Investigate test status, feature gates, and any uncommitted state. Report a punch list: ✓ ready · ✗ blockers · ? open. ``` Fields: - `name` (required) — letters/digits/underscore/hyphen, max 64 chars. Used directly as `subagent_type` in tool calls. - `description` (required) — one line shown to the parent model in the `Agent` tool schema. - `tools` (optional) — allowlist of tool names. Defaults to "inherit all parent tools (minus `Agent` itself)". Use `*` or `["*"]` to be explicit. - `model` (optional) — adapter model override for this agent. Honored when [cache-safe task routing](/docs/models/#cache-safe-task-routing) is enabled (`[router].mode` = `manual` or `auto`); it always wins over the auto heuristic. With routing `off` the field is parsed but inert. Useful for pinning a search-heavy agent to a cheaper model, or a high-stakes agent to a stronger one. - `background` (optional) — when `true`, dispatches default to background unless the caller explicitly passes `run_in_background:false`. Falls back to foreground in sessions where background isn't available (oneshot). Use this for slow off-turn checks the parent shouldn't block on (e.g. the `verification` builtin). - Body — the agent's system prompt. Be specific about what the parent should expect back. Unknown tool names in `tools:` emit a startup warning and are silently dropped from the allowlist. ## Foreground vs background The `Agent` tool accepts `run_in_background: true`: - **Foreground (default, always available)**: the parent's tool call blocks until the child produces a final reply. The child's result lands directly in the parent's message history as a single `tool` role message. Use when the parent needs the answer to decide its next step in the same turn. - **Background** (**experimental — see [experimental.md](/docs/experimental/)**): the call returns immediately with a task id. The child runs to completion in a detached goroutine. The TUI surfaces completion via a `SubagentBackgroundDone` card on the next render cycle; `oneshot` rejects background calls because it has no long-running session to host them. Use when the parent can keep working without the answer. Background subagents are **gated behind the `background_subagents` experimental feature**. Without the gate, the model's `run_in_background:true` calls return a recoverable error pointing at how to enable. Enable with any of: - `yottacode --experimental background_subagents` - `YOTTACODE_EXPERIMENTAL=background_subagents` - `[experimental]\nbackground_subagents = true` in `~/.yottacode/config.toml` See [experimental.md](/docs/experimental/) for the broader system. The gate exists because the model's reflexes around background subagents need more iteration — it tends to spawn one and then duplicate the work itself, producing slow or contradictory results. Foreground delegation is the stable surface. The trade is between **context isolation** (both variants give it), **parallelism** (both variants now — foreground subagents emitted in the same assistant message fan out concurrently via the loop's parallel-batch path; background still adds long-running off-turn parallelism on top), and **causal chaining** (only foreground — the parent gets the answer in the same turn it asked). ## Mode propagation A subagent runs under the **same mode as its parent**. The parent's `PlanModeState`, `AutoModeState`, and `YoloModeState` pointers are shared with the child's `LoopConfig`, so the child observes the same runtime flags. Mode changes on the parent (`/plan`, `Shift+Tab`) propagate to in-flight subagents on the next tool dispatch — same atomic-pointer mechanism the parent loop uses for itself. **Why this design**: Claude Code itself has an open ambiguity about plan-mode subagent behavior ([anthropic/claude-code#4750](https://github.com/anthropics/claude-code/issues/4750)). Without a canonical reference, yottacode picks the safer invariant: *"plan mode forbids mutations transitively, including through delegation."* The rule is also easy to remember — *"a subagent runs under the same mode as its parent."* ### Plan mode + subagents When the parent is in plan mode (entered via `/plan`, `Shift+Tab`, or `--permission-mode plan`): - The child enters plan mode with the **same plan file** as the parent (pointer-shared `PlanModeState`). - The child can call any read-only tool freely (`read_file`, `grep`, `glob`, `list_*`, `git_*` read subcommands, `fetch_url`, `todo_write`). - The child's write attempts go through `PlanModeGate`, which allows writes ONLY to the parent's plan file. Any other write target (other than the plan file) returns the gate's block message as the tool result — same UX the parent gets in plan mode. - The child cannot call `exit_plan_mode` (filtered from the child registry — leaving plan mode is the parent's decision, not a subagent's). - The Explore and Plan built-ins remain fully useful during planning (their tool allowlists are already read-only); a general-purpose subagent becomes effectively read-only under a plan-mode parent. **Visible effect**: a parent that says *"dispatch a Plan subagent to draft how we'd add a /history command"* gets a useful plan back even if it spawned the Plan subagent mid-investigation. The child can investigate the codebase, append to the plan file (the same one the parent is composing), and return a structured reply. **Prompt nudge**: the plan-mode addendum also steers the parent to dispatch subagents with `run_in_background:false`, so each subagent's findings return in the same turn and fold directly into the plan body — rather than landing via `get_subagent_result` after the plan is already being written. ### Auto mode + subagents When the parent is in auto mode (entered via `Shift+Tab`, `--permission-mode auto`, or the plan-card `[A]` auto-approval hotkey): - The child auto-allows non-safety-floor mutating tools (no per-call modal). Same rule as for the parent: edits, writes, and most git subcommands silently auto-execute; the safety floor (`run_bash`, `git_commit`, `git_checkpoint`, `rollback`) still triggers approval. - The child's iteration budget is `childIterationCap × 4` = **160** iterations (vs. the standard 40), inherited from auto mode's multiplier. A child that runs deeper than 40 model→tool round-trips is rare but legitimate for "do all this work unattended" workflows. - Foreground subagent + safety-floor tool → child's `ApprovalNeeded` forwards to the parent's modal (per the approval-flow rule below). The `[subagent:]` badge makes it clear which agent is asking for the bash/commit. - Background subagent + safety-floor tool → auto-deny (the background contract still holds; nobody's watching, so even auto-mode safety-floor approvals can't run). **Visible effect**: a parent in auto mode that delegates *"refactor all the import paths in pkg/X"* to a foreground subagent gets the refactor done with no per-edit modal — just like a parent doing the refactor directly under auto mode. The `git_commit` step at the end still pops the approval modal (with `[subagent:general-purpose]` prefix) because that's safety-floor. ### Yolo mode + subagents `YoloModeState` is process-wide and pointer-shared. Once entered (via `--yolo` at startup), it applies to all subagents in the session, including background runs. The yolo override skips every approval — including the safety floor — and removes the iteration cap entirely. Use only in trusted unattended contexts; subagents inherit the same risk profile as the parent. ## Approval flow Two cases, governed by foreground vs background: - **Foreground subagent** + child tool needs approval → the request forwards to the parent's modal with a `[subagent:]` badge in the preview so the user knows which agent wants what. The user's verdict routes back to the child via the parent's decisions channel. This works because while the parent's `Agent.Execute` call is running, the parent loop is itself blocked, so the decisions channel has no competing reader. - **Background subagent** + child tool needs approval → **auto-denied** with a steering message. The parent's turn may have ended hours ago; a surprise modal arriving long after spawn is bad UX. Pre-authorize via `permissions.json` if the background subagent genuinely needs mutating tools. ## /subagents command | Form | Effect | | --- | --- | | `/subagents` | Open the picker overlay in **tasks** view | | `/subagents types` | Open the picker overlay in **types** view | | `/subagents stop ` | Cancel a running task from the cmdline | Inside the picker: | Key | Effect | | --- | --- | | `↑` / `↓` | Move cursor | | `Enter` | Open the highlighted task's transcript in `$PAGER` (tasks view only) | | `t` | Toggle between **tasks** and **types** views | | `s` | Stop the highlighted task (tasks view, running only) | | `r` | Refresh the snapshot | | `Esc` | Close the picker | Task ids are 16-char hex; the first 8 chars are usually unique enough for the `/subagents stop` cmdline form. ## Transcripts Every subagent run writes its full transcript (every event, every tool call) to `~/.yottacode/projects//subagents/-.md`. The parent's context never includes this content, but the user can inspect it through the `/subagents` picker (Enter on a row) or by opening the file directly. ### Format The transcript is markdown. Each tool call renders as a section: ```markdown ### Grep("AuthMiddleware") ​``` internal/auth/middleware.go:23:func AuthMiddleware(next http.Handler) http.Handler { internal/auth/middleware.go:45: return AuthMiddleware ​``` _12ms_ ``` Streamed assistant content accumulates into one paragraph per message (not one line per token). Tool outputs are fenced with a language hint when one makes sense (`bash` for `run_bash` / `run_tests`, `diff` for `edit_file` / `apply_diff` / `git_diff_files`, plain otherwise). Errored tool calls carry an `_errored_` tag in the meta footer below the fenced block. The full tool output is preserved — no truncation. The live TUI truncates tool cards at a fixed line cap so the scrollback stays skimmable; the transcript is the place you go when you need the complete output. (The parent agent never sees these outputs anyway — only the child's final reply crosses the subagent boundary.) `TurnDone` and `IterCap` collapse into horizontal rules (`---`) so turn boundaries are visible without a separate event line per boundary. The final `**Outcome:** runner_completed` (or `runner_canceled` / `runner_iter_cap` / `runner_errored` / `runner_no_final_reply`) sits at the end of the file as a guaranteed end-of-record marker, followed by the `## Final result` section carrying the same string the parent model received. ### Viewing a transcript Pressing Enter on a row in the `/subagents` picker suspends the TUI and opens the transcript in your pager. The default is plain `less -R`: ANSI color through, no auto-quit so you always have time to read. You open at the top of the file. Standard `less` keys apply: | Key | Effect | |---|---| | `↑` / `↓` / `k` / `j` | Scroll one line up/down | | `PgUp` / `PgDn` / `b` / `Space` | Scroll one screen up/down | | `g` / `G` | Jump to top / bottom of file | | `/` | Search forward (`?` for backward) | | `n` / `N` | Next / previous search match | | `r` | Re-read the file and redraw (useful for seeing what a running subagent has written since you opened it) | | `q` | Quit and return to the TUI | **Seeing new content as the subagent runs**: press `r`. less re-reads the file and redraws with whatever has been written since you opened it. Press it again to refresh again. **Live tail** (advanced): less supports `Shift+F` to enter `tail -f`-style follow mode, but the follow-mode UX is awkward — it drops into a `Waiting for data... (interrupt to abort)` state that only `Ctrl+C` exits. The `r` refresh workflow covers the same "see new lines" need without the dance. **Pager resolution order**: `$YOTTACODE_PAGER` → `$PAGER` → `less -RF` → `more` → inline scrollback fallback. If `$PAGER` is set, we honor it verbatim — `$PAGER=less -FRSX` keeps your flags untouched, no injection. **Pager resolution order**: `$YOTTACODE_PAGER` → `$PAGER` → `less -RF` (with our keys-hint prompt) → `more` → inline scrollback fallback. If `$PAGER` is set, we honor it verbatim — your `$PAGER=less -FRSX` keeps your flags untouched and our key hint is not injected (your pager, your rules). ## Recursion guard The child registry **always** excludes the `Agent` tool itself. A malicious or accidental config that names `Agent` in its `tools:` allowlist cannot reintroduce it — the exclusion runs after the allowlist filter, every time. Subagents cannot spawn subagents. ## Iteration cap Child subagents always run under the standard `MaxIterations` cap (40 in the current build). The parent's auto-mode 4× multiplier and yolo's uncapped budget do **not** apply to children. A subagent that needs more than 40 model→tool round-trips is almost certainly stuck — split the work into separate subagent calls instead. ## Token cost Subagents make their own API calls against the same provider key as the parent. Token usage rolls into the session's overall counter via the shared adapter. Per-subagent token counts are surfaced in the `SubagentDone` event and stored in the task registry. With [cache-safe task routing](/docs/models/#cache-safe-task-routing) enabled, read-only/search subagents run on the cheaper `fast_model`, heavier ones on `smart_model`, and any agent with an explicit `model:` on whatever it names — all in an isolated context that never shared the main thread's prompt cache, so routing is a pure saving. The model each subagent ran on shows in the `/subagents` picker and on its completion card. (Per-subagent token figures are estimates; yottacode does not yet aggregate per-model token totals across a session.) ## Known limitations Things that are imperfect but not bugs — worth knowing about so the behavior doesn't surprise you. ### Multi-line tool cards can interleave with other output When two multi-line cards (e.g. a tool result card and a subagent start card) emit close in time, their lines may print interleaved 1:1 instead of one fully landing before the other starts. The content is still correct; the visual block structure isn't. This is a TUI rendering interaction with Bubbletea's inline-mode print pipeline — not subagent-specific. It affects any concurrent multi-line emission. We'll address it in a focused rendering pass in a future release. ### Model occasionally picks the wrong agent type The parent model selects `subagent_type` from the prompt and the tool descriptions. It mostly picks well, but sometimes routes a trivial lookup ("how many files?") through `general-purpose` instead of `Explore`. `general-purpose` then over-investigates because its prompt encourages thorough research. Result: a 10× or worse latency vs. what `Explore` would have produced. Workaround: name the agent explicitly in your prompt ("Use the **Explore** subagent to find..."). Otherwise the model's choice is what you get. Prompt steering in `DefaultSystemPrompt` nudges toward correct selection but isn't enforcement. ### Background subagents need babysitting Even with `background_subagents` enabled, the parent's reflexes around fire-and-forget delegation are uneven: - It may spawn a background subagent and then duplicate the work itself with `find` / `list_project_structure` / `bash`, reporting its own answer instead of the subagent's. - It may pick `general-purpose` for a read-only count (slow + may trip the auto-deny on `run_bash`). Foreground delegation is the stable, recommended surface today. Background is gated experimental — opt in for parallel investigations where you're willing to manage the workflow. ### Background subagents don't survive process restart Tasks live in an in-process registry. Quitting yottacode forgets them. Their transcript files on disk persist (you can read them manually), but the task list and the `get_subagent_result` tool won't surface them in a new session. ### Per-config permission narrowing isn't supported yet Today subagents inherit the parent's `permissions.json` rules unchanged. A future `permissions:` field on the agent definition would let a custom agent declare narrower rules ("this agent can't touch git"); not yet implemented. ### Background subagents can't prompt for approval Foreground subagents forward approval requests to the parent's modal. Background subagents auto-deny — there's no live UI to prompt against. Pre-authorize the tools the subagent needs in `permissions.json` (`allow` rules) if your background workflow needs mutations. ## Why this design The point of subagents is **context isolation**. The parent's adapter context tracks one conversation thread; investigating "where is X implemented across these 50 files?" would balloon that context with read_file outputs, none of which the parent needs after the answer is found. By delegating to an `Explore` subagent, the parent gets back a single concise reply — the equivalent of asking a colleague to look something up instead of doing it yourself. ## Open decisions (next steps) Design questions surfaced but not yet decided. Each is recorded so the choice gets made deliberately rather than via silent scope creep. ### Should background subagents be hard-restricted to read-only tools? **Current behavior**: background subagents can be configured with any tools (per agent definition). Mutating tools auto-deny at runtime because no UI is attached, but they remain *present in the child's tool schema*. A user can opt mutating tools back in by allowlisting them in `.yottacode/permissions.json`, after which the auto-deny is bypassed and a background subagent can write to disk unattended. **Proposed alternative**: in `buildChildRegistry`, when constructing the child for a background run, strip any tool whose `RequiresApproval("")` returns true. The child's model would never see mutating tools in its schema and cannot call them at all. The schema-level enforcement removes both the auto-deny path AND the `permissions.json` escape valve for background subagents. **Trade-offs:** | | Keep current | Hard-strip in background | | --- | --- | --- | | Background can write disk under user-allowlist? | Yes (via `permissions.json`) | No (schema-strip is unconditional) | | Model wastes turns calling tools that auto-deny? | Sometimes | Never | | Safety of unattended runs | Depends on `permissions.json` discipline | Always read-only | | Power-user "background formatter" workflow | Possible | Not possible | | Implementation cost | 0 (current) | ~15 LOC | | Diverges from "foreground = write-capable" symmetry | No | Yes (asymmetric) | **Decision deferred to**: a future session once we have more usage data on whether anyone actually relies on writable-via-allowlist background subagents. Until then, foreground forwards approvals, background auto-denies, both keep their full configured toolset in-schema. ### Should ALL subagents (foreground + background) be hard-restricted to read-only? The stronger version of the above question. Discussed at length in this session; the recommendation was yes (delete the approval-forwarding plumbing entirely, simplify the mental model to "subagents investigate, parent acts"), but no action was taken. Worth revisiting after we have a few more real-world workflows that exercise foreground approval forwarding — if it never gets used, the case for keeping the plumbing weakens. ### Related: per-agent `mutating: true` opt-in frontmatter field If we go hard read-only on subagents, add an optional `mutating: true` frontmatter field to agent definitions so a power user can opt back in for a specific custom agent. Default would remain read-only; the field would be undocumented in v1 to avoid encouraging the pattern. Deferred until the hard read-only decision is made. ### Should subagents share the parent's plan file, or get their own? **Current behavior**: plan-mode subagents write to the parent's plan file directly. A `Plan` subagent dispatched mid-investigation can extend the same `~/.yottacode/plans/.md` the parent is composing. The user sees the merged result. **Proposed alternatives:** 1. **Separate child plan files** — `~/.yottacode/plans//.md`. The child drafts its own plan in isolation; the parent reads it back and decides whether to incorporate. Cleaner separation but requires the parent to do a merge step. 2. **Subagents cannot write to any plan file** — plan-mode children become strictly read-only investigators. They name what should be added to the plan in their final reply; the parent applies it. Strictest invariant, most consistent with the "subagents investigate, parent acts" framing from the hard-read-only discussion. **Trade-offs:** | | Share parent's plan file (current) | Separate child files | Subagents can't write plan file | | --- | --- | --- | --- | | Plan file authoritative source | Yes (one file) | Multiple files to merge | Yes (one file, parent-only) | | Subagent can plan in parallel | Risk: overwriting parent's edits | Yes (isolated files) | No (parent must transcribe) | | Recovery if subagent goes off-track | User manually edits plan file | Discard the child file | Nothing to recover from | | Mental model | "subagent extends the plan" | "subagent drafts a draft" | "subagent reports findings" | **Decision deferred**: requires more real-world usage of `Plan` subagent invocations to see whether the current shared-file behavior causes real friction. ### Should auto-mode subagents get the full 4× iteration budget? **Current behavior**: a child under an auto-mode parent gets `childIterationCap × 4` = **160 iterations**. The 4× multiplier matches what the parent gets under auto mode. **Concern**: 160 iterations of unattended, auto-approving mutations is a lot. A subagent that legitimately needs more than the standard 40 iterations is rare; one that needs more than 80 is almost certainly stuck. The current cap optimizes for the rare "let it run" case at the cost of much wider blast radius for stuck runs. **Proposed alternatives:** 1. **Tighter ceiling for children regardless of parent mode**: `childIterationCap = 40` always, no multiplier. The "let it run" intent applies to the parent, not transitively. Conservative. 2. **Scaled multiplier**: `childIterationCap × 2` under auto mode = 80 iterations. Recognizes auto mode's intent without going as wide as the parent's 4×. Middle ground. 3. **Per-foreground-vs-background ceiling**: foreground children inherit 4× (user is watching); background children stay at 40 regardless (unattended → bound tighter). Honors the foreground/ background symmetry already at play in the approval-flow design. **Decision deferred**: revisit once we've observed real subagent runs hitting the cap. If `runner_iter_cap` outcomes are common, that's evidence the current 160 is reasonable; if they're never hit, evidence the lower cap would suffice. ### Should auto-mode subagent mutations be visible in scrollback? **Current behavior**: in auto mode, mutating tool calls from the child auto-allow silently. The parent's scrollback shows the subagent's progress ticks (`├ edit_file(...)`), but no per-edit modal flashes. This matches what the parent does in auto mode for its own calls. **Concern**: when a foreground subagent does 15 file edits auto-approved under the parent's auto mode, the user sees the ticks but doesn't get the file-by-file diff cards a normal foreground tool call would produce. Information density goes down. **Proposed alternatives:** 1. **Render full tool cards for child auto-approved mutations** — even when no modal is shown, emit a tool-card line in parent scrollback showing the diff/preview. Symmetric with how parent auto-mode renders mutations today. ~30 LOC in `runChild`'s event translator. 2. **Aggregate into a summary line** — at end of subagent run, emit a count-summary card: `subagent[X] · edited 7 files, wrote 2 new files, ran 3 git commands`. Less detail per change but cleaner scrollback. 3. **Status quo** — terse ticks only; users open `/subagents view ` for the full transcript when they want detail. **Decision deferred**: contingent on whether subagents-with-mutations remain a supported workflow at all. If we move to hard read-only, this question disappears. ### Should subagents be allowed to call `exit_plan_mode`? **Current behavior**: `exit_plan_mode` is filtered from every child registry, regardless of the agent's `tools:` declaration. Leaving plan mode is the parent's decision; subagents propose plans, they don't approve them. **Concern**: when a Plan subagent finishes a thorough plan, it'd be natural for it to signal "I think we're done planning, ready to implement." Today it has to express that as plain text in its final reply, and the parent decides whether to act on it. **Possible alternative**: allow Plan-typed subagents to call `exit_plan_mode` as a signal — the call still surfaces the approval card to the parent's user, who answers. The user approval remains the canonical gate. **Recommendation**: keep the current behavior. The "subagent proposes, parent disposes" model is cleaner than letting children trigger user-facing approvals from their own context. If the Plan subagent's final text reads *"plan complete; recommend exit_plan_mode and implement"*, the parent can take it from there with a single call. Marking as low-priority deferred. ================================================================================ # Memory Source: https://yottacode.ai/docs/memory/ yottacode keeps three kinds of memory, each with one job: - **Trust anchors** — the curated files (`USER.md`, `YOTTACODE.md`) injected verbatim into every turn's system prompt. - **Agent-managed typed memories** — markdown files the agent writes via dedicated tools (`memory_save`, `memory_forget`) when something is worth remembering across sessions. - **Recall + summarization** — full-text search across past sessions plus on-demand compression of long histories. The system is offline-first, deterministic, and entirely file-based. Every memory is a markdown file you can read, edit, or delete with your editor. The TUI's `/memory` picker is a convenience for the same on-disk state. --- ## How the four memory sources flow into the prompt The agent reads from four distinct on-disk locations every turn. Two are unfiltered "trust anchors"; the other two are agent-managed and pass per-entry bodies through a relevance filter (their indexes still inject in full). ``` ON DISK — four memory sources ┌────────────────────────────────────────────────────────────────┐ │ │ │ TRUST ANCHORS (always injected verbatim, never filtered) │ │ ────────────────────────────────────────────────────────── │ │ │ │ ① ~/.yottacode/USER.md cross-project, human-only │ │ ② /.yottacode/YOTTACODE.md per-repo, agent-writable │ │ through approval modal │ │ │ │ AGENT-MANAGED (index in full · per-entry bodies filtered) │ │ ────────────────────────────────────────────────────────── │ │ │ │ ③ ~/.yottacode/memory/ user-scope │ │ ├── MEMORY.md auto-generated table of contents │ │ └── .md typed memories (one file each) │ │ │ │ ④ ~/.yottacode/projects//memory/ project-scope │ │ ├── MEMORY.md auto-generated table of contents │ │ └── .md typed memories (one file each) │ │ │ │ ③ + ④ are written by memory_save, deleted by memory_forget │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ memory.Load(cwd) — read all four ▼ ┌────────────────────────────────────────────────────────────────┐ │ Loaded struct (in-memory) │ │ │ │ UserText ProjectText │ │ UserMemoryIndex ProjectMemoryIndex │ │ UserMemories[] ProjectMemories[] │ └────────────────────────────────────────────────────────────────┘ │ │ SystemPromptFor(base, loaded, turnInput, cfg) │ ─ trust anchors pass through unchanged │ ─ MEMORY.md indexes pass through unchanged │ ─ memory bodies are scored against turnInput │ and capped at cfg.top_k (shared budget │ across user + project scopes) ▼ ┌────────────────────────────────────────────────────────────────┐ │ Composed system prompt (rebuilt per turn) │ │ │ │ │ │ ─── opens BACKGROUND REFERENCE block ─── │ │ ① ## User preferences ← USER.md (full) │ │ ② ## Project context ← YOTTACODE.md (full) │ │ ③ ## User memory index ← MEMORY.md (full) │ │ ### [type] ← top-K bodies (filtered) │ │ ④ ## Project memory index ← MEMORY.md (full) │ │ ### [type] ← top-K bodies (filtered) │ │ ─── closes BACKGROUND block, action directive ─── │ └────────────────────────────────────────────────────────────────┘ │ ▼ sent to the model ``` The rebuild runs at the start of every turn (`internal/tui/cmd_retrieval.go`), so a `memory_save` mid-conversation lands in the next turn's prompt without an explicit reload. Disk errors leave the previous prompt in place — they don't fail the turn. --- ## Layer 1 — Trust anchors The two trust anchors are the load-bearing context the agent sees on every single turn. They render in full and are never filtered. > [!WARNING] > Trust anchors are injected into **every** prompt sent to your model provider — never put secrets, tokens, or private data in `USER.md` or `YOTTACODE.md`. | File | Location | Authorship | Scope | |---|---|---|---| | `USER.md` | `~/.yottacode/USER.md` | Human-only (in the agent's write-deny list) | Cross-project — applies to every session | | `YOTTACODE.md` | `/.yottacode/YOTTACODE.md` | Human-seeded; the agent keeps it fresh through approval-gated writes | Per-repo — only this project | {{< tabs items="USER.md,YOTTACODE.md" >}} {{< tab >}} `USER.md` holds preferences that travel with you ("prefer table-driven Go tests", "no trailing summaries"). Edit it through `/memory` (which opens vim) or directly — **the model never writes there**. {{< /tab >}} {{< tab >}} `YOTTACODE.md` is the project's brief. `/init` drafts it from the current repo (build commands, layout, conventions, gotchas) and aims to keep it under ~150 lines. After non-trivial work the agent will offer to refresh it through the approval modal. The startup "large memory will impact performance" notice is keyed on **file size, not line count** — it fires once a curated file exceeds 40k bytes (see [Trust model](#trust-model)). To make `YOTTACODE.md` human-only on a specific repo, add a deny rule to `.yottacode/permissions.json`: ```json { "permissions": { "deny": ["Edit(.yottacode/YOTTACODE.md)", "Write(.yottacode/YOTTACODE.md)"] } } ``` {{< /tab >}} {{< /tabs >}} --- ## Layer 2 — Agent-managed typed memories The agent owns this layer end-to-end. It decides in-conversation what is worth remembering, and it forgets when something becomes wrong or stale. ### Layout ``` ~/.yottacode/ memory/ # user-scope (cross-project) MEMORY.md # auto-generated index .md # one file per memory .vec # embedding sidecar (semantic mode) .archive/ # prior versions kept on overwrite (see below) ..md projects/ / memory/ # project-scope (this repo only, private to you) MEMORY.md .md .vec # embedding sidecar (same as user scope) .archive/ ``` The `.archive/` subdirectory holds the prior version of any memory that `memory_save` overwrote (named `...md` — the timestamp is for humans, the random suffix guarantees two concurrent archivers can't clobber each other), so an update can never silently destroy a different memory that reused the name. It's a dotted subdir, so the scanner skips it — archived versions never appear in the index, retrieval, or `memory list`. There is **no automatic retention policy or config knob today**: `memory_forget` deletes the live file and its `.vec` but does not prune `.archive/`, and nothing ages archives out. Pruning is fully manual (`rm -rf ~/.yottacode/memory/.archive`). Each archived body is a small markdown file, so unbounded growth is a housekeeping nit rather than a disk concern for a single user — a configurable retention policy is a known follow-up, not a shipped feature. `` is derived from the git remote (`https://github.com/user/repo.git` → `github-com-user-repo`); falls back to `filepath.Base(cwd)` for non-git directories. Slugs are not guaranteed collision-free: two non-git repos can collide on basename, and the remote-derived slug collapses the org/repo boundary (`.` and `/` both become `-`), so distinct remote URLs can also map to the same slug and share a project-memory directory. A collision means one repo's project memories load/edit in the other. Per-project memories live in your home directory, not in the repo. They're private to this user/machine — a clone of the same repo on a different machine starts with an empty project memory. Use `YOTTACODE.md` for things the team should share; use project-scope memory for what *you* personally want to remember about working in this repo. ### File shape Every memory file has YAML frontmatter plus a markdown body: ``` --- name: jwt-refresh-flow type: project description: How auth refresh interacts with the token cache created: 2026-05-08T12:34:56Z --- The refresh handler in pkg/auth/refresh.go writes the new token to the cache *before* it returns. Tests that mock the cache must seed it ahead of the call or the refresh path 401s on the next request. ``` The **filename is the memory's identity**, not the frontmatter `name:` field — the index links to `.md` and `memory_forget` resolves a memory by recomputing `.md`, so the scanner trusts the basename and the frontmatter `name:` is human-facing redundancy. `name` must be kebab-case (`^[a-z0-9][a-z0-9-]{0,63}$` — lowercase alphanumeric start, hyphens, ≤64 chars), and a small set of names is **reserved and rejected** (`user`, `project`, `memory`, `index`, `sessions`, `yottacode`, `feedback`, `reference`) so a memory file can't collide with a structural filename. `memory_save` also refuses path traversal and won't write through a symlink. `MEMORY.md` is auto-generated — a table-of-contents grouped by type, regenerated every time `memory_save` or `memory_forget` runs. Don't edit it; edit individual `.md` files instead. ### Types — four conventions, free-form underneath `type` is a short label the agent attaches when saving. Four labels are **conventional** (and group together, in this order, in `MEMORY.md`): - **`user`** — preferences, style, tooling. ("Prefer two-space indents." "Don't summarize after every change.") - **`feedback`** — corrections the user gave you. ("Don't generate stack traces in final answers — cut to the fix.") - **`project`** — load-bearing facts about this repo. ("The schema migration runner reads `migrations/sql/*.up.sql`, not `*.sql`.") - **`reference`** — material to look back at. (API shapes, command incantations, "what does `make ship` actually do".) But the set is **not closed**: when none of the four fit, the agent may coin its own short label — e.g. `decision`, `gotcha`, `api-shape`. A custom type is validated only as a label (lowercased + trimmed, then any run of spaces/underscores/hyphens collapsed to a single hyphen; lowercase letters, digits, hyphens; ≤32 chars) and renders as its own `## ` group in the index, after the four conventional ones (alphabetically). The `type` only labels and groups — it never restricts what the body can hold and is not a retrieval filter. The body content is unconstrained regardless of type. > **Separators are canonicalized.** Validation lowercases, trims, and then > collapses any run of spaces, underscores, or hyphens to a single hyphen > (trimming the ends), so `api shape`, `api_shape`, and `API-shape` all > store as `api-shape` and group under **one** `## api-shape` section > rather than fragmenting into three near-duplicate headers. Since `type` > is a label and index-grouping key only — never a retrieval filter — this > is purely about keeping the index tidy; the body and ranking are > unaffected either way. ### What the agent saves The agent is designed to be **self-learning** — it actively builds its understanding of you and your work across sessions and projects, so every future conversation starts smarter than the last. Save when: - The user states a durable preference, correction, or project fact. - The user **confirms or validates** a non-obvious approach — save what worked and why. - The user supplies a reference you'd otherwise re-derive every turn. - The agent observes a **recurring pattern**: the user always approves a certain style, always rejects a certain approach, always asks for the same thing. The agent doesn't wait for "remember this" — if it sees a pattern twice, it saves it. - A task outcome teaches something: an approach that failed and why, a subtle constraint discovered, a debugging technique that cracked a hard problem. Don't save: - Code patterns derivable from a quick grep. - Ephemeral state ("we're mid-refactor of the user model"). - Git-derivable info (current branch, last commit message). - One-off task instructions. - Anything sensitive (API keys, internal URLs, PII). ### Scope selection — cross-project learning Scope selection is critical for building knowledge that transfers across projects: - **`scope=user`** (stored in `~/.yottacode/memory/`, loaded in **every** project): anything about the person, not the repo. Coding style, communication preferences, tool preferences, workflow patterns, feedback corrections, debugging approaches, domain expertise areas. The test: "would this help me in a completely different repo for this user?" If yes, it's user-scope. - **`scope=project`** (stored per-repo, loaded only in that repo): **only** for facts that are meaningless outside this specific codebase — architecture decisions, naming conventions unique to this repo, team-specific processes, deployment targets. - **Default to user-scope.** Most things the agent learns about how someone works, thinks, and prefers are portable. Project-scope is the exception, not the default. - When saving a project-scope memory, the agent considers: is the underlying principle user-scope? E.g., "user wants table-driven tests in this Go repo" is really "user prefers table-driven tests" (user-scope) — the Go repo is just where it was learned. The full guidance lives in the agent's system prompt; see `internal/agent/prompt.go` for the current copy. **Precedence — project shadows user.** If the same memory name exists in both scopes, the project-scope version wins *in that repo*: its body injects and the user-scope twin's body is suppressed (it would otherwise duplicate or contradict). This matches how slash commands and config layering resolve project-over-user. The user file stays on disk and still applies in every other repo, where no project twin shadows it. ### The five tools The agent has five memory tools, all **silent by default** (no approval modal — they're as ordinary as `read_file`): - **`memory_save`** — creates a memory file, or updates an existing one of the same name. On a same-name update the prior version is **archived** to `/.archive/..md` (recoverable, never silently lost; excluded from the index, retrieval, and `memory list`) and the original `created` timestamp is preserved. The result reports `created` vs `updated` (and whether a version was archived). Updates `MEMORY.md`. Generates a `.vec` sidecar when an embedding model is available; if embedding is unavailable, the save still succeeds and the result notes that the semantic index wasn't updated. - **`memory_forget`** — deletes a memory file by name. Updates `MEMORY.md`. Errors when the named memory doesn't exist (so the agent learns the right names). - **`memory_search`** — searches across user and/or project memory stores, returning ranked results with relevance scores (zero-relevance entries are omitted). The agent uses this to check for duplicates before saving, find related memories when reasoning about a topic, or verify a remembered fact. Accepts `scope` (`all`, `user`, `project`) and `limit` parameters. - **`memory_get`** — returns the full, untruncated contents (frontmatter + body) of one memory by `scope` + `name`. Used before updating a memory so the agent can preserve the parts it isn't changing, instead of blindly overwriting from the 300-char `memory_search` preview. - **`session_recall`** — searches across all past sessions via the FTS5 full-text index. Returns ranked snippets with session metadata (name, date, model). The agent uses this to find prior discussions, check if an issue was already resolved, or pull in context from earlier conversations. Supports FTS5 query syntax (OR, exact phrases in quotes): the raw query is tried first so operators work, and only if that's a syntax error is a sanitized version retried — so a naive hyphenated or punctuation-heavy query still returns results instead of erroring. The introspection tools (`memory_search`, `memory_get`, `session_recall`) are the key to self-learning — they let the agent think based on its own accumulated knowledge rather than relying only on what the retrieval orchestrator injects each turn. All five tools resolve to the `Memory` permission namespace (save / forget / search / get / recall), so a single rule gates every memory operation — every read included, `session_recall`'s cross-session search among them. To require approval per memory operation, add an `ask` rule: ```json { "permissions": { "ask": ["Memory(*)"] } } ``` To deny entirely: ```json { "permissions": { "deny": ["Memory(*)"] } } ``` Or block only forgets while leaving saves silent: ```json { "permissions": { "deny": ["Memory(forget *)"] } } ``` ### Durability & concurrency **Atomic writes.** Every memory write (a `.md`, the regenerated `MEMORY.md`, or a `.vec` sidecar) goes through one atomic-write path: stage to a **unique** temp file in the same directory, `fsync`, then `rename` onto the destination and `fsync` the directory. The unique temp name means two writers can't interleave bytes into a shared staging file or delete each other's in-flight temp; the `fsync` closes the crash window that a bare `rename` leaves (a file coming back zero-length or stale after power loss). Reads are best-effort and never block a turn. **In-process serialization.** `memory_save` holds a per-path mutex across its whole *read → archive → write → regenerate-index* sequence. This matters because the same tool instance is shared into detached background subagents: without the lock, two concurrent saves to the same name would both read the same prior, both archive only that prior, and the second `rename` would silently drop the first writer's *new* content. The lock makes the sequence serial, so each writer archives the previous writer's version and nothing is lost. (`memory_forget` and the TUI delete don't take the lock; they're already serialized within a single agent loop because the memory tools aren't parallel-safe.) **Cross-process guarantee.** There is **no OS-level file lock** (no `flock`/`fcntl`), so the per-path mutex doesn't reach across processes — two separate yottacode processes, or a process plus a concurrent `yottacode memory` CLI invocation, share no lock. The exact guarantee that still holds: - **Body files are never lost or corrupted.** Each memory is a distinct path; the atomic rename is last-writer-wins *on a valid file*, and `ArchivePrior` stages through a unique temp so a prior version is never clobbered. - **Only `MEMORY.md` can go transiently stale.** Index regeneration is a read-modify-write over the whole directory (scan all `*.md` → render → atomic-write), so an unlucky interleave can let a regen rendered from an older scan land last and drop a just-added entry's *table-of-contents line*. This is **cosmetic and self-healing**: `MEMORY.md` is a rendered convenience index, not the source of truth — retrieval and injection scan the directory directly (`Load` → `scanMemoryDir`), so the dropped entry's body still injects and is still searchable, and the next save/forget rewrites the index from a fresh scan. No memory disappears. For a single-user desktop tool this residual race is rare (a millisecond window needing two simultaneously-mutating processes) and harmless. An advisory directory lock around index regeneration would close it; it's an accepted, documented gap rather than a shipped guard. ### Per-turn retrieval Memory grows over time. By the time you have dozens of memories, dumping every body into every prompt is wasteful. The retrieval orchestrator scores each memory body against the current user prompt and injects only the top-K. What's filtered: - **Per-entry bodies** under both scopes — scored, ranked, capped at `retrieval.top_k` and `retrieval.max_bytes`. What is NOT filtered: - `USER.md`, `YOTTACODE.md` — always in full. - Both `MEMORY.md` indexes — always in full. The model needs to know which files exist even when their bodies aren't injected. #### Retrieval strategies yottacode supports three scoring strategies, selectable via config: | Strategy | How it scores | When to use | |---|---|---| | `keyword` | Exact token overlap, name/type/description weighted 3x over body | Legacy fallback; fast, fully transparent | | `bm25` | Porter stemming + synonym expansion + Okapi BM25 ranking (IDF weighting, term saturation, length normalization) | Default when no embedding model is available. Handles "fakes" → "mocks", "running" → "run", "db" → "database" | | `semantic` | BM25 score (60%) + cosine similarity from local Ollama embeddings (40%) | When you want conceptual matching — "error handling philosophy" finds memories about soft failures even without shared keywords | | `auto` **(default)** | Probes for a local Ollama embedding model at session start. If found → `semantic`; otherwise → `bm25` | Recommended. Zero config, best available scoring | **BM25** is the baseline — pure Go, zero dependencies, deterministic. It ships a Porter stemmer and ~15 hand-curated synonym groups for programming/dev vocabulary (test/mock/fake, database/db/sql, deploy/release/ship, auth/login/credential, etc.). This alone is a major upgrade over raw keyword matching. Synonym-derived query terms are scored at a fractional weight (half of an exact term) so a memory that incidentally touches several distinct synonyms of a group can't outrank one that uses the exact term you searched for — recall stays up, exact-match precision wins ties. (The CLI / TUI `/memory search` preview uses equal weights; the agent's retrieval applies the down-weight.) **Semantic** layers local embeddings on top when a local Ollama server is available with an embedding model installed. Vector sidecars (`.vec` files) are stored alongside memory `.md` files and generated automatically on `memory_save`. The combined score blends BM25 (which excels at exact matches like file paths and function names) with cosine similarity (which captures conceptual relationships) — by default **60% BM25 / 40% cosine**, tunable via `retrieval.semantic_weight` (the cosine fraction; BM25 gets the rest). Raise it to trust meaning-based matches more on paraphrased queries, lower it (or set `0.0`) to lean on exact keywords. Because the blended score is re-normalized to top=1.0, only the ratio matters. A sidecar produced by a *different* embedding model than the one in use is skipped for the cosine term (cross-model vectors aren't comparable) — that entry simply ranks on BM25 until `memory reindex` rebuilds it. **Score normalization & `min_score`.** All strategies normalize their top match to 1.0, so `retrieval.min_score` means the same thing regardless of strategy — and doesn't silently start dropping every memory the moment `auto` resolves to `semantic` (Ollama present). **Interactive timeout & fallback.** On the synchronous, user-facing paths — both per-turn retrieval and `memory_save` — the embedding call is bounded by a short ~2s timeout. If Ollama is slow or goes away mid-session, retrieval falls back to BM25 for that turn and `memory_save` still completes (the `.md` is written; only the `.vec` is skipped, with a note to run `memory reindex` later) — neither blocks the UI. Batch `memory reindex` keeps the longer 30s timeout. **Caching.** The BM25 corpus (keyed by a content fingerprint of the memory set) and parsed `.vec` vectors (keyed by file mtime + size) are cached across turns, so a steady-state turn re-ranks without re-stemming every body or re-reading every sidecar. The caches self-invalidate when a memory body or its `.vec` changes (the corpus by content fingerprint, vectors by mtime + size). #### Changing the embedding model Each `.vec` sidecar records **which model produced it**. The file starts with a `YVEC` magic header followed by the embedding model name and dimension count, then the float32 vector. Two consequences: - **Retrieval is self-protecting.** `entryCosine` only blends a sidecar's cosine into the score when its recorded model matches the active model. A sidecar from a *different* model — or a pre-header *legacy* sidecar with no model recorded — contributes cosine 0 and the entry ranks on BM25 alone. So switching `embedding_model` never injects garbage similarity; at worst you lose the semantic boost on not-yet-reindexed entries until you rebuild them. (Legacy raw-float32 `.vec` files written before the header existed are still readable and are simply treated as "needs re-embed".) - **`memory reindex` is the migration path, and it's incremental.** Reindex calls `NeedsReembed`, which re-embeds only entries whose sidecar is missing, legacy, or from a different model — entries already embedded with the current model are skipped and reported as "up-to-date". So after changing the model you run `yottacode memory reindex` (or `/memory` → **Reindex embeddings**) once and only the stale sidecars are rewritten. There is **no automatic trigger** that detects a model change and reindexes for you — the rebuild is a manual (but cheap and incremental) step. Until you run it, affected entries fall back to BM25 rather than producing wrong results. #### Enabling semantic retrieval To get the full advantage of semantic memory retrieval: 1. Install [Ollama](https://ollama.com) if you haven't already 2. Pull a small embedding model: ``` ollama pull nomic-embed-text ``` 3. Restart yottacode — semantic retrieval activates automatically At session start yottacode runs a short (~800ms) probe against the Ollama server (`/api/tags`) that distinguishes three states: server unreachable (stay on BM25, silent), server reachable but the configured model missing (stay on BM25 and print a one-line `[memory] embedding model … not installed — run: ollama pull …` notice), and model present (resolve `auto` → `semantic`). The probe is deliberately separate from the per-turn embedding timeout so a missing model surfaces a targeted hint instead of silently degrading. `nomic-embed-text` runs entirely on CPU — no GPU required. The model is small (~270MB) and fast, and it runs locally so no data leaves your machine. Once installed, every `memory_save` generates a vector sidecar alongside the memory file. To generate vectors for existing memories, use `/memory` → **Reindex embeddings** or: ``` yottacode memory reindex ``` If you prefer an even smaller model (~45MB), `all-minilm` works too: ``` ollama pull all-minilm ``` Then set it in your config: ```toml [retrieval] embedding_model = "all-minilm" ``` #### Config tunables ```toml [retrieval] enabled = true # off → load every entry every turn (no filter) top_k = 10 # cap on memory bodies per turn (shared across user + project) max_bytes = 24000 # cap on combined injected body bytes per turn (0 = unlimited) min_score = 0.0 # 0.0 = no relevance floor (every entry up to top_k); >0 drops below it strategy = "auto" # "keyword" | "bm25" | "semantic" | "auto" embedding_model = "nomic-embed-text" # Ollama model for semantic retrieval semantic_weight = 0.4 # cosine fraction of the semantic blend; BM25 gets 1 - this (0=pure BM25, 1=pure cosine) ``` `top_k` and `max_bytes` are independent caps applied together: retrieval stops at whichever binds first. The byte cap drops the least-relevant tail (entries are rank-ordered), but the single top-ranked entry is always admitted even if it alone exceeds `max_bytes`. #### Measuring retrieval accuracy Retrieval quality is *measured*, not guessed. A relevance-eval harness lives in `internal/memory/eval_test.go`: a labeled fixture (a corpus of memories plus query→expected-memory cases) scored with standard IR metrics — **Hit@1**, **Hit@3**, and **MRR** (mean reciprocal rank). ``` go test ./internal/memory -run Relevance -v ``` - **`TestRetrievalRelevance_BM25`** is the deterministic, dependency-free gate: it runs the fixture through BM25 and fails if quality falls below calibrated floors, so a regression in stemming / synonym expansion / headline weighting is caught in CI. - **`TestRetrievalRelevance_Semantic`** runs the same fixture through the BM25+embedding blend when a local Ollama model is available (skipped otherwise) and logs a BM25-vs-semantic comparison — including a **paraphrase / low-overlap** set, the regime where keyword scoring is weakest and semantic cosine earns its keep. On topic-distinct memories BM25 alone already scores perfectly; semantic's measurable advantage shows up on paraphrased, low-keyword-overlap queries. Add cases to the fixture to harden the gate or to characterize a new scoring change before shipping it. ### `/memory` picker The TUI's `/memory` command opens a six-row picker (plus a conditional seventh row): | Row | Action | |---|---| | Project context | Edits `/.yottacode/YOTTACODE.md` in vim | | User preferences | Edits `~/.yottacode/USER.md` in vim | | Browse user memories | Sub-list of `~/.yottacode/memory/*.md` | | Browse project memories | Sub-list of `~/.yottacode/projects//memory/*.md` | | Search memories | Opens a query box; ranks saved memories and lets you open one (see below) | | Reindex embeddings | Generates `.vec` sidecars for semantic retrieval (requires Ollama) | | Enable semantic search | Appears only when no embedding model is active (e.g. first run without Ollama); pulls an Ollama embedding model and reindexes | In the browse sub-lists: `Enter` opens the chosen memory in vim, `d` deletes it (and regenerates `MEMORY.md`), `f` opens the folder in your file manager, `Esc` returns to the root menu. **Searching memories.** Two entry points land in the same interactive results overlay: the **Search memories** picker row (which opens a query box first), or `/memory search ` typed directly. Results are ranked by the same BM25 the agent's retrieval uses (each row shows scope + score + description). `↑`/`↓` scroll, `Enter` opens the highlighted memory in vim, and `Esc` steps back (results → root → close). Exiting the editor returns you to the **same results** — the query isn't lost. Crucially, results render in the overlay and are **never printed into the conversation transcript**, so searching doesn't pollute your session scrollback. It's a deterministic, zero-token way to find "what do I have stored about X" without spending a model turn (the interactive equivalent of the `yottacode memory search` CLI command). Use `/recall ` for the analogous search over past *sessions*. ### Cobra subcommands (for scripts) The same actions are exposed as non-interactive subcommands so CI or one-off shells can list, delete, and reindex memories without launching the TUI: ``` yottacode memory list [--scope user|project] # default: project yottacode memory forget --scope yottacode memory reindex # generate .vec sidecars for all memories yottacode memory search # search memories by query (same as memory_search tool) ``` ### Agent introspection flow The agent's self-learning loop uses these tools together: ``` session_recall("was this discussed before?") │ ▼ memory_search("do I already know about X?") │ ├── found a match → use it, update if stale │ └── no match → learn from this session │ ├── memory_save(scope=user, ...) for portable knowledge └── memory_save(scope=project, ...) for repo-specific facts ``` The agent decides autonomously when to search, save, update, or forget — the tools give it the capability, but the LLM owns the judgment about when and what to remember. --- ## Layer 3 — Recall + summarization `/recall` remains available as a user-initiated slash command. The agent can now also search past sessions proactively via the `session_recall` tool — same FTS5 index, same ranked results, but the agent decides when to look. `/recall ` searches every saved session in `~/.yottacode/sessions/` via an SQLite FTS5 index at `~/.yottacode/index.sqlite`. Useful for "I remember we discussed X — which session was that in?" The index is rebuilt incrementally on every session save and backfilled at TUI startup. `/summarize` compresses the active session's transcript when context is filling up. Replaces the message history with a synopsis injected into the system prompt under `## Prior session context (summarized)`. Auto-summarization fires automatically before the next turn at `context.auto_threshold` (default 0.85 — 85% of the model's window). --- ## Decision tree: where does this go? | Scenario | Where it lives | Why this scope | |---|---|---| | "I prefer table-driven tests" | `USER.md` (you write) or `memory_save scope=user, type=user` (agent learns) | Portable — applies in every repo | | "Build / test / lint commands for this repo" | `YOTTACODE.md` (`/init` drafts; agent keeps fresh) | Repo-specific, team-shareable | | "User said don't show stack traces" | `memory_save scope=user, type=feedback` | Portable — a communication preference | | "User approved the bundled-PR approach" | `memory_save scope=user, type=feedback` | Portable — a validated workflow pattern | | "An approach failed because of X constraint" | `memory_save scope=user, type=feedback` | Portable — lesson learned | | "JWT cache lives in pkg/auth/cache.go" | `memory_save scope=project, type=project` | Meaningless outside this repo | | "API has these public endpoints (this repo)" | `memory_save scope=project, type=reference` | Repo-specific API surface | | "We're mid-refactor of the user model" | Don't save — ephemeral | | | "Look up which session we discussed X in" | `/recall ` | | | "Compress the current transcript" | `/summarize` | | --- ## Trust model - **Memory tools run silently by default.** Add `ask: ["Memory(*)"]` to your permissions if you want a modal on every memory write. - **Don't put secrets in any memory file.** They get loaded into the system prompt every turn and persist on disk in plaintext. - **Project-scope memory is per-user.** Two developers on the same repo see different `~/.yottacode/projects//memory/` dirs. Use `YOTTACODE.md` (in the repo) for things the team should share. - **The curated layer never gets filtered.** Whatever you write in `USER.md` and `YOTTACODE.md` lands in every system prompt — keep them concise. The "Large file will impact performance" notice fires past 40k bytes. ================================================================================ # Sessions Source: https://yottacode.ai/docs/sessions/ Sessions preserve your conversation history, tool calls, and tool results across restarts. ## How sessions are saved Every completed turn is saved atomically to: ```text ~/.yottacode/sessions/.json ``` Session ids are timestamp-like strings, for example: ```text 20260426-150100.123456 ``` There is no manual save step. Each user message in a session also gets a per-prompt checkpoint capturing the conversation and any files the agent is about to touch. Open the picker mid-session via `/checkpoints` or **Esc Esc** to restore conversation, files, or both. See [tui-slash-commands.md](/docs/tui-slash-commands/#checkpoints---checkpoints--esc-esc). Token usage (input, output, and any prompt-cache hits) is recorded per turn alongside the assistant message and accumulated into per-model totals on the session. See [`/usage`](/docs/cost/) for how those totals surface. ## List sessions ```bash yottacode sessions list yottacode sessions list --json ``` In the TUI: ```text /sessions ``` ## Resume a session ```bash yottacode sessions resume yottacode --resume yottacode --continue # the most recent session in this directory yottacode -c # short form ``` `--continue` (mirrors Claude Code's flag) skips the picker and resumes the newest saved session whose cwd matches the current directory — useful for "I quit a few minutes ago and want to pick up where I left off." When no saved session matches the current directory, `--continue` errors out with a hint. `--continue` and `--resume` are mutually exclusive; pass one or the other. In the TUI: ```text /sessions /sessions ``` Runtime flags like `--max-iterations` and `--yolo` are not stored in the session. Pass them again when resuming if you need them. Mode state (auto / plan) is also not persisted: a session that ended in auto mode resumes in normal mode, same as Claude Code — use `Shift+Tab` to re-enter the mode you want. ## Summarized resume Large sessions can consume a lot of context before the first new prompt. Use summarized resume to carry forward the important parts compactly: ```bash yottacode sessions resume --summarized ``` In the TUI sessions picker, press `s` to toggle summarized loading. ## Rename a session ```bash yottacode sessions rename my-feature-work ``` Names are convenience labels. The canonical identity is still the session id. ## Export a session ```bash yottacode sessions export yottacode sessions export path.md yottacode sessions export path.md --force ``` Export writes a readable Markdown transcript with turns, tool calls, and tool output. System messages are omitted. ## Search sessions with recall ```text /recall authentication ``` Recall uses a local SQLite FTS5 index at: ```text ~/.yottacode/index.sqlite ``` The index is rebuilt/backfilled automatically from saved session files. ## Clear vs delete `/clear` saves the current session, then starts a new session with the same system prompt and memory. It does not delete anything. To delete a session, remove the JSON file: ```bash rm ~/.yottacode/sessions/.json ``` ## Summarization snapshots `/summarize` writes a pre-compression snapshot before rewriting in-memory history: ```text ~/.yottacode/sessions/-pre-summary-.json ``` Keep these files if you may need to inspect or restore pre-summary history. ================================================================================ # Worktrees — parallel yottacode sessions Source: https://yottacode.ai/docs/worktrees/ Worktrees let two or more yottacode sessions edit the same repository in parallel without colliding. Each session lives in a separate directory under `~/.yottacode/worktrees///` on its own branch, so the agent's writes in one session are invisible to the read tools of another. Keeping the worktrees outside the repo means they never show up in `ls`, never get walked by IDE indexers / `find` / `grep`, and don't depend on `.gitignore` discipline. If you want the agent to chew through a big feature autonomously, the recommended pattern is **plan in a worktree, then run auto mode inside it** — three stacked safety layers that bound the blast radius of any mistake. See [Canonical safe-autonomy workflow](#canonical-safe-autonomy-workflow) below. ## Quickstart ```bash # Launch the TUI inside a fresh worktree yottacode --worktree feature-auth # Same, but let yottacode generate the name (e.g. bright-running-fox) yottacode --worktree # Short flag yottacode -w feature-auth # Oneshot yottacode run --worktree feature-auth "list failing tests" # Admin from any directory inside the repo yottacode worktree list yottacode worktree status feature-auth yottacode worktree remove feature-auth yottacode worktree prune ``` Each worktree materializes at `~/.yottacode/worktrees///` on branch `worktree-`. The `` is `-<8-char-hash>` (e.g. `yottacode-1a2b3c4d`), derived deterministically from the absolute repo path so two repos with the same basename never collide. Each yottacode worktree session uses `git worktree add` under the hood — git stores its administrative data in the originating repo's `.git/worktrees//` regardless of where the actual working tree lives. ## Prerequisites - **Workspace trust** — `--worktree` refuses on untrusted repos with a one-line hint: run `yottacode` once in the repo to accept the trust prompt, then re-run with `--worktree`. Worktree sessions resolve trust back to the originating repo via `git rev-parse --git-common-dir`; the worktree dir itself is never added to the persistent trust set. See [security-and-allow-lists.md](/docs/security-and-allow-lists/). - **Git ≥ 2.5** — `git worktree` is a standard git subcommand available in every modern git release. ## `.worktreeinclude` — bring `.env` along A fresh git worktree starts empty of every gitignored file. That's fine for a clean cut, but most projects rely on `.env` / `.vscode/settings.json` / `node_modules/` / `.local-config` to actually run. `.worktreeinclude` is a per-repo file (gitignore-style syntax) that lists which gitignored files yottacode should copy into each new worktree at creation time. ``` # /.worktreeinclude .env .env.* .vscode/settings.json **/.config.local ``` Lines starting with `#` are comments; blank lines are skipped. Patterns match against repo-relative paths using `filepath.Match` semantics, with `**` as a wildcard that spans zero or more path segments. Missing files are silently skipped; unreadable files return an error so you can fix permissions instead of silently shipping a broken worktree. Symlinks (and other non-regular files like devices or FIFOs) are skipped, not followed — a matched symlink won't copy its target's bytes into the worktree. Commit `.worktreeinclude` to the repo so every contributor's worktrees inherit the same setup. ## Agent surface The agent reaches the same machinery through three tools, all of which require approval — `enter_worktree` and `exit_worktree` are in the **auto-mode safety floor** (they prompt even when auto mode is on, because they shift the agent's working context): | Tool | What it does | |---------------------|-------------------------------------------------------------------------------| | `enter_worktree` | Create or attach to a yottacode worktree. Args: `name?`, `base?` (`fresh`\|`head`). Swaps the session cwd to the new worktree so subsequent relative paths resolve inside it. | | `exit_worktree` | Leave a worktree. Args: `name?` (infers from cwd), `cleanup?` (`auto`\|`keep`\|`remove`). Auto cleanup removes clean trees and keeps dirty ones. Swaps cwd back to the originating repo root. | | `worktree_status` | Read-only peek: per-worktree clean/dirty state with reason chips. Useful when juggling several at once. | Plus the thin `git_worktree_{list,add,remove,lock,unlock,prune}` wrappers for finer-grained admin. The list / status tools are read-only and never prompt. ## Canonical safe-autonomy workflow The combination yottacode is designed around — plan inside a worktree, then run autonomously in auto mode, with the worktree disposable on the way out: ``` 1. yottacode --worktree feature-x # cwd = ~/.yottacode/worktrees//feature-x/, branch = worktree-feature-x # (or: agent calls enter_worktree, you approve once) 2. /plan # still in worktree, read-only gate on 3. (write plan) # plan file lands in ~/.yottacode/plans/ 4. ExitPlanMode # still in worktree, normal mode 5. /auto # still in worktree, auto mode active 6. (agent executes the plan) # edits land inside the worktree only 7. agent calls exit_worktree # clean: auto-remove; dirty: keep/remove prompt ``` Three independent safety layers stacked: 1. **Filesystem isolation** (worktree) — the agent can't touch your main checkout while it's working in the worktree. 2. **Read-only planning** (`/plan`) — no risk of damage while designing. The agent writes a plan, not code. 3. **Bounded blast radius** (auto mode inside a worktree) — if the agent goes off the rails, `exit_worktree` with `cleanup="remove"` discards everything; your main checkout is untouched. **Persistence:** worktree state is filesystem + git; mode is a permission policy. They're orthogonal — a mode flip never touches the worktree. Switching between `/plan`, `/auto`, and the default mode while you're inside a worktree leaves you in the same worktree, on the same branch, with the same uncommitted state. Concurrent yottacode in two worktrees of the same repo is safe — distinct cwds, distinct session records. ## Cleanup behavior | Context | Clean tree (no uncommitted, untracked, or unpushed work) | Dirty tree | |------------------------------------|----------------------------------------------------------|---------------------------------------------------| | TUI session, `exit_worktree` | Auto-removes worktree + branch | Keeps the worktree, returns "use cleanup=remove" hint | | TUI session, `exit_worktree cleanup=remove` | Removes | Force-removes (destructive) | | Oneshot / `--print` | No auto-cleanup. Run `yottacode worktree remove ` afterward. | Same. | Branches that follow our `worktree-` naming are deleted along with the worktree; user-named branches are left alone. ## Permission-mode interactions | Action | Plan mode | Auto mode | Normal | `--yolo` | |---------------------------------------|-------------|------------------------------------|------------|----------------------------------| | `enter_worktree` | blocked | **prompts** (safety floor) | prompts | bypassed | | `exit_worktree` (clean) | blocked | **prompts** (safety floor) → remove| prompts | auto-remove | | `exit_worktree` (dirty) | blocked | **prompts**; keep/remove sub-prompt fires too | prompts twice | keep/remove sub-prompt | | `git_worktree_list` / `worktree_status` | allow (read-only) | allow | allow | allow | | `git_worktree_{add,remove,lock,unlock,prune}` | blocked | auto-allow | prompts | bypassed | | Edits inside the worktree cwd | blocked | auto-allow (cwd-confined) | prompts | bypassed | `enter_worktree` and `exit_worktree` are in the auto-mode safety floor (alongside `run_bash`, `git_commit`, `git_checkpoint`, and `rollback`). The plain `git_worktree_*` wrappers stay auto-allowed in auto mode because they're narrow and explicit. Use `[A]-always` on the safety-floor prompt once if you want the agent to spin worktrees freely in the rest of the session. ## Base ref — `fresh` vs `head` When the agent calls `enter_worktree`, the `base` argument controls which commit the new branch starts from: - `fresh` (default) — branch from `origin/HEAD` when reachable, otherwise local `HEAD`. No local-only commits travel with the worktree. Best for "give the agent a clean room." - `head` — branch from the current local `HEAD`. Unpushed work follows the worktree. Best for "carry my in-progress branch into isolation." The CLI flag (`yottacode --worktree foo`) always uses the `fresh` default; the agent tool exposes the choice. ## Sessions and resume `yottacode sessions list` shows the worktree column for each session, and `yottacode sessions resume ` lands you back in the session's recorded cwd — i.e. inside the right worktree. Sessions that ran in the main checkout show `worktree=—`. ## What's not in v1 - **PR-fetch** (`--worktree "#1234"`) — Claude accepts this; we'll add it once the typed go-github client surfaces the helper. - **`WorktreeCreate` / `WorktreeRemove` hooks** for non-git VCS (svn / hg / perforce) — git-first for v1. - **Subagent isolation** (`isolation: worktree` frontmatter on custom subagents) — depends on custom-subagent stability; will follow. - **Crash-sweep at startup** of orphan `_subagent-*` worktrees — paired with subagent isolation. ## Migrating from in-repo worktrees If you used a pre-relocation build that materialized worktrees at `/.yottacode/worktrees//`, those directories still exist on disk but yottacode no longer addresses them. Two cleanup options: 1. **Throw them away** — they were ephemeral anyway. From the repo: ```bash # Inspect the leftover branches (worktree-* naming convention) git branch | grep '^ worktree-' # Force-remove the working trees, then delete the branches git worktree remove --force .yottacode/worktrees/ git branch -D worktree- ``` 2. **Promote them to the new location** — if you have uncommitted work in an old worktree, move the changes into a fresh worktree: ```bash # In the new location, after relocation yottacode --worktree # Then copy / merge from the old /.yottacode/worktrees// # by hand, commit, and `git worktree remove` the old one. ``` No automatic migration tool ships with the relocation — the cut-over is intentional, mirroring how procedural migrations are handled elsewhere in yottacode (no coexistence window). ## Troubleshooting See [troubleshooting.md](/docs/troubleshooting/) for "worktree exits with trust error" and "worktree from `--print` not cleaned up." ================================================================================ # GitHub Integration Source: https://yottacode.ai/docs/github/ yottacode talks to GitHub through a typed `go-github` adapter (`internal/github`), not by shelling out to the `gh` CLI. This page covers the auth setup, the tool surface, the permission shape, and the rate-limit / cache / offline behavior. ## Auth setup Token discovery walks three tiers in this order: 1. **`$GITHUB_TOKEN`** — explicit env var. CI-friendly. Pass via shell env or a `.envrc`; yottacode never writes to it. 2. **`gh auth token`** — opportunistic shell-out to the `gh` CLI if it's on `$PATH` and authenticated. Most common local path because users who already use GitHub workflows usually have `gh` installed. 3. **`~/.yottacode/github.json`** — schema `{"token": "ghp_..."}`. Written by `yottacode setup github`. Personal, gitignored, file permissions tightened to `0600`. Run `yottacode setup github` for the interactive flow (prompts for a fine-grained PAT, writes the file). Run `yottacode setup github --token ghp_xxx` for a non-interactive path. Run `yottacode setup github --remove` to delete the file. The resolver caches its result once per process via `sync.Once`. A user who re-auths with `gh auth login` mid-session restarts yottacode to pick up the new token. ## Tool surface ### Read tools (no approval) | Tool | What it returns | |---|---| | `gh_pr_read` | PR metadata (title, body, state, draft, base/head, mergeable, author, labels, URL). **One API call.** | | `gh_pr_review_context` | PR metadata + diff (capped) + check-run rollup + failing-checks summary. Three API calls, one snapshot. Used by `/git-review-pr`. | | `gh_pr_context` | Local pre-PR context (base resolution, ahead-count, push state, PR template). No network — git-local. | | `gh_issue_read` | Issue metadata (title, body, state, labels, assignees) + recent comments. | | `gh_issue_list` | Open issues matching label / assignee / milestone filters. | The two read tools the model is most likely to reach for via `run_bash gh pr view --json …` are `gh_pr_read` (body-only) and `gh_pr_review_context` (review). Each tool's description explicitly calls out the typed-vs-bash trade-off so the model picks correctly. ### Write tools (approval required) | Tool | What it does | Slash command | |---|---|---| | `gh_pr_create` | Opens a PR (validates title, body, base) | `/git-create-pr` | | `gh_pr_update` | Rewrites an existing PR's title + body | `/git-update-pr` | | `gh_pr_add_comment` | Posts a conversation comment on a PR | (model-callable) | Every write goes through the approval modal — the modal renders the full title + body before the call lands. `--yolo` bypasses the modal, but `deny` rules still apply. ## Slash commands | Command | Flow | |---|---| | `/git-create-pr [base]` | Validates current branch + opens a PR. Default base is the repo's default branch. | | `/git-update-pr [ref]` | Refreshes title/body to match the current commit list. | | `/git-review-pr [ref]` | Structured PR review: failing-checks summary, blockers, suggestions, nits. Output is local scrollback only. | | `/git-push` | Pushes the current branch (sets upstream on first push; surfaces the PR URL when one exists). | | `/git-implement-issue ` | End-to-end: fetch issue → research → plan mode → branch → implement → tests → commit → push → draft PR. | `/git-implement-issue` is the largest of the bunch. Spec: `yottacode-roadmap/git-fix-issue.md` (the design doc was written under the older `/git-fix-issue` name; the shipped command uses `/git-implement-issue`). ## Permissions Github(...) rules are documented in [`configuration.md`](/docs/configuration/#rule-prefixes). The verb names are the canonical descriptors: ```json { "permissions": { "allow": [ "Github(read_*)", "Github(list_open_issues)" ], "ask": [ "Github(create_pr)", "Github(update_pr)", "Github(add_pr_comment)" ], "deny": [ "Github(*-merge)" ] } } ``` Owner/repo scoping (`Github(create_pr owner/repo)`) is deferred — every call currently resolves against the cwd's git remote. ## In-session cache Reads are cached for the session lifetime via `CachingClient`. Duplicate reads (same `(owner, repo, ref)` for PR tools; same `(owner, repo, number, max_comments)` for issues; same filter fingerprint for issue lists) make exactly one API call. Writes pass through. `UpdatePR` invalidates the matching `ReadPR` entry so the next read sees fresh data. `AddPRComment` does not invalidate `ReadPR` (comments aren't in `PRDetails`). Errors — including `ErrPRNotFound` and `ErrGitHubUnreachable` — are NOT cached. A retry after fixing auth or after a transient network blip always re-hits the API. The cache lives only in process memory; the runtime tears it down at session end. There's no TTL, no on-disk persistence, no manual invalidation API. ## Rate limits The typed client captures `X-RateLimit-Remaining` from every response. `RateLimit()` on the client returns the most recent snapshot. The doctor surfaces it in `--- github ---`: ```text --- github --- token: present (source=env) probe: reachable=yes auth=yes user=octocat rate: 4500/5000 remaining (resets in 45m0s) github: ok ``` Soft-warn threshold is 100 remaining. `Snapshot.IsLow()` reports true at or below; the doctor adds a warning line. ## Offline degradation Network errors (DNS failure, refused connection, TLS handshake, timeout) classify as `ErrGitHubUnreachable` — distinct from `ErrGhUnavailable` (auth missing) and `ErrPRNotFound` (logical). Callers branch on these typed sentinels so the recovery hint is right: auth wants `gh auth login`, unreachable wants "check your network." The doctor probe runs a single authenticated `/user` call against api.github.com with a 5s ceiling. A failed probe never hangs the doctor. ## Cloud forward-compat The same `internal/github.Interface` will back the planned cloud `yottacode-bot` GitHub App (SaaS Phase 2). The bot will swap in a JWT-installation-token implementation behind the same surface; the slash commands and tools won't change. See `yottacode-roadmap/github-integration.md` for the design. ================================================================================ # TUI slash commands Source: https://yottacode.ai/docs/tui-slash-commands/ Type `/` in the TUI to open the slash-command palette. The palette filters as you type, supports Tab completion, and can be dismissed with `Esc`. ## Command reference | Command | Args | What it does | |---|---|---| | `/help` | — | List all commands with help text | | `/quit` | — | Exit yottacode | | `/clear` | — | Save the current session and start a fresh one | | `/permissions` | — | Print shared and local permission file paths | | `/system` | — | Show the active system prompt, including injected memory | | `/usage` | — | Show per-session token totals, today's rolling total, live rate-limit headroom, and a per-provider billing-dashboard link. No dollar estimate — token counts are exact, but cost would need an unmaintainable price table. See [cost.md](/docs/cost/). | | `/sessions` | `[id\|name]` | Open the sessions picker or resume a known session directly | | `/model` | `` | Switch the active model for this session | | `/provider` | — | Show resolved provider, API style, built-ins, capabilities, and diagnostics | | `/effort` | `[default\|low\|medium\|high]` | Set reasoning effort for this session on providers that support it. Bare opens a picker; a positional argument sets it directly. `default` (aliases `off`/`none`) injects no reasoning override — every provider behaves as if `/effort` were never used. Session-only, mirroring `--reasoning-effort`. See [providers.md](/docs/providers/#reasoning-effort). | | `/doctor` | — | Probe the provider `/models` endpoint | | `/redo` | — | Rewind the last user message and put it back in the input box | | `/recall` | `` | Search across saved sessions | | `/summarize` | — | Compress the current session after snapshotting it | | `/checkpoints` | — | Open the checkpoints picker — also `Esc Esc`. Restore conversation, files, or both to any prior prompt | | `/memory` | — | Edit curated memory or browse agent-managed memories | | `/setup` | — | Suspend the TUI and rerun setup | | `/init` | — | Ask the agent to draft or refresh `.yottacode/YOTTACODE.md` | | `/git-commit` | — | Compose and run a one-line commit on the staged changes. Procedural: control flow is in Go, the model only synthesizes the subject. Replaces the legacy markdown `/git:commit-message`. | | `/git-create-pr` | `[base]` | Open a pull request for the current branch. Procedural: base resolution, ahead-count gating, push-state detection, title validation, and gh-unavailable fall-through all live in Go. Replaces the legacy markdown `/git:create-pr`. | | `/git-review-pr` | `[ref]` | Self-review an existing pull request. Ref is a number (`17`) or branch (`feature/x`); empty defaults to the current branch's PR. Fetches PR metadata + diff + check rollup via the typed `internal/github.Interface`, surfaces failing CI at the top, emits a structured review (Failing checks / Blockers / Suggestions / Nits). Output to scrollback only — posting back to GitHub is deferred to a future `--post` flag. | | `/git-push` | — | Push the current branch to origin. Procedural: deterministic upstream detection (adds `-u origin HEAD` only on first push), detached-HEAD early exit, no force-push surface. Surfaces "PR updated: ``" when a PR exists for the branch, or points at `/git-create-pr` when one doesn't. | | `/git-update-pr` | `[ref]` | Refresh an existing PR's title and body to match the current commit list. Ref is a number or branch; empty defaults to the current branch's PR. Keeps the existing title verbatim when scope hasn't materially changed (no cosmetic title churn); regenerates the body from the full commit log. Scope-pinned: only edits title and body — labels, reviewers, base, draft state are off-limits. | | `/plan` | — | Toggle plan mode (also `Shift+Tab`). Type `/plan list` to open a picker and resume an earlier plan. | | `/subagents` | `[list \| view \| stop \| types]` | List subagent runs, view a transcript, stop a running task, or list available agent types. See [subagents.md](/docs/subagents/). | | `/mcp` | `[logs ]` | List configured MCP servers (status + tool count), or dump a server's recent stderr with `logs `. See [mcp.md](/docs/mcp/). | | `/theme` | `[set \| ]` | Change the theme — opens the picker with arrow-key live preview across every registered palette (`terminal`, `catppuccin`, `dimmed`, `gruvbox`, `high-contrast`, `low-contrast`, `no-color`, `nord`, `one-dark`, `solarized-dark`, `tokyo-night`). Enter applies and persists to `~/.yottacode/config.toml`; Esc reverts. Scriptable shortcuts: `/theme set ` and `/theme ` bypass the picker. See [themes.md](/docs/themes/). | Beyond the built-ins, you can ship your own slash commands by dropping markdown files in a `commands/` directory — see [Custom commands](#custom-commands). > **Auto mode and the permissions-bypass overlay are intentionally not slash commands** (mirroring Claude Code). Auto enters via `Shift+Tab` (cycle: `normal → auto → plan → normal`) or `yottacode --permission-mode auto` at startup. Permissions bypass enters only via `yottacode --yolo` at startup — there is no in-TUI toggle, no palette entry, no accidental activation. See [Auto mode](#auto-mode) and [Permissions bypass](#permissions-bypass) below. ## Provider picker `/provider` shows the resolved provider profile and diagnostics. `/provider use ` switches to a configured provider directly. The provider picker also supports adding and removing profiles; adding `openai-auth` starts the browser OAuth flow inline, and adding `copilot-auth` starts the GitHub device code flow inline. Both store account-specific model lists after login. ## Sessions picker `/sessions` opens a picker with actions for loading, resuming, renaming, and exporting sessions. - Recent sessions are shown newest first. - `/sessions ` resumes directly. - Press `s` in the list, or `Ctrl+S` in the resume input, to toggle summarized resume for large transcripts. - Export writes a Markdown transcript suitable for sharing or archiving. ## Memory picker `/memory` opens a four-row picker: - Project context: `./.yottacode/YOTTACODE.md` - User preferences: `~/.yottacode/USER.md` - Browse user memories (`~/.yottacode/memory/`) - Browse project memories (`~/.yottacode/projects//memory/`) Opening a curated memory file (`USER.md`, `YOTTACODE.md`) suspends the TUI to `vim`; on exit, yottacode reloads memory and patches the active system prompt so the next turn sees your edits. The browse rows drop into a sub-list of agent-managed memories where `Enter` opens an entry in `vim`, `d` deletes it, `f` opens the folder in your file manager, and `Esc` returns to the root menu. ## Custom commands Drop a markdown file in either location and it becomes a slash command: - `~/.yottacode/commands/` — **user scope**, applies to every yottacode session for this user - `/.yottacode/commands/` — **project scope**, committable so a team can share commands via git The filename (without `.md`) becomes the command name. Subdirectories namespace the name with `:` — `commands/frontend/component.md` → `/frontend:component`. Each path segment must be lowercase letters, digits, or hyphens, starting with a letter or digit; invalid segments cause the file to be skipped with a startup warning. ### Frontmatter Optional YAML block at the top of the file sets metadata shown in the palette and `/help`: ```markdown --- description: Review a PR and suggest changes argument-hint: --- You are reviewing PR #$1. @docs/code-review.md Fetch the PR diff with `gh pr diff $1`, then walk the checklist file by file. ``` - `description` — shown in the right column of `/help` and the palette. Defaults to `(custom command)` when omitted. - `argument-hint` — shown after the command name and used by the palette: when set, pressing Enter on a highlighted command fills `/name ` and waits for input (matching how `/recall ` behaves) instead of firing immediately. Leave empty for commands that take no arguments. - **YAML gotcha:** if your hint uses square brackets (the common `[optional]` convention), quote the value: `argument-hint: '[base-branch]'`. Bare `[...]` is YAML flow-sequence syntax and will fail to parse as a string. Angle brackets like `` don't need quoting. Unknown frontmatter keys are silently ignored, so the file stays forward-compatible if more keys ship later. ### Argument substitution Two forms are recognized in the body: - `$ARGUMENTS` — the entire post-name remainder. For `/review-pr 123 force`, `$ARGUMENTS` expands to `123 force`. - `$1` .. `$9` — positional arguments (whitespace-split). Missing positionals expand to the empty string. `$10` is parsed as `$1` followed by a literal `0` — only single-digit positionals are recognized. This matches Claude Code's documented surface; if you need more arguments, use `$ARGUMENTS`. A literal `$ABC` (not one of the recognized tokens) is left as-is. ### File references in the body Any `@` token in the resolved body is picked up by the same filerefs pipeline that handles user-typed `@` references. The file's contents inject into the system prompt before the turn fires, and the `@` is stripped from the user message so the model sees a plain path. This lets you pin a command to a specific spec or checklist file: ```markdown --- description: Audit security-sensitive changes --- Audit the staged diff against @docs/security-and-allow-lists.md. List anything that violates the deny lists or skips approval gates. ``` ### Conflict resolution - **Same name in the same scope**: both copies are dropped with a startup error. The loader can't pick a winner safely — you should rename one of the files. - **Same name across scopes**: project wins, user is shadowed. A startup notice tells you which file got shadowed. - **Same name as a built-in** (e.g. you create `commands/help.md`): the custom command is dropped with a startup warning. Built-ins always win at the dispatcher. Startup notices render in the same scrollback band as memory diagnostics — error lines in red, warnings in muted yellow. ### Permissions and approvals Custom commands are a **prompt shortcut, not a permission bypass**. Typing `/release 0.2.0` does not pop an approval modal — the substituted body is sent to the agent immediately, the same way typing the prompt by hand would be. But every mutating tool the agent calls in response (`write_file`, `edit_file`, `apply_diff`, `git_commit`, `run_bash`, …) still goes through the normal per-tool approval system. A multi-step command like `/release` will surface multiple approval modals during execution, one per mutating tool call. Three ways to reduce friction on commands you trust: - **Auto mode** (`Shift+Tab` or `yottacode --permission-mode auto`) — edits auto-allow; `run_bash`, `git_commit`, `git_checkpoint`, and `rollback` remain in the safety floor and still prompt. See [Auto mode](#auto-mode). - **`.yottacode/permissions.json` allow rules** — pre-approve specific shell invocations or tool patterns (`allow: ["Bash(go test*)", "Bash(go mod tidy)"]`). Rules apply equally to commands the agent calls from a custom-command turn and to anything else. - **`yottacode --yolo`** — everything auto-runs, with a high but finite iteration budget. Use only for fully-trusted scripted runs. See [Permissions bypass](#permissions-bypass). Per-command `allowed-tools:` frontmatter (a Claude Code feature that scopes which tools a command can call) is **not** supported in v1; the closest equivalent today is auto mode plus an `.yottacode/permissions.json` allow list. See [Out of scope](#out-of-scope-for-now). ### Out of scope (for now) To keep the v1 surface tight, two Claude Code features were deferred: - **`` !`` `` pre-execution** — embedding shell output in the prompt before send-off. Would require a per-command permission gate, output truncation, and timeout policy. Workaround: the body can tell the agent to call `run_bash` itself (one extra round-trip, but uses the existing approval gates). - **Per-command `model:` / `allowed-tools:` frontmatter** — pinning a command to a specific model or tool subset. Hot-reload is also deferred — changes to `commands/` files take effect the next time yottacode starts (same behavior as memory files). ### Worked example end-to-end 1. Create `~/.yottacode/commands/review-pr.md`: ```markdown --- description: Review a PR and suggest changes argument-hint: --- You are reviewing PR #$1. @docs/code-review.md Fetch the PR diff with `gh pr diff $1`, then walk the checklist file by file. Report findings in markdown. ``` 2. Launch `yottacode`. The palette filter `/rev` shows `/review-pr Review a PR and suggest changes` alongside built-ins. 3. Hit Enter on the row — the input fills `/review-pr ` (because `argument-hint` is set). 4. Type `123` and submit. 5. The user message that lands in scrollback reads `You are reviewing PR #123. docs/code-review.md …`, with `docs/code-review.md` injected into the system prompt for that turn. 6. The agent runs the normal turn loop — tool calls, streaming reply, approvals all behave the same as if you'd typed the prompt by hand. ### Built-in defaults Two markdown commands ship with the binary and are available on first launch — no setup, no `~/.yottacode/commands/` files needed. Both cover pre-commit / pre-PR correctness checks. They appear in the palette and `/help` under the **Custom commands:** section, tagged `(default)` so you can see they're shipped rather than authored. ``` /check:review [base] (default) /check:verify [task-or-hint] (default) ``` > **Note:** the git workflows that used to ship as markdown defaults > (`/git:commit-message`, `/git:create-pr`) are now the procedural > built-ins `/git-commit` and `/git-create-pr` (see the command > reference above). They're driven by composite Layer-1 tools so > empty staging, ahead-count gating, oversize titles / subjects, > trailing periods, push-state detection, gh-unavailable > fall-through, and hook failures are caught deterministically > rather than asked of the model in prose. > > Typing `/git` in the palette filters to every git-related > built-in. The full family today is `/git-commit`, > `/git-create-pr`, `/git-push`, `/git-update-pr`, and > `/git-review-pr` — five procedural commands covering the > commit → push → open PR → refresh PR → review PR workflow. > The slugs are flat (`git-commit`, not `git:commit`) because the > `:` namespace is reserved for custom-command path derivation; > built-in slugs use the kebab prefix for the same palette-filter > effect. #### What each does | Command | Role | |---|---| | `/check:review` `[base]` | Self-reviews the branch diff against the resolved base across six dimensions (correctness, scope, tests, style, security, performance). Emits findings grouped **Blocker / Suggestion / Nit** with `file:line` refs and a one-paragraph recommendation. | | `/check:verify` `[task-or-hint]` | Detects the project's stack — **Go, Python, Java (Maven or Gradle), Rust**, plus `Makefile` as the universal fallback — and runs the appropriate build / test / lint commands. **Go runs with `-count=1` mandatory** to bypass the test cache (no stale-pass surprises). On failure, diagnoses by re-running the failing test in isolation AND checking `git log` to see if the test was touched in this branch — never declares "pre-existing" without that evidence. The argument is mixed-purpose free-form: a task description (cross-checked against the diff for scope drift) and/or a stack hint or command override (e.g., ``use `cargo make verify` ``) that single-turns unsupported stacks. Anything outside the four supported stacks falls through to "Unknown — ask the user" rather than guessing. Prints a structured **Verdict** (Done / Not done / Done with caveats / Inconclusive). | Both use only existing tools (`run_bash`, `read_file`, `git_*`) — no new infrastructure. Each invocation runs through the normal per-tool approval gates; see [Permissions and approvals](#permissions-and-approvals). #### Overriding a default To customize a default's body (e.g. you want `/check:review` to enforce a team-specific checklist), drop a file at the **same name path** in user or project scope: ``` ~/.yottacode/commands/check/review.md → overrides the default for you everywhere /.yottacode/commands/check/review.md → overrides for anyone working in that repo ``` The override is **silent** — no startup warning fires when a user/project file shadows a default, because customizing the starter kit is the documented use case, not a misconfiguration. The override wins on every invocation; delete the file to fall back to the embedded default. Precedence summary (highest priority first): 1. Project scope (`/.yottacode/commands/`) 2. User scope (`~/.yottacode/commands/`) 3. Built-in defaults (embedded in the binary) Built-in commands like `/help`, `/clear`, `/model`, `/plan` sit above all three tiers and cannot be shadowed. ## Agent Skills A **skill** is a reusable capability playbook the agent loads on demand. Names + descriptions are always in the system prompt so the model picks the right skill by keyword match; the body is loaded only when invoked. Skills are spec-compliant with [agentskills.io](https://agentskills.io/specification), so a skill authored for Claude Code drops in without changes. ### Default policy: off **Skills are off by default each session.** The model sees no skill list in its system prompt at startup; the SkillTool's `Skill(skill="")` call returns "unknown skill" until you opt in. Open `/skills` to pick which skills to expose — selection lasts the session. A startup line like `[skills] 10 available — type /skills to enable for this session` surfaces the gate. This trades convenience for context discipline: the model can't ambient-reach for a skill the user didn't ask about, and the prompt stays small. ### Two ways to invoke - **Model-side** — once you've enabled it via `/skills`, the agent can call `Skill(skill="")` when a user request matches a skill's described scope. The tool returns the body so the model can apply it in the current turn. - **User-side** — type `/` to inject the skill body yourself, optionally with extra context (`/remote-ops tail logs on prod-app-01`). Slash invocations **bypass the enablement gate** because typing the slash IS the selection. The body lands in the next user message and the model continues from there. ### The `/skills` menu `/skills` opens a top-level menu — pick a row to act on: | Item | What it does | |---|---| | `Catalog` | Open the picker; tabs for Built-in vs Installed (user + project). | | `Install` | Inline textinput for the source string. Submit with Enter, cancel with Esc. | | `Uninstall` | Focused list of installed (user-scope) skills; Enter removes the selected one. Built-in and project skills aren't listed. | | `Check` | Run the drift report. Output lands in the transcript. | | `Update` | Re-fetch every tracked skill from its recorded source. Output lands in the transcript. | Inside the Catalog picker: | Key | Action | |---|---| | `Up` / `Down` | Move cursor within the active tab | | `Left` / `Right` / `Tab` | Cycle Built-in ↔ Installed (cursor resets) | | `/` | Filter rows by substring (matches name + description) | | `Space` | Toggle the cursor row's enablement | | `a` / `n` | Enable / disable all in the current tab | | `Enter` | Open the cursor row's body in `$PAGER` (for review) | | `u` | Uninstall the cursor row (Installed tab only; built-ins are embedded) | | `Esc` | Save enablement toggles and close (writes the enabled set to `[skills] default_on` so it survives restart; uninstalls already took effect) | While the filter is active: type to narrow rows, `Backspace` edits, `Enter` keeps the filter and resumes row navigation, `Esc` clears the filter and exits filter mode. The Catalog shows every loaded skill in the active tab with its source tag and description. On commit, the system prompt is recomposed so the next turn sees the updated "Available skills" section. ### Persistent default-on set The Catalog picker auto-saves your enablement on Esc — committed toggles are written to `~/.yottacode/config.toml` as `[skills] default_on`, so the next session restores the same set without you re-picking. Matches Claude Code's auto-persisting `/skills` and Hermes Agent's saved enablement. You can also hand-edit the block if you prefer config-as-code: ```toml [skills] default_on = ["test-driven-development", "diagnose"] ``` Either way, names that don't match any loaded skill produce a stderr warning at startup so a typo surfaces immediately. Without this block (or after un-toggling everything), sessions start with nothing enabled (the small-prompt default). Uninstalling a skill from the Catalog (Installed tab → `u`) also scrubs its name from `default_on` so the startup warning doesn't fire for an entry the picker itself just removed. ### Install, list, show, uninstall `/skills` overloads on subcommand — no args opens the picker; the four subcommands mirror the `yottacode skills` CLI tree for in-TUI management. | Form | What it does | |---|---| | `/skills install [--force]` | Install a skill from a local path, `https://.../SKILL.md` URL, or `owner/repo[/path]` GitHub shorthand. Refuses to overwrite an existing slug unless `--force` is set. | | `/skills show ` | Print one skill's full body — the same bytes the model receives when it calls `Skill(skill="")`. | | `/skills uninstall ` | Remove a user-scope skill from `~/.yottacode/skills/`. Built-in and project-scope skills are out of scope (project skills are committed source — remove via git/rm; built-ins are embedded in the binary). | | `/skills check [name]` | Report drift between the installed bytes and `~/.yottacode/skills/.lock.json`. Statuses: `ok`, `modified`, `missing-lock`, `orphaned-lock`, `hash-error`. Read-only. | | `/skills update [name] [--force]` | Re-fetch from the originally-recorded source. Skips installs whose on-disk hash diverges from the lockfile unless `--force` is set, so hand-edits aren't silently overwritten. | Source shapes: ```text ./path/to/skill local directory containing SKILL.md ./path/to/skill/SKILL.md local SKILL.md file (no resources copied) https://.../SKILL.md single-file fetch — URL must end in /SKILL.md owner/repo GitHub repo root (must contain SKILL.md) owner/repo/path/to/skill GitHub subpath; scripts/, references/, assets/ are walked via the Contents API ``` The installer writes into `~/.yottacode/skills//` where `` is taken from the SKILL.md frontmatter `name` — so a source dir named `weird-name` whose frontmatter says `name: sample` lands at `~/.yottacode/skills/sample/`. The on-disk dir name is always the canonical slug. Authenticated GitHub fetches: set `GITHUB_TOKEN` to lift the 60-req/hr unauthenticated rate limit on the Contents API. Only sent to `api.github.com` hosts. ### Provenance and updates Every install records a row in `~/.yottacode/skills/.lock.json`: ```json { "version": 1, "entries": { "remote-ops": { "name": "remote-ops", "source_type": "github", "source": "obra/superpowers/skills/remote-ops", "hash": "sha256:…", "installed_at": "2026-05-27T12:00:00Z", "trust": "unverified" } } } ``` The lockfile is dot-prefixed so the skill loader skips it. `trust` is reserved for a future signing/trust subsystem and is always `"unverified"` in this release. `/skills check` compares the on-disk hash of every installed skill to its recorded hash; `/skills update` re-runs the installer against the recorded `source` and refreshes the lockfile entry. The dirty check on `update` ensures a hand-edit is never silently overwritten — you'll see `skipped-user-modified` until you pass `--force` to confirm the overwrite. ### Discovery Three tiers, project wins: 1. **Project scope** — `/.yottacode/skills//SKILL.md` (committable) 2. **User scope** — `~/.yottacode/skills//SKILL.md` 3. **Built-in** — 16 skills compiled into the binary: - **Engineering loop**: `test-driven-development`, `verification-before-completion`, `diagnose`, `writing-plans`, `executing-plans`, `brainstorming`, `receiving-code-review`, `handoff` - **Architecture & perf**: `improve-codebase-architecture`, `prototype`, `performance-profiler` - **Targeted reviews**: `dockerfile-review`, `security-auditor`, `webapp-testing` - **Ops & history**: `remote-ops`, `git-investigation` A skill's directory name must match its frontmatter `name` exactly. Names that would shadow a built-in slash command (`help`, `plan`, etc.) are dropped with a startup warning. ### SKILL.md format ```markdown --- name: remote-ops description: SSH/scp/rsync playbook for connecting to remote hosts. license: MIT metadata: author: you slash: "true" allowed-tools: Bash(ssh:*) Bash(scp:*) Read --- # Remote operations …body in markdown… ``` | Field | Required | Notes | |---|---|---| | `name` | yes | `[a-z0-9-]{1,64}`, must match parent dir | | `description` | yes | 1-1024 chars; keyword-rich (drives matching) | | `license` | no | string or LICENSE.txt reference | | `compatibility` | no | ≤500 chars, documentation-only | | `metadata` | no | free-form map for host-specific keys | | `metadata.slash` | no | `"false"` opts out of the `/` palette entry; default is exposed | | `allowed-tools` | no | **parsed but not enforced in v1** — gated on the per-tool sandbox direction | A skill may ship `scripts/`, `references/`, `assets/` subdirectories. The body references them by relative path (`./scripts/check.sh`); the agent reads them via `read_file` or runs them via `run_bash` on demand. ### Out of scope (for now) - `allowed-tools` enforcement — landing alongside the broader per-tool sandbox work. - Ed25519-signed skills + a public registry — post-v0.4.0 per the roadmap. The lockfile's `trust` field is reserved for this. ## Plan mode `/plan` (or `Shift+Tab`) toggles plan mode — a read-only research state that mirrors Claude Code's `/plan`. The agent investigates the request, asks clarifying questions, and writes a plan file under `~/.yottacode/plans/.md`. While plan mode is on: - Read-only tools (`read_file`, `grep`, `glob`, `list_*`, `git_log_file`, `fetch_url`, …) work normally. - `todo_write` works normally. - `write_file` / `edit_file` / `apply_diff` are blocked except when writing to the resolved plan file — writes to the plan file auto-allow without a prompt (it's the only legitimate mutation surface during planning). - Every other mutating tool (`run_bash`, `git_commit`, `git_stage_files`, …) returns a "tool unavailable in plan mode" message to the model. - A one-line banner immediately above the cmdline shows the mode, the plan file name (or "pending" before the file exists), and the current agent activity during a turn. `/plan` and `Shift+Tab` take no arguments — the plan slug is derived from the first user message of the plan-mode session. The banner shows "ready — your next message names the plan" until that message arrives. You can also launch directly into plan mode with `yottacode --permission-mode plan`. If the model surfaces material ambiguity during investigation — questions whose answers would change the plan's scope, approach, or target files — it is instructed to ask in its reply and end the turn *without* calling `exit_plan_mode`, so you can answer in your next message. The approval modal is hotkey-only ([A]/[M]/[L]/[K]); putting dangling questions next to it would leave you with no way to type answers. Trivia that doesn't change the plan's shape can still live in the plan's "Open questions" section. When the model finishes investigating and the plan is unambiguous, it calls the `exit_plan_mode` tool — which takes no arguments; the TUI reads the plan body from the file on disk and renders it in an approval card with four hotkeys: - **`[A]` auto-approval** — exits plan mode AND enters auto mode for the implementation. Edits auto-allow; `run_bash`, `git_commit`, `git_checkpoint`, and `rollback` still prompt (safety floor). - **`[M]` manual approval** — exits plan mode and the agent immediately resumes execution. Per-tool approval prompts continue as normal, so you can review each step. - **`[L]` later** — exits plan mode but signals the model to *end the turn now without implementing*. The plan file stays on disk; resume any time via `/plan list` or `yottacode --plan-resume `. - **`[K]` keep planning** — stays in plan mode; the model gets refinement guidance and is expected to revise the plan file and call `exit_plan_mode` again. If the plan file is missing or empty when `exit_plan_mode` is called, the TUI auto-denies with a console notice. ## Auto mode Press `Shift+Tab` from normal mode (or launch with `yottacode --permission-mode auto`) to enter auto mode — a state where mutating tools auto-allow without the per-tool approval modal. Reduces friction during a multi-step implementation when you trust the plan. Mirroring Claude Code, auto mode has no slash command; the entry points are the `Shift+Tab` cycle, the `--permission-mode auto` startup flag, and the plan-card's `[A]` auto-approval hotkey. Safety floor (always prompts even in auto mode): - `run_bash` — arbitrary shell commands. - `git_commit` — writes permanent git history. - `git_checkpoint` — writes a checkpoint commit. - `rollback` — resets the repo state. **`run_bash` carve-out for read-only inspection.** The model habitually opens implementation work with `cd && grep …` chains. To keep auto-mode flow uninterrupted, a `run_bash` call auto-allows (without showing the modal) when every segment uses a verb from a built-in read-only allowlist (`ls`, `cat`, `head`, `tail`, `wc`, `grep`, `rg`, `find`, `awk`, `cut`, `sort`, `uniq`, `diff`, `cd`, `pwd`, `which`, `echo`, `date`, `tree`, `stat`, `file`, `du`, `df`, …) AND no segment carries a risk flag (no `>` redirects, no pipe-into-shell, no sudo). Anything mutating (`rm`, `mv`, `touch`, `mkdir`, `curl`, `go test`, `sed -i`, …) still prompts. Auto mode and plan mode are mutually exclusive — entering one exits the other. The two share the `Shift+Tab` chord: ``` Shift+Tab cycle: normal → auto → plan → normal ``` The plan-approval card's `[A]` auto-approval hotkey is a shortcut: it approves the plan AND enters auto mode in one keystroke, so the agent can implement the approved plan with minimal friction. (Pick `[M]` instead if you want plan mode to exit but keep per-tool prompts.) Auto mode persists across turns until you toggle it off. The banner above the cmdline (`▸ auto mode · edits + read-only bash auto-allow; commits prompt`) is always visible while active so the state isn't easy to forget. The default per-turn iteration cap is 50; auto mode raises the effective cap to 200 (4×). If you still hit the cap on long implementations, run `/max-iterations 500` (sanity ceiling) or relaunch with `--yolo` (raises the cap to `max-iterations × 20`, at least 1000; see [Permissions bypass](#permissions-bypass)). ## Permissions bypass Permissions bypass is the unrestricted overlay — every tool auto-runs (`run_bash`, `git_commit`, edits, everything), and the iteration cap is raised to a generous but finite budget (`max-iterations × 20`, at least 1000) so a runaway model still terminates. Intended for unattended long-running implementations where you've decided no further oversight is needed. The startup flag is `--yolo`; the in-TUI banner label reads "permissions bypass" (the codebase calls the overlay state "yolo" internally). Mirroring Claude Code, the overlay enters **only via `yottacode --yolo` at startup**. There is no slash command, no `Shift+Tab` binding, and no in-TUI toggle — opt in once per process, and recovery requires restarting yottacode without the flag. This is deliberate: the high-autonomy state should be a conscious one-time decision, not a key chord away. The overlay is a **modifier**, not a mode — once active, it sits on top of normal, auto, or plan. Entering auto or plan via `Shift+Tab` does not turn bypass off. The bypass banner takes visual priority while it's on (it's the loudest signal), and when a mode (auto or plan) is also active, the mode banner picks up a `⚠ bypass` suffix instead. Explicit `deny` rules in `.yottacode/permissions.json` still win — the bypass overlay is "skip prompts," not "ignore my policy." `Ctrl+C` is the escape hatch if a model goes into a runaway loop. The banner (`⚠ permissions bypass · all tools auto-allow · high iteration cap`) renders in red so the state isn't easy to forget; when a mode (auto or plan) is also active, the mode banner picks up a `⚠ bypass` suffix instead. Plan-mode state is per-launch — a new `yottacode` session starts in normal mode, and resuming an old session never re-enters plan mode automatically. Plan files persist on disk under `~/.yottacode/plans/`, sorted newest-first. To resume an earlier plan: - **`/plan list`** opens a picker over saved plans (newest first). Enter resumes; Esc closes. Resuming attaches the plan file to plan mode (creates the mode if it's off) and re-applies the per-tool write allowance. - **`yottacode --plan-resume `** at launch matches the substring against saved plans (case-insensitive) and resumes the most recent match. Unmatched values fall back to a fresh plan with a stderr warning. Plans never expire automatically — clean up the directory manually if it gets crowded. The plan-mode gate runs *before* permissions evaluation, so explicit deny rules in `.yottacode/permissions.json` still win. `--yolo` does not skip the `exit_plan_mode` approval card — that approval is the user-visible signal, not a safety gate. ## Checkpoints (`/checkpoints` / `Esc Esc`) Every user prompt automatically creates a checkpoint *before* the agent responds: a snapshot of the conversation history plus the pre-edit contents of any files the agent is about to touch. `/checkpoints` or **`Esc Esc`** (double-tap within 500ms) opens a picker over those checkpoints, newest first. Pick a checkpoint and choose one of four actions: - **Restore code and conversation** — rewrite tracked files and rewind history to the moment that prompt was sent. The original prompt reappears in the input box so you can edit and resend. - **Restore conversation only** — rewind history; files are left untouched. - **Restore code only** — rewrite tracked files; conversation continues from where it is now. - **Summarize from here** — compress history up to that prompt, then keep going. Files untouched. **What's tracked.** Only file changes made through the `write_file`, `edit_file`, `apply_diff`, `delete_file`, `move_file`, and `copy_file` tools. Mirrors Claude Code's `/rewind`. **Bash mutations (`rm`, `sed`, `mv`, redirects), git operations, and external edits to files are NOT tracked** — those are off-checkpoint side effects. **Storage.** `~/.yottacode/checkpoints//`. File pre-images are content-addressed and deduped across checkpoints, so editing the same file twice only stores its two distinct pre-images. Checkpoints expire 30 days after creation by default; configure with `[checkpoints] retention_days = N` in `~/.yottacode/config.toml`. Sweep runs opportunistically on session open. **Caveats.** Directories (`mkdir`) aren't restored. Permission bits on restored files are limited to `0o777` (no setuid/setgid). Restoring code under an active turn is not allowed — the picker is gated until the turn ends. ## Interrupting a turn Pressing **Enter** while the agent is thinking captures whatever you typed, cancels the in-flight iteration, and queues the message for auto-submission the moment the loop unwinds. Any tokens that streamed before the cancel land in history as a partial assistant message, and any tool calls that were in flight or queued get a synthetic `interrupted by user` tool result so the next turn sees a valid conversation. This is the "interrupt with feedback" path — works the same in normal, plan, and auto modes. Press **Esc** or **Ctrl+C** while a turn is running to cancel without submitting. Any queued message is dropped; the textarea contents are preserved so a draft survives an accidental Esc. Slash commands typed mid-turn (e.g. `/clear`, `/model`) follow the same rule they always have — they cancel the turn and execute immediately. Slash commands that the codebase marks `PreservesTurn=true` (`/subagents`, `/help`) inspect without cancelling. Either way, a slash command mid-turn discards any plain-text message that was queued by an earlier Enter, so a `/clear` doesn't resurrect a stale follow-up message into a wiped session. ## Palette behavior - Choosing a command with no args executes it immediately. - Choosing a command that needs args fills the command prefix, such as `/model `, and waits for input. - If you already typed a full command with args, Enter executes what you typed. ## Keyboard shortcuts - `Enter` submits (mid-turn: interrupt and queue the new message) - `Ctrl+J` inserts a newline - `Esc` cancels the current turn (alias for Ctrl+C, mirrors Claude Code) - `Esc Esc` (idle, tapped within 500ms) opens the `/checkpoints` picker - `Ctrl+C` cancels the current turn; quits when no turn is running - `Ctrl+D` exits when input is empty - `?` opens the cheatsheet when input is empty - `Shift+Tab` cycles agent modes: normal → auto → plan → normal The TUI uses inline rendering rather than an alternate screen, so your terminal scrollback remains available. ================================================================================ # Themes Source: https://yottacode.ai/docs/themes/ The yottacode TUI ships with eleven built-in color palettes. Switch between them with `/theme` — an interactive picker with a live preview pane — or pin one in `~/.yottacode/config.toml` so every new session boots into it. ## The picker `/theme` opens a two-pane overlay. Theme names live on the left; the right pane shows a live showcase rendered in the **highlighted** palette — inline tokens, a chroma-highlighted code block, a diff fragment, and status / state markers. | Key | Effect | |---|---| | `↑` / `↓` (also `j` / `k`) | Move the cursor; the highlighted theme is live-applied to the whole TUI so you see the new look across the entire surface, not just the preview pane. | | `Home` / `End` | Jump to the first / last theme. | | `Enter` | Commit the highlighted theme and persist it to `~/.yottacode/config.toml`. | | `Esc` | Cancel — revert to the theme that was active when the picker opened. Navigation is non-destructive. | The cursor opens on the currently-active theme. The green `❯` arrow stays green across every palette so the "you are here" cue doesn't shift color when you preview a theme. ## Scriptable shortcuts For muscle memory or non-interactive callers (e.g. one-shot mode), two forms bypass the picker: ``` /theme set # explicit /theme # positional shortcut, same effect ``` Both apply, persist, and report `[theme] switched to "" (persisted)`. ## Built-in palettes Order matches what the picker displays: `terminal` leads (the universal-safe and default pick), then alphabetical. | Name | Description | Chroma style | |---|---|---| | `terminal` (default) | The main-branch look — adaptive colors that respect your terminal background. Foreground colors are spelled as `AdaptiveColor` light/dark pairs (dark text on a light terminal, light text on a dark terminal); the ✓ success dot stays on ANSI green (2/10) so it matches your terminal palette exactly. The "what yottacode looked like before themes" theme. | terminal default (`monokai`) | | `catppuccin` | Catppuccin Mocha — pastel lavender, peach, and teal on warm dark. The most-requested community theme of the last few years. | `catppuccin-mocha` | | `dimmed` | GitHub Dark Dimmed — soft graphite slate (`#22272e`) with muted blue/green accents on a `#adbac7` foreground. GitHub's own "easier on the eyes" alternative to full dark mode. The backdrop paints chrome surfaces (palette overlay, approval modal, watermark box) so the theme identity is visible; fenced code blocks intentionally **do not** pick up the backdrop, so chroma syntax highlighting stays clean. | `github-dark` | | `gruvbox` | Warm retro — burnt orange, mustard yellow, and sage green on dark brown. The popular Vim colorscheme. | `gruvbox` | | `high-contrast` | Maximum legibility — bright primaries on a transparent background, no mid-greys. Designed for low-vision and bright-room terminals. | `github-high-contrast` | | `low-contrast` | The deliberate inverse of `high-contrast` — every role pulled toward mid-grey so the entire surface sits in a narrow tonal band and nothing pops. State colors carry a faint hue cast (cool / green / amber / rose) but stay desaturated and tonally close to body text. A "calm" reading surface for long ambient sessions; not the right pick if you need errors to be loud. | `github` | | `no-color` | Monochrome — every role renders as default terminal foreground. Even fenced code blocks render in B&W. Useful for piping to tools that don't strip ANSI, or for users who prefer a flat surface. | `bw` | | `nord` | Arctic Ice Studio's cool blue-grey — restrained northern tones, minimal saturation. | `nord` | | `one-dark` | Atom's classic slate with cool blue, green, and a distinctive coral red. The "default dark" muscle memory for a large slice of devs. | `onedark` | | `solarized-dark` | Ethan Schoonover's classic — deliberate L*a*b* tones, blue base, amber/violet accents. | `solarized-dark` | | `tokyo-night` | Deep navy with vivid blue, purple, and cyan. More saturated than Nord while staying cool; very popular in the Neovim community. | `tokyonight-night` | ## Why there's no true "light" theme yottacode is an **inline** TUI — it preserves your terminal scrollback rather than taking over an alternate screen. lipgloss styles only paint behind text they render, not behind the whole viewport, so a theme can't make a dark terminal look like a light one. The `dimmed` theme works around this for chrome surfaces (palette overlay, approval modal) only. If you want the full light experience, run yottacode in a terminal whose background is light. For "softer than dark, regardless of terminal," use `low-contrast`. ## Persistence The picker's Enter (or `/theme set `) writes: ```toml [theme] name = "dimmed" ``` to `~/.yottacode/config.toml`. The default theme (`terminal`) is *not* persisted — omitting the section keeps the file minimal for users who never touched the command. A typo in the persisted `name` is rejected at load time: ``` config: ~/.yottacode/config.toml: theme.name = "termnal" is not a registered theme (try one of terminal, catppuccin, dimmed, gruvbox, high-contrast, low-contrast, no-color, nord, one-dark, solarized-dark, tokyo-night) ``` ## Adding a theme (for contributors) Drop an `internal/tui/themes/.go` file with a single `init()` that calls `register(Palette{ Name: …, Description: …, Highlight: …, Accent: …, …})`. The TUI's `styles.go` builds every UI style from those role colors; nothing else needs editing for the new theme to surface in the picker. Tests in `themes_test.go` assert the registered set — append the new name to `expectedThemes` in the right slot (head pins `terminal` first; the tail is alphabetical) so the registry-coverage test stays accurate. Lock the theme contract: - **Every role must be filled.** `styles.go` reads all ten role fields; an unset `lipgloss.AdaptiveColor{}` renders as default-foreground (effectively invisible against the terminal background) and looks like a bug. - **`HasBackground = true` is opt-in.** Only set it when you want chrome surfaces (palette overlay, approval modal, watermark box) painted with your `Background` color. Code blocks are intentionally exempted to preserve chroma syntax highlighting; most themes leave `HasBackground` zero so the user's terminal background shows through. - **`Highlight` is the chroma style name.** Pair it deliberately — a light UI palette with `monokai` reads as broken; a dark UI with `github` washes out fenced code. - **Pin vs. adapt.** Use `pin(...)` when the theme's identity is "this exact look on every terminal" (Catppuccin, Gruvbox, Solarized). Use `lipgloss.AdaptiveColor{Light: …, Dark: …}` when the theme should respond to terminal background detection (`low-contrast`). ================================================================================ # Security and allow lists Source: https://yottacode.ai/docs/security-and-allow-lists/ yottacode is designed to be explicit about risk. It can inspect, edit, test, and run commands in your project, so approval and path policy matter. ## Folder trust On first launch in a directory yottacode prompts: ``` Accessing workspace: /home/me/my-repo Quick safety check: is this a project you created or one you trust? (Your own code, a well-known open-source project, or work from your team.) If not, take a moment to review what's in this folder first. yottacode will be able to read, edit, and execute files here. [1] Yes, I trust this folder [2] No, exit ``` The decision is recorded in `~/.yottacode/trusted-roots.json`. Every subfolder of a trusted root inherits the trust automatically, so cloning a new repo under an already-trusted parent does not re-prompt. **Skip the prompt:** - `--allow-paths ` or `YOTTACODE_ALLOW_PATHS=` — passing the cwd's tree as an allow-paths root satisfies the gate session-only (no write to `trusted-roots.json`). - `YOTTACODE_TRUST_ALL=1` — CI escape hatch. Also session-only. - `yottacode run` (non-interactive) — trust verification is skipped entirely, matching Claude Code's `-p` behavior. - `--yolo` does **not** skip the trust gate on its own. Combine with `YOTTACODE_TRUST_ALL=1` for fully unattended runs. **Manage trust roots:** ```bash yottacode trust list # show every trusted root + grant timestamp yottacode trust add [path] # default: cwd yottacode trust remove # exact-path match yottacode trust clear # remove every entry ``` **Worktrees and trust.** `yottacode --worktree ` requires the repo root to be trusted; it refuses on an untrusted clone with a one-line hint to run `yottacode` in the repo once. Worktree directories live in user home at `~/.yottacode/worktrees///`. The first-launch trust gate inside a worktree session resolves back to the originating repo via `git rev-parse --git-common-dir`, so the worktree dir itself never needs to appear in `~/.yottacode/trusted-roots.json` — trust on the repo flows through. An active worktree session can write inside its own cwd normally; write validation reads cwd dynamically so an in-session `enter_worktree` swap doesn't leave the validator locked to the pre-swap perimeter. See [worktrees.md](/docs/worktrees/). **Out-of-workspace writes.** When the model tries to write a file outside cwd + `--allow-paths` roots, yottacode shows an inline elevation prompt: ``` Write outside workspace Requested path: /home/me/elsewhere/notes.md Session workspace: /home/me/my-repo [1] Allow once — trust just this file for the session [2] Trust for session — trust this directory and every subfolder [3] Reject — model sees the original error ``` `[1]` and `[2]` are session-scoped — they do **not** write to `trusted-roots.json`. Cross-session expansion still goes through `--allow-paths` or `yottacode trust add`. ## Approval model By default: - read-only tools run without prompting - mutating filesystem tools prompt - shell commands prompt - git read-only commands run without prompting - git mutations prompt - destructive flags are called out in previews Approval prompts can be answered once or turned into a reusable allow rule. ### Gate precedence Tool calls flow through layered gates in this order: 1. **`Deny` rules** in `permissions.json` always win. 2. **Plan-mode gate** (only when plan mode is active) — blocks every mutating tool except `todo_write`, `exit_plan_mode`, and writes to the resolved plan file. Returns a structured error to the model so it can switch to a read-only or plan-file alternative. 3. **Permissions-bypass auto-allow** (only when `--yolo` was passed at startup) — every tool auto-allows silently. No safety floor. 4. **Plan-mode auto-allow** — writes to the resolved plan file are the model's only legitimate mutation surface while planning; they auto-allow without a prompt. 5. **Auto-mode auto-allow** (only when auto mode is active) — non-safety-floor mutating tools auto-allow. Safety floor (`run_bash`, `git_commit`, `git_checkpoint`, `rollback`) normally still prompts, with one carve-out: `run_bash` calls whose every segment uses a verb from a built-in read-only allowlist (`ls`, `cat`, `head`, `tail`, `wc`, `grep`, `rg`, `find`, `awk`, `cut`, `sort`, `uniq`, `diff`, `cd`, `pwd`, `which`, `echo`, `date`, `tree`, `stat`, `file`, `du`, `df`, …) AND carries no risk flag (no `>` redirects, no pipe-into-shell, no sudo) auto-allow under Source `auto-mode-safe-bash`. The intent: a model's habitual `cd && grep …` chain doesn't break flow, while any mutation (rm, mv, touch, curl, go test, sed -i, …) still prompts. 6. **`Allow` rules** in `permissions.json` skip the prompt. 7. **`Ask` rules** force a prompt even on tools that would normally auto-execute. 8. **Tool-default policy** (the tool's own `RequiresApproval`) prompts mutating tools and auto-executes read-only ones. `Deny` always wins, including over permissions bypass. `--yolo` is "skip prompts," not "ignore my policy." Trust controls separate into **modes** (workflow shape, mutually exclusive) and the **permissions-bypass overlay** (orthogonal startup flag): | Surface | Entry point | Effect | |---|---|---| | Plan mode | `/plan` · `Shift+Tab` · `--permission-mode plan` | Read-only research; gated to plan file; ends with `exit_plan_mode` | | Auto mode | `Shift+Tab` · `--permission-mode auto` (no slash command) | Edits auto, bash/commits prompt, 4× iteration cap | | Permissions bypass | `--yolo` at startup (no slash, no keybinding) | Drops all prompts, no iteration cap; sits on top of any mode | Mirroring Claude Code, auto mode has no slash command and permissions bypass enters only via the startup flag — these high-autonomy states are intentionally kept off the palette and off the `Shift+Tab` cycle so they can't be triggered by accident. Permissions bypass, once enabled, is one-way per process: restart without the flag to recover. The bypass banner (`⚠ permissions bypass`) takes precedence visually while it's on; when a mode (auto or plan) is also active, the mode banner picks up a `⚠ bypass` suffix. ## No in-process sandbox > [!CAUTION] > yottacode has no in-process sandbox — `run_bash`, file edits, and git commands run directly on the host. For real isolation, run yottacode inside a container or devcontainer. yottacode does not sandbox tools inside its own process. `run_bash`, file edits, git commands, and other tools run on the host. For real isolation, run yottacode itself inside a container or devcontainer. That isolates every tool, not just shell commands. This is a deliberate scope choice: yottacode does not ship bwrap, firejail, landlock, or pluggable sandbox backends. ## Write-path validation Mutating filesystem tools are constrained before they run: - paths must be inside the current working directory or configured extra allow roots - symlink writes are rejected - yottacode app state is denied - canonical `.git` internals are denied - `.git/hooks/` is deliberately allowed - in `/plan` mode, the *only* write target outside cwd is the resolved plan file at `~/.yottacode/plans/.md` — symlink rejection and the deny list still apply Extra write roots: ```bash export YOTTACODE_ALLOW_PATHS=/home/me/shared-configs,/home/me/other-repo ``` ## Read protection Read tools do not prompt, so yottacode blocks common secret-bearing paths from silent reads, including examples like: - `~/.ssh` - `~/.aws` - `~/.gnupg` - `~/.netrc` - `~/.kube/config` - `~/.docker/config.json` - `/.env` - `/.env.local` If you truly need to inspect a protected file, do it through an explicit shell command that prompts for approval. ## Permission files Project-local permission rules live in `/.yottacode/`: {{< tabs items="permissions.json,permissions.local.json" >}} {{< tab >}} Team-shared rules that can be committed: ```text /.yottacode/permissions.json ``` {{< /tab >}} {{< tab >}} Personal rules that should be gitignored: ```text /.yottacode/permissions.local.json ``` Add this to `.gitignore`: ```gitignore .yottacode/permissions.local.json ``` {{< /tab >}} {{< /tabs >}} ## Rule shape ```json { "permissions": { "allow": ["Bash(go test *)", "Edit(internal/**)"], "deny": ["Bash(rm *)"] } } ``` Rules support `allow`, `ask`, and `deny` policy. Explicit deny rules still apply even when `--yolo` is set. ## Creating allow rules from approvals When an approval modal appears, choose the always-allow option to save a derived rule into `permissions.local.json`. The modal shows the exact rule before it is saved. Examples: - `Bash(go test *)` - `Edit(internal/**)` - `Write(docs/**)` - `Git(commit *)` - `MCP(filesystem/read_*)` — allow filesystem MCP server's read tools (see [MCP](/docs/mcp/)) - `MCP(github/*)` — allow every tool from the GitHub MCP server ## Permissions bypass (the danger setting) > [!WARNING] > `--yolo` drops approval prompts and raises the iteration cap to a high but finite budget. Use it only in trusted automation or disposable environments — explicit deny rules still apply, and you must restart without the flag to recover. ```bash yottacode --yolo ``` This is dangerous. It skips approval prompts for matching operations and raises the iteration cap to a high but finite budget, but explicit deny rules remain enforced. Use it only in trusted automation or disposable environments. There is no in-TUI toggle — restart without the flag to recover. ## Provider-hosted search allow lists Provider-native web search can be restricted with domain filters: ```bash export YOTTACODE_SEARCH_ALLOWED_DOMAINS=docs.example.com,github.com export YOTTACODE_SEARCH_EXCLUDED_DOMAINS=spam.example ``` xAI `x_search` can be restricted with handle and date filters: ```bash export YOTTACODE_X_SEARCH_ALLOWED_HANDLES=xai,openai export YOTTACODE_X_SEARCH_EXCLUDED_HANDLES=badhandle export YOTTACODE_X_SEARCH_FROM_DATE=2026-01-01 export YOTTACODE_X_SEARCH_TO_DATE=2026-12-31 ``` ## Secrets guidance > [!WARNING] > Never put secrets in prompts, `USER.md`, `YOTTACODE.md`, or any agent-managed memory file — those are sent to your model provider. Keep API keys in environment variables, not in `config.toml`. Do not put secrets in prompts, `USER.md`, `YOTTACODE.md`, or any agent-managed memory file. Those files are included in prompts sent to your configured model provider. Keep API keys in environment variables, not in `config.toml`. ================================================================================ # Architecture Source: https://yottacode.ai/docs/architecture/ `yottacode` is deliberately small and layered: one agent loop, one event channel, two user interfaces, and a set of structured tools. ## Core Data Flow ```text ┌──────────────────────┐ │ User Input │ │ (CLI or TUI) │ └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ TUI / Oneshot │ │ (Consumer) │ └──────────┬───────────┘ events ◄──────── │ decisions │ ▼ ┌──────────▼───────────┐ │ agent.Turn │ │ (Main Loop) │ │ ┌───────────────┐ │ │ │ Tool Registry │◄─┘ │ └───────────────┘ │ │ │ │ │ ▼ │ │ Adapter Stream │ └──────────────────────┘ │ ┌──────────▼───────────┐ │ Model Provider API │ │ (OpenAI-compatible) │ └──────────────────────┘ ``` ## Package Layout ```text cmd/yottacode/ cobra root command + run subcommand internal/ cli/ ChatOptions resolution adapter/ OpenAI-compatible streaming + OpenAI Responses routing agent/ Turn loop, tool registry, approvals, write-path validation session/ Session persistence and resume logic memory/ USER.md / YOTTACODE.md loading, agent-managed memory store, retrieval orchestrator recall/ SQLite + FTS5 indexing for /recall tui/ Bubble Tea interface, approval UI, slash commands oneshot/ non-interactive `yottacode run` consumer version/ version string ``` ## Core Runtime Model `internal/agent.Turn` is the only part of the system that talks to the model and executes tools. Everything else either prepares its inputs or consumes its events. ```text user input | v consumer (tui or oneshot) | ^ | events | decisions v | agent.Turn --------------------> tool registry | v adapter stream ``` This split is the reason the TUI and `yottacode run` can share nearly all of the execution stack. ## Event Flow The agent emits typed events such as: - streamed assistant content - streamed reasoning updates - approval requests and auto-approval notices - tool start and tool result messages - iteration-cap warnings - terminal completion or error events Consumers decide how to render those events. The Bubble Tea UI turns them into transcript rows, status changes, and approval modals. The one-shot runner sends answer content to stdout and operational detail to stderr. ## Session Lifecycle ### `yottacode` Running `yottacode` with no subcommand starts the interactive TUI directly. Startup flow: 1. Parse flags and resolve environment variables. 2. Open or resume a session. 3. Load `USER.md`, `YOTTACODE.md`, and any agent-managed memories under user and project scope. 4. Build the model adapter and tool registry. 5. Start the Bubble Tea program and hand user turns to `agent.Turn`. ### `yottacode run` `yottacode run ""` uses the same core loop, but without the TUI. - Prompt input comes from the CLI argument or stdin. - Assistant content is written to stdout. - Reasoning, tool status, and errors are written to stderr. - Approval-required tool calls fail unless an `allow` rule in `.yottacode/permissions.json` matches them, or `--yolo` is set (DANGEROUS). ## Tools And Safety Layers The agent exposes twenty-eight structured tools in [`tools.md`](/docs/tools/). Two independent safety systems gate every model-emitted call: - **Permissions** (`internal/permissions/`) — project-local `.yottacode/permissions.json` (committable) and `.yottacode/permissions.local.json` (gitignored) carry pattern-based allow / ask / deny rules per tool. Decision precedence is deny > allow > ask > default. - **Write-path validation** (`internal/agent/writepath.go`) — filesystem mutators (write/edit/mkdir/copy/move/delete) are confined to cwd, refuse symlinks, and refuse a hardcoded deny list of yottacode and git internal paths. `apply_diff` parses its diff header so each touched file goes through the same validator — the patch surface can't bypass the deny list. - **Read-path validation** (`internal/agent/writepath.go`, `ValidateReadPath` + `DefaultDenyReadPaths`) — the auto-execute read tools (`read_file`, `read_many_files`, `grep`) refuse a narrow list of credential-bearing locations (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.netrc`, `~/.yottacode/.env`, `/.env*`, …) so prompt injection can't silently exfiltrate keys. The user can still read these via `run_bash`, which always prompts. There is no in-process sandbox, and there will not be one. yottacode deliberately stays out of the OS-isolation business — no bwrap, firejail, landlock, seccomp, or pluggable `Sandbox` backends to maintain — so the core stays small and portable. For real isolation across every tool (`run_bash`, `write_file`, `git`, etc.), run yottacode itself inside a container or devcontainer. ## Agent modes Two **modes** (mutually exclusive, control workflow shape) and one **startup-only overlay** (orthogonal, applies on top of any mode) sit on top of the base approval flow: - **Plan mode** — read-only research state. Entered via `/plan`, `Shift+Tab`, or `--permission-mode plan`. The model can read, search, ask, and write only to a single plan file under `~/.yottacode/plans/.md`. `exit_plan_mode` surfaces the plan in an approval card with four hotkeys: `[A]` auto-approval (implement with auto mode enabled), `[M]` manual approval (implement, per-tool prompts continue), `[L]` save for later, `[K]` keep refining. State lives on `agent.PlanModeState`. - **Auto mode** — implementation state. Entered via `Shift+Tab` or `--permission-mode auto` (no slash command, mirroring Claude Code). Mutating tools auto-allow except a safety floor (`run_bash`, `git_commit`, `git_checkpoint`, `rollback`). Effective iteration cap is 4× the configured `MaxIterations`. State lives on `agent.AutoModeState`. - **Permissions-bypass overlay** — drops permission prompts on *all* tools (no safety floor) and removes the iteration cap entirely. Entered only via `--yolo` at startup (mirroring Claude Code) — no slash command, no keybinding, no in-TUI toggle. Sits on top of whichever mode is active; the banner shows the mode label with a `⚠ bypass` suffix (the standalone banner reads `⚠ permissions bypass`). State lives on `agent.YoloModeState` — the Go identifier predates the user-facing rename and is kept for internal stability. `Shift+Tab` cycles through `normal → auto → plan → normal`. Permissions bypass is intentionally **not** in the cycle — the only entry point is the startup flag, so high-autonomy state is a conscious one-time decision, not a key chord away. ## Interrupts Mid-turn user input is a first-class flow, not a blocked interaction: pressing **Enter** while the agent is thinking (streaming, calling a tool, or running a foreground subagent) captures the new message, cancels the in-flight iteration via the turn's context, and queues the message for auto-submission as soon as `agent.Turn` unwinds. The TUI's `pendingInputAfterTurn` field carries the queue across the cancel; `turnEndedMsg` consumes it and calls `startTurn` so the agent sees the user's feedback without the operator needing to retype anything. Behaves identically across normal, plan, and auto modes — the loop is mode-agnostic about interrupts. **Esc** and **Ctrl+C** are the explicit "stop without sending" surface: they cancel the turn and drop any queued message, but leave the textarea contents alone. Esc mirrors Claude Code's cancel feel; Ctrl+C keeps the terminal-native semantics. **Synthetic `tool_result` policy.** Mid-turn cancellation must preserve provider-valid history: every `tool_use` block in the just- cancelled assistant message needs a matching `tool_result`, or the next request fails. The agent loop handles this in three places: 1. `streamIteration` accumulates streamed content tokens. On cancel, it returns a partial assistant message with the accumulated content and no tool calls (any tool-use the adapter was mid-building is deliberately dropped — content-only messages are valid for every provider). 2. `executeToolCall` propagates `ctx.Err()` from a ctx-respecting tool (instead of swallowing it as an `error: context canceled` string), so the caller can route into the cancel branch. 3. `executeToolCalls` (serial and parallel) appends `"interrupted by user"` `tool_result` entries for every orphaned call — both the in-flight tool that was cancelled and any queued calls that never started. Parallel workers that completed cleanly before the cancel keep their real result. Once history is repaired, the loop emits a `TurnInterrupted` event (distinct from `ErrorEvent` so consumers render it as a calm `↩ interrupted` line, not a red error) and returns. The TUI's auto-submit then fires the queued message into a fresh turn that sees the partial assistant content + synthetic tool results in history. **Background subagents are exempt.** Their context is detached from the parent turn (`context.Background()`, not the parent's ctx), so a parent-turn cancel does not propagate. They continue running to completion and surface via `SubagentBackgroundDone` whenever they finish, regardless of which parent turn is active. ## Subagents The `Agent` tool (`internal/agent/agent_tool.go`) is the parent's delegation surface. When the model calls it, yottacode constructs a fresh `LoopConfig` that reuses the parent's adapter, permissions, and cwd, but pairs them with a filtered tool registry, fresh inactive plan/auto mode states, a standard iteration cap, and an isolated message history seeded from the chosen agent definition's system prompt and the user-supplied subagent prompt. `agent.Turn` runs recursively against that config; the child's events flow into a runner-local channel that the parent **does not** consume directly — the runner translates only high-level activity (subagent start / progress / done) into events on the parent's `events` channel, so the parent's context window never sees the child's reasoning or tool outputs. Only the child's final assistant content is returned as the parent's tool-result string. Foreground runs block the parent's tool call; background runs (`run_in_background: true`, TUI-only) detach to a goroutine bound to a session-scoped context, and surface their completion via a long-lived inbox channel the TUI's Model drains in parallel with the per-turn event stream. The child registry **always** excludes `Agent` itself (hard recursion guard, even against adversarial config) and `exit_plan_mode`. See [subagents.md](/docs/subagents/) for the user-facing surface — agent definition format, built-in agents, `/subagents` command, and limitations. The loop reads all three flags at turn start (effective iteration cap) and on every tool dispatch. Approval-chain priority (the internal "yolo" name still appears in the precedence label since `YoloModeState` is the Go identifier): `Deny > yolo > plan-gate > plan-file-allow > auto-allow > Allow > Ask > tool default`. See [security-and-allow-lists.md](/docs/security-and-allow-lists/) for the full precedence table. ## Extension Points Most feature work lands in one of these seams: - Add a new tool by implementing `agent.Tool` and registering it in the TUI and oneshot setup paths. - Add a new slash command in [`internal/tui/commands.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/commands.go). - Add or expand an adapter while keeping `agent.Turn` unchanged. - Add a new built-in subagent type by dropping a markdown file under [`internal/subagents/builtins/`](https://github.com/yottadynamics/yottacode/tree/main/internal/subagents/builtins/); `//go:embed` picks it up at build time without Go changes. Provider diagnostics follow the same seam discipline: - static resolution and validation belong in `internal/adapter` - active probes belong in `internal/adapter` - `/provider`, `/doctor`, `yottacode doctor`, and oneshot preflight are thin consumers of that adapter-level API The general rule is simple: keep UI concerns in `tui` or `oneshot`, provider details in `adapter`, and agent behavior in `agent`. ================================================================================ # Development Source: https://yottacode.ai/docs/development/ Building, testing, and extending yottacode. ## Build ```bash go build -o yottacode ./cmd/yottacode ``` Requirements: Go 1.25+. The module is pure Go, so cross-compilation is straightforward: ```bash GOOS=darwin GOARCH=arm64 go build -o yottacode-darwin-arm64 ./cmd/yottacode GOOS=linux GOARCH=amd64 go build -o yottacode-linux-amd64 ./cmd/yottacode GOOS=windows GOARCH=amd64 go build -o yottacode-windows.exe ./cmd/yottacode ``` ## Test ```bash go test ./... # unit tests, fast, no live deps go test -tags=integration ./... # adds live-provider tests go test -race ./... go test -cover ./... ``` Standing rules: - Unit tests must not require network, GPU, or external services. - Integration tests belong behind `//go:build integration`. - Bug fixes should ship with a regression test. - A change is not done until `go test ./...` is green. ## Adding A Built-In Tool Implement [`agent.Tool`](https://github.com/yottadynamics/yottacode/blob/main/internal/agent/tools.go) and register the tool in both entry points: - [`internal/tui/run.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/run.go) - [`internal/oneshot/oneshot.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/oneshot/oneshot.go) Conventions: - Resolve relative paths against the session working directory. - Require approval for mutating behavior. - Cap output so tool results do not explode transcript size. - Return errors as normal errors, not panics. - Add tests for schema, happy path, edge cases, and approval behavior. ## Adding A Slash Command Add a `slashCommand` entry in [`internal/tui/commands.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/commands.go) and implement the handler with the usual `(Model, tea.Cmd)` signature. If the command takes arguments, set the `Args` field so the palette can insert the command prefix instead of executing it immediately. ## Refreshing The Embedded Model Catalog The cloud provider model lists for Anthropic, OpenAI, and Gemini live in [`internal/catalog/catalog.gen.json`](https://github.com/yottadynamics/yottacode/blob/main/internal/catalog/catalog.gen.json), embedded into the binary at build time. When a provider ships new models (or deprecates old ones), regenerate the file: ```bash ANTHROPIC_API_KEY=… OPENAI_API_KEY=… GEMINI_API_KEY=… \ go run ./cmd/yotta-models refresh ``` Optional flags: ```bash go run ./cmd/yotta-models refresh --output internal/catalog/catalog.gen.json ``` A missing key skips that provider with a warning; existing entries for that provider in the output file are preserved untouched, so you can refresh one provider at a time. The script: - Calls each provider's list-models endpoint (Anthropic `/v1/models`, OpenAI `/v1/models`, Gemini `/v1beta/models`). - Filters OpenAI to chat-completions models via prefix regex ([`fetch_openai.go`](https://github.com/yottadynamics/yottacode/blob/main/cmd/yotta-models/fetch_openai.go)) — drop embeddings/tts/audio/realtime/image variants. Widening for new families (e.g. `gpt-6`, `o5`) is a one-line PR. - Filters Gemini to entries whose `supportedGenerationMethods` contains `"generateContent"` — drops embedding-only and countTokens-only models. - Maps each row onto the common [`catalog.Model`](https://github.com/yottadynamics/yottacode/blob/main/internal/catalog/model.go) schema with tristate (`*bool`) capability flags so renderers can distinguish "supported" / "not supported" / "unknown." - Sorts the result by (provider, ID) so diffs across runs are minimal. Commit the refreshed `catalog.gen.json` like any other source file. There is no runtime equivalent — users can't refresh locally because they don't necessarily have keys for all three providers, and the embedded catalog keeps offline-first ergonomics. Anything else (Ollama, openai-compatible endpoints) is fetched live at picker-open time via [`catalog.Live`](https://github.com/yottadynamics/yottacode/blob/main/internal/catalog/live.go). ## Adding Or Expanding A Model Adapter Today the adapter layer supports OpenAI-compatible chat-completions endpoints plus automatic OpenAI Responses routing for supported reasoning models. The agent depends only on the streaming interface, so provider-specific expansion belongs in `internal/adapter` rather than `internal/agent`. ### Provider Profiles, Diagnostics, And Probes Provider-specific setup and validation now have two layers: - **Static diagnostics** in [`internal/adapter/provider.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/adapter/provider.go) and [`internal/adapter/diagnostics.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/adapter/diagnostics.go) - **Active probes** in [`internal/adapter/diagnostics.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/adapter/diagnostics.go) The split is intentional: - `ProviderProfile` is the resolved static view of a config: - detected provider - routing choice (`chat-completions` vs Responses) - supported native capabilities - enabled built-in tools - static issues and warnings - `Probe(ctx, cfg)` performs a lightweight live check against the configured endpoint, currently via `/models`, and reports: - endpoint reachability - auth status - selected-model visibility - the same resolved profile and static diagnostics Keep these rules when extending provider support: - Put provider-specific validation in `internal/adapter`, not in `tui`, `oneshot`, or `cmd`. - Treat `/provider`, `/doctor`, and `yottacode doctor` as **consumers** of the diagnostics API, not as the implementation. - Prefer cheap, broadly-supported probes first. `/models` is useful because it distinguishes network, auth, and model-visibility failures without spending prompt tokens. - Keep probes side-effect free. They should validate configuration, not mutate provider state. - Return structured data first; format human-readable output at the edge. When adding a new provider capability check: 1. Extend `adapter.Config` and `ProviderProfile` if the capability is part of the resolved static surface. 2. Add static validation in `providerDiagnostics(...)`. 3. Add live validation in `Probe(...)` only if it can be done cheaply and reliably across that provider's endpoint shape. 4. Cover both layers with tests: - `internal/adapter/*_test.go` for static and probe behavior - consumer tests only for rendering / command wiring ### CLI And TUI Consumers The current diagnostics consumers are: - TUI startup/background probing in [`internal/tui/model.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/model.go) - static `/provider` output in [`internal/tui/commands.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/commands.go) - active `/doctor` output in [`internal/tui/commands.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/tui/commands.go) - shell/CI command `yottacode doctor` in [`cmd/yottacode/main.go`](https://github.com/yottadynamics/yottacode/blob/main/cmd/yottacode/main.go) - oneshot preflight fail-fast in [`internal/oneshot/oneshot.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/oneshot/oneshot.go) If you add a new diagnostics field, update the adapter tests first, then only the consumers that need to display it. ## Project Layout Reminder ```text cmd/yottacode/ cobra root internal/cli/ option resolution internal/adapter/ provider streaming layer internal/agent/ turn loop, tools, approvals internal/session/ saved conversations internal/memory/ prompt memory composer and agent-managed memory store internal/recall/ FTS5 session search internal/tui/ interactive terminal UI internal/oneshot/ one-shot runner internal/version/ version string ``` ## Release Versioning The release number lives in [`internal/version/version.go`](https://github.com/yottadynamics/yottacode/blob/main/internal/version/version.go): ```go const Current = "0.1.0" ``` Use semantic versioning: `MAJOR.MINOR.PATCH`. - Bump `PATCH` for backward-compatible bug fixes. - Bump `MINOR` for backward-compatible features. - Bump `MAJOR` for breaking changes. Examples: - `0.1.0` -> `0.1.1`: bug-fix release - `0.1.0` -> `0.2.0`: new feature release without a stable-API commitment yet - `0.1.0` -> `1.0.0`: first stable public release - `1.2.3` -> `2.0.0`: breaking change Before `1.0.0`, treat `0.x` as pre-stable. That means breaking changes may still happen before the first stable release, even if they land in a minor bump. ================================================================================ # Experimental features Source: https://yottacode.ai/docs/experimental/ Some yottacode capabilities are merged-and-tested but not yet ready for the default experience. They live behind named feature flags so early adopters can opt in while general users get a stable surface. A feature lives in `experimental` when: - The code is reliable enough to ship, but - The UX, model behavior, or API shape isn't settled, and - We want production users to encounter it only on deliberate opt-in. When a feature stabilizes, the gate is removed and the feature becomes a default-on capability. The flag name stays a no-op for one release so existing configs don't break. ## Current catalog | Name | Status | What it enables | | --- | --- | --- | | `background_subagents` | experimental | `run_in_background:true` on the Agent tool — fire-and-forget subagent dispatch with `get_subagent_result` for fetching. Foreground subagents are always available; this gate only controls the bg variant. | (Adding a feature here is a one-constant change in `internal/experimental/features.go`. See that file's package doc for the contract.) ## How to enable Three sources, merged at startup (CLI > env > config; later sources add to earlier ones but never disable): ### 1. CLI flag (repeatable) ```bash yottacode --experimental background_subagents # stack multiple: yottacode --experimental background_subagents --experimental other_feature # or comma-separated in one invocation: yottacode --experimental background_subagents,other_feature ``` The flag is inherited by all subcommands (`yottacode run`, `yottacode sessions`, etc.) so the same opt-in works regardless of entry point. ### 2. Environment variable ```bash export YOTTACODE_EXPERIMENTAL=background_subagents # or comma-separated: export YOTTACODE_EXPERIMENTAL=background_subagents,other_feature ``` Useful for shell sessions / CI where setting the env once is cleaner than threading the flag everywhere. ### 3. `~/.yottacode/config.toml` ```toml [experimental] background_subagents = true # future: # other_feature = true ``` Persistent across sessions; survives reinstalls. Good for the "this is now my default" case. ## What happens when a flag is on Each feature's code path checks `experimental.Set.IsEnabled(...)` at the relevant decision point and changes behavior accordingly. See the catalog above for what each flag actually controls. When off, the feature is fully inert — no performance cost, no model exposure. ## What happens with an unknown flag name Typos or graduated/removed feature names land in the Set's "unknown" bucket and produce a one-line startup warning on stderr: ``` warning: --experimental "foo_made_up" is not a recognized feature (typo? graduated? see docs/experimental.md) ``` The session continues normally. The intent is *fail-soft*: a flag that used to exist shouldn't break someone's config when the feature graduates and the gate goes away. ## When a feature graduates When the team decides a feature is ready for default-on behavior: 1. The gate at the use site is removed. 2. The constant in `internal/experimental/features.go` is dropped. 3. `Recognized()` now returns false → users with the old flag get the startup warning but the feature still works (it's default-on now). 4. After one full release cycle, the warning is removed (the name becomes truly unknown, no different from a typo). This means: setting `--experimental foo` for a feature that has since graduated is harmless — the feature still works, the user just sees a warning that they can clean up at their leisure. ## Reading from code ```go import "github.com/yottadynamics/yottacode/internal/experimental" // At startup, build the Set from CLI/env/config sources: exp := experimental.NewSet() for _, name := range opts.Experimental { exp.Enable(name) } // ... // At the gate's call site: if exp.IsEnabled(experimental.BackgroundSubagents) { // turn the feature on } ``` `Set` is nil-safe on read (`(*Set)(nil).IsEnabled(...)` returns `false`) so subsystems that haven't been wired up yet (or tests that don't build a Set) can call without guarding. ================================================================================ # Troubleshooting Source: https://yottacode.ai/docs/troubleshooting/ ## Missing model or base URL If yottacode exits with a configuration error, set both values: ```bash export YOTTACODE_MODEL= export YOTTACODE_BASE_URL=https://api.openai.com/v1 ``` For remote providers, also set an API key: ```bash export YOTTACODE_API_KEY=sk-... ``` Or run: ```bash yottacode setup ``` ## Connection refused or red status dot Check that the provider is running and the base URL is correct: ```bash curl "$YOTTACODE_BASE_URL/models" yottacode doctor ``` For Ollama: ```bash ollama serve ollama pull qwen3.5:latest ``` Use `http://localhost:11434/v1` as the base URL. ## Model not visible Run: ```bash yottacode doctor yottacode model fetch ``` Common causes: - wrong provider account or API key - model id typo - provider does not expose the model on `/models` - using a cloud model name against a local/custom endpoint ## Tool call loop hits max iterations Default max iterations is `25`. Increase it for larger tasks: ```bash yottacode --max-iterations 50 ``` Keep the limit finite; it protects you from runaway tool loops. ## The trust prompt fires on every launch The first-launch trust prompt records cwd in `~/.yottacode/trusted-roots.json` on Yes. If you see it again on a directory you already accepted, the cwd is most likely a fresh path (different absolute path, different worktree, different bind-mount). List and add directly: ```bash yottacode trust list yottacode trust add /path/to/repo ``` Trust covers every subfolder of an added root. To bypass the prompt in CI runs, set `YOTTACODE_TRUST_ALL=1` (session-only — does not persist). ## Approval prompts are too frequent Use project-local allow rules for operations you trust: ```json { "permissions": { "allow": ["Bash(go test *)", "Edit(docs/**)"] } } ``` Save personal rules in `.yottacode/permissions.local.json` and gitignore that file. ## A saved memory is wrong or stale Inspect and prune agent-managed memories: ```bash yottacode memory list --scope user yottacode memory list --scope project yottacode memory forget --scope ``` Or browse them in the TUI: `/memory` → **Browse user memories** / **Browse project memories**, then `d` on the offending entry. Restart yottacode or reload memory from the TUI so the active session picks up the change. ## The agent keeps making the same mistake Put durable corrections in curated memory: - user-wide preference: `~/.yottacode/USER.md` - repo-specific convention: `./.yottacode/YOTTACODE.md` Open the memory picker: ```text /memory ``` ## Large session or context warnings Use: ```text /summarize ``` or start fresh with: ```text /clear ``` Summarization snapshots are saved before history is compressed. ## Pasted text is collapsed in the input Large pastes are shown as a short marker to keep the input line usable. The full pasted text is still sent when you submit. ## Terminal rendering looks odd after resize The TUI uses inline rendering so your terminal owns scrollback. If rendering becomes messy after a resize, clear the terminal or restart yottacode; the saved session can be resumed. ## ChatGPT OAuth: `Missing required parameter: 'input[n].output'` This is not usually a login failure. It means the `openai-auth` backend rejected the conversation history because one prior tool-result item was missing the required `output` field. Common causes are a saved/resumed session with an interrupted tool call, or an adapter history-conversion bug exposed by the stricter Responses/Codex payload validator. Try, in order: ```text /redo ``` If that does not help, start a fresh session: ```text /clear ``` If you need the context from a large session, manually summarize the important state into the new prompt instead of replaying the full tool history. Re-running `yottacode openai-auth login` will not fix malformed session history. ## `--worktree` exits with "workspace trust not accepted" `yottacode --worktree ` requires the repo root to be trusted. Run yottacode once in the repo without `--worktree`, accept the trust prompt, then retry: ```bash cd /path/to/repo yottacode # accept the trust prompt # (Ctrl-C to exit when the TUI opens, or finish your session) yottacode --worktree feature-x ``` Trust is persistent — you only do this once per repo. See [security-and-allow-lists.md](/docs/security-and-allow-lists/). ## Worktree from `yottacode run --worktree` wasn't cleaned up This is intentional. Oneshot / non-interactive runs cannot show a keep-or-remove prompt, so they leave the worktree in place. Remove it manually: ```bash yottacode worktree remove # if clean yottacode worktree remove --force # if dirty (discards uncommitted work) ``` `yottacode worktree list` shows everything under `~/.yottacode/worktrees//`. ## Reporting bugs Include: - `yottacode version` - provider and model - `yottacode doctor` output, redacted - a minimal reproducer - exported session Markdown if relevant: `yottacode sessions export ` ================================================================================ # For LLMs and coding agents Source: https://yottacode.ai/docs/llms/ YottaCode's documentation publishes machine-readable entry points so LLMs and coding agents can load it directly: - [`/llms.txt`](/llms.txt) — a curated index of every doc page with short descriptions. ~8 KB, safe to load into an LLM context window. - [`/llms-full.txt`](/llms-full.txt) — every doc page concatenated into a single markdown file for one-shot ingestion. ~260 KB. Both files also resolve under the docs section, at [`/docs/llms.txt`](/docs/llms.txt) and [`/docs/llms-full.txt`](/docs/llms-full.txt). They're generated fresh on every deploy, so they always match the current documentation. ================================================================================ # FAQ Source: https://yottacode.ai/docs/faq/ {{< details title="What is yottacode?" closed="true" >}} yottacode is a model-agnostic terminal AI agent for real codebases. It provides an interactive TUI, a one-shot CLI, structured tools, session persistence, memory, and cross-session recall. {{< /details >}} {{< details title="Does yottacode require OpenAI?" closed="true" >}} No. It works with OpenAI, Anthropic, Gemini, Ollama, xAI, ChatGPT OAuth through `openai-auth`, GitHub Copilot through `copilot-auth`, and OpenAI-compatible endpoints. Provider behavior depends on the endpoint and model you configure. {{< /details >}} {{< details title="Can I use my ChatGPT account instead of an OpenAI API key?" closed="true" >}} Yes. Run `yottacode openai-auth login` and configure provider `openai-auth`. Tokens are stored under `~/.yottacode/auth/`, and that directory is blocked from model reads and writes. {{< /details >}} {{< details title="Can I use my GitHub Copilot subscription?" closed="true" >}} Yes. Run `yottacode copilot-auth login` and configure provider `copilot`. Available models depend on your Copilot tier (Free, Pro, Pro+). The model picker marks plan-gated models with "upgrade plan". {{< /details >}} {{< details title="Why is there no default model?" closed="true" >}} A silent default would either fail confusingly against localhost or accidentally use a paid provider. yottacode fails fast until you explicitly configure a model and base URL. {{< /details >}} {{< details title="What is the difference between yottacode and yottacode run?" closed="true" >}} `yottacode` opens the interactive TUI. `yottacode run ""` runs one prompt and exits, which is better for scripts and CI. {{< /details >}} {{< details title="Where is state stored?" closed="true" >}} Global state lives in `~/.yottacode/`, and project-local state lives in `/.yottacode/`. {{< /details >}} {{< details title="Are sessions saved automatically?" closed="true" >}} Yes. Every completed turn is saved to `~/.yottacode/sessions/`. {{< /details >}} {{< details title="Can I search old sessions?" closed="true" >}} Yes. Use `/recall ` in the TUI. yottacode indexes saved sessions with local SQLite FTS5. {{< /details >}} {{< details title="Does yottacode sandbox commands?" closed="true" >}} No. There is no in-process sandbox. Use a container or devcontainer for real isolation. {{< /details >}} {{< details title="Can I auto-approve trusted commands?" closed="true" >}} Yes. Use `.yottacode/permissions.json` for shared rules and `.yottacode/permissions.local.json` for personal rules. {{< /details >}} {{< details title="Can I bypass every prompt?" closed="true" >}} Launch yottacode with `--yolo` (the permissions-bypass overlay), but only in trusted contexts. Explicit deny rules still apply. The overlay is one-way per process — restart without the flag to recover. {{< /details >}} {{< details title="What should go in USER.md?" closed="true" >}} Global preferences you want in every project: communication style, coding style, test expectations, and recurring workflow preferences. {{< /details >}} {{< details title="What should go in YOTTACODE.md?" closed="true" >}} Repo-specific context: architecture, commands, conventions, gotchas, and project status. It is usually worth committing. {{< /details >}} {{< details title="How does the agent remember things across sessions?" closed="true" >}} The agent has two tools — `memory_save` and `memory_forget` — that let it write and delete typed markdown memories during a conversation. Memories live under `~/.yottacode/memory/` (user scope) and `~/.yottacode/projects//memory/` (per-repo scope). See [Memory](/docs/memory/) for details. {{< /details >}} {{< details title="How do I delete a memory?" closed="true" >}} Use the `/memory` picker (browse the relevant scope, press `d` on the entry) or run `yottacode memory forget --scope ` from the shell. {{< /details >}} {{< details title="Can local models access the web?" closed="true" >}} They can fetch specific URLs through yottacode's local `fetch_url` tool. Provider-hosted web search is available only for providers that support it. {{< /details >}} {{< details title="How do I switch models mid-session?" closed="true" >}} Use `/model ` in the TUI. Use `yottacode model use ` to change the configured default. {{< /details >}} {{< details title="How do I check provider setup?" closed="true" >}} Use `/provider` for resolved static configuration and `/doctor` or `yottacode doctor` for an active endpoint probe. {{< /details >}}