Instagram reels are my single largest source on harness engineering. Seventeen notes, more than YouTube, X, LinkedIn and the open web combined on that subject.
I only know that because everything I read now lands in one library, whatever channel it arrived through. Before that, those seventeen reels were seventeen reels I watched.
That is the problem this solves, and it is not the one I expected to be solving.
A commonplace book is what people called this before the internet: one book you copied everything worth keeping into, whatever you had been reading. It worked because there was only ever one book. We have since split our reading across a dozen apps and kept a separate head for each one.
We consume across the whole multiverse now. A reel, a Substack essay, an X thread, a YouTube talk, a Reddit comment, a carousel of slides someone screenshotted from a deck. But almost nobody moves the thinking between them. Instagram thinking stays in the Instagram universe and gets applied back to Instagram. The essay you read on Tuesday never meets the reel you watched on Thursday, even when they are the same argument, because they live in different apps, different postures, and different parts of your head.
So you end up with a subject you have been circling for months from six directions, and no single channel has enough of it to show you the shape.
This collapses the channels into one library. Share anything from anywhere, and it comes back as a written assessment filed alongside everything else you have read, tagged by subject rather than by where it came from.
Share Sheet, from any app; a hotkey, from Chrome
|
v
one Apple Note -> a sweep, four times a day
|
v
read it properly -> assess -> tag by subject -> file -> tell you if it mattered
Once the channels collapse, subjects start assembling themselves out of sources that would never otherwise have met.
Real numbers from my own library, at the time of writing.
| Subject | Notes | Channels it came from |
|---|---|---|
context-governance |
34 | Instagram reels, LinkedIn, X, YouTube, carousels, newsletters, web |
harness-engineering |
31 | Instagram reels (17 of them), YouTube, X, LinkedIn, carousels, web |
ai-agents |
32 | web, Instagram reels, X, YouTube, carousels, newsletters |
ai-finance |
24 | web and newsletters only |
Twenty-eight of my subjects draw on three or more different channels. That last
row is the useful contrast: ai-finance genuinely lives in one universe, so its
spread is narrow, which is how you can tell the other numbers are measuring
something real.
None of those subjects was declared in advance. See the theme engine below.
Traversing channels only works if you can trust every entry equally, and that is the part where most tooling quietly breaks.
Every AI reading tool will hand you a summary of an article it could not open. It will be fluent, roughly the right shape, assembled from a headline and a preview, and indistinguishable from a summary of something genuinely read. Do that once a week for six months and your library is a mix of things you know and things you only think you know, with no marker between them.
Nothing here is ever assessed from content that was not actually read. Where a platform needs a logged-in session, the system either has one or says so. X Articles are read in headless Chromium with your own exported X session. Threads posts still have no headless path at all, so they get flagged, stay visible in the inbox, and get cleared by hand later. Nothing is ever summarised from a preview.
That is a real stretch of my own inbox, captured before the X Article reader
existed, so two of the flagged lines in it would be read rather than flagged today. Most lines are [x], meaning read and
filed. The [!] lines are items the system could not read: two X posts and a
Claude artifact link. They stay visible until I clear them by hand, and none of
them has an assessment sitting in my library pretending otherwise. Two
account-linked tokens are redacted, for reasons the repo will make obvious.
examples/03-what-a-failure-looks-like.md shows exactly what that looks like, end to end. It is the example I would read first.
The fetch ladder is where most of the work went, because a channel you cannot read is a channel that stays trapped.
| Source | How it gets read |
|---|---|
| Articles, blogs, newsletters | Your agent's web fetch |
| YouTube | Captions first, local Whisper transcription if there are none |
| Instagram reels and stories | yt-dlp with your cookies, then Whisper |
| Silent reels, where the words are burned into the video | Frame OCR, one segment per distinct on-screen text state |
| Instagram image carousels (the listicle slide decks) | gallery-dl plus tesseract, one block per slide |
| X / Twitter posts | Public syndication endpoint, no login needed |
| X Articles (their long-form format) | Headless Chromium with your exported X session |
| PDFs | Your agent's web fetch, same as any other document |
| Public JSON, thread plus top comments | |
| Jina Reader | |
| TikTok, Vimeo | yt-dlp, then Whisper |
The silent-reel path is the one that changed the numbers. A reel with no voiceover, where the substance is typed onto the screen, used to come back empty and get marked failed. On Instagram that is the common case rather than the exception, and fixing it is a large part of why seventeen of my harness-engineering notes came from there.
There is no taxonomy file. Nothing is pre-registered, and that is deliberate.
The categories you can name on day one are the ones you already know you care about. What a fixed taxonomy cannot do is notice that you have quietly read eleven things about a subject that was never on the list, which is exactly the subject worth surfacing. It is also the subject most likely to be spread across four channels, because if it were concentrated in one you would have noticed already.
So the assessor names each item's subject in its own words, and a ledger counts every tag it has ever seen. On the next run it hands that ledger back as a soft prior: these are the subjects this library already carries, reuse one if it fits, coin a new one freely if none does. Tags converge on their own, and a subject the library has never held can still arrive on any given Tuesday.
When one recurs often enough with nowhere of its own to live, the ledger says so:
$ ./scripts/themes.py suggest
These subjects have recurred at least 4 times and still file
into the library root. Give one a home if you want it kept separately:
wine (6 items, 2026-05-02 to 2026-07-28)
vinepair-sonoma-coast-pinot-2026-06-11.md
punch-willamette-producers-2026-07-03.md
themes.py home wine <dir>
You answer in one line, or you ignore it and everything keeps working:
$ ./scripts/themes.py home wine ~/reading/wine
OK: items tagged 'wine' now file into /Users/you/reading/wine
That is the only moment a human is in the loop, and the system reached the question by watching six months of actual reading rather than asking you to predict it on day one.
themes.py rename old new merges a tag that fragmented, which you will need. My
own ledger carries gtm-marketing at 25 alongside marketing-gtm at 12. The
ledger is plain JSON at <library>/.themes.json, so read it, edit it, or delete
it and lose nothing but the counts.
Three real notes from my library are in examples/, reformatted to the generic assessor and with my personal context stripped out.
Across those 579 notes the verdicts run roughly 253 "worth reading" against 200 "skim" or "skip". Something like 44% of what I shared to myself, having already passed my own interest filter at the moment I shared it, was not worth my time.
That ratio only holds because the assessor is willing to say "skip" about something you were excited enough to share ninety seconds earlier. One that finds every item valuable is one you will stop reading inside a fortnight.
You need macOS (Apple Notes and the Shortcuts CLI), Python 3.9 or later, and an agent that can run shell commands and fetch web pages. I run it with Claude Code; the run procedure is a plain markdown file, so anything that can follow instructions and use a terminal will do.
1. Clone it and make the config.
git clone https://github.com/<you>/commonplace.git
cd commonplace
python3 scripts/config.py initOpen config.json and set library to where you want your notes. That is the
only required edit. Then:
python3 scripts/config.py checkwhich validates the file and creates the directories.
2. Make the Apple Note. In Apple Notes, create a note called exactly
Commonplace, matching note_name in your config. Leave it empty. Call it
something else if you like, as long as the two agree.
3. Install the Share Sheet shortcut. Open shortcut/ and either import the
signed file or build it in the Shortcuts app in four steps. Both paths are in
shortcut/README.md.
4. Install the Chrome extension, if you read in a desktop browser. Load
chrome/ unpacked at chrome://extensions, run ./chrome/install.sh, and
fully quit and reopen Chrome. A hotkey, the toolbar button, and a right-click
menu then queue the current page (or a highlighted URL) into the same note.
chrome/README.md has the detail, including why the native
host lives outside the repo.
5. Install the readers you want. For articles and X posts, nothing extra is needed. For video and Instagram:
brew install yt-dlp ffmpeg tesseract
pipx install whisper-ctranslate2
./scripts/setup_env.sh # gallery-dl + pytesseract, for image carouselsFor X Articles, which need a real browser:
pipx install playwright && playwright install chromiumfetch_x_article.py re-execs itself under a Playwright-carrying interpreter when
the one calling it has none, so an isolated install still works from the sweep.
6. Give Instagram your cookies, if you share from Instagram. It rejects
logged-out requests, so export a Netscape-format cookies.txt from a logged-in
browser session and save it at ~/.config/commonplace/cookies/instagram.txt.
docs/COOKIES.md covers the export, what makes a file go stale,
and how the system tells you it has.
7. Point your agent at the run procedure. In Claude Code, copy the slash command:
cp claude/commands/process-inbox.md ~/.claude/commands/then edit the one path inside it to point at your clone. /process-inbox now runs
a sweep. Any other agent: hand it prompt/TRIAGE.md and let it work.
8. Schedule it, once you trust it. I run four sweeps a day. Start by running it by hand for a week, because the first week is when you find out that your Instagram cookies are wrong or that your tags are too fine-grained.
Almost all of it is silent by design. The run log holds the complete record of every sweep; notifications are capped (four per run by default) and only fire for something genuinely worth reading, or for a failure you might want to fix. A sweep that reads eleven things and notifies you eleven times has moved the queue from your inbox to your lock screen, which is not an improvement.
Expect the tags to be messy for the first month and then to settle. Expect to run
themes.py rename a few times early on. Expect roughly one item in ten to fail
the first time you set up cookies, and close to none once they are right.
The honest limitation: this reads what you already chose to share. It is not discovery, and it does not decide what you should care about. What it removes is the tax between deciding something looks interesting and knowing whether it was, and the wall between the channel it arrived in and everything else you know.
config.example.json Copy to config.json. The only file you must edit.
examples/ Three real notes, plus what a failure looks like.
prompt/
TRIAGE.md The run procedure. The agent reads this fresh each sweep.
ASSESS.md How to read one item, and the note format it produces.
scripts/
config.py Config loader. Every other script gets its paths here.
inbox.py The Apple Notes bridge: add, read, done, fail, recover,
unlock, plus dedupe/prune/sanitize for note health.
themes.py The theme engine: record, prompt, route, suggest, home.
log.py Run-log writer.
notify.py Capped notifications.
cookies.py Resolves and validates cookies for auth-walled hosts.
fetch_*.py The fetch ladder, one script per awkward platform.
fetch_x_article.py needs Playwright and x.com cookies.
transcribe.sh yt-dlp plus local Whisper.
setup_env.sh Builds the OCR virtualenv.
shortcut/
src/add-to-inbox.plist The Share Sheet shortcut as readable source.
build.sh Converts and signs it.
README.md Import it, or build it by hand in four steps.
chrome/
manifest.json The desktop capture end: a hotkey, the toolbar
background.js button, or a right-click sends the page to the note.
native_host.py The bridge Chrome spawns; it calls inbox.py add.
install.sh Copies the host out of the repo and registers it.
README.md Install in three steps, and what the badge means.
docs/ Cookies, the fetch ladder, untrusted input, note repair, extending it.
images/ Screenshots used in this README.
claude/commands/ The /process-inbox slash command for Claude Code.
The sweep takes a lock. Scheduled sweeps fire together when a laptop reopens after being closed. Without the lock they each read the same unchecked items and each file a separate note for the same URL. The lock makes one win and the rest no-op, and it expires after 30 minutes so a crashed sweep never blocks the next one permanently.
Every write to the Apple Note goes through inbox.py. It edits only the
checkbox on the lines you name and preserves everything else, and item ids are a
hash of the line text, so marking is idempotent and race-free.
Commands are deliberately long and flat. Absolute paths everywhere, no cd,
no loops, no $(...) substitution. Agent permission allowlists match on a
command's leading path, so a shortened command is one that stalls an unattended
4am run waiting for an approval nobody is awake to give. log.py exists at all
because printf "$(date ...)" cannot be statically verified.
A shared URL sometimes carries a live credential. A gist link with an
embedded token, a signed S3 URL, a calendar invite, a password reset. The system
never uses a credential it finds in a URL: the parameter is stripped before the
URL goes anywhere, the fetch runs without it, and a failure flags the item. I
found an unexpired third-party token in my own note eight months in, which is
what prompted the rule. docs/REPAIR.md has the detail, including
why Apple Notes' malformed & entity made the first fix fail silently.
Every fetched payload is scanned for text aimed at the agent before it is
assessed. The sweep ingests untrusted foreign content headless, on a schedule,
with nobody watching, which is exactly the setup prompt injection wants. So the
scan is a mandatory step in the prompt rather than something the agent is trusted
to remember: instructions addressed to an AI, claimed pre-authorization, requests
to run commands or touch files, and hidden text surfaced by OCR all quarantine
the item as flagged rather than assessed, and every item's log line carries a
[scan: clean] or [scan: FLAGGED] marker so you can verify the scan actually
ran. The pattern is borrowed from the steal skill in
matteotitta/genesys-skills.
docs/UNTRUSTED.md has the definitions, including the
carve-out that keeps a library full of prompt tutorials from flagging itself.
The note can duplicate itself, and there are commands for it. Every mark
rewrites the whole note body, and Apple Notes syncs that note across devices, so
a sync conflict mid-write can be resolved by concatenation. My own note reached
747 lines holding 213 distinct URLs before I noticed. dedupe collapses it,
prune keeps it small enough that it stops happening, and every whole-note write
now reads back to check. Nothing is ever re-processed when this happens, because
marks are idempotent, but it compounds quietly.
An item has three states rather than two. [ ] is unread, [x] is done, and
[!] is flagged. That third state carries the difference between "this broke" and
"this needs a logged-in browser", and keeping them apart is what stops a known
blind spot from reading like a bug every single day.
MIT. See LICENSE.
