Dependency floors are refreshed across the monorepo for newer
LangChain/LangSmith releases, provider integrations, pytest tooling,
`ruff`, and `ty`. The code changes are the compatibility work needed for
those newer lint and type checks, with small structural cleanups where
the tools needed clearer narrowing or less complex functions.
## Changes
- Raised runtime and test dependency minimums across SDK, CLI, Code,
ACP, evals, examples, and partner packages, including updated
LangChain/LangSmith/provider pins and current lint/test tooling
baselines.
- Reworked backend factory typing around `BACKEND_TYPES` with
`_resolve_backend`, widened file transfer error fields to support
backend-specific strings, and updated filesystem/summarization
middleware to use the shared resolver.
- Tightened `deepagents-code` typing for middleware stacks, MCP tool
filtering, update checks, and Textual compatibility patches, replacing
stale suppressions with `ty`-specific ignores where runtime behavior is
intentionally narrower than static types.
- Split large CLI command handlers into focused helpers such as
`_dispatch_command`, `_execute_agents_command`, and the MCP server
subcommand helpers while preserving existing command behavior.
- Updated Harbor LangSmith sandbox handling to rely on explicit
`NetworkPolicy` and `capabilities` instead of legacy property overrides,
with tests adjusted around the new capability checks.
- Applied lint/format/type-check cleanups across tests and examples so
the staged code passes the refreshed tooling expectations.
Updates the deploy CLI backend handling to match the current Managed
Deep Agents API shape. New scaffolded projects use `state`, and
sandbox-backed projects can use `backend.type: "sandbox"` with
`sandbox_config.scope`, policy IDs, and TTL fields. Existing `default`,
`thread_scoped_sandbox`, `agent_scoped_sandbox`, and `backend.sandbox`
configs continue to normalize for compatibility.
This also updates the CLI README and example deploy project so users see
the canonical `sandbox_config` form.
Tests run:
- `uv run --group test python -m pytest tests/unit_tests/`
- `uv run --group test ruff check deepagents_cli/deploy/project.py
deepagents_cli/deploy/commands.py
tests/unit_tests/deploy/test_project.py
tests/unit_tests/deploy/test_payload.py
tests/unit_tests/deploy/test_init_command.py
tests/unit_tests/deploy/test_deploy_command.py`
- `uv run --group test ty check deepagents_cli/deploy/project.py`
Live validation:
- Created a temporary Managed Deep Agent using `backend.type: "sandbox"`
and `sandbox_config.scope: "thread"`.
- Confirmed the API stored `type: "sandbox"` and `sandbox_config.scope:
"thread"`.
- Updated the same temporary agent with `idle_ttl_seconds` and
`delete_after_stop_seconds`; confirmed both fields persisted.
- Deleted the temporary remote agent and verified it returned 404
afterward.
Minor clean up for the `deepagents` deploy CLI so going from `init` to a
deployed agent
is less fiddly, plus a few `mcp-servers` fixes.
- Subagent `agent.json` now takes `model` (same key as the top-level
config and
the SDK `SubAgent` spec). `model_id` still works as a deprecated
fallback.
- `init` now defaults `backend.type` to `default`, ships an empty
`tools.json`
(so the first deploy works before any MCP server is registered),
scaffolds an
example skill and subagent, and prints a short walkthrough of the
workflow.
- `mcp-servers get`/`update`/`delete`/`connect` accept an id, name, or
URL
instead of only the UUID (resolved client-side against the server list).
- New `mcp-servers tools <id|name|url>` lists a server's tools and
prints a
ready-to-paste `tools.json` snippet. `mcp-servers add` runs the same
discovery
automatically after registering (best-effort; `--no-tools` to skip).
The tools features rely on the platform change that mounts
`/v1/deepagents/mcp/tools` (langchainplus #26365) — merge that first.
Until it's
live, `mcp-servers tools` errors and the post-`add` listing falls back
to a hint,
so nothing breaks.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
## ⚠ Breaking changes
`deepagents-cli` `0.1.x` → `0.2.0`. The deploy CLI is a private-preview
beta, but
this rewrites the command surface, config format, and deploy backend, so
existing
projects and scripts will not work unchanged.
| What broke | Before (`0.1.x`) | After (`0.2.0`) |
| --- | --- | --- |
| **`deepagents dev` removed** | `deepagents dev --port 2024` ran a
local `langgraph dev` server off the bundle | No replacement. Local
bundling/serving is gone; the project is now a managed agent. |
| **Deploy backend** | `deepagents deploy` bundled the project and
shipped via `langgraph deploy` to LangSmith Deployment | `deepagents
deploy` patches agent metadata + syncs source files through the Managed
Deep Agents API (`/v1/deepagents/*`) |
| **Config file** | `deepagents.toml` (auto-discovered or `--config`) |
`agent.json` (metadata, top-level `backend`, `runtime.model`,
permissions) + `AGENTS.md`, `tools.json`, `skills/`, `subagents/` |
| **`--config` / `--dry-run` flags** | accepted on `deploy`/`dev` |
removed |
| **MCP tool wiring** | prior tool-config shape (deployed an agent with
**no tools**) | `tools.json` with `tools[].mcp_server_url`; servers must
be registered and `can_invoke == true` or deploy fails fast |
| **Deploy state** | derived from the bundle | tracked user-local in
`~/.deepagents/deployments/`, keyed by project root + endpoint (not
committed) |
### Migration
1. **Re-scaffold or convert the project.** Run `deepagents init` to
generate the
canonical layout (`agent.json`, `AGENTS.md`, `tools.json`, `skills/`,
`subagents/`), then port settings out of your old `deepagents.toml`:
- system prompt → `AGENTS.md`
- model / permissions → `agent.json` (`runtime.model`, `permissions`)
- runtime/sandbox config → top-level `backend` block (`init` scaffolds
`thread_scoped_sandbox`; the legacy value `"sandbox"` is still accepted
as a
local alias)
2. **Register MCP servers and attach tools.** Use `deepagents
mcp-servers add`
(then `connect` for OAuth servers), and reference them from `tools.json`
via
`tools[].mcp_server_url`. The old tool-config shape silently deployed an
agent
with no tools.
3. **Replace `deepagents dev`.** There is no local dev-server equivalent
in this
release; iterate via `deepagents deploy` against your workspace.
4. **Drop removed flags.** Remove `--config` and `--dry-run` from any
deploy
scripts.
New commands: `deepagents agents` (`get`, `--include-files`, …) and
`deepagents mcp-servers` (`create`/`list`/`get`/`update`/`delete` +
OAuth
`connect`). `get` redacts stored header values.
## Overview
Migrates `deepagents deploy` from the legacy `langgraph deploy` path to
the new
Managed Deep Agents API (`/v1/deepagents/*`). Instead of bundling and
shipping a
deployment, the CLI now treats a project as a managed agent: agent
**metadata**
lives on the agent record and **source files** live in Hub-backed
directory
state. On redeploy, the CLI patches only metadata and syncs the managed
file tree
via Hub directory commits, rather than re-uploading everything.
The CLI surface is now `init` / `deploy` / `agents` / `mcp-servers`,
operating on
the canonical project layout:
```
agent.json # name, description, top-level backend, runtime.model, permissions
AGENTS.md # system prompt
tools.json # MCP tool references (tools[].mcp_server_url) + interrupt_config
skills/<name>/SKILL.md # frontmatter-tagged skills
subagents/<name>/ # delegated subagent definitions
```
Bumps `deepagents-cli` to `0.2.0`. The deploy flow is gated behind a
beta warning
while Managed Deep Agents is in private preview.
## What changed
**Deploy model**
- Source files are kept in Hub-backed directory state; redeploy patches
agent
metadata only and reconciles files through directory commits (create →
first
deploy, patch + directory delta → subsequent deploys).
- Deploy state is tracked **user-local** (`~/.deepagents/deployments/`),
keyed by
project root + endpoint — not committed to the repo.
**Runtime / backend config**
- Managed runtime config moved to a **top-level `backend`** block;
`init` scaffolds
`thread_scoped_sandbox` by default.
- Backend/sandbox config is validated at load time; legacy `"sandbox"`
is accepted
as a local alias for `thread_scoped_sandbox`.
**MCP server flows**
- Consistent dotenv loading across all commands (project `.env`
discovered upward,
then `~/.deepagents/.env`).
- `mcp-servers` supports create/list/get/update/delete plus OAuth
connect; `get`
redacts stored header values.
- Deploy fails fast when a referenced server is unregistered or the
caller
`can_invoke == false`, with actionable hints (including OAuth
`connect`).
**Tooling docs/examples** (also in this branch)
- `tools.json` (`tools[].mcp_server_url`) is the supported way to attach
MCP tools;
fixed the `deploy-content-writer` example and README quickstart, which
previously
implied a tool config shape that deploys an agent with no tools.
## Checked with
```bash
uv run --group test python -m pytest tests/unit_tests/deploy -v
uv run --group test ruff check .
uv run --group test ty check
```
Also ran the flow end to end against a live workspace: `init` →
`mcp-servers add`
→ `tools.json` → `deploy` (create + redeploy) → `agents get
--include-files`,
confirming metadata patch, directory sync, and MCP health check.
## Related
- Linear:
https://linear.app/langchain/issue/AB-2470/mda-wrap-apis-in-deepagents-deploy
- Docs PR: `langchain-ai/docs` `vic/mda-cli-update` (reconciles the
Managed Deep
Agents guide with this behavior)
BREAKING CHANGE: `deepagents deploy` now targets the Managed Deep Agents
API; `deepagents dev`, `deepagents.toml`, and the `--config`/`--dry-run`
flags are removed. Run `deepagents init` to re-scaffold (`agent.json` +
`tools.json`). See
[PR](https://github.com/langchain-ai/deepagents/pull/3609) for the full
migration guide.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#3508
---
Reviving #3506 (auto-closed pending issue assignment). Credit to
@chiplay for the original patch — preserved as the commit author.
The emitted `_load_mcp_tools()` forwarded `mcp.json` header values to
`MultiServerMCPClient` verbatim, so configs like `"Authorization":
"Bearer ${SUBTEXT_API_KEY}"` reached MCP servers as the literal `${VAR}`
string. `client.get_tools()` then errored and the loader's `except:
return []` returned silently, leaving the agent with no MCP tools.
This adds an `os.path.expandvars` pass over `url` and each header value
inside `MCP_TOOLS_TEMPLATE`, mirroring the `${VAR}` substitution already
documented for `deepagents-code`'s `.mcp.json` — so an mcp config shape
that works with `dcode` now also works with `deepagents deploy`. Unset
variables are left as the literal `${VAR}` so a recognizable token
surfaces in the failing request rather than a silent empty header.
Unit test asserts the rendered MCP block references `os.path.expandvars`
and wires it through both the `url` and `headers` paths.
---------
Co-authored-by: Chip Lay <chip.lay@gmail.com>
Fixes#3431
---
Adds a `--host` argument to `deepagents dev` (default `127.0.0.1`) that
is forwarded to `langgraph dev --host`. When running the dev server
inside a container, binding to `0.0.0.0` is required so the server is
reachable from outside the container.
Supersedes #3432 — that PR was accidentally merged and then force-undone
from `main`. GitHub does not allow reopening merged PRs, so this revival
lives on a maintainer branch with original authorship preserved as
`Kevin Pedretti`.
---------
Co-authored-by: Kevin Pedretti <2306930+kevin-pedretti@users.noreply.github.com>
BREAKING CHANGE: removes the interactive TUI / coding-agent surface from
`deepagents-cli`. The package now only ships the `deploy` command and
supporting config. Users relying on the chat REPL, sessions, MCP
tooling, skills, and related widgets should migrate to
`deepagents-code`.
Resolves a race condition where skill discovery and server startup could
simultaneously cold-import overlapping parts of the Deep Agents module
graph, causing CPython's per-module import locks to deadlock. Adds
graceful degradation for malformed or partially-written skill files so
one bad source doesn't block all others.
## Changes
- **Serialized cold imports** with an `asyncio.Lock`
(`_deepagents_import_lock`) held across skill discovery thread offload
and model creation startup, ensuring prewarm completes before either
path re-enters the graph
- **Ordered startup sequence** so `_discover_skills` explicitly awaits
prewarm imports before spawning the discovery thread
- **Broadened per-source error handling** in `list_skills` from
`(OSError, KeyError, TypeError)` to `Exception`, degrading gracefully
when a single skill source is malformed or unreadable
- **Added debug logging bootstrap** (`configure_debug_logging`) and
richer user-facing warnings that include the exception type and a
`DEEPAGENTS_CLI_DEBUG=1` hint
- **Added targeted regression tests** enforcing the prewarm→discovery
call order, import lock gating during server startup, and failure
isolation across skill sources
Adds `OSC 11` / `OSC 111` support so the terminal dynamically adopts the
background color of the active Textual theme, and resets it cleanly on
exit. This makes the CLI feel more visually cohesive, especially with
transparent terminal windows or terminal-native UI chrome.
`_context_tokens` is declared with `PrivateStateAttr`, which keeps it
out of the graph's input/output schemas, but `Pregel.aupdate_state`
still emits its own top-level `UpdateState` run on every turn — so the
count surfaces as a noisy standalone trace in LangSmith on each LLM
response and offload.
Wrap the `aupdate_state` call in `_persist_context_tokens` with
`langsmith.tracing_context(enabled=False)` so the internal bookkeeping
write is fully suppressed from the active LangSmith project while still
being checkpointed for thread resume.
_Opened collaboratively by Mason Daugherty and open-swe._
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#3119
---
Adds a small `terminal_escape` module that centralizes the
"fire-and-forget" pattern the CLI uses for cosmetic terminal control
sequences:
- `write_terminal_escape(sequence)` — prefers `/dev/tty` so output
reaches the terminal even when stdout/stderr are redirected; falls back
to `sys.__stderr__` only when it is a TTY; swallows `OSError` so
cosmetic writes can never crash the app.
- `write_osc(command, payload, *, st=False)` — composes an OSC sequence
with BEL or ST termination.
- `TerminalProgressState` + `set_terminal_progress` /
`clear_terminal_progress` — `OSC 9;4` taskbar-progress helpers with
validation (clamps to `[0, 100]` for determinate states, normalizes
`INDETERMINATE` and `CLEAR` to `0`).
- Opt-out via `DEEPAGENTS_CLI_NO_TERMINAL_ESCAPE=1` for terminals where
users prefer to suppress it.
Wires indeterminate progress into the existing agent-turn lifecycle in
`DeepAgentsApp._set_spinner`: the indicator turns on whenever a spinner
is shown, clears when the spinner is hidden. `atexit` cleanup is
registered lazily — only after a non-clear emit succeeds — so the helper
stays a true no-op on test imports, redirected runs, or quick exits.
Unsupported terminals silently ignore unknown OSC sequences (the design
constraint the issue calls out), so callers can fire unconditionally
without runtime probing. Existing OSC 52 clipboard and iTerm
cursor-guide writers are left as-is — the issue lists migrating them as
a "consider," not a must — and remain easy follow-ups.
_Opened collaboratively by Mason Daugherty and open-swe._
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Closes#2663
---
`together` was already wired into `_MODEL_PROVIDER_ENV`, so deploy-time
validation accepted `TOGETHER_API_KEY` — but the bundler had no mapping
for it, the project had no `together` optional extra, and the parity
test (`test_deps_cover_all_validated_providers`) was masking the gap via
a `no_partner_pkg` exemption. A `model = "together:..."` config silently
produced a bundle missing `langchain-together` and crashed at runtime
with `ImportError: No module named 'langchain_together'`.
Wires `together` end-to-end:
- Adds `together` to `_MODEL_PROVIDER_DEPS` so the deploy bundler
includes `langchain-together` whenever a `together:` model string (or
`[subagents.*]` model override) is configured.
- Adds the `together` optional dependency
(`langchain-together>=0.4.0,<2.0.0`) and folds it into the
`all-providers` composite extra.
- Removes the `together` carve-out from the bundler parity test, so any
future provider added to `_MODEL_PROVIDER_ENV` without a corresponding
bundler dep fails CI instead of slipping through.
---------
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Closes#2087
---
Adds a Claude-Code-style _reject with reason_ flow to the CLI's HITL
approval prompt. With the Reject option highlighted, pressing Tab swaps
the option for an inline single-line `Input`. Pressing Enter submits the
decision with the typed text attached as `RejectDecision.message`;
pressing Esc returns to the menu without deciding. Leaving the input
blank submits a plain reject.
The reason is surfaced in the chat transcript as a dim `Reason: …` line
attached to the rejected `ToolCallMessage`, so the user can see exactly
what they told the model. The reason round-trips through `MessageData`
so it survives message rehydration.
Existing `Reject (n)` muscle memory is preserved — `n`, `3`, and Esc
still produce an immediate plain reject. `Tab` is also intercepted while
the input is open so Ctrl+C / Esc cancel the input first instead of
submitting an empty reject.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Closes#3308
---
The `Project MCP servers require approval` prompt didn't explain what
project-level trust actually means or how to revoke it once granted.
Adds a `Learn more:` line that links to the project-level-trust section
of the MCP tools docs, so users have a one-click jump to the security
model, the trust store, and the `--trust-project-mcp` flag before
deciding.
The link is rendered with Rich's `[link=...]` markup (same pattern used
by `show_help` and the auth provider hints), so terminals that support
OSC 8 make it clickable while plain terminals still see the URL.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#2374
---
The startup project MCP approval prompt was listing servers once per
config file rather than once per server name. The downstream loader
already deduplicates via `merge_mcp_configs` (later configs override
earlier ones), so the prompt was inconsistent with what actually got
loaded — most noticeably for users of `rulesync`, which generates
`.deepagents/.mcp.json` alongside the project's root `.mcp.json`.
`_check_mcp_project_trust` now runs the loaded project configs through
`merge_mcp_configs` before extracting server summaries, so each server
name appears exactly once in the prompt and the displayed entry matches
the one that will be loaded.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Fixes#2343
---
Normal keystrokes in `ChatInput` currently trigger blocking filesystem
stat calls (`Path.exists()` / `Path.is_file()`) on every
`TextArea.Changed` event because `_is_dropped_path_payload` and
`_apply_inline_dropped_path_replacement` run unconditionally. This is
wasted work for single-character changes — which can never be a
drag-drop or bracketed-paste payload — and adds noticeable latency on
each keypress.
## Changes
- **Gate path detection by insert size.** Introduced
`_should_check_path_payload()` to compare the previous and current
`TextArea` text, compute the length of the inserted span, and skip the
path-detection helpers entirely when only a single character was
inserted. Multi-character changes (drag-drop, bracketed paste, or
replacement edits) are still evaluated.
- **Preserve replacement-edit detection.** The check looks at the actual
changed span rather than the net length delta, so replacing selected
text with a path of similar length still correctly triggers inline image
attachment.
- **Added unit-test coverage.** `TestPathPayloadDetectionGating`
verifies that character-by-character typing never invokes the detection
helpers, while bulk text changes and replacement edits continue to
attach dropped paths and images.
Closes#1091
---
When the agent calls a shell tool that requires approval, the command
was rendered twice on screen at the same time — once in the streamed
`ToolCallMessage` header, and again in the approval dialog. For
non-shell tools (`write_file`, `edit_file`) the approval dialog shows
diffs/file contents so the duplication is hidden, but for shell-family
tools (`shell`, `bash`, `execute`) both surfaces render the same command
string.
Adds `set_awaiting_approval` / `clear_awaiting_approval` on
`ToolCallMessage` that toggle the widget's `display` attribute, and
wraps the approval await in `execute_task_textual` with a
`try`/`finally` that suppresses any in-flight shell tool messages while
the prompt is visible. The widget is restored before the post-decision
`set_running` / `set_rejected` calls run, so the existing status-update
flow is unchanged.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Closes#1095
---
The existing 120-char truncation in the approval prompt only collapsed
horizontally long commands. Multi-line commands where each line was
short (e.g. a `python3 -c` one-liner with embedded newlines) bypassed
the per-line threshold and rendered untruncated — pushing the
approve/reject options far down the terminal.
Adds a line-count threshold (`_SHELL_COMMAND_TRUNCATE_LINES = 5`)
alongside the existing character threshold and applies both inside the
same `press 'e' to expand` affordance, so the user experience is
unchanged for short commands and consistent (one ellipsis, one expand
hint) for any command that exceeds either dimension.
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Fixes#1403
---
Adds width-aware wrapping and smarter truncation to `write_todos` tool
output in the chat transcript. Long todo items now wrap to the available
widget width when expanded, and collapsed previews show an explicit
"full todo text" hint instead of silently clipping content. The expand
toggle also skips doing anything when there is no hidden text to reveal.
Before:
<img width="652" height="247" alt="Screenshot 13"
src="https://github.com/user-attachments/assets/a126d3fa-6e21-4e8a-95ac-7168815d7f4a"
/>
After:
<img width="530" height="316" alt="Screenshot 15"
src="https://github.com/user-attachments/assets/f2644195-8b3a-4e25-a238-674453ae7b10"
/>
## Changes
- `ToolCallMessage._format_todo_line()` wraps long todo text to the
available widget width when expanded, with continuation lines indented
to align under the content column.
- Collapsed todo previews now truncate content longer than
`_MAX_TODO_CONTENT_LEN` and surface a "full todo text" expansion hint
instead of silently clipping.
- `ToolCallMessage._has_expandable_output()` prevents the expand toggle
from firing when there is no hidden content (short output, single-line
todos, or already-visible text).
- Added `is_preview` threading through `_format_todos_output()` and
`_format_single_todo()` so preview and expanded states apply different
truncation rules consistently.
The CLI hides the Thinking spinner as soon as a tool call is rendered,
which leaves a visual gap for `edit_file` — its argument streaming, HITL
interrupt, and user approval can take multiple seconds with no feedback.
Keep the spinner anchored beneath the tool call row for calls in the new
`_TOOL_CALLS_KEEP_THINKING_SPINNER` set (currently just `edit_file`),
and only clear it once the tool finishes.
When a sandbox provider is missing or misconfigured, the server
subprocess exits before the health endpoint comes up. The parent CLI
process raises a `RuntimeError` that includes the last few thousand
characters of log output, but the actual error message is often buried
under a Python traceback and Uvicorn shutdown noise. This makes it hard
for users to understand why `deepagents up` failed.
Closes#2304
---
Auto-populate `sub_title` with the active sandbox provider name (e.g.
`"Sandbox: Daytona"`) so the optional header bar indicates which sandbox
is in use. Display names use proper casing via a lookup dict, with a
`.title()` fallback for unrecognised providers. An explicitly passed
`sub_title` takes precedence over the auto-detected value.
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#1990.
## What this does
The `/threads` picker (and `deepagents threads list`) now scopes to the
current working directory by default, so you only see conversations that
started in this folder. The Options side-panel exposes a `Show threads
from:` dropdown to widen the view to every project.
## How to use it
**Inside a session:**
- Open the picker with `/threads`.
- You'll see only threads from the current cwd. In the right-hand
**Options** panel, the `Show threads from:` `Select` shows the active
scope.
- Open the dropdown and choose **`All directories`** to see threads from
any cwd. Choose **`Current directory`** to scope back.
**From the shell:**
- `deepagents threads list` → unchanged, lists everything (kept for
script compatibility).
- `deepagents threads list --cwd` → only threads from the current
directory.
- `deepagents threads list --cwd /some/path` → only threads from that
path.
## Tests
All existing tests pass. Test fixtures that pass `initial_threads` opt
out of the default cwd scoping with `filter_cwd=None`, since their mock
rows don't carry a matching `cwd`. The Tab-focus tests were updated to
account for the new `Select` sitting between the search input and the
sort checkbox.
## Docs
Couldn't find any user-facing docs file that lists `/threads` controls
or `deepagents threads list` flags beyond the in-CLI help text (which I
did update in `ui.py`). If there's an external docs page that should
mention the new dropdown/flag, happy to update it — just point me to it.
---------
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
The `/clear` slash command was resetting the conversation thread but
leaving the internal thread ID stale and rendering the new thread ID as
plain text. Bring it in line with the resumed-thread flow by
synchronizing the local thread variable and promoting the confirmation
message to a clickable link.
## Changes
- `/clear` now assigns the new thread ID to `_lc_thread_id`, preventing
the CLI from tracking a stale ID after a thread reset
- Thread reset confirmation is now scheduled for link upgrade via
`_schedule_thread_message_link`, matching the clickable thread ID
pattern used on thread resume
- Added unit test coverage validating the `/clear` command's thread ID
synchronization and UI link scheduling behavior
The status bar footer was showing "... tokens" immediately on the first
LLM invocation, before any real token count had been established. This
created visual noise during the initial streaming period when no context
tokens exist yet.
The CLI no longer requires a preconfigured model or environment
credentials to open. When no default model can be resolved on startup,
the TUI launches in a deferred state with inline guidance instead of
exiting with an error code.
## Changes
- Launch the TUI even when no model credentials are configured.
`run_textual_cli_async` catches the no-credentials `ModelConfigError`,
sets `defer_server_start=True`, and opens the app rather than printing
an error and exiting.
- Defer server startup inside `DeepAgentsApp` via a new
`defer_server_start` init flag. The UI is fully usable, but the backing
server remains paused until the user runs `/auth`, `/model`, or
`/reload`.
- Auto-resume deferred startup once a model is available.
`_maybe_start_deferred_server_from_default()` checks for a resolved
default model after auth flows and automatically transitions from
deferred to connecting state.
- First-launch guidance. `_mount_deferred_start_notice()` displays a
message pointing users to `/model` when they land in a session with no
credentials and no pre-selected model.
- Updated `_get_default_model_spec` to consider stored TUI credentials
(`auth_store.get_stored_key`) and `get_provider_auth_status()` rather
than only environment variables, making the fallback chain more robust
for deferred-start scenarios.
- Preserve initial prompt (`-m`) submissions across deferred startup.
The prompt is held until `on_deep_agents_app_server_ready` fires instead
of being consumed before the agent exists.
The post-upgrade banner now renders the changelog URL as a proper
clickable hyperlink in supported terminals. Previously the URL was plain
text, forcing users to copy-paste it manually.
The thread selector's right-hand controls pane could become inaccessible
when the terminal window was too short to fit all column toggles.
Wrapping the pane in a scrollable container and showing an ellipsis when
content is clipped keeps the UI usable on small screens.
Adds a full `Markdown.update()` pass once streaming finishes to work
around Textualize/textual#6518, where incremental
`MarkdownFence._update_from_block` refreshes the visible `Label` but
leaves `_highlighted_code` pinned to the first chunk. Any later
recompose (click, focus change, theme update) re-yields the stale value
and wrapped fenced-code bodies vanish. A full re-parse rebuilds every
fence with correct internal state.
Fixes part 1 of #2146
---
Add per-terminal theme mapping so users switching between terminals
(e.g. dark iTerm vs. light Apple Terminal) get the right theme
automatically without re-picking each time.
## Changes
- Add `[ui.terminal_themes]` config table keyed on `TERM_PROGRAM` —
looked up before the saved global preference in
`_load_theme_preference`, after the `DEEPAGENTS_CLI_THEME` env var
override
- Introduce `_resolve_theme_name` to canonicalize theme values (registry
key, human label, case-insensitive, whitespace-tolerant, plus
`textual-ansi` → `ansi-light` legacy migration) shared across env var,
terminal mapping, and saved-preference resolution paths
- Add `save_terminal_theme_mapping` for atomic TOML writes to
`[ui.terminal_themes]`; preserves existing table entries and repairs
malformed scalar values with a warning
- Extend `ThemeSelectorScreen` with `n` to toggle between human-readable
labels and canonical registry keys, and `t` to persist the highlighted
theme for the current `TERM_PROGRAM` without touching `[ui].theme` — the
picker stays open and the `(default)` badge updates in place, so users
can keep browsing
- Badge the option list with `(default)` when a theme matches the
current terminal's saved mapping; combine with `(current)` as `(current,
default)` when both apply
- Serialize `config.toml` writes with a process-local lock so a quick
`t`-then-Enter sequence can't clobber either mutation
PR 1 of 2 — system dark/light auto-detection will follow once
Textualize/textual#6151 lands upstream.
---------
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#2091
---
Adds an incognito local shell mode for `!!` in the interactive CLI.
Existing `!` commands still follow the normal shell path, while `!!`
commands run locally and render their command and output as app-level
transcript entries without adding user or assistant records to the
model-visible conversation.
The input parser now detects the longest mode prefix so `!!` survives
alongside `!`, history recall restores the right mode, queued and sent
messages render the correct shell glyph, and shell-mode typing can be
upgraded to incognito by entering a second `!`.
The UI distinguishes incognito shell mode with a dedicated theme color,
input border title, message border, and `SHELL` status badge styling.
Help text and startup tips document the new prefix, and focused unit
coverage exercises prefix parsing, input and history behavior, queue
draining, shell routing, local-only output, rendering, status, theme
variables, help text, and tips.
---------
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Adds `/about` as a hidden alias for the `/version` slash command,
routing it to the same handler and queue-bypass behavior
(`BYPASS_WHEN_CONNECTING`) as the original command.
The status bar previously cleared the token count entirely during
streaming, leaving an empty gap that looked like a UI glitch. Switch to
showing a `"... tokens"` placeholder so users know a count is coming
back, and ensure the accurate count is reported even when a turn ends
early (e.g. on HITL rejection or cancellation).
## Changes
- **Replaced `hide_tokens` with `show_pending_tokens`** across
`StatusBar`, `TextualUIAdapter`, and `DeepAgentsApp`. The new method
renders `"... tokens"` instead of blanking the display, making the
pending state explicit.
- **Persist token counts on early-exit paths** — added
`_report_and_persist_tokens` calls in `execute_task_textual` before
returning for HITL rejection and `ask_user` cancellation, so the last
known count is restored to the status bar instead of staying in the
pending state.
- **Kept `set_tokens` refresh semantics** — documented and preserved the
forced-re-render behavior when the reactive value is unchanged, which is
required after the placeholder swaps out the widget text.
- **Added/updated unit tests** for placeholder display, early-exit token
restoration, and no-op safety when the widget is not yet mounted.
Auto-populates the model switcher with locally installed Ollama models
by probing the daemon lazily from the selector flow. `langchain-ollama`
does not ship static profile data, so users previously had to curate
`models = […]` and profile overrides by hand before local Ollama models
showed useful metadata.
## Changes
- **`get_available_models()`** merges models discovered from `GET
/api/tags` with static profiles and explicit config lists, preserving
config order and deduplicating across sources.
- **`get_model_profiles()`** supplements Ollama models with best-effort
metadata from `POST /api/show`, including context length and
capabilities. Configured Ollama models are still inspected even when
`/api/tags` returns no names.
- **`_get_ollama_installed_models()`** caches successful `/api/tags`
results per endpoint so selector loading does not probe tags separately
for available models and profiles. Empty tag results are not cached, so
a daemon that starts later can recover without `/reload`.
- **`_fetch_ollama_installed_model_profiles()`** caches successful
`/api/show` profile results per endpoint/model to avoid repeated serial
profile probes while still allowing failed or empty results to retry.
- **`_profile_from_ollama_show_payload()`** maps Ollama
`*.context_length` values to `max_input_tokens`, maps `completion`,
`tools`, and `thinking` capabilities to selector profile fields, and
logs a debug canary when a shaped payload yields no recognized fields.
- **`_fetch_ollama_installed_models()`** and
**`_fetch_ollama_installed_model_profiles()`** share discovery headers,
use a short timeout, and degrade gracefully on network or parse
failures. Discovery bearer auth is only attached for local endpoints, so
`OLLAMA_API_KEY` is not forwarded to arbitrary remote `base_url` values.
- **`_ollama_discovery_enabled()`** keeps discovery enabled by default,
accepts explicit truthy/falsy values through
`DEEPAGENTS_CLI_OLLAMA_DISCOVERY`, and warns on unrecognized values.
Config and CLI profile overrides continue to win over discovered Ollama
metadata. The staged unit tests cover model listing, cached tag
discovery, no negative caching for tag misses, successful profile
caching, `/api/show` profile parsing, configured-model fallback,
local-only discovery auth, malformed tag payloads, malformed JSON,
provider opt-out cases, schema edge cases, cache clearing, and override
precedence.
Closes#743
---
Adds a `/copy` slash command that copies the latest completed assistant
message to the clipboard. The command reuses the existing clipboard
backends, keeps source markdown intact, and reports empty or failed-copy
states in chat.
Also registers `/copy` in command autocomplete, help, queue policy
coverage, and startup tips.
---------
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Adds a live progress modal for self-updates, hourly background rechecks,
and debug tooling to make the update flow observable and non-disruptive.
Upgrade output is now streamed in real-time and persisted to timestamped
logs with automatic cleanup.
Adds `/connect` as a slash-command alias for `/auth` to improve
discoverability. The new alias routes to the same provider API-key
management screen and is classified with the same `IMMEDIATE_UI` bypass
tier as the original command.
Update-available messages now distinguish between how recently the
latest release was published and how old the currently installed version
is. This gives users better context when deciding whether to upgrade.
Closes#2165
---
When a user cancels an `ask_user` interrupt, the agent now halts the
current turn instead of resuming with a cancelled-status payload and
continuing generation. This prevents the model from attempting to
proceed with an empty-answer placeholder that the user explicitly
rejected.
Closes#2332
---
Multiple-choice option labels in the `ask_user` menu were clipped to a
single row in narrow terminals because the `.ask-user-choice` CSS rule
hard-coded `height: 1`. Switching to `height: auto` lets labels wrap
naturally.
Previously the CLI unconditionally re-enabled iTerm2's cursor guide on
exit, which left the feature on for users who had it disabled before
launch. Now it reads the active profile from iTerm2 preferences and
restores the guide only when the profile had it enabled at startup.
Fixes#3166
---
`AppBlur` is an OS-level event that doesn't change widget focus, so
Textual's `TextArea._watch_has_focus`-based blink pause never triggered
when the terminal window lost focus. Adds an `on_app_blur` handler
symmetric to `on_app_focus` plus a `ChatInput.set_cursor_blink` mediator
that toggles the public `cursor_blink` reactive without blurring the
widget, so key events keep flowing while the cursor is paused.
Verified by reproducing in terminal (cursor freezes on window switch,
resumes on return). Added two unit tests covering the reactive toggle
and focus preservation.
---------
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Closes#2335
---
## Description
The `ask_user` free-text input was a single-line `Input`, so long
answers were truncated/scrolled horizontally. Swap it for a small
`TextArea` subclass (`AskUserTextArea`) with `soft_wrap=True`: long
answers wrap visually and the widget auto-grows up to the CSS
max-height. Enter submits; Shift/Alt/Ctrl+Enter (and Ctrl+J) insert a
literal newline for users who want explicit paragraph breaks.
## Release Note
CLI: `ask_user` free-text answers now wrap automatically and support
multi-line input via Shift+Enter.
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Tall `ask_user` prompts in the chat UI were mounting with only their
bottom edge visible, hiding the first question and menu border from the
user. Added `_scroll_ask_user_into_view` in `DeepAgentsApp` to detect
oversized prompts and align them to the top of the viewport with
`scroll_visible(animate=False, top=True)`, while preserving default
scroll behavior for shorter menus.
The `ask_user` tool call is now displayed as a first-class
`ToolCallMessage` widget in the TUI instead of appearing inline with the
chat stream. This keeps long question lists from cluttering the main
transcript and lets users collapse or expand the arguments with a click
or `Ctrl+O`, matching the behavior of other tool calls.
## Summary
- auto-wire an issues board after successful `deepagents deploy` when
memories backend is `hub`
- resolve tracing session by deployed project name and call `POST
/v1/platform/sessions/{session_id}/issues-agent`
- derive `context_hub_repo_handle` from the hub identifier
(`memories.identifier` or `-/{agent.name}`) and send only the
repo-handle segment
- on `409`, patch the existing board with the same
`context_hub_repo_handle`
- add unit tests for default and explicit identifier -> repo-handle
mapping
## Verification
- `uv run --project libs/cli --group test pytest
libs/cli/tests/unit_tests/deploy/test_commands.py -q`
Adds `ContextHubBackend` — a `BackendProtocol` implementation that
persists files to a LangSmith Hub agent repo. Typical wiring: route
under a `CompositeBackend` path (e.g. `/memories/`) for durable,
versioned agent state across invocations.
Wraps `langsmith.Client.pull_agent` / `push_agent`. First read pulls the
tree into a read-through cache; each `write` / `edit` commits
immediately with `parent_commit=self._commit_hash` and refreshes the
hash from the response URL. The `FileEntry | AgentEntry | SkillEntry`
discriminated union from `langsmith.schemas` separates inline files from
linked repos at the type level — no HTTP error-string sniffing.
## Verification
- 34 unit tests (MagicMock `Client`) covering every method + error paths
- 5 integration tests round-tripping a throwaway hub repo (skipped
unless `LANGSMITH_API_KEY` is set)
- Prod E2E with real ai-sdr `AGENTS.md` + skill files: every byte
round-trips identically
---------
Co-authored-by: emanbareket <immanuel.bareket@langchain.dev>
Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
Fireworks and other providers expose models via long fully-qualified IDs
like `accounts/fireworks/models/kimi-k2p6` that consume most of the
status bar width. A per-provider registry now strips these prefixes
before display so the model label remains compact and readable.
Truncation and width calculations use the cleaned name, preventing the
raw prefix from leaking into the ellipsis.