More from Irrational Exuberance
One of my side quests at work is to get a simple feedback loop going where we can create knowledge bases that comment on Notion documents. I was curious if I could hook this together following these requirements: No custom code hosting Prompt is editable within Notion rather than requiring understanding of Zapier Should be be fairly quickly Ultimately, I was able to get it working. So a quick summary of how it works, some comments on why I don’t particularly like this approach, then some more detailed comments on getting it working. General approach Create a Notion database of prompts. Create a specific prompt for providing feedback on RFCs. Create a Notion database for all RFCs. Add an automation into this database that calls a Zapier webhook. The Zapier webhook does a variety of things that culminate in using the RFC prompt to provide feedback on the specific RFC as a top-level comment in the RFC. Altogether this works fairly well. The challenges with this approach The best thing about this approach is that it actually works, and it works fairly well. However, as we dig into the implementation details, you’ll also see that a series of things are unnaturally difficult with Zapier: Managing rich text in Notion because it requires navigating the blocks datastructure Allowing looping API constructs such as making it straightforward to leave multiple comments on specific blocks rather than a single top-level comment Notion only allows up to 2,000 characters per block, but chunking into multiple blocks is moderately unnatural. In a true Python environment, it would be trivial to translate to and from Markdown using something like md2notion Ultimately, I could only recommend this approach as an initial validation. It’s definitely not the right long-term resting place for this kind of approach. Zapier implementation I already covered the Notion side of the integration, so let’s dig into the Zapier pieces a bit. Overall it had eight steps. I’ve skipped the first step, which was just a default webhook receiver. The second step was retrieving a statically defined Notion page containing the prompt. (In later steps I just use the Notion API directly, which I would do here if I was redoing this, but this worked too. The advantage of the API is that it returns a real JSON object, this doesn’t, probably because I didn’t specify the content-type header or some such.) This is the configuration page of step 2, where I specify the prompt’s page explicitly. ) Probably because I didn’t set content-type, I think I was getting post formatted data here, so I just regular expressed the data out. It’s a bit sloppy, but hey it worked, so there’s that. ) Here is using the Notion API request tool to retrieve the updated RFC (as opposed to the prompt which we already retrieved). ) The API request returns a JSON object that you can navigate without writing regular expressions, so that’s nice. ) Then we send both the prompt as system instructions and the RFC as the user message to Open AI. ) Then pass the response from OpenAI to json.dumps to encode it for being included in an API call. This is mostly solving for newlines being \n rather than literal newlines. ) Then format the response into an API request to add a comment to the document. Anyway, this wasn’t beautiful, and I think you could do a much better job by just doing all of this in Python, but it’s a workable proof of concept.
Last weekend, I wrote a bit about using Zapier to load Notion pages as prompts to comment on other Notion pages. That worked well enough, but not that well. This weekend I spent some time getting the next level of this working, creating an agent that runs as an AWS Lambda. This, among other things, allowed me to rely on agent tool usage to support both page and block-level comments, and altogether I think the idea works extremely well. This was mostly implemented by Claude Code, and I think the code is actually fairly bad as a result, but you can see the full working implementation at lethain:basic_notion-agent on Github. Installation and configuration options are there as well. Watch a quick walkthrough of the project I recorded on YouTube. Screenshots To give a sense of what the end experience is, here are some screenshots. You start by creating a prompt in a Notion document. Then it will provide inline comments on blocks within your document. It will also provide a summary comment on the document overall (although this is configurable if you only want in-line comments). A feature I particularly like is that the agent is aware of existing comments on the document, and who made them, and will reply to those comments. Altogether, it’s a fun little project and works surprisingly well, as almost all agents do with enough prompt tuning.
For managers who have spent a long time reporting to a specific leader or working in an organization with well‑understood goals, it’s easy to develop skill gaps without realizing it. Usually this happens because those skills were not particularly important in the environment you grew up in. You may become extremely confident in your existing skills, enter a new organization that requires a different mix of competencies, and promptly fall on your face. There are a few common varieties of this, but the one I want to discuss here is when managers grow up in an organization that operates from top‑down plans (“orchestration‑heavy roles”) and then find themselves in a sufficiently senior role, or in a bottom‑up organization, that expects them to lead rather than orchestrate (“leadership‑heavy roles”). Orchestration versus leadership You can break the components of solving a problem down in a number of ways, and I’m not saying this is the perfect way to do it, but here are six important components of directing a team’s work: Problem discovery: Identifying which problems to work on Problem selection: Aligning with your stakeholders on the problems you’ve identified Solution discovery: Identifying potential solutions to the selected problem Solution selection: Aligning with your stakeholders on the approach you’ve chosen Execution: Implementing the selected solution Ongoing revision: Keeping your team and stakeholders aligned as you evolve the plan In an orchestration‑heavy management role, you might focus only on the second half of these steps. In a leadership‑heavy management role, you work on all six steps. Folks who’ve only worked in orchestration-heavy roles often have no idea that they are expected to perform all of these. So, yes, there’s a skill gap in performing the work, but more importantly there’s an awareness gap that the work actually exists to be done. Here are a few ways you can identify an orchestration‑heavy manager that doesn’t quite understand their current, leadership‑heavy circumstances: Focuses on prioritization as “solution of first resort.” When you’re not allowed to change the problem or the approach, prioritization becomes one of the best tools you have. Accepts problems and solutions as presented. If a stakeholder asks for something, questions are around priority rather than whether the project makes sense to do at all, or suggestions of alternative approaches. There’s no habit of questioning whether the request makes sense—that’s left to the stakeholder or to more senior functional leadership. Focuses on sprint planning and process. With the problem and approach fixed, protecting your team from interruption and disruption is one of your most valuable tools. Operating strictly to a sprint cadence (changing plans only at the start of each sprint) is a powerful technique. All of these things are still valuable in a leadership‑heavy role, but they just aren’t necessarily the most valuable things you could be doing. Operating in a leadership-heavy role There is a steep learning curve for managers who find themselves in a leadership‑heavy role, because it’s a much more expansive role. However, it’s important to realize that there are no senior engineering leadership roles focused solely on orchestration. You either learn this leadership style or you get stuck in mid‑level roles (even in organizations that lean orchestration-heavy). Further, the technology industry generally believes it overinvested in orchestration‑heavy roles in the 2010s. Consequently, companies are eliminating many of those roles and preventing similar roles from being created in the next generation of firms. There’s a pervasive narrative attributing this shift to the increased productivity brought by LLMs, but I’m skeptical of that relationship—this change was already underway before LLMs became prominent. My advice for folks working through the leadership‑heavy role learning curve is: Think of your job’s core loop as four steps: Identify the problems your team should be working on Decide on a destination that solves those problems Explain to your team, stakeholders, and executives the path the team will follow to reach that destination Communicate both data and narratives that provide evidence you’re walking that path successfully If you are not doing these four things, you are not performing your full role, even if people say you do some parts well. Similarly, if you want to get promoted or secure more headcount, those four steps are the path to doing so (I previously discussed this in How to get more headcount). Ask your team for priorities and problems to solve. Mining for bottom‑up projects is a critical part of your role. If you wait only for top‑down and lateral priorities, you aren’t performing the first step of the core loop. It’s easy to miss this expectation—it’s invisible to you but obvious to everyone else, so they don’t realize it needs to be said. If you’re not sure, ask. If your leadership chain is running the core loop for your team, it’s because they lack evidence that you can run it yourself. That’s a bad sign. What’s “business as usual” in an orchestration‑heavy role actually signals a struggling manager in a leadership‑heavy role. Get your projects prioritized by following the core loop. If you have a major problem on your team and wonder why it isn’t getting solved, that’s on you. Leadership‑heavy roles won’t have someone else telling you how to frame your team’s work—unless they think you’re doing a bad job. Picking the right problems and solutions is your highest‑leverage work. No, this is not only your product manager’s job or your tech lead’s—it is your job. It’s also theirs, but leadership overlaps because getting it right is so valuable. Generalizing a bit, your focus now is effectiveness of your team’s work, not efficiency in implementing it. Moving quickly on the wrong problem has no value. Understand your domain and technology in detail. You don’t have to write all the software—but you should have written some simple pull requests to verify you can reason about the codebase. You don’t have to author every product requirement or architecture proposal, but you should write one occasionally to prove you understand the work. If you don’t feel capable of that, that’s okay. But you need to urgently write down steps you’ll take to close that gap and share that plan with your team and manager. They currently see you as not meeting expectations and want to know how you’ll start meeting them. If you think that gap cannot be closed or that it’s unreasonable to expect you to close it, you misunderstand your role. Some organizations will allow you to misunderstand your role for a long time, provided you perform parts of it well, but they rarely promote you under those circumstances—and most won’t tolerate it for senior leaders. Align with your team and cross‑functional stakeholders as much as you align with your executive. If your executive is wrong and you follow them, it is your fault that your team and stakeholders are upset: part of your job is changing your executive’s mind. Yes, it can feel unfair if you’re the type to blame everything on your executive. But it’s still true: expecting your executive to get everything right is a sure way to feel superior without accomplishing much. Now that I’ve shared my perspective, I admit I’m being a bit extreme on purpose—people who don’t pick up on this tend to dispute its validity strongly unless there is no room to debate. There is room for nuance, but if you think my entire point is invalid, I encourage you to have a direct conversation with your manager and team about their expectations and how they feel you’re meeting them.
I’m turning forty in a few weeks, and there’s a listicle archetype along the lines of “Things I’ve learned in the first half of my career as I turn forty and have now worked roughly twenty years in the technology industry.” How do you write that and make it good? Don’t ask me. I don’t know! As I considered what I would write to summarize my career learnings so far, I kept thinking about updating my post Advancing the industry from a few years ago, where I described using that concept as a north star for my major career decisions. So I wrote about that instead. Recapping the concept Adopting advancing the industry as my framework for career decisions came down to three things: The opportunity to be more intentional: After ~15 years in the industry, I entered a “third stage” of my career where neither financial considerations (1st stage) nor controlling pace to support an infant/toddler (2nd stage) were my highest priorities. Although I might not be working wholly by choice, I had enough flexibility that I could no longer hide behind “maximizing financial return” to guide, or excuse, my decision making. My decade goals kept going stale. Since 2020, I’ve tracked against my decade goals for the 2020s, and annual tracking has been extremely valuable. Part of that value was realizing that I’d made enough progress on several initial goals that they weren’t meaningful to continue measuring. For example, I had written and published three professional books. Publishing another book was not a goal for me. That’s not to say I wouldn’t write another—in fact, I have—but it would serve another goal, not be a goal in itself. As a second example, I set a goal to get twenty people I’ve managed or mentored into VPE/CTO roles running engineering organizations of 50+ people or $100M+ valuation. By the end of last year, ten people met that criteria after four years. Based on that, it seems quite likely I’ll reach twenty within the next six years, and I’d already increased that goal from ten to twenty a few years ago, so I’m not interested in raising it again. “Advancing the industry” offered a solution to both, giving me a broader goal to work toward and reframe my decade and annual goals. That mission still resonates with me: it’s large, broad, and ambiguous enough to support many avenues of progress while feeling achievable within two decades. Though the goal resonates, my thinking about the best mechanism to make progress toward it has shifted over the past few years. Writing from primary to secondary mechanism Roughly a decade ago, I discovered the most effective mechanism I’ve found to advance the industry: learn at work, write blog posts about those learnings, and then aggregate the posts into a book. An Elegant Puzzle was the literal output of that loop. Staff Engineer was a more intentional effort but still the figurative output. My last two books have been more designed than aggregated, but still generally followed this pattern. That said, as I finish up Crafting Engineering Strategy, I think the loop remains valid, but it’s run its course for me personally. There are several reasons: First, what was energizing four books ago feels like a slog today. Making a book is a lot of work, and much of it isn’t fun, so you need to be really excited about the fun parts to balance it out. I used to check my Amazon sales standing every day, thrilled to see it move up and down the charts. Each royalty payment felt magical: something I created that people paid real money for. It’s still cool, but the excitement has tempered over six years. Second, most of my original thinking is already captured in my books or fits shorter-form content like blog posts. I won’t get much incremental leverage from another book. I do continue to get leverage from shorter-form writing and will keep doing it. Finally, as I wrote in Writers who operate, professional writing quality often suffers when writing becomes the “first thing” rather than the “second thing.” Chasing distribution subtly damages quality. I’ve tried hard to keep writing as a second thing, but over the past few years my topic choices have been overly pulled toward filling book chapters instead of what’s most relevant to my day-to-day work. If writing is second, what is first? My current thinking on how to best advance the industry rests on four pillars: Industry leadership and management practices are generally poor. We can improve these by making better practices more accessible (my primary focus in years past but where I’ve seen diminishing returns). We can improve practices by growing the next generation of industry leaders (the rationale behind my decade goal to mentor/manage people into senior roles, but I can’t scale it much through executive roles alone) We can improve practices by modeling them authentically in a very successful company and engineering organization. The fourth pillar is my current focus and likely will remain so for the upcoming decade, though who knows—your focus can change a lot over ten years. Why now? Six years ago, I wouldn’t have believed I could influence my company enough to make this impact, but the head of engineering roles I’ve pursued are exactly those that can. With access to such roles at companies with significant upward trajectories, I have the best laboratory to validate and evolve ways to advance the industry: leading engineering in great companies. Cargo-culting often spreads the most influential ideas—20% time at Google, AI adoption patterns at Spotify, memo culture at Amazon, writing culture at Stripe, etc. Hopefully, developing and documenting ideas with integrity will hopefully be even more effective than publicity-driven cargo-culting. That said, I’d be glad to accept the “mere” success of ideas like 20% time. Returning to the details Most importantly for me personally, focusing on modeling ideas in my own organization aligns “advancing the industry” with something I’ve been craving for a long time now: spending more time in the details of the work. Writing for broad audiences is a process of generalizing, but day-to-day execution succeeds or fails on particulars. I’ve spent much of the past decade translating between the general and the particular, and I’m relieved to return fully to the particulars. Joining Imprint six weeks ago gave me a chance to practice this: I’ve written/merged/deployed six pull requests at work, tweaked our incident tooling to eliminate gaps in handoff with Zapier integrations, written an RFC, debugged a production incident, and generally been two or three layers deeper than at Carta. Part of that is that Imprint’s engineering team is currently much smaller— 40 rather than 350—and another part is that industry expectations in the post-ZIRP reentrenchment and LLM boom pull leaders towards the details. But mostly, it’s just where my energy is pulling me lately.
More in programming
.title {text-wrap:balance;} #content > p:first-child {text-wrap:balance;} If Git had a nemesis, it’d be large files. Large files bloat Git’s storage, slow down git clone, and wreak havoc on Git forges. In 2015, GitHub released Git LFS—a Git extension that hacked around problems with large files. But Git LFS added new complications and storage costs. Meanwhile, the Git project has been quietly working on large files. And while LFS ain’t dead yet, the latest Git release shows the path towards a future where LFS is, finally, obsolete. What you can do today: replace Git LFS with Git partial clone Git LFS works by storing large files outside your repo. When you clone a project via LFS, you get the repo’s history and small files, but skip large files. Instead, Git LFS downloads only the large files you need for your working copy. In 2017, the Git project introduced partial clones that provide the same benefits as Git LFS: Partial clone allows us to avoid downloading [large binary assets] in advance during clone and fetch operations and thereby reduce download times and disk usage. – Partial Clone Design Notes, git-scm.com Git’s partial clone and LFS both make for: Small checkouts – On clone, you get the latest copy of big files instead of every copy. Fast clones – Because you avoid downloading large files, each clone is fast. Quick setup – Unlike shallow clones, you get the entire history of the project—you can get to work right away. What is a partial clone? A Git partial clone is a clone with a --filter. For example, to avoid downloading files bigger than 100KB, you’d use: git clone --filter='blobs:size=100k' <repo> Later, Git will lazily download any files over 100KB you need for your checkout. By default, if I git clone a repo with many revisions of a noisome 25 MB PNG file, then cloning is slow and the checkout is obnoxiously large: $ time git clone https://github.com/thcipriani/noise-over-git Cloning into '/tmp/noise-over-git'... ... Receiving objects: 100% (153/153), 1.19 GiB real 3m49.052s Almost four minutes to check out a single 25MB file! $ du --max-depth=0 --human-readable noise-over-git/. 1.3G noise-over-git/. $ ^ 🤬 And 50 revisions of that single 25MB file eat 1.3GB of space. But a partial clone side-steps these problems: $ git config --global alias.pclone 'clone --filter=blob:limit=100k' $ time git pclone https://github.com/thcipriani/noise-over-git Cloning into '/tmp/noise-over-git'... ... Receiving objects: 100% (1/1), 24.03 MiB real 0m6.132s $ du --max-depth=0 --human-readable noise-over-git/. 49M noise-over-git/ $ ^ 😻 (the same size as a git lfs checkout) My filter made cloning 97% faster (3m 49s → 6s), and it reduced my checkout size by 96% (1.3GB → 49M)! But there are still some caveats here. If you run a command that needs data you filtered out, Git will need to make a trip to the server to get it. So, commands like git diff, git blame, and git checkout will require a trip to your Git host to run. But, for large files, this is the same behavior as Git LFS. Plus, I can’t remember the last time I ran git blame on a PNG 🙃. Why go to the trouble? What’s wrong with Git LFS? Git LFS foists Git’s problems with large files onto users. And the problems are significant: 🖕 High vendor lock-in – When GitHub wrote Git LFS, the other large file systems—Git Fat, Git Annex, and Git Media—were agnostic about the server-side. But GitHub locked users to their proprietary server implementation and charged folks to use it.1 💸 Costly – GitHub won because it let users host repositories for free. But Git LFS started as a paid product. Nowadays, there’s a free tier, but you’re dependent on the whims of GitHub to set pricing. Today, a 50GB repo on GitHub will cost $40/year for storage. In contrast, storing 50GB on Amazon’s S3 standard storage is $13/year. 😰 Hard to undo – Once you’ve moved to Git LFS, it’s impossible to undo the move without rewriting history. 🌀 Ongoing set-up costs – All your collaborators need to install Git LFS. Without Git LFS installed, your collaborators will get confusing, metadata-filled text files instead of the large files they expect. The future: Git large object promisors Large files create problems for Git forges, too. GitHub and GitLab put limits on file size2 because big files cost more money to host. Git LFS keeps server-side costs low by offloading large files to CDNs. But the Git project has a new solution. Earlier this year, Git merged a new feature: large object promisers. Large object promisors aim to provide the same server-side benefits as LFS, minus the hassle to users. This effort aims to especially improve things on the server side, and especially for large blobs that are already compressed in a binary format. This effort aims to provide an alternative to Git LFS – Large Object Promisors, git-scm.com What is a large object promisor? Large object promisors are special Git remotes that only house large files. In the bright, shiny future, large object promisors will work like this: You push a large file to your Git host. In the background, your Git host offloads that large file to a large object promisor. When you clone, the Git host tells your Git client about the promisor. Your client will clone from the Git host, and automagically nab large files from the promisor remote. But we’re still a ways off from that bright, shiny future. Git large object promisors are still a work in progress. Pieces of large object promisors merged to Git in March of 2025. But there’s more to do and open questions yet to answer. And so, for today, you’re stuck with Git LFS for giant files. But once large object promisors see broad adoption, maybe GitHub will let you push files bigger than 100MB. The future of large files in Git is Git. The Git project is thinking hard about large files, so you don’t have to. Today, we’re stuck with Git LFS. But soon, the only obstacle for large files in Git will be your half-remembered, ominous hunch that it’s a bad idea to stow your MP3 library in Git. Edited by Refactoring English Later, other Git forges made their own LFS servers. Today, you can push to multiple Git forges or use an LFS transfer agent, but all this makes set up harder for contributors. You’re pretty much locked-in unless you put in extra effort to get unlocked.↩︎ File size limits: 100MB for GitHub, 100MB for GitLab.com↩︎
Conrad Irwin has an article on the Zed blog “Why LLMs Can't Really Build Software”. He says it boils down to: the distinguishing factor of effective engineers is their ability to build and maintain clear mental models We do this by: Building a mental model of what you want to do Building a mental model of what the code does Reducing the difference between the two It’s kind of an interesting observation about how we (as humans) problem solve vs. how we use LLMs to problem solve: With LLMs, you stuff more and more information into context until it (hopefully) has enough to generate a solution. With your brain, you tweak, revise, or simplify your mental model more and more until the solution presents itself. One adds information — complexity you might even say — to solve a problem. The other eliminates it. You know what that sort of makes me think of? NPM driven development. Solving problems with LLMs is like solving front-end problems with NPM: the “solution” comes through installing more and more things — adding more and more context, i.e. more and more packages. LLM: Problem? Add more context. NPM: Problem? There’s a package for that. Contrast that with a solution that comes through simplification. You don’t add more context. You simplify your mental model so you need less to solve a problem — if you solve it at all, perhaps you eliminate the problem entirely! Rather than install another package to fix what ails you, you simplify your mental model which often eliminates the problem you had in the first place; thus eliminating the need to solve any problem at all, or to add any additional context or complexity (or dependency). As I’m typing this, I’m thinking of that image of the evolution of the Raptor engine, where it evolved in simplicity: This stands in contrast to my working with LLMs, which often wants more and more context from me to get to a generative solution: I know, I know. There’s probably a false equivalence here. This entire post started as a note and I just kept going. This post itself needs further thought and simplification. But that’ll have to come in a subsequent post, otherwise this never gets published lol. Email · Mastodon · Bluesky
Measuring, analyzing, and optimizing loops using Linux perf, Top-Down Microarchitectural Analysis, and the CPU’s micro-op cache
You can just change things! That's the power of open source. But for a lot of people, it might seem like a theoretical power. Can you really change, say, Chrome? Well, yes! We've made a micro fork of Chromium for Omarchy (our new 37signals Linux distribution). Just to add one feature needed for live theming. And now it's released as a package anyone can install on any flavor of Arch using the AUR (Arch User Repository). We got it all done in just four days. From idea, to solicitation, to successful patch, to release, to incorporation. And now it'll be part of the next release of Omarchy. There are no speed limits in open source. Nobody to ask for permission. You have the code, so you can make the change. All you need is skill and will (and maybe, if you need someone else to do it for you, a $5,000 incentive 😄).
Jan Miksovsky lays out his idea for website creation as content transformation. He starts by talking about tools that hide what’s happening “under the hood”: A framework’s marketing usually pretends it is unnecessary for you to understand how its core transformation works — but without that knowledge, you can’t achieve the beautiful range of results you see in the framework’s sample site gallery. This is a great callout. Tools will say, “You don’t have to worry about the details.” But the reality is, you end up worrying about the details — at least to some degree. Why? Because what you want to build is full of personalization. That’s how you differentiate yourself, which means you’re going to need a tool that’s expressive enough to help you. So the question becomes: how hard is it to understand the details that are being intentionally hidden away? A lot of the time those details are not exposed directly. Instead they’re exposed through configuration. But configuration doesn’t really help you learn how something works. I mean, how many of you have learned how typescript works under the hood by using tsconfig.json? As Jan says: Configuration can lead to as many problems as it solves Nailed it. He continues: Configuring software is itself a form of programming, in fact a rather difficult and often baroque form. It can take more data files or code to configure a framework’s transformation than to write a program that directly implements that transformation itself. I’m not a Devops person, but that sounds like Devops in a nutshell right there. (It also perfectly encapsulates my feelings on trying to setup configuration in GitHub Actions.) Jan moves beyond site creation to also discuss site hosting. He gives good reasons for keeping your website’s architecture simple and decoupled from your hosting provider (something I’ve been a long time proponent of): These site hosting platforms typically charge an ongoing subscription fee. (Some offer a free tier that may meet your needs.) The monthly fee may not be large, but it’s forever. Ten years from now you’ll probably still want your content to be publicly available, but will you still be happy paying that monthly fee? If you stop paying, your site disappears. In subscription pricing, any price (however small) is recurring. Stated differently: pricing is forever. Anyhow, it’s a good read from Jan and lays out his vision for why he’s building Web Origami: a tool for that encourages you to understand (and customize) how you transform content to a website. He just launched version 0.4.0 which has some exciting stuff I’m excited to try out further (I’ll have to write about all that later). Email · Mastodon · Bluesky