Blog

谢谢你,中国! (Thank you, China!)

First things first: Happy Canada Day, everyone! Bonne fête du Canada, tout le monde !

Yes, I know. Starting a post that celebrates Canada Day with a giant title thanking China might feel a bit ironic, if not entirely contradictory. But stick with me for a minute. Because if you are a Canadian working in tech right now, navigating a world where our longest-standing friendship and alliance has ruptured into open hostility from the US government, you should be thanking them too.

For decades, the Canadian tech ecosystem operated under an unspoken assumption: we were fundamentally part of the same digital backyard as Silicon Valley. We relied on the same infrastructure, trusted the same partners, and assumed that a shared border meant shared access to the future of technology.

The rise of large language model regulation in the US has shattered that illusion. As LLMs became the foundational infrastructure for the next generation of software, the narrative shifted toward a suffocating, centralized reality. Suddenly, it looked like access to “intelligence” would be controlled by a couple of trillion-dollar American tech giants, operating under the tight regulatory grip and arbitrary “national security mandates” of an increasingly authoritarian and hostile US government. That’s… not great.

The message to us and to the rest of the world was clear: if you want to build next-gen software, you have to rent it from them and you have to behave or they’ll turn off the tap.

When the US government forced Anthropic to turn Fable and Mythos off a couple of weeks ago, it hit me how all of a sudden things could turn. And that’s when I starting thinking about how different things could have been had China not chosen the path of opensource.

The Open-Source Weapon

Had Chinese AI labs followed the American playbook, the trap would have snapped shut.

When the US president put measures to prevent chips from being sold to China, they inadvertently scored one of the biggest own goals ever, because faced with severe hardware restrictions and intense domestic competition, Chinese companies and research labs like Alibaba, Z.AI and Moonshot took a radically different path. While American companies felt safe in closing their gates, shifting from open research to aggressive, closed-source monopolies, the Chinese chose to weaponize open weights.

They realized the fastest way to bypass the American monopoly was to simply give the models away.

And they didn’t just dump mid-tier toys. Their models are improving at a much higher pace compared to the American ones and have pretty much caught up in practical terms. They’ve consistently commoditized the technology that the US would love to leverage against us.

Autonomy through Proliferation

There is a profound irony here. China has long been known for its heavy domestic internet censorship and state oversight. Yet, they’ve inadvertently become the world’s greatest guarantor of decentralized, open-source AI infra.

By flooding the ecosystem with high-quality opensource models, they’ve made it functionally impossible for an authoritarian US government to lock down the future of software development.

Once a model like GLM 5.2 or Kimi K2.7 are on Hugging Face, downloaded, and running locally on hardware right here in Canada, the gates cannot be closed. It belongs to the global developer community. Better yet, it can be fine-tuned, quantized, and run entirely offline.

This isn’t about blind defence of the Chinese government or anything. It’s a pragmatic look at power dynamics: hegemony is the absolute enemy of innovation, and proliferation is the ultimate antidote to bullying.

When global superpowers compete by releasing open infrastructure, developers win. In fact, the aggressive opensourcing of Chinese frontier models may threaten the duopoly of Anthropic and OpenAI. OpenAI has already postponed their IPO, as companies start having trouble reconciling the costs of inference charged by these labs with whatever gains in productive they’re seeing from them.

Because Chinese labs chose to open up their models, the dream of an API-locked AI dystopia where Canada and other “middle powers” can be frozen out at the border is dead. We have choices, which means we have autonomy.

So, to the engineers and teams pushing the boundaries of open weights on the other side of the world, from a developer celebrating a complicated Canada Day: 谢谢你,中国。 You kept the future of AI open when our neighbours tried to lock it away.

Updated LLM Coding Workflow

Back in January I posted about how I view LLMs, which included my workflow of doing LLM-assisted coding. To summarize, my workflow was:

  1. Reverse rubber-ducking
  2. Planning and writing a spec file
  3. Implement each phase of the plan, one by one
  4. Validation and commit

I can say lots have changed since January. For one, models are significantly better and more reliable. As well, I feel like I’ve got better at steering them. If I look at the above list by itself, without details, it doesn’t feel like things changed so much, but look closer and it’s a whole new world. My current workflow is an evolution of the above.

Before continuing, let me make something clear: this is my professional workflow. It’s what I use to write production code on cloud services.

