# opendot: A terminal AI agent that snapshots every action so you can undo it

> Source: <https://github.com/vedaant00/opendot>
> Published: 2026-07-29 18:11:00+00:00

[
](https://pypi.org/project/opendot/)

An interactive terminal AI agent you can fully undo.

opendot works directly on your real files and shell — but unlike other terminal
agents, **every action it takes is snapshotted first**, so you can see exactly
what it did and cleanly walk it back. Files *and* shell commands, not just
in-repo edits. Commands whose effects escape your workspace (network, sudo,
`git push`

, deleting outside the working dir) are flagged and confirmed before
they run, with an honest note about what can't be undone.

That's the point of opendot: an agent you can let loose because nothing it does is a surprise, and (almost) nothing is irreversible.

opendot is model-agnostic — it works with any model through LiteLLM (OpenAI, Anthropic, Google, DeepSeek, …) and runs fully local via Ollama. Ollama is just the zero-setup local option; use whatever backend you prefer.

```
# try it instantly, no install
uvx opendot

# recommended (isolated global CLI)
uv tool install opendot        # or: pipx install opendot

# also works
pip install opendot
opendot                              # open an interactive chat
opendot -p "summarize this project"  # one-shot, for scripts / CI
opendot --model claude-opus-4-5      # launch with a specific model (see below)

opendot log                          # audit: what has the agent done here?
opendot undo                         # revert the last action
opendot undo 000004                  # restore the workspace to before action #4
```

Inside the chat, slash-commands: `/model`

(searchable model picker),
`/provider`

(connect a provider + paste an API key), `/log`

, `/undo`

, `/clear`

,
`/compact`

, `/help`

.

Any model works — cloud, local, or Hugging Face. You need an API key for the
provider you want to use (opendot is BYO-key; it doesn't host models). Pick a
model and paste a key right inside the chat with `/model`

and `/provider`

, or
set the key in your environment and pass `--model`

:

Provider names link to where you get a key.

| Provider | Env var | Example `--model` |
|---|---|---|
|

`OPENAI_API_KEY`

`gpt-5.1`

[Anthropic](https://console.anthropic.com/settings/keys)`ANTHROPIC_API_KEY`

`claude-opus-4-5`

`GEMINI_API_KEY`

`gemini/gemini-3-pro`

[DeepSeek](https://platform.deepseek.com/api_keys)`DEEPSEEK_API_KEY`

`deepseek/deepseek-chat`

[Groq](https://console.groq.com/keys)`GROQ_API_KEY`

`groq/llama-3.3-70b-versatile`

[Hugging Face](https://huggingface.co/settings/tokens)`HF_TOKEN`

`huggingface/together/deepseek-ai/DeepSeek-R1`

[Ollama](https://ollama.com)(local, no key)`ollama/qwen3`

Reasoning models stream their thinking live.

**Local OpenAI-compatible servers** (llama.cpp / `llama-server`

, vLLM, LM Studio):
point opendot at the server with `--api-base`

and an `openai/`

-prefixed model.

```
# e.g. llama.cpp: llama-server -m model.gguf --port 8080
opendot --model openai/local --api-base http://localhost:8080/v1
```

**Which model runs.** The default is `gpt-5.1`

. If its key (`OPENAI_API_KEY`

)
isn't set but another provider's key is, opendot automatically switches to that
provider on launch — e.g. with only `DEEPSEEK_API_KEY`

set, a bare `opendot`

uses `deepseek/deepseek-chat`

. If **no** provider key is found, opendot starts
fine but the first message shows a hint to set a key or run `/provider`

(rather
than a raw provider error). `ollama/*`

models need no key — just a local Ollama.

opendot is an [MCP](https://modelcontextprotocol.io) client: connect any MCP
server and its tools become available to the agent alongside the built-in ones.
Manage them from inside the chat with ** /mcp** (a dropdown of your servers and
their status, with "➕ Add a server"), or from the command line:

```
# a stdio server — put its launch command after `--`
opendot mcp add <name> --env KEY=VALUE -- <command> [args...]

# a remote server (http/sse)
opendot mcp add <name> --url <https url>

# a remote server that needs auth — pass an HTTP header
opendot mcp add supabase \
  --url "https://mcp.supabase.com/mcp?project_ref=<id>&read_only=true" \
  --header "Authorization=Bearer <your-supabase-access-token>"

opendot mcp list           # show configured servers
opendot mcp remove <name>  # remove one
```

Servers are stored in `~/.opendot/mcp.json`

and connect automatically on the
next launch; connected servers appear in the sidebar. For authenticated remote
servers, opendot supports the header/token method (e.g. Supabase's access
token) — the interactive browser-OAuth flow is not implemented yet.

Because opendot can't know what an external tool does, **every MCP tool call is
treated as irreversible** — it's confirmed before running and marked ✗ in the
ledger. Your built-in file/shell actions stay snapshotted and undoable as usual.

Beyond MCP, opendot can connect to [Composio](https://composio.dev)'s 1000+ app
tools (Gmail, Slack, GitHub, Notion, Linear, …) using **your own** Composio API
key. Just use `/composio`

in the chat:

- The first
`/composio`

asks for your Composio API key (stored in`~/.opendot/composio.json`

, owner-readable only). - After that,
`/composio`

lists the available apps. Pick one — if it needs OAuth, opendot opens your browser to authorize and waits for you to finish; direct/API-key connectors activate immediately. - Enabled apps appear in the sidebar; their tools load on the next launch.

Composio tools reach external services, so — like MCP — **every call is treated
as irreversible**: confirmed first, marked ✗ in the ledger.

Drop an `OPENDOT.md`

in your project. Its prose is given to the agent as
context. You can also control what gets snapshotted with an `opendot`

block:

opendot
# snapshot these even though they'd normally be skipped:
snapshot: dist
# never snapshot these:
skip: data, *.log
By default opendot skips `.git`

, `node_modules`

, virtualenvs, and build caches
when snapshotting — your rules override those in either direction.

- Before every file write or shell command, opendot snapshots the working
directory into a
**content-addressed store** in`~/.opendot`

(each unique file stored once, so snapshots are cheap). - Every action is recorded in an
**append-only ledger** you can inspect with`opendot log`

. `opendot undo`

restores the workspace to a chosen point, exactly.- A conservative
**classifier** decides which shell commands are workspace- contained (auto-run, undoable) vs. escaping (confirmed first, marked irreversible). When unsure, it asks.

Honest boundary: opendot cannot undo effects that leave your machine (a sent
email, a dropped remote database, a `git push`

). It tells you *before* running
those, rather than pretending otherwise.

Issues and PRs welcome — see [CONTRIBUTING.md](/vedaant00/opendot/blob/main/CONTRIBUTING.md) for setup and
the one hard rule (don't break reversibility). Security reports go through
[SECURITY.md](/vedaant00/opendot/blob/main/SECURITY.md).

```
git clone https://github.com/vedaant00/opendot
cd opendot
uv pip install -e ".[dev]"   # or: pip install -e ".[dev]"
pytest
```

Early (alpha). The interactive agent, local tools, and the full reversibility
engine work and are tested. Streaming, slash-commands, and `OPENDOT.md`

rules
are in. A richer TUI and more tools are coming.
