Help us shape the future of Go! Take the 2026 Go Developer Survey

zot

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 12 Imported by: 0

README

zot

zot

An automated software factory in a single binary. Give it a software job and its autonomous coding harness plans, edits, runs, and verifies your code until the work is complete.

zot demo

Status

zot is 0.x: functional and in active use, with improvements landing release to release. Until 1.0 the CLI flags, config, and behavior may still change between versions - pin a version and skim the changelog before upgrading.

Why zot exists

Most coding tools optimize the conversation between a developer and an agent. zot optimizes the production run. A software brief goes in, the repository moves toward the requested outcome, and a verified working tree comes out.

The factory is powered by an autonomous coding harness that runs entirely in the binary. It drives the whole loop (plan → act → observe → verify → exit) from one brief, without waiting for follow-up prompts at every step - and without a hosted engine. zot talks straight to a model provider over the OpenAI-compatible API, so all you need is a provider key: OpenAI, Anthropic, Groq, Mistral, DeepSeek, OpenRouter, a local Ollama, or anything else that speaks the same API. No account is required beyond the provider's own, and nothing is sent anywhere except the provider you configure.

The factory model

Stage What zot does
Brief Accepts one task plus the repository's AGENT.md and skills
Plan Breaks the requested outcome into executable work
Build Reads, writes, and edits files, then runs commands in the repo
Verify Inspects results and iterates when the job is not complete
Output Leaves the completed work in the working tree with a visible log

How it works

The harness is zot's own, in the agent package:

  • agent.ExecuteWithTools runs the model in a loop - plan → act → observe → progress → exit - until it records an outcome or hits a budget.
  • agent.DefaultTools() gives it the coding toolbox: read, write, list and shell.

What sits under that is the part worth knowing about:

  • Thread assembly fits the conversation to the model's context window, newest-first, keeping tool calls paired with their results.
  • Compaction condenses older history into a summary when the window fills, rather than dropping it.
  • Loop detection notices when the agent has stopped making progress - four overlapping heuristics, because the obvious one (repeated messages) silently misses reasoning models, which interleave a thought between every tool call.
  • Settle mode ends a run when the agent records an outcome, not when its prose happens to sound final. An answer containing "task completed" is not an ending.
  • Session logs record every run to disk as it happens, so a run nobody watched is still answerable afterwards - and so it can be picked up again.

zot itself is a Bubble Tea front-end for that production run. It launches the harness in a goroutine and renders the event stream (ToolCallStart, ToolCallEnd, Iteration, token narration, AgentExit, …) into a scrollable, read-only viewport. The UI deliberately has no text input.

Prerequisites

  • A key for whichever model provider you want to use
  • Go 1.26+ - only if you build from source

Provider credentials

zot defaults to the openai backend and reads OPENAI_API_KEY:

export OPENAI_API_KEY="sk-…"
zot "add input validation to the signup handler and a test"

Every built-in backend reads its own conventional variable - ANTHROPIC_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, OPENROUTER_API_KEY, TOGETHER_API_KEY, CEREBRAS_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY, ZAI_API_KEY, DASHSCOPE_API_KEY - so switching provider is a flag:

export ANTHROPIC_API_KEY="sk-ant-…"
zot --backend anthropic "…"

A local model needs no key at all:

zot --backend ollama --model llama-4 "…"

Anything else that speaks the OpenAI chat-completions API works too - see Any other provider.

Any key can equally live in the config file (~/.config/zot/config.yaml, or the path given to --config), including as a $ENV_VAR reference so no secret is written to disk:

# ~/.config/zot/config.yaml
backends:
  openai:
    api_key: '$OPENAI_API_KEY'

Developer builds

make build produces a release binary. make dev produces a developer one, and the difference is a security boundary rather than a convenience:

release (make build) developer (make dev)
reads .env from --dir no yes

zot runs unattended with a provider key and a shell tool, so a released binary must not take credentials from whatever directory it was pointed at - running it against a repository you cloned to review would otherwise be enough to load a stray committed .env into the process that is about to run commands. Released builds read credentials from the config file and the real environment, both of which you chose deliberately.

The switch is a build tag (-tags dev) and defaults to off, so a build that forgets it loses a convenience rather than a boundary. zot --version prints which kind you have.

Install

Grab a prebuilt binary from the releases page - no toolchain required. Pick the archive for your platform (linux-amd64, linux-arm64, darwin-amd64, darwin-arm64, windows-amd64):

VERSION=vX.Y.Z           # replace with the latest release tag
OS=linux ARCH=amd64      # e.g. darwin/arm64 on Apple Silicon
curl -L "https://github.com/openzot/openzot/releases/download/${VERSION}/zot-${VERSION}-${OS}-${ARCH}.tar.gz" | tar xz
mv "zot-${VERSION}-${OS}-${ARCH}/zot" ~/.local/bin/ # or any directory on your PATH
zot --version

Build from source

Requires Go 1.26+.

git clone https://github.com/openzot/openzot
cd openzot
make                     # lists the targets
make build               # or: go build -o zot ./cmd/zot
./zot --version

make on its own prints the targets rather than assuming one, because build and dev produce binaries that differ in what they will read from disk. make build stamps the version in; make test, make race, make cover, make vet and make cross GOOS=… GOARCH=… are also available.

Docker

Official Linux amd64 and arm64 images are published to GitHub Container Registry from the same version tag as the binary release. The agent works in /workspace - mount the checkout you are happy for it to change there:

docker pull ghcr.io/openzot/openzot:latest

docker run --rm -it \
  --user "$(id -u):$(id -g)" \
  --env HOME=/tmp \
  --env OPENAI_API_KEY \
  --volume "$PWD":/workspace \
  ghcr.io/openzot/openzot:latest "add a /health endpoint and a test for it"

--user makes the files the agent writes belong to you rather than the image's zot user (uid 10001), and HOME=/tmp gives that user a writable home so tools like git can store their own config. Both are only needed for host bind mounts; with a named volume the defaults are fine.

The entrypoint is zot, so everything after the image name is zot's own viewer; without a TTY it streams plain text, which is what you want in CI:

docker run --rm \
  --user "$(id -u):$(id -g)" --env HOME=/tmp \
  --env OPENAI_API_KEY \
  --volume "$PWD":/workspace \
  ghcr.io/openzot/openzot:latest --max-iterations 40 --task-file TASK.md | tee run.log

A container is also the cleanest answer to Safety: file writes and shell commands are confined to the volume you mounted, so a run cannot reach the rest of your machine. See docs/docker.md for credentials, config and AGENT.md mounts, and hardening flags.

Backends

zot ships with a backend for each common provider. Pick one with --backend, or set default_backend in config.

Backend Endpoint Credential from
openai https://api.openai.com/v1 OPENAI_API_KEY
anthropic https://api.anthropic.com/v1 ANTHROPIC_API_KEY
groq https://api.groq.com/openai/v1 GROQ_API_KEY
mistral https://api.mistral.ai/v1 MISTRAL_API_KEY
deepseek https://api.deepseek.com/v1 DEEPSEEK_API_KEY
openrouter https://openrouter.ai/api/v1 OPENROUTER_API_KEY
together https://api.together.xyz/v1 TOGETHER_API_KEY
cerebras https://api.cerebras.ai/v1 CEREBRAS_API_KEY
xai https://api.x.ai/v1 XAI_API_KEY
moonshot https://api.moonshot.cn/v1 MOONSHOT_API_KEY
zai https://api.z.ai/api/paas/v4 ZAI_API_KEY
qwen DashScope compatible mode DASHSCOPE_API_KEY
ollama http://localhost:11434/v1 none

Any other provider

Anything that speaks the OpenAI chat-completions API works. Name a backend, give it a base URL and a key:

default_backend: mygateway

backends:
  mygateway:
    provider: custom
    base_url: https://gateway.internal.example.com/v1
    api_key: '$GATEWAY_KEY'

The endpoint must be https unless it is loopback, and a custom endpoint needs its own key - a credential is scoped to the host it was issued for, and zot will not forward one to a URL you just typed.

The Responses API

On OpenAI, reasoning models use the Responses API automatically. It carries reasoning state between tool rounds as an opaque item the model resumes from; chat-completions has nowhere to put it, so a reasoning model driven that way re-derives its thinking on every round.

Only OpenAI implements it today, so everywhere else stays on chat-completions. Override per backend if a gateway supports it:

backends:
  mygateway:
    provider: custom
    base_url: https://gateway.example.com/v1
    use_responses: true       # or disable_responses: true

Configuration

Configuration is layered: built-in defaults < config file < ZOT_* environment variables < CLI flags. The config file is optional - env vars alone are enough.

zot config        # opens the config in $EDITOR, creating it from a template
zot config path   # print the config file location

Scalar fields have a matching ZOT_<PATH> env var (e.g. agent.model → ZOT_AGENT_MODEL, default_backend → ZOT_DEFAULT_BACKEND). Provider credentials come from their own conventional variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, …) and so need no ZOT_ prefix; they can equally be set in config, referencing a variable you export. See configs/zot.example.yaml.

Flags

zot --help lists them all. The ones worth knowing:

Flag Effect
--backend / --model which provider and model to run against
--dir the directory the agent reads, writes and runs commands in
--max-iterations cap the agentic rounds; the default is deliberately large
--task-file read the brief from a file instead of the command line
--resume / --session-dir / --no-session see Sessions
--diff show a syntax-highlighted diff under each write
--plain stream unstyled output; auto-enabled when stdout is not a terminal
--config use a specific config file

Controls

Because the agent is autonomous, the only keys are for viewing:

Key Action
↑ / ↓ scroll the log
PgUp/PgDn page the log
g / G jump to top/bottom
q quit

Sessions

Every run is written to ~/.local/state/zot/sessions/ as it happens - one JSON object per line, holding the brief, the model, every message, every tool call and how it ended.

That matters because an autonomous run is unattended by definition: nobody watched it, and by the time you look the terminal is gone. The log is what turns "it failed overnight" into something you can answer.

# what has run, newest first
zot sessions

# read one
cat ~/.local/state/zot/sessions/20260805-155859.jsonl | jq .

# pick up where it stopped - the agent keeps everything it already knew
zot --resume last "now add the tests you skipped"

# or continue the original brief, unchanged
zot --resume last

--resume takes a session id, a path, or last. A resumed run writes its own log and records which session it continued, so a chain of continued runs stays reconstructable.

The log is appended and flushed line by line, so it is readable while the run is still going and a killed run still leaves everything up to the kill. Use --no-session to record nothing, or --session-dir (or ZOT_SESSION_DIR) to put the logs somewhere else.

Project context (AGENT.md & skills)

On startup zot folds in context from two places - the config directory (~/.config/zot/, global) and the working directory (--dir, per-project):

  • AGENT.md - at the root of either directory; its contents are appended to the agent's backstory (config first, then project). Use it for conventions the agent should always follow.
  • skills - each <name>/SKILL.md (with name / description YAML front matter) is described to the agent in its backstory, and the agent reads a skill's full file on demand when it's relevant. Both .skills/ (typical at a project root) and skills/ are searched.
~/.config/zot/          ./ (your project, --dir)
├── AGENT.md            ├── AGENT.md
└── skills/             └── .skills/
    └── greet/              └── deploy/
        └── SKILL.md            └── SKILL.md

Everything here is optional - missing files and directories are ignored.

⚠️ Safety

zot is fully autonomous and has real file-write and shell-exec access from --dir. The flag changes the process working directory; it is not a filesystem sandbox. Absolute paths and shell commands retain all permissions of the zot process. Point it at a scratch directory or a disposable git checkout you are happy for it to change - not your home directory.

Configured backend credentials are resolved into zot's in-memory configuration and then removed from the process environment before the agent starts, so its shell commands do not inherit those API keys. Other secrets already present in the environment or readable from disk remain accessible to those commands.

opens a session in, and the prompts come from whoever that client lets through - so the blast radius is the client's access-control policy, not just yours.

The published container image is the practical way to bound this: the agent can only touch the volume you mounted, and docker run gives you the rest of the levers (read-only root, dropped capabilities, resource limits) in one place. docs/docker.md covers them.

Architecture

Path Responsibility
cmd/zot/ the binary: flag parsing, sessions, working dir, then zot.Run
zot.go embeddable core: builds the provider client + agent options and runs it
agent/ the public harness: ExecuteWithTools, tools, skills, events
internal/loop/ the agentic loop: budgets, guards, settle mode, message hygiene
internal/thread/ context-window assembly and the four loop-detection heuristics
internal/compaction/ condensing older history into a summary when the window fills
internal/provider/ the Transport seam: chat-completions and the Responses API
internal/catalogue/ what each model's context window and capabilities are
internal/tokenizer/ BPE token counting with embedded vocabularies
internal/session/ JSONL run logs: write, read, list, resume
internal/config/ layered config (defaults < file < env), XDG paths, env overrides
internal/build/ release vs developer build, and what that changes
internal/version/ build-time version stamping and GitHub update checks
internal/tui/ the Bubble Tea read-only viewer (model, render, styles, agent bridge)
configs/ example configuration

Releasing is driven by the VERSION file and the GitHub workflows - see RELEASES.md and CHANGELOG.md.

Ecosystem

Project Role
Pantalk Connect coding agents to the chat platforms people already use
MCPShim Turn MCP servers and HTTP APIs into standard CLI commands
crmkit Give agents a shared CRM and system of record over HTTP or MCP

Documentation

Overview

Package zot is the embeddable core of the zot automated software factory. It turns a single plain-English task into an agentic run - plan, act, observe, exit - and renders the run in a read-only terminal UI.

The agentic loop is zot's own (see the agent package): thread assembly, compaction, loop detection and the tool-call cycle all run locally, against any OpenAI-compatible provider. zot needs no account and no hosted engine.

The standalone binary is cmd/zot; an embedding program can import this package and call Run directly while zot's internals stay internal.

Index

Constants

View Source
const DefaultBackstory = `` /* 1028-byte string literal not displayed */

DefaultBackstory is the system instruction handed to the agent when the configuration does not override it. It establishes the fully-autonomous, no-questions-asked contract: zot has no input channel, so the agent must never wait for the user.

Variables

View Source
var ExampleConfigYAML []byte

ExampleConfigYAML is the commented starter config baked into the binary, used by `zot config` to seed ~/.config/zot/config.yaml when it does not exist yet.

Functions

func DefaultConfigPath

func DefaultConfigPath() string

DefaultConfigPath is the default config file location.

func LoadProjectContext

func LoadProjectContext(cfg *Config, dirs ...string) error

LoadProjectContext augments cfg with on-disk context discovered under the given directories, searched in order (typically the config directory first, then the working directory):

  • <dir>/AGENT.md - appended to the agent backstory
  • <dir>/skills/ - loaded via the SDK and added as a "skills" feature

Missing files and directories are ignored, and duplicate directories are searched once. AGENT.md content augments (never replaces) the base backstory.

func Run

func Run(ctx context.Context, cfg Config, task string) error

Run executes one autonomous coding task, rendering the agent's activity in the read-only TUI. The agent's file and shell tools operate on the current working directory, so callers should chdir into the target project first. Run blocks until the user quits the viewer or the program errors.

func RunWith

func RunWith(ctx context.Context, cfg Config, task string, options RunOptions) error

RunWith is Run with session recording and resume.

func Version

func Version() string

Version reports the build version of the linked zot core.

Types

type Config

type Config = config.Config

Config is the fully-resolved zot configuration. Load it with Load; its Validate method enforces the same checks the standalone binary runs.

func Load

func Load(path string) (Config, error)

Load reads configuration, layering defaults < file < env. A missing default file is fine (env vars alone can configure zot).

type RunOptions

type RunOptions struct {
	// SessionDir is where the run's log is written. Empty disables recording.
	SessionDir string

	// Resume seeds the conversation from an earlier session, so a run that ran
	// out of budget or died overnight continues rather than starting again.
	Resume *session.Session

	// OnSession is called once the log is open, with its path. Used to tell the
	// operator where the record is before the run takes over the screen.
	OnSession func(path string)
}

RunOptions configures a run beyond the configuration itself.

Directories

Path Synopsis
Package agent runs autonomous, tool-using conversations against any OpenAI-compatible model provider.
Package agent runs autonomous, tool-using conversations against any OpenAI-compatible model provider.
cmd
zot command
Command zot is an automated software factory you watch, not drive.
Command zot is an automated software factory you watch, not drive.
internal
buildinfo
Package buildinfo reports which kind of binary this is.
Package buildinfo reports which kind of binary this is.
catalogue
Package catalogue records what the engine needs to know about a model before it calls one: how much context it has, how much of that it may spend on the answer, and whether it can use tools or emits a reasoning channel.
Package catalogue records what the engine needs to know about a model before it calls one: how much context it has, how much of that it may spend on the answer, and whether it can use tools or emits a reasoning channel.
compaction
Package compaction condenses a conversation that has outgrown the model's context window.
Package compaction condenses a conversation that has outgrown the model's context window.
config
Package config loads zot's configuration, layering built-in defaults, an optional YAML file, and environment variables (defaults < file < env).
Package config loads zot's configuration, layering built-in defaults, an optional YAML file, and environment variables (defaults < file < env).
loop
Package loop runs the agentic conversation: call the model, execute the tools it asks for, feed the results back, repeat until the task settles.
Package loop runs the agentic conversation: call the model, execute the tools it asks for, feed the results back, repeat until the task settles.
provider
Package provider talks to OpenAI-compatible chat-completions endpoints.
Package provider talks to OpenAI-compatible chat-completions endpoints.
session
Package session records a run to disk so it can be inspected afterwards and picked up again.
Package session records a run to disk so it can be inspected afterwards and picked up again.
thread
Package thread assembles conversation threads that fit a model's context window, and detects when an agent has stopped making progress.
Package thread assembles conversation threads that fit a model's context window, and detects when an agent has stopped making progress.
tokenizer
Package tokenizer counts tokens the way a model actually does.
Package tokenizer counts tokens the way a model actually does.
tui
Package tui renders the read-only terminal view of an autonomous agent run.
Package tui renders the read-only terminal view of an autonomous agent run.
version
Package version provides build-time version information and update checking for the zot binary.
Package version provides build-time version information and update checking for the zot binary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL