The installer no longer prints a spurious "shadowing install" warning
when `~/.local/bin` appears on `PATH` under a non-normalized alias (such
as `~/.local/share/../bin`) that resolves to the same directory.
---
The `deepagents-code` installer printed a spurious "shadowing install"
warning on every run for users whose `PATH` contains a non-normalized
alias of `~/.local/bin` — most commonly `~/.local/share/../bin`, where
`share/..` collapses back to `.local`. Some third-party tools write that
un-normalized spelling into a shell profile (derived from
`$XDG_DATA_HOME/../bin`), and it can sort before the canonical entry.
Both spellings resolve to the same uv-tool symlink the installer just
created, but `detect_shadowing_install` compared `PATH` strings, so the
alias looked like a separate, older install.
The primary fix makes the guard compare by inode using bash's `-ef` test
(same device+inode, resolves `..` and symlinks) in addition to the
existing string equality, so same-file aliases short-circuit and never
warn. Both operands are guaranteed to exist at that point, and a
genuinely different binary at a distinct inode still fails `-ef` and
correctly warns, so only the false positive is suppressed. Secondarily,
`local_bin_in_profile` now also recognizes the `~/.local/share/../bin`
spelling so the installer doesn't append a duplicate `~/.local/bin` PATH
line; this covers the common alias only and isn't a full path
normalizer.
Made by [Open
SWE](https://openswe.vercel.app/agents/870c16ab-41c7-3eee-e0d1-55385367ec4f)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The installer's "already installed / up to date" message is now more
concise.
Before:
```
▸ deepagents-code 0.1.34 found — checking for updates...
✔ deepagents-code is already up to date.
```
After:
```
▸ dcode 0.1.34 found — checking for updates...
✔ Already up to date!
```
Made by [Open
SWE](https://openswe.vercel.app/agents/6b29a6dc-fc2b-12bc-52d1-98a3bed3785d)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
**deepagents-code installer (`install.sh`):**
- Accepts a positional version: `curl -LsSf https://langch.in/dcode |
bash -s -- 0.1.0rc1`
- Uses a managed, rewriteable PATH block in shell profiles (`# >>>
deepagents-code installer >>>`)
- Warns when an existing `dcode`/`deepagents-code` on `PATH` shadows the
uv tool install
- Shows actionable hints for signal kills (e.g., OOM on Linux: exit 137)
- Serializes concurrent installs with a cross-process lock
(`flock`/`mkdir` fallback)
- Accepts `DEEPAGENTS_CODE_YES=true|yes|TRUE| YES ` for non-interactive
installs
- Propagates uv's real exit code on failure
---
### User-facing changes
- **Positional version argument**: `curl ... | bash -s -- 0.1.0rc1` now
works alongside the existing `DEEPAGENTS_CODE_VERSION` env var
- **Managed PATH block**: Shell profiles now get a delimited `# >>>
deepagents-code installer >>>` block that the installer owns and
rewrites in place (instead of appending loose lines)
- **Shadowing detection**: Warns when an older `dcode`/`deepagents-code`
binary earlier on `PATH` would be used instead of the newly installed uv
tool
- **Signal-aware failure hints**: Exit codes ≥128 now surface actionable
messages (e.g., exit 137 on Linux → "ran out of memory, free up memory
or use a larger machine")
- **Concurrent install safety**: A cross-process lock (`flock`
preferred, `mkdir` fallback with stale-lock reclamation) prevents racing
`curl | bash` runs from corrupting the shared uv tool directory
### Internal hardening
- `DEEPAGENTS_CODE_YES` now accepts truthy values (`true`, `TRUE`,
`yes`, ` YES `)
- Installer propagates uv's real exit code instead of flattening to `1`
- Early up-to-date exit no longer creates a lock directory
- macOS path avoids `lockf` (command-scoped, not fd-based)
The `dcode` install script now supports `--help`/`-h` and
`--version`/`-v` flags, and the uv installer download is verified before
execution to guard against non-shell responses from proxies or captive
portals.
## Release note
- `curl -LsSf https://langch.in/dcode | bash -s -- --help` now prints a
full usage reference (all environment variables, valid extras, and a
docs link) and exits 0 without touching the network. `--version`/`-v`
prints the installer version string and exits 0. Unknown flags (e.g. a
typo like `--verison`) are rejected with a non-zero exit and an
actionable message instead of silently proceeding to a full install.
- The uv bootstrap no longer pipes the downloader directly into `sh`. It
downloads the installer to a tempfile first, verifies the first line is
a shell shebang, and only then executes it. A 200 response containing
HTML from a transparent proxy or captive portal is caught and rejected
with a clear error before it can reach `sh`.
- On a failed download, the downloader's own stderr (curl DNS errors,
TLS failures, HTTP status) is now surfaced to the user alongside the
generic failure message, instead of being discarded.
## Changes
- Added a CLI-flag loop at the top of `install.sh` that handles
`--help`/`-h` (prints a `print_help` reference of all env vars and valid
extras), `--version`/`-v` (prints `INSTALLER_VERSION`), and rejects
unknown flags with exit code 2 — all before any install work begins.
- Rewrote `install_uv` to download the uv installer to a tempfile via
`curl -o` / `wget -O` (capturing the downloader's stderr) instead of
piping directly to `sh`. On download failure, the captured stderr is
printed before the generic error. On success, the script's first line is
checked for a shell shebang (`^#!.*(sh|bash)`) before execution; a
non-shell response exits with an actionable message.
- Updated the `media` extra in the env-var documentation within the help
text (was previously listed only as `quickjs` under standalone
integrations).
- Added unit tests covering: `--help`/`-h` output and early exit,
`--version`/`-v` output and early exit, unknown-flag rejection, shebang
verification rejection (HTML response), download-failure error
surfacing, and the wget download path with verbose output.
The installer now actively fixes `dcode` PATH resolution instead of just
warning the user, detects snap-sandboxed `curl` that silently fails
downloads, and cleans up temp files on Ctrl-C without printing a
contradictory failure message.
## Changes
- **Automatic PATH setup** — `ensure_path_setup` symlinks the verified
binary into an existing PATH dir (`~/.local/bin`, `~/bin`, `~/.bin`); if
none are on PATH, creates `~/.local/bin`, symlinks there, and appends a
PATH export to the detected shell profile (`.zshrc`, `.bashrc`,
`.bash_profile`, `config.fish`). Prompts interactively before modifying
a profile, auto-adds in non-interactive mode, and skips entirely when
uv's `~/.local/bin/env` already handles PATH.
- **Snap `curl` detection** — `is_snap_curl` checks whether `curl` lives
under `/snap/`, in which case it has sandbox permission issues that
cause silent download failures. `install_uv` and the new
`download_to_stdout` helper fall back to `wget`, or emit a clear error
if no working downloader exists.
- **Interrupt-safe temp cleanup** — a `TEMP_FILES` registry
(`register_temp` / `cleanup_temp_files`) tracks every `mktemp` so
they're removed on both normal exit and Ctrl-C. A dedicated
`cleanup_on_interrupt` handler for INT/TERM disarms the EXIT trap first,
so the user sees only "Installation interrupted." rather than that
followed by a contradictory "Installation failed (exit code 1)."
- **`download_to_stdout` helper** — consolidates the duplicated
curl-or-wget logic from `install_uv` and `fetch_latest_version` into one
function with snap detection built in.
`dcode` install no longer prints routine ripgrep setup messages unless
verbose mode is enabled or ripgrep setup fails.
---
Routine ripgrep provisioning is an implementation detail of `dcode`
installation, so successful managed setup no longer adds extra status
lines to default installer output. Verbose installs and failure paths
still expose the setup command output for debugging.
Adds rubric-driven acceptance criteria to Deep Agents Code and layers a
goal workflow on top of it.
A rubric is the explicit definition of done. Use it when you already
know the criteria the agent should satisfy before it considers the work
complete. Criteria can be sticky for the thread, one-shot for the next
turn, or loaded from a file. While the agent works, the TUI shows
grading lifecycle messages so the user can see when the work is being
checked, revised, satisfied, or stopped.
Usage examples:
```text
/rubric set tests pass; no unrelated files changed; help text is updated
/rubric next only change the auth callback; do not refactor unrelated code
/rubric file acceptance.md
/rubric show
/rubric clear
```
A goal is the objective-oriented workflow. Use it when you know the
outcome, but want `dcode` to propose acceptance criteria before
execution. `/goal <objective>` asks the model to draft criteria and
displays them in an inline review prompt. The user can accept the
criteria, edit them directly, reject them with feedback to regenerate,
or cancel. Accepted goal criteria become the sticky rubric for the
thread.
Usage examples:
```text
/goal add OAuth refresh handling
/goal show
/goal clear
```
The `--goal` CLI flag starts the same goal-review workflow when
launching the TUI. It is intentionally interactive: the generated
criteria must be reviewed before execution. After the user accepts the
proposal, the accepted goal is sent as the first task.
```bash
dcode --goal "add OAuth refresh handling"
```
`--goal` cannot be combined with `-n`, `-m`, `--skill`, or `--rubric`.
For non-interactive/headless runs, users should provide criteria
explicitly with `--rubric` instead:
```bash
dcode -n "implement OAuth refresh handling" --rubric "tests pass; no unrelated files changed"
dcode -n "implement OAuth refresh handling" --rubric @acceptance.md
```
Accepted goal/rubric state is persisted on the thread and restored on
resume. For example, a user can set `/goal add OAuth refresh handling`,
accept the proposed criteria, quit the TUI, and later resume the same
thread with the goal and criteria still active. That matters because the
acceptance criteria continue to guide future turns and remain visible in
`/goal show` instead of becoming hidden context that disappears between
sessions.
When the agent believes the goal is done, it does not get to declare
victory on its own. For example, after implementing OAuth refresh
handling, the agent can ask to mark the goal complete with evidence such
as "tests pass." `dcode` keeps that request pending until the rubric
check finishes. If the rubric still needs revision, the goal stays
active and the user sees why it was not completed. If the rubric is
satisfied, auto-approve mode records the completion automatically;
manual mode asks the user before changing the goal status. This keeps
the user's accepted criteria as the source of truth for whether the goal
is actually finished.
The active criteria and goal are also visible to the agent through
constrained tools. `get_rubric` lets the agent inspect the current
criteria and whether they came from a goal, a sticky rubric, or the
current invocation. `get_goal` lets it inspect the active objective,
status, criteria, and any prior note. `update_goal` lets it report when
it believes the goal is `complete` or `blocked` with evidence. The
constraints matter: the agent can read criteria and update progress, but
it cannot create, pause, resume, clear, or replace goals. Those
lifecycle actions stay user/system controlled so the model cannot
silently redefine or remove the user's objective.
The install script now resolves an existing `uv` before invoking the
upstream installer, including installs that are present in
`~/.local/bin` but hidden by a minimal MDM or cron PATH. Bad `UV_BIN`
values fail fast with a clear error instead of falling through to a
network install attempt.
## Changes
- Added `resolve_uv_bin` to check `UV_BIN`, PATH, uv’s generated env
file, and `~/.local/bin/uv` before installing uv.
- Hardened env-file sourcing so stale or hand-written `~/.local/bin/env`
files with non-zero commands do not abort the installer.
- Tightened `UV_BIN` path validation so directories are rejected before
`uv tool install` runs.
- Added installer regression coverage for minimal-PATH uv detection,
defensive env-file sourcing, and invalid `UV_BIN` values.
Follow-up to fd0b17deb0, which changed the
installer to use `--prerelease if-necessary` but still allowed uv to
resolve `deepagents-code==0.1.22` instead of the latest release that
pins `deepagents==0.7.0a2`.
`if-necessary` is not enough here because uv can satisfy the top-level
`deepagents-code` requirement with an older stable candidate. The
installer now defaults to `--prerelease allow`, which lets the latest
stable `deepagents-code` release resolve even when it depends on a
prerelease SDK.
Stable `deepagents-code` releases can temporarily depend on pre-release
packages, which made the default installer path fail when uv refused
pre-release resolution. The installer now applies uv's `if-necessary`
strategy for unpinned installs while keeping exact version pins mutually
exclusive with explicitly requested pre-release settings.
## Changes
- Default unpinned installs to `--prerelease if-necessary`, but avoid
forwarding that default when `DEEPAGENTS_CODE_VERSION` selects an exact
package version.
- Keep the mutual-exclusion error for explicit
`DEEPAGENTS_CODE_PRERELEASE` plus `DEEPAGENTS_CODE_VERSION`, preserving
pinned install behavior.
- Filter uv download/build progress and newer timing summary variants
from non-verbose output while preserving them under
`DEEPAGENTS_CODE_VERBOSE=1`.
- Shorten the managed ripgrep setup message so the installer no longer
advertises the opt-out path during the normal setup flow.
Import-only checks were constructing the server graph, which let runtime
startup behavior leak into local validation and touch a developer’s real
dcode config. The server graph is now exposed as a LangGraph factory so
MCP discovery runs only when the server actually builds the graph, and
the import checker runs against an isolated home directory.
## Changes
- Switch the generated LangGraph reference from `server_graph.py:graph`
to `server_graph.py:make_graph`, relying on LangGraph’s factory support
instead of constructing the graph at module import time.
- Keep startup error marker behavior inside `make_graph()` so server
startup failures still surface cleanly to the parent process, while
plain imports remain side-effect free.
- Run `check_imports.py` with a temporary `HOME` so import validation
cannot read or depend on local `~/.deepagents` config, MCP auth tokens,
or other user state.
- Update server graph tests to assert MCP discovery does not happen on
import and still happens when `make_graph()` is invoked.
- Override `UV_FROZEN` only for the `uv lock --check` command so `make
check` performs the real lockfile freshness check without warning.
## Testing
- `make -C libs/code check PYTHON_FILES=
PYTEST_EXTRA="tests/unit_tests/test_server_graph.py
tests/unit_tests/test_server_manager.py -q" COV_ARGS=`
- `make -C libs/code check_imports`
- Focused ruff, ty, and pytest checks for the touched server
graph/server manager files
`dcode tools install` provisions the managed ripgrep binary; the install
script now sets it up by default (opt out with
`DEEPAGENTS_CODE_RIPGREP_INSTALLER=system` or
`DEEPAGENTS_CODE_OFFLINE=1`).
---
Today the pinned, SHA-256-verified ripgrep binary only lands on first
run (via `managed_tools.ensure_ripgrep`), so a fresh user pays a
one-time download the first time they launch. This makes the install
script provision it eagerly so `rg` is ready immediately, without
reaching for `sudo` package managers by default.
Rather than re-encoding the pinned version + checksum table in bash
(drift risk), a new `dcode tools install` verb reuses the existing
managed-install code, and `scripts/install.sh` invokes the freshly
installed binary. The verb is also handy on its own to repair a missing
or stale `rg`.
Power users can keep their own toolchain with
`DEEPAGENTS_CODE_RIPGREP_INSTALLER=system`, which preserves the existing
brew/apt/cargo path in the install script and skips the managed download
at runtime. A system `rg` already on `PATH` is reused under either
setting, and `DEEPAGENTS_CODE_OFFLINE` / `DEEPAGENTS_CODE_SKIP_OPTIONAL`
continue to apply. The default eager install is announced and honors
those opt-outs, per the package's "default shell-outs must announce +
offer a documented opt-out" rule.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The `libs/code` install script already prints the version on the
preceding `deepagents-code <ver> found — checking for updates...` line,
so repeating it on the `is already up to date` status line is redundant.
Simplified both the `log_info` (rebuild-with-extras) and `log_success`
(no-op exit) lines to `deepagents-code is already up to date`.
Made by [Open
SWE](https://openswe.vercel.app/agents/deff72b5-ac91-9dff-439e-934b65a8e99f)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The install script now hides the uv installer's output by default and
exits early on macOS when Xcode Command Line Tools are not installed.
---
The uv installer's verbose download/PATH output cluttered a fresh
`dcode` install; it's now hidden by default and shown only with
`DEEPAGENTS_CODE_VERBOSE=1` or on failure. On macOS, the installer now
fails fast with an actionable message when the Xcode Command Line Tools
are missing, instead of letting a downstream tool (uv interpreter
discovery / git) trigger the blocking macOS "install developer tools"
GUI popup mid-install.
## Test Plan
- [ ] On a fresh macOS without CLT, run the installer and confirm it
exits with the `xcode-select --install` message instead of showing the
GUI popup.
- [ ] On a machine without uv, run the installer and confirm the uv
installer's output is hidden unless `DEEPAGENTS_CODE_VERBOSE=1`.
Made by [Open
SWE](https://openswe.vercel.app/agents/c4c6c178-52dd-957a-68b7-dcba7f3223e0)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The installer now distinguishes a true no-op reinstall from a
same-version reinstall that still moves transitive packages. When `uv`
reports dependency changes, the final status and footer say dependencies
were updated, and the raw install output is saved so users can inspect
the full details after abbreviated terminal output.
New-contributor onboarding currently requires stitching knowledge
together from `AGENTS.md`, per-package `Makefile`s, and external docs,
and neither the cross-layer request flow nor the `libs/code` process
model is mapped anywhere in the repo.
This adds:
- Root `ARCHITECTURE.md` (the three-layer `deepagents` → `create_agent`
→ LangGraph stack and request flow) and `DEVELOPMENT.md` (a single
bootstrap + command reference), cross-linked from `README.md` and
`libs/README.md`.
- `libs/code/ARCHITECTURE.md` (process model, interactive/headless
request lifecycles, module map, and a "where do I change X" cheat
sheet), linked from `DEV.md` and `AGENTS.md`.
Made by [Open SWE](https://openswe.vercel.app)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The `dcode` install script printed every transitive dependency (~130
lines) on a fresh install, which is noise for the common `curl | bash`
path. The full list only ever appeared on fresh installs, since uv's
diff reports every package as newly added.
Now a fresh install suppresses the per-package list entirely — the user
still sees the `Installing…` line and the final `✔ deepagents-code
<version> installed.` / `Setup complete.` confirmation, just not the
wall of transitive dependencies. The full list stays available behind
the existing `DEEPAGENTS_CODE_VERBOSE=1` env var.
Upgrades are unaffected: they still show the compact changed-package
diff under `Updated packages:` (`→` for version bumps, `(new)` for added
packages, `(removed)` for dropped ones), which is small and genuinely
useful.
The two cases are told apart by whether uv's diff contains any removals:
a fresh install — or a pure-addition re-run, e.g. adding an extra to an
existing env — has only added rows and is suppressed, while an upgrade
mixes removed/added rows and is printed.
Made by [Open
SWE](https://openswe.vercel.app/agents/b1a5995a-8994-73b4-9bc3-05755afa197e)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The `/model` picker now lists recommended models from uninstalled
providers and offers to install the provider automatically when you
select one.
---
The `/model` selector only listed models from already-installed
providers, so common OSS providers (e.g. `baseten`) were invisible until
the user manually ran `/install`. This surfaces recommended models from
not-yet-installed providers in the selector and, when one is picked,
opens a confirmation modal that installs the provider's extra (reusing
the existing `/install` flow + restart offer) before switching to the
model. Installed models always rank above install-required ones in
search so the common case is never displaced.
Made by [Open SWE](https://openswe.vercel.app)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The `deepagents-code` install script now prompts before updating an
out-of-date install in interactive shells (and skips work when already
up to date); set `DEEPAGENTS_CODE_YES=1` to auto-accept.
---
The `deepagents-code` installer (`scripts/install.sh`) ran `uv tool
install -U` unconditionally, silently pulling the latest version on
every run. It now probes PyPI for the latest release so it can be
deliberate about updates: it exits early when already up to date, and
for an existing default-path install it prompts before upgrading in an
interactive shell. Piped/non-interactive runs with no usable TTY still
upgrade automatically (preserving prior behavior), and
`DEEPAGENTS_CODE_YES=1` accepts the update without prompting. Pinned
versions (`DEEPAGENTS_CODE_VERSION`) and pre-release strategies
(`DEEPAGENTS_CODE_PRERELEASE`) express explicit intent and install
directly without a prompt.
A `can_prompt` helper verifies `/dev/tty` is actually openable, since
`IS_INTERACTIVE` only access-checks it and can be wrong under
cron/systemd/CI.
Made by [Open SWE](https://openswe.vercel.app)
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The install script documented the `DEEPAGENTS_CODE_EXTRAS` environment
variable but only pointed readers to `pyproject.toml` for valid values.
This enumerates the available extras directly in the header comment —
grouped into model providers, sandbox providers, and standalone
integrations — so users can discover supported options without leaving
the script. The authoritative list still lives in `pyproject.toml`,
which the comment notes.
Made by [Open SWE](https://openswe.vercel.app)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
The `dcode` install script can now install exact `deepagents-code`
versions or ask `uv` to resolve pre-release versions using an explicit
strategy. Invalid version pins, invalid pre-release strategies, and
conflicting version/pre-release settings fail before invoking `uv` so
the installer does not forward ambiguous or unsafe arguments.
When a configured model's provider package can't be imported, the model
is built *before* the LangGraph server starts, so construction fails and
the app holds a `_server_startup_error` state that parks queued slash
commands until a successful start drains them. `/install` — the command
that installs the missing package — is `QUEUED`, so it was trapped
behind the very failure it repairs: typing `/install <pkg>` appeared to
hang because it never ran. `/model` and `/auth` already escaped this
wedge via `IMMEDIATE_UI`, but the package-recovery path did not.
Trim the `curl … | bash` install output to the lines users actually
need. The full `dcode -v` dump, the "Checking optional tools" header,
and the multi-line docs footer were noisy on every run — particularly on
the common already-up-to-date path. Verbose behavior is preserved behind
`DEEPAGENTS_CODE_VERBOSE=1`.
The install-script env vars (`DEEPAGENTS_EXTRAS`, `DEEPAGENTS_PYTHON`,
`DEEPAGENTS_SKIP_OPTIONAL`) didn't signal that they only configured the
`deepagents-code` installer, conflicting with the per-package prefix the
rest of the ecosystem already uses (`DEEPAGENTS_CLI_*`, etc.). Renames
them under a `DEEPAGENTS_CODE_*` namespace and adds one new flag for
debugging the install output.
The `curl … | bash` install flow currently dumps every line uv prints —
timing, prep counts, an unformatted `- pkg==X` / `+ pkg==Y` diff — and
always logs `deepagents-code installed.` even when nothing changed.
Re-frames the output so the user immediately sees what changed and what
actually happened.
The `curl … | bash` install for `deepagents-code` now tells users
plainly when it's about to replace an existing editable install or
rebuild the venv, instead of leaking uv's internal warning verbatim.
Motivated by a confusing run where a previous `uv tool install -e …`
install made uv print `Ignoring existing environment for
`deepagents-code`: the requested Python interpreter does not match the
environment interpreter` — accurate, but unhelpful to non-technical
readers.
The bundled install script probed for a `deepagents` binary that the
`deepagents-code` package never installs (its entry points are `dcode`
and `deepagents-code`), so the pre-install upgrade detection and
post-install verification were effectively no-ops on a real install.
The `deepagents-code` package still referenced `deepagents/cli` in its
documentation URLs and had an incorrect working directory for the
`langsmith` sandbox backend. Both issues are corrected across the
package and its tests.