create (Write)
# grade this stage (from your course directory)
npx theinference test --stage create --watchEvery read tool you built was harmless — view, glob, grep only look. This step crosses the line into side effects: create writes a brand-new file to disk, for real, inside the sandbox. It's the same Write tool the model reaches for when Claude Code scaffolds a file. Two things make a good create: it refuses to clobber an existing file, and it hands the loop back a result block clean enough to thread straight into the next turn.
Your tool returns a ToolOutput ({ content: string, is_error?: boolean }). The loop wraps it into a tool_result block — { type: 'tool_result', tool_use_id, content } — and feeds it straight back to the model as the next user turn. On success, return ok('created <path>'); on failure, set is_error so the model sees it went wrong and can recover.
What to build
Implement Tools.create
Implement create(path, fileText) on the Tools class. Resolve path through the sandbox guard (never trust a raw path), error if the file already exists (a fresh create must not overwrite), write fileText, and return a well-formed success result. Record the written content as the read-version anchor too — Chapter 4's edit guard will lean on it.
/** `create` — write a new file; error if it already exists. */
create(path: string, fileText: string): ToolOutput {
const { resolved, error } = this.resolve(path);
if (error) return err(error);
// 1. if the file already exists -> err(`already exists: ${path}`)
// 2. writeFileSync(resolved!, fileText)
// 3. anchor the read-version: this.readVersions.set(resolved!, hash(fileText))
// 4. return ok(`created ${path}`)
}
The existence check is the whole point of create versus a blind write. If the file is already there, return an error result — err('already exists: ' + path) — and write nothing. The model that wanted to edit an existing file should be reaching for str_replace, not create; surfacing the error is how it learns that.
process mode, stdout_json_match. The grader runs program.ts create, which builds a temp sandbox, calls create, then reads the file back off disk and prints { result, content }. Both must match the reference exactly: the success result.content ("created new.txt") and the on-disk bytes ("hello"). The byte-compare is what proves the write actually landed.
./your_program.sh create
# case: { files: {}, path: "new.txt", text: "hello" }
# -> {"result":{"content":"created new.txt"},"content":"hello"}