[GH-ISSUE #225] StateBackend.write() needs overwrite support (or add an overwrite/writeOrCreate method) #47

Open
opened 2026-02-16 06:16:58 -05:00 by yindo · 1 comment
Owner

Originally created by @Amedzro-Elikplim on GitHub (Feb 12, 2026).
Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/225

Originally assigned to: @hntrl on GitHub.

Hey team, love the library — ran into a friction point with StateBackend.write() that I think others will hit
too.

Right now write() flat out refuses if the file already exists:

write(filePath, content) {
if (filePath in this.getFiles())
return { error: Cannot write to ${filePath} because it already exists... };
// ...
}

Which makes sense as a safety guard, but it becomes a real pain in multi-tool workflows where several tools
need to write to the same file across a graph execution.

What happens in practice

Say you have a pipeline like:

tool_A (writes /state.json) → tool_B (generates new data, needs to update /state.json) → tool_C (reads
/state.json)

tool_B's write() silently fails because tool_A already created the file. No error thrown, just a quiet {
error: "..." } return with no filesUpdate. If you're not explicitly checking for it (and honestly, who
expects a write to a file they own to fail?), downstream tools read stale data and things break in confusing
ways.

The workaround we found is chaining readRaw() + edit() with the entire file content as oldString:

const fileData = backend.readRaw(filePath);
const currentContent = fileData.content.join('\n');
const editResult = backend.edit(filePath, currentContent, newContent);

It works, but it feels wrong — we're using a surgical find-and-replace tool to do a full file overwrite. It's
like using sed to replace an entire file.

What would help

An overwrite flag on write():

write(filePath: string, content: string, overwrite?: boolean): WriteResult

When overwrite: true, skip the existence check — use updateFileData (preserving created_at) if the file
exists, createFileData if it doesn't. Default stays false so nothing breaks for existing users.

Or if you'd prefer to keep write() strict, a separate method would work just as well:

writeOrOverwrite(filePath: string, content: string): WriteResult

Why edit() isn't a great workaround

  • It's meant for targeted string replacements, not wholesale file overwrites
  • Passing empty oldString on a non-empty file errors out ("oldString cannot be empty when file has content")
  • You need to know that FileData stores content as string[] and join with \n to get the comparison string —
    that's leaking internals
  • It's two calls (readRaw + edit) for what should be one atomic operation

Why we can't just let the agent handle it

In a lot of real-world workflows, waiting for the agent LLM to write files is too slow and unreliable. When a
tool already has the exact data (parsed API response, generated content, synced DB state), writing it
directly from tool code is way faster and more predictable than:

  1. Returning the data to the agent
  2. Hoping the agent decides to write it
  3. Hoping it doesn't mangle the content on the way

This matters most for intermediate state files that downstream tools depend on — the write needs to be
deterministic inside the tool, not an optional LLM action. The current restriction pushes you toward either
accepting that overhead or hacking around it with readRaw() + edit().

Real-world example

We have an agent where:

  • sync-state pulls data from DB and writes /state.json
  • generate produces new content and needs to update the same file
  • persist reads the file and saves back to DB

This kind of iterative state-building across tools feels like a natural pattern for agentic workflows, and
having a clean way to overwrite files from tool code would make it a lot smoother.

Originally created by @Amedzro-Elikplim on GitHub (Feb 12, 2026). Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/225 Originally assigned to: @hntrl on GitHub. Hey team, love the library — ran into a friction point with StateBackend.write() that I think others will hit too. Right now write() flat out refuses if the file already exists: write(filePath, content) { if (filePath in this.getFiles()) return { error: `Cannot write to ${filePath} because it already exists...` }; // ... } Which makes sense as a safety guard, but it becomes a real pain in multi-tool workflows where several tools need to write to the same file across a graph execution. What happens in practice Say you have a pipeline like: tool_A (writes /state.json) → tool_B (generates new data, needs to update /state.json) → tool_C (reads /state.json) tool_B's write() silently fails because tool_A already created the file. No error thrown, just a quiet { error: "..." } return with no filesUpdate. If you're not explicitly checking for it (and honestly, who expects a write to a file they own to fail?), downstream tools read stale data and things break in confusing ways. The workaround we found is chaining readRaw() + edit() with the entire file content as oldString: const fileData = backend.readRaw(filePath); const currentContent = fileData.content.join('\n'); const editResult = backend.edit(filePath, currentContent, newContent); It works, but it feels wrong — we're using a surgical find-and-replace tool to do a full file overwrite. It's like using sed to replace an entire file. What would help An overwrite flag on write(): write(filePath: string, content: string, overwrite?: boolean): WriteResult When overwrite: true, skip the existence check — use updateFileData (preserving created_at) if the file exists, createFileData if it doesn't. Default stays false so nothing breaks for existing users. Or if you'd prefer to keep write() strict, a separate method would work just as well: writeOrOverwrite(filePath: string, content: string): WriteResult Why edit() isn't a great workaround - It's meant for targeted string replacements, not wholesale file overwrites - Passing empty oldString on a non-empty file errors out ("oldString cannot be empty when file has content") - You need to know that FileData stores content as string[] and join with \n to get the comparison string — that's leaking internals - It's two calls (readRaw + edit) for what should be one atomic operation Why we can't just let the agent handle it In a lot of real-world workflows, waiting for the agent LLM to write files is too slow and unreliable. When a tool already has the exact data (parsed API response, generated content, synced DB state), writing it directly from tool code is way faster and more predictable than: 1. Returning the data to the agent 2. Hoping the agent decides to write it 3. Hoping it doesn't mangle the content on the way This matters most for intermediate state files that downstream tools depend on — the write needs to be deterministic inside the tool, not an optional LLM action. The current restriction pushes you toward either accepting that overhead or hacking around it with readRaw() + edit(). Real-world example We have an agent where: - sync-state pulls data from DB and writes /state.json - generate produces new content and needs to update the same file - persist reads the file and saves back to DB This kind of iterative state-building across tools feels like a natural pattern for agentic workflows, and having a clean way to overwrite files from tool code would make it a lot smoother.
yindo changed title from StateBackend.write() needs overwrite support (or add an overwrite/writeOrCreate method) to [GH-ISSUE #225] StateBackend.write() needs overwrite support (or add an overwrite/writeOrCreate method) 2026-06-05 17:21:10 -04:00
Author
Owner

@christian-bromann commented on GitHub (Feb 23, 2026):

Thanks for raising the issue @Amedzro-Elikplim, great insights on the use case. We will take this to the team and discuss the best approach here. Will share more soon!

<!-- gh-comment-id:3947428832 --> @christian-bromann commented on GitHub (Feb 23, 2026): Thanks for raising the issue @Amedzro-Elikplim, great insights on the use case. We will take this to the team and discuss the best approach here. Will share more soon!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#47