dora is a tiny, modular LLM agent kernel for Go. Its core is one loop and
two interfaces: Model and Tool.
No configuration file is required for the built-in providers: set the selected
provider's API key (for example DEEPSEEK_API_KEY), optionally select a model
with the per-capability policy setting (for example
DORA_POLICY_TEXT_PROVIDER=deepseek), and run immediately.
See docs/architecture.md for module boundaries,
dependencies, interfaces, and runtime flows.
DoraBar is a companion macOS tray/menu-bar app for dora, written in Swift. It provides a lightweight menu-bar interface for interacting with the dora CLI.
Benchmark Results
dora is evaluated on terminal-bench-2.1(89 tasks). The table below tracks the mean pass rate across evaluation runs as dora evolves:
deepseek-v4-flash (thinging disabled)
| Run | Date | Mean | Passed | Notes |
|---|---|---|---|---|
| 1 | 2026-08-12 | 0.573 | 51/89 | Baseline |
| 2 | 2026-08-13 | 0.483 | 43/89 | Rate-limit affected |
| 3 | 2026-08-13 | 0.596 | 53/89 | |
| 4 | 2026-08-14 | 0.607 | 54/89 | Background job support |
| 5 | 2026-08-14 | 0.618 | 55/89 | System prompt |
| 6 | 2026-08-14 | 0.685 | 61/89 | read/write/edit tools |
deepseek-v4-pro (thinging: high)
| Run | Date | Mean | Notes |
|---|---|---|---|
| 1 | 2026-08-28 | 0.787 |
https://hub.harborframework.com/jobs/747f9a67-ed3a-498b-a70e-6786a70eb8a5
Scope
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 multiple tool calls in one response
are executed concurrently (results are emitted in the returned order). There is
no built-in memory, policy engine, middleware, or provider SDK.
CLI
Install
On macOS or Linux, install the latest release with curl:
curl -LsSf https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | shUse wget when curl is unavailable:
wget -qO- https://github.com/lgxz/dora/releases/latest/download/dora-installer.sh | shOn 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 shInstall 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 | shRun dora --version to inspect the installed version, source commit, and build
date. Standalone releases that include self-update support can update themselves:
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:
Usage:
cat notes.md | dora Summarize the following content | mdcat
Build from source
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:
The binary is written to build/dora (build/dora.exe on Windows). For a
smaller distribution binary without symbol and DWARF debug data, use:
The release target uses -trimpath, strips debug data, and embeds the version,
commit, and build date, writing to dist/dora (dist/dora.exe on Windows).
Override VERSION, COMMIT, or BUILD_DATE when needed. On Windows the
equivalent Go command can be run directly with dist/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:
install depends on release and creates $(PREFIX)/bin if needed. Override
PREFIX to choose another location, for example make install PREFIX=/usr/local.
API keys
Dora derives an API key environment variable from each provider name: uppercase
the name, replace non-alphanumeric characters with _, and append _API_KEY.
For example, deepseek, trust, and open-router use
DEEPSEEK_API_KEY, TRUST_API_KEY, and OPEN_ROUTER_API_KEY. A non-empty
process value overrides the same key under config env. Config values are
internal fallbacks and are never exported to child processes.
Model selection is driven by per-capability policy, not by a single model
selector. Each capability (text and image) has an optional {provider, profile} pair; either field left empty falls back to automatic (auto)
selection. The environment override for a policy field is
DORA_POLICY_<CAPABILITY>_<FIELD> (for example DORA_POLICY_TEXT_PROVIDER,
DORA_POLICY_TEXT_PROFILE, DORA_POLICY_IMAGE_PROVIDER,
DORA_POLICY_IMAGE_PROFILE); environment values take precedence over the
config file.
For an interactive first-time setup, run:
The setup flow selects one of Dora's built-in providers, stores its API key
under the config-local env mapping, and sets policy.text.provider. Selecting
a concrete model profile is optional; pressing Enter leaves profile selection
automatic. Existing configuration fields and comments are preserved. The
configuration is written to ~/.dora/config.yaml (or the path supplied with
--config) with owner-only file permissions on Unix. Process environment
variables continue to take precedence over stored values.
macOS / Linux, temporary (current terminal only):
export TRUST_API_KEY="sk-..." export DORA_POLICY_TEXT_PROVIDER="trust" export DORA_POLICY_TEXT_PROFILE="deepseek-v4-flash"
macOS / Linux, permanent: append the export lines above to ~/.zshrc (zsh)
or ~/.bashrc (bash), then reload it:
Windows PowerShell, temporary (current session only):
$env:TRUST_API_KEY = "sk-..." $env:DORA_POLICY_TEXT_PROVIDER = "trust" $env:DORA_POLICY_TEXT_PROFILE = "deepseek-v4-flash"
Windows PowerShell, permanent (persists for the current user):
[Environment]::SetEnvironmentVariable("TRUST_API_KEY", "sk-...", "User") [Environment]::SetEnvironmentVariable("DORA_POLICY_TEXT_PROVIDER", "trust", "User") [Environment]::SetEnvironmentVariable("DORA_POLICY_TEXT_PROFILE", "deepseek-v4-flash", "User")
Windows CMD, temporary (current session only):
set TRUST_API_KEY=sk-... set DORA_POLICY_TEXT_PROVIDER=trust set DORA_POLICY_TEXT_PROFILE=deepseek-v4-flash
When a policy field is left to auto, Dora walks the catalog in order —
providers in the order they are listed, then that provider's profiles in the
order they are listed — and selects the first entry that both has a non-empty
API key (that is, is usable) and satisfies the capability constraint. Catalog
order is therefore priority. A provider with no API key is treated as
unavailable and is skipped during selection. Local endpoints that do not
require authentication (such as Ollama) still need a non-empty placeholder API
key to be selectable. With no configuration file and no keys, Dora retains the
built-in DeepSeek default.
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.
env: DEEPSEEK_API_KEY: sk-...
This minimal file uses the embedded DeepSeek catalog and auto-selects the first
available text model. Replacing the key with TRUST_API_KEY selects Trust.
When both keys are configured, set policy.text explicitly:
env: DEEPSEEK_API_KEY: sk-deepseek... TRUST_API_KEY: sk-trust... policy: text: provider: trust profile: deepseek-v4-flash
The embedded provider catalog supplies built-in deepseek, trust, and openrouter
definitions with base_url, so catalog entries with those names may omit that
field. Models are always listed explicitly in providers[].profiles. Each
entry's name is a unique profile name used by policy.*.profile; model is
the identifier sent to the provider. model defaults to name when omitted,
and multiple profiles may use the same model with different parameters. Each
profile declares its capabilities with capabilities, for example
capabilities: [text] or capabilities: [text, image_input]. Set
provider-level or model-level 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.
Setting OPENROUTER_API_KEY makes the built-in openrouter/auto profile
available for automatic text and image selection. Catalog order remains
DeepSeek, Trust, then OpenRouter, so an available earlier provider still wins.
Select it explicitly with -m openrouter/auto or policy when desired. The
built-in openrouter/ox-alpha profile targets stealth/ox-alpha directly.
Third-party OpenAI-compatible providers
To use any third-party provider that speaks the OpenAI Chat Completions
protocol (for example Ollama, LM Studio, vLLM, Groq, Together, or
a self-hosted endpoint), add it to providers with base_url and profiles. 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, leave
OLLAMA_API_KEY unset:
providers: - name: ollama base_url: http://localhost:11434/v1 profiles: - name: llama3.1
To replace OpenRouter's built-in auto profile with an explicit catalog, list
the provider in the config; user profile lists replace the built-in profiles:
providers: - name: openrouter base_url: https://openrouter.ai/api/v1 profiles: - name: balanced model: openrouter/auto env: OPENROUTER_API_KEY: sk-or-v1-...
Even a local endpoint that does not require authentication needs a non-empty API key (any placeholder value works) to be selectable. Provider names that normalize to the same environment variable are rejected. Config files containing keys are secrets and should be protected accordingly.
Control the normal per-response output budget, hard model output capacity, and
sampling with max_tokens, max_output_tokens, and temperature:
providers: - name: openrouter base_url: https://openrouter.ai/api/v1 profiles: - name: balanced model: openrouter/auto max_tokens: 32768 max_output_tokens: 65536 temperature: 0.7 policy: text: provider: openrouter profile: balanced
max_tokens caps the number of tokens the model generates in one response and
defaults to 32768. It is sent on the wire as max_tokens for the
chat_completions API and as max_output_tokens for the responses API; an
explicit 0 means "no explicit cap" and is relayed as-is. temperature has no
default: when it is omitted, no value is sent and the provider uses its default
sampling. It accepts values in [0, 2]. Because some reasoning and
tool-calling models ignore or reject non-default temperatures, treat
temperature as best-effort. max_output_tokens is an optional positive model
capability with no default. It clamps both max_tokens and a request-specific
output limit. These keys are model catalog settings; there are no command-line
flags for them.
context_window belongs to a model profile and records that model's context
capacity in tokens. It defaults to 1048576, and configured values must be
positive. When a provider reports usage, Dora bases the next tool round on the
previous call's exact total_tokens plus an estimate for tool results produced
after that response. Without reported usage, Dora estimates the whole message
history and tool schemas. The provider-neutral estimate treats about four
ASCII bytes or one non-ASCII rune as a token and includes a small per-message
framing allowance; it does not estimate vision tokens. When predicted usage,
including an output reserve, reaches 80% of the context window, Dora asks the
active model to replace the model-visible history with a semantic summary of
at most 20% of the window, further capped by the profile's optional
max_output_tokens. That effective target is sent as a request-specific output
limit, overriding the ordinary max_tokens value so a provider's smaller
default cannot silently constrain compaction. The summary call has no tools or
continuation. A non-empty summary returned at the output limit is accepted. The
complete Turn remains unchanged for persistence, and a failed summary never
falls back to deleting or locally truncating history.
thinking controls the model's "thinking mode" reasoning effort. Set it to one
of off, minimal, low, medium, high, xhigh, or max. It has no default: when
omitted, no value is sent and the provider uses its own reasoning default.
Support varies by provider, and unsupported values are silently ignored rather
than causing an error:
- openai: on the Responses API all values are sent (
offbecomesnone); on Chat Completionsminimal–maxare sent asreasoning_effort, butoffis not supported (gpt-5's floor isminimal) and is ignored. - deepseek:
lowthroughmaxare sent on both APIs,minimalis not supported and is ignored, andoffis sent asthinking.type: disabledon Chat Completions orreasoning.effort: noneon Responses. - trust: treated best-effort like OpenAI on both APIs.
- openrouter:
minimal–maxare sent asreasoning_efforton Chat Completions, andoffis sent asreasoning_effort: none.
Because a setting may simply be dropped, treat thinking as a hint rather
than a guarantee. For one invocation, --thinking overrides the selected
model's setting with one of off, minimal, low, medium, high, xhigh, or max:
./dora --thinking high "Solve a hard problem"preserve_thinking is a per-profile switch (default off) for reasoning models
on Chat Completions. When true, reasoning captured from earlier tool-calling
rounds is resent on those assistant history messages. Plain reasoning uses
reasoning_content; structured reasoning_details are kept in order without
semantic modification in the provider continuation and take precedence when
present. DeepSeek and OpenRouter's built-in profiles enable it. Providers that
ignore or reject resent reasoning (or expect it stripped, like Qwen/DashScope)
keep it off.
For one invocation, -m/--model overrides the configured conversation model
as PROVIDER/PROFILE, selecting a provider and an optional profile by name. A
bare PROVIDER (or trailing slash) selects that provider's default profile (the
first profile matching the requested capabilities). It does not change the
configuration file:
./dora -m trust/deepseek-v4-flash "Explain this repository" ./dora -m deepseek "Explain this repository"
-m only affects the text conversation model; the image and skill models are
unaffected. If the name does not match a profile under that provider, it is used
as a model ID without prior registration. Only the first / separates the
provider, so -m openrouter/vendor/model-name sends vendor/model-name.
The transient text profile uses the provider's connection and API settings,
max_tokens: 32768, and context_window: 1048576; it does not inherit another
profile's parameters. --thinking still applies.
Existing profiles always take precedence, including their capability restrictions.
Unknown providers, missing API keys, and incompatible existing profiles still
result in router: no model satisfies the constraints. Unknown model IDs
(including misspelled profile names) are left for the provider to reject.
This fallback applies only to -m/--model; policy fields and their environment
overrides continue to require configured profiles.
Dora runs up to 256 model-tool rounds per segment by default. Keep the safeguard but adjust it for unusually long tool workflows when needed:
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 without
persisting the incomplete turn. With piped or redirected I/O, Dora does not
prompt and returns dora: maximum rounds exceeded instead.
Every Agent has an immutable system prompt. The binary ships a built-in default
(working habits such as verifying results against the literal request before
declaring a task complete); a non-empty agent.system_prompt fully replaces
it:
agent: system_prompt: | You are a terminal assistant for the Acme deploy team. Always prefer the deploy scripts under /opt/acme.
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
Progress colors use --color=auto by default: they are enabled when stderr is
a terminal and NO_COLOR is unset. Use --color=always to preserve ANSI color
when stderr is redirected, or --color=never to disable it. An explicit color
mode overrides automatic terminal and environment detection; progress remains
visible on stderr in every mode. Before execution, progress identifies the
resolved conversation model as Model PROVIDER/PROFILE · thinking=VALUE; these
are the effective settings after policy, automatic availability filtering, and
CLI overrides. thinking=default means no thinking value is explicitly sent
to the provider. --quiet suppresses this line with the rest of the progress
output.
Reasoning models stream their chain-of-thought before the final answer. Dora
hides it by default because streaming it to the terminal slows runs on slow
terminals; pass --reasoning to show it live in a dim style in place of the
"Thinking..." placeholder. The final answer still goes to stdout on its own
line, and --quiet suppresses the reasoning display along with all other
progress.
When a provider reports token usage, Dora captures it for every completed model
round, including cached and reasoning-token details when available. The
terminal renderer does not print usage. Per-round usage and the final response
usage are saved in the active SQLite session and returned by the history tool;
without --session, that database is in memory and disappears when Dora exits.
Use --workdir to choose the base directory for relative paths used by tools:
./dora --workdir /path/to/project "Run the tests and inspect failures"The directory must already exist. Dora resolves it to an absolute path without
changing the process working directory, so concurrent Agent runs can use
different bases safely. It applies to command tools and to relative paths used
by read, write, edit, grep, glob, and view_image. Absolute tool
paths are unchanged. Configuration and --session paths continue to be
resolved from the process working directory. --workdir selects a path
reference, not a filesystem sandbox or an additional permission boundary.
Sessions
Pass a SQLite file to retain turns across CLI invocations:
./dora -s ./system-status.sqlite "Analyze this machine's system status" ./dora -s ./system-status.sqlite "Continue with the busiest processes"
Every invocation is a fresh, independent turn. Previous messages are never
loaded into the model context automatically. Dora always adds a history tool,
including before the first turn: the model can
list turns, see each turn's status, error, round count, and final-response usage,
and get chronological round pages using turn_id, offset, and limit; an
empty database returns an empty list. A round is one assistant tool-call message
plus all corresponding tool result messages and that model call's optional
usage. Successfully completed turns are appended atomically. A turn
stopped by the maximum-round limit is also saved with status max_rounds, its
error, and all completed tool rounds; it has no final result or final-response
usage. Ctrl+C cancellation is saved with status canceled; Dora uses a separate
short-lived commit context so canceling the run does not also cancel its session
write. Any other failed turn is saved with status failed. Both retain their
error and only the tool rounds that completed before termination; partial
streamed model output is not saved. Confirming the interactive continuation
prompt keeps using the same Turn and does not save an intermediate max_rounds
record. Provider continuation is kept only while that turn runs.
With --session, SQLite schema version 6 contains turns and messages tables and records the
turn status and error, system prompt, user input, final result, intermediate
tool rounds, reasoning captured on round assistant messages, and each model
call's usage JSON. Newly created files use 0600 permissions. The old named
JSON session format, --fresh, and automatic migration are not supported
(schema version 5 and earlier databases, plus development v6 files whose status
constraint predates canceled, are rejected; start a new file). When
--session/-s is omitted, Dora uses an in-memory SQLite database for the
process lifetime. This allows long-running modes to retain earlier turns while
keeping ordinary CLI invocations ephemeral. Session databases can contain
commands, tool output, and token usage, so treat persistent files as sensitive.
Use --config, -m/--model, --thinking, --max-rounds, or --no-skills to override the
corresponding configuration for one invocation.
Agent Client Protocol
Run Dora as an ACP v1 agent over stdin/stdout:
An ACP client launches that command and exchanges newline-delimited JSON-RPC
on its standard streams. Configuration, model selection, --thinking,
--max-rounds, and --no-skills work as in the CLI. Protocol output owns
stdout; diagnostics remain on stderr.
Each session/new creates an isolated Agent, job manager, and in-memory SQLite
history store. The absolute cwd supplied by the client becomes that session's
tool working directory. Repeated session/prompt calls create independent Dora
Turns whose earlier results remain available through the history tool. Sessions
can run concurrently, while a single session rejects overlapping prompts.
The initial ACP surface supports initialization, session creation, text and
resource-link prompts, streamed answer and thought chunks, tool-call lifecycle
updates, cancellation, and session close. Reaching Dora's maximum round count
returns the ACP max_turn_requests stop reason. Closing a session cancels its
active prompt and background jobs and discards its in-memory history.
Clients that advertise terminal authentication receive a Configure Dora
method which launches dora --setup. Authentication remains local: the setup
flow configures the selected model provider and the ACP connection never
receives the API key. Dora can complete initialize without a configured
model; session/new returns Authentication required until setup is complete
and the client reconnects.
This first version does not support protocol-driven authentication/logout,
persistent session listing/resume/load, prompt images/audio/embedded resources,
modes, config options, client filesystem/terminal delegation, or MCP servers
supplied by the client. Non-empty mcpServers and additionalDirectories are rejected.
--acp cannot be combined with a prompt, --session, --workdir, or
--events; ACP supplies its own prompts, sessions, and working directories.
Skills
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 skills in ~/.dora/skills (or DORA_HOME/skills)
followed by ~/.agents/skills/, independent of the active config.yaml path.
Each default directory is included only if it exists as a directory (an absent
default is silently skipped). No configuration is needed.
Use skills.directories to replace the defaults with a specific set of parent
directories:
skills: directories: - /absolute/path/to/additional-skills
Each configured path must be an absolute path or start with ~/. Relative
paths are rejected. Configured directories are converted to their absolute
form and deduplicated; they are used exactly as listed and are not merged with
the defaults. Use --no-skills to disable every skill source for one
invocation; it takes precedence over 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.
Releasing
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
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 powershell: enabled: true task: enabled: false
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 the directory selected by --workdir, or in
Dora's process working directory when the option is omitted. The model can use
cd inside a command when it needs another directory. The tool returns exit
code, stdout, and stderr to the model as JSON. 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 uses the same working-directory rule and can use Set-Location
inside a command when needed.
Both command tools use a single wait_seconds knob. It is the maximum number
of seconds to wait for the command to finish before moving it to the
background; once in the background it continues running (it is not terminated)
and Dora returns a job_id that the job tool can use to inspect it. Background
processes are also not terminated when Dora itself exits — they keep running
after the session ends, so stop them explicitly with the job tool's kill action
when needed. On Unix, kill terminates the command's process group; on Windows,
it terminates the process tree. This covers ordinary descendants such as
timeout 240 python3 .... Stdout and stderr are each capped at 32 KiB per
result (head and tail kept, with a marker naming the original size) in command
results, background-adoption results, and job polls; redirect larger output to
a file to inspect it. By default (omitted) Dora waits 10 seconds, and a
value of 0 moves the command to the background immediately:
{
"command": "go build ./...",
"wait_seconds": 300
}Command job IDs are prefixed by the source tool and counted independently, for
example bash_0 and powershell_0. The public job results do not include a
separate job-kind field; the ID identifies the source.
Independent tasks
The task tool is enabled by default. It accepts one self-contained
instruction, runs it through the same Agent and model in a fresh Turn, and
returns that turn's final text to the parent conversation. The fresh turn does
not inherit the parent's messages or provider continuation. It uses the same
immutable Agent system prompt and can use every other
tool available to the parent, but task itself is removed from both the tool
definitions and executable tool set to prevent recursive delegation.
Multiple task calls in one model response run concurrently, following Dora's normal tool execution semantics. There is no separate task concurrency limit. Child progress is not sent to the terminal Observer; only the parent message and the completed task summary and duration are rendered.
Set background to true when the parent should continue immediately instead
of waiting for the independent turn:
{
"instruction": "Run the full test suite and summarize failures",
"background": true
}The result immediately contains a task_N job ID. Use the same job tool to
list, poll, or kill it; job.poll.wait_seconds controls how long that poll
waits, while Task itself has no duration estimate. A completed Task result is
retained for repeated polls. Unlike background shell processes, background
Tasks run inside Dora and stop—with their uncollected result lost—when the Dora
process exits. Their nested progress remains hidden, including while they run
in the background. Killing a Task cancels its context cooperatively. The job
reports cancelling until the nested run actually returns and only then reports
killed; a component that ignores context cancellation can therefore delay
termination.
Task context isolation is not a security sandbox. Parent and child runs share the process, current working directory, filesystem, permissions, model client, and concrete tool instances. Disable the tool when delegation is not wanted:
tools: task: enabled: false
Image understanding
Dora treats vision support as a model-profile capability. A profile that can
understand images declares capabilities: [text, image_input]; a text-only
model declares capabilities: [text]. There is no CLI override or direct
image-attachment flag.
When the model wants to view an image, it calls the view_image tool, which is
always registered. The tool accepts a local path or a remote url and routes
the image through a transient visual model selected with the image_input
capability constraint, returning a text description of the image. The image
itself never enters the main model's context — only the returned description
does.
Dora limits each local image file to 4 MiB and rejects files that are not
images. When a view_image call points at a missing, non-image, or oversized
file, the tool reports the error to the model so it can correct the path. If no
catalog entry advertises image_input, the view_image call reports that no
visual model is available.