Source Code:
src/gaia/agents/tools/file_io_tools.pyComponent: FileIOToolsMixin
Module:
gaia.agents.tools.file_io_tools
Import: from gaia.agents.tools.file_io_tools import FileIOToolsMixinOverview
FileIOToolsMixin provides comprehensive file I/O operations with intelligent file type detection, syntax validation, and security controls. It handles Python files with full AST analysis, markdown with structure extraction, and generic text files with appropriate encoding. Key Features:- Read files with automatic type detection and analysis
- Write Python files with syntax validation
- Edit files with diff generation
- Search code across directories
- Generate diffs for changes
- Markdown file operations
- Generic file operations without validation
- Security through path validation
Concurrent file changes
write_file, write_python_file, write_markdown_file, update_gaia_md,
replace_function, edit_file, and edit_python_file refuse a file whose
mtime or size changed since the agent read it, and refuse one it has never read
at all. Rejection leaves the file and its backups untouched. A successful write
records the file again, so the agent’s own change does not block its next edit.
Creating a new file needs no read.
See Read before edit for what counts as a read, the exact
refusal shape, and how the rule interacts with confirmation prompts.
No backup, no write
Every tool that modifies an existing file backs it up first. If that backup cannot be made — a full disk, an unwritable cache directory —write_file,
write_python_file, write_markdown_file, replace_function, edit_file, and
edit_python_file refuse the change and leave the file untouched. The error
names the cause and the directory to free or make writable.
The refusal carries executed: False, because nothing ran. A tool result
without it is treated as having executed and failed, which is how a one-off
full disk would otherwise be stored as a durable “writing this file is broken”
lesson and replayed into later prompts long after the disk was freed.
Host Agent Contract
FileIOToolsMixin is a mixin, not a base class — it reads self.path_validator
off the host, which nothing sets for it. A class mixing it in must set
self.path_validator to a PathValidator instance before _register_tools()
runs.
ChatAgent (hub/agents/chat/python/gaia_agent_chat/agent.py)
sets it before calling super().__init__(), because _register_tools() runs
inside that call.
A host that forgets is told what’s missing instead of the tool silently
skipping its own check, or crashing with a bare, unhelpful AttributeError:
- Write-capable tools (
write_python_file,edit_python_file,write_markdown_file,write_file,edit_file,replace_function) report the missing setup and don’t touch the target file, instead of writing without running the check that would otherwise have applied. - The remaining tools (
read_file,search_code,generate_diff,update_gaia_md) raise aMissingHostAttributeErrornaming the host class, the missing attribute, and this section.
self.path_validator has no legitimate None value — unlike RAGToolsMixin’s
self.rag, there is no “intentionally disabled” state here.
Tool Specifications
1. read_file
Read any file and intelligently analyze based on file type. A successful read, a paged one included, is what lets the write and edit tools change the file — see Read before edit. Parameters:file_path(str, required): Path to the file to read
.docx, .pdf, .xlsx) return their extracted
text instead of [Binary file], one part at a time. A part is at most the
model’s tool-result budget (limit can shrink it) and ends on a line break:
start_line/end_line work on the extracted text too. Extraction is cached
per file while it is unchanged, so paging does not re-parse it. A scanned PDF
with no text layer, an encrypted one, or a corrupt file is an error naming the
file; .doc, .pptx, .xls and the OpenDocument formats are an error pointing
at index_document.
When the agent also has index_document, the mixin adds a short
READING DOCUMENTS prompt fragment: to review, compare, reconcile, audit or
summarise a few documents, read each in full with read_file; index and query
only a large collection or a single fact.
2. write_python_file
Write Python code to a file with syntax validation. Parameters:file_path(str, required): Path where to write the filecontent(str, required): Python code contentvalidate(bool, optional): Validate syntax before writing (default: True)create_dirs(bool, optional): Create parent directories (default: True)
3. edit_python_file
Edit a Python file by replacing content with validation. Parameters:file_path(str, required): Path to the fileold_content(str, required): Content to find and replace. Must match exactly one location — see Edit semantics.new_content(str, required): New content to insertbackup(bool, optional): Create backup (default: True). The copy goes to~/.gaia/cache/backups/<original absolute path>/<name>.<timestamp>.bak, never beside the file, with or without aPathValidator. The backups directory is owner-only, and the five newest backups of each file are kept. If the backup cannot be made (a full disk, an unwritable cache), the edit is refused and the file is left unchanged.dry_run(bool, optional): Only simulate (default: False)
4. search_code
Search for patterns in code files. Parameters:directory(str, optional): Directory to search (default: ”.”)pattern(str, optional): Pattern to search forfile_extension(str, optional): File extension filter (default: “.py”)max_results(int, optional): Maximum results (default: 100)
5. generate_diff
Generate a unified diff for a file. Parameters:file_path(str, required): Path to original filenew_content(str, required): New content to comparecontext_lines(int, optional): Context lines in diff (default: 3)
6. write_file
Write content to any file without syntax validation. Parameters:file_path(str, required): Path where to writecontent(str, required): Content to writecreate_dirs(bool, optional): Create parent directories (default: True)project_dir(str, optional): Project root for resolving relative paths
7. edit_file
Edit any file by replacing content without validation. Parameters:file_path(str, required): Path to fileold_content(str, required): Exact content to find and replace. Must match exactly one location — see Edit semantics.new_content(str, required): New content to replace withproject_dir(str, optional): Project root for resolving paths
Read before edit
Every tool here that changes an existing file —edit_file, edit_python_file,
replace_function, and write_file, write_python_file, write_markdown_file
and update_gaia_md when the target already exists — refuses the file unless
the agent has read it. An edit made from a grep snippet or a guess is made
blind; this makes the agent look at what it is about to change.
- What counts as a read:
read_filereturning the file, a paged (offset/limit) read included. So does the agent creating the file, or editing it successfully. A shellcatdoes not. filesystem_tools’read_fileunlocks only its text views — the plain, paged, CSV and JSON ones. Its PDF, image, binary andmetadataviews show a summary rather than the file, so they unlock nothing. Thefile_ioandfile_searchreads have no such split.- Changed on disk since the read — a different mtime or size, a bare
touchincluded — is refused too, until the agent reads the file again. - Creating a file needs no read.
- The record belongs to one agent instance and lasts as long as it does. Another agent’s reads unlock nothing.
Edit semantics
edit_file and edit_python_file share one match-and-replace implementation,
gaia.agents.tools.file_edit,
so the two cannot drift apart. Two rules:
old_content must match exactly one location. Matching several is an
error, not a first-match replacement — picking one edits a region the caller
never named while reporting success, and the resulting diff looks plausible.
Extend old_content with surrounding lines until it is unique.
A rejection carries the file’s current content, so the retry does not need
a separate read.
Rejection shape:
file_edit tool (cpp/src/file_tools.cpp) follows both rules and
rejects a stale write, but still lets an agent edit a file it never read.
8. replace_function
Replace one function definition in a Python file. The replaced span is the definition and its decorators, taken from the AST — nothing between it and the next definition is touched. The edit is refused unless the function still resolves at the same qualified name afterwards, so a de-indented replacement cannot silently relocate a method to module level, and the tool never renames or moves a definition. Parameters:file_path(str, required): Path to Python filefunction_name(str, required): A module-level function name, or a qualifiedClass.method/outer.innername. A bare name that exists only as a method is an error naming the qualified alternatives, not a guess.new_implementation(str, required): The complete new definition, including any decorator it should keep and the indentation of the definition it replacesbackup(bool, optional): Create backup (default: True). The copy goes to~/.gaia/cache/backups/<original absolute path>/<name>.<timestamp>.bak, never beside the file, with or without aPathValidator. The backups directory is owner-only, and the five newest backups of each file are kept. If the backup cannot be made (a full disk, an unwritable cache), the edit is refused and the file is left unchanged.
9. write_markdown_file
Write (or overwrite) a Markdown file, optionally creating parent directories. Used by the code agent when generating READMEs, spec documents, or reports. Parameters:file_path(str, required): Destination pathcontent(str, required): Markdown content to writecreate_dirs(bool, optional): Create parent directories if missing (default: True)
10. update_gaia_md
Refresh the project’sGAIA.md (or another project-level index file) with
generated metadata — used by the code agent to keep project docs in sync with
what it’s building.
Parameters:
project_root(str, optional): Project root directory (default:".")project_name(str, optional): Display name to write into the filedescription(str, optional): Short project descriptionstructure(str or dict, optional): Formatted directory structureinstructions(str, optional): Agent-facing instructions section
Usage Examples
Example 1: Read and Analyze Python File
Example 2: Write Validated Python Code
Example 3: Edit File with Diff
Example 4: Search Codebase
Example 5: TypeScript/Generic File Operations
Security Model
Path Validation
The allowlist answers “is this in scope”. It does not answer “is this safe”, so reads and writes each add a denylist on top of it. Reads go throughvalidate_read, which refuses secrets that happen to sit inside
an allowed directory — .env, id_rsa, credentials.json, .netrc, .pem/.key
files, and anything under ~/.ssh, ~/.aws, ~/.gnupg, ~/.kube:
validate_write, which additionally refuses
BLOCKED_DIRECTORIES and any file that executes
on its own — shell rc files, PowerShell profiles, ~/.config/autostart/*,
systemd user units, LaunchAgents, and anything inside a repo’s .git/.
search_in_files applies the read denylist per file as it walks, so a directory
grep cannot return what read_file would have refused.
Allowed Paths Configuration
An allowed path may be a directory or a single file. Prefer files when the scope is derived from something a user attached — granting the folder a document happens to sit in grants everything else in it too.allowed_paths has three distinct states:
The blocked directories (
/etc, ~/.ssh, ~/.gaia, Windows system dirs, …) are
computed at import time in gaia.security; they are not a constructor argument.
File Type Detection
Python Files (.py)
- Full AST parsing
- Syntax validation
- Symbol extraction (functions, classes)
- Error reporting with line numbers
Markdown Files (.md)
- Header extraction (# patterns)
- Code block extraction with language tags
- Link extraction (text patterns)
Binary Files
- Detected via UnicodeDecodeError
- Returns size and binary flag
- No content parsing
Text Files
- Raw content reading
- Line count and size reporting
- No special analysis
Dependencies
Testing Requirements
Coverage for this mixin lives undertests/unit/ (e.g.
test_file_editing_prompt.py, test_file_access_scope.py,
test_file_write_guardrails.py), not a single dedicated file:
FileIOToolsMixin Technical Specification