Guides > Agentic Engineering Patterns > Hoard things you know how to do
Changes to Hoard things you know how to do
March 16, 2026, 6:18 p.m. #
---
+++
@@ -6,7 +6,7 @@
The more answers to questions like this you have under your belt, the more likely you'll be able to spot opportunities to deploy technology to solve problems in ways other people may not have thought of yet.
-The best way to be confident in answers to these questions is to have seen them illustrated by *running code*. Knowing that something is theoretically possible is not the same as having seen it done for yourself. A key asset to develop as a software professional is a deep collection of answers to questions like this, ideally illustrated by running code.
+The best way to be confident in answers to these questions is to have seen them illustrated by *running code*. Knowing that something is theoretically possible is not the same as having seen it done for yourself. A key asset to develop as a software professional is a deep collection of answers to questions like this, accompanied by proof of those answers.
I hoard solutions like this in a number of different ways. My [blog](https://simonwillison.net) and [TIL blog](https://til.simonwillison.net) are crammed with notes on things I've figured out how to do. I have [over a thousand GitHub repos](https://github.com/simonw) collecting code I've written for different projects, many of them small proof-of-concepts that demonstrate a key idea.
March 16, 2026, 6:17 p.m. #
---
+++
@@ -6,7 +6,7 @@
The more answers to questions like this you have under your belt, the more likely you'll be able to spot opportunities to deploy technology to solve problems in ways other people may not have thought of yet.
-Knowing that something is theoretically possible is not the same as having seen it done for yourself. A key asset to develop as a software professional is a deep collection of answers to questions like this, ideally illustrated by running code.
+The best way to be confident in answers to these questions is to have seen them illustrated by *running code*. Knowing that something is theoretically possible is not the same as having seen it done for yourself. A key asset to develop as a software professional is a deep collection of answers to questions like this, ideally illustrated by running code.
I hoard solutions like this in a number of different ways. My [blog](https://simonwillison.net) and [TIL blog](https://til.simonwillison.net) are crammed with notes on things I've figured out how to do. I have [over a thousand GitHub repos](https://github.com/simonw) collecting code I've written for different projects, many of them small proof-of-concepts that demonstrate a key idea.
March 16, 2026, 3:15 p.m. #
---
+++
@@ -18,7 +18,7 @@
## Recombining things from your hoard
-Why collect all of this stuff? Aside from helping you build and extend your own abilities, the assets you generate along the way become incredibly powerful inputs for your coding agents.
+Why collect all of this stuff? Aside from helping you build and extend your own abilities, the assets you generate along the way become powerful inputs for your coding agents.
One of my favorite prompting patterns is to tell an agent to build something new by combining two or more existing working examples.
Feb. 28, 2026, 10:38 p.m. #
---
+++
@@ -34,128 +34,130 @@
Here’s the full prompt I fed into a model (at the time it was Claude 3 Opus), combining my two examples and describing the solution I was looking for:
-> This code shows how to open a PDF and turn it into an image per page:
+````markdown-copy
-> ```html
+This code shows how to open a PDF and turn it into an image per page:
-> <!DOCTYPE html>
+```html
-> <html>
+<!DOCTYPE html>
-> <head>
+<html>
-> <title>PDF to Images</title>
+<head>
-> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"></script>
+ <title>PDF to Images</title>
-> <style>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js"></script>
-> .image-container img {
+ <style>
-> margin-bottom: 10px;
+ .image-container img {
-> }
+ margin-bottom: 10px;
-> .image-container p {
+ }
-> margin: 0;
+ .image-container p {
-> font-size: 14px;
+ margin: 0;
-> color: #888;
+ font-size: 14px;
-> }
+ color: #888;
-> </style>
+ }
-> </head>
+ </style>
-> <body>
+</head>
-> <input type="file" id="fileInput" accept=".pdf" />
+<body>
-> <div class="image-container"></div>
+ <input type="file" id="fileInput" accept=".pdf" />
->
+ <div class="image-container"></div>
-> <script>
+
-> const desiredWidth = 800;
+ <script>
-> const fileInput = document.getElementById('fileInput');
+ const desiredWidth = 800;
-> const imageContainer = document.querySelector('.image-container');
+ const fileInput = document.getElementById('fileInput');
->
+ const imageContainer = document.querySelector('.image-container');
-> fileInput.addEventListener('change', handleFileUpload);
+
->
+ fileInput.addEventListener('change', handleFileUpload);
-> pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';
+
->
+ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';
-> async function handleFileUpload(event) {
+
-> const file = event.target.files[0];
+ async function handleFileUpload(event) {
-> const imageIterator = convertPDFToImages(file);
+ const file = event.target.files[0];
->
+ const imageIterator = convertPDFToImages(file);
-> for await (const { imageURL, size } of imageIterator) {
+
-> const imgElement = document.createElement('img');
+ for await (const { imageURL, size } of imageIterator) {
-> imgElement.src = imageURL;
+ const imgElement = document.createElement('img');
-> imageContainer.appendChild(imgElement);
+ imgElement.src = imageURL;
->
+ imageContainer.appendChild(imgElement);
-> const sizeElement = document.createElement('p');
+
-> sizeElement.textContent = `Size: ${formatSize(size)}`;
+ const sizeElement = document.createElement('p');
-> imageContainer.appendChild(sizeElement);
+ sizeElement.textContent = `Size: ${formatSize(size)}`;
-> }
+ imageContainer.appendChild(sizeElement);
-> }
+ }
->
+ }
-> async function* convertPDFToImages(file) {
+
-> try {
+ async function* convertPDFToImages(file) {
-> const pdf = await pdfjsLib.getDocument(URL.createObjectURL(file)).promise;
+ try {
-> const numPages = pdf.numPages;
+ const pdf = await pdfjsLib.getDocument(URL.createObjectURL(file)).promise;
->
+ const numPages = pdf.numPages;
-> for (let i = 1; i <= numPages; i++) {
+
-> const page = await pdf.getPage(i);
+ for (let i = 1; i <= numPages; i++) {
-> const viewport = page.getViewport({ scale: 1 });
+ const page = await pdf.getPage(i);
-> const canvas = document.createElement('canvas');
+ const viewport = page.getViewport({ scale: 1 });
-> const context = canvas.getContext('2d');
+ const canvas = document.createElement('canvas');
-> canvas.width = desiredWidth;
+ const context = canvas.getContext('2d');
-> canvas.height = (desiredWidth / viewport.width) * viewport.height;
+ canvas.width = desiredWidth;
-> const renderContext = {
+ canvas.height = (desiredWidth / viewport.width) * viewport.height;
-> canvasContext: context,
+ const renderContext = {
-> viewport: page.getViewport({ scale: desiredWidth / viewport.width }),
+ canvasContext: context,
-> };
+ viewport: page.getViewport({ scale: desiredWidth / viewport.width }),
-> await page.render(renderContext).promise;
+ };
-> const imageURL = canvas.toDataURL('image/jpeg', 0.8);
+ await page.render(renderContext).promise;
-> const size = calculateSize(imageURL);
+ const imageURL = canvas.toDataURL('image/jpeg', 0.8);
-> yield { imageURL, size };
+ const size = calculateSize(imageURL);
-> }
+ yield { imageURL, size };
-> } catch (error) {
+ }
-> console.error('Error:', error);
+ } catch (error) {
-> }
+ console.error('Error:', error);
-> }
+ }
->
+ }
-> function calculateSize(imageURL) {
+
-> const base64Length = imageURL.length - 'data:image/jpeg;base64,'.length;
+ function calculateSize(imageURL) {
-> const sizeInBytes = Math.ceil(base64Length * 0.75);
+ const base64Length = imageURL.length - 'data:image/jpeg;base64,'.length;
-> return sizeInBytes;
+ const sizeInBytes = Math.ceil(base64Length * 0.75);
-> }
+ return sizeInBytes;
->
+ }
-> function formatSize(size) {
+
-> const sizeInKB = (size / 1024).toFixed(2);
+ function formatSize(size) {
-> return `${sizeInKB} KB`;
+ const sizeInKB = (size / 1024).toFixed(2);
-> }
+ return `${sizeInKB} KB`;
-> </script>
+ }
-> </body>
+ </script>
-> </html>
+</body>
-> ```
+</html>
-> This code shows how to OCR an image:
+```
-> ```javascript
+This code shows how to OCR an image:
-> async function ocrMissingAltText() {
+```javascript
-> // Load Tesseract
+async function ocrMissingAltText() {
-> var s = document.createElement("script");
+ // Load Tesseract
-> s.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
+ var s = document.createElement("script");
-> document.head.appendChild(s);
+ s.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
->
+ document.head.appendChild(s);
-> s.onload = async () => {
+
-> const images = document.getElementsByTagName("img");
+ s.onload = async () => {
-> const worker = Tesseract.createWorker();
+ const images = document.getElementsByTagName("img");
-> await worker.load();
+ const worker = Tesseract.createWorker();
-> await worker.loadLanguage("eng");
+ await worker.load();
-> await worker.initialize("eng");
+ await worker.loadLanguage("eng");
-> ocrButton.innerText = "Running OCR...";
+ await worker.initialize("eng");
->
+ ocrButton.innerText = "Running OCR...";
-> // Iterate through all the images in the output div
+
-> for (const img of images) {
+ // Iterate through all the images in the output div
-> const altTextarea = img.parentNode.querySelector(".textarea-alt");
+ for (const img of images) {
-> // Check if the alt textarea is empty
+ const altTextarea = img.parentNode.querySelector(".textarea-alt");
-> if (altTextarea.value === "") {
+ // Check if the alt textarea is empty
-> const imageUrl = img.src;
+ if (altTextarea.value === "") {
-> var {
+ const imageUrl = img.src;
-> data: { text },
+ var {
-> } = await worker.recognize(imageUrl);
+ data: { text },
-> altTextarea.value = text; // Set the OCR result to the alt textarea
+ } = await worker.recognize(imageUrl);
-> progressBar.value += 1;
+ altTextarea.value = text; // Set the OCR result to the alt textarea
-> }
+ progressBar.value += 1;
-> }
+ }
->
+ }
-> await worker.terminate();
+
-> ocrButton.innerText = "OCR complete";
+ await worker.terminate();
-> };
+ ocrButton.innerText = "OCR complete";
-> }
+ };
-> ```
+ }
-> Use these examples to put together a single HTML page with embedded HTML and CSS and JavaScript that provides a big square which users can drag and drop a PDF file onto and when they do that the PDF has every page converted to a JPEG and shown below on the page, then OCR is run with tesseract and the results are shown in textarea blocks below each image.
+```
+Use these examples to put together a single HTML page with embedded HTML and CSS and JavaScript that provides a big square which users can drag and drop a PDF file onto and when they do that the PDF has every page converted to a JPEG and shown below on the page, then OCR is run with tesseract and the results are shown in textarea blocks below each image.
+````
This worked flawlessly! The model kicked out a proof-of-concept page that did exactly what I needed.
@@ -167,18 +169,19 @@
If your coding agent has internet access you can tell it to do things like:
-> Use curl to fetch the source of `https://tools.simonwillison.net/ocr` and `https://tools.simonwillison.net/gemini-bbox` and build a new tool that lets you select a page from a PDF and pass it to Gemini to return bounding boxes for illustrations on that page.
+````markdown-copy
-
+Use curl to fetch the source of `https://tools.simonwillison.net/ocr` and `https://tools.simonwillison.net/gemini-bbox` and build a new tool that lets you select a page from a PDF and pass it to Gemini to return bounding boxes for illustrations on that page.
+````
(I specified `curl` there because Claude Code defaults to using a WebFetch tool which summarizes the page content rather than returning the raw HTML.)
Coding agents are excellent at search, which means you can run them on your own machine and tell them where to find the examples of things you want them to do:
-
+````markdown-copy
-> Add mocked HTTP tests to the `~/dev/ecosystem/datasette-oauth` project inspired by how `~/dev/ecosystem/llm-mistral` is doing it.
+Add mocked HTTP tests to the `~/dev/ecosystem/datasette-oauth` project inspired by how `~/dev/ecosystem/llm-mistral` is doing it.
-
+````
Often that's enough - the agent will fire up a search sub-agent to investigate and pull back just the details it needs to achieve the task.
Since so much of my research code is public I'll often tell coding agents to clone my repositories to `/tmp` and use them as input:
-
+````markdown-copy
-> Clone `simonw/research` from GitHub to `/tmp` and find examples of compiling Rust to WebAssembly, then use that to build a demo HTML page for this project.
+Clone `simonw/research` from GitHub to `/tmp` and find examples of compiling Rust to WebAssembly, then use that to build a demo HTML page for this project.
-
+````
The key idea here is that coding agents mean we only ever need to figure out a useful trick *once*. If that trick is then documented somewhere with a working code example our agents can consult that example and use it to solve any similar shaped project in the future.
Feb. 26, 2026, 8:33 p.m. #
Draft status changed from draft to published.
Feb. 26, 2026, 8:05 p.m. #
---
+++
@@ -22,7 +22,7 @@
One of my favorite prompting patterns is to tell an agent to build something new by combining two or more existing working examples.
-A project that helped crystallize how effective this can be was the first thing I added to my tools collection - a browser-based [OCR tool](https://tools.simonwillison.net/ocr).
+A project that helped crystallize how effective this can be was the first thing I added to my tools collection - a browser-based [OCR tool](https://tools.simonwillison.net/ocr), described [in more detail here](https://simonwillison.net/2024/Mar/30/ocr-pdfs-images/).
I wanted an easy, browser-based tool for OCRing pages from PDF files - in particular PDFs that consist entirely of scanned images with no text version provided at all.
Feb. 26, 2026, 7:55 p.m. #
---
+++
@@ -167,18 +167,18 @@
If your coding agent has internet access you can tell it to do things like:
-> Use curl to fetch the source of https://tools.simonwillison.net/ocr and https://tools.simonwillison.net/gemini-bbox and build a new tool that lets you select a page from a PDF and pass it to Gemini to return bounding boxes for illustrations on that page.
+> Use curl to fetch the source of `https://tools.simonwillison.net/ocr` and `https://tools.simonwillison.net/gemini-bbox` and build a new tool that lets you select a page from a PDF and pass it to Gemini to return bounding boxes for illustrations on that page.
(I specified `curl` there because Claude Code defaults to using a WebFetch tool which summarizes the page content rather than returning the raw HTML.)
Coding agents are excellent at search, which means you can run them on your own machine and tell them where to find the examples of things you want them to do:
-> Add mocked HTTP tests to the `~/dev/ecosystem/datasette-oauth` project inspired by how ~/dev/ecosystem/llm-mistral` is doing it.
+> Add mocked HTTP tests to the `~/dev/ecosystem/datasette-oauth` project inspired by how `~/dev/ecosystem/llm-mistral` is doing it.
Often that's enough - the agent will fire up a search sub-agent to investigate and pull back just the details it needs to achieve the task.
Since so much of my research code is public I'll often tell coding agents to clone my repositories to `/tmp` and use them as input:
-> Clone simonw/research from GitHub to /tmp and find examples of compiling Rust to WebAssembly, then use that to build a demo HTML page for this project.
+> Clone `simonw/research` from GitHub to `/tmp` and find examples of compiling Rust to WebAssembly, then use that to build a demo HTML page for this project.
The key idea here is that coding agents mean we only ever need to figure out a useful trick *once*. If that trick is then documented somewhere with a working code example our agents can consult that example and use it to solve any similar shaped project in the future.
Feb. 26, 2026, 7:48 p.m. #
---
+++
@@ -22,13 +22,13 @@
One of my favorite prompting patterns is to tell an agent to build something new by combining two or more existing working examples.
-A project that helped crystalize how effective this can be was the first that I added to my tools collection - a browser-based [OCR tool](https://tools.simonwillison.net/ocr).
+A project that helped crystallize how effective this can be was the first thing I added to my tools collection - a browser-based [OCR tool](https://tools.simonwillison.net/ocr).
I wanted an easy, browser-based tool for OCRing pages from PDF files - in particular PDFs that consist entirely of scanned images with no text version provided at all.
I had previously experimented with running the [Tesseract.js OCR library](https://tesseract.projectnaptha.com/) in my browser, and found it to be very capable. That library provides a WebAssembly build of the mature Tesseract OCR engine and lets you call it from JavaScript to extract text from an image.
-I didn’t want to work with images though, I wanted to work with PDFs. Then I remembered that I had also experimented in the past with Mozilla’s [PDF.js](https://mozilla.github.io/pdf.js/) library, a which among other things can turn individual pages of a PDF into rendered images.
+I didn’t want to work with images though, I wanted to work with PDFs. Then I remembered that I had also worked with Mozilla’s [PDF.js](https://mozilla.github.io/pdf.js/) library, which among other things can turn individual pages of a PDF into rendered images.
I had snippets of JavaScript for both of those libraries in my notes.
@@ -159,4 +159,26 @@
This worked flawlessly! The model kicked out a proof-of-concept page that did exactly what I needed.
-I ended up [iterating with it a few times](https://gist.github.com/simonw/6a9f077bf8db616e44893a24ae1d36eb) to get to my final result, but it took just a few minutes to build a genuinely useful tool that I’ve benefitted from ever since.
+I ended up [iterating with it a few times](https://gist.github.com/simonw/6a9f077bf8db616e44893a24ae1d36eb) to get to my final result, but it took just a few minutes to build a genuinely useful tool that I’ve benefited from ever since.
+
+## Coding agents make this even more powerful
+
+I built that OCR example back in March 2024, nearly a year before the first release of Claude Code. Coding agents have made hoarding working examples even more valuable.
+
+If your coding agent has internet access you can tell it to do things like:
+
+> Use curl to fetch the source of https://tools.simonwillison.net/ocr and https://tools.simonwillison.net/gemini-bbox and build a new tool that lets you select a page from a PDF and pass it to Gemini to return bounding boxes for illustrations on that page.
+
+(I specified `curl` there because Claude Code defaults to using a WebFetch tool which summarizes the page content rather than returning the raw HTML.)
+
+Coding agents are excellent at search, which means you can run them on your own machine and tell them where to find the examples of things you want them to do:
+
+> Add mocked HTTP tests to the `~/dev/ecosystem/datasette-oauth` project inspired by how ~/dev/ecosystem/llm-mistral` is doing it.
+
+Often that's enough - the agent will fire up a search sub-agent to investigate and pull back just the details it needs to achieve the task.
+
+Since so much of my research code is public I'll often tell coding agents to clone my repositories to `/tmp` and use them as input:
+
+> Clone simonw/research from GitHub to /tmp and find examples of compiling Rust to WebAssembly, then use that to build a demo HTML page for this project.
+
+The key idea here is that coding agents mean we only ever need to figure out a useful trick *once*. If that trick is then documented somewhere with a working code example our agents can consult that example and use it to solve any similar shaped project in the future.
Feb. 26, 2026, 7:35 p.m. #
Initial version.