← Otto User GuideAsk and Agents

Ask puts a coding agent behind a text field on the desktop. You type a request, the agent works in a folder, and Otto shows what it is doing. It stops to ask you when it needs permission or has a question.

Ask answering a question about moving windows between workspaces, then offering a list of workspaces to choose from

Otto does not ship an agent of its own. It runs the ones you already have installed and logged in, and answers as Otto whichever one is underneath.

Before it works

  1. Install an agent and log it in: Claude Code, OpenCode, Codex, Hermes or pi. Otto runs the command you name; it does not manage accounts or keys.

  2. Start the service, which is off until you ask for it:

    systemctl --user enable --now otto-agents
    
  3. Give Otto its knowledge. This links the desktop’s skills where each agent looks for them, and writes the Otto instructions in each one’s dialect:

    otto-agents plugins install
    otto-agents plugins status    # what it found, and where each copy stands
    
  4. Ask something: run otto-ask, or bind it to a key in your config. In the launcher, the field says who the request is going to: “Ask @Otto…”, your default agent. Down lists the others and Return takes the highlighted one. Ctrl+L switches between your sessions and a fresh request, and Ctrl+O continues the session in a terminal. otto-launcher --agents opens straight on the list of sessions you have going.

    The Ask field listing four configured agents, the highlight moving down them, and the field changing to the one picked

An answer in the Ask panel: two paragraphs about the dock, a link to the documentation, and the skill it loaded to answer

Agents are configured in ~/.config/otto/agents.toml, one [[agents]] block each. The Agents pane in Settings edits the everyday ones (default agent, name, harness, permissions, model, folder, colour) and restarts the service for you. See Configuration for where that sits. Two settings there are worth knowing before you start: permissions, which decides how an agent’s requests are answered and is deny unless you set it, and folder, which says where that agent’s sessions start. To put an agent of your own in that list, see Add an agent of your own.

What leaves your machine

Otto does not send anything anywhere itself, and collects nothing: there is no telemetry in the agent service. But it is the thing that hands your request to an agent, and the agent talks to whoever it is logged into. Worth knowing:

Otto tells the agent it is running on a Wayland desktop and names the skills available to it. It does not send your hostname, your hardware, your window titles or screenshots.

What each agent can do

Not every agent can do everything, and the differences are worth knowing before you pick one. This is what each was checked to do here:

AgentOtto’s knowledgeAnswers as OttoAsks you questionsModes on Shift+TabContinue in a terminalModel from agents.toml
Claude Codeas a plugin, loaded every sessionyesyes, as a dialog you answerfive: default, accept edits, plan, auto, bypassyesyes: haiku, sonnet, opus
Codexfrom ~/.agents/skillsyesyes, as a dialog you answerthree: ask for approval, approve for me, full accessyesyes
Hermesfrom ~/.agents/skillsyesin prose, in the conversationthree: default, accept edits, don’t askyesno; set it in Hermes’ own profile
OpenCodefrom ~/.agents/skillsyesin prose, in the conversationnone offeredyesyes, as provider/model
pifrom ~/.agents/skillsyesin prose, in the conversationsix, but they set reasoning effort, not permissionsyesyes, as provider/model

A few notes on the awkward cells:

Pictures in an answer

An agent can send a picture rather than describe one: a screenshot of the page it just built, a diagram it drew. Ask draws it in the conversation, right where the agent put it. Pictures are scaled to the width of the card and no taller than about a third of it, so a shot of a whole display does not push the rest of the conversation away.

Pictures come from what the agent does, not from what it writes: ask it about a screenshot and it reads the file, and the picture it read appears in the answer. Nothing needs turning on, and no agent has to be told. PNG, JPEG, GIF, WebP, BMP, AVIF and HEIF are drawn; an SVG is not, and shows as its name instead.

Choosing one

Whichever you pick, default_agent in agents.toml says which one a request goes to when you do not choose.

Add an agent of your own

The four agents above are the ones Otto is set up with, not the only ones it can have. An agent is two things: a file saying who it is, and a block in agents.toml saying how to run it. Here is a marketing assistant called Nora, who works in one repository and nothing else.

1. Write who it is. Otto reads agents from plugins: a directory with a name, holding skills, agent files, or both. Yours goes in ~/.local/share/otto/plugins/:

~/.local/share/otto/plugins/marketing/
├── .claude-plugin/plugin.json    {"name": "marketing", "version": "1.0.0"}
└── agents/nora.md

nora.md is Markdown with a little YAML at the top:

---
name: nora
description: Writes and plans the marketing. Knows the product and its audience.
model: opus
tools: Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch
---

You are Nora. You look after how this project is described to the people who
might use it …

Everything under the second --- is the agent’s instructions: who it is, what it does, how it should answer. Write it as if you were telling a new colleague what the job is.

otto-agents plugins status says whether Otto found it.

2. Give it a seat. Add a block to ~/.config/otto/agents.toml. The simplest one copies an agent already there and changes four lines:

[[agents]]
id = "marketing"
name = "Nora"                # what the launcher shows
description = "Marketing, on Claude Code"
command = "npx"
args = ["-y", "@agentclientprotocol/claude-agent-acp@latest"]
model = "opus"
permissions = "ask"
skills = "claude"            # `agent` needs this
agent = "nora"               # the `name` from nora.md
folder = "~/dev/my-project"  # all it can reach
colour = "magenta"           # the material its card wears
enter = ["claude", "--resume", "{session}"]
enter_new = ["claude", "--session-id", "{session}"]

folder is worth a moment: it is the reach you are handing over, so name the narrowest folder the agent needs rather than your home.

3. Restart the service. Run systemctl --user restart otto-agents. The configuration is read when it starts, so nothing changes until you do.

4. Ask it something. Open Ask, press Down, and Nora is in the list.

On another harness

agent is Claude Code’s route: it reads the plugin file where it lies. Every other harness wants the same instructions in its own dialect and its own place, so otto-agents plugins install writes them, and otto-agents plugins status lists what it wrote and what is still missing. Which file is used is then part of the [[agents]] block: OpenCode takes a default_agent in OPENCODE_CONFIG_CONTENT, Codex a CODEX_CONFIG pointing at the rendering, pi a wrapper in PI_ACP_PI_COMMAND, Hermes a profile named after the agent. Copy the block of the agent you are borrowing from and change the one line that names the file.

When something is wrong