langchain-ai / langchain-ai/deepagentsjs

deepagents/browser has no non-deprecated way to store agent files

Open
#834 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1.6k
Forks
272
Avg merge
1d 13h
Merged PRs (30d)
38

Description

## Summary

`deepagents` ships and recommends a browser entrypoint, and `StateBackend` is the backend in its own browser-safe example. But in a browser, the only way to construct `StateBackend` that actually works is the one the code calls "legacy" and whose return channel is already marked `@deprecated`. The mode it's being replaced by requires `AsyncLocalStorage`, which browsers don't have.

So the documented browser setup currently has no non-deprecated way to work, and completing that deprecation would leave `deepagents/browser` without a working filesystem at all.

I'd like to see a supported path for the browser here — either the runtime form stays, or zero-arg gains a way to work without ambient context.

Versions: `deepagents@1.13.2`, `langchain@1.5.10`, `@langchain/core@1.2.9`, `@langchain/langgraph@1.4.13`.

## What happens

**Live demo, no API key needed:** https://stackblitz.com/edit/c5ksaquc

It runs three cases on load and prints the results. This issue is about **cases A and B**, which build the same agent twice and change only the `backend`:

| case | `backend` | result |
| --- | --- | --- |
| A | `new StateBackend()` | ❌ `TypeError: Cannot read properties of undefined (reading 'configurable')` |
| B | `(runtime) => new StateBackend(runtime)` | ✅ writes the file |

(Case C is a separate `task` bug, filed as #833.) The models are fake — they return a fixed tool call — so there's no API key and the result is the same every time.

This also means the README's browser example is incomplete: it imports `StateBackend` from `deepagents/browser`, but constructing it the plain way, `new StateBackend()`, throws in the environment that example is written for.

## Why case A can't work in a browser

This isn't something a caller can fix by passing different arguments. `files` is a getter, so it takes none:

```js
get files() {
if (this.runtime) return this.runtime.state.files ?? {};
const read = getConfig().configurable?.[PREGEL_READ_KEY];
```

There's nowhere to hand it a config, so without `AsyncLocalStorage` it has no way to reach the state. And there is none in a browser: bundlers resolve `@langchain/langgraph` to its browser build, which — unlike the Node one — never calls `initializeAsyncLocalStorageSingleton()`. `@langchain/core` then falls back to `MockAsyncLocalStorage`, and the lookup returns `undefined`. The demo prints this before running the cases:

```
AsyncLocalStorage in use: MockAsyncLocalStorage
getStore() -> undefined
getRunnableConfig() -> undefined
```

Case A alone was reported in #547 and closed as not planned. On its own that's defensible, since `createDeepAgent` defaults to the case B form. The problem is what case B is labelled as.

## The conflict

Case B is the only option in a browser, and it's the one being phased out:

- the code calls it "the legacy factory pattern" (`get isLegacy()`)
- in that mode, state changes come back through `filesUpdate`
- `filesUpdate` is marked deprecated:

```
@deprecated Zero-arg backends send state updates internally via
`__pregel_send`. Check `if (result.filesUpdate)` before using.
```

- `__pregel_send` comes from `getConfig()` — `AsyncLocalStorage` again

So the replacement for the deprecated mode is precisely the one that cannot run in a browser. Right now every browser user of `deepagents/browser` is on a deprecated path with nowhere to move to, whether they know it or not — including anyone who just calls `createDeepAgent` without touching `backend`, since its default is `backend = (config) => new StateBackend(config)`, the "legacy" form.

## What's needed

A browser entrypoint that's advertised as recommended needs a supported, non-deprecated way to store agent files. Concretely, one of:

- **keep the runtime/factory form as supported API**, and drop the "legacy" framing — outside Node it isn't a legacy option, it's the only one; or
- **give zero-arg mode a way to receive state without ambient context**, so it works under the `browser` condition too.

If the second is the intended direction, it would help to know before `filesUpdate` is removed, since there's no migration available today.

## Related

- #833 — the `task` tool is already broken in browser builds for the same underlying reason (`getCurrentTaskInput()` called without the config it has in hand).
- #553 — the same missing-config mistake in `@langchain/quickjs`, still open.
- #612, #616, #617 — other open browser reports.
- For contrast, the same file already handles missing context correctly elsewhere: `StoreBackend.getState()` and `getNamespaceConfig()` wrap the same kind of lookup in `try/catch` and fall back.

---

_Disclosure: this report was researched and written with the help of an AI assistant (Claude). The demo runs as described, and every code reference above was checked against the published `deepagents@1.13.2` build._

Contributor guide

Open the contributing guide

Research direction

Start with the deepagents/browser entrypoint and the StateBackend implementation, especially the files getter and the zero-argument versus runtime factory forms described in the report. Reproduce cases A and B in the linked StackBlitz demo, then inspect the README browser example. Done means the documented browser setup has a supported, non-deprecated way to store agent files and the example works.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.