Phase 0: Reverse Rubber-ducking

I still have this, but I no longer use a chat interface. I start directly with an agent (almost always OpenCode) and I now first get the agent to engage with the code before anything else. Let’s say I need to make a change to the flux capacitors, so I go and tell the agent what I think happens:

This is cloud-service-foo and it handles requests to create farbelizer connectors. I believe it then nimbolizes the farbelizers before sending them to cloud-service-bar that processes them through the flux capacitors. Check what the actual flow is and summarize it for me.

Most of the times – though not always – I know exactly how the flow works, but I do this to sort of prime the LLM for discussing what I want to change. I just found that it tends to work well for me; better then just telling it directly what I want to change.

An indirect effect of doing this is that sometimes it will tell me something that doesn’t meet my understanding, so I ask details to figure out if it’s really something I missed or just something the LLM got wrong.

When I know the LLM has the context, I will say something like

I have an issue where if the farbelizer connector starts with “foo”, then the flux capacitors should suppress the harmonic back-feeding before it reaches the primary gimbal housing.

I often add a little more about what the actual goal is, but it’s something like this. This usually causes the LLM to tell me what it thinks should be done. It will sometimes ask a question or two, but eventually it will give me a solution. Many times the solution is one I know I don’t want due to some constraint and I will tell it. Sometimes I steer it a bit more and tell it what I think we should do.

And then when I’m happy, I’ll say:

Plan a series of independent PRs to implement this. List which ones can be done in parallel vs linearly

That’s it. That’s the entire plan phase now. I no longer need a spec file.

Phase 1: Implementation

Given the list of PRs, I will ask it to implement them either a few in parallel or, if there’s a dependency, one by one. I also now let the LLM agent commit its changes. (if they were in parallel, I also let it push the changes.)

Again, that’s it?

Phase 2: Validation

I then test the work locally, I still insist on doing that because I don’t want to cause a SEV. I’ll then push the branches and review the diffs myself in GitHub before asking for others to review: I want to avoid wasting people’s time.

You still have to watch them

I had an interesting interaction with GPT 5.5 a few weeks ago, where it wrote code that was akin to this:

attempt := 0

for {
	attempt++
	if attempt > maxAttempts {
		return errTooManyAttemps
	}
	err := fetchData(ctx)
	if err != nil {
		switch {
		case errors.Is(err, errTimeout):
			fmt.Println("Warning: Timeout occurred. Retrying...")

		case errors.Is(err, context.Canceled):
			fmt.Println(" -> Context was canceled. Exiting...")

		default:
			return err
		}
		
		time.Sleep(100 * time.Millisecond) 
	}
}

When I saw that, I immediately knew it didn’t look right, so I asked the agent about the case with the context.Canceled and it happily explained to me that it would log the error and then return with default:. I said, no, it won’t, that’s not how Go switches work. And it insisted! “I understand your confusion, but because there is no break statement, the code will simply fall through the next case.”

No, it forking won’t! So I told it to prove it by writing a test that returned a context canceled. It did, caught the infinite loop and conceded.

My point? They can still make mistakes. I have to check them.

Conclusion

That said, I will concede that the LLMs are so much better now and that these errors are getting more and more rare. My flow is much quicker than before. I still review code like a caveman, I still make sure the LLM gets what I want it to do. But I basically killed the entire “plan” step. It’s just not needed. And I almost never write code by hand.

Your App Subscription Is Now My Weekend Project

I pay for a lot of small apps. One of them was Wispr Flow for dictation. That’s $14 CAD/month that I was paying until I had a few lazy days visiting my mother. And then on the afternoon of New Year’s Day, I vibecoded Jabber.

Now, don’t get me wrong, Jabber is not “production quality.” I would never sell it as a product or even recommend it to other people, but it does what I needed from Wispr Flow, and it does exactly the way I want it to. For free.

At work, I’m often asked to make small videos showing some support agent how something works, or sharing some knowledge with new team members, or just a regular demo of something. In the past, I used to use Loom, which costs $15/month. So after creating Jabber, I got excited and vibecoded Reel.

Reel does exactly what I wanted Loom to do: I can record my camera, I can move it around, and I get to trim the video after it’s done (I don’t remember being able to do that with Loom).

Then just yesterday, a friend of mine was telling me how he got tired of paying for Typora and decided to vibecode his own Markdown editor. And that gave me the idea of creating an editor for my blog.

That’s Hugora! Yes, horrible name, but who cares? It’s just for me. I get to edit my Hugo blog just the way I like. It even shows my site theme.

You see the pattern here?

All of these $10/month apps are suddenly a weekend project for me. I’m an engineer, but I have never written a single macOS application. I’ve never even read Swift code in my life, and yet, I now can get an app up and running in a couple of hours. This is crazy.

Last year, a Medium post predicted:

Most standalone apps will be “features, not products” in the long run — easy to copy and bundle into larger offerings.

And I think we’re there. I don’t know what that means for the future of our industry, but it does seem like a big shift.

I’m still skeptical of vibecoding in general. As I mentioned above, I would not trust my vibecoding enough to make these into products. If something goes wrong, I don’t know how to fix it. Maybe my LLM friends can, but I don’t know. But vibecoding is 100% viable for personal stuff like this: we now have apps on demand.

Being Specific when Pairing with Bots

A couple of days ago I posted about my workflow and I made light fun of something I do:

I also give it context to save time. The agents nowadays are very smart and can find their way, but I can shortcut that by giving it hints “in internal/foo/foo.go there’s a function called DoFoo() and it does this and that and I want it to do that other thing before that” or whatever. Less tokens, faster iteration. This is probably astrology for nerds, pure superstition at this point, but I still do it.

Turns out, maybe it’s not really astrology for nerds? Today, Quinn Slack shared an article about How To Pair with an Agent and in it, the author says “The more you can specify, the better” and gives this example of a good, specified prompt:

Specified prompt:

Build a new API endpoint for user notifications. Follow the pattern in src/api/messages.ts as your reference. Run the API tests after each step. Don’t move on until they pass.

You gave it a reference to follow and a way to check its own work. Now you can step away. Let the agent iterate until the tests pass.

So maybe it’s not astrology for nerds, but a good practice? I always felt like it gave me better results, but I wasn’t 100% sure if it wasn’t just a superstition. Nice to see it’s probably not.

Thoughts on Amp's ad-supported business model

I’m agent-agnostic, in which I don’t use only one. I keep changing from time to time. My earliest forays into agentic programing were with Claude Code, which was then and still is probably the gold standard. But since then I’ve tried quite a few: Codex CLI (good models, barebones agent), Droid (not a fan), OpenCode (big fan!), and Amp.

I’ve been using Amp for a while, but only from time to time to see its evolution. With the move to Opus 4.5, I found that Amp has become very capable and I started using it more and more until it became my go-to agent.

The one downside of Amp is that it can get expensive. Since it’s not tied to any of the labs, it needs to charge API pricing, which can get expensive if you use it a lot, though maybe less than most people think.

But it’s undeniable that there is a psychological impact at seeing money constantly being drawn as you use, even if at the end of the day you’d spend the same as with a subscription.

These are challenges the Amp people have been working on for a while. Then not that long ago, they came up with a first attempt: an ad-supported free tier. You could use Amp up to $10 worth of API per day as long as you agreed to see ads on your agent.

To be clear, ads are optional. You only see the ads if you choose to and if you do, you get $10 worth of inference per day, using some cheaper models. This is how the ads appear:

Personally, I find them unobstructive, but opinions may vary. Now, you may notice that I emphasized the fact that these ads are options. The reason why I did so is that it appears that the idea of ads hits a nerve in some people. Ever since their free tier came out, I’ve seen several tweets of people announcing their refusal to ever try Amp because they don’t want ads.

Now Amp came up with a next step, whereas paying customers can also enable Amp Free and get those $10 a day of inference and their balance will only be drawn from once they exhaust their free allowance. That’s the equivalent of $300 a month of free inference. Not only that, but you can use that free allowance with Opus 4.5.

I understand the aversion to ads. I share it. But $300/month is an incredible value to ignore. So I decided to enabled Amp Free and use Amp exclusively this month to see how much I will have spent by the end of the month. My suspicion is that I’ll spend less than my Claude Max subscription.

But something that I think is very important to remember is that there’s a whole world of engineers outside of the developed world. For a developer in, say, South America, the cost of a Claude or Codex subscription is prohibitive. This is keeping a whole world of engineers out of the LLM revolution. Amp’s approach offers a way for them to have access to premium models they otherwise wouldn’t have access to.