Show HN: Dora – a tiny LLM agent with only bash tool Dora, a tiny modular LLM agent kernel for Go, has been released as an open-source project on GitHub, featuring a core loop and two interfaces (Model and Tool) with a stateless design and sequential tool execution. The project provides installers for macOS, Linux, and Windows, supports self-update with checksum verification, and requires Go 1.25 or newer to build. dora is a tiny, modular LLM agent kernel for Go. Its core is one loop and two interfaces: Model and Tool . See docs/architecture.md /lgxz/dora/blob/main/docs/architecture.md for module boundaries, dependencies, interfaces, and runtime flows. model := newMyModel weather := newWeatherTool agent, err := dora.New model, weather if err = nil { log.Fatal err } result, err := agent.Run ctx, dora.Message{ {Role: dora.RoleUser, Content: "What's the weather?"}, } if err = nil { log.Fatal err } fmt.Println result.Content The agent is stateless. Keep result.Messages and pass them to a later call to continue a conversation. The kernel supports optional model streaming events while keeping its baseline Model interface synchronous. Tool calls are deliberately executed only after the current model response completes, and are still sequential. There is no built-in memory, policy engine, middleware, or provider SDK. On macOS or Linux, install the latest release with curl: curl -LsSf https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | sh Use wget when curl is unavailable: wget -qO- https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | sh On Windows, use PowerShell: powershell -ExecutionPolicy Bypass -c "irm https://github.com/lgxz/dora/releases/latest/download/dora-installer.ps1 | iex" The installers download the archive for the current OS and architecture, verify it against the release SHA-256 checksums, and install dora into $HOME/.local/bin by default. Set DORA INSTALL DIR to choose another directory: curl -LsSf https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh \ | env DORA INSTALL DIR=/usr/local/bin sh Install a specific release by using its tagged installer URL. Each installer is pinned to the release that contains it: curl -LsSf https://github.com/lgxz/dora/releases/download/v0.1.0/dora-installer.sh | sh Run dora --version to inspect the installed version, source commit, and build date. Standalone releases that include self-update support can update themselves: dora -update The updater checks the latest stable GitHub Release, verifies its archive against checksums.txt , validates the downloaded executable, and replaces the current binary with rollback on failure. Go builds, manual archive installs, and package-manager installs remain unmanaged; upgrade those through their original installation method. Re-run the latest installer once to enable self-update on an older installation. Release archives and checksums remain available on the GitHub Releases page for manual installation and verification. To replace an unmanaged or development build for example, one installed with make install with the latest release, bypassing the standalone-install marker and version checks: dora -update --force Building Dora requires Go 1.25 or newer. CI checks both the minimum supported Go 1.25 line and the current Go 1.26 line; release binaries use the latest Go 1.26 patch release. Build the command with debug information: make build The binary is written to build/dora build/dora.exe on Windows . For a smaller distribution binary without symbol and DWARF debug data, use: make release The release target uses -trimpath , strips debug data, and embeds the version, commit, and build date. Override VERSION , COMMIT , or BUILD DATE when needed. On Windows the equivalent Go command can be run directly with build/dora.exe as the output path when make is unavailable. To build a release binary and install it into $ PREFIX /bin/dora default $HOME/.local/bin/dora , use: make install install depends on release and creates $ PREFIX /bin if needed. Override PREFIX to choose another location, for example make install PREFIX=/usr/local . Dora reads each provider's API key from a dedicated environment variable. The following table lists the supported providers, their environment variables, and their default models: | Provider | Environment variable | Default model | |---|---|---| | openai | OPENAI API KEY | gpt-5 | | deepseek | DEEPSEEK API KEY | deepseek-v4-flash | | trust | TRUST API KEY | auto | Set the environment variable for the provider you want to use. The commands differ by operating system. macOS / Linux, temporary current terminal only : export OPENAI API KEY="sk-..." macOS / Linux, permanent: append the export line above to ~/.zshrc zsh or ~/.bashrc bash , then reload it: source ~/.zshrc Windows PowerShell, temporary current session only : $env:OPENAI API KEY = "sk-..." Windows PowerShell, permanent persists for the current user : Environment ::SetEnvironmentVariable "OPENAI API KEY", "sk-...", "User" Windows CMD, temporary current session only : set OPENAI API KEY=sk-... When you set keys for more than one provider, specify model.provider explicitly in ~/.dora/config.yaml ; otherwise Dora reports an ambiguity error. Setting exactly one key lets Dora select that provider automatically. With exactly one supported provider API key set, Dora runs without a configuration file and selects that provider automatically. For example, DEEPSEEK API KEY selects deepseek , OPENAI API KEY selects openai , and TRUST API KEY selects trust . If multiple provider keys are set, configure model.provider explicitly. If none are set, Dora retains deepseek as the fallback and reports that DEEPSEEK API KEY is missing. To customize the defaults, create ~/.dora/config.yaml . Dora uses this ~/.dora/ layout on every operating system, including macOS. Set the DORA HOME environment variable to an absolute path to override the home directory. You can also place the file anywhere and pass --config path/to/config.yaml ; an explicitly requested file must exist. model: provider: deepseek An explicit provider always takes precedence over environment-based selection. The same automatic selection applies when a configuration file exists but omits model.provider . The deepseek preset defaults to the chat completions API, deepseek-v4-flash , https://api.deepseek.com , and DEEPSEEK API KEY . The openai preset defaults to chat completions , gpt-5 , https://api.openai.com/v1 , and OPENAI API KEY . The trust preset defaults to chat completions , auto , https://api.trustoken.cn/v1 , and TRUST API KEY . Override any preset field when needed, and set api: responses to use the Responses API. Both APIs always use SSE streaming. Responses tool loops replay typed output items locally and do not depend on server-side response storage. To use any third-party provider that speaks the OpenAI Chat Completions protocol for example Ollama, LM Studio, vLLM, Groq, Together, OpenRouter, or a self-hosted endpoint , keep model.provider: openai and override base url , name , and api key env or api key . The Chat Completions endpoint is base url + "/chat/completions" , so base url should be the provider's /v1 or equivalent root. For a self-hosted Ollama endpoint that requires no authentication, set api key env: "" to disable the API key: model: provider: openai name: llama3.1 base url: http://localhost:11434/v1 api key env: "" For a hosted OpenAI-compatible service that requires a key, such as OpenRouter or Groq, point api key env at a custom environment variable: model: provider: openai name: openrouter/auto base url: https://openrouter.ai/api/v1 api key env: OPENROUTER API KEY For a one-off invocation, override the model and base URL on the command line: ./dora --model llama3.1 --base-url http://localhost:11434/v1 "prompt" Literal api key is also supported, but an environment variable keeps secrets out of the configuration file. A non-empty literal key takes precedence over api key env . Set api key env: "" explicitly for a local endpoint that does not require authentication. Dora runs up to 64 model-tool rounds per segment by default. Keep the safeguard but adjust it for unusually long tool workflows when needed: agent: max rounds: 96 Override it for one invocation with --max-rounds : ./dora --max-rounds 96 "Complete a long task" When the limit is reached with both stdin and stderr attached to a terminal, Dora asks whether to continue for another segment. Confirming resumes from the completed tool output without replaying work. Declining stops normally and saves the partial state of a named session. With piped or redirected I/O, Dora does not prompt and returns dora: maximum rounds exceeded instead. Run a one-shot prompt or combine an instruction with piped input: export DEEPSEEK API KEY="..." ./dora "Explain this repository" git diff | ./dora "Review this change" Progress is shown on stderr with a small Dora personality, while the final answer remains on stdout. Use --quiet or -q when only the answer is wanted: ./dora --quiet "Explain this repository" When stdout is a terminal, Dora prints the final answer as plain text. Redirected or piped stdout is identical, preserving stable output for scripts: ./dora "Write release notes" ./dora "Write release notes" release-notes.md Colors are enabled automatically for terminal output. Set NO COLOR=1 to keep the layout without ANSI colors; progress remains visible on stderr. Use a session name to continue the same conversation across CLI invocations: ./dora -s system-status "Analyze this machine's system status" ./dora -s system-status "Continue with the busiest processes" Start over under the same session name with --fresh . Existing history is ignored for this run and replaced only after the new task succeeds; if the run fails, the previous session remains intact: ./dora -s system-status --fresh "Analyze this machine from scratch" Session names may contain letters, numbers, . , , and - . Dora stores each session as a versioned JSON snapshot with 0600 permissions. Session v3 binds the configured provider, API, model, and base URL: Chat Completions resumes from messages, while Responses additionally persists its opaque typed-item continuation. Use --fresh before changing a session's backend. Version 1 and 2 session files are not supported. The default directory is ~/.dora/sessions on every operating system. Omit --session / -s to keep the existing stateless behavior. Session files can contain commands and tool output, so treat them as sensitive. Do not run two Dora processes against the same session name concurrently. Use --config , --model , --base-url , --max-rounds , --skills-dir , or --no-skills to override the corresponding configuration for one invocation. Skills are local instruction packages loaded by the model only when relevant. Each skill is a directory containing a SKILL.md with strict YAML front matter: skills/ └── system-status/ └── SKILL.md --- name: system-status description: Analyze CPU, memory, disk, and busy processes. --- System status Inspect the machine methodically and summarize actionable findings. By default, Dora discovers the skills directory at ~/.dora/skills or DORA HOME/skills , independent of the active config.yaml path. No configuration is needed. Use skills.directories only to add more parent directories: skills: directories: - /absolute/path/to/additional-skills For a one-off run, add one or more parent directories on the command line: dora --skills-dir ./project-skills --skills-dir ~/shared-skills "Run checks" Command-line directories are merged with the default and configured directories, converted to absolute paths, and deduplicated. Use --no-skills to disable every skill source for one invocation; it takes precedence over both --skills-dir and skills.directories . Dora advertises only skill names and descriptions in the skill tool schema. The absolute skill directory and complete SKILL.md are returned when the model calls that tool, allowing instructions to reference files such as scripts/check.sh . The skill tool never executes those files; execution still requires an enabled tool such as Bash. Names must contain lowercase letters, numbers, and hyphens, and must match their directory name. Duplicate names are rejected. A missing or empty default directory simply leaves the tool disabled; malformed discovered skills and missing explicitly configured or command-line directories are errors. Dora does not automatically read or migrate the previous macOS directory. Move existing files manually before running the new version: mkdir -p "$HOME/.dora" mv "$HOME/Library/Application Support/dora/config.yaml" "$HOME/.dora/config.yaml" mv "$HOME/Library/Application Support/dora/skills" "$HOME/.dora/skills" mv "$HOME/Library/Application Support/dora/sessions" "$HOME/.dora/sessions" Skip any mv command whose source does not exist. If DORA HOME is set, use its directory instead of the fallback destination shown above. Pushing a semantic version tag runs the release workflow: git tag v0.1.0 git push origin v0.1.0 The workflow runs the full validation, renders installers pinned to the tag, and uses GoReleaser to publish static archives for Linux, macOS, and Windows on amd64 and arm64. It also publishes checksums.txt ; public repositories receive GitHub build provenance attestations. Tags that do not match semantic version syntax fail before publishing. Command tools use platform-aware automatic defaults: Bash is enabled on Linux and macOS, while PowerShell is enabled on Windows. The other command tool is disabled even if its executable is on PATH . Omit enabled to use that policy, or set it explicitly to override the platform default: tools: bash: enabled: false timeout seconds: 30 powershell: enabled: true timeout seconds: 30 Automatic tools whose executable is absent are skipped. A tool explicitly enabled with enabled: true must exist on PATH , otherwise Dora reports an error. Discovery currently checks executable presence only; it does not launch the shell to probe its runtime environment. The Bash tool runs bash -lc in Dora's current directory. The model can use cd inside a command when it needs another directory. The tool returns exit code, stdout, stderr, timeout, and truncation information to the model as JSON. Output is limited to 1 MiB per stream. This tool grants the model the same filesystem and process permissions as the dora process, so disable it unless you trust the environment in which Dora runs. The independent powershell tool prefers PowerShell Core pwsh and falls back to Windows PowerShell powershell.exe . If both tools are explicitly enabled, they are exposed separately so their command syntaxes remain distinct. PowerShell also starts in Dora's current directory and can use Set-Location inside a command when needed. Both command tools accept an optional per-command timeout. It overrides the configured default for that call and cannot exceed 3600 seconds: { "command": "go build ./...", "timeout seconds": 300 } When omitted, timeout seconds comes from the corresponding YAML tool setting, or defaults to 30 seconds when that setting is zero or absent.