## Summary
This fixes a CI-breaking type mismatch in the LangSmith sandbox backend
introduced by the snapshot lifecycle update. `LangSmithSandbox.create()`
was using a `snapshotId` flow that did not match the current `langsmith`
SDK typings, which surfaced as unhandled Vitest typecheck errors in CI.
## Summary
Expose the new LangSmith sandbox snapshot and lifecycle APIs (landed in
`langsmith@0.5.20`) through the `LangSmithSandbox` wrapper.
- New methods on `LangSmithSandbox`: `start()`, `stop()`,
`captureSnapshot()`
- `LangSmithSandbox.create()` now accepts `snapshotId` (preferred) or
`templateName` (deprecated); throws if neither is provided
- Re-export upstream types as `LangSmithSnapshot`,
`LangSmithCaptureSnapshotOptions`, `LangSmithStartSandboxOptions`
- Bump `langsmith` peer dep to `>=0.5.20`
- Update the example to boot from `LANGSMITH_SANDBOX_SNAPSHOT_ID`
## Test plan
- [x] Unit tests: added coverage for `start()`, `stop()`,
`captureSnapshot()`; 41/41 pass
- [x] Type-check clean
- [x] Dev smoke test against `dev.api.smith.langchain.com` — create from
snapshot, execute, stop, start, execute after restart all working
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com>
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
### Summary
This PR optimizes memory usage by allowing us to use a single WASM
module. To do this we needed to fix two failure modes that occur when
multiple sessions share a single WASM module. Both are caused by the
async module loader creating asyncify suspensions (unwind/rewind cycles
on a module-global stack buffer) during import resolution.
__Failure 1__: Concurrent eval crash. The asyncify variant allows only
one async operation per module at a time. If two sessions eval
simultaneously and both hit an import, the second eval tries to suspend
asyncify while the first is already suspended. This crashes with
QuickJSAsyncifySuspended: Already suspended.
Fix: AsyncEvalQueue queues evalCodeAsync calls so only one runs at a
time.
__Failure 2__: Silent disposal corruption. When a session imports a
multi-file skill (e.g. an entry point that imports greet.js and
shout.js), each import causes an asyncify unwind/rewind cycle. The eval
succeeds. But when the session's runtime is disposed, the disposal does
not fully reset the module's asyncify stack buffer and the unwind/rewind
cycles left residual state (saved stack frames, rewind pointers) that
emscripten manages at the module level, not the runtime level. The next
session creates a fresh runtime on the same module, but the corrupted
asyncify state causes module loader callbacks to silently never fire.
Fix: synchronous module loader. Skill files are preloaded into a Map
before eval starts. The loader callback does a plain map.get(). No
await, no asyncify suspension, no residual state on disposal.
Note: The eval queue alone does not fix failure 2. The corruption
happens at runtime disposal, which runs outside the queue. Both fixes
are required.
#### What This PR Adds
- Replaces per-session WASM module instantiation with a process-global
singleton shared by all sessions. Each session still gets its own
runtime and context (full isolation); only the underlying WASM module is
shared. Reduces WASM memory from ~16 MB per session to ~16 MB total.
- Adds AsyncEvalQueue to serialize evalCodeAsync calls across sessions,
enforcing the asyncify one-concurrent-async-call-per-module constraint.
- Makes the module loader synchronous by preloading skill files into an
in-memory cache before eval (preloadReferencedSkills). Why is this
needed? An async module loader causes asyncify suspensions on each
import. Disposing a runtime after multi-file skill imports corrupts the
shared module's asyncify state and silently breaks the loader for all
subsequent sessions. The sync loader eliminates asyncify suspensions
from imports entirely.
#### Synchronous Module Loading
As stated above, synchronous module loading is a necessary change due to
async module loading causing asyncify suspensions on each import which
corrupts the singleton WASM module's asyncify state which silently
breaks the loader for all subsequent sessions. The following
demonstrates the before and after regarding this change:
__Before__:
User code: `import { run } from "@/skills/demo"`
1. eval() is called
2. evalCodeAsync() runs the code
3. QuickJS hits the import, calls the module loader callback
4. The callback is async: await ensureSkillLoaded("demo")
→ fetches /skills/demo/index.js from the backend (network I/O)
→ this await causes the first asyncify suspension in the WASM module
5. index.js has: import { greet } from "./greet.js"
6. QuickJS calls the loader again for greet.js
→ await ensureSkillLoaded() → network fetch
→ The second asyncify suspension in the WASM module is hit
7. index.js has: import { shout } from "./shout.js"
8. QuickJS calls the loader again for shout.js
→ await ensureSkillLoaded() → network fetch
→ third asyncify suspension in the WASM module is hit
9. eval completes, runtime is disposed
10. runtime.dispose() is called to clean up the session
11. The runtime disposal does NOT fully reset the module's asyncify
stack buffer. The 3 unwind/rewind cycles left residual state (saved
stack frames, rewind pointers, etc.) that the dispose path doesn't know
to clean up, because asyncify bookkeeping is managed by emscripten at
the module level, not by QuickJS at the runtime level.
12. Next session creates a fresh runtime on the same module, but the
module's asyncify machinery is corrupted. When QuickJS tries to call the
new session's module loader, the stale asyncify state causes the
callback to silently not fire. No crash, no error. Imports just resolve
to nothing.
__After__:
User code: `import { run } from "@/skills/demo"`
1. eval() is called
2. preloadReferencedSkills() runs BEFORE evalCodeAsync():
→ scans source code for "@/skills/*" specifiers
→ finds "demo"
→ fetches all 3 files from the backend into a Map:
"@/skills/demo" → source code
"@/skills/demo/greet.js" → source code
"@/skills/demo/shout.js" → source code
→ (this is async but happens OUTSIDE the WASM module so no asyncify)
3. evalCodeAsync() runs the code
4. QuickJS hits the import, calls the module loader callback
5. The callback is sync: map.get("@/skills/demo") → returns source
immediately
→ NO asyncify suspension
6. QuickJS resolves greet.js → map.get("@/skills/demo/greet.js") →
immediate
→ NO asyncify suspension
7. QuickJS resolves shout.js → map.get("@/skills/demo/shout.js") →
immediate
→ NO asyncify suspension
8. eval completes, runtime is disposed
9. Zero asyncify suspensions occurred → no residual state → module is
clean
10. Next session works perfectly
### Tests
- 4 new AsyncEvalQueue unit tests: serialization (no interleaving of
concurrent ops), return values, error isolation (rejected op doesn't
break queue), insertion order preservation
- All 160 existing tests pass (session, skills, middleware, transform,
utils, eval queue)
### Memory Benchmarking
#### Per-session module (before)
| Sessions | RSS Delta (MB) | Per-Session RSS (MB) | WASM Memory (MB) |
Module Init (ms) | Setup (ms) |
|----------|----------------|----------------------|------------------|------------------|------------|
| 1 | 0.53 | 0.53 | 16 | 11.4 | 15.9 |
| 5 | 2.13 | 0.43 | 80 | 30.2 | 37.9 |
| 10 | 5.28 | 0.53 | 160 | 31.1 | 42.8 |
| 15 | 5.53 | 0.37 | 240 | 57.4 | 72.1 |
| 20 | 6.80 | 0.34 | 320 | 73.6 | 91.1 |
---
#### Singleton (after)
| Sessions | RSS Delta (MB) | Per-Session RSS (MB) | WASM Memory (MB) |
Module Init (ms) | Setup (ms) |
|----------|----------------|----------------------|------------------|------------------|------------|
| 1 | 0.61 | 0.61 | 16 | 6.7 | 9.1 |
| 5 | 0.59 | 0.12 | 16 | 12.5 | 28.5 |
| 10 | 0.91 | 0.09 | 16 | 12.2 | 37.0 |
| 15 | 1.81 | 0.12 | 16 | 12.0 | 45.1 |
| 20 | 1.70 | 0.09 | 16 | 10.4 | 50.4 |
---
#### Comparison at 20 sessions (realistic workload)
| Metric | Before | After | Improvement |
|------------------------|---------|--------|-----------------|
| WASM virtual memory | 320 MB | 16 MB | 20x reduction |
| RSS delta | 6.80 MB | 1.70 MB| 4x reduction |
| Module instantiation | 73.6 ms | 10.4 ms| 7x reduction |
| Setup time | 91.1 ms | 50.4 ms| 1.8x reduction |
### Summary
- Rename `createQuickJSMiddleware` → `createREPLMiddleware` and
`QuickJSMiddlewareOptions` → `REPLMiddlewareOptions` to match the Python
`REPLMiddleware` naming
- Rename middleware registration from `"QuickJSMiddleware"` →
`"REPLMiddleware"`
- Rename default tool name from `"js_eval"` → `"eval"`, now configurable
via `toolName` option
- Add `captureConsole` option (default `true`) to toggle console output
capture
- Align default memory limit to 64 MiB (was 50 MiB) and timeout to 5s
(was 30s) to match Python
### Tests
- All 156 existing unit tests pass with updated references
- Updated `middleware.test.ts`, `middleware.int.test.ts`, and example
files to use new names
---------
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
## Problem
When using `@langchain/google` as a model provider in the examples
workspace, the server crashes immediately with:
1. Runtime crash on startup:
```bash
ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath './utils/uuid' is not
defined by "exports" in @langchain/core/package.json
```
`@langchain/google` imports `@langchain/core/utils/uuid`, a subpath
added in `@langchain/core@1.1.42`. Both the examples workspace and
`libs/deepagents` were pinned to `^1.1.40` which does not export this
path.
2. TypeScript type error after fixing the runtime crash:
```bash
Type 'ChatGoogle' is not assignable to type 'BaseLanguageModel<any,
BaseLanguageModelCallOptions>'
```
Even after bumping `@langchain/core` in `examples/` only, pnpm kept two
separate copies in its store - `1.1.40` for `deepagents` and `1.1.42`
for `@langchain/google`. TypeScript treats them as structurally
incompatible types because the type import paths differ.
## Fix
- Bump `@langchain/core` from `^1.1.40` → `^1.1.42` in all
`../package.json` files
## Testing
```bash
cd examples/research
pnpm dlx @langchain/langgraph-cli dev
# -> No ERR_PACKAGE_PATH_NOT_EXPORTED and agent starts correctly
---------
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
## Summary
Adds LangSmith code-language metadata to the QuickJS REPL evaluation
tool so tool inputs are correctly interpreted as JavaScript in traces.
## Changes
### @langchain/quickjs
- Added `metadata: { ls_code_input_language: "javascript" }` to the
`js_eval` tool definition in `createQuickJSMiddleware`.
- Extended middleware tool-registration tests to assert that `js_eval`
exposes `ls_code_input_language` metadata.
- Verified behavior with quickjs unit tests (`src/middleware.test.ts`).
### Summary
Adds functional skill modules to the QuickJS REPL. Skills can now ship
JS/TS entrypoints via a module: frontmatter key on SKILL.md, which the
model dynamically imports at eval time with await
import("@/skills/<name>").
When skillsBackend is passed to createQuickJSMiddleware, the middleware
reads skillsMetadata from the current task state before each eval, scans
the source for skill references, and pushes the metadata + backend into
the session. The session wires a QuickJS module loader that lazily
fetches skill files from the backend, strips TypeScript syntax, and
caches the result for the lifetime of the session. Skills that are
referenced but not available on the agent short-circuit with a clear
error before evaluation starts.
On the deepagents side, SkillMetadata gains an optional module field
(validated and normalized from frontmatter), and formatSkillsList now
advertises importable skills in the system prompt with an await
import(...) hint.
```ts
const backend = new FilesystemBackend({ rootDir: "/project", virtualMode: true });
const agent = createDeepAgent({
model: "claude-sonnet-4-6",
backend,
skills: ["/skills/"],
middleware: [
createQuickJSMiddleware({ skillsBackend: backend }),
],
});
```
### Tests
- Unit tests cover module path validation, TS syntax stripping, skill
loading (single/multi-file, error cases, bundle size cap), skill
reference scanning, module loader resolution and caching, and system
prompt formatting
- Ran an E2E eval that exercised the full pipeline from SKILL.md parse
through dynamic import and function call in the REPL, with a LangSmith
trace
### Benchmarks
#### memory: console_log concurrent
- Total time: 15820ms
| name | hz | min | max | mean | p75 | p99 | p995 | p999 | rme | samples
|
|--------------------------|---------|--------|--------|--------|--------|--------|--------|--------|--------|---------|
| 1 concurrent sessions | 145.91 | 1.8263 | 27.7702| 6.8536 | 9.1753 |
21.1492| 25.0420| 27.7702| ±5.30% | 730 |
| 8 concurrent sessions | 39.1773 | 15.5854| 61.5015| 25.5250| 27.8461|
49.0536| 61.5015| 61.5015| ±3.58% | 196 |
| 32 concurrent sessions | 13.9753 | 48.5504| 110.40 | 71.5547| 76.6394|
110.40 | 110.40 | 110.40 | ±3.72% | 70 |
---
#### memory: ptc_tools concurrent
- Total time: 16755ms
| name | hz | min | max | mean | p75 | p99 | p995 | p999 | rme | samples
|
|--------------------------|---------|--------|--------|--------|--------|--------|--------|--------|--------|---------|
| 1 concurrent sessions | 86.9205 | 4.1809 | 32.1833| 11.5048| 15.3222|
25.2676| 28.2697| 32.1833| ±4.76% | 435 |
| 8 concurrent sessions | 21.8310 | 33.5598| 69.0041| 45.8065| 50.8740|
60.2001| 69.0041| 69.0041| ±2.77% | 110 |
| 32 concurrent sessions | 7.0269 | 137.88 | 148.90 | 142.31 | 144.05 |
148.90 | 148.90 | 148.90 | ±0.70% | 36 |
---
#### throughput: single thread many iterations
- Total time: 31656ms
| name | hz | min | max | mean | p75 | p99 | p995 | p999 | rme | samples
|
|-------------------------------------------|--------|--------|--------|--------|--------|--------|--------|--------|--------|---------|
| 200 sequential evals (ptc + console.log) | 3.5897 | 240.92 | 347.19 |
278.58 | 289.81 | 341.02 | 347.19 | 347.19 | ±1.44% | 108 |
## Description
Updates the Deep Agents README badge to point to the current JavaScript
X handle while preserving the existing visible Follow text casing.
## Test Plan
- [ ] Verify the README X badge links to @langchain_js
_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>
### Summary
ReplSession uses a static map to preserve QuickJS WASM runtime state
across js_eval calls within a run. Previously sessions were never
cleaned up, causing unbounded memory growth as each thread_id
accumulated a live WASM runtime for the lifetime of the process.
- Added ReplSession.deleteSession(key) to dispose of the QuickJS runtime
and removes the entry from the session map
- Added an afterAgent hook in createQuickJSMiddleware that calls
deleteSession for the current thread's session key at the end of each
agent run
Sessions still persist across all js_eval calls within a single run. The
map remains for correct concurrent thread isolation. Without it,
simultaneous runs from different threads would share a runtime.
### Tests
Unit tests covering the following scenarios:
- ReplSession.deleteSession disposes and removes an existing session
- ReplSession.deleteSession is a no-op for a key that does not exist
- afterAgent disposes the session for the finished thread
- afterAgent on a thread with no session does not throw
- afterAgent only removes the session for the finished thread, leaving
other active threads intact
### Summary
- Removed built-in readFile/writeFile globals from the QuickJS REPL.
File I/O is now exclusively through PTC tools, keeping the REPL a pure
JS sandbox with no implicit side effects
- ptc now accepts StructuredToolInterface instances directly alongside
tool name strings. Custom tools can be injected into the REPL without
being registered on the agent
- StateBackend now provides read-your-writes semantics within a single
js_eval superstep by reading via __pregel_read with fresh=true. Pregel
already tracks pending sends in task.writes; asking for a fresh read
applies them through the reducer, removing the need for a manual write
buffer
- Removed ptc: boolean shorthand from QuickJSMiddlewareOptions; use the
array or object forms instead
### Tests
- Added 12 unit tests to state.test.ts covering write-then-read, chained
edits, ls/grep/glob/readRaw visibility, upload/download, duplicate write
error, edit-nonexistent, and superstep boundary behavior
(commitSends/clearPending)
- Expanded middleware.test.ts with PTC instance injection coverage
- Verified end-to-end with createDeepAgent + createQuickJSMiddleware
across 7 scenarios (write-read, write-edit-read, write-ls, write-grep,
write-glob, cross-eval, upload-download); traces confirmed in LangSmith
---------
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
### Summary
- Adds a FilesystemPermission class and a permissions parameter to
createDeepAgent and SubAgent that controls which filesystem operations
each agent is allowed to perform
- Rules are evaluated first-match-wins with a permissive default; mode:
"deny" blocks the operation before any backend call
- Permissions are closed over at tool-creation time — no runtime config
threading, no ToolPolicy in langchain-core
- ls, glob, and grep enforce permissions in two passes: the base path
check throws early; results are post-filtered to strip entries the
caller can't read
- Subagents receive the parent agent's permissions by default; setting
permissions on a SubAgent is a full replacement, not a merge
- FilesystemPermission, FilesystemPermissionOptions,
FilesystemOperation, and PermissionMode are exported from the top-level
package entry point
### Tests
- permissions/types.test.ts — FilesystemPermission construction,
validation (rejects relative paths, .., ~), defaults
- permissions/enforce.test.ts — validatePath, globMatch (wildcards,
brace expansion, dotfiles), decidePathAccess (first-match-wins,
operation mismatch, multiple ops/paths)
- middleware/fs.permissions.test.ts — each fs tool (read, write, edit,
ls, glob, grep) with allow/deny rules and mock backends; verifies
backend is never called on a denied path; verifies execute is unaffected
- permissions/agent.permissions.test.ts —
CreateDeepAgentParams.permissions type and default; wiring to
createFilesystemMiddleware
- middleware/subagents.permissions.test.ts — SubAgent.permissions type;
?? resolution (inherit vs override vs own deny);
createFilesystemMiddleware behavior for each resolved-permissions case
---------
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
## Fix: `ERR_MODULE_NOT_FOUND` on Windows when building `deepagents`
### Problem
Building `libs/deepagents` on Windows produces a `dist/index.js` that
imports
local modules with `.ts` extensions instead of bundling them:
```js
import { createDeepAgent } from "./agent.ts";
import { ConfigurationError } from "./errors.ts";
```
This causes an immediate crash at runtime for any consumer of the
library:
```
Error [ERR_MODULE_NOT_FOUND]: Cannot find module
'C:\...\deepagentsjs\libs\deepagents\dist\agent.ts'
imported from C:\...\deepagentsjs\libs\deepagents\dist\index.js
```
**Root Cause**
The `external` filter in `libs/deepagents/tsdown.config.ts:` used a regex intended to match
only npm package names (which don't start with `.` or `/`):
```js
const external = [/^[^./]/];
```
On Unix absolute paths start with `/` so the regex correctly excludes them. On Windows absolute paths start with a drive letter (e.g. C:\...), which also doesn't start with `.` or `/` - so rolldown matches them as external, excludes them from the bundle, and leaves raw `.ts` references in the output.
**Fix**
Replace the regex with a function that explicitly excludes Windows-style
absolute paths:
```js
const external = (id: string) =>
!id.startsWith(".") && !id.startsWith("/") &&
!/^[A-Za-z]:[\\/]/.test(id);
```
After this change `pnpm -w run build` in `libs/deepagents` produces a fully
bundled `[dist/index.js]` with no `.ts` imports and examples run correctly on Windows.
Notes
- `deps.neverBundle` (the tsdown-recommended replacement for `[external]` is not yet functional in tsdown 0.21.7 / rolldown 1.0.0-rc.12 and causes a build error. This fix is the correct workaround until that API stabilises.
---------
Co-authored-by: Hunter Lovell <hunter@hntrl.io>
## Summary
Fixes#398
Align deepagents summarization with the active request-time model path
instead of relying on a separate middleware-specific model resolution
path. This keeps summarization behavior consistent with the main agent
model selection flow and avoids divergence in provider/model handling
during middleware execution. The update also simplifies runtime behavior
by removing unnecessary explicit config threading for summary calls.
## Changes
### `deepagents` middleware/model routing
- Updated summarization middleware to prefer `request.model` as the
resolved model used for profile-derived limits and summary generation.
- Kept fallback model resolution support for cases where a request model
is not present.
- Removed explicit runtime config plumb-through in summary invocation,
relying on LangChain runnable context propagation.
### `deepagents` agent wiring
- Updated `createDeepAgent` built-in and subagent middleware wiring to
construct summarization middleware without explicitly passing a separate
model option, so summarization follows the active request model path.
### `deepagents` tests
- Added/updated summarization tests to cover request-model-based
summarization behavior and ensure middleware behavior remains stable
with the new routing path.
- Simplified test request construction to keep request-model assumptions
explicit.
## Summary
- Add `-L` flag to `find` in `buildLsCommand`, `buildFindCommand`, and
`buildGrepCommand` so that symlinks are followed
- Fixes#447
## Problem
`BaseSandbox` helper functions use `find` without `-L`, which means
symbolic links are not dereferenced. On Modal (and potentially other
providers), volumes are mounted as symlinks, so the `ls`, `glob`, and
`grep` sandbox tools all return empty results when pointed at symlinked
paths.
## Fix
The `-L` flag tells `find` to follow symbolic links. This is a one-line
change in each of the three helper functions:
```diff
- find ${quotedPath} -maxdepth 1 ...
+ find -L ${quotedPath} -maxdepth 1 ...
```
This matches user expectations — filesystem exploration tools should
transparently follow symlinks, just like `ls` and `find -L` do in a
normal shell.
## Test plan
- [ ] Verify `ls` tool returns files when the target path is a symlink
- [ ] Verify `glob` tool traverses symlinked directories
- [ ] Verify `grep` tool searches inside symlinked directories
- [ ] Confirm no regression on non-symlinked paths (behavior is
identical when no symlinks are present)
---------
Co-authored-by: Itay Cohen <itaycohen@Itays-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Description
Ports the skill loading fix from the Python SDK
(langchain-ai/deepagents#2721) to JS. The system prompt now instructs
agents to pass `limit=1000` when reading skill files via `read_file`,
since the default of 100 lines is too small for most skill files.
## Test Plan
- [ ] Verify skill loading prompt includes `limit=1000` instruction
_Opened collaboratively by Sydney Runkle and open-swe._
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
## Changes
### `libs/deepagents`
- Rewrote `README.md` from an exhaustive reference style to a minimal
structure:
- concise overview and "What's included"
- short install + quickstart example
- brief customization example
- LangGraph-native note, value proposition bullets, docs/resources, and
security section
- Updated top docs anchor link to JavaScript Deep Agents docs.
- Changed `<picture>` fallback `<img>` source from `logo-dark.svg` to
`logo-light.svg` for better npm/default rendering.
- Removed duplicated deep-dive sections and verbose option-by-option
content in favor of docs links.
## Summary
Fixes#443
- Removed static `import { ChatAnthropic } from "@langchain/anthropic"`
at module level
- Changed default `model` parameter from `new
ChatAnthropic("claude-sonnet-4-6")` to the string `"claude-sonnet-4-6"`
## Problem
`deepagents@1.9.0` unconditionally imported `@langchain/anthropic` at
the top of `agent.ts`. Bundlers follow static imports regardless of
which model the user actually uses, causing builds to fail for users who
only use other providers (e.g. `ChatOpenAI`) and don't have
`@langchain/anthropic` installed.
## Solution
`createAgent` from `langchain` natively accepts a model name string
(`string | AgentLanguageModelLike`), so no `ChatAnthropic` instantiation
is needed at the library level. With a string default, bundlers have no
static reference to `@langchain/anthropic` and can tree-shake it
entirely.
Users who want Anthropic models can either:
- Pass `new ChatAnthropic(...)` explicitly (requires
`@langchain/anthropic` in their own `package.json`)
- Rely on the `"claude-sonnet-4-6"` string default (also requires
`@langchain/anthropic` at runtime, but no longer breaks builds for
non-Anthropic users)
---------
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
Aligned `StoreBackend` in `libs/deepagents/src/backends/store.ts` more
closely with the Python version while keeping the current TS behavior
working:
- `namespace` now supports either a static `string[]` or a dynamic
factory, so callers can derive store namespaces from runtime
state/config instead of hardcoding them.
- Legacy namespace fallback now checks config metadata for
`assistant_id` / `assistantId` first, then falls back to the existing
injected `assistantId`, and finally to `["filesystem"]`.
- Added exported types for the new namespace factory context in
`libs/deepagents/src/backends/index.ts` and
`libs/deepagents/src/index.ts`.
* fix: add default value to grep tool glob schema for strict mode compatibility
The grep tool's `glob` field was defined with `.optional().nullable()` but
without `.default()`, causing it to be excluded from the JSON schema
`required` array. This violates OpenAI's strict mode requirement that all
properties must be listed in `required`.
Fixes#401
* Fix grep tool glob schema for strict mode
Added a default value to the grep tool glob schema to ensure compatibility with strict mode.
* Update .changeset/lucky-candles-reply.md
---------
Co-authored-by: Christian Bromann <git@bromann.dev>
* feat(sdk): evict large HumanMessages to filesystem
Port of python PR #2183 to JS. Adds a beforeAgent hook to the
FilesystemMiddleware that checks the most recent message. If it is a
HumanMessage whose text content exceeds toolTokenLimitBeforeEvict, the
full content is written to the backend and the message is replaced with
a truncated preview pointing to the file.
Changes:
- Add TOO_LARGE_HUMAN_MSG template for evicted human messages
- Add extractTextFromMessage() to extract text from any message type
- Add buildEvictedHumanContent() to preserve non-text blocks (images)
- Add beforeAgent hook to createFilesystemMiddleware
- Add comprehensive tests for HumanMessage eviction
Co-authored-by: Christian Bromann <christian-bromann@users.noreply.github.com>
* format
* Create angry-jobs-film.md
* refactor: align with final Python PR - tag-based eviction architecture
Rework the HumanMessage eviction to match the final shipped Python PR:
Architecture change:
- beforeAgent: writes large content to backend and TAGS the message
with lc_evicted_to in additional_kwargs (preserving original content
in state for checkpoint persistence)
- wrapModelCall: truncates all tagged messages before sending to model
(model sees preview, state keeps full content)
Key differences from initial port:
- Separate humanMessageTokenLimitBeforeEvict option (default: 50000)
- Original content preserved in state (not replaced)
- Messages tagged with lc_evicted_to for multi-turn truncation
- Path changed from /large_messages/ to /conversation_history/
- Added buildTruncatedHumanMessage helper for model-view truncation
- wrapModelCall now handles truncation of all tagged messages
Tests updated:
- beforeAgent tests verify tagging behavior (original content preserved)
- New wrapModelCall test suite for truncation of tagged messages
- Test for skip already-tagged messages
- Test for mixed tagged/untagged message lists
- Test for non-text block preservation during truncation
Co-authored-by: Christian Bromann <christian-bromann@users.noreply.github.com>
* format
* fix: update beforeAgent to use resolveBackend after main merge
The merge from main replaced getBackend/StateAndStore with
resolveBackend/BackendRuntime. Update the beforeAgent hook to use the
new API.
Co-authored-by: Christian Bromann <christian-bromann@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Christian Bromann <christian-bromann@users.noreply.github.com>