Skip to content

feat(code): make /offload server-owned - #5261

Merged
Mason Daugherty (mdrxy) merged 81 commits into
mainfrom
mdrxy/code/offload-seeded-compaction-no-hitl
Aug 24, 2026
Merged

feat(code): make /offload server-owned#5261
Mason Daugherty (mdrxy) merged 81 commits into
mainfrom
mdrxy/code/offload-seeded-compaction-no-hitl

Conversation

@mdrxy

@mdrxy Mason Daugherty (mdrxy) commented Aug 3, 2026

Copy link
Copy Markdown
Member

/offload now runs only as a server-owned operation on built-in dcode servers. Local and ACP agents no longer support it, and custom or older servers without the route are unsupported.


The graph-operation prototype coupled the TUI to LangGraph run routing and lifecycle behavior. This revision puts the ownership boundary in dcode's server backend: the built-in LangGraph deployment registers a custom HTTP app that resolves the same cached runtime, compaction policy, hooks, model configuration, and CompositeBackend as the interactive agent.

flowchart LR
    user["User runs /offload"] --> tui["TUI"]

    subgraph client["Client"]
        tui --> remote["RemoteAgent"]
        hookexec["Configured hook executor"]
        remote <--> hookexec
    end

    subgraph server["Built-in dcode LangGraph server"]
        api["Offload HTTP boundary"]
        runtime["Shared server runtime"]
        operation["OffloadOperation"]
        hooks["PreCompact + PreToolUse"]
        compact["Agent compaction service"]
        state[("Thread checkpoint")]
        backend["Agent CompositeBackend"]
        cost["Cost recorder"]

        api -->|"resolve"| runtime
        runtime --> operation
        operation --> hooks --> compact
        api -->|"read + validate"| state
        compact -->|"plan archive"| backend
        cost -->|"priced delta"| api
        api -->|"summary event + cost"| state
        api -->|"then append archive"| backend
    end

    remote -->|"thread ID + runtime context"| api
    api -->|"typed result or hook request"| remote
    compact -->|"summarize"| model["Configured model provider"]

    style api fill:#dcfce7,stroke:#16a34a
    style operation fill:#dcfce7,stroke:#16a34a
    style state fill:#dcfce7,stroke:#16a34a
Loading

The server operation:

  • reads and hydrates checkpoint state itself; the request contains no graph name, checkpoint, or conversation messages;
  • refuses a thread that holds work in flight — active, interrupted, carrying pending graph tasks, or advanced past the checkpoint it read. A thread whose last run failed is still offloadable: LangGraph leaves that thread on error until the next run completes, which is exactly when a user reaches for /offload to recover from an overflow;
  • commits only the channels OffloadStateUpdate declares. The runtime guard is an allowlist derived from that type, so a future merge adding any other channel is refused, not just messages. No synthetic assistant or tool message is persisted, and the operation cannot replace conversation history;
  • resolves the summarizer's model and transport from the checkpoint, not from the request. A client cannot point the server's credentialed provider calls at an endpoint of its choosing;
  • runs the agent's PreCompact and PreToolUse hooks, transporting interrupt/resume payloads opaquely through the client and returning denials as typed results. The session's approval mode is carried across, so a configured hook sees the same mode it sees on an interactive turn;
  • reserves the summary in the checkpoint before appending the archive, and rolls the append back if the link cannot be committed. A per-session lock serializes an archive's read/write cycle against concurrent compactions;
  • records priceable model cost in the same checkpoint update. Every prepared charge is explicitly committed or rolled back, and an abandoned one warns rather than vanishing;
  • uses the agent's existing compaction backend/policy and archive guard, so server-side archives remain readable by the agent;
  • accepts an explicit cancellation for an in-flight operation and confirms the outcome, so a client that gives up learns whether the operation finished or was cancelled.

The client calls the server route directly. There is no capability probe and no seeded tool-call fallback. A local in-process or ACP agent gets a short unsupported message. A server that does not register the route gets a message naming that cause instead of a bare transport error.

Careful review is warranted around the custom-route/thread-state boundary, the archive reserve-then-append ordering, and hook replay identity. The real integration test launches the production server configuration, checks message preservation around /offload, verifies route authentication, and reads the archive back through the running agent.

User-visible output

Success is unchanged in shape:

Offloaded 6 older messages, freeing up context window space.
Conversation: ~1.0K → ~250 tokens (75% decrease), 4 messages kept.

Three failure paths now say something actionable:

Situation Before Now
Last turn failed (thread on error) Offload failed: Cannot offload while the thread has an active or interrupted run. — and no way out until a turn succeeds Offloads normally
Server does not register the route Offload failed: 404 Not Found Offload failed: This server does not provide dcode's /offload operation. Use the built-in dcode server, or upgrade the server to a version that registers it.
Reporting fails after the server committed Offload failed: <exception> — prompting a second offload of an already compacted conversation The conversation was offloaded, but the result could not be displayed. Check logs for details.

A dropped endpoint override is now logged with the key names, so a user whose gateway configuration is being ignored has something to find.

Test plan
  • Full make test: 14,199 passed, 2 skipped.
  • Real-server integration: 3 passed.
  • make format and make lint: passed, including Ruff, ty, and command-catalog validation.
  • Three added tests were mutation-verified: re-keying the per-thread lock on operation_id, renaming the route's path converter, and deleting the hook round-limit break each fail the new test and passed before it.

Made by Open SWE

Mason Daugherty (mdrxy) and others added 5 commits July 31, 2026 06:27
`/offload` injects a synthetic assistant message carrying a forced
`compact_conversation` call directly into graph state, so
`awrap_model_call` never runs and no Auto decision plan is checkpointed.
`aafter_model` then treated that as an invalid-plan case and raised a
HITL interrupt for an action the user had just requested, forcing the
`/offload` driver to catch and self-approve its own interrupt.

Recognize the seed from trust signals a model cannot forge — the
client-set `offload_tool_call_id` run context, the deterministic seed
message ID the compaction tool's execution guard already requires, and
exact `force=True` args — and pass it through. Any other gated call in
the batch keeps the existing manual-review fallback.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
- Log WARNING on the two impossible seed-signal states (no trusted
  compaction tool, seed message missing the forced call) so a broken
  trust signal no longer degrades silently behind the driver's
  blind-approve fallback.
- Reject the bypass when a non-compaction call reuses the authorized
  tool-call ID; previously the subset check let a same-ID `execute`
  call through.
- Loosen the seed args check to `args.get("force") is True`,
  matching `_offload_rejection` and `_decisions_for_interrupt`, and
  read the context ID via the existing `_offload_tool_call_id` helper.
- Include the seeded ID in the bypass DEBUG line and drop the
  Auto-only wording; the bypass is mode-independent.
- Update `app.py` docstrings: the bypass is the normal path, and
  interrupt-self-approve is the fail-closed fallback for graphs
  without the Auto HITL middleware.
- Tests: cover message-ID vs tool-call-ID mismatch separately, pin
  that a seed-ID'd message carrying another gated tool is reviewed,
  and add a positive-control chunk assertion to the integration
  recorder. Fix the integration comment (the app runs Manual mode).

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
@github-actions github-actions Bot added dcode Related to `deepagents-code` internal User is a member of the `langchain-ai` GitHub organization refactor Code change that neither fixes a bug nor adds a feature size: L 500-999 LOC labels Aug 3, 2026
@mdrxy Mason Daugherty (mdrxy) changed the title refactor(code): run /offload as a server operation refactor(code): run /offload as a server operation Aug 3, 2026
@mdrxy
Mason Daugherty (mdrxy) marked this pull request as ready for review August 3, 2026 19:21

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 2 potential issues.

Open in WebView Open SWE trace

Comment thread libs/code/deepagents_code/app.py Outdated
Comment thread libs/code/tests/integration_tests/test_offload_server_side.py Outdated
@github-actions github-actions Bot added size: XL 1000+ LOC and removed size: L 500-999 LOC labels Aug 3, 2026

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 3 potential issues.

Open in WebView Open SWE trace

Comment thread libs/code/deepagents_code/app.py Outdated
Comment thread libs/code/deepagents_code/offload_middleware.py Outdated
Comment thread libs/code/deepagents_code/offload_middleware.py Outdated
Mason Daugherty (mdrxy) and others added 12 commits August 4, 2026 10:37
…face failures as errors

Replace the bare private-attribute writes on the composite backend with an
OffloadServerResources NamedTuple attached through attach_offload_resources /
offload_resources_from, keeping the attribute name in one place.

The operation graph's node now raises RuntimeError with the
COMPACTION_FAILURE_PREFIX on compaction failure and on PreCompact hook
veto, so a server-backed /offload reports the real reason instead of a
generic internal error or a misleading "already compact" no-op. The
client unwraps RemoteException via format_agent_exception, and fires the
SessionStart(compact) hook itself since the operation graph produces no
tool result to key on.

RemoteAgent.for_graph now caches sibling clients per graph name instead
of leaking two httpx connection pools per call, and the server graph
factories share one cached resources tuple. The driver replays the
thread's own state as run input (minus _summarization_event, which the
server cannot deserialize) because an empty input leaves a real server
run with nothing to compact.

Also documents the /offload graph's no-HITL authorization model and the
noop-auth writable channels in the threat model.
…eded-compaction-no-hitl

# Conflicts:
#	libs/code/deepagents_code/offload_middleware.py
…eded-compaction-no-hitl

# Conflicts:
#	libs/code/deepagents_code/agent.py
…in failures

The `offload` operation graph accepted every channel `_OffloadState` declares
as writable run input. `PrivateStateAttr` / `OmitFromInput` are honored by
`create_agent`, not by a raw `StateGraph`, so the markers inherited from
`CostState` did not restrict anything. Because the driver replayed the whole
thread state as input and `_session_cost_usd` reduces with `operator.add`,
every `/offload` echoed the checkpointed total back and doubled the thread's
persisted spend, compounding across runs; `_session_cost_transfers`
(`operator.or_`) resurrected settled subagent transfers, and a local caller on
the `noop`-auth port could set the compaction cutoff directly.

Declare an explicit `input_schema` carrying only `messages`, so the restriction
is enforced by the graph rather than by the client's discipline in what it
sends, and replay only `messages`. Inherit `_summarization_event` from
`SummarizationState` instead of re-declaring it without the SDK's annotation.

The driver also gave up silently in two reachable cases: an interrupt it cannot
answer (a `PreToolUse` hook returning an `ask` permission makes the hook
middleware raise a plain `HITLRequest`, which the operation graph has no HITL
middleware to route) and a hook that outlasts the resume bound. Both left the
run paused while the caller read the unchanged event as "nothing to offload".
Both now return an error string. The resume bound is shared with the seeded
driver rather than duplicated as a second literal.

Further fixes:

- Mint a fresh forced-tool-call id per `PreCompact` dispatch. The hook
  `invocation_id` derives from it plus the prompt id, which only rotates on
  user-prompt submit, so a constant id made two `/offload`s in one turn collide
  and replay the first decision, including a denial.
- Convert hook-dispatch failures to `RuntimeError` so the server's serde
  allowlist does not replace them with "An internal error occurred".
- Stop letting a cost-drain failure discard a committed compaction, which would
  report "your conversation is unchanged" while leaving an orphaned archive
  section no `_summarization_event` references.
- Report a failed post-run graph rebind instead of only logging it; a later
  `/goal` or `/rubric` would otherwise fail with no explanation.
- Do not let a stopping `SessionStart` hook suppress an already-committed
  offload and leave the status bar at pre-offload counts.
- Derive the `offload` langgraph.json ref from the agent ref's module. The two
  graphs share one server runtime only because both resolve into the same
  factory closure, so a hardcoded ref would silently build a second sandbox and
  MCP session if `graph_ref` were overridden.
- Return the graph/backend pair as a named `ServerRuntime`, make the hook
  middleware non-optional, and narrow the resource accessors to
  `CompositeBackend`.
- Correct THREAT_MODEL DC5 (three producers; the archive guard covers only the
  forced paths), TB2 (`PreToolUse` is dispatched too), and TB10 (the input
  surface and the mechanism that restricts it), and drop docstrings describing
  an Auto-mode approval bypass that no longer exists.
…eded-compaction-no-hitl

# Conflicts:
#	libs/code/deepagents_code/offload_middleware.py
#	libs/code/tests/unit_tests/test_compact_tool.py
The `/offload` operation graph minted its forced tool-call id with `uuid4()`
inside the node. Answering a hook interrupt re-executes a node from the top,
so the id changed between the request and the resume; `ServerHooksMiddleware`
folds it into the hook `invocation_id`, and `parse_hook_resume_value` rejects a
mismatched id as fatal. Every `/offload` therefore failed outright for anyone
with a `PreCompact`/`PreToolUse` hook configured, and the client's whole
fulfill/resume loop was unreachable. The id is now derived from the
task-scoped `checkpoint_ns`, which LangGraph reuses when it replays a task and
mints fresh for each run -- stable across resumes, distinct across runs.

The post-compaction cost drain awaited `aafter_agent`, which
`CostTrackingMiddleware` does not implement, so it resolved to
`AgentMiddleware`'s empty base method and charged nothing. It now calls
`after_agent` through a thread. Its test patched the async name onto a mock,
materializing a method that does not exist in production.

`stream_completed` was set after both `drain_error` breaks, so a failed drain
plus a failed rebind mounted "Offload finished, but..." alongside "Offload
could not complete". The rebind outcome is now recorded and reported by
`_handle_offload`, which also covers the inverse case the old flag missed: a
stream error that still committed the compaction is reconciled into a success
and now carries the warning.

Also:

- Read the hook outcome under the middleware's own `_PRE_TOOL_STATE_KEY`; a
  re-spelling yielded `{}` and compacted through a denial.
- Re-raise `GraphBubbleUp` out of the compaction handler, matching the hook
  handler, so control-flow exceptions are not reported as failures.
- Log a failed archive write at the call site; `_aoffload_to_backend` swallows
  `_ArchiveReadGuard`'s fail-closed error into a bare `None`.
- Report an empty state re-read as unconfirmed rather than "already compact".
- Log discarded hook fulfillments and tell the user those hooks will re-run.
- Check for `RemoteGraph._validate_client` explicitly so an SDK rename is not
  downgraded to a generic rebind warning.
- Make `_OffloadInput` total, name the graph factories, and type the forced
  compaction update as `SummarizationEvent` -- which surfaced that the
  summary message's `HumanMessage` shape was never verified.
- Correct docstrings claiming the graph cannot create a synthetic tool call
  (it does, in memory only), the THREAT_MODEL "no HITL interrupt" claim, and
  the conflicting accounts of what an empty run input does.
Pydantic rejects `typing.TypedDict` on Python < 3.12 when langgraph
routes the `/offload` operation graph's `input_schema` through its
schema inspection, failing CI on 3.11 with PydanticUserError.
…load `model_params`

The server-owned offload HTTP boundary type-checked `context.model_params`
but passed its values verbatim to `create_model`, where they merge into the
model constructor kwargs. Keys like `base_url`, `openai_proxy`,
`http_client`, or `default_headers` would redirect the summarizer's
credentialed provider calls to a client-chosen destination. The dev server
accepts connections from any local process (accepted risk T6), so the
request's model selection must not extend to its network plumbing.

The boundary now drops endpoint, proxy, and transport keys via a denylist in
`offload_api._strip_transport_model_params` — a denylist rather than an
allowlist because `create_model` serves arbitrary providers and an
allowlist would silently drop legitimate provider-specific params. The
in-process `CLIContextSchema` path stays unfiltered since those params come
from the user's own flags and config. Threat model updated to record the new
boundary control.

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/code/deepagents_code/client/remote_client.py
Mason Daugherty (mdrxy) and others added 19 commits August 24, 2026 10:53
A run that raises anything other than an interrupt or rollback leaves the
thread row on `error` until the next run completes, and `aensure_thread` uses
`if_exists="do_nothing"` so it never clears it. Requiring `idle` therefore
refused `/offload` for the entire window after a failed turn -- exactly when a
user reaches for it to recover from a context overflow -- and the 409 blamed an
"active or interrupted run" that did not exist.

Gate on a named set of quiescent statuses instead. In-flight work is still
caught by the separate `next`/`tasks`/`interrupts` check against the
checkpoint, which also covers an errored run that left a pending node.
Everything between `aoffload` returning and the SessionStart hook is local
reporting -- token math, message mounting, the status-bar update -- over a
conversation the server has already compacted and persisted. All of it sat
inside the try whose handler mounts "Offload failed", so any failure there told
the user the offload failed and sent them to run it again on an already
compacted conversation.

The SessionStart hook below it was already carved out for exactly this reason.
Track the commit point and give the generic handler the same treatment, naming
the reporting failure instead of the operation.
`OffloadStateUpdate` names three permitted checkpoint channels, but the runtime
guard tested for `messages` alone. The type stated "only these three"; the
check enforced "not this one", so a future merge adding any other channel --
`todos`, `files` -- would pass both the checker and the guard and reach the
checkpoint write unattributed to any run.

Derive the allowlist from the type's annotations so the two cannot drift, and
name the offending channels in the error.
`_OFFLOAD_API_VERSION`, `_OFFLOAD_PROTOCOL_VERSION`, and the
`GET /dcode/offload` capability route were a remnant of an abandoned
backwards-compatibility effort. No production code sent, read, or compared any
of them: the constants' only reader was a test pinning them to each other, and
the route's only caller was an integration assertion. Their docstrings claimed
version pinning that does not exist, which reads as an enforced contract.

Route authentication is still covered -- those assertions use the POST route,
not the capability probe.
A custom `graph_ref` server never registers dcode's HTTP app, so `/offload`
against one POSTs into a missing route and the SDK's error carries only
"404 Not Found" -- naming neither the cause nor a fix. An unregistered thread
cannot be confused with this: the server catches that case and answers 409 with
its own message.

Translate the 404 into a message that says the server does not provide the
operation and what to do about it.
`PreparedOperationCost` documents a use-exactly-once contract whose violation
it calls unrecoverable ("nothing can detect the loss afterwards"), but only the
rollback half set `_settled`. A commit left the instance indistinguishable from
a leak, so the destructive case -- a prepare that is neither committed nor
rolled back, silently deleting its spend from the thread total -- was the one
outcome with no trace.

Add `commit()` to settle a persisted delta, call it on both paths that
deliberately keep records claimed, and warn on collection when an instance is
still unsettled.
`_write_landed` returned `True` both when the thread demonstrably advanced and
when it could not be read at all, so the caller logged "failed after the thread
advanced" for a readback that never happened. Anyone auditing a missing charge
would read that line and conclude the spend was accounted for.

Return the three outcomes separately. Both non-`unchanged` cases still keep the
records claimed -- the bias against double-charging is unchanged -- but the
unreadable case now says the write may never have landed and names the amount
at risk, making an otherwise undetectable loss auditable.
The operation runs the agent's `PreCompact` and `PreToolUse` hooks, and the
server defaults a missing `approval_mode` to `manual`. `/offload` never sent
one, so a configured hook saw Manual even in Auto-Accept or YOLO -- a different
mode than the same hook sees on every interactive turn, where the adapter
injects both fields.

The boundary already type-checks `approval_mode` and `auto_approve`; nothing
was sending them.
Stripping endpoint/proxy/transport keys from a request's `model_params` was
entirely silent: nothing logged, nothing returned, nothing surfaced. A user
running against a self-hosted gateway got their `base_url` ignored for
`/offload` with no way to discover why.

Log the dropped key names -- names only, since the values are endpoints and
headers. Also record in the denylist's docstring that it is now a backstop
rather than the primary control, since `_checkpoint_model_context` discards the
request's model selection outright; a reader could not previously tell which of
the two overlapping mitigations was load-bearing.
- The context-fields comment cited "approval mode" as an example of a field the
  operation never touches, while `approval_mode` sat in the tuple it introduced
  and is read by `_hook_context`. Reframe it as what the boundary validates,
  and note that `classifier_model` is validated but unread.
- `_handle_offload` no longer writes graph state, so the shell-flush comment's
  "parity with the offload path's `aupdate_state`" and the token-display
  docstring's attribution both pointed at deleted behavior.
- The 409 enumeration omitted a registered thread carrying no checkpoint.
- `_validated_offload_result` said the renderer reads fields "positionally"; it
  subscripts them by key, which is the failure mode the rest of the paragraph
  describes.
- `DCA_TEST_OFFLOAD_GATE_ENV` sat under a comment introducing prompt markers
  and is neither, and `_wait_at_summary_gate` writes its marker on every
  request, not once.
- Lowercase `dcode` in the module docstring, as everywhere else.
The DC5 rewrite established that the archive leaf is a framework-minted
`session_<uuid4 hex>`, not the thread id, but four sites still asserted the old
reading: the DC5 summary-table row, the Gaps line, the input-coverage row, and
D4's entire rationale. D4's conclusion survives -- the filename still has no
user-controlled component -- but its stated reasoning did not.

Also:
- `offload.offload_messages_to_backend` does not exist anywhere in the repo.
  Point at the SDK's `_aoffload_to_backend`, reached through
  `CLICompactionMiddleware`.
- The C18 components row described the route as authenticated without the
  caveat TB10 and the coverage row both carry: the shipped server runs `noop`
  auth and relies on the loopback bind.
- Split the three multi-claim bullets (DC5 Producers, TB2, TB10) into one claim
  per sentence.
- Pad the two C18 diagram rows to the box width.
`_thread_lock` and its `async with` had no coverage at all: deleting the
acquisition, or keying the `WeakValueDictionary` on `operation_id` instead of
`thread_id`, broke nothing in the suite. The lock is what makes the
read-check-execute-recheck-write sequence meaningful for two same-process
requests, so it is worth pinning.

Assert that an offload blocks while the thread's lock is held externally, that
a different thread is unaffected, and that the registry returns one lock per
thread. Verified by mutation: re-keying the lock on `operation_id` fails the
first of these.
Every other route test fabricates a request with hand-written `path_params`, so
nothing checked that the app registers the paths and methods the client
actually calls. Renaming the converter to `{tid:str}` would leave the unit
suite green while the real handler raised `KeyError` -- neither `TypeError` nor
`ValueError`, so it escapes the 422 block and surfaces as a bare 500.

Drive both routes with `TestClient`, and pin that GET on the offload path is
405 now that the capability probe is gone. Verified by mutation: the converter
rename fails the first test.
`_event_cutoff` accepted any `int`, and `bool` is an `int` subclass, so a
malformed `cutoff_index: true` read as cutoff 1 -- silently shifting
`messages_offloaded`/`messages_kept` by one message and the already-compacted
short circuit with them. The HTTP boundary already excludes bools from
`model_context_limit` for this reason.

The helper also had no direct test at all; it was exercised only incidentally
through well-formed events. Cover every malformed shape.
Two guards had no test that would notice their removal:

- The `break` at the round limit exists because the loop runs one extra
  iteration to POST the last fulfillment and read the reply. Deleting it
  fulfills one hook too many while still reporting the lower number, and the
  existing test -- which asserts only the message and the logged ids -- stayed
  green. Assert the fulfillment count.
- `OffloadOperation.execute` must let a hook request reach the client rather
  than fold it into a `failed` result. Two mechanisms protect this (the
  `BaseException` base and the explicit re-raise) and either alone suffices, so
  the test asserts the outcome; mutating both together fails it.
`HttpClient.post` requires the keyword-only `json` argument, so the
Esc-cancellation path raised `TypeError` before reaching the server and
the operation could commit after the UI had cancelled it.
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
@mdrxy
Mason Daugherty (mdrxy) merged commit b494f43 into main Aug 24, 2026
56 checks passed
@mdrxy
Mason Daugherty (mdrxy) deleted the mdrxy/code/offload-seeded-compaction-no-hitl branch August 24, 2026 17:19
Mason Daugherty (mdrxy) pushed a commit that referenced this pull request Aug 24, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Release notes preview: keep this section in sync with the package
`CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`,
not this PR description — keep them aligned anyway so the PR stays an
accurate historical record for reviewers and anyone returning later._

---


##
[0.1.61](deepagents-code==0.1.60...deepagents-code==0.1.61)
(2026-08-24)

### Features

- Added `google_anthropic_vertex` provider support for Claude on Vertex
AI ([#5760](#5760)).
- Enforced configured model allowlists
([#5649](#5649)).
- Injected goal and rubric context directly, replacing `get_goal` and
`get_rubric`
([#5041](#5041)).
- Made `/offload` server-owned
([#5261](#5261)).
- Added prompt clipboard support
([#5733](#5733)).
- Show Auto approval review progress
([#5729](#5729)).

### Bug Fixes

- Kept long thread resumes responsive
([#5772](#5772)).
- Render first streamed text immediately
([#5761](#5761)).
- Show the incognito shell command widget
([#5768](#5768)).
- Only highlight actionable tool rows
([#5769](#5769)).
- Warn and ignore `--auto-approve` and `--yolo` in headless mode
([#5750](#5750)).
- Sweep expired history archives at startup
([#5751](#5751)).
- Clarified auth environment setup
([#5767](#5767)).

_End release notes preview._

---

> [!NOTE]
> A **community contributors** list and a **Special thanks** section
(crediting the users who filed the issues this release's PRs closed) are
appended to the GitHub release notes automatically at publish time (see
[Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 3).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dcode Related to `deepagents-code` do-not-close Bypass PR auto-deletion feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant