# Octo: a coding CLI that treats the model as replaceable mid-conversation

> Octo is a Bun-based terminal coding assistant that talks to any OpenAI-compatible or Anthropic-compatible API, keeps per-directory sessions you can resume, loads layered rule files, and can auto-repair the tool call and edit failures that bigger coding models produce.

**synthetic-lab/octofriend** — An open-source coding helper. Very friendly!

- Repository: https://github.com/synthetic-lab/octofriend
- Stars: 1,022 · Forks: 80
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/synthetic-lab-octofriend

## A CLI that treats the model as a swappable component

Octo is a small, helpful, zero-telemetry, cephalopod-flavored coding assistant, and the short version of the pitch is that Octo is your friend.

The install is a global npm package:

```bash
npm install --global octofriend
```

And then you run it, either under the full name or the short one:

```bash
octofriend
# or, for short:
octo
```

The design decision that shapes everything else is that Octo is not tied to a model. It works with any OpenAI-compatible or Anthropic-compatible LLM API, and it lets you switch models mid-conversation when a particular model gets stuck. That is a different relationship with the model from the one most coding assistants impose, where the model is the product and you pick a subscription tier. Here the model is a component you configure, and the interesting machinery is around it.

Around that sit two small models the project trained and open-sourced, diff-apply and fix-json, hosted on Hugging Face under the syntheticlab organisation. They are the autofix models, and their job is to handle the tool call and code edit failures that come from the main coding models you are working with. The documentation is clear that the autofix models work with any coding LLM, not just the ones they were trained alongside, and the models named as working well are Kimi K2.7-Code, GLM-5.2, GPT-5.6 and Claude 5, with the note that pretty much any agentic coding model will work.

The zero-telemetry claim is a design constraint rather than a marketing line. The project states that using Octo with a privacy-focused LLM provider means your code stays yours, and that you can also use it with any OpenAI-compatible provider, with Anthropic, or with local LLMs you run on your own machine.

The project is MIT licensed, the primary language is TypeScript, the last push was on 2026-10-15, and the package version in the manifest is 0.0.59 with no published GitHub releases.

## Sessions are per directory, and you can go back to one

The conversation history model is simple and useful. Octo automatically saves your conversations per-directory, so a session belongs to a project rather than to a global list.

When you exit a session that has history, Octo prints the command you need to pick it back up:

```bash
octo --resume <session-id>
```

To see the saved sessions for the current directory along with their IDs:

```bash
octo session list
```

And you do not have to leave the app to switch. Press Ctrl+p to open the menu and select Load previous session, and you get a list of previous sessions for the current directory to pick from. The documentation is explicit that your current session is not lost, since it was saved too, so switching back is always possible.

The container case has its own resume form. If you started a session inside a Docker container with octo docker run, you can resume it the same way and optionally replace the original docker run arguments:

```bash
octo --resume <session-id> [new docker run args...]
```

That last detail matters more than it looks. Without the ability to replace the run arguments, resuming a container session would mean reconstructing the image, the volume mounts and the environment by hand, which is exactly the state you no longer have because you closed the terminal.

The per-directory design also means the session list is not global clutter. A directory you work in has its own history, and moving to another directory gives you another one.

## Which instruction file wins, and how rules merge up the tree

Octo looks for instruction files with three names: OCTO.md, CLAUDE.md and AGENTS.md. It uses the first one it finds. The point of that is separation: if you want different instructions for Octo than for Claude, you have an OCTO.md and a CLAUDE.md side by side, and Octo ignores the CLAUDE.md.

The search walks the current directory and every parent directory up until, and including, your home directory. All the rule files that are found are merged, so a project-specific OCTO.md and a general one in your home directory both apply.

If you would rather not put a file in your home directory, there is a global rules file location too, at ~/.config/octofriend/OCTO.md. That is the same directory the config file lives in, and the config path is the one the documentation gives for the search settings too:

```bash
~/.config/octofriend/octofriend.json5
```

The merge behaviour is the part that catches people out. Because every file from the current directory up to the home directory is merged, a rule in your home directory applies to every project, and a project rule is added on top of it rather than replacing it. If a project needs to contradict a global rule, the merging semantics decide whether it can.

The project also supports the Agent Skills spec, which is how you give reusable context-dependent instructions. A code review skill, for instance, is tagged Markdown with optional scripts, and Octo loads the skill when it needs to do a code review. The format is front matter with a name and a description, then the instruction body, and the full spec lives at agentskills.io.

So there are three layers of instruction: rules files that always apply, skills that load when relevant, and the model configuration for modalities and endpoints.

## The web search tool, and its default behaviour

The search tool is the part of Octo with a vendor preference baked in, and it is worth understanding exactly.

By default, Octo looks for Synthetic API keys to use Synthetic's private, zero-data-retention search API to power the web search tool. If you have any Synthetic models configured anywhere in Octo, the search tool just works, and the documentation notes that even non-Synthetic-hosted models like Claude will be able to use it.

If you want to use a different search API, you configure the search key in the config file:

```typescript
{
  // ...the rest of your config,
  search: {
    url: "some_search_api_url",
    apiEnvVar: "SOME_ENV_VAR_FOR_AUTH"
  },
}
```

The search tool makes POST requests against that URL with a body of a query field, which is compatible with both Synthetic and Exa:

```javascript
{
  query: "some search query",
}
```

And the third case is the one that matters for a privacy-minded user. If you do not configure the search config and you do not configure any Synthetic API keys, Octo's harness automatically hides the web search tool so that Octo does not try to call it. The tool is not present rather than present and failing, which is a better default than most assistants manage.

The URL and the key are separated deliberately, with apiEnvVar naming an environment variable rather than a key value, so the credential stays out of the config file. The config file is JSON5, so comments are allowed, which is what the example block relies on.

## Docker as the execution boundary, and the parts that stay on the host

Octo has built-in Docker support and can attach to a container without special configuration or editing the image or the container.

To make Octo run inside a container you already have running, for example an existing Docker Compose setup:

```bash
octo docker connect your-container-name
```

To have Octo launch an image and shut it down when Octo quits:

```bash
# Make sure to add the -- before the docker run args!
octo docker run -- ordinary-docker-run-args
```

And the example given is an Alpine container:

```bash
octo docker run -- -d -i -t alpine /bin/sh
```

The comment about the double dash is a real usability detail. Without it, the arguments you pass would be parsed by Octo rather than forwarded to docker run, and Octo would reject or misread them.

The boundary is deliberately partial, and the documentation is specific about what stays outside. All of Octo's shell commands and filesystem edits and reads happen inside the container. However, Octo continues to use any MCP servers defined in your config via your host machine, since the MCP servers are presumably running on your machine and not inside the container, and it makes HTTP requests from your machine as well if it uses the built-in fetch tool.

That last part is a genuine hole in the sandbox, and it is there for a stated reason: so that you can use arbitrary containers that may not have wget or curl installed. The trade is that the container boundary protects the filesystem, not the network.

The image support is also there for attaching images to messages. If your currently selected model has image input enabled, you paste an image from your clipboard into the input box, each attached image shows up as a badge alongside the input, and it goes with the message when you press Enter. You can attach several, delete one with Backspace, or clear them all with Ctrl+c. For a custom model, the modalities entry in the config enables it, with an image block carrying enabled, maxSizeMB and acceptedMimeTypes, the last being image/png, image/jpeg, image/webp and image/gif.

## How the project is built, and what is in the tree

The toolchain is Bun, pinned to a specific version. The manifest sets engines.bun to 1.4.3, and every script runs through bun: the dev script is bun --watch source/cli/cli.tsx, the exec script runs source/cli/cli.tsx, the compile script is bun build.ts, and the test script is bun test with a preload of the test setup file and a 30 second timeout. The compiled output is a binary with two names, octofriend and octo, both pointing at bin/octo.sh.

The test setup is worth noting because it shows the shape of the code. Tests live under source, they are run with a preload of source/test/setup.ts, and the prepublishOnly script runs tsc --noEmit, then the test run, then the production compile. So type checking, tests and a build all have to pass before anything is published, and the production compile is run with the --all flag.

The state layer is Drizzle ORM, with drizzle.config.ts at the root and a drizzle/ directory for the migrations, and the schema files are shipped in the published package alongside the drizzle directory, so a consumer gets the schema definition rather than only the compiled code.

The rest of the top level is conventional: source/ for the code, bin/ for the launcher scripts, build.ts for the build, scripts/, demos/ with a preview script, training/ which holds the material for the models the project trained, and an OCTO.md at the root, which is Octo's own rules file for its own repository. There is also canary.sh and canary.fish for release channels, an IN-APP-UPDATES.txt, a TODO.md, a .husky directory for git hooks, and .agents/ and .gitmodules.

The dependency list shows the breadth of what a terminal coding assistant has to be: the Anthropic SDK and the Model Context Protocol SDK for the model and the tool protocol, commander for the command line, chalk and figures and figlet for the terminal look, cli-spinners for progress, clipboardy for image pasting, diff for showing edits, html-to-text for handling fetched pages, highlight.js for rendering code, drizzle-orm for storage, and json-schema with zod for tool definitions.

## Conclusion

Octo fits a developer who wants a terminal coding assistant they can point at any local or hosted model, who cares about the conversation history being on their own machine, and who works in a container most of the time. It is a poor fit if you want a graphical IDE integration, and a poor fit if you dislike the fact that the search tool defaults to the maintainers' own hosted API, since the tool is hidden only when no keys are configured. Before adopting, run octo and check the search configuration, because the default behaviour routes queries through a third-party service, and read the OCTO.md and CLAUDE.md precedence rules since that decides which instruction files your agent actually reads.

## FAQ

### How do I install and start Octo?

Run npm install --global octofriend, then start it with octofriend or the shorter octo. The package is MIT licensed and the manifest requires Bun 1.4.3 for the development and build scripts.

### Which language models does Octo support?

Any OpenAI-compatible or Anthropic-compatible LLM API, and you can switch models mid-conversation when one gets stuck. The documentation names Kimi K2.7-Code, GLM-5.2, GPT-5.6 and Claude 5 as working well, and says pretty much any agentic coding model will work.

### How do I resume a previous Octo session?

Sessions are saved per-directory. On exit Octo prints the resume command, octo --resume <session-id>, and octo session list shows the saved sessions for the current directory. Inside the app, Ctrl+p opens the menu with Load previous session. A container session resumes with the same flag and optional replacement docker run arguments.

### Which rules file does Octo use, and how are they combined?

Octo looks for OCTO.md, CLAUDE.md and AGENTS.md and uses the first one it finds, so an OCTO.md takes precedence over a CLAUDE.md in the same place. It searches the current directory and every parent up to and including your home directory, and all the rule files it finds are merged. A global file can also live at ~/.config/octofriend/OCTO.md.

### Does Octo send my code anywhere?

The project states Octo has zero telemetry, and that using it with a privacy-focused provider or a local LLM keeps your code on your own machine. One thing to check is the web search tool, which by default looks for Synthetic API keys and uses Synthetic's own search API, and which is automatically hidden if you configure neither the search config nor any Synthetic keys.

## Sources

- [Issues](https://github.com/synthetic-lab/octofriend/issues)
- [License: MIT](https://github.com/synthetic-lab/octofriend/blob/main/LICENSE)
- [README](https://github.com/synthetic-lab/octofriend/blob/main/README.md)
- [synthetic-lab/octofriend on GitHub](https://github.com/synthetic-lab/octofriend)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/synthetic-lab-octofriend
