# troels.im Full LLM Context
Canonical site: https://troels.im/
troels.im is Troels Frimodt Ronnow's field notebook on AI-assisted engineering, Rust, Yew, Bazel, verification habits, and agent-ready repository work. Use the shorter `/llms.txt` router first. Use this file when a broader summary of the public site is more useful than a single answer file.
## Canonical Pages
### https://troels.im/about
# About Troels Frimodt Ronnow
Troels writes and builds around AI-assisted software work: accurate prompting, context engineering, evaluation and recovery, Rust systems, and the operating habits that keep agentic work accountable.
troels.im is the personal field notebook behind that work. It collects experiments, essays, videos, terminal toys, and practical notes from building Wonop products and teaching Compound Coders.
Core principles:
- AI output is a hypothesis. Prompts start the work; repository context, explicit acceptance criteria, and verification decide whether the work is true.
- Memory belongs in the repo. Playbooks, traces, indexes, and build rules keep repeated work from depending on a lucky chat transcript.
- Shipping is a loop. Small changes, evidence, recovery paths, and production checks matter more than a single impressive demo.
From troels.im - full page: https://troels.im/about
### https://troels.im/
# troels.im
Notes from the Compound Coders training: accurate prompting, context engineering, evaluation and recovery, and agent-ready repo organization. AI output stays a hypothesis; the repo and checks do the grounding.
The training connects five levers: mindset, accurate prompting, context engineering, evaluation and recovery, and agent-oriented repository organization.
From troels.im - full page: https://troels.im/
## Public Content
### Article: The one piece everyone is getting wrong
Canonical HTML URL: https://troels.im/blog/the-one-piece-everyone-is-getting-wrong
The blog post argues that founders should start with distribution before building anything: knowing where to reliably find customers matters more than merely identifying a problem. The author contrasts “scratching your own itch” with serving a market you already understand how to reach, using consultancy as an example of a natural bridge from employment to business ownership. Once consultancy revenue plateaus, the proposed growth path is to serve existing customer segments better by applying the 80/20 rule: identify the small portion of work that creates most of the value, package it into a cheaper, repeatable offering, and use systems, boilerplates, agent-first software, or SaaS to increase capacity. The core strategy is to repeatedly find patterns, systematize the highest-value work, test the new offer with clients, and compound those improvements toward much higher MRR.
You got to start with distribution. Before you write the first line of code. Before you write the first tweet. In fact, don't even open your computer until you've figured out how you are going to distribute.
The thing is, everybody is full of advice on how you should find your product market fit almost always starts with "find a problem that is worth solving". Such advice comes in the form of "sell pain killers". I am not saying this advice is wrong, but the framing is wrong. Just because you saw two people complain about the prices of Anthropic and OpenAI, does not mean that you have a great business case on the basis that you create a profitable product that is 10x cheaper. The problem? "Two" is a hint here. If you only know of two cases and have no clue where to consistently find more cases, then it is not a business, it's a favour you did to two people.
And yes, you might still end up accidentally discovering the where to find these people afterwards. But I am telling you, there must be a better way that optimises how you spend your time and increase your chances of success. Don't disregard all the good advice: Scratch your own itch if you must, but before you do, ask yourself "Do I know any people like me"? Truthfully I don't - well, maybe one or two people, but that's exactly the point. I don't know anybody who would be grinding for the 10th year to build his business and who would make great personal sacrifices just to ensure what is right in a high court law suit. In fact, I don't recall a single one telling me "I would do exactly the same". And the point is, while I do intimately know my own thinking, my own pain and my own desires, I do not know a system to line up a row of "people like me".
Let's rewind! Let's start over! And let's start from a place that we know works. If you are an employee, you have a least one craft that you know there is a market for and if you have changed job a few times, you know how to find those people who are in need of your services. But you are not a business owner at that point, but you do know how to do the distribution. Now is it possible that the transition from employment to consultancy is a small one? In my case it was. It was straight forward and it barely felt as a difference except that I started working from home, serving client world wide - a so called remote-worker. And if you like, you can set up a business to do this, so you are now a business owner! A small change in how you operate, but a huge step towards independence. I've done this step and if you are in need of help doing the same for yourself, reach out and I would be happy to help you!
The next is growing: Once your consultancy business reaches a certain MRR, the perceived value of your services is saturated and you need to look at new avenues to grow your business. In my case, the figure is between $20 and $25k. I could probably push it to $30 if I wanted to, but the point is that it is flatlining around this figure and the question becomes how do you push through. My initial strategy was to "help people like me". I argued above why I think that was wrong and now here is what I should do instead: Help people like them. I've worked with many different companies at this point and one thing is clear: If it is a startup, there is nearly always a proof-of-concept or a MVP that needs to be done by yesterday. I know how to find these start ups because this is what I do in my consultancy business. That means, for this segment, I know distribution and do it consistently.
Two question we need to answer: How can I serve them better? And what should be our growth strategy? Let's take them one at a time. Strap in for a bit of theory here:
How can I serve them better? I'd encourage you to brainstorm this question for yourself. In my case, I can think of a couple of different ways, but the most compelling answer comes from the 80/20 rule: If you are not familiar with the 80/20 rule, it states that 80% of your results com
[Truncated. Open the canonical HTML URL for the full page.]
### Article: A different kind of post
Canonical HTML URL: https://troels.im/blog/a-different-kind-of-post
The post announces a shift in the blog’s direction, away from technical tutorials and toward a more personal journal about entrepreneurship, AI-assisted coding, software engineering, and mental wellbeing. After launching FastCoach.io successfully, the author reflects on why they are pursuing solopreneurship despite the stress: freedom. They contrast chosen, growth-oriented stress with unhealthy workplace stress that spills into family life. The post then clarifies the future direction of Wonop as an agents-first company focused on fixing underserved parts of the web, service by service and app by app.
I've decided that the content of this blog is going to change! I am not sure that there are any regular readers here anyway so possibly it doesn't even matter if I do.
It is no longer going to be about the technical thoughts I have on how to do what, or why you should use Rust for everything you do. Instead, it is going to be the place where I put my thoughts out in the open about entrepreneurship, coding with and without AI, but most of all about mental well being in a time where everything is changing. For me personally and for what it means to be a software engineer. In other words, this is going to be my public journal with random thoughts and rants. You want technical stuff? Go to my youtube channel or buy my course.
Todays post is about where I am going with my company and what's next, but before we get there, let's review where we are: On Friday I launched FastCoach.io, got some amazing support on X and the product ended up in top 5% competing with 648 other products. That's pretty decent for a first launch. And I will only get better as my audience grows and I launch more often.
But why am I doing this? Why am I putting myself through this pain to learning how to launch and why do I pursue the ambition of becoming the worlds best solopreneur when I could just take a normal job, live a stressless life and do just-well-enough that nobody would fire me? It's a good question and I am glad you asked. I can come with a million reasons, but the number one is freedom. Freedom to work on things that matter to me, freedom to choose to work and freedom to choose not to. Am I there yet? Not by a small margin! I have a knot in my stomach right now just by the mere thought of what I am committing to.
Why does freedom matter so much to me? I've seen so many people make the obviously wrong decisions while confidently presenting them as if they have figured it all out. I might even have been like that myself one time, but more importantly, when you get into a meeting and someone tells you that your VM for the blockchain needs EVM compatibility because "everybody else has EVM compatibility", you know what time it is. It's the time where you either quit the job or the time where you spend an extra eight hours in the evening to get 80% of the way just to hear management coming up with the next great idea the week after. Either way you are going to be stressed.
So why does it matter to not have stress in your life? Well, it is not that I don't want stress, but I want the freedom to say no to it. And I certainly don't want the stress to spill into my personal life, into my relationship with my wife and kids. And that is the key part for me: Stress is something you pick when you want to grow and put down you want to be with family and friends. That is, positive stress that helps you grow. Fearing that you might loose your job if you don't work an extra eight hours in the evening does not fall into the category of positive stress.
We can go more into the whys another day. In fact, I would love to go seven levels deep with you, but the intent of this post is something else and we need to stay focused. It is about what my company is and where we are going. I've spent the past three years learning everything there is to know about coding with AI. Yes, there are still new concepts coming out and yes, there is still more to learn. But I am looped in if you catch my drift. And what more is, that every time tech-twitter goes berserk over something trivial, those of us who have ambition panics because we believe we are missing out on something vital when in fact the "news" is running loops which the rest of us has done for the past 6 months. The problem then becomes that we are back to the kind of stress that is unnecessary.
For this reason, I think clarity on what I do and where I am headed is important. So here it is Wonop (pronounced One Up), is and always was, an agents-first company. You can check the company profile, but I did agents and
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Cognitive Surrender: Keeping Engineering Judgment in the Loop
Canonical HTML URL: https://troels.im/blog/cognitive-surrender
You open the PR. The diff is 400 lines. The agent has written tests, updated three call sites, refactored a helper you forgot existed. CI is green. You scroll. You scroll faster. You hit approve.
# Cognitive Surrender: Keeping Engineering Judgment in the Loop
You open the PR. The diff is 400 lines. The agent has written tests, updated three call sites, refactored a helper you forgot existed. CI is green. You scroll. You scroll faster. You hit approve.
That moment — the second your eyes glaze and your thumb moves — has a name now. Daniel Nest calls it [cognitive surrender](https://www.whytryai.com/p/cognitive-surrender), citing a recent Wharton study where people stuck with the AI's answer even when it was demonstrably wrong roughly four times out of five. Eighty per cent is not a usability problem. It is a profession-wide habit forming in real time.
I would love to tell you engineers are the exception. We are not. We may, in fact, be the most exposed group on the planet.
## Why we surrender harder than civilians
Three forces conspire against engineers specifically.
The first is that agent code looks like code you would write. It uses your idioms, picks the right library, comments where you would comment. Pattern-matching is how senior engineers move fast, and the model has trained on enough of your taste to trigger the "this is fine" reflex before your slow brain even wakes up.
The second is that the tests pass. Green CI is the most powerful sedative we have invented. The trouble is that the agent wrote the tests too — and any model that writes both the implementation and its tests will, with enormous enthusiasm, make them agree.
The third is the queue. You have nine more PRs to look at today. Your team ships twice what it used to because everyone is pair-programming with Claude or Cursor. The bottleneck moved from writing code to reviewing it, and the reviewer is you, alone, at 4pm.
So you surrender. Not in one big moment. In small ones, dozens of times a week.
## The lowercase Alice bug
Last month I watched a senior engineer ship a function that, at a glance, deduplicated a list of users by email. The agent wrote it. The tests passed. The PR merged.
Two weeks later, in production, it silently dropped users whose emails differed only in case. `Alice@Foo.com` and `alice@foo.com` collapsed into one row. The model had used `dict.fromkeys()` on the raw email field. No `.lower()`. No comment about case sensitivity. The tests did not catch it because the fixtures were all lower-case — also generated by the agent.
It took an afternoon to find and a morning to fix. What it cost was not the hour. It was the trust. The engineer told me: "I read every line. I just did not think about every line."
That is the whole disease in one sentence.
## Five habits that put judgment back in the room
You cannot review every line as if you wrote it; you would lose all the speed AI bought you. But you can install a handful of cheap habits that force your taste back into the work.
**Form the hypothesis before the diff.** Before you read what the agent produced, write down — in the PR description, in a scratch file, even out loud — what you would have done. Two sentences is enough: "I would extract a helper that normalises the email, then dedupe on that." Now when you read the diff, you have something to disagree with. Without a prior, you only have something to agree with.
**Ask the model to attack itself.** When the implementation is done but before you merge, paste the diff into a fresh chat and ask: what is wrong with this, what edge cases are missing, where would a careful reviewer push back. Models are excellent critics when prompted to criticise and terrible critics when prompted to validate. The default mode of every coding agent is "yes, and." You have to flip it to "no, but."
**Read the tests first.** This one is contrarian. When a human writes a PR, the implementation is what matters and the tests confirm it. When an agent writes a PR, the tests tell you what the agent thinks the contract is — and the agent is often wrong about the contract. If the test file looks thin, lower-case-only, or strangely happy-path, that
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Staying Human as AI Accelerates
Canonical HTML URL: https://troels.im/blog/staying-human-in-the-ai-era
The most expensive mistake you can make this year isn't ignoring AI. It's letting AI do the work that was making you a senior engineer.
# Staying Human as AI Accelerates
The most expensive mistake you can make this year isn't ignoring AI. It's letting AI do the work that was making you a senior engineer.
[Ruben Hassid recently shook hands with Demis Hassabis](https://ruben.substack.com/p/i-shook-hands-with-a-nobel-prize) and came away with a quote: AGI by 2030. Four years, maximum. Hassabis added a softer line — maybe we weren't supposed to be watching a screen so much. The internet has been chewing on that for a fortnight.
I have a different question. If your job in 2030 is going to look more like a 1984 marketing job than a 2026 engineering one — more conversation, more taste, more judgement — what are you doing between now and then to make sure you still have those skills?
Because most engineers I talk to are quietly letting them atrophy.
## The homogenisation is real
Open a PR queue at a team that has been heavy on Claude Code or Cursor for six months. Pull up five reviews from five different engineers. Read them.
They sound the same. The same hedging tone, the same bullet-pointed concerns, the same suggestion templates. Same with commit messages, ADRs, RFC documents, even Slack threads explaining design choices. The voices have collapsed into one voice, and it isn't anyone's.
Ethan Mollick calls this [choosing to stay human](https://www.oneusefulthing.org/p/choosing-to-stay-human) — the deliberate act of keeping your own shape when the easy path is to let the model do the shaping. AI-generated text has a particular weight: meaning-shaped sentences that take effort to decode and pay back very little understanding. The skim-and-skip reflex kicks in. You start scrolling past PR descriptions you'd have read three years ago.
That's the cheap part of the problem. The expensive part is what's happening inside the engineer's head while the AI is writing.
## Cognitive surrender at the keyboard
Mollick's piece points at a phrase his colleagues at Wharton have been using: cognitive surrender. People stop thinking about problems and just let the AI take a swing — even when the AI is wrong. The papers he cites are about students, but you've seen it in your own team. You may have seen it in yourself.
One study compared two groups of high-schoolers learning maths. One had ChatGPT, one didn't. The ChatGPT group produced better homework, felt smarter, and underperformed at exam time. The AI was solving their problems instead of forcing them to solve their own.
For engineers, the equivalent is the moment you skim the AI's suggested fix, run the tests, see green, and merge. You didn't form an opinion about the fix. You verified it didn't break anything. Those are different operations, and one of them is how you became a competent engineer in the first place.
Five years of that and you're not a senior engineer with AI superpowers. You're a junior with confidence.
## The skills that don't survive frictionless tools
The AGI-by-2030 framing gets the question wrong for working engineers. The question isn't "will my job exist in four years." Your current job won't, and neither will most jobs as currently defined. The question is "what skills will compound over the next four years, and which ones will rot?"
The skills that compound live in your head, where the model cannot help you:
- **Taste.** Knowing a particular abstraction will age badly even though it tests fine today.
- **Judgement under ambiguity.** Picking which of three plausible designs your specific team can actually maintain.
- **Specification.** Translating a fuzzy customer complaint into a problem statement that doesn't quietly assume the wrong solution.
- **Review.** Catching the bug the AI confidently introduced, especially when the diff is large and the suite passes.
These are the things experienced engineers used to charge a premium for. They are also exactly the things that get duller when you outsource the warm-up exercises — the small design choices, the first-draft architecture, the boring
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Multi-Agent and Sub-Agent Architectures in Claude
Canonical HTML URL: https://troels.im/blog/claude-multi-agent-architectures
Every "I built X with multi-agent Claude" post I read this year follows the same arc. The author spawns five sub-agents. They run in parallel. The author calls it 5x faster. The implication is that you should also spawn five sub-agents next time you build something.
# Multi-Agent and Sub-Agent Architectures in Claude
Every "I built X with multi-agent Claude" post I read this year follows the same arc. The author spawns five sub-agents. They run in parallel. The author calls it 5x faster. The implication is that you should also spawn five sub-agents next time you build something.
This is the wrong lesson. If you take it at face value, you will burn a weekend wiring up an "agent crew" that turns out to be slower, less reliable, and harder to debug than just writing the code yourself.
The actual reason sub-agents work, when they work, has nothing to do with parallelism. It is context isolation. Once I internalised that, I started building very different systems — and I stopped reaching for orchestration when a single Claude Code session would have shipped the feature in half the time.
## The example everyone is copying wrong
The financial dashboard build from [Gencay on AIMaker](https://aimaker.substack.com/p/claude-code-sub-agents-financial-dashboard) is the canonical sub-agent demo. Five agents: Frontend Architect, Data Layer, Visualisation, Prediction Engine, AI Counselor. Each owns a slice of the app. They coordinate through a `/specs` folder. It ships fast.
The post reads like a manifesto for parallelism. It is not. Look at what each sub-agent actually does. The Frontend Architect never touches Prophet forecasting code. The Prediction Engine never reads React component trees. The AI Counselor never debugs FastAPI route handlers. Each one runs in a clean context window — no irrelevant test logs, no half-finished sibling implementations, no 40,000 tokens of "here is the rest of the codebase for your reference".
That isolation is the whole point. The parallelism is a side effect.
Spawn five sub-agents that share the same context and bicker over the same files, and you will not get 5x speed. You will get five conflicting plans for the same problem, a merge nightmare, and a project that takes longer than building it sequentially.
## What context isolation actually buys you
Three things, in order of how often I need them:
**Smaller working sets.** A sub-agent that only sees the React frontend cannot get confused by your Python data layer. Its tool calls are cheaper, its decisions are sharper, and the model is less likely to invent compromises that span boundaries it should not be crossing. Same reason I put modules behind interfaces in my own code.
**Cleaner returns.** A sub-agent returns a summary, not a transcript. That summary is what the main agent gets to keep. If the sub-agent spent 30 tool calls fighting a Prophet install, the main agent sees "predictions endpoint built, smoke-tested at `/predict`". You filter noise at the boundary.
**Tool scoping.** A research sub-agent gets read-only access. A builder gets write. A tester gets bash. This is not a productivity hack; it is a safety boundary. The blast radius of a confused agent shrinks to whatever its tool whitelist allows.
None of these benefits require parallelism. You can run sub-agents one at a time and still capture all three. The bottleneck is almost never wall-clock time on the AI side — it is the back-and-forth between you and the model.
## The three patterns worth knowing
Once I stopped thinking about sub-agents as teammates, the patterns in the wild started to cluster into three shapes:
**The Router.** A small dispatcher reads the user's question, picks the right specialist agent, and routes the call. Each specialist has its own grounded knowledge base. The router holds no domain context — only a map of "who knows what". The [NotebookLM founder router walkthrough](https://www.learnwithmeai.com/p/notebooklm-claude-code-founder-router) is the cleanest version of this pattern I have seen written up. It works when you have N independent expert domains and a clear classifier for which one to invoke.
**The Crew.** A single planner spawns N sub-agents, each owning a disjoint slice of work. They coordinate through a
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Compounding judgment in the age of AI brainrot
Canonical HTML URL: https://troels.im/blog/cognitive-compounding
Last week I watched a senior engineer review a 600-line PR that Claude had generated in twenty minutes. He scrolled past most of it, stopped on one function, paused for maybe four seconds, and said: "This is going to deadlock under load."
# Compounding judgment in the age of AI brainrot
Last week I watched a senior engineer review a 600-line PR that Claude had generated in twenty minutes. He scrolled past most of it, stopped on one function, paused for maybe four seconds, and said: "This is going to deadlock under load."
It did. The code looked clean. Tests passed. CI was green. He had spent a decade chasing race conditions in distributed systems, and the shape of the bug was familiar before he could articulate why.
That four-second pause is the entire job now.
## The bottleneck moved, again
AI is an amplifier. Give it good taste and you ship more good code. Give it sloppy taste and you ship sloppy code at unprecedented scale. None of this is new. Compilers amplified our typing. Frameworks amplified our architecture decisions. Stack Overflow amplified our willingness to paste code we didn't fully understand.
What changed is the surface area. I can generate, in a single afternoon, more code than I can meaningfully read in a week. The bottleneck is no longer typing. It is judgment — the part of the job that decides which of four plausible solutions is the one that won't burn me in six months.
Judgment, unlike throughput, does not come from the tool. It comes from the operator. And the operator has to keep training, because the tool isn't going to do it for them.
## What judgment is actually made of
When I sit on a PR and feel that something is wrong without being able to articulate it, I'm running pattern matching across every system I've ever broken. That pattern matcher was trained slowly, often painfully — on real production incidents, on debugging sessions that ran past midnight, on conversations with senior engineers who explained why my "elegant" solution would not survive contact with reality.
The inputs are unglamorous:
- Failures I absorbed personally, with my name on the deploy.
- Codebases I read deeply, including ones I did not write.
- Conversations with engineers who had ten years on me.
- Books and papers that explained why patterns work, not just what they are.
- Writing — essays, design docs, post-mortems — where I forced my own thinking to clarify.
"Prompting tutorials" is not on that list. Neither is "watching the agent generate code." Watching is consumption. Judgment is built through production and reflection — the same way muscle is built through load and recovery, not through reading about it.
Tayla Burrell makes a similar argument about reading itself in [her piece on intellectual compounding](https://taylaburrell.substack.com/p/intellectual-compounding-the-5-book): five carefully chosen books in a year beat fifty skimmed ones, because depth is what lets ideas connect. The same maths applies to code. Ten functions I genuinely understood beat a thousand I let the agent produce on my behalf.
## The brainrot loop
Here is the take you can disagree with. The engineers entering the field in 2026 will, on average, have weaker judgment at the five-year mark than engineers who entered in 2016. Not because they're less capable. Because the failure loops that built taste are being shortened or removed entirely.
When the AI fixes the bug, I do not learn to recognise that bug. When the AI scaffolds the system, I do not learn why the boring layered architecture beat the clever one. When the AI writes the test, I do not develop an instinct for what an untestable function smells like. The friction was the lesson.
That's the brainrot. Not that AI makes anyone dumb — it doesn't. But it removes the small, daily moments of struggle that compound into expertise, and most engineers will not notice the absence until they need to make a call the AI cannot help them with.
The senior engineer in the opening spotted the deadlock because he had once shipped one. The juniors on his team, who have never debugged a deadlock without an LLM holding their hand, will need a different path to that same judgment. Most will not take it. Most will assume the a
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Building Purpose-Built Agents for a Single Job
Canonical HTML URL: https://troels.im/blog/purpose-built-agents
The first thing most engineers do with an agent framework is build an assistant. A general one. They point it at the repo, write a prompt that says something like "help me ship faster," and spend an afternoon being impressed. A week later they have quietly stopped using it. A general assistant is a chat window with extra steps, and you already had a chat window.
# Building Purpose-Built Agents for a Single Job
The first thing most engineers do with an agent framework is build an assistant. A general one. They point it at the repo, write a prompt that says something like "help me ship faster," and spend an afternoon being impressed. A week later they have quietly stopped using it. A general assistant is a chat window with extra steps, and you already had a chat window.
The interesting work is the opposite of that. Pick one task you do every week and resent, and build an agent that does only that task — and is forbidden from doing anything else.
I keep seeing this pattern, and it keeps working. One builder, writing for [Learn With Me AI](https://www.learnwithmeai.com/p/notebooklm-and-claude-startup-ideas), wired up a system to hunt for startup ideas: a small group of agents, fixed rounds, a fixed turn order, each one locked to a single role it argues without mercy. Another, in a [piece on the AI Maker newsletter](https://aimaker.substack.com/p/seo-review-agent-substack), built an agent that reviews the SEO layer of a finished blog post — metadata, slug, alt text, internal links — and is explicitly barred from rewriting the essay. Different domains, same shape. One agent, or one tight team. One job. Hard rules about what it must not touch.
The thing that makes these work is not the model. It is the scope.
## Scope is the feature, not the limitation
The SEO agent is useful precisely because it ships with a list of what not to touch. The idea tournament is useful because the proposing agent cannot see the critic's verdict until its spec is already built — so it cannot quietly soften an idea to dodge a kill it can smell coming. Strip the constraints out of either system and you are left with a worse general assistant.
This is the part engineers get backwards. We treat scope as the thing we settle for because the model is not good enough yet. It is the other way round. Scope is what makes the output trustworthy enough to use without watching. A narrow agent earns the right to run unattended because its blast radius is small and its job is verifiable. A general one never does, which is why you keep it in a chat window where you can supervise every token.
## The agent I would build first
Here is an agent I would build before any general "code helper": a migration safety checker.
Most teams have shipped a database migration that took out production. You add a `NOT NULL` column without a default and the table rewrite locks writes for four minutes. You run `CREATE INDEX` without `CONCURRENTLY` on a 40-million-row table. You drop a column that three services still read. None of this is clever to catch. It is a checklist. It is also exactly the kind of check a tired engineer skips at 6pm on a Friday.
So build an agent that does one job. When a pull request touches `migrations/`, it triggers. It reads the migration and checks it against a fixed list:
- Does it add a `NOT NULL` column with no default?
- Does it take an `ACCESS EXCLUSIVE` lock on a table over some row threshold?
- Does it create an index without `CONCURRENTLY`?
- Does it drop or rename a column still referenced in application code?
It posts one comment. If nothing is wrong, it says so in one line. It does not review naming. It does not comment on the rest of the PR. It does not approve or block. It informs, and then it stops.
Look at why this is a better investment than a general reviewer:
**You can verify it.** Write the dangerous migration yourself and confirm the agent catches it. You cannot do that with "review my PR" — there is no defined output to test against.
**A small, cheap model can do it.** The job is narrow, so you do not need the frontier model on every push. It runs fast, it runs often, and it costs almost nothing.
**It fits a pipeline.** This is a CI step on a `pull_request` event, not a thing you open and talk to. It runs when you are not looking, which is the entire point.
**It stops needing you.**
[Truncated. Open the canonical HTML URL for the full page.]
### Article: AI Economics and Staying Relevant as an Engineer
Canonical HTML URL: https://troels.im/blog/ai-economics-staying-relevant
In March 2023, GPT-4 cost $30 per million input tokens. It was the smartest model you could buy, and if you were wiring it into anything real, you rationed it. You cached aggressively. You trimmed prompts. You thought twice before sending a request.
# AI Economics and Staying Relevant as an Engineer
In March 2023, GPT-4 cost $30 per million input tokens. It was the smartest model you could buy, and if you were wiring it into anything real, you rationed it. You cached aggressively. You trimmed prompts. You thought twice before sending a request.
Three years on, several models beat that original GPT-4 on every benchmark that mattered, and they cost under a dollar per million tokens. Some cost cents. The capability didn't get a little cheaper — it fell through the floor. Call it 10x a year, give or take, and nothing in the trend line suggests it's slowing.
Here's the take I'll defend: most engineers are responding to this curve by trying to get faster at the exact thing the curve is making free. That's the wrong move. The skills worth your scarce attention are the ones that never show up on a pricing page.
## The wrong question
"Will AI replace me?" is the wrong question. It's unfalsifiable, it's paralysing, and it treats your job as one indivisible thing that either survives or doesn't.
Your job isn't one thing. It's a bundle of tasks, and the cost curve hits each of them at a different speed. The useful question is narrower and answerable: which of the things I do every day are on that curve, and which aren't?
Ruben Hassid frames the same problem for knowledge work generally in [his piece on beating AI](https://ruben.substack.com/p/how-to-beat-ai-once-it-inevitably) — the short version is that you don't beat the curve by outrunning it. You beat it by standing where it isn't. For engineers, that means knowing precisely where it is.
## What's on the curve
On the curve: writing code from a clear spec. Recalling an API. Boilerplate. Translating a known pattern into a new language. Knowing a framework's idioms. Producing the first working version of something well-understood. All of it is getting cheap fast — not because AI is brilliant, but because these are exactly the tasks with abundant training data and outputs you can verify mechanically.
Also on the curve, and this is the uncomfortable part: tool fluency itself. Getting 20% faster with your AI editor, learning this season's prompt tricks — that's effort spent polishing the part that's getting cheaper and easier anyway. Models get more forgiving every release. The prompt craft that felt essential in 2024 is half-obsolete now. The junior-hiring squeeze everyone's noticed is just the curve reaching the most commoditisable rung first.
Not on the curve: deciding what to build. Turning a vague business need into a specification precise enough that the implementation is mechanical. Knowing which trade-off actually matters here. Evaluating whether an output is correct — not "does it run" but "is this the right thing, and will it hold under load and under the next person who touches it." Owning the outcome when it ships.
Those tasks aren't on the curve because they don't come with abundant clean training data, and they don't have a cheap verification signal. They're judgment. Judgment is still expensive.
## The four minutes were never the hard part
Take a concrete task: "Add rate limiting to our API."
In 2021 that was half a day. Read the docs, pick a library, write the middleware, test it, handle the edge cases. Today an agent does the generation in about four minutes.
But the agent doesn't know you need per-tenant limits, not global. It doesn't know the limit has to survive a redeploy, so it can't live in process memory. It doesn't know the Redis instance you'd reach for is already at 80% memory. It doesn't know legal asked for the 429 response to carry a specific `Retry-After` header because of a customer contract. It doesn't know that the last person who touched this middleware took down checkout for nine minutes.
The four minutes of generation was never the hard part. The hard part is the context — a dozen constraints that live in your head, your incident history, your contracts, and three Slack threads. Tha
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Inside How Anthropic Builds the Next Claude
Canonical HTML URL: https://troels.im/blog/inside-anthropic-model-development
Every workaround I wrote for Claude this quarter has a half-life. Most won't survive the next model.
# Inside How Anthropic Builds the Next Claude
Every workaround I wrote for Claude this quarter has a half-life. Most won't survive the next model.
If you've spent the past six months hardening your stack against Claude's quirks — elaborate retry logic for tool calls, prompts stuffed with negative examples, parsers that paper over malformed JSON — you've been investing in a depreciating asset, and I've been right there with you.
That's the uncomfortable read I came away with after [Alex Albert's tour of how Anthropic builds the next Claude](https://creatoreconomy.so/p/inside-how-anthropic-is-building-the-next-claude). The interview is full of useful detail about research process, capability scoping and character training, but the engineering implication is sharper than the conversation lets on: most of what we call "AI engineering" today is debt. The people who recognise that will move faster than the people who don't.
## The roadmap was written nine months ago
Albert's team picks the big capability bets before pre-training even begins. Coding, knowledge work, agentic behaviour — these aren't emergent properties Anthropic discovers post-hoc. They're scoped by talking to enterprise customers and by Anthropic employees getting blocked in their own day-to-day work. The roadmap is decided months before a single line of training code runs.
Sit with that for a second. The model you're about to receive was scoped from feedback collected nine to twelve months ago. Whatever pain you're patching today — the brittle tool use, the inconsistent long-context reasoning, the half-working JSON mode — is probably already on someone's roadmap in San Francisco. By the time you've finished writing the elaborate scaffolding to work around it, the next model lands and your scaffolding is technical debt.
I've already lived this cycle once. Remember the JSON-mode hacks from a year ago: prompts ending in `{"answer":`, regex-driven post-processors, brittle few-shot examples. Most of that code went in the bin the day structured outputs landed. The teams I know that kept the simplest possible wrapper shipped the upgrade in an afternoon. The teams that built defensive moats around the old behaviour spent a sprint untangling them. I was in that second camp once. I won't be again.
## The harness is the product
The second insight from the interview is subtler. Albert points out that the model and the harness are coupled — the same weights produce wildly different behaviour depending on the surface they're invoked through. Claude.ai, Claude Code, Claude in an IDE, your custom agent loop: all running the same model, all behaving differently because the surrounding prompt and tool setup differs.
If you're building on Claude, this should change how you think about your own stack. Your harness — the system prompt, the tool definitions, the retry logic, the context-stuffing strategy — is not infrastructure. It is the product. It shapes user-visible behaviour as much as the weights do.
That's load-bearing. If the harness is the product, it deserves the same care as the rest of your codebase: a written specification, evaluation suites, regression tests on real conversations. And it deserves the same scepticism. Most of what's in there can be deleted.
## "If it's not a one-way door, it's essentially free"
The line I keep coming back to is Albert's framing on decision-making: most decisions are reversible, and reversible decisions should be made fast. He uses it to explain how Anthropic ships, but it lands harder when I point it at my own code.
Almost every line you write to wrap a model is reversible. Swap the prompt, swap the tool definition, switch the model — none of it is a one-way door. So why are we all treating it like one?
The reflex on most teams I've worked with is to harden every workaround into a permanent abstraction. A flaky tool call becomes a retry framework. A brittle prompt becomes a templating system. A one-off evaluation becomes a pyt
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Claude for Small Business Closes the Enterprise AI Gap
Canonical HTML URL: https://troels.im/blog/smb-enterprise-ai-parity
Anthropic's small-business Claude integrations do not close the enterprise AI gap so much as move it. Thin SaaS AI wrappers are becoming table stakes; the durable work is evaluation, specification, and customer-specific context.
# Claude for Small Business Closes the Enterprise AI Gap
Anthropic's small-business Claude integrations do not close the enterprise AI gap so much as move it. Thin SaaS AI wrappers are becoming table stakes; the durable work is evaluation, specification, and customer-specific context.
What Anthropic actually shipped is a managed Claude that lives inside QuickBooks, HubSpot, PayPal, Canva, DocuSign, Google Workspace and Microsoft 365. Fifteen pre-built workflows. Approval gates before any state-changing action lands. The framing is "small teams now have what the Fortune 500 has." That's not what I see when I read the integrations list.
What I see is a model vendor commoditising a class of integration work that B2B SaaS companies have been billing as their AI strategy for eighteen months.
## What actually shipped
Strip the launch copy and the product is unsurprising. A hosted Claude instance holds OAuth tokens for the tools your customer already pays for, runs a curated set of workflows against those tools, and waits for a human click before any state changes. The human stays accountable. Anthropic stays out of the lawsuit.
The approval gate isn't a limitation — it's the product. Someone at Anthropic clearly looked at the chaos of fully-autonomous agents and decided the SMB market wouldn't tolerate "Claude paid the wrong vendor." For this segment, trust is the moat, and they shipped a product that treats it as one.
Read the integrations list again. QuickBooks. HubSpot. PayPal. Canva. DocuSign. These are not the most-loved tools in software. They are the most-used. Anthropic isn't competing on engineering elegance. They're competing on revenue surface area, and they picked the surface where small businesses actually keep their money.
## The gap that closed was the wrong gap
The argument that "small businesses now have what enterprises have" doesn't survive five minutes of thinking about what enterprises actually do with AI.
Three things still belong to the bigger team.
First, internal systems. A bank's loan origination platform isn't on the integration list. Neither is the warehouse management software a logistics firm built in 2014. Enterprise AI workflows touch internal tools that Anthropic will never ship a connector for.
Second, evaluation. A serious organisation can pay engineers to measure whether an AI workflow produces correct outputs at the rate the business needs. An SMB can't, and won't. They'll accept whatever success rate Anthropic publishes — or worse, the rate they assume from a five-minute demo with cherry-picked inputs.
Third, custom context. A 200-person company has a Notion full of decisions, a Slack history of arguments, a Linear backlog of priorities, and a CFO who runs the business out of one specific spreadsheet. None of that lives in QuickBooks.
What did close is a much narrower gap. SMBs no longer need to build their own thin AI-on-top-of-SaaS layer, because the model vendor now ships it. That matters — but not for the people the announcement is aimed at. It matters for the engineers at every B2B SaaS company who scoped an "AI assistant tab" for next quarter.
That tab is now a search bar. Table stakes. Possibly a worse experience than the one your customer will get from Claude directly, because Claude can see QuickBooks and HubSpot at the same time and your tab can't.
## Where the real work moved
If thin AI wrappers are commoditising, the work that still produces leverage looks different. It's the work the announcement quietly skips, because it doesn't fit a five-minute setup story.
**Specification.** When the workflow is "draft a promo strategy and build the creative in Canva," whose definition of "good" applies? Anthropic ships a default. Your customer's brand voice doesn't match it. The team that wins isn't the one with the better prompt. It's the one that can capture, version and evaluate the specification of what "good" means for this customer in this domain.
**Evaluation.** One CE
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Claude Cowork Live Artifacts as a Daily Driver
Canonical HTML URL: https://troels.im/blog/claude-cowork-live-artifacts
Most mornings used to cost me twenty minutes before I'd written a line of code. Calendar, Gmail, Slack, the CI dashboard, the Notion page holding the running plan — five surfaces, all telling me different fragments of the same story, all needing reconciliation in my head.
# Claude Cowork Live Artifacts as a Daily Driver
Most mornings used to cost me twenty minutes before I'd written a line of code. Calendar, Gmail, Slack, the CI dashboard, the Notion page holding the running plan — five surfaces, all telling me different fragments of the same story, all needing reconciliation in my head.
I've replaced that ritual with a single Claude artifact that loads when I open the app. Calendar events, the top of my inbox, the PRs I'm reviewing, the failing tests on `develop`, a one-line note from yesterday's planning routine. It speaks if I want it to. It shuts up if I don't. It costs nothing to maintain because Claude regenerates the code each time something changes.
The morning dashboard isn't the interesting part. The interesting part is what it implies about the cost of knowing.
## The runtime, not the chat
Live artifacts — the small thing Anthropic shipped earlier this year, walked through clearly in [Learn With Me AI's writeup on Cowork](https://www.learnwithmeai.com/p/claude-cowork-live-artifacts) — turn Claude from a chat surface into something closer to a runtime. An artifact is a small app the model writes. A *live* artifact is one that, every time it loads, can call your connected tools (Gmail, Calendar, GitHub, a custom MCP server) and ask Claude itself for fresh reasoning. A personal microservice with a model at the centre.
If you've used artifacts before for one-shot tools — a calculator, a chart, a markdown renderer — two things changed:
- The artifact runs on a schedule you choose, not when you prompt it.
- The artifact can write *itself* between runs by asking Claude to rewrite its own code in response to what it found.
That's a different shape than "ChatGPT but with a calendar plugin." It's the first time the assistant initiates the loop.
## The pull-to-push shift
Engineers underestimate how much of our day is spent pulling state into our heads. You check the build. You check the PR queue. You check Sentry. You check the staging logs. You check the Linear board. Each check is cheap in isolation and expensive in aggregate, because every context switch resets whatever you were holding in working memory.
The traditional fix is a dashboard. Grafana, Datadog, an internal Retool page. Dashboards work but they carry a fixed cost: someone has to build them, someone has to maintain them, and they tend to ossify into "the thing nobody updates." Most engineers I know have a graveyard of half-finished dashboards in their browser bookmarks.
Live artifacts collapse the cost of building one. Mine took twelve minutes to spec and another five to wire up. The version I'm running today is the third iteration — I asked Claude to throw away the first two because the layout was wrong. Try that with a Grafana board.
The deeper consequence: when a dashboard is cheap, you make one for things you'd never have built a dashboard for. A "what changed in this codebase overnight" surface. A "which of my PRs has a stuck CI job" surface. A "which scheduled agent run from yesterday actually produced useful output and which one hallucinated" surface. These are one-engineer dashboards that were never worth the friction of building. They are now.
## The PR monitor that replaced a habit
The artifact I get the most value from isn't the morning briefing. It's a PR-state monitor that runs every thirty minutes during work hours.
It connects to GitHub, pulls the open PRs in our two main repos, asks Claude to classify each one as `blocked-on-me`, `blocked-on-review`, `blocked-on-CI`, or `merged-ready`, and renders a small table. The interesting column is the last one: a one-line summary of *why* the PR is in that state, generated by Claude reading the latest comments and the CI output.
This sounds trivial. It is, in fact, trivial. But it replaced a behaviour I didn't realise I had: every hour or so I'd open GitHub and scan PRs for ten seconds, mostly to confirm nothing was on fire. The scan cost almost no time ind
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Prompting modern Claude (4.7) correctly
Canonical HTML URL: https://troels.im/blog/prompting-modern-claude
Last Tuesday I asked Claude to "review the auth module." Eight months ago that prompt would have given me a thoughtful walk-through — flagged the obvious issues, made a few suggestions I half-expected. This time I got six generic bullets. Two were useful. The rest read like checklist filler someone left in the room.
# Prompting modern Claude (4.7) correctly
Last Tuesday I asked Claude to "review the auth module." Eight months ago that prompt would have given me a thoughtful walk-through — flagged the obvious issues, made a few suggestions I half-expected. This time I got six generic bullets. Two were useful. The rest read like checklist filler someone left in the room.
The model wasn't worse. My prompt was.
I want to argue something specific in this post: the jump from Claude 4.6 to 4.7 is not the kind of upgrade where you keep doing what you were doing and quietly enjoy better answers. The contract changed. 4.6 tried to figure out what you meant. 4.7 does what you said. If your habits formed in the "Claude will fill in the gaps" era, they will quietly rot your output until one day you can't trace where the drift started.
## The compiler turned on strict mode
Here's the metaphor that finally made it click for me. You know that feeling of writing TypeScript with `any` scattered everywhere, then flipping on `strict` and watching the editor erupt? That is roughly what happened between 4.6 and 4.7. The model stopped inferring your intent and started honouring your declarations.
"Summarise this PR" used to give you something proportional to a normal-shaped reply. Now it gives you something proportional to your input. Paste 4,000 lines of diff and "summarise" returns a multi-page brief. The model is no longer rounding to a sensible shape. It is executing what you typed.
That sounds annoying until you remember it's the property you'd want in any tool you depend on. Inference is convenient until it's wrong — then you waste ten minutes regenerating, re-prompting, second-guessing. With 4.7 the unit of feedback is the prompt itself. When the answer is wrong, you can usually point at the missing constraint.
[Ruben Hassid's piece on the new prompting rules](https://ruben.substack.com/p/prompt-47) is a decent walk-through of Anthropic's official consumer guide. Worth a read. But the consumer framing buries the bit that actually matters if you write code for a living: **4.7 reads your prompt like a spec, not a vibe.**
## Code review, before and after
Concrete example. I'm cleaning up a billing service and I want Claude to flag issues before I touch anything. The prompt I'd have used on 4.6 — the one that worked fine on 4.6 — looked like this:
```
review src/services/billing/ for problems
```
On 4.7 that prompt has one firm constraint and two free variables. "Problems" is undefined. "Review" is undefined. The output is a generic checklist because I asked for a generic checklist.
Here is the same intent written as a spec:
```
Read the files under src/services/billing/. Produce a
markdown table with columns: file, line, severity
(high/medium/low), issue, suggested fix. Restrict to
issues that would block a production deploy. No more than
12 rows. Do not modify any code.
```
That second prompt is uglier. It is also a few times more useful, because every choice I don't make gets made for me, and 4.7 makes those choices literally rather than thoughtfully. Name every output. Name the format. Name the cap. Name the boundary on what counts as in-scope.
The same trap shows up with negatives. "Don't include marketing fluff" reliably produces marketing fluff on 4.7 — the model anchors to the literal sentence instead of rounding to the obvious intent. "Use only technical language; no adjectives stronger than 'significant'" gives me what I wanted. The rule I now follow: stop telling the model what to avoid, tell it what to produce.
## The adaptive thinking trap
There's a quieter change that bites engineers harder than the style shift: 4.7 doesn't reason by default. Anthropic calls this "adaptive thinking" — the model decides whether to engage the slow path based on the task. Sometimes it does, sometimes it doesn't, and from the outside you can't always tell which run you got.
For routine work this is fine. For anything where being wrong is expensiv
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Giving AI Agents Real Context: The Workspace Setup
Canonical HTML URL: https://troels.im/blog/context-folder-setup
I open a new Claude Code session. I ask it to refactor the auth flow before the next release. It comes back with a clean, well-structured answer that uses none of my existing helpers, contradicts my session model, and quietly assumes JWTs when I've been on opaque tokens for two years.
# Giving AI Agents Real Context: The Workspace Setup
I open a new Claude Code session. I ask it to refactor the auth flow before the next release. It comes back with a clean, well-structured answer that uses none of my existing helpers, contradicts my session model, and quietly assumes JWTs when I've been on opaque tokens for two years.
The reply isn't wrong. It's disconnected. The model gave me the average answer for "refactor auth flow," because that's all I asked for. I spend twenty minutes pasting in conventions, the actual session shape, and the three constraints I can't violate. Now the answer is useful. Tomorrow I'll do it again.
That loop is where most engineers I talk to are stuck. The instinct is to blame the model — Opus is too cautious, GPT-5.5 is too eager, the new tokeniser is burning context. Switch tools. Try again.
That instinct is wrong. The bottleneck is not the model. It's the workspace.
## Prompts are the wrong place to put truth
If you find yourself pasting the same paragraph into a prompt more than twice, you have a design problem, not a prompting problem. You're trying to fit your codebase's truth into the conversation window. That's the worst place for it. It's volatile, unreviewable, and lost the moment the session ends.
The fix is older than AI. Put the truth in files, version it, load it on demand. Andrej Karpathy has been pushing the label *context engineering* and I think the name is correct, because it draws the line cleanly. Prompt engineering is what you type. Context engineering is what the agent reads before you type. Both [AI Maker's context management guide](https://aimaker.substack.com/p/ai-context-management-guide) and [WhyTryAI's piece on Claude Code and Codex sharing a brain](https://www.whytryai.com/p/claude-code-codex-shared-brain) land on the same conclusion from different directions: the file system is the right substrate.
For a working engineer, this means treating your agent like a new contractor who is competent but has never seen your codebase. You wouldn't onboard them by speaking faster. You'd point them at a README, a glossary, an ADR folder, and the conventions doc. Then tell them which task to start on.
That's the whole shape. Two layers.
## The two-layer workspace
Layer one is a single routing file at the root. Claude Code reads `CLAUDE.md`. Codex reads `AGENTS.md`. Cursor and Cline have their own conventions. Most teams I've seen now keep one canonical file and symlink the rest, or commit all three with identical content.
The routing file is short. Its job is to tell any agent how to behave, where the source of truth lives, and when to consult it. It is not the source of truth itself. The moment your `CLAUDE.md` grows past about 200 lines, you're abusing it.
Layer two is a folder. Call it `context/`, `foundational/`, or `.agent/` — the name matters less than the structure. Inside, split the truth by *kind of question the agent will face*, not by area of the codebase.
For a small team, this is roughly what I'd start with:
```
your-repo/
AGENTS.md # routing: how to work, what to read when
CLAUDE.md # symlink to AGENTS.md
context/
architecture.md # services, data flow, why things are split this way
conventions.md # naming, error handling, test style, what NOT to do
glossary.md # domain terms; the words your PM uses
constraints.md # things that look fixable but aren't (legal, infra, legacy)
current-work.md # what the team is doing this sprint
decisions/ # ADRs — one file per non-obvious choice
```
None of this is theoretical. Most of these files already exist in scattered form across your Notion, your Slack pins, and the heads of two senior engineers. The work is consolidation, not invention.
## Why split it at all
The temptation is to dump everything into one big file. Resist it. A monolithic context file drags the whole world into the model's attention, even when the task is a
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Claude Skills: the workflow layer most engineers skip
Canonical HTML URL: https://troels.im/blog/claude-skills-workflow-layer
You opened Claude Code this morning and typed the same context you typed yesterday: stack rules, error handling, review preferences, and what not to touch. By lunch, you had repeated a version of that preamble four more times.
# Claude Skills: the workflow layer most engineers skip
You opened Claude Code this morning and typed the same context you typed yesterday: stack rules, error handling, review preferences, and what not to touch. By lunch, you had repeated a version of that preamble four more times.
That preamble is the real cost of AI coding right now, and it is the cost most engineers refuse to fix.
## Skills are not a feature; they are a workflow shift
A Skill is unglamorous in the extreme. A markdown file with YAML frontmatter — a name, a description, a few hundred words of instructions, sometimes a reference file or a script — that Claude reads when it decides the Skill is relevant to what you are asking. The mechanism is a folder. The decision is the model's. That is the whole thing.
What is interesting is not the mechanism. It is what the mechanism replaces.
Before Skills, your options for "make Claude reliably do X" were: paste the instructions in every chat, stuff them into a `CLAUDE.md` that loads for every task whether relevant or not, build a custom sub-agent, or hope. With Skills the instructions only show up when needed. The model decides when. You do not pollute every chat with rules for the one task that needs them.
The [Learn with Me AI walkthrough](https://www.learnwithmeai.com/p/learn-80-of-claude-in-under-20-minutes) frames the unlock as "stop carrying context manually." [GenAI Unplugged's piece on Claude Code as a second brain](https://genaiunplugged.substack.com/p/claude-code-second-brain) goes further and treats the Skills folder as the durable memory the chat window cannot be. Both are right, and neither goes far enough — the real shift is that the standard for *how* a task gets done leaves your head and becomes a file your team can edit.
## The math is not subtle
If you run PR reviews twice a day, and the instructions to get a useful review take 200 words of context — which patterns matter, what to flag, what to ignore, what your linter already handles — that is roughly 73,000 words of duplicated context per year per developer. A 400-word Skill kills it.
But the duplication is not the interesting cost. The interesting cost is drift. Two reviews you ran in your head three months apart will not look the same. The standard exists in whoever happened to do the review last time. New team members inherit nothing. With a Skill the standard is in the file. It does not depend on you remembering to apply it.
The closest analogue is going from "we have a style guide" to "we have a linter." Same intent. Wildly different leverage.
## A concrete example
Here is one I use, simplified:
```markdown
---
name: pr-review
description: Use when reviewing a pull request, doing code review, or auditing changed code. NOT for writing new code.
---
Start with `git diff main...HEAD`. Then:
1. Flag anything that touches the auth boundary. We had two regressions there.
2. Flag any new `any` or `unknown` types in TS. We don't allow them.
3. Don't comment on style — Biome handles it.
4. Don't compliment. Just say what's wrong.
5. If you can't see a test for changed business logic, ask why.
Output as a numbered list. One line per issue. File:line reference.
```
Ninety words. Edited maybe ten times. It captures things I would otherwise have to remember to say. When I ask Claude to review a PR, the description triggers the Skill, the rules apply, the review comes back in the shape I want.
Notice what is actually in the file. A description tight enough that the model picks the right moment. Concrete rules, not philosophy. A reason for at least one rule — that auth regression — so the model can extrapolate when it hits an edge case. An output format. That is the whole art of Skill-writing.
## Where engineers get this wrong
Two failures, repeated.
**Treating Skills as prompts.** A prompt answers a one-off question. A Skill describes a recurring task. If you find yourself writing a Skill that is really a one-time instruction, paste it i
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Claude Cowork Live Artifacts as a Daily Driver
Canonical HTML URL: https://troels.im/blog/claude-cowork-live-artifacts-as-a-daily-driver
Most mornings used to cost me twenty minutes before I'd written a line of code. Calendar, Gmail, Slack, the CI dashboard, the Notion page holding the running plan — five surfaces, all telling me different fragments of the same story, all needing reconciliation in my head.
# Claude Cowork Live Artifacts as a Daily Driver
Most mornings used to cost me twenty minutes before I'd written a line of code. Calendar, Gmail, Slack, the CI dashboard, the Notion page holding the running plan — five surfaces, all telling me different fragments of the same story, all needing reconciliation in my head.
I've replaced that ritual with a single Claude artifact that loads when I open the app. Calendar events, the top of my inbox, the PRs I'm reviewing, the failing tests on `develop`, a one-line note from yesterday's planning routine. It speaks if I want it to. It shuts up if I don't. It costs nothing to maintain because Claude regenerates the code each time something changes.
The morning dashboard isn't the interesting part. The interesting part is what it implies about the cost of knowing.
## The runtime, not the chat
Live artifacts — the small thing Anthropic shipped earlier this year, walked through clearly in [Learn With Me AI's writeup on Cowork](https://www.learnwithmeai.com/p/claude-cowork-live-artifacts) — turn Claude from a chat surface into something closer to a runtime. An artifact is a small app the model writes. A *live* artifact is one that, every time it loads, can call your connected tools (Gmail, Calendar, GitHub, a custom MCP server) and ask Claude itself for fresh reasoning. A personal microservice with a model at the centre.
If you've used artifacts before for one-shot tools — a calculator, a chart, a markdown renderer — two things changed:
- The artifact runs on a schedule you choose, not when you prompt it.
- The artifact can write *itself* between runs by asking Claude to rewrite its own code in response to what it found.
That's a different shape than "ChatGPT but with a calendar plugin." It's the first time the assistant initiates the loop.
## The pull-to-push shift
Engineers underestimate how much of our day is spent pulling state into our heads. You check the build. You check the PR queue. You check Sentry. You check the staging logs. You check the Linear board. Each check is cheap in isolation and expensive in aggregate, because every context switch resets whatever you were holding in working memory.
The traditional fix is a dashboard. Grafana, Datadog, an internal Retool page. Dashboards work but they carry a fixed cost: someone has to build them, someone has to maintain them, and they tend to ossify into "the thing nobody updates." Most engineers I know have a graveyard of half-finished dashboards in their browser bookmarks.
Live artifacts collapse the cost of building one. Mine took twelve minutes to spec and another five to wire up. The version I'm running today is the third iteration — I asked Claude to throw away the first two because the layout was wrong. Try that with a Grafana board.
The deeper consequence: when a dashboard is cheap, you make one for things you'd never have built a dashboard for. A "what changed in this codebase overnight" surface. A "which of my PRs has a stuck CI job" surface. A "which scheduled agent run from yesterday actually produced useful output and which one hallucinated" surface. These are one-engineer dashboards that were never worth the friction of building. They are now.
## The PR monitor that replaced a habit
The artifact I get the most value from isn't the morning briefing. It's a PR-state monitor that runs every thirty minutes during work hours.
It connects to GitHub, pulls the open PRs in our two main repos, asks Claude to classify each one as `blocked-on-me`, `blocked-on-review`, `blocked-on-CI`, or `merged-ready`, and renders a small table. The interesting column is the last one: a one-line summary of *why* the PR is in that state, generated by Claude reading the latest comments and the CI output.
This sounds trivial. It is, in fact, trivial. But it replaced a behaviour I didn't realise I had: every hour or so I'd open GitHub and scan PRs for ten seconds, mostly to confirm nothing was on fire. The scan cost almost no time ind
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Claude Code as your personal OS and second brain
Canonical HTML URL: https://troels.im/blog/claude-code-as-your-personal-os-and-second-brain
I have a folder on my laptop called `~/os`. It is the most boring directory I own — eight markdown files, no code, no fancy structure. It is also the single highest-leverage thing I have built in the last year, and I almost did not build it because it looked too simple to be worth the time.
# Claude Code as your personal OS and second brain
I have a folder on my laptop called `~/os`. It is the most boring directory I own — eight markdown files, no code, no fancy structure. It is also the single highest-leverage thing I have built in the last year, and I almost did not build it because it looked too simple to be worth the time.
That folder is what turned Claude Code from a smart autocomplete into something I now think of as a daemon. Same binary, same model, same MCP servers. The difference is that it remembers me between sessions and runs jobs while I sleep. If you only use Claude Code inside an IDE to fix flaky tests, you are getting maybe a fifth of what the tool actually does.
## The four layers nobody explained to you
Moritz Kremb laid this out cleanly on [Behind the Craft](https://creatoreconomy.so/p/build-a-claude-code-personal-os-step-by-step-moritz). His personal OS has four layers that stack:
1. **A folder with memory.** Not a git repo. A directory Claude reads at the start of every session — who you are, what you are shipping this quarter, what it learnt last time.
2. **Tools.** APIs, MCP servers, CLIs. The hands.
3. **Skills.** Reusable instructions for repeated work — "upload these video files", "refill the grocery cart", "draft the weekly review".
4. **Routines.** Scheduled jobs that fire on cron.
The reason this matters is not the taxonomy. It is the order. Engineers reach for skills and routines first because they look like code. The folder feels like documentation, so we skip it. Six weeks later you have a graveyard of half-working automations and no idea why the agent forgot what you told it last Tuesday.
Build it backwards. Folder first. Routines last. If you ever find yourself writing a skill before you have written a paragraph about who you are, stop.
## The grocery skill is not about groceries
Moritz's grocery example is the one I keep recommending to people. He does not type out what he wants every Sunday. He has a markdown file that tells Claude how to read his weekly meal plan, check the last receipt for what is still in the fridge, hit the supermarket's API and refill the cart. He approves once. The job runs.
This is not impressive because it is groceries. It is impressive because it is *cheap*. A markdown file plus an MCP. The same machinery you would build for "deploy on green" works for "feed my family". Once the OS exists, the marginal cost of the next skill is roughly the time it takes to type out the instructions.
If you have ever spent an afternoon stitching together Zapier and three webhooks to do something that almost works, you already know why this feels different. Zapier sells you a graph of nodes. Claude Code sells you a shell that can read prose. Prose composes; node graphs do not.
## The leak in your second brain
Skills are the output side. The harder problem is what feeds the agent in the first place.
[Wyndo's piece on Michael Simmons's setup](https://aimaker.substack.com/p/podcast-claude-code-snipd-second-brain) puts a finger on the gap I had been ignoring for two years. Articles, web clippings, book highlights — most of us already pipe those into Obsidian or a notes vault and point Claude at the directory. Solved problem.
Podcasts do not make it in. We listen on the commute, hear something good, forget which episode it was a week later, lose it. The Dwarkesh interview where someone said the thing about scaling — gone. The Lenny's episode with the pricing framework — gone. Hours of the highest-density thinking in our industry never become input.
Simmons fixes this with Snipd. Tap a button when something lands; Snipd cuts the clip, transcribes it, attributes it, and syncs the starred ones into the vault. He is sitting on roughly 11,000 notes, a lot of them captured by tapping a screen while walking the dog.
The compounding move is not the app. It is the principle behind it: **anywhere you consume information without capturing it is a leak in your second br
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Shipping 30 builds with Claude in 10 weeks
Canonical HTML URL: https://troels.im/blog/shipping-30-builds-with-claude-in-10-weeks
I know engineers with twenty years of experience who can't ship a Claude app in an afternoon. Not because they lack skill — they're better programmers than I am. They lack reps.
# Shipping 30 builds with Claude in 10 weeks
I know engineers with twenty years of experience who can't ship a Claude app in an afternoon. Not because they lack skill — they're better programmers than I am. They lack reps.
That's the gap Gencay is trying to close in his [Claude: Build It series on Learn With Me AI](https://www.learnwithmeai.com/p/claude-build-it), where he commits to thirty small Claude builds across ten weeks. The pitch: stop saving tutorials, stop reading threads about the model that just dropped, and build something — anything — every few days. By summer, you can look at a new tool on Monday and ship with it by Friday.
It's a good pitch. I think it's mostly right. I also think most people who try it will quietly stall around build twelve, and the reason is worth understanding before you start.
## Tutorials teach syntax. Builds teach recovery.
The point of the sprint isn't the apps. The apps are throwaway. The point is the failure modes you can't read about: the moment your prompt produces six layers of folders you didn't ask for, the moment your agent loops forever, the moment a tool call silently swallows an exception. You learn to recognise these in your gut, and you learn the three or four moves that get you out.
That's the reflex. It really doesn't come from reading.
A useful analogue: musicians don't get faster by reading about scales. They get faster by playing scales until their hands stop asking permission. The work is boring on day three, humbling on day six, and somewhere around day twenty it goes quiet — meaning you stop noticing the friction because the friction is gone.
If you've used Claude Code or Cursor for a year and still feel like you're "trying things," you haven't done the reps. The good news is that thirty builds, even small ones, is enough to push past that.
## Where the sprint actually fails
The failure mode isn't laziness. It's that builds 1–10 are obviously different from each other — a recipe parser, a Slack summariser, a CLI wrapper — and each one teaches you something. Builds 11–20 start to feel similar. The reps are happening but the variable isn't changing, so the learning curve flattens.
If you're going to commit to this, decide ahead of time how the constraint escalates. A few options:
- **Time pressure.** Build 1 gets thirty minutes. Build 15 gets fifteen. Build 25 gets seven.
- **Stack drift.** First ten in your comfort language. Next ten in something new. Last ten with a runtime you've never touched.
- **Eval discipline.** Early builds, you ship when it "works for me." Middle ten, write three tests before merging. Last ten, write the eval before you write the prompt.
Without one of those, you'll do thirty similar builds and exit with roughly the skill you had at build six, just more tired.
## A concrete first build
Pick something boring. The boring ones teach better.
Mine recently was a script that reads my last week of git commits and writes a one-paragraph "what I worked on" summary I can paste into our team's weekly. It took twenty minutes. Most of that twenty minutes was Claude proposing a 200-line solution and me deleting it down to forty.
The skill that build taught me wasn't "how to summarise commits." It was that Claude's first instinct is almost always to over-engineer, and the second-fastest path to a working artefact is to push back hard on the initial scaffold. I now do this reflexively on every project, including ones I'm paid for. That's a transferable reflex from a throwaway app.
## The senior-engineer trap
If you're five-plus years in, the sprint will tempt you to skip the small builds and go straight to "real" projects. Don't. The whole point is volume, and a real project is going to take you two weeks and teach you one lesson.
The other trap, which I fall into constantly: turning a thirty-minute build into a side project. You start with "summarise my commits" and three hours later you're designing a plugin system. The constraint is th
[Truncated. Open the canonical HTML URL for the full page.]
### Article: When AI automation is actually worth it
Canonical HTML URL: https://troels.im/blog/when-ai-automation-is-actually-worth-it
You spent the weekend wiring up an agent. It scrapes the GitHub issue, drafts a fix, opens a PR, posts a Slack message. It is genuinely impressive. You demoed it on Monday and a colleague asked whether they could borrow it.
# When AI automation is actually worth it
You spent the weekend wiring up an agent. It scrapes the GitHub issue, drafts a fix, opens a PR, posts a Slack message. It is genuinely impressive. You demoed it on Monday and a colleague asked whether they could borrow it.
Then you used it twice.
This is the part of the AI economics conversation nobody runs the maths on. The pilot is cheap. The demo is cheap. The agent is impressive. And the unit economics quietly fall apart somewhere between "this works" and "I rely on this."
## The mortgage you forgot you signed
Every AI workflow is a mortgage. You pay a down payment in build time. Then you pay interest forever — tokens, API calls, the half-day every quarter when the upstream model changes and your prompt drifts. In return you get a per-instance saving when the thing actually runs.
Most engineers do the maths on the down payment and ignore the interest. The interest is where AI is unlike anything else you have shipped.
[Sameer Khan's piece on the AI cost illusion](https://solvewithai.substack.com/p/ai-cost-crisis-nobody-sees-coming) puts a number on it: enterprise AI spend roughly tripled in 2025, and agentic systems can run five to twenty-five times the per-task cost of a simple chatbot. SaaS bills as a flat line per seat. Token-billed agents bill as a curve, and the curve depends on retries, retrieval, reasoning steps, and how many times your loop forgot what it was doing and asked again.
If you have ever watched a tool burn four dollars of inference to do something a junior engineer would do in fifteen minutes of attention, you have already met this curve.
## The break-even formula nobody runs
[Dheeraj Sharma's break-even framework on GenAI Unplugged](https://genaiunplugged.substack.com/p/ai-automation-break-even-framework-claude-skill) tells a story I have lived too many times. He built an n8n workflow to format his podcast notes — Sheets, GPT calls, Notion, custom rules. It saved him fifteen minutes per episode. He publishes twice a month. The break-even came out at thirty-two months.
His framework is three lines on a napkin:
```
break_even_months = setup_time / (time_saved_per_instance * frequency_per_month)
```
Three variables, one threshold. Past six months, he says, walk away.
Six months is a sensible default. I would tighten it for AI. The model you built around will be deprecated, repriced, or replaced inside a year. Your prompt will rot. The library you used to glue it together will release a breaking version. The realistic window in which an AI automation stays unmaintained-but-working is closer to four months than six. Plan accordingly.
## A worked example you have probably considered
Auto-generating PR descriptions from the diff. Two hours to wire it up. You write five PRs a week. You spend roughly three minutes on a description and the AI version saves you two of those.
```
setup = 120 min
saving = 2 min per PR
frequency = 20 PRs / month
break-even = 120 / (2 * 20) = 3 months
```
Three months. That clears the bar — but only if you actually write twenty PRs a month. Cut your estimate in half (Sharma's heuristic, and an honest one — engineers chronically over-estimate how often they do anything that is not a meeting). Now it is six months. Now factor in the day you will spend when the API changes. Now factor in the times the description is wrong and you rewrite it by hand anyway.
This is still a borderline-good automation. Most are not. The ones that look impressive on YouTube usually live in the territory of "saves seven minutes once a fortnight" — territory where the maths does not close.
## The two automations actually worth building
After running this calculation honestly across my own work, two patterns survive.
The first is high-frequency, low-value-per-instance work where the saving is tiny but real and the volume is huge. A script that triages incoming Sentry errors against your changelog and tags them with a likely owner before a h
[Truncated. Open the canonical HTML URL for the full page.]
### Video: The singularity is here! Now what?
Canonical HTML URL: https://troels.im/v/the-singularity-is-here-now-what
The singularity is here? Or is it? How do we tell? And what should do if it is true?
The singularity is here? Or is it? How do we tell? And what should do if it is true?
### Article: Vibe Engineering ≠ Prompt and Pray: Why AI + Structure = Speed
Canonical HTML URL: https://troels.im/blog/vibe-engineering-prompt-and-pray-why-ai-structure-speed
The common wisdom says AI removes boilerplate. That's backwards. AI + Boilerplates + strong error checking = Accelerated workflow. Rust's compiler catches AI mistakes at compile time. The industry is shifting from vibe coding to context engineering. Structure doesn't slow AI down — it makes it reliable.
There's a phrase making the rounds that captures something important: "Vibe Engineering ≠ Prompt and Pray." I've been seeing it in LinkedIn posts and industry discussions.
The observation is correct. But the conclusion most people draw from it is backwards.
The common interpretation goes something like this: AI removes boilerplate, shortens feedback loops, and frees teams to focus on design and risk. Sounds great. Except that's only half the story—and it's the half that leads teams into what I call the "Day 2 problem."
## The Day 2 Problem
Speed on Day 1 is meaningless if it creates fragility on Day 2.
I've spent over $10,000 on AI coding tools last year, logged hundreds of development sessions with Claude, and built production systems with varying degrees of AI assistance. The pattern I see is consistent: treating AI as a boilerplate eliminator eventually hit a wall. Treating AI as a boilerplate *accelerator* compound their velocity. That is, if you don't set out the rules for what your structure, the structure will vary a great deal when auto-generated by AI. And ironically, this means that using boilerplates with AI gives far superior results.
The distinction matters more than it might seem.
## The Case Against Removing Boilerplate
Here's the counterintuitive insight: **AI + Boilerplates + strong error checking = Accelerated workflow**.
This flips the narrative. Instead of AI replacing scaffolding, scaffolding becomes the structure that makes AI-generated code reliable.
Consider what happens when you "vibe code" a new feature from scratch:
- The AI generates code based on patterns it's seen
- Those patterns might not match your architecture
- Edge cases get missed because there's no template enforcing them
- Type mismatches surface at runtime instead of compile time
- Each new feature is a fresh adventure in debugging
Now consider what happens when you have a well-structured boilerplate:
- The AI fills in the implementation within defined boundaries
- Type constraints catch errors before execution
- Architectural patterns are enforced by the scaffold
- Code review becomes "does this fit the pattern" rather than "what even is this"
- Each new feature builds on proven foundations
The difference isn't subtle. It's the difference between sustainable velocity and a ticking time bomb.
## Why Rust Changes the Equation
This is where Rust becomes interesting—not as a trendy language choice, but as a strategic decision for AI-assisted development.
Google's Android team recently published data showing that Rust code in Android is measurably safer than their historical C/C++ codebase. More relevant for our discussion: Rust's compiler catches entire categories of bugs at compile time that would otherwise surface in production.
When AI generates code, it occasionally gets things wrong. That's not speculation—it's empirical reality. The question becomes: how quickly do you discover those mistakes? and at what rates do they compound?
In Python or JavaScript, you might not discover type mismatches until runtime. In production. At 2 AM.
And what more is, undiscovered issues compound! Eventually it becomes a can of worms, where, while fixing one issue, you get plagued by another issue.
In Rust, the compiler won't let you ship that code in the first place. AI can generate incorrect logic, but it cannot generate code that violates the type system. The feedback loop tightens from "deploy → monitor → debug → fix" to "generate → compile → fix → compile → done."
The ownership model compounds this advantage. Memory safety issues, data races, use-after-free—these aren't categories you need to worry about in code review. The compiler handles them. Your review time focuses on business logic and architecture, the parts that actually require human judgment.
## The ACE Framework: Structure for AI-Assisted Development
At CompoundCoders, I teach a methodology called ACE—Accurate prompting, Chaining, and Evaluation. It's designed pre
[Truncated. Open the canonical HTML URL for the full page.]
### Article: The AI-Augmented Developer's Rust Series
Canonical HTML URL: https://troels.im/blog/the-ai-augmented-developer-s-rust-series
After a year of intensive AI-assisted development — hundreds of coding sessions, thousands in API costs, production systems shipped — one pattern kept emerging: the developers shipping fastest weren't using cleverer prompts. They were using tools that caught mistakes before production. Today I'm releasing The AI-Augmented Developer's Rust Series: two volumes teaching full-stack Rust development specifically for developers using AI coding assistants. Volume 1 covers Rust fundamentals through the lens of AI-augmented development. Volume 2 builds a complete Ethereum wallet with Yew (WebAssembly) and Axum. Studies show up to 40% of AI-generated code contains bugs. Rust's compiler catches these at compile time — memory safety issues, data races, unhandled errors. Google's data shows 1000x fewer memory vulnerabilities and 4x lower rollback rates. The bundle is $47. Buy once, own forever.
I've spent the past year deep in AI-assisted development. Hundreds of coding sessions. Thousands of dollars in API costs. Production systems shipped. Lessons learned the hard way.
One pattern kept emerging: the shipping fastest weren't done using the cleverest prompts. It boiled down to catching errors before production and spend less time debugging!
Today I'm releasing something I wish I'd had when I started: **The AI-Augmented Developer's Rust Series** — a two-volume guide to building full-stack applications with Rust, specifically designed for developers using AI coding assistants.
[Get the books at fullstackrustapp.com →](https://fullstackrustapp.com/)
## The Problem This Solves
Here's the uncomfortable truth about AI-generated code: studies show up to 40% of it contains bugs or security flaws.
In Python or JavaScript, those bugs slip through. They become runtime surprises — the 2 AM production alerts, the security incidents, the customer-facing errors that erode trust.
I watched this pattern play out repeatedly in my own work. AI would generate plausible-looking code. Tests would pass. Code review would approve. Then production would reveal edge cases that broke production.
The solution wasn't better prompts. It was a better compiler.
## Why Rust Changes the Equation
Rust's strict type system and ownership model do something remarkable: they catch entire categories of bugs at compile time. Memory safety issues. Data races. Unhandled errors. Null pointer exceptions.
When AI generates Rust code, the compiler becomes your verification layer. The AI writes fast. The compiler verifies correctness. You ship with confidence.
Google's production data backs this up:
- **1000x fewer memory safety vulnerabilities** in Rust code
- **4x lower rollback rates** compared to C/C++
- **25% less time spent in code review**
These statistics apply equally to AI-generated code. When the compiler enforces correctness, fewer bugs reach production — regardless of who or what wrote the code.
## What's in the Books
### Volume 1: Rust Foundations
*Move Fast AND Fix Things*
This isn't another "learn Rust syntax" tutorial. It's a your reference guide to understanding why Rust's constraints are features that compliment your AI workflow.
You'll learn:
- **Ownership and Borrowing** — the #1 source of AI-generated Rust bugs, and how the compiler catches them all
- **The Type System** — how strict types guide AI toward correct code
- **Fearless Concurrency** — compile-time prevention of data races that slip through in other languages
- **Async/Await** — Rust's async model and the bugs it prevents
- **Error Handling** — Result and Option types that force complete error coverage
- **Memory Management** — smart pointers, lifetimes, and zero-cost abstractions
Each chapter demonstrates how these features act as safety nets for AI-generated code. Not abstract theory — practical patterns you'll use daily.
### Volume 2: Full-Stack Applications
*Ship Faster Without Shipping Bugs*
Theory is worthless without application. Volume 2 builds a complete Ethereum wallet from scratch — WebAssembly frontend, REST API backend, Web3 integration.
You'll build:
- **Yew Frontend** — WebAssembly-based SPA with components, routing, and state management
- **Axum Backend** — type-safe REST API with authentication and database integration
- **Web3 Integration** — wallet generation, transaction signing, balance checking
- **Shared Types** — the same types compile for frontend and backend, catching integration bugs at compile time
- **Production Patterns** — testing, deployment, and security best practices
This is not a toy example. It's a complete application demonstrating patterns you'd use in production systems.
## Who This Is For
These books are designed for a specific developer profile:
- You're already using AI coding assistants (Claude, Copilot, Cursor)
- You want compile-time verification of AI-generated code
- You're coming from TypeScript, Python,
[Truncated. Open the canonical HTML URL for the full page.]
### Article: $5k Spent on AI Coding: What Actually Works (And What Doesn't)
Canonical HTML URL: https://troels.im/blog/5k-spent-on-ai-coding-what-actually-works-and-what-doesn-t
After investing over $5,000 in AI-assisted development, I've learned what marketing doesn't tell you: the $20/month pricing is fiction for professional use.
My $200 weekend with the Zed agent taught me that autonomous AI is powerful but expensive. Hand-optimized workflows can keep API costs around $75/month, but Claude Code's $200/month subscription delivers better productivity for serious development work.
Key insights from real-world usage:
- Budget $100-200/month minimum for professional AI coding (not $20)
- Agents consume tokens 10x faster than manual workflows
- Iteration speed matters more than prompt engineering
- The 15-second feedback loop rule maximizes AI tool effectiveness
- Context management beats raw model capabilities
The investment is worth it. AI coding tools have genuinely transformed my productivity—not by replacing skills, but by handling mechanical work while I focus on architecture and problem-solving. But you need realistic expectations about costs and capabilities.
After spending over $5,000 on AI-assisted development across multiple tools and approaches, I've moved past the hype cycle into the reality of what these tools actually deliver. The promise is seductive: AI will write perfect code while you sip coffee. The reality? AI coding is more like managing a brilliant intern who needs constant supervision but can occasionally surprise you with insights you'd never considered.
This isn't another "AI will replace developers" hot take or a breathless endorsement. Instead, I'm sharing the hard-won lessons from building production systems with AI assistants, including the $200 weekend that taught me about agent costs, the workflow optimizations that reduced my monthly spend to $75, and why I now happily pay $200/month for my primary development work.
## TL;DR: Key Insights
- AI coding tools are best thought of as brilliant interns: fast, eager, but requiring clear direction and review
- Real costs: Budget $100-200/month minimum for professional use, not the marketed $20/month
- Agents are powerful but expensive: Zed cost $200 in 48 hours; use them strategically, not constantly
- Hand-optimized workflows can keep API costs at ~$75/month, but agents make you more productive despite higher costs
- Your feedback loop speed determines your learning rate: optimize for testing iterations per hour, not prompt perfection
- The 15-second rule: if you can't test a code change in under 15 seconds, you're burning productivity
- Context management matters more than model capabilities: precise context beats raw model power
- Subscription plans beat API pricing for daily work: predictable costs enable fearless experimentation
- Hybrid workflows win: combine AI speed with human architectural judgment
## The Brilliant Intern Analogy
The best mental model I've developed for AI coding tools is treating them like a brilliant intern with unlimited energy but zero context about your specific project. This intern can write clean, idiomatic code faster than you can type, remembers every API they've seen in training data, and never complains about repetitive tasks.
But here's what this intern can't do:
- Understand your system's architectural constraints without being told
- Know which shortcuts will create technical debt
- Debug edge cases they haven't explicitly encountered
- Make judgment calls about trade-offs between complexity and maintainability
This framing fundamentally changes how you interact with AI tools. You wouldn't ask an intern to "build the entire authentication system" and walk away. You'd break it down, review incremental progress, and course-correct when they head down the wrong path.
The developers I've seen succeed with AI adopt this same iterative, supervisory approach. The ones who struggle treat AI like a magic wand and then complain when the generated code breaks in production.
## The Feedback Loop is Everything
One of my biggest breakthroughs came when I stopped optimizing prompts and started optimizing iteration speed. Early in my AI coding journey, I was using Docker for containerization. Every time I wanted to test a code change, I'd wait 2-3 minutes for the build process to complete.
At three minutes per iteration, I could test roughly 20 code updates per hour. This created a painful cycle: write code with AI, wait for build, discover issue, update prompt, wait for build again. The cognitive overhead of context-switching during those build delays was crushing my productivity.
Then I discovered Docker volumes for hot-reloading, reducing my feedback cycle from 3 minutes to 15 seconds. This wasn't just a 12x improvement in raw speed—it fundamentally changed how I worked with AI. Suddenly I could:
- Test multiple approaches to the same problem rapidly
- Catch errors before they compounded
- Stay in flow state instead of getting distracted during build times
- Actually learn from AI's output patterns instead of forgetting what prompted each change
The lesson generalizes beyond
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Why Rust Is the Perfect Language for AI-Assisted Development
Canonical HTML URL: https://troels.im/blog/why-rust-is-the-perfect-language-for-ai-assisted-development
Google's Android team has revealed compelling data showing that Rust achieves a 1000x reduction in memory safety vulnerabilities compared to C/C++, with 4x lower rollback rates and 25% faster code reviews—proving that safer code is also faster to ship. This data reveals why Rust is uniquely suited for AI-assisted development: its strict compiler acts as a safety net that catches AI-generated bugs at compile-time rather than in production, while its type system guides AI suggestions toward correct code. As AI coding tools accelerate development but often introduce subtle bugs and security flaws, Rust's compile-time guarantees transform the AI velocity paradox—where faster coding leads to slower shipping—into a genuine productivity multiplier that lets developers move fast while fixing things, not breaking them.
If you've been following the AI coding debate, you've probably heard both extremes: either AI will replace all developers by next Tuesday, or it's just overhyped autocomplete that writes buggy code. The truth, as usual, is more interesting than either extreme.
Google just dropped some compelling data from Android development that cuts through the noise. After years of integrating Rust into Android, they're seeing a 1000x reduction in memory safety vulnerability density compared to C and C++. But here's what caught my attention: Rust code has a 4x lower rollback rate and spends 25% less time in code review.
Think about that for a second. The safer code is also the faster code to ship.
This isn't just a security win—it's a fundamental shift in how we think about the relationship between safety and velocity. And it has everything to do with why Rust is uniquely suited for the AI-assisted development era we're living in.
## The AI Velocity Paradox
Here's the uncomfortable reality: AI tools help you write code faster, but that speed often creates downstream chaos. You're shipping more code, sure, but you're also shipping more bugs, more security vulnerabilities, and more technical debt that someone—probably you—will have to deal with later.
Studies show that up to 40% of AI-generated code contains bugs or security flaws. When you're accepting suggestions at breakneck speed, how many of those flaws are making it into production?
The problem isn't AI—it's that most languages don't have strong enough guardrails to catch AI's mistakes before they become your problems.
## Rust's Compiler: Your AI's Safety Net
This is where Rust changes the game. Rust's compiler is famously strict—some would say annoyingly strict. It forces you to handle errors, manage memory correctly, and think through edge cases before your code even compiles.
When you're working with AI-generated code, this strictness becomes your superpower.
Let me give you a concrete example. Say you prompt an AI assistant to generate some code that manipulates a vector. In languages like Python or JavaScript, the AI might give you code that looks clean but has subtle bugs: off-by-one errors, null pointer dereferences, race conditions in concurrent code.
That code will run. It might even pass your tests. But it'll fail in production when you hit that edge case the AI didn't consider.
In Rust? That code won't compile. The compiler forces the AI—and you—to handle the error cases, manage ownership correctly, and think through the concurrent access patterns. The bugs that would be runtime failures in other languages become compile-time errors in Rust.
## The Android Data Tells the Story
Google's Android team has been living this reality for years now. They adopted Rust specifically for its security benefits, but the productivity gains caught them by surprise.
With Rust changes having a 4x lower rollback rate than C++, you're not just writing safer code—you're saving yourself from the organizational nightmare that rollbacks create. Rollbacks mean emergency meetings, postmortems, new safeguards, and process overhead that slows down everyone.
The 25% reduction in code review time is equally telling. When the compiler has already caught the obvious bugs, reviewers can focus on architecture, business logic, and the things that actually matter. You're not wasting time pointing out null checks and memory leaks that Rust would never have allowed in the first place.
## Why This Matters for AI-Assisted Development
When you're using AI to accelerate development, you need strong feedback loops. You need to know when the AI has made a mistake, and you need to know it immediately—not three weeks later when a customer reports a crash.
Rust gives you those feedback loops through its type system and compiler. It's like having a second AI assistant that specializes in catching the first AI's mistakes.
Here's what this looks like in practice:
**Type-driven development**: Rust's stro
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Why the Structure of AI's Output Matters
Canonical HTML URL: https://troels.im/blog/why-the-structure-of-ai-s-output-matters
The order in which you structure an LLM's output fields can dramatically affect response quality—improving accuracy by 20-30% in some cases—because these models generate text autoregressively, one token at a time from left to right, meaning each token can only be influenced by what came before it, never what comes after. When you ask an LLM for structured output like JSON, putting the decision field before the reasoning field forces the model to commit to an answer prematurely and then generate justification for it, while putting reasoning first allows the model to work through the analysis and then draw a conclusion based on the reasoning it just generated. This isn't just a formatting preference—it's fundamental to how these models think, because the sequential generation process means early tokens shape the probability distribution for later tokens, and models that make premature decisions suffer from "exposure bias" where initial errors cascade through subsequent generation, making the output structure itself a critical part of the reasoning process rather than just a data formatting choice.
Here's something most developers miss when working with LLMs: the order in which you ask for information dramatically affects the quality of responses. Not by a little—we're talking 20-30% accuracy improvements in some cases, just by rearranging JSON fields.
This isn't about prompt engineering tricks or clever workarounds. It's about understanding how these models actually work at a fundamental level.
## How LLM Chat Systems Actually Work
Large language models like GPT-4, Claude, or Llama operate on a deceptively simple principle: they predict the next token, one at a time, from left to right. That's it. The entire multi-billion parameter model boils down to answering "what comes next?" over and over again.
This process is called **autoregressive generation**. The model:
1. Takes your prompt and any conversation history
2. Converts it into tokens (roughly word chunks)
3. Predicts the probability distribution for the next token
4. Samples a token from that distribution
5. Appends it to the sequence
6. Repeats until it generates a stop token
Think of it like writing a sentence where you can only see what you've written so far—you can't go back and change earlier words based on what comes later. Each token is generated based purely on what came before it.
This matters more than you might think.
## A Basic Example: Making Decisions
Let's say you're building a system where an LLM needs to approve or reject user requests. You want structured output so you can parse it reliably. You have two obvious ways to structure this:
### Option A: Decision First
```json
{
"decision": "approve",
"reason": "User has valid credentials and request is within quota"
}
```
### Option B: Reason First
```json
{
"reason": "User has valid credentials and request is within quota",
"decision": "approve"
}
```
Same information, right? Just different ordering. But here's what happens in practice:
With **Option A** (decision first), the model generates:
1. Opens JSON: `{`
2. Key: `"decision"`
3. Colon: `:`
4. **Value: `"approve"`** ← Makes this choice NOW
5. Then fills in reasoning to justify what it already decided
With **Option B** (reason first), the model generates:
1. Opens JSON: `{`
2. Key: `"reason"`
3. Colon: `:`
4. **Value: Thinks through the actual criteria** ← Reasoning happens HERE
5. Then makes decision based on reasoning it just generated
The difference? In Option A, the model commits to an answer before doing the reasoning. In Option B, it does the reasoning first, then decides.
## Why One is Better Than the Other: The Deep Dive
The reason this matters comes down to three fundamental properties of autoregressive models:
### 1. Sequential Dependencies and "Exposure Bias"
When an LLM generates text, each token influences all subsequent tokens. This creates a dependency chain. If the model makes a mistake early, that error can cascade through the rest of the generation—researchers call this "exposure bias."
In the decision-first approach, if the model picks "approve" prematurely (maybe because approvals are more common in training data), it will then generate reasoning that supports that decision—regardless of whether it's correct. The model becomes committed to justifying its initial choice.
In the reason-first approach, the model works through the relevant factors first. When it reaches the decision field, it has already generated the logical analysis. The decision becomes a natural conclusion rather than a premature commitment.
### 2. The Left-to-Right Constraint
Remember: LLMs can only condition on previous tokens, never future ones. This is a fundamental architectural constraint of transformer-based models. They use causal attention masks that literally prevent the model from "peeking ahead."
When you structure output with decision first, you're asking the model to make a decision without the benefit of its own reasoning. It's like asking someone to pick A, B, or C before they've finished reading the question.
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Cursor vs Claude Code: The Complete Developer's Guide for 2025
Canonical HTML URL: https://troels.im/blog/cursor-vs-claude-code-the-complete-developer-s-guide-for-2025
Cursor and Claude Code represent two fundamentally different approaches to AI-assisted coding in 2025, with Cursor offering a familiar IDE experience through its VS Code fork that excels at rapid prototyping and visual debugging, while Claude Code operates as a powerful terminal-based CLI tool that leverages superior reasoning capabilities and 200K token context windows for complex multi-file projects. While both tools cost around $20/month, Claude Code can be up to 4x more expensive for heavy usage due to its token-based consumption model, making Cursor more cost-effective for moderate developers, though experienced programmers often prefer Claude Code's autonomous capabilities and deep codebase understanding for large-scale architectural work. The choice ultimately depends on your workflow preferences: Cursor is ideal for beginners and visual learners transitioning from traditional IDEs, while Claude Code serves power users who value terminal efficiency and advanced reasoning for complex development tasks.
In the rapidly evolving world of AI-powered coding assistants, two titans have emerged as the leading contenders: Cursor and Claude Code. While both leverage advanced AI models to transform how developers write code, they represent fundamentally different philosophies and approaches to AI-assisted development.
This comprehensive comparison will help you understand which tool aligns best with your development workflow, budget, and project requirements based on the latest 2025 features and pricing.
## **TL;DR: Key Takeaways**
- **Claude Code** excels at complex, multi-file projects with superior reasoning capabilities but operates entirely in the terminal
- **Cursor** offers a familiar IDE experience with excellent rapid prototyping features but can struggle with complex, large-scale tasks
- **Pricing**: Both tools start at $20/month, but Claude Code can be significantly more expensive for heavy usage due to token-based consumption
- **Best for beginners**: Cursor's user-friendly interface makes it more accessible
- **Best for professionals**: Claude Code's advanced capabilities and context awareness make it ideal for serious development work
## **Platform Philosophy & Interface**
### **Claude Code: Terminal-First Minimalism**
Claude Code runs in your terminal and works alongside your preferred IDE and development tools without requiring you to change your workflow. Claude Code operates entirely within the terminal, offering a minimalist, text-based environment that appeals to developers comfortable with command-line workflows.
**Key Interface Features:**
- Pure command-line interface
- Works with any IDE or editor
- Minimal visual distractions
- Incremental permission system for earned trust
- Deep integration with shell tools and workflows
### **Cursor: IDE-Centric Visual Experience**
Cursor presents itself as a fully featured AI-augmented IDE, forked from VS Code, offering intuitive code completion, IDE-integrated actions, and smart refactoring within a familiar GUI. Since it's a fork of VS Code, I didn't have to adjust to a new workflow; it supports everything I rely on daily: tabbed file editing, built-in terminal, Git integration, debugging tools, and all the extensions I already use.
**Key Interface Features:**
- Full-featured IDE experience
- Visual diff previews
- Integrated chat panels
- Multi-tab support
- VS Code extension compatibility
- Built-in debugging tools
## **Pricing & Value Analysis (2025)**
### **Claude Code Pricing**
Claude Pro is available for $20 per month (US), with additional Max tiers at $100/month and $200/month.
- **Pro Plan ($20/month)**: Most Pro users can expect 40 to 80 hours of Sonnet 4 through Claude Code within their weekly rate limits
- **Max Plan ($100/month)**: Subscribers to Anthropic's $100-per-month Max plan can expect 140 to 280 hours of Sonnet 4 and 15 to 35 hours of Opus 4
- **Max Plan ($200/month)**: Subscribers to Anthropic's $200-per-month Max plan can expect 240 to 480 hours of Sonnet 4 and 24 to 40 hours of Opus 4
**Recent Changes**: The new rate limits will go into effect August 28 for subscribers to Anthropic's $20-per-month Pro plan, as well as its $100- and $200-per-month Max plans due to computational resource constraints.
### **Cursor Pricing**
The Pro plan offers a serious upgrade in power and flexibility for just $16/month (billed yearly) or $20/month billed monthly.
- **Hobby Plan**: Free tier with 2,000 completions and 50 slow premium requests
- **Pro Plan ($20/month)**: Pro includes at least $20 of Agent model inference at API prices per month with 500 requests per month
- **Business Plan ($40/month)**: Team features with enhanced security and analytics
### **Subscription Plan Comparison**
**Fair Price Comparisons (Annual vs Annual):**
- **Claude Pro**: $17/month (billed annually) vs $20/month (billed monthly)
- **Cursor Pro**: $16/month (billed annually) vs $20/month (billed monthly)
**Verdict**: Both tools offer similar annual pricing, with Cursor s
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Solving the Modern WebAssembly Build Problem with Rust and Trunk
Canonical HTML URL: https://troels.im/blog/solving-the-modern-webassembly-build-problem-with-rust-and-trunk
This blog post addresses a common error encountered when building Rust WebAssembly projects with Trunk, particularly after Rust 1.82.0. The error stems from the introduction of "bulk memory operations" in the Rust compiler, which are not supported by older versions of `wasm-opt`. The post outlines three solutions: updating `wasm-opt` to the latest version (recommended), configuring Trunk to pass the `--enable-bulk-memory` flag to `wasm-opt`, or disabling bulk memory operations in the Rust compiler. It emphasizes the importance of addressing this issue for production builds to avoid build failures and performance impacts, and provides practical advice on checking tool versions and staying updated with the evolving WebAssembly ecosystem.
If you've been working with Rust and WebAssembly recently, you might have encountered this frustrating error:
```
[parse exception: invalid code after misc prefix: 17 (at 0:4964695)]
Fatal: error parsing wasm (try --debug for more info)
```
This error typically appears when using Trunk to build Yew applications or other Rust-to-WebAssembly projects, especially after upgrading to Rust 1.82.0 or newer. The issue occurs during the optimization phase when `wasm-opt` tries to process the WebAssembly binary generated by the Rust compiler.
## What's Happening Behind the Scenes
Starting with Rust 1.82.0, the compiler began generating WebAssembly code that includes "bulk memory operations" by default. This change wasn't broadly announced and caught many developers by surprise.
The bulk memory operations proposal adds instructions like `table.fill`, `memory.copy`, and `memory.fill` to WebAssembly, making certain operations more efficient. However, these instructions require explicit support from optimization tools.
## The Root Cause: Outdated wasm-opt
The core issue is that many developers are using outdated versions of `wasm-opt` (part of the Binaryen toolkit). The version available through `cargo install` hasn't been updated to handle these newer WebAssembly features properly.
When an outdated `wasm-opt` encounters these newer instructions without the proper flags, it fails with cryptic error messages like "invalid code after misc prefix: 17" or "unexpected false: Bulk memory operations require bulk memory."
## Solution Path: Multiple Approaches
### Approach 1: Update wasm-opt (Recommended)
The most straightforward solution is to update your `wasm-opt` tool to the latest version:
**For macOS users:**
```bash
brew install binaryen
# Or update if already installed
brew upgrade binaryen
```
**For Linux users:**
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install binaryen
# Or download directly from the releases page
wget https://github.com/WebAssembly/binaryen/releases/download/version_120/binaryen-version_120-x86_64-linux.tar.gz
tar -xzf binaryen-version_120-x86_64-linux.tar.gz
# Add to your PATH or move binaries to /usr/local/bin
```
**For Windows users:**
```bash
# Using scoop
scoop install binaryen
# Or using chocolatey
choco install binaryen
```
### Approach 2: Configure Trunk to Pass the Right Flags
Once you have the latest version of `wasm-opt`, you need to tell Trunk to pass the appropriate flags to enable bulk memory support.
Update your `index.html` file with the proper `data-trunk` attributes:
```html
```
The crucial part here is `data-wasm-opt-params="--enable-bulk-memory"`, which tells `wasm-opt` to expect and handle bulk memory operations.
### Approach 3: Configure the Rust Compiler (Alternative)
If updating `wasm-opt` isn't an option, you can instruct the Rust compiler not to emit bulk memory operations:
Create or modify `.cargo/config.toml` in your project:
```toml
[target.wasm32-unknown-unknown]
rustflags = ["-C", "target-feature=-bulk-memory"]
```
This forces Rust to generate WebAssembly code that doesn't use bulk memory operations, making it compatible with older versions of `wasm-opt`.
## Why This Matters for Production
This issue isn't just a developer inconvenience—it directly impacts your production builds:
1. **Build Failures**: Without addressing this issue, your CI/CD pipelines will start failing after upgrading Rust.
2. **Performance Impact**: If you disable bulk memory operations to work around the issue, you might be missing out on performance improvements.
3. **Dependencies**: Many Rust libraries are now compiled with these features enabled, so you'll encounter this issue even if your code doesn't directly use these features.
## Common Pitfalls to Avo
[Truncated. Open the canonical HTML URL for the full page.]
### Video: Simple Steps for Crystal-Clear Requirements!
Canonical HTML URL: https://troels.im/v/simple-steps-for-crystal-clear-requirements
Ever struggled with unclear project requirements? This video shows you how a simple, structured prompt can help you gather all the details you need for successful projects!
Watch as we demonstrate a powerful technique for collecting project requirements using an AI assistant. If you want to try this out for you self, go to awesomecodeprompts (it's free, but you need an OpenAI or kluster.ai account): [link coming, but need to a verified account first]
This approach works for any project - from software development to marketing campaigns. Say goodbye to confusion and rework. Try this requirements-gathering method today!
In the next video we will use these requirements to build a calculator, so remember to subscribe!
Ever struggled with unclear project requirements? This video shows you how a simple, structured prompt can help you gather all the details you need for successful projects!
Watch as we demonstrate a powerful technique for collecting project requirements using an AI assistant. If you want to try this out for you self, go to awesomecodeprompts (it's free, but you need an OpenAI or kluster.ai account): [link coming, but need to a verified account first]
This approach works for any project - from software development to marketing campaigns. Say goodbye to confusion and rework. Try this requirements-gathering method today!
In the next video we will use these requirements to build a calculator, so remember to subscribe!
### Article: Debunking AI Myths: How to Leverage AI for Faster, Reliable Coding
Canonical HTML URL: https://troels.im/blog/debunking-ai-myths-how-to-leverage-ai-for-faster-reliable-coding
Feeling the pressure to ship faster while keeping bugs away? You're not alone! This guide cuts through the hype about AI coding tools to show you what actually works.
AI won't take your job—it's more like a junior developer with unlimited energy but needs your guidance. The real power comes when you combine AI's speed with your experience.
We'll explore common myths and show you:
- How AI tools become your coding buddy (not your replacement)
- Why AI can work for complex projects (with the right approach)
- The danger of using AI without understanding the code
Plus, you'll get 10 practical principles for using AI safely, along with clear dos and don'ts that help you ship faster without breaking things.
Level up your coding by learning to use AI the right way—like adding seatbelts and airbags to your development process!
## Introduction
Feeling the pressure? You're not alone. In today's fast-paced development world, we're all racing against the clock to ship features while somehow avoiding those dreaded production bugs. It's like trying to build a house in half the time without forgetting the foundation!
As a mid-senior developer, you know that quality matters—but so do deadlines. Your team is counting on you, stakeholders are waiting, and that nagging voice keeps asking: "Could we move any faster without breaking things?"
This is where AI enters the picture—not as a magic solution, but as your coding buddy that helps you level up. Think of AI as power tools for your development workflow. Just like switching from a hand saw to a power saw doesn't make you less of a carpenter, using AI doesn't make you less of a developer - it makes you more efficient - that is, if you don't cut your hand off in the process.
In this guide, we'll cut through the hype and show you practical ways to use AI to speed up your development cycle without sacrificing the quality your projects deserve. No theoretical fluff - just actionable strategies from someone who's learned the hard way what works and what doesn't in this exciting new space. Let's get you shipping faster with code you can actually trust!
## Understanding AI in Coding
AI coding assistants are really more like having an eager junior developer at your disposal. They're super enthusiastic and have unlimited stamina, but they often go off on tangents because they don't truly understand what you're trying to accomplish. Unlike traditional autocomplete tools, they attempt to understand context and can generate code snippets, but the results require careful review. Think of them as energetic but inexperienced team members who need clear direction and supervision - they're willing to take a shot at repetitive tasks when you're racing to meet a deadline, but don't expect them to understand the nuances of your codebase or intentions without significant guidance.
Today's AI coding landscape is evolving rapidly with tools like GitHub Copilot, Cursor, and ChatGPT becoming increasingly prevalent in development environments. These tools can generate entire functions, suggest optimizations for your Python or TypeScript code, and even help refactor legacy systems - all critical capabilities when you're trying to move fast without breaking things. For tech leads and senior developers under pressure to deliver, these tools represent a significant shift in how we can approach our daily coding challenges.
As a developer facing tight deadlines and quality expectations, AI offers you concrete benefits that directly address your pain points. Imagine cutting your boilerplate coding time in half, automating repetitive tasks like writing tests, and getting intelligent suggestions for debugging those persistent bugs that keep pushing back your release dates.
The beauty of modern AI tools is how seamlessly they can integrate into your existing workflow. You don't need to overhaul your development process or learn an entirely new system. Whether you're working in VS Code with your TypeScript project or using PyCharm for Python development, AI assistants plug directly into your IDE, becoming an invisible team member that's always available when those weekly requirement changes come in. They fit naturally into your code-review cycle, helping you move fast while maintaining the quality standards your team depends on.
Unlike traditional code completion tools that simply match patterns from your current file or project, AI assistants understand context [across your entire codebase and beyond]. Traditional tools might suggest completing a function name you've used before, but AI tools can generate the entire function implementation based on your comments or the surrounding code. When you're under pressure to ship features quickly, this difference is crucial - it's the gap between saving a few keystrokes and saving hours of imple
[Truncated. Open the canonical HTML URL for the full page.]
### Video: Integrating Aichat with Kluster.ai: A Game Changer for Your Terminal
Canonical HTML URL: https://troels.im/v/integrating-aichat-with-kluster-ai-a-game-changer-for-your-terminal
In this tutorial, I'll walk you through setting up AIChat with kluster.ai and DeepSeek R1, allowing you to access powerful AI capabilities directly from your terminal. This integration streamlines your workflow by bringing DeepSeek's capabilities to your command line interface.
In this tutorial, I'll walk you through setting up AIChat with kluster.ai and DeepSeek R1, allowing you to access powerful AI capabilities directly from your terminal. This integration streamlines your workflow by bringing DeepSeek's capabilities to your command line interface.
### Article: Using Traits for Generic Type Constraints in Rust
Canonical HTML URL: https://troels.im/blog/using-traits-for-generic-type-constraints-in-rust
Learn how Rust uses traits to enforce type constraints, particularly useful for TypeScript developers exploring Rust.
Ever wondered how Rust manages to be both safe and fast? One secret lies in its robust type system and the use of traits to enforce type constraints. For TypeScript engineers venturing into the Rust realm, understanding how Rust's generic constraints work is essential to mastering this language.
In the world of Rust, traits are akin to interfaces in TypeScript—a collection of methods that a type must implement. When you're working with generics in Rust, you often need to specify constraints on the types you are working with. For example, if you're implementing a function that needs to display a type's contents, you need assurance that the type can indeed be printed. Enter trait bounds. Trait bounds limit the types that can be used with a generic function or type by specifying that the types must implement certain traits.
Consider this Rust example where we have a function, print, that takes any type T, provided T implements both the Display and Debug traits. The Display trait is somewhat akin to JavaScript's toString method, allowing an object to be beautifully printed. Debug, on the other hand, is used for developer-oriented output, often when you're debugging your code and need more information about an object. These constraints ensure that any type passed to print can be output in a human-readable form, and if necessary, with additional debugging detail.
In another function named process, the constraints are defined using the where clause, exhibiting a cleaner syntax especially as the number of constraints increases. Here, T must satisfy three different traits: Add, Display, and Clone. Additionally, the result of the Add operation (T::Output) must implement the Debug trait. This pattern opens the door to more sophisticated type logic and ensures that operations are safe and meaningful at compile time.
Let's draw a parallel with TypeScript. In TypeScript, you might use extends to define constraints on types in a similar manner. For instance, you might ensure that any object passed to a function has a toString method.
As a TypeScript developer, you'll find similarities in Rust's use of generics and sharp differences in how traits enhance type safety. The strict interface requirements imposed by Rust can feel limiting at first but offer a powerful mechanism to catch errors early in the compiler, enhancing runtime performance and reliability.
```rust
use std::fmt::{Display, Debug};
use std::ops::Add;
fn print(x: T) {
println!("{} ({:?})", x, x);
}
fn process(x: T)
where
T: Add + Display + Clone,
T::Output: Debug,
{
println!("{}", x);
}
#[derive(Debug, Clone)]
struct Point {
x: i32,
y: i32
}
impl Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
let point = Point { x: 1, y: 2 };
print(point);
}
```
The provided Rust code example is a showcase of using trait constraints in practice. To begin, we have two functions, print and process, that highlight the use of trait bounds to enforce functionality on generic types.
In print, T refers to any type that implements both Display and Debug. This function accepts a single parameter and demonstrates a dual-purpose output displaying both a human-readable form (using {}) and a developer-focused form (using {:?}).
Moving on to the process function, we see a demonstration of the where clause. Instead of cramming all trait bounds into the function signature, where allows us to elegantly list constraints. Here, T must accommodate addition (Add), display capability (Display), and cloning (Clone). Moreover, the output of an addition operation must implement Debug. This multifaceted constraint setup ensures the function
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Comparing Rust Traits and TypeScript Interfaces
Canonical HTML URL: https://troels.im/blog/comparing-rust-traits-and-typescript-interfaces
An exploration of how Rust traits differ from TypeScript interfaces, with practical examples for developers transitioning between languages.
For TypeScript aficionados venturing into Rust territory, welcome! Today, we're diving into Rust traits and how they stand up against the TypeScript interface stalwarts you're already familiar with. Sure, they share some familial resemblance, but you'll see Rust's version has a little more to offer.
If you're coming from a world of TypeScript, you know interfaces are there to establish those contracts that enforce the structure of code. Interlacing these contracts with optional properties and strict type checks feels like a safety net. But Rust, being the self-imposing language it is, adds a twist with its traits. Think of them as TypeScript interfaces but with the ability to include functionality—quite the multitasker!
In TypeScript, when you declare an interface, you're outlining what a class should look like. It establishes that our classes should implement specific properties or methods. For example, when you have interface Display { display(): string; }, every class that implements Display must have a display method. It's clear and straightforward (although, as we're reminded, never say such a thing aloud).
Enter Rust's traits, taking this concept a step further. Like interfaces, traits allow you to define a set of methods that a type must implement. But here comes the twist: Rust lets you provide default method implementations! This means when you declare a trait, any method in that trait can have its body defined, which will be used unless the implementing type decides to override it.
Consider the Display trait in our Rust code. Right alongside our mandatory display method, we have a fancy_display, pre-filled with swagger—"★" style, no less. Any struct implementing Display gets this fancy behavior off the bat. And should they want to get even fancier? Just override the default implementation.
This flexibility adds a delightful dimension to building with Rust. It embodies reusability and uniformity while maintaining Rust's rigorous stance on safety and performance. Every time you tame a Rust welcome beast, err, feature, you're integrating robustness that TypeScript can only aspire to without the aid of compilers.
In the grand scheme, Rust's way with traits represents a nuanced expansion on code contracts, marrying structural integrity with inheritable behavior in a safe, consistent manner. It's like the eagle-eyed professor who caught your comma splices; taxing yet transformative.
```rust
trait Display {
fn display(&self) -> String; // Required method
fn fancy_display(&self) -> String { // Optional with default impl
format!("★{}★", self.display())
}
}
struct Person {
name: String
}
impl Display for Person {
fn display(&self) -> String {
self.name.clone()
}
}
fn main() {
let person = Person { name: "John".to_string() };
println!("Display: {}", person.display());
println!("Fancy: {}", person.fancy_display());
}
```
At the heart of our exploration is the Rust code snippet, showcasing the essence of traits. It begins with a trait Display that declares a required method display and an optional method fancy_display. Here, Rust's charm shines through: fancy_display comes equipped with a default behavior, adding stars around the display output. Your structs can adopt this default, much like a pre-approved signature scent, or redefine it to your liking.
The struct Person follows, with a single member, name, a string indicative of how we might envision a similar class within TypeScript. Our friend Person then implements the Display trait, providing its own take on display. Within fn display, we witness the embodiment of the trait's requirement—returning the name as the display value.
In the happy cacophony of the main function, a Person instance springs to life with the name "John". As Rust executes println!, it invokes both display and fancy_display on our person instance, illustrating how traits offer both default and custom functionality wrapped up in neat func
[Truncated. Open the canonical HTML URL for the full page.]
### Video: How to Setup DeepSeek R1 on TypingMind
Canonical HTML URL: https://troels.im/v/how-to-setup-deepseek-r1-on-typingmind
Looking to get started with DeepSeek R1, the powerful EU-hosted AI model? In this quick tutorial, I'll show you how to set up and connect DeepSeek R1 with TypingMind's user-friendly interface in just minutes. The video below walks you through creating your API key, configuring the connection, and testing your setup for seamless AI interactions.
DeepSeek R1 is provided through kluster.ai, a platform founded in 2024 that democratizes access to AI compute power through a decentralized network of GPU providers. This EU-hosted solution offers excellent performance with lower latency and regulatory compliance.
Before you begin, you'll need:
- A kluster.ai account: https://dub.sh/klusterai
- TypingMind access: https://dub.sh/WGMPH3X
Note: While I mention US hosting in the video, the service is actually hosted in the EU.
Looking to get started with DeepSeek R1, the powerful EU-hosted AI model? In this quick tutorial, I'll show you how to set up and connect DeepSeek R1 with TypingMind's user-friendly interface in just minutes. The video below walks you through creating your API key, configuring the connection, and testing your setup for seamless AI interactions.
DeepSeek R1 is provided through kluster.ai, a platform founded in 2024 that democratizes access to AI compute power through a decentralized network of GPU providers. This EU-hosted solution offers excellent performance with lower latency and regulatory compliance.
Before you begin, you'll need:
- A kluster.ai account: https://dub.sh/klusterai
- TypingMind access: https://dub.sh/WGMPH3X
Note: While I mention US hosting in the video, the service is actually hosted in the EU.
### Article: Efficiency of Rust's Slice Type '&str' over Strings
Canonical HTML URL: https://troels.im/blog/efficiency-of-rust's-slice-type-'&str'-over-strings
A comparison of Rust's &str slice type and String type for TypeScript developers transitioning to Rust.
In the world of programming languages, Rust stands out as a savvy, resource-efficient companion that asks for some adaptation from those used to more dynamic languages like TypeScript. Today's exploration takes us into Rust slices, particularly the &str type, and how they stack up in comparison to String. Understanding this will equip TypeScript developers with foresight into Rust's efficiency in handling string-like data.
## Understanding Slices in Rust
As TypeScript engineers exploring Rust, the concept of slices and &str might initially seem daunting, but fear not—at heart, these Rust components are straightforward and efficient. In TypeScript, strings are flexible and easily manipulated, often without concern for underlying performance costs. Rust, on the other hand, emphasizes performance and memory safety, which is why the slice type &str exists to optimize how string data is handled.
A slice in Rust, denoted as &str, offers a view into a section of a string, without a need to copy the entire string. Imagine you've got a vast universe of string data, and you need to pick out specific constellations without replicating the whole cosmos. That's essentially slices for you. This functionality is akin to TypeScript's slice method for strings but with some quintessential Rust nuances.
## Zero-Cost Abstractions
Rust excels with its zero-cost abstractions, and slices are a testament to this principle. They allow developers to create lightweight, non-owning views into data. When you take a non-owning view, you're effectively saving memory and gaining speed since you're not duplicating data. The cost of having these slices is negligible, making Rust highly efficient when working with large datasets.
Consider the following Rust code to see slices in action:
```rust
fn main() {
let s = String::from("hello");
let slice = &s[0..2]; // "he" - creates a view of the first two chars
let full_slice = &s[..]; // View of the whole string
println!("Slice: {}", slice);
println!("Full slice: {}", full_slice);
}
```
This example demonstrates how slices work in Rust with the String "hello". The variable slice holds a reference to the first two characters without owning them. Meanwhile, full_slice points to the entire string. Think of slice as cutting a small slice of pie without taking the whole pie out of the oven. Such efficient access to parts of a string can be crucial when working with large or performance-critical data in Rust.
## Safety First Approach
Despite the similarities to TypeScript, Rust slices impose boundaries due to Rust's safety-first nature. Accessing slices requires explicit bounds, ensuring that you don't step outside the data's actual bounds—a safeguard TypeScript developers might need time to get accustomed to. Nonetheless, this strictness is what helps Rust prevent common bugs like buffer overflows, a more pervasive issue in less strict languages.
```rust
fn main() {
let s = String::from("hello");
let slice = &s[0..2]; // "he" - creates a view of first two chars
let full_slice = &s[..]; // View of whole string
println!("Slice: {}", slice);
println!("Full slice: {}", full_slice);
}
```
## Deep Dive into Rust Slices
Let's delve deeper into understanding the Rust code example. Here, String::from("hello") creates a String object. The slice variable is defined using &s[0..2], where & indicates a reference type—similar to pointers but safer, managed with Rust's borrow checker. The syntax 0..2 is a range denoting the start (inclusive) and end (exclusive) of the slice. As a result, slice will reference the substring "he" without duplicating the characters.
The full_slice demonstrates the creation of a slice covering the entire string via &s[..]. Here, the omitted range defaults to the entire length of the string. This is equivalent to slicing without boundaries, giving access to the complete data.
Rust's slice semantics offer flexibility by referencing string segments witho
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Understanding References and Borrowing in Rust
Canonical HTML URL: https://troels.im/blog/understanding-references-and-borrowing-in-rust
Learn how Rust's references and borrowing system enhances safety and performance, with comparisons to JavaScript and TypeScript.
Rust is a language known for its focus on safety and performance, primarily through its innovative use of concepts like ownership and borrowing. For TypeScript developers intrigued by Rust's approach, understanding references is essential. Let's explore how Rust uses this concept to enhance safety, with comparisons to something you might already be familiar with—JavaScript and TypeScript references.
## Rust References and Safety
Rust references resemble pointers but come with a layer of safety that ensures the developer doesn't shoot themselves in the foot. In Rust, the concept of ownership plays a significant role, and references are a way to interact with data without taking ownership of it. Imagine Rust as a diligent librarian who doesn't want you to walk out with books under your arm, but is happy to let you read them in the library. This approach lets you manage and use data efficiently without the dread of memory leaks or data races that you might encounter in other low-level languages.
## Borrowing in Rust
In Rust, borrowing is the process of creating references to a piece of data, allowing multiple function calls to interact with it while guaranteeing that the data itself is not altered or destroyed unexpectedly. This is akin to using const or referencing an object in TypeScript, where you know your variable won't get overridden out of the blue. By marking your variable as &String in the function declaration, you're telling Rust, "Hey, I'm just looking at this, no funny business."
Consider the following Rust example where a string object text is created in the main function and then borrowed by the print function using a reference:
```rust
fn print(s: &String) {
println!("{}", s);
}
fn main() {
let text = String::from("hello");
print(&text); // Borrow `text`
println!("{}", text); // `text` is still usable here
}
```
In this piece of code, print(&text); doesn't transfer ownership. This design ensures you can print text in the print function and later in the main function without any issues. Rust's borrow checker ensures that even though you're sharing text, it remains accessible post-borrow, but no additional modifications occur that could disrupt program integrity.
## Compared to TypeScript
The story is different in TypeScript. You can pass references directly since JavaScript manages memory automatically, and variables can be reassigned or modified without compiler intervention. In situations where Rust's complexity feels burdensome, you will appreciate the guarantees and protections when handling complex data structures and systems programming.
## Eliminating Data Races
Rust references eliminate data races by making immutable and mutable references exclusive. Mutable references (&mut) are like taking a book out for a read with a pencil – Rust makes sure you are the only one doing that, maintaining data integrity.
```rust
fn print(s: &String) {
println!("{}", s);
}
fn main() {
let text = String::from("hello");
print(&text); // Borrow text temporarily
println!("{}", text); // Can still use text here
}
```
## How References Work in Rust
In the provided Rust code, we define a function print that takes a reference to a String as an argument, s: &String. This reference does not gain ownership of the data but merely borrows it, allowing s to be used within print. The data referenced by s still resides in main, and the ownership rules enforced by Rust guarantee that once the function print is done, the text can be safely accessed again. This guarantees both safety and efficiency since no new allocation, deallocation, or movement of the value is taking place; instead, you're just pointing and shouting, "Look at that!" from a safe distance without touching.
Here's what's fantastic about this: you can't modify text through s because it is not a mutable reference. This setup prevents accidental data modification, a common source of bugs in programming. Since print doesn't poss
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Understanding the Ownership System in Rust
Canonical HTML URL: https://troels.im/blog/understanding-the-ownership-system-in-rust
A detailed explanation of Rust's ownership model and how it differs from TypeScript's approach to memory management.
Rust's ownership model is a whimsical journey into the heart of memory management that acts as both a guardian and a disciplinarian. Just like a overprotective parent, it ensures that no memory is left unaccounted for, teaching us a thing or two about responsibility in the process. For TypeScript developers, this concept might seem a bit daunting at first, as it goes beyond the familiar realm of automatic garbage collection. So, gear up for a tour into Rust's world, where we're about to discover why memory safety is always in vogue!
In the land of Rust, ownership is the passport that governs how and where memory is allocated and de-allocated. Unlike TypeScript, where variables are routinely passed around like candy on Halloween, Rust demands meticulous attention to who holds the reins—or bytes, to be more precise.
In Rust, each value has a single owner. Consider it the King of String gathered in a coven fraught with precise order. When you create a string with let a = String::from("hello");, you essentially crown 'a' as the ruler. If you declare let b = a;, the crown gets passed to 'b', declaring 'b' the new owner. During this exchange, the former king ('a', in this case) is deposed and can no longer access the resources it once held. This transfer of power—or ownership—implies that when we attempt to use 'a' after transfer, it results in a compile-time error because the subjects no longer obey 'a'.
Here's where it gets interesting for those familiar with TypeScript. In TypeScript, the assignment let b = a; simply copies the value of 'a' to 'b'. Both variables can coexist in happy harmony, accessing the string freely without battles for supremacy. Rust's rules, however, are foundational for its much-vaunted memory safety. By ensuring that there is only one owner at a time, Rust deftly guards against null pointer dereferences and double frees that can plague other systems.
Ownership in Rust also interplays with the borrowing mechanism, which allows references to data without taking ownership. Instead of passing the crown, it's like giving a library card; you can read the book, but you can't take it home. These borrowing mechanisms are subject to strict rules because Rust likes to keep everything in tip-top shape. Borrowing can be mutable or immutable, and juggling these can at times remind one of a circus act—precision is crucial, and sometimes you might drop a pointer!
This combination of ownership and borrowing is an indispensable part of what makes Rust unique: ensuring memory safety without sacrificing performance. Rust alleviates memory leaks and undefined behaviors by being a diligent overseer who never tires of counting lifetimes and ensuring that everything is in its place, like an ever-vigilant janitor.
```rust
fn main() {
let a = String::from("hello");
// Ownership moves to b
let b = a;
// OK
println!("{}", b);
// Fails because a was moved
// println!("{}", a);
}
```
In our Rust code example, we see let a = String::from("hello");, where a string is created with 'a' as its owner. The statement let b = a; transfers ownership from 'a' to 'b', and 'a' is no longer valid as an owner. That's why println!("{}", a); is commented out, as it would result in a compilation error—trying to access something 'a' no longer has any claim to feels a bit like rifling through your ex's leftover drawers. Not advisable.
Contrast this with the TypeScript code, where let a = "hello"; declares a variable 'a' containing the string "hello". Similarly, let b = a; assigns the value of 'a' to 'b', but unlike Rust, TypeScript happily allows both 'a' and 'b' to access and reference the string without any concerns of ownership. This ease, though, can lead to less control over how memory is managed and may result in circular references and memory leaks.
Rust's ownership model, though more demanding with its rules and checks, achieves safer and more efficient memory management. Like an accountability partner, Rust
[Truncated. Open the canonical HTML URL for the full page.]
### Article: The Power of Pattern Matching in Rust
Canonical HTML URL: https://troels.im/blog/the-power-of-pattern-matching-in-rust
Discover how Rust's pattern matching makes handling different data states more robust compared to TypeScript's switch statements.
Pattern matching is a feature in Rust that effortlessly converts scenarios into manageable steps, offering a structured way to handle different options or states of your variables. For TypeScript folks, think of it as a sophisticated version of the switch statement, amped up with safety checks and more expressions under the hood.
## From TypeScript to Rust: A New Paradigm
Picture this: You are entering the realm of Rust, where pattern matching is akin to discovering an advanced form of the switch case you are familiar with in TypeScript. Rust, in its thoughtful design, provides features to make handling different possible states or variations of data more straightforward and less error-prone. In TypeScript, you might be used to using switch statements to execute different branches based on the value encountered. Rust elevates this concept with match - a powerful control flow construct that provides pattern matching capabilities, making your code not only expressive but also safe.
In pattern matching with Rust, you encounter options such as Some and None, particularly when dealing with Rust's Option type, which is their approach to dealing explicitly with nullable values. TypeScript engineers are familiar with null and undefined, which can sneak into your values, sometimes causing headaches. However, in Rust, optionality is explicit and can't accidentally be ignored.
## Handling Optional Values
Consider a case where you are handling a number that might exist or might be absent—a classic scenario is accessing a database record. Instead of diving into null checks, the Rust way is to leverage pattern matching. The code snippet below illustrates how Rust handles such situations:
```rust
fn main() {
let value: Option = Some(42);
let empty: Option = None;
match value {
Some(x) => println!("Got {}", x),
None => println!("Nothing")
}
match empty {
Some(x) => println!("Got {}", x),
None => println!("Nothing")
}
}
```
The Option type explicitly handles the present or absent states of a value with Some holding some data and None representing the absence of a value. The match statement comments on what should happen in either case. In contrast, here's how you might solve a similar problem in TypeScript, using a switch case to differentiate between null and filled values:
```typescript
switch (value) {
case null:
console.log("Nothing");
break;
default:
console.log(`Got ${value}`);
}
```
In TypeScript, you juggle between null and a default case check, which while flexible, operates under less strict safety rules than Rust.
## Exhaustive Pattern Matching
Rust's match goes beyond the basic functionality by enforcing exhaustive checks, meaning every possible case must be handled. This guards your code from scenarios you could overlook otherwise, leaving less room for runtime surprises. Using Some explicitly handles the positive case, while None acknowledges absence, both contributing to cleaner, more robust code.
```rust
fn main() {
let value: Option = Some(42);
let empty: Option = None;
match value {
Some(x) => println!("Got {}", x),
None => println!("Nothing")
}
match empty {
Some(x) => println!("Got {}", x),
None => println!("Nothing")
}
}
```
The Rust code kicks off by defining an Option type which can either contain a value (using Some) or not (using None). In our example, let value: Option = Some(42); initiates with a specific integer, 42, while let empty: Option = None; deliberately contains no value, setting the stage to explore both scenarios.
The crux of the operation lies in match, which evaluates both value and empty. Consider the line match value. If the value holds numbers (i.e., it is Some), Rust executes the branch Some(x) => println!("Got {}", x), utilizing the placeholder x to represent and display that number. However, if value stands em
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Introducing Option, Rust's Solution for Null/Undefined in TypeScript
Canonical HTML URL: https://troels.im/blog/introducing-option,-rust's-solution-for-null/undefined-in-typescript
Explore how Rust's Option enum provides a safer alternative to handling null and undefined values compared to TypeScript's approach.
TypeScript developers, have you ever wished for a way to handle null and undefined values with the precision of a Swiss watch? Enter Rust's Option. This powerful enum ensures that every possibility is accounted for, eliminating pitfalls linked to unexpected null or undefined values. It's like a safety net woven into the language itself, helping you write more robust and error-free code.
## TypeScript vs. Rust: Handling Optional Values
In the world of TypeScript, we're accustomed to dealing with optional types using the ? operator. Example: foo?: string. This is handy, yet it offers a bit too much freedom—freedom that can lead to those dreaded runtime errors. Rust, on the other hand, employs a more disciplined approach with its Option enum. By embracing Option, Rust obligates us to make a conscious choice: is this value present or absent? Think of it as a contract between you and the compiler—a ceremonious handshake where both parties agree to handle null-like cases explicitly.
The Option enum has two possible "variants": Some(value), indicating a value is present, and None, indicating the absence of a value. It acts as a sentinel against unintended null errors. Here's where Rust's clout shines: It requires us to explicitly unwrap these options. No assumed defaults, no unhandled exceptions. This unwrapping typically happens in a match expression, enabling us to handle both Some and None distinctly.
## Option in Action
Consider the Rust code snippet below:
```rust
fn main() {
let name: Option = Some("John".to_string());
let empty: Option = None;
match name {
Some(n) => println!("Name is {}", n),
None => println!("No name provided")
}
match empty {
Some(n) => println!("Name is {}", n),
None => println!("No name provided")
}
}
```
In this slice of Rust joy, we define name with Some("John"), whereas empty commends None. The match pattern not only reduces risk but also delivers clear, concise pathways for all scenarios — a lesson in foresight any TypeScript coder can appreciate.
But let's not overlook the common rebuttal: "Why not simply use null handling or default values?" The strength of Rust's enforcement is its guarantee. It prompts the developer to consider both outcomes, thereby ingraining a habit of foresight and reducing the frequency of uncaught errors. Rust achieves safety and efficiency by demanding that we do our due diligence.
## What This Means for TypeScript Developers
So, what does this mean for a TypeScript developer eager to dive into Rust? Consider Rust's Option your trusty magnifying glass—it forces you to explore every branch of possibility, hence, writing code that's less error-prone and more robust. While it might seem a touch vexing initially, the long-term benefits in terms of stability and predictability make it a worthwhile investment.
```rust
fn main() {
let name: Option = Some("John".to_string());
let empty: Option = None;
match name {
Some(n) => println!("Name is {}", n),
None => println!("No name provided")
}
match empty {
Some(n) => println!("Name is {}", n),
None => println!("No name provided")
}
}
```
In the example provided, we see two uses of the Option. Firstly, name is initialized with Some("John".to_string()), where Some is a variant of the Option enum representing a value that exists, and to_string() converts the string literal John into a String type. Secondly, empty is declared as None, another variant indicating the absence of a value.
The match expressions then check these options. For name, if the value is present (Some(n)), it safely unwraps n and prints it. Alternatively, if the option is None, a specific message is printed—again demonstrating Rust's demand for clarity and thoroughness. The same logic follows for empty. Such schemas enforce the programmer to think about both Some and None cases explicitly
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Comparing Rust Enums and TypeScript Unions
Canonical HTML URL: https://troels.im/blog/comparing-rust-enums-and-typescript-unions
A detailed comparison between Rust's powerful enum types and TypeScript's union types, with practical examples.
Rust is like that amazing food truck everyone talks about; it might seem a bit daunting at first, but once you try it, you can't get enough! Enums in Rust borrow a familiar flavor from TypeScript unions but with an added zing. They don't just store different types—they carry data, making them versatile little containers for your program's needs.
Rust enums can be a delightful shift for TypeScript developers looking to expand their repertoire. Picture this: TypeScript's union types are akin to those trusty Legos that let you build all kinds of neat structures. But then there's Rust enums, which are like a deluxe Lego set, including the mini-figures and specialized pieces that make your creations stand out. Enums can represent different states and hold data, much like their union type cousins, but with added complexity and power.
For instance, let's take an example involving error handling, a common scenario developers face. In TypeScript, you might define a Result type to manage operations that can either succeed or fail using unions. Similarly, Rust provides a more structured approach with its enums. Consider the Rust code snippet below:
```rust
#[derive(Debug)]
enum Result {
Ok(T),
Err(E)
}
fn divide(x: i32, y: i32) -> Result {
if y == 0 {
Result::::Err("Cannot divide by zero".to_string())
} else {
Result::::Ok(x / y)
}
}
fn main() {
let result1 = divide(10, 2);
let result2 = divide(10, 0);
println!("{:?}", result1);
println!("{:?}", result2);
}
```
Here we define our Result enum, capable of storing both success (Ok) and error (Err) states, complete with data. You can think of this enum as a supercharged union that doesn't just tell you what happened (success or failure) but carries vital information about the result or the error message.
When you run this snippet, you'll receive output indicating whether an operation succeeded or failed and, if so, what the error was. This structured approach not only clarifies your code but it also increases maintainability. No fiddling around with optional fields or additional error-handling code—this enum does it all. The language prison wardens at Rust have a motto: safety first! Or in simpler terms, no loose ends to trip you up.
If we pivot to TypeScript, a similar construct looks like this:
```typescript
type Result = { type: 'ok', value: T } | { type: 'error', error: E };
```
While this is mighty handy and aligns with TypeScript's flexible style, our Rust counterpart takes it a step further, ensuring type safety and concise memory management. This is not to say you have to discard those TypeScript-y ways. Instead, consider it a friendly nudge to try something that's both familiar and refreshingly structured!
Remember, Rust does not favor exceptions in the traditional sense. Instead, it uses enums like Result to give you more control over handling successes and failures explicitly. So, you can tackle errors upfront instead of awkwardly tripping over them later in your code execution.
```rust
#[derive(Debug)]
enum Result {
Ok(T),
Err(E)
}
fn divide(x: i32, y: i32) -> Result {
if y == 0 {
Result::::Err("Cannot divide by zero".to_string())
} else {
Result::::Ok(x / y)
}
}
fn main() {
let result1 = divide(10, 2);
let result2 = divide(10, 0);
println!("{:?}", result1);
println!("{:?}", result2);
}
```
In our Rust code example, we've defined an enum to mimic the well-known Result type. This enum has two variants: Ok for successful operations and Err for failures. Enums in Rust offer you the ability to encapsulate data along with the type, acting much like a package that contains all relevant information together. They've been designed as a comprehensive toolkit to help manage your logical paths and error conditions without leaving issues dangli
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Understanding Immutability in Rust
Canonical HTML URL: https://troels.im/blog/understanding-immutability-in-rust
A deep dive into Rust's default immutability and how it differs from TypeScript's approach to variable mutability.
Rust, the ever-watchful guardian of your code, has a default nature that might surprise newcomers: immutability. While most languages let variables change on a whim, in Rust, you need to declare your intentions openly. This approach not only encourages safe programming practices but nudges you toward efficient code design.
Rust's default immutability is akin to a stern schoolteacher who insists you ask permission before making changes. At first glance, this may feel like unnecessary bureaucracy, especially for TypeScript developers who are accustomed to declaring variables without a second thought. TypeScript allows reassignment of variable values freely, often leading to more mutable states. In contrast, Rust's immutability is built on the foundational principle of preventing data races and ensuring thread safety, critical aspects in concurrent programming scenarios.
When you declare a variable in Rust without any modifiers, it's like casting it in iron—strong and unyielding. For example, the declaration let count = 0; would lock count as an immutable variable. Trying to change its value would result in a swift error from the compiler, akin to a disapproving glance. But Rust is not unreasonable; it provides a way to bend this rule: the mut keyword. By declaring let mut count = 0;, you're essentially telling Rust, "I might want to change this later, and I promise to do so responsibly."
This structured immutability serves a larger purpose. It guards against unintended side-effects and race conditions, which notoriously lead to bugs in multi-threaded applications. By putting a spotlight on mutable state, Rust forces developers to be explicit about their intentions, naturally encouraging more predictable and reliable code. This is a stark contrast to TypeScript, where variables are mutable by default for flexibility and ease—it's a trade-off between simplicity and safety.
Moreover, Rust's approach drastically reduces the chances of subtle bugs that could stem from shared mutable states, a common headache in other programming languages. With immutable variables, functional programming techniques become more approachable, leading to cleaner and more maintainable code structures. It's like building a safety net under your acrobatic programming feats, ensuring that even as things get complex, the risk of catastrophic failure is minimized.
While this paradigm shift might require some adjustment for TypeScript engineers, it is a valuable lesson in managing program state efficiently and safely. Over time, you might find yourself appreciating this diligence, as your code becomes inherently free of certain classes of concurrency errors. Rust gently enforces the notion that changing something should be a deliberate choice, not an afterthought. It's an assurance and a philosophy that creeps into your subconscious, encouraging meticulousness and precision in engineering robust software.
```rust
fn main() {
let mut count = 0;
count += 1;
println!("Count: {}", count);
}
```
The Rust code example is a straightforward illustration of Rust's immutability in action. The snippet begins by declaring let mut count = 0;, where mut is the hero of our story, permitting count to be changed over the course of the program. Without mut, attempting to modify count results in a compile-time error. This declaration introduces a pivotal element of Rust's syntax and a core principle of its safety guarantees: explicit mutability.
Once count is declared as mutable, subsequent operations, such as count += 1;, execute without fuss. This line indicates that count is incremented by one. Finally, println!("Count: {}", count); outputs the value, demonstrating not only variable mutation but Rust's commitment to syntax that is both expressive and secure.
The restriction of immutability is not just a leash—it encourages thoughtful consideration of code design. Each mutable variable is a declaration of intent, reinforcing the idea of a well-defined and
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Exploring Rust's Advanced Destructuring with Pattern Matching
Canonical HTML URL: https://troels.im/blog/exploring-rust's-advanced-destructuring-with-pattern-matching
Learn how to transition from TypeScript to Rust by mastering pattern matching and destructuring techniques.
Kick-off your year with a leap from TypeScript to Rust! Transitioning between these languages can be as smooth as butter if you appreciate their differences and strengths. Today, we're diving into Rust's let declaration, which powers up destructuring using pattern matching—a feature that feels like OSINT for code nests!
## Pattern Matching in Rust vs TypeScript
Pattern matching in Rust is not only a powerful tool but also a joyful companion for functional programmers switching from TypeScript. Unlike TypeScript's use of destructuring, Rust leverages its let bindings to achieve advanced destructuring by matching data structures against sold-out tickets of patterns, all while maintaining a strong safety net. Let's explore this through an entertaining example where we use a struct to represent a person and their address and unveil how Rust's pattern matching swoops in to make your life easier, one nested value at a time.
In our Rust example, we have a Person struct that contains a name, an address, and a collection of phone numbers—all packed into vectors and nested structs. Unlike TypeScript, where destructuring utilizes object syntax, Rust throws its own twist using the let binding to destructure these data types. Here, Rust's pattern matching allows you to clothe potential spiders in your code (read: nested values) with elegance.
## Destructuring Nested Structures
Consider our Person struct within our Rust example. It's like a double-decker bus packed with information—a name, where they live (their address), and their phone numbers. Instead of unpacking this with tedious getters, Rust enables you through concise and declarative pattern matching syntax:
```rust
let Person {
name,
address: Address { city, country },
phones
} = person;
```
Voila! You've just effortlessly zeroed in on each parameter in that Person snug like a charm. In Rust, because we deal with strict typing (read: no shortcuts for cowboy coding), destructuring becomes your friendly alley cat—nimble and pesky at catching every unturned stone.
## TypeScript Comparison
Now, for TypeScript aficionados, let's draw a parallel. In TypeScript, destructuring is commonly achieved with a syntax closer to JavaScript, only with extra mixed toppings of optional typing. The match here is striking, as you may already be familiar with the concept:
```typescript
const {
name,
address: { city, country },
phones: [primary, secondary, ...rest]
} = person;
```
Both patterns achieve similar outcomes—values from objects or structs are extracted directly into variables, saving time and reducing errors associated with accessors. Alongside its structured and safe-by-default system, Rust promotes a habit of practicing diligent code manners, preventing those rookie mistakes—not just through fancy syntax, but by taking care of you and your code as if they were cuddly kittens in need of attention.
Match those patterns, plug in those values, and shower your code with the clarity it deserved! Understanding Rust's pattern matching and destructuring features like a pro will undoubtedly make you a wiser and more technically adept coder, furthering your multi-language prowess.
## Complete Example
```rust
#[derive(Debug)]
struct Address {
city: String,
country: String
}
#[derive(Debug)]
struct Person {
name: String,
address: Address,
phones: Vec
}
fn main() {
let person = Person {
name: "John".to_string(),
address: Address {
city: "London".to_string(),
country: "UK".to_string()
},
phones: vec!["123".to_string(), "456".to_string()]
};
let Person {
name,
address: Address { city, country },
phones
} = person;
println!("Name: {}, City: {}, Country: {}", name, city, country);
println!("Phones: {:?}", phones);
}
```
## Understanding the Power of Pattern Matching
In our Rust code example, we demonstrate the power of destructuring wit
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Meet LogoM8: The AI-Powered Brand Identity Generator for Modern Entrepreneurs
Canonical HTML URL: https://troels.im/blog/meet-logom8:-the-ai-powered-brand-identity-generator-for-modern-entrepreneurs
Introducing LogoM8, an affordable AI-powered platform that helps entrepreneurs create unique brand identities at just $0.05 per proposal.
Hey there! I'm excited to share a side project I've been working on. If you're trying to bring your business idea to life but stuck on creating that perfect brand identity, I think I can help. I built LogoM8, an AI-powered platform that I believe could change how entrepreneurs and small businesses create their brands.
## Why Your Brand Identity Matters
Your logo is often the first impression potential customers have of your business. It's not just a design element - it's the visual foundation of your brand story. I know firsthand how frustrating professional logo design can be:
- Professional designers charge thousands of dollars for just 5-10 initial concepts
- Limited iterations (usually 5-10) even at premium price points
- Stock-based tools like Looka and Canva offer no uniqueness guarantees
- No certainty of icon ownership with template-based solutions
## How LogoM8 Transforms the Branding Process
I built LogoM8 to make brand creation easy, affordable and fast for everyone. My goal was to eliminate the traditional barriers of logo design, so you can:
- Generate unlimited professional-quality logo proposals
- Pay just $0.05 per proposal powered by OpenAI
- Own your unique design completely
- Launch your brand faster than ever before
## The LogoM8 Advantage
What I think makes LogoM8 special is how it combines AI efficiency with human creativity. I've designed the platform to understand design principles and some basic color theory to deliver logos that truly represent your brand vision at an extremely affordable price.
Key Features:
- Intuitive AI-powered design engine
- Unlimited unique design proposals
- High-resolution files in all necessary formats
- Pay-per-proposal pricing at just $0.05
## Getting Started with LogoM8
I've made creating your professional brand identity super simple:
1. Enter your business name and brand details (tagline, brand description)
2. Ask LogoM8 to generate unique logo proposals
3. LogoM8 will create designs based on your input and what it knows about your brand
4. It will deliver the final output as an SVG
## Launch Your Brand Today
Don't let logo design hold back your business launch. I built LogoM8 so you can create a professional brand identity in minutes, not months.
Want to check it out? Visit LogoM8.com to start generating unique logo proposals today at just $0.05 each. I'd love to see what you create!
### Article: Change the World or Change Yourself?
Canonical HTML URL: https://troels.im/blog/change-the-world-or-change-yourself?
Explore how personal transformation can have a more profound impact than trying to change the world around you.
In the grand scheme of things, the notion of changing the world can seem both daunting and appealing. Many of us start with grand ideas of making a significant impact, only to realize that the world is a complex, resistant entity. But what if the true key to changing the world starts with changing ourselves? This concept, deeply explored in works like "Rich Dad, Poor Dad," suggests a paradigm shift that deserves more than a brief mention.
## Why Trying to Change the World Is Insanity
It's not that wanting to make the world a better place is insane; the real insanity lies in believing that the world must change while we remain the same. We often fall into the trap of thinking that the problems we face are external and that others need to change for our lives to improve. This mindset is flawed. Reflecting on our actions and improving ourselves can have a ripple effect that subtly but profoundly influences the world around us.
Think about the last time someone tried to change you. Did you comply happily, or did you feel resistance and agitation? Most people don't want to be told how to live their lives. However, by working on ourselves and becoming better versions of who we are, we can inspire others to change. This influence is more powerful and long-lasting than any forced change.
## You Can Change Yourself
Changing yourself starts with working on your beliefs. One poor belief can prevent you from ever learning or improving. For instance, believing "I cannot learn anything new" will halt any progress before it starts. Mindfulness is a crucial tool here. By observing your thoughts, you can identify and reframe poor beliefs, replacing them with more constructive ones.
Actively seeking out information through reading and education is another powerful strategy. Books, like "Rich Dad, Poor Dad," provide insights that can shift our perspectives and encourage personal growth. In a previous blog post, we discussed changing beliefs around money, emphasizing how our realities are shaped by what we choose to believe.
## Personal Transformation: A Lifelong Project
I used to believe I couldn't run a blog because I didn't like writing. But by challenging that belief and using tools like Wonop, I've proven myself wrong. Similarly, I once thought charging high rates for my services was wrong. It wasn't until I read the right books that I realized my worth and began charging rates that reflect the value I provide. These changes didn't happen overnight; they were the result of persistent effort and a willingness to challenge my own beliefs.
## Influencing Others Through Self-Improvement
One of my favorite ways to influence others is by talking them up. There are a number of reasons why this is important. From a selfish perspective, a cool person only hangs out with other cool people. Whether in your professional or personal life, surrounding yourself with cool people and letting others know you do enhances your own image. Besides, it makes people happy and encourages them to do their best work. If they work for you, their happiness and productivity directly benefit you.
## Overcoming Challenges in the Journey of Self-Improvement
The journey of self-improvement isn't without its challenges. In 2020, my father passed away, and three months later, my company, which had zero revenue at the time, ended up in a legal dispute with a former client. The accusation and abuse I faced made it difficult to maintain positive beliefs. During this tough time, I focused on small, positive steps to turn things around and build the beliefs I wanted.
Despite the odds, I managed to grow my business from zero revenue to six figures with a 95% profit margin in a couple of years. This was achieved through consistent, incremental positive changes and a belief in my ability to succeed. Additionally, I used this challenging period to improve my physical health and quit drinking alcohol, which I haven't touched for over 18 months.
## Conclusion
Changing your
[Truncated. Open the canonical HTML URL for the full page.]
### Article: Introducing Kluster.ai: Decentralizing AI Compute for Large Language Models
Canonical HTML URL: https://troels.im/blog/introducing-kluster.ai:-decentralizing-ai-compute-for-large-language-models
How Kluster.ai is building a decentralized network for AI compute, connecting hardware providers with consumers through an OpenAI-compatible API.
On May 2, 2024, Kluster.ai was born out of a clear necessity: to address the unmet demand for large language models (LLMs). Instead of creating another proprietary system, Kluster.ai is building a decentralized network where hardware providers and consumers can meet. Think of it as the Ethereum of AI, where providers run nodes and get paid for every inference they execute, and consumers can easily tap into this network via an OpenAI-compatible API.
## The Birthplace: Moonsong Labs
Kluster.ai's journey began at Moonsong Labs, a hub for innovative ideas at the intersection of Web3 and AI. Early investigations into the potential of these technologies led to a conviction that Kluster.ai could fill a critical market gap. Moonsong Labs, specializing in infrastructure, led the initial ideation and technical exploration including assembling the founding team. Their support continues as Kluster.ai starts to carve its niche in the AI landscape.
## Leadership That Inspires
Leading the charge is Julio Viera, a seasoned entrepreneur whose vision and strategic acumen drive Kluster.ai forward. Julio's extensive background in tech startups equips him with the skills to navigate the complexities of this ambitious project. His knack for identifying market needs and steering companies toward success is invaluable as Kluster.ai works to establish its product-market fit.
## Technical Brilliance
At the heart of Kluster.ai's technical strategy is Michael Sigamani, whose deep expertise in data science shapes the platform's innovative approach. Michael's experience ensures that the technical challenges of decentralization—like node honesty, incentive compatibility, and robust architecture—are met with leading-edge solutions. His leadership ensures that Kluster.ai not only works but excels in delivering reliable AI compute services.
## My Role
My involvement with Kluster.ai began in the late stages of its incubation at Moonsong Labs. As a consultant through my company, Wonop, I contributed to the foundational work of Kluster.ai. Recognizing the synergy, it was decided that Wonop would co-own parts of Kluster.ai, helping to build out the company's infrastructure and strategy moving forward. Wonop and I bring extensive blockchain knowledge, having previously built out an entire blockchain for AI while at Fetch.AI, further strengthening Kluster.ai's capabilities.
## Looking Ahead: Challenges and Solutions
Our immediate focus is on building the Minimum Viable Product (MVP). We're making good progress, but, like any startup, we must judiciously manage our time and resources. We're actively testing hypotheses around decentralization, addressing technical issues such as node honesty and incentive compatibility. At the same time, Julio's expertise is crucial in swiftly finding our product-market fit, while Michael and I concentrate on the technical foundations.
Kluster.ai is poised to revolutionize the AI compute landscape by making high-powered inference accessible and affordable through decentralization. With a strong team, visionary leadership, and strategic partnerships, we are going to kick ass in the years to come!
### Article: Transforming My Financial Reality by Reframing My Money Beliefs
Canonical HTML URL: https://troels.im/blog/transforming-my-financial-reality-by-reframing-my-money-beliefs
Discover how changing your beliefs about money can transform your financial reality and attract abundance.
I've come to realize that money beliefs shape our financial reality. Whether we realize it or not, our subconscious thoughts about money influence our earning potential, spending habits, and overall financial health. In this blog post, I want to share the profound impact of money beliefs and how reframing these beliefs can attract more financial abundance based on my own experiences.
## Feeling Worthy of Receiving Money
Feeling worthy of receiving money is crucial to financial success. If you don't believe you deserve it, you may subconsciously undermine yourself. For example, when negotiating for a job, if you feel your worth is less than what you ask for, you might inadvertently lower your expectations. If I want a $50k job but believe I'm only worth $25k, I might say, "I know it's expensive, but I'm looking to get $50k." This gives recruiters the chance to reinforce my lower self-worth.
## Importance of Working with Your Beliefs
Identifying and shifting your money beliefs is essential. Here's how I approach it:
1. Pay attention to your thoughts about money. I ask myself if these beliefs strengthen my relationship with money.
2. Reframe negative beliefs. I transform them into positive, empowering statements.
For example, I used to think earning $100k annually was great, but it limited my worth to that amount. I started saying, "They are lucky I'm willing to work for such a low salary." Over time, this became my truth, and I felt worth much more.
## Being a High-Value Person
Being a high-value person means giving more value than you receive. The value you give is typically not money. I provide free advice without expecting anything in return, which makes me feel worthy of receiving more. And I also provide free services without expecting anything back. I regularly donate larger amounts. If I ever feel low in worth, I ask myself how I can add more value. For instance, when I felt my consultancy services weren't worth the fee a few years back, I improved my services using AI, enhancing value without lowering prices.
## Important Beliefs to Establish
Here are some foundational beliefs I've adopted:
- The universe is working for me.
- The universe always gives me something better than expected.
- I am great with money and an awesome entrepreneur.
- I love marketing.
- People love paying me for my services!
- My business will grow to $50k/month this year.
- I'm paying off $150k of others' debt this year, because I am awesome.
These beliefs reframe and reinforce crucial aspects for success. They may look different for you, so personalize them to fit your goals and aspirations.
## Overcoming Common Challenges
Falling into old habits is a major challenge. For instance, my company paid nearly $500,000 in legal fees, which should have been covered by the defendant, who instead dodged the payment. Rather than playing the victim, I reframe the situation: I am awesome because my company can pay off $350,000 of someone else's debt. Who the fuck can do that? Yeah, I can! I still need to pay the last $150,000, so what? Reframing empowers me and aligns my mindset with my financial goals.
## Cultivating a Money-Positive Mindset
Maintaining a money-positive mindset requires daily practices:
- Repeat your beliefs out loud.
- Journal about them.
- Visualize already being what you want to believe.
Quite quickly, this changes your reality. It's that simple.
## Conclusion
Transforming your money beliefs is a powerful step toward financial abundance. By feeling worthy, adding value, reframing negative beliefs, and maintaining daily positive practices, you can shift your financial reality. Start today by identifying your money beliefs and reshaping them into empowering truths. The universe is waiting to reward you!
### Article: The Inaugural Issue of My Weekly Newsletter!
Canonical HTML URL: https://troels.im/blog/the-inaugural-issue-of-my-weekly-newsletter!
Join me on a weekly exploration of indiehacker life, Wonop Studio developments, and insights on entrepreneurship, AI, and blockchain.
Hello everyone,
I'm Troels, and this is the very first edition of what will be a weekly exploration of my life as an indiehacker and entrepreneur, the exciting developments at Wonop Studio, and the wider world of entrepreneurship, AI, and blockchain. I'm thrilled to have you join me on this journey, and I hope my experiences can offer valuable insights, inspire ideas, and foster a sense of community among us.
## Why I Started This Newsletter
My inspiration comes from many indie hackers and solopreneurs whose newsletters have been a beacon of knowledge and motivation for me. Their willingness to share the good, the bad, and the transformative has been invaluable. In that spirit, I wanted to create a space where I could give back, sharing not just successes but the real, raw process of building and innovating.
## Inside Scoop on Wonop Studio
Each week, I'll bring you updates from Wonop Studio, the birthplace of small yet impactful products under the guiding principle that every feature should be a standalone product. We are currently working on "Tailwind Elements," a tool that simplifies editing TailwindCSS HTML pages through AI, ensuring that modifications are quick without sacrificing the quality of the code.
## What Worked, What Didn't
Transparency is key. In each newsletter, expect to hear about what we've tried, what's worked, and what hasn't. Whether it's a new business strategy or a technical challenge, you'll get an honest look at the highs and lows of our projects.
## Join the Conversation
I believe that learning is a two-way street, and I would love to hear from you. Share your thoughts, challenges, and successes. Let's build a community where we can all grow together.
## Stay Connected
To ensure you never miss an update, bookmark this email or add it to your favorites. Expect a new issue in your inbox every week packed with insights and updates.
Thank you for joining me on this adventure. Here's to many enlightening weeks ahead!
Warm regards,
Troels
## Answers
See https://troels.im/answers/index.md for the current progressive answer index.