Skip to content

StateBackend.write() needs overwrite support (or add an overwrite/writeOrCreate method) #225

@Amedzro-Elikplim

Description

@Amedzro-Elikplim

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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions