{"slug": "show-hn-dora-a-tiny-llm-agent-with-only-bash-tool", "title": "Show HN: Dora – a tiny LLM agent with only bash tool", "summary": "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.", "body_md": "`dora`\n\nis a tiny, modular LLM agent kernel for Go. Its core is one loop and\ntwo interfaces: `Model`\n\nand `Tool`\n\n.\n\nSee [ docs/architecture.md](/lgxz/dora/blob/main/docs/architecture.md) for module boundaries,\ndependencies, interfaces, and runtime flows.\n\n```\nmodel := newMyModel()\nweather := newWeatherTool()\n\nagent, err := dora.New(model, weather)\nif err != nil {\n\tlog.Fatal(err)\n}\n\nresult, err := agent.Run(ctx, []dora.Message{\n\t{Role: dora.RoleUser, Content: \"What's the weather?\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\n\nfmt.Println(result.Content)\n```\n\nThe agent is stateless. Keep `result.Messages`\n\nand pass them to a later call to\ncontinue a conversation.\n\nThe kernel supports optional model streaming events while keeping its baseline\n`Model`\n\ninterface synchronous. Tool calls are deliberately executed only after\nthe current model response completes, and are still sequential. There is no\nbuilt-in memory, policy engine, middleware, or provider SDK.\n\nOn macOS or Linux, install the latest release with curl:\n\n```\ncurl -LsSf https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | sh\n```\n\nUse wget when curl is unavailable:\n\n```\nwget -qO- https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | sh\n```\n\nOn Windows, use PowerShell:\n\n```\npowershell -ExecutionPolicy Bypass -c \"irm https://github.com/lgxz/dora/releases/latest/download/dora-installer.ps1 | iex\"\n```\n\nThe installers download the archive for the current OS and architecture,\nverify it against the release SHA-256 checksums, and install `dora`\n\ninto\n`$HOME/.local/bin`\n\nby default. Set `DORA_INSTALL_DIR`\n\nto choose another\ndirectory:\n\n```\ncurl -LsSf https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh \\\n  | env DORA_INSTALL_DIR=/usr/local/bin sh\n```\n\nInstall a specific release by using its tagged installer URL. Each installer is pinned to the release that contains it:\n\n```\ncurl -LsSf https://github.com/lgxz/dora/releases/download/v0.1.0/dora-installer.sh | sh\n```\n\nRun `dora --version`\n\nto inspect the installed version, source commit, and build\ndate. Standalone releases that include self-update support can update themselves:\n\n```\ndora -update\n```\n\nThe updater checks the latest stable GitHub Release, verifies its archive\nagainst `checksums.txt`\n\n, validates the downloaded executable, and replaces the\ncurrent binary with rollback on failure. Go builds, manual archive installs,\nand package-manager installs remain unmanaged; upgrade those through\ntheir original installation method. Re-run the latest installer once to enable\nself-update on an older installation. Release archives and checksums remain\navailable on the GitHub Releases page for manual installation and verification.\n\nTo replace an unmanaged or development build (for example, one installed with\n`make install`\n\n) with the latest release, bypassing the standalone-install\nmarker and version checks:\n\n```\ndora -update --force\n```\n\nBuilding 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.\n\nBuild the command with debug information:\n\n```\nmake build\n```\n\nThe binary is written to `build/dora`\n\n(`build/dora.exe`\n\non Windows). For a\nsmaller distribution binary without symbol and DWARF debug data, use:\n\n```\nmake release\n```\n\nThe release target uses `-trimpath`\n\n, strips debug data, and embeds the version,\ncommit, and build date. Override `VERSION`\n\n, `COMMIT`\n\n, or `BUILD_DATE`\n\nwhen\nneeded. On Windows the equivalent Go command can be run directly with\n`build/dora.exe`\n\nas the output path when `make`\n\nis unavailable.\n\nTo build a release binary and install it into `$(PREFIX)/bin/dora`\n\n(default\n`$HOME/.local/bin/dora`\n\n), use:\n\n```\nmake install\n```\n\n`install`\n\ndepends on `release`\n\nand creates `$(PREFIX)/bin`\n\nif needed. Override\n`PREFIX`\n\nto choose another location, for example `make install PREFIX=/usr/local`\n\n.\n\nDora 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:\n\n| Provider | Environment variable | Default model |\n|---|---|---|\n| openai | `OPENAI_API_KEY` |\n`gpt-5` |\n| deepseek | `DEEPSEEK_API_KEY` |\n`deepseek-v4-flash` |\n| trust | `TRUST_API_KEY` |\n`auto` |\n\nSet the environment variable for the provider you want to use. The commands differ by operating system.\n\nmacOS / Linux, temporary (current terminal only):\n\n```\nexport OPENAI_API_KEY=\"sk-...\"\n```\n\nmacOS / Linux, permanent: append the `export`\n\nline above to `~/.zshrc`\n\n(zsh)\nor `~/.bashrc`\n\n(bash), then reload it:\n\n```\nsource ~/.zshrc\n```\n\nWindows PowerShell, temporary (current session only):\n\n```\n$env:OPENAI_API_KEY = \"sk-...\"\n```\n\nWindows PowerShell, permanent (persists for the current user):\n\n```\n[Environment]::SetEnvironmentVariable(\"OPENAI_API_KEY\", \"sk-...\", \"User\")\n```\n\nWindows CMD, temporary (current session only):\n\n```\nset OPENAI_API_KEY=sk-...\n```\n\nWhen you set keys for more than one provider, specify `model.provider`\n\nexplicitly in `~/.dora/config.yaml`\n\n; otherwise Dora reports an ambiguity\nerror. Setting exactly one key lets Dora select that provider automatically.\n\nWith exactly one supported provider API key set, Dora runs without a\nconfiguration file and selects that provider automatically. For example,\n`DEEPSEEK_API_KEY`\n\nselects `deepseek`\n\n, `OPENAI_API_KEY`\n\nselects `openai`\n\n, and\n`TRUST_API_KEY`\n\nselects `trust`\n\n. If multiple provider keys are set, configure\n`model.provider`\n\nexplicitly. If none are set, Dora retains `deepseek`\n\nas the\nfallback and reports that `DEEPSEEK_API_KEY`\n\nis missing.\n\nTo customize the defaults, create `~/.dora/config.yaml`\n\n. Dora uses this\n`~/.dora/`\n\nlayout on every operating system, including macOS. Set the\n`DORA_HOME`\n\nenvironment variable to an absolute path to override the home\ndirectory. You can also place the file anywhere and pass\n`--config path/to/config.yaml`\n\n; an explicitly requested file must exist.\n\n```\nmodel:\n  provider: deepseek\n```\n\nAn explicit provider always takes precedence over environment-based selection.\nThe same automatic selection applies when a configuration file exists but\nomits `model.provider`\n\n.\n\nThe `deepseek`\n\npreset defaults to the `chat_completions`\n\nAPI,\n`deepseek-v4-flash`\n\n, `https://api.deepseek.com`\n\n, and `DEEPSEEK_API_KEY`\n\n. The\n`openai`\n\npreset defaults to `chat_completions`\n\n, `gpt-5`\n\n,\n`https://api.openai.com/v1`\n\n, and `OPENAI_API_KEY`\n\n. The `trust`\n\npreset defaults\nto `chat_completions`\n\n, `auto`\n\n, `https://api.trustoken.cn/v1`\n\n, and\n`TRUST_API_KEY`\n\n. Override any preset field when needed, and set\n`api: responses`\n\nto use the Responses API. Both APIs always use SSE streaming.\nResponses tool loops replay typed output items locally and do not depend on\nserver-side response storage.\n\nTo use any third-party provider that speaks the OpenAI Chat Completions\nprotocol (for example Ollama, LM Studio, vLLM, Groq, Together, OpenRouter, or\na self-hosted endpoint), keep `model.provider: openai`\n\nand override `base_url`\n\n,\n`name`\n\n, and `api_key_env`\n\n(or `api_key`\n\n). The Chat Completions endpoint is\n`base_url + \"/chat/completions\"`\n\n, so `base_url`\n\nshould be the provider's `/v1`\n\n(or equivalent) root.\n\nFor a self-hosted Ollama endpoint that requires no authentication, set\n`api_key_env: \"\"`\n\nto disable the API key:\n\n```\nmodel:\n  provider: openai\n  name: llama3.1\n  base_url: http://localhost:11434/v1\n  api_key_env: \"\"\n```\n\nFor a hosted OpenAI-compatible service that requires a key, such as OpenRouter\nor Groq, point `api_key_env`\n\nat a custom environment variable:\n\n```\nmodel:\n  provider: openai\n  name: openrouter/auto\n  base_url: https://openrouter.ai/api/v1\n  api_key_env: OPENROUTER_API_KEY\n```\n\nFor a one-off invocation, override the model and base URL on the command line:\n\n```\n./dora --model llama3.1 --base-url http://localhost:11434/v1 \"prompt\"\n```\n\nLiteral `api_key`\n\nis also supported, but an environment variable keeps secrets\nout of the configuration file. A non-empty literal key takes precedence over\n`api_key_env`\n\n. Set `api_key_env: \"\"`\n\nexplicitly for a local endpoint that does\nnot require authentication.\n\nDora runs up to 64 model-tool rounds per segment by default. Keep the safeguard but adjust it for unusually long tool workflows when needed:\n\n```\nagent:\n  max_rounds: 96\n```\n\nOverride it for one invocation with `--max-rounds`\n\n:\n\n```\n./dora --max-rounds 96 \"Complete a long task\"\n```\n\nWhen the limit is reached with both stdin and stderr attached to a terminal,\nDora asks whether to continue for another segment. Confirming resumes from the\ncompleted tool output without replaying work. Declining stops normally and\nsaves the partial state of a named session. With piped or redirected I/O, Dora\ndoes not prompt and returns `dora: maximum rounds exceeded`\n\ninstead.\n\nRun a one-shot prompt or combine an instruction with piped input:\n\n```\nexport DEEPSEEK_API_KEY=\"...\"\n./dora \"Explain this repository\"\ngit diff | ./dora \"Review this change\"\n```\n\nProgress is shown on stderr with a small Dora personality, while the final\nanswer remains on stdout. Use `--quiet`\n\nor `-q`\n\nwhen only the answer is wanted:\n\n```\n./dora --quiet \"Explain this repository\"\n```\n\nWhen stdout is a terminal, Dora prints the final answer as plain text. Redirected or piped stdout is identical, preserving stable output for scripts:\n\n```\n./dora \"Write release notes\"\n./dora \"Write release notes\" > release-notes.md\n```\n\nColors are enabled automatically for terminal output. Set `NO_COLOR=1`\n\nto keep\nthe layout without ANSI colors; progress remains visible on stderr.\n\nUse a session name to continue the same conversation across CLI invocations:\n\n```\n./dora -s system-status \"Analyze this machine's system status\"\n./dora -s system-status \"Continue with the busiest processes\"\n```\n\nStart over under the same session name with `--fresh`\n\n. Existing history is\nignored for this run and replaced only after the new task succeeds; if the run\nfails, the previous session remains intact:\n\n```\n./dora -s system-status --fresh \"Analyze this machine from scratch\"\n```\n\nSession names may contain letters, numbers, `.`\n\n, `_`\n\n, and `-`\n\n. Dora stores each\nsession as a versioned JSON snapshot with `0600`\n\npermissions. Session v3 binds\nthe configured provider, API, model, and base URL: Chat Completions resumes\nfrom messages, while Responses additionally persists its opaque typed-item\ncontinuation. Use `--fresh`\n\nbefore changing a session's backend. Version 1 and\n2 session files are not supported. The default directory is\n`~/.dora/sessions`\n\non every operating system. Omit `--session`\n\n/`-s`\n\nto keep\nthe existing stateless behavior. Session files can contain commands and tool\noutput, so treat them as sensitive. Do not run two Dora processes against the\nsame session name concurrently.\n\nUse `--config`\n\n, `--model`\n\n, `--base-url`\n\n, `--max-rounds`\n\n, `--skills-dir`\n\n, or\n`--no-skills`\n\nto override the corresponding configuration for one invocation.\n\nSkills are local instruction packages loaded by the model only when relevant.\nEach skill is a directory containing a `SKILL.md`\n\nwith strict YAML front\nmatter:\n\n```\nskills/\n└── system-status/\n    └── SKILL.md\n---\nname: system-status\ndescription: Analyze CPU, memory, disk, and busy processes.\n---\n\n# System status\n\nInspect the machine methodically and summarize actionable findings.\n```\n\nBy default, Dora discovers the `skills`\n\ndirectory at `~/.dora/skills`\n\n(or\n`DORA_HOME/skills`\n\n), independent of the active `config.yaml`\n\npath. No\nconfiguration is needed.\n\nUse `skills.directories`\n\nonly to add more parent directories:\n\n```\nskills:\n  directories:\n    - /absolute/path/to/additional-skills\n```\n\nFor a one-off run, add one or more parent directories on the command line:\n\n```\ndora --skills-dir ./project-skills --skills-dir ~/shared-skills \"Run checks\"\n```\n\nCommand-line directories are merged with the default and configured\ndirectories, converted to absolute paths, and deduplicated. Use `--no-skills`\n\nto disable every skill source for one invocation; it takes precedence over\nboth `--skills-dir`\n\nand `skills.directories`\n\n.\n\nDora advertises only skill names and descriptions in the `skill`\n\ntool schema.\nThe absolute skill directory and complete `SKILL.md`\n\nare returned when the\nmodel calls that tool, allowing instructions to reference files such as\n`scripts/check.sh`\n\n. The skill tool never executes those files; execution still\nrequires an enabled tool such as Bash. Names must contain lowercase letters,\nnumbers, and hyphens, and must match their directory name. Duplicate names are\nrejected. A missing or empty default directory simply leaves the tool disabled;\nmalformed discovered skills and missing explicitly configured or command-line\ndirectories are errors.\n\nDora does not automatically read or migrate the previous macOS directory. Move existing files manually before running the new version:\n\n```\nmkdir -p \"$HOME/.dora\"\nmv \"$HOME/Library/Application Support/dora/config.yaml\" \"$HOME/.dora/config.yaml\"\nmv \"$HOME/Library/Application Support/dora/skills\" \"$HOME/.dora/skills\"\nmv \"$HOME/Library/Application Support/dora/sessions\" \"$HOME/.dora/sessions\"\n```\n\nSkip any `mv`\n\ncommand whose source does not exist. If `DORA_HOME`\n\nis set, use\nits directory instead of the fallback destination shown above.\n\nPushing a semantic version tag runs the release workflow:\n\n```\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\nThe workflow runs the full validation, renders installers pinned to the tag,\nand uses GoReleaser to publish static archives for Linux, macOS, and Windows on\namd64 and arm64. It also publishes `checksums.txt`\n\n; public repositories receive\nGitHub build provenance attestations. Tags that do not match semantic version\nsyntax fail before publishing.\n\nCommand tools use platform-aware automatic defaults: Bash is enabled on Linux\nand macOS, while PowerShell is enabled on Windows. The other command tool is\ndisabled even if its executable is on `PATH`\n\n. Omit `enabled`\n\nto use that policy,\nor set it explicitly to override the platform default:\n\n```\ntools:\n  bash:\n    enabled: false\n    timeout_seconds: 30\n  powershell:\n    enabled: true\n    timeout_seconds: 30\n```\n\nAutomatic tools whose executable is absent are skipped. A tool explicitly\nenabled with `enabled: true`\n\nmust exist on `PATH`\n\n, otherwise Dora reports an\nerror. Discovery currently checks executable presence only; it does not launch\nthe shell to probe its runtime environment.\n\nThe Bash tool runs `bash -lc`\n\nin Dora's current directory. The model can use\n`cd`\n\ninside a command when it needs another directory. The tool returns exit\ncode, stdout, stderr, timeout, and truncation information to the model as JSON.\nOutput is limited to 1 MiB per stream. This tool grants the model the same\nfilesystem and process permissions as the `dora`\n\nprocess, so disable it unless\nyou trust the environment in which Dora runs.\n\nThe independent `powershell`\n\ntool prefers PowerShell Core (`pwsh`\n\n) and falls\nback to Windows PowerShell (`powershell.exe`\n\n). If both tools are explicitly\nenabled, they are exposed separately so their command syntaxes remain distinct.\n\nPowerShell also starts in Dora's current directory and can use `Set-Location`\n\ninside a command when needed.\n\nBoth command tools accept an optional per-command timeout. It overrides the configured default for that call and cannot exceed 3600 seconds:\n\n```\n{\n  \"command\": \"go build ./...\",\n  \"timeout_seconds\": 300\n}\n```\n\nWhen omitted, `timeout_seconds`\n\ncomes from the corresponding YAML tool setting,\nor defaults to 30 seconds when that setting is zero or absent.", "url": "https://wpnews.pro/news/show-hn-dora-a-tiny-llm-agent-with-only-bash-tool", "canonical_source": "https://github.com/lgxz/dora", "published_at": "2026-08-11 08:03:05+00:00", "updated_at": "2026-08-11 08:10:46.091154+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "developer-tools"], "entities": ["Dora", "GitHub", "Go"], "alternates": {"html": "https://wpnews.pro/news/show-hn-dora-a-tiny-llm-agent-with-only-bash-tool", "markdown": "https://wpnews.pro/news/show-hn-dora-a-tiny-llm-agent-with-only-bash-tool.md", "text": "https://wpnews.pro/news/show-hn-dora-a-tiny-llm-agent-with-only-bash-tool.txt", "jsonld": "https://wpnews.pro/news/show-hn-dora-a-tiny-llm-agent-with-only-bash-tool.jsonld"}}