{"course":{"slug":"context-engineering","title":"Context Engineering","url":"https://addaly.com/learn/context-engineering","level":"advanced","track":"build"},"lesson":{"slug":"the-filesystem-as-context","title":"Put the payload on disk and the pointer in the window","url":"https://addaly.com/learn/context-engineering/the-filesystem-as-context","position":67,"of":87,"minutes":9,"answer":"Any material the model needs to consult rather than read can live in a file with a query tool over it.","keyIdea":"Any material the model needs to consult rather than read can live in a file with a query tool over it, which turns a fixed window into unbounded working memory: at the price of stale reads, forgotten artefacts and a new surface for untrusted text."},"body":{"format":"markdown","text":"## The window is not the only place state can live\n\nA 40,000-token CSV in the window is 40,000 tokens on every subsequent turn, and the model will still miscount its rows, because counting 8,000 lines of text is not what these systems are good at.\n\nThe alternative costs about ninety tokens:\n\n```\n<artefacts>\n/work/orders.csv — 8,412 rows. Columns: order_id, customer_id, placed_at,\n  status, total_inr. Query it with run_sql (DuckDB syntax, read-only).\n</artefacts>\n```\n\nThe data is on disk. The window holds its location, its shape, and how to ask it questions. `SELECT status, COUNT(*) FROM 'orders.csv' GROUP BY status` returns five rows and is exactly correct, which is more than the model would have managed reading all 8,412.\n\nThis is the oldest idea in computing applied to a new constraint: keep the working set small and the store large. Everything in this lesson follows from it.\n\n## What it unlocks\n\n**State that outlives the window.** A long task can write intermediate results to disk, compact its context to almost nothing, and carry on. The transcript is disposable; the files are the work.\n\n**Resumability.** A run that crashes at step 30 with its findings in a file can restart from the file. A run that crashed with its findings in a compacted transcript cannot.\n\n**Correctness on the operations models are worst at.** Counting, summing, sorting, joining, deduplicating. Every one of these is a database's job. `sqlite3` is on almost every machine, DuckDB installs with one `pip` command and reads CSV and Parquet directly with no import step, and both are free. There is no cost argument for making a language model add up a column.\n\n**Handover.** Files are readable by humans, by the next session, and by a different model entirely. A compacted summary is readable by none of those with any confidence.\n\n## The manifest, and why it is not optional\n\nThe predictable failure is that the model forgets a file exists. It wrote `/work/findings.md` at step 6, and by step 24 that mention is 40,000 tokens back, in the middle of the window, which is the position least likely to be used.\n\nSo maintain the artefact list as a block your code renders at a fixed position on every turn: the same discipline as the structured state block in the conversation module, for the same reason. Your code knows which files exist; the model should not have to remember.\n\nKeep it to one line per artefact: path, what it is, how big, how to read it. When the list grows past a dozen entries the task has usually sprawled, and that is worth noticing on its own.\n\n## Three failure modes to design against\n\n**Stale reads.** The model read a file at step 8 and believes it still knows the contents at step 20, having edited it at step 14. The stub message from the agent-loop lesson solves this, and the wording matters: say the content is stale, not merely that it was omitted.\n\n**Invented paths.** A model will confidently call `read_file(\"/work/summary.md\")` for a file nobody created. The error must say so plainly and list what does exist: `No file at /work/summary.md. Files in /work: orders.csv, findings.md, notes/`. That single line converts a dead end into a correction.\n\n**Write collisions.** Two parallel sub-agents writing the same path, or a retry overwriting a good result with a partial one. Give each writer its own path, or write to a temporary file and rename atomically.\n\n## The part people skip\n\nA file is untrusted if anything untrusted wrote it.\n\nIf your agent fetches a web page and saves it to `/work/page.html`, that file now contains whatever the page's author chose to put there, including, quite possibly, a paragraph addressed to your model. When the model later reads that file, the text arrives in the window with no marker distinguishing it from material you authored. The filesystem has quietly laundered untrusted content into something that looks like your own working notes.\n\nThe defence is to keep provenance on the file, not in your head. A separate directory for anything externally sourced, a manifest entry that names it as untrusted, and a wrapper that reads such files inside explicit delimiters saying where the content came from. The next module is entirely about what follows from that, and it is the single most common way a well-built agent is turned against its owner.\n\n## The free path, which is all of it\n\nNothing here needs a product. A directory, `sqlite3` or DuckDB, and four tools (`read_file`, `write_file`, `list_files`, `run_sql`) is the whole pattern. It runs on a laptop, it runs inside a container, and it runs against a temporary directory that you delete when the task ends, which is also the simplest data-retention policy you will ever implement."},"check":{"question":"An agent saves a fetched web page to /work/page.html and reads it back two steps later. What has changed about that text on the way through the filesystem?","options":[{"text":"It is now trusted by the model, because content written by the agent's own tool arrives with the agent's authority.","correct":false,"tempting":"Close, but it reverses cause and effect: the model never assigns trust, it simply reads tokens; the problem is that nothing marks the origin."},{"text":"It was sanitised on write, since serialising to disk strips any active content from the markup.","correct":false,"tempting":"Writing bytes to a file changes nothing about them; sanitisation only happens if you wrote code to do it."},{"text":"Nothing has changed, but it now arrives with no marker of its origin, looking like the agent's own notes.","correct":true},{"text":"It is safer, because the model reads it through a tool wrapper that applies truncation and escaping.","correct":false,"tempting":"Truncation bounds size and escaping protects your delimiters; neither addresses whether the surviving text is instructions from a stranger."}]},"prev":{"slug":"sub-agents-and-window-isolation","title":"Sub-agents buy you a window and charge you a boundary","url":"https://addaly.com/learn/context-engineering/sub-agents-and-window-isolation"},"next":{"slug":"untrusted-text-in-context","title":"When untrusted text enters the context","url":"https://addaly.com/learn/context-engineering/untrusted-text-in-context"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Put the payload on disk and the pointer in the window', in Context Engineering. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/context-engineering/the-filesystem-as-context (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Put the payload on disk and the pointer in the window","courseTitle":"Context Engineering","module":{"index":7,"slug":"tools-schemas-and-the-agent-window","title":"Tools, schemas and the agent’s window"},"answer":null,"keyIdea":"Any material the model needs to consult rather than read can live in a file with a query tool over it, which turns a fixed window into unbounded working memory: at the price of stale reads, forgotten artefacts and a new surface for untrusted text.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-window-is-not-the-only-place-state-can-live","runs":[{"text":"The window is not the only place state can live"}]},{"t":"p","runs":[{"text":"A 40,000-token CSV in the window is 40,000 tokens on every subsequent turn, and the model will still miscount its rows, because counting 8,000 lines of text is not what these systems are good at."}]},{"t":"p","runs":[{"text":"The alternative costs about ninety tokens:"}]},{"t":"code","lang":"","label":"Text","text":"<artefacts>\n/work/orders.csv — 8,412 rows. Columns: order_id, customer_id, placed_at,\n  status, total_inr. Query it with run_sql (DuckDB syntax, read-only).\n</artefacts>"},{"t":"p","runs":[{"text":"The data is on disk. The window holds its location, its shape, and how to ask it questions. "},{"text":"SELECT status, COUNT(*) FROM 'orders.csv' GROUP BY status","code":true},{"text":" returns five rows and is exactly correct, which is more than the model would have managed reading all 8,412."}]},{"t":"p","runs":[{"text":"This is the oldest idea in computing applied to a new constraint: keep the working set small and the store large. Everything in this lesson follows from it."}]},{"t":"h2","id":"what-it-unlocks","runs":[{"text":"What it unlocks"}]},{"t":"p","runs":[{"text":"State that outlives the window.","b":true},{"text":" A long task can write intermediate results to disk, compact its context to almost nothing, and carry on. The transcript is disposable; the files are the work."}]},{"t":"p","runs":[{"text":"Resumability.","b":true},{"text":" A run that crashes at step 30 with its findings in a file can restart from the file. A run that crashed with its findings in a compacted transcript cannot."}]},{"t":"p","runs":[{"text":"Correctness on the operations models are worst at.","b":true},{"text":" Counting, summing, sorting, joining, deduplicating. Every one of these is a database’s job. "},{"text":"sqlite3","code":true},{"text":" is on almost every machine, DuckDB installs with one "},{"text":"pip","code":true},{"text":" command and reads CSV and Parquet directly with no import step, and both are free. There is no cost argument for making a language model add up a column."}]},{"t":"p","runs":[{"text":"Handover.","b":true},{"text":" Files are readable by humans, by the next session, and by a different model entirely. A compacted summary is readable by none of those with any confidence."}]},{"t":"h2","id":"the-manifest-and-why-it-is-not-optional","runs":[{"text":"The manifest, and why it is not optional"}]},{"t":"p","runs":[{"text":"The predictable failure is that the model forgets a file exists. It wrote "},{"text":"/work/findings.md","code":true},{"text":" at step 6, and by step 24 that mention is 40,000 tokens back, in the middle of the window, which is the position least likely to be used."}]},{"t":"p","runs":[{"text":"So maintain the artefact list as a block your code renders at a fixed position on every turn: the same discipline as the structured state block in the conversation module, for the same reason. Your code knows which files exist; the model should not have to remember."}]},{"t":"p","runs":[{"text":"Keep it to one line per artefact: path, what it is, how big, how to read it. When the list grows past a dozen entries the task has usually sprawled, and that is worth noticing on its own."}]},{"t":"h2","id":"three-failure-modes-to-design-against","runs":[{"text":"Three failure modes to design against"}]},{"t":"p","runs":[{"text":"Stale reads.","b":true},{"text":" The model read a file at step 8 and believes it still knows the contents at step 20, having edited it at step 14. The stub message from the agent-loop lesson solves this, and the wording matters: say the content is stale, not merely that it was omitted."}]},{"t":"p","runs":[{"text":"Invented paths.","b":true},{"text":" A model will confidently call "},{"text":"read_file(\"/work/summary.md\")","code":true},{"text":" for a file nobody created. The error must say so plainly and list what does exist: "},{"text":"No file at /work/summary.md. Files in /work: orders.csv, findings.md, notes/","code":true},{"text":". That single line converts a dead end into a correction."}]},{"t":"p","runs":[{"text":"Write collisions.","b":true},{"text":" Two parallel sub-agents writing the same path, or a retry overwriting a good result with a partial one. Give each writer its own path, or write to a temporary file and rename atomically."}]},{"t":"h2","id":"the-part-people-skip","runs":[{"text":"The part people skip"}]},{"t":"p","runs":[{"text":"A file is untrusted if anything untrusted wrote it."}]},{"t":"p","runs":[{"text":"If your agent fetches a web page and saves it to "},{"text":"/work/page.html","code":true},{"text":", that file now contains whatever the page’s author chose to put there, including, quite possibly, a paragraph addressed to your model. When the model later reads that file, the text arrives in the window with no marker distinguishing it from material you authored. The filesystem has quietly laundered untrusted content into something that looks like your own working notes."}]},{"t":"p","runs":[{"text":"The defence is to keep provenance on the file, not in your head. A separate directory for anything externally sourced, a manifest entry that names it as untrusted, and a wrapper that reads such files inside explicit delimiters saying where the content came from. The next module is entirely about what follows from that, and it is the single most common way a well-built agent is turned against its owner."}]},{"t":"h2","id":"the-free-path-which-is-all-of-it","runs":[{"text":"The free path, which is all of it"}],"role":"free"},{"t":"p","runs":[{"text":"Nothing here needs a product. A directory, "},{"text":"sqlite3","code":true},{"text":" or DuckDB, and four tools ("},{"text":"read_file","code":true},{"text":", "},{"text":"write_file","code":true},{"text":", "},{"text":"list_files","code":true},{"text":", "},{"text":"run_sql","code":true},{"text":") is the whole pattern. It runs on a laptop, it runs inside a container, and it runs against a temporary directory that you delete when the task ends, which is also the simplest data-retention policy you will ever implement."}]}],"check":{"question":[{"text":"An agent saves a fetched web page to /work/page.html and reads it back two steps later. What has changed about that text on the way through the filesystem?"}],"options":[{"text":[{"text":"It is now trusted by the model, because content written by the agent’s own tool arrives with the agent’s authority."}],"correct":false,"tempting":[{"text":"Close, but it reverses cause and effect: the model never assigns trust, it simply reads tokens; the problem is that nothing marks the origin."}]},{"text":[{"text":"It was sanitised on write, since serialising to disk strips any active content from the markup."}],"correct":false,"tempting":[{"text":"Writing bytes to a file changes nothing about them; sanitisation only happens if you wrote code to do it."}]},{"text":[{"text":"Nothing has changed, but it now arrives with no marker of its origin, looking like the agent’s own notes."}],"correct":true,"tempting":null},{"text":[{"text":"It is safer, because the model reads it through a tool wrapper that applies truncation and escaping."}],"correct":false,"tempting":[{"text":"Truncation bounds size and escaping protects your delimiters; neither addresses whether the surviving text is instructions from a stranger."}]}],"order":[1,0,3,2],"why":null},"prev":{"slug":"sub-agents-and-window-isolation","title":"Sub-agents buy you a window and charge you a boundary","minutes":9},"next":{"slug":"untrusted-text-in-context","title":"When untrusted text enters the context","minutes":9}}}