# files extension `files` gives the model file vocabulary inside the Python session: bounded reads, one directory listing, exact edits, and search. It is enabled by default and depends on `python` and `run`. Nothing here spawns a process directly. When ripgrep is installed, search runs as a supervised `run` job, so every child belongs to a job's process group and is ended with it. Without ripgrep the same calls fall back to pure Python. The internal search job is awaited and forgotten when it finishes; it does not appear in `jobs` or wake the session. If the cell is cancelled mid-search, its job is stopped before the cancellation continues. ```python files.read("src/app.py", start_line=40, end_line=80) files.ls("src") files.edit("src/app.py", "def handler(request):", "def handler(request, *, trace):") await files.find("prepare_history", "src", glob="*.gleam") await files.paths("integration") files.write("notes/plan.md", "...") ``` ## client buffers The extension's `files` host route calls the session-local client-binding broker on every read/write; Python `setup(api)` uses synchronous `host_now` so results remain immediate and awaitable. The broker, not Python, resolves current capabilities and proves local fallback. Advertised read/write capabilities use the client's text buffers before any disk existence check, including dirty buffers and new paths. Callbacks have a 20-second deadline beneath the Python host call's 30-second deadline. Disconnect, expired required bindings, client errors and timeouts fail without disk fallback or automatic retry (a timed-out write has unknown outcome). `Files()` without an API and remote `rem.files` remain local. `PythonApi.workspace_client` defaults to true; kernel startup sets it from actual link ownership (`--run` detached session versus raw stdio owner), so raw remote owners stay local even without the optional remote target stamp. Synchronous host RPC stays available for memory and other session tools. Listings and searches stay disk-only. Editor-backed edits retain exact-match, occurrence and line-hint validation, then re-read the buffer before writing. This is best-effort: ACP supplies no compare-and-swap, so a concurrent editor change between recheck and write can still be overwritten. The internal read-for-edit and write-for-edit routes check both capabilities on each operation. Read-only and write-only client bindings cannot be edited: unsupported write capability never publishes editor-derived text onto disk, and unsupported read capability never sends a stale disk-derived edit into a dirty buffer. Explicit whole-file writes still work with write-only capability. Whole-file writes do not invent previous buffer content for trace diffs; client activities are recorded without re-reading disk. ## read `read(path, start_line=1, end_line=None, *, limit=None, max_chars=16000)` returns numbered lines. `limit` is a number of lines; `max_chars` is a separate character budget that keeps a huge window out of the context. A window stopped by either ends with which one stopped it and the line to resume from, so a large file is read in explicit steps rather than truncated silently. Lines are never shortened, and the numbers are the ones `edit(line_hint=)` accepts. Reads decode UTF-8 with replacement and recognize Python line boundaries, including CRLF and Unicode separators. Scanning uses 64 KiB chunks and bounds memory by the chunk size and `max_chars`, even for giant lines. An open-ended read scans to EOF to report exact remaining line counts; an explicit `end_line` can stop scanning after that line. ## edit `edit(path, old_str, new_str, line_hint=None)` replaces one exact, unique occurrence: - The file is read with its identity (device, inode, mode, size, mtime), written to a temporary file with the same mode, and published with `os.replace` only while that identity still matches. A concurrent change fails the edit instead of overwriting it. - A string that does not appear reports the closest candidates with line numbers and a similarity score. When the text is there and only whitespace differs (indentation, tabs, trailing spaces, line endings) the message says so first. `cells.run` replacements say the same. - A string that appears several times changes nothing and lists every occurrence's line range with numbered context. Retry with `line_hint=`, or widen `old_str`. A hint only chooses between exact occurrences; it never moves the edit elsewhere. `write(path, content)` replaces a whole file and creates parent directories, for new files rather than edits. ## search `await find(pattern, path=".", glob=..., context=0, max_results=50, literal=False, case_sensitive=None, hidden=False)` searches contents and returns rows that print as `path:line: text`. `path` may be a list of paths. `context=N` adds up to N lines around each match, printed grep-style as `path-line- text` and marked `context=True`; `max_results` counts matches, not context lines. Slicing or indexing before the await applies to the rows, so `await find(...)[:10]` reads as intended. `await paths(pattern=None, path=".", glob=...)` searches file names; like `find`, `path` and `glob` may each be a list. A pattern with `*`, `?` or `[` globs the file name, or the path from the search root when it has a `/` (`src/*/*_test.go`; a leading `./` is optional). Returned paths carry no leading `./`. Both bound their results and say when the list was cut. ## await `read`, `ls`, `edit`, and `write` return their result immediately, and that result may also be awaited: `files.read(path)` and `await files.read(path)` are the same call. `find` and `paths` run a background search, so they must be awaited; using one without `await` prints, or raises on iteration, a message saying so rather than a coroutine.