-
Notifications
You must be signed in to change notification settings - Fork 117
Description
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:
- Returning the data to the agent
- Hoping the agent decides to write it
- 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.