Your coding agent's little brother

ototo.dev · 2026-10-05 · by Long Nguyen ·generic
Otōto is an MCP server for Claude Code and OpenCode: a small model on a server you choose explores the code and hands your agent short answers with checked citations.

Your coding agent's little brother

Your coding agent's

little brother

Otōto is an MCP server for Claude Code and OpenCode. Your agent hands it a question; a small model on a server you choose explores the code and brings back a short answer, with the lines that back it checked and quoted.

curl -fsSL https://ototo.sh | shCopy

macOS on Apple silicon and Linux · See the script · Open source · How it works

RustTypeScriptPythonJavaTerraformMaven plugin

Claude Code asksotōto

Where does a failed request move on to the next model server?

Otōto answers

Llm::request tries each endpoint in turn; one that fails is marked down for 60 s and the next is asked. When every failure was one that passes (a dropped connection, a 502), it goes round once more after 2 s.

  • src/ototo/agent/llm.rs:144`for round in 0..2 {`
  • src/ototo/agent/llm.rs:163`*b.down_until.lock().unwrap() = Some(Instant::now() + DOWN_FOR);`
  • src/ototo/agent/llm.rs:174`tokio::time::sleep(RETRY_AFTER).await;`
  • 4 turns · 6 tool calls · 20.0k tokens on the small model · 18 s

    Claude Code asksmaplibre-gl-js

    How does flyTo work out how long its animation takes?

    Otōto answers

    Camera.flyTo computes its animation duration from the "optimal path" model of Van Wijk & Nuij (2003), unless the caller passes an explicit duration: … options.duration = 1000 \* S / V. So the time is proportional to the path length in screenfulls divided by the speed in screenfulls per second. If options.maxDuration is set and the computed duration exceeds it, duration is set to 0, which makes \_ease jump instantly to the end.

  • src/ui/camera.ts:1121`let S = (zoomOutFactor(true) - r0) / rho;`
  • src/ui/camera.ts:1139`options.duration = 1000 S / V;`
  • src/ui/camera.ts:1143`options.duration = 0;`
  • 4 turns · 4 tool calls · 15.5k tokens on the small model · 47 s

    Claude Code askshttpx

    How does the client decide whether to follow a redirect, and what stops it following redirects forever?

    Otōto answers

    After each response, the client checks response.has\_redirect\_location. That property is True only for 301/302/303/307/308 status codes AND a Location header present. … What stops infinite redirects: at the top of each loop iteration, if len(history) > self.max\_redirects: raise TooManyRedirects(...). max\_redirects is a client constructor parameter defaulting to DEFAULT\_MAX\_REDIRECTS = 20.

  • httpx/\_client.py:971`if len(history) > self.max_redirects:`
  • httpx/\_client.py:985`if not response.has_redirect_location:`
  • httpx/\_config.py:248`DEFAULT_MAX_REDIRECTS = 20`
  • 5 turns · 7 tool calls · 16.7k tokens on the small model · 40 s

    Claude Code asksspring-petclinic

    Where is the database chosen between H2, MySQL and Postgres, and how does a user switch it?

    Otōto answers

    The database is chosen via Spring Boot profiles, not a single config flag. The default profile uses H2: application.properties sets database=h2 … The mysql and postgres profiles each have their own property file that override database to mysql/postgres and set the JDBC datasource URL. To switch, a user activates the corresponding Spring profile: run with `--spring.profiles.active=mysql` or `--spring.profiles.active=postgres`.

  • src/main/resources/application.properties:2`database=h2`
  • src/main/resources/application-mysql.properties:3`spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic}`
  • src/main/resources/application-postgres.properties:2`database=postgres`
  • 3 turns · 4 tool calls · 11.2k tokens on the small model · 39 s

    Claude Code asksterraform-aws-vpc

    When does this module create one NAT gateway per availability zone, and when only one?

    Otōto answers

    The module decides the NAT gateway count in one local (main.tf:1203). One NAT gateway: when single\_nat\_gateway = true (takes precedence over one\_nat\_gateway\_per\_az). One NAT gateway per availability zone: when single\_nat\_gateway = false and one\_nat\_gateway\_per\_az = true. … All of this only applies when create\_vpc && enable\_nat\_gateway is true.

  • main.tf:1203`nat_gateway_count = var.single_nat_gateway ? 1 : var.one_nat_gateway_per_az ? length(var.azs) : local.max_subnet_length`
  • main.tf:1229`count = local.create_vpc && var.enable_nat_gateway ? local.nat_gateway_count : 0`
  • main.tf:1239`var.single_nat_gateway ? 0 : count.index,`
  • 4 turns · 5 tool calls · 24.0k tokens on the small model · 45 s

    Claude Code readsspring-petclinic

    `read pom.xml#webjars-locator-lite`

    The maven plugin answers

    The dependency as Maven resolves it: its version worked out through the property that sets it, each part with the line it comes from.

    <dependency> # pom.xml:96-101 <groupId>org.webjars</groupId> <artifactId>webjars-locator-lite</artifactId> <version>${webjars-locator.version}</version> <scope>runtime</scope> </dependency> version: 1.1.3 # pom.xml:99: ${webjars-locator.version} webjars-locator.version = 1.1.3 # pom.xml:26 scope: runtime # pom.xml:100

    the maven plugin · no model · 58 ms

    Fig. 1Real asks on public repositories and on Otōto itself, and a plugin; replies shortened. Claude Code reads a paragraph and a few quoted lines, not the files they came from.

    01How it works

    Your agent asks. Its little brother does the reading.

    Every file an agent opens, every search result and every dead end stays in its context for the rest of the session, and each later turn carries it again. Otōto does that exploring in a context of its own, thrown away after each call.

    1

    Claude Code asks

    A whole question to `ask`, or `locate` and `callers` for where something is and who uses it.

    →

    2

    Otōto explores

    A small model on your server reads, outlines and searches the repository with read-only tools, as many turns as it takes.

    →

    3

    A checked answer comes back

    A few lines, with every `path:line` checked against the files and quoted. A claim the code does not back is flagged.

    Your agent's context, when it explores itselfEverything it read stays, and goes with every later turn.

    your task

    grep "request" · 212 lines

    read src/agent/llm.rs · 480 lines

    read src/agent/worker.rs · 610 lines

    grep "down\_until" · 9 lines

    read src/config.rs · 420 lines

    read src/mcp.rs · 900 lines

    about 2,600 lines of files and searches

    Your agent's context, when it asks OtōtoThe answer stays; Otōto's reading was thrown away.

    your task

    the answer and three quoted lines

    still free for the task itself

    a paragraph and three lines

    Fig. 2What one lookup leaves in your agent's context: an illustration, not to scale. With a paid model the smaller context is a smaller bill; with one model on both sides (OpenCode on a local model), a main agent that stays on the task.

    02Tools

    Some questions need a model. Most lookups don't.

    Three tools hand the work to the small model. The rest answer at once, from the files and from git, for when your agent already knows what it wants to see.

    | Tool | What it does | For example | | ------------ | ---------------------------------------------------------------------------- | ------------------------------------------------- | | askmodel | A whole question, answered in a few lines with checked citations. | how is auth wired into the router? | | locatemodel | Where something is defined, or where a behaviour is decided. | where are retries configured? | | callersmodel | Who calls or uses a function, type or setting. | who uses DOWN\_FOR? | | read | Code by address, many at once: a declaration by name, a range, a config key. | src/ototo/agent/llm.rs#brief, pom.xml#spring-core | | outline | A file's or a directory's declarations, with their lines. | src/agent/ | | search | Exact text or a regex across the repository, as path:line hits. | \-F RETRY\_AFTER | | changes | What a branch, a commit or a range changed, by declaration. | \--commit 907d144 | | history | The commits behind some lines or a declaration. | src/ototo/agent/llm.rs#request | | digest | A build or test run cut to its failures, errors and summary. | ototo digest -- cargo test |

    $ ototo read src/ototo/agent/llm.rs#brief ── src/ototo/agent/llm.rs:290-301 brief · fn brief(e: &anyhow::Error) -> String 290| fn brief(e: &anyhow::Error) -> String { 291| if let Some(r) = e.downcast_ref::<reqwest::Error>() { 292| if r.is_connect() { 293| return "unreachable".into(); 294| } 295| if r.is_timeout() { 296| return "timed out".into(); 297| } 298| } 299| let s = e.to_string(); 300| if s.chars().count() > 120 { … } else { s } 301| }

    Fig. 3One function by name, with its lines: no file to open, no model involved.

    $ ototo changes --commit 907d144 ── changes in 907d144 changes: one commit, or a range, told by declaration, 3 files, +123 −20 M src/ototo/main.rs +13 −2 changed Command (69-291) changed main (361-585) M src/ototo/mcp.rs +19 −7 changed instructions (31-86) changed tools (220-380) M src/ototo/tools/git.rs +91 −11 added commit (171-181) added range (184-205)

    Fig. 4A commit told by the functions it touched, instead of a diff to read through.

    03Plugins

    For the files your build tools understand, and your agent has to guess at.

    A plugin reads one kind of file the way its tool does: `read pom.xml#spring-core` returns the version Maven resolves, not the line that says `${spring.version}`. Plugins are WebAssembly, signed and sandboxed: a file plugin sees the repository, read-only; an archive plugin is lent only the files it opens; a forge plugin reaches only the hosts you grant it and never sees its token. Each has its own version, and `ototo plugins update` brings yours up to date between releases.

    archive archive

    Inside a JAR without unpacking it; a class as its declarations, no javap.

    \.jar, \.war, \.zip, \.whl, \*.class

    maven file

    A dependency's effective version and scope, through parents, BOMs and properties.

    pom.xml

    spring-config file

    A property as each profile sees it, and where each value is set.

    application\*.yml, .properties

    terraform file

    A resource with its variables worked out, per environment.

    \.tf, \.tfvars, \*.tofu

    kube file

    A Kustomize resource as it builds; a Helm value and the templates that use it.

    kustomization.yaml, values.yaml

    gitlab-ci file

    A job as GitLab runs it: includes, extends and anchors resolved.

    .gitlab-ci.yml

    compose file

    A service as docker compose runs it, overrides and .env applied.

    compose.yaml

    gitlab forge

    The branch's merge request, a pipeline by stage, a failed job's log cut to what failed.

    gitlab.com, or yours

    Every plugin, by tag, and how to add one

    04Dashboard

    See what it does, call by call.

    `ototo ui` is a dashboard over Otōto's run log, on your machine: every call from every session, how long the model server took, what the small model did turn by turn, and the exact reply your agent got. `ototo tail` follows calls as they run. The page listens on 127.0.0.1 only, and needs its key.

    The dashboard: totals, reliability, activity over time and calls by tool

    Fig. 5An hour of calls on two public repositories: totals, reliability, activity and each tool.

    One call opened: the question, the small model's steps turn by turn, and the reply

    Fig. 6One ask opened: the question, the small model's steps turn by turn, and the reply Claude Code got, its citations checked.

    05Organisations

    On your machines, for the whole team.

    Otōto runs where your code is, and talks only to the model servers you give it. Nothing is sent to us, and nothing checks for updates unless you run `ototo update`. For a company rolling it out to many machines:

    Your model servers

    vLLM, llama.cpp, Ollama, LM Studio or any OpenAI-compatible server. An organisation can make its own the only ones, so code goes nowhere else.

    Settings you enforce

    One file under every user's own; what it lists as enforced applies whatever a user sets: model servers, telemetry, trusted plugin signers. `ototo doctor` says what is enforced.

    Managed rollout

    `ototo managed` writes what to push with MDM, Ansible or a golden image: Claude Code's managed settings and MCP registration, Otōto's settings and trusted keys.

    Usage, savings and the fleet

    Counts, never code, over OpenTelemetry beside Claude Code's own, to a hub of collector, Prometheus and Grafana: usage and savings per person and team, and the versions and servers running now.

    Plugins you trust

    Only plugins signed by keys you trust load. An organisation can ship its own plugins and signers, and a forge plugin reaches only the hosts granted to it.

    A security review

    What runs, what it reads and writes, what leaves the machine, written down. Releases are signed and carry a CycloneDX SBOM; `ototo update` can read from your own mirror.

    Rolling it out to a team? [email protected]

    06Numbers

    What it saved, measured.

    41%less Claude spend on our benchmark, every answer still rightnine coding tasks we wrote, three runs each

    16%less on 27 real bug fixes in four Java libraries, as many fixedjudged by each library's own tests, three runs each

    38%less than Claude Code's own Explore agent on the same fixessame tasks, same model

    Claude Code on Claude Opus 5.5. Your numbers will differ with your code, your questions and your model server.

    07Install

    One command.

    1. curl -fsSL https://ototo.sh | shCopy For macOS on Apple silicon and Linux (x86\_64, arm64). It downloads the newest release, checks it against Otōto's release key, and runs its installer, which finds your model server and sets up Claude Code and OpenCode, asking first. Name the server with `sh -s -- --base-url http://gpu:8000/v1`. Rather see it first? Read the script, then copy it or save it and run it yourself. 2. ototo doctorCopy Checks the settings, the model servers (with a tool-call test), Claude Code and OpenCode, and the plugins. 3. ototo updateCopy When there is a newer release: checked the same way before anything changes. Nothing updates by itself.

    08What's new

    In the latest release.

    2026.14 beta 5 October 2026

  • `ototo forge` finds a tag's pipeline. `ototo forge --ref <name>` (`ref` for the tool) asks about another branch, or a tag, in place of the checked-out branch, and a checkout at a tag asks about the tag. A release's pipeline runs under its tag, and could not be asked for before.
  • `ototo doctor` no longer calls a working model's name wrong. Some servers list one name and answer to another (a vLLM started with a served name answers to the model's own too). Doctor went by the list and marked the endpoint ✗; it now asks for one token under the name in your settings before saying so.
  • Upgrading from 2026.13: `ototo update`.

Every release · Follow releases with the feed

(•)

Open source.

Otōto's source, its plugins and the kit for writing your own are public under MIT or Apache-2.0, your choice, on GitLab. Found a bug, or missing something? Open an issue there.

Releases are signed, and since 2.10 a download can be checked against the source: `sh dist/reproduce.sh` rebuilds the commit a release was built from and compares the binary and every plugin, byte for byte.

ototo.dev · 5 images · 2 videos · 11 links

Comments (0)

No comments yet