<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>coles.codes</title><link>https://coles.codes/</link><description>Recent content on coles.codes</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 02 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://coles.codes/index.xml" rel="self" type="application/rss+xml"/><item><title>herdr vs cmux, after two months on each</title><link>https://coles.codes/posts/herdr-vs-cmux/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/herdr-vs-cmux/</guid><description>&lt;p>I left cmux for herdr at the start of June because its remote support did not fit how I work. Maybe I should have waited a little longer! cmux &lt;a href="https://cmux.com/docs/changelog">shipped a daemon that keeps SSH terminals alive across reconnects&lt;/a> on 1 June, then added SSH agent forwarding on 4 June.&lt;/p>
&lt;p>I stayed on &lt;a href="https://coles.codes/posts/herding-agents-with-herdr/">herdr&lt;/a> anyway. I&amp;rsquo;ve now spent about two months with each, moving between Linux boxes and my Mac while running several coding agents at once.&lt;/p></description></item><item><title>Reviewing code you didn't write</title><link>https://coles.codes/posts/reviewing-code-you-didnt-write/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/reviewing-code-you-didnt-write/</guid><description>&lt;p>Most of my last couple of weeks have been spent on code reviews and planning. &lt;a href="https://coles.codes/posts/the-shapeshifting-engineer/">Everyone can write code fast and cheaply&lt;/a>, teammates and agents both, so the wall of PRs mounts quicker than anyone can read it. Reviewing is how I stay a contributor to all that code: reading a change properly, even just its core path, means I know what&amp;rsquo;s going on in that part of the system and I can still challenge the approach with everything I know about the rest of it. Skip the review and I&amp;rsquo;m not really involved in that code any more, just adjacent to it.&lt;/p></description></item><item><title>Where I run my coding agents, and why it depends on the box</title><link>https://coles.codes/posts/where-the-agents-run/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/where-the-agents-run/</guid><description>&lt;p>I use Pi and Claude Code day to day, and I run them in two places. On my Mac, Claude Code&amp;rsquo;s permissions stay on because the box holds personal credentials and uncommitted work. On a pair of second-hand Dell micros, both agents run unattended with scoped project access and no irreplaceable local state.&lt;/p>
&lt;p>I assume an unattended agent will eventually do something I didn&amp;rsquo;t mean, so the useful safety question is what that mistake can reach. Hooks and instruction files still help inside the session, but the machine and its credentials decide how much damage is possible.&lt;/p></description></item><item><title>Designing MCP tools for agents</title><link>https://coles.codes/posts/designing-mcp-tools-for-agents/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/designing-mcp-tools-for-agents/</guid><description>&lt;aside class="tldr" aria-label="Summary">
 &lt;p class="tldr__label">TL;DR&lt;/p>
 &lt;p>Each MCP tool puts a contract in the model&amp;rsquo;s context. I keep related typed filters together and let the agent shape authorised results in code. Server-side scoping stays where access requires it. Progressive discovery stops a large catalog from using too much context.&lt;/p>

&lt;/aside>

&lt;p>The tempting way to build a data MCP server is one &lt;code>query_data(dataset, filters)&lt;/code> tool. Every new slice adds another branch to its filter grammar.&lt;/p>
&lt;p>The agent clients I use give the model a code execution environment, so I first ask whether the server needs to shape the result. If the caller may see the full dataset, the tool can return the rows and let the agent work with them in Python or DuckDB. A small &lt;a href="https://coles.codes/posts/skills-or-mcp-servers/">skill&lt;/a> can explain the dataset&amp;rsquo;s conventions.&lt;/p></description></item><item><title>Why local models get stuck repeating JSON</title><link>https://coles.codes/posts/grammar-constrained-repetition-trap/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/grammar-constrained-repetition-trap/</guid><description>&lt;p>I disqualified a 30B vision model for being too slow, and I was wrong about why. On my DGX Spark it timed out past 21 minutes on trivial documents, with no reading gain over its 8B sibling to show for the wait, so I binned it. Then I re-ran the same task on an RTX 5090 and successful runs finished in 22 to 140 seconds, so speed was never the real problem. The model still fails, because under grammar-constrained decoding it drops into a repetition loop that no sampler setting I tried gets it out of, and on the Spark that loop had just run slowly enough to look like a timeout.&lt;/p></description></item><item><title>Pydantic AI structured outputs and evals on Bedrock</title><link>https://coles.codes/posts/pydantic-evals/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/pydantic-evals/</guid><description>&lt;aside class="tldr" aria-label="Summary">
 &lt;p class="tldr__label">TL;DR&lt;/p>
 &lt;p>A guaranteed JSON schema tells you the shape is right, not that the content is any good. So I trust an LLM in stages: lock the shape with structured outputs, now native on Bedrock through constrained decoding, then check the content with Pydantic Evals, then grade the open-ended parts with an LLM judge you&amp;rsquo;ve calibrated against your own labels. That&amp;rsquo;s how I get output I&amp;rsquo;ll actually gate a merge on.&lt;/p></description></item><item><title>Building and securing MCP servers with FastMCP</title><link>https://coles.codes/posts/securing-mcp-servers-fastmcp/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/securing-mcp-servers-fastmcp/</guid><description>&lt;aside class="tldr" aria-label="Summary">
 &lt;p class="tldr__label">TL;DR&lt;/p>
 &lt;p>A typed FastMCP tool takes a few lines. Putting one in front of company data adds caller authentication and per-tool access control. This build-along then adds an audit trail and file delivery. The complete example runs without an identity provider or AWS account.&lt;/p>

&lt;/aside>

&lt;p>Most MCP tutorials stop at a few tools running on your own machine with no authentication. That is enough to learn the protocol because you are the only person who can call them. Company data already has access controls around it, so a tool that answers anyone who asks bypasses those controls.&lt;/p></description></item><item><title>Skills or MCP servers: when you need a server</title><link>https://coles.codes/posts/skills-or-mcp-servers/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/skills-or-mcp-servers/</guid><description>&lt;p>The argument I keep hearing is that skills have made MCP servers redundant. A skill can drive any CLI, so point the agent at &lt;code>aws&lt;/code> or &lt;code>psql&lt;/code> and it can do anything a server can. For one person working on their own machine, that&amp;rsquo;s mostly true.&lt;/p>
&lt;p>Calling a CLI doesn&amp;rsquo;t make a skill a good way to authenticate or retrieve governed data. The skill uses whatever credentials happen to be on your laptop, which means the caller is whoever you last logged in as. It can do anything those credentials allow. There is no central place deciding which tools finance can see or recording who touched a customer&amp;rsquo;s data. The skill may also give the agent more access than the task needs.&lt;/p></description></item><item><title>Anchoring specs to code with ast-grep</title><link>https://coles.codes/posts/anchoring-specs-to-code-with-ast-grep/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/anchoring-specs-to-code-with-ast-grep/</guid><description>&lt;p>Specs rot for a reason: we link them to code by file path and line number, and code moves. The spec says the token refresh logic lives in &lt;code>src/auth/service.py&lt;/code>, someone refactors it into &lt;code>TokenService&lt;/code>, and now the spec describes a file that no longer exists. Nobody notices until the spec is wrong enough to mislead someone, usually an agent, usually mine.&lt;/p>
&lt;p>Instead of pointing at where the code lives, match what it looks like. Anchor the spec section to an ast-grep rule for a function called &lt;code>refresh_token&lt;/code> inside &lt;code>TokenService&lt;/code>, and moving the file no longer breaks the link. A path is an address; the rule recognises the code&amp;rsquo;s shape. Here&amp;rsquo;s how each survives the same edits:&lt;/p></description></item><item><title>Breaking In</title><link>https://coles.codes/posts/breaking-in/</link><pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/breaking-in/</guid><description>&lt;p>If you&amp;rsquo;re thinking about getting into software engineering right now, the vibes are terrible.&lt;/p>
&lt;p>Every second post on social media tells you the career is dead: AI writes the code and nobody hires juniors, so pack it up.&lt;/p>
&lt;p>I understand why people feel that way, and some of it is true, but the full picture is messier than the doom scroll suggests and a lot more hopeful. I&amp;rsquo;ve been building software for over a decade, these days as a principal engineer with AI tools open every working day, so here&amp;rsquo;s what I think you need to know before you write the whole thing off.&lt;/p></description></item><item><title>The shapeshifting engineer</title><link>https://coles.codes/posts/the-shapeshifting-engineer/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/the-shapeshifting-engineer/</guid><description>&lt;p>This post is for the mids, seniors, and principals trying to work out what our jobs look like now that the machine writes the code. If you&amp;rsquo;re early in your career and wondering whether the whole thing is still worth pursuing, that&amp;rsquo;s a different conversation and I&amp;rsquo;ll write it up separately soon.&lt;/p>
&lt;p>I&amp;rsquo;ve been building software for over a decade, and these days I use AI coding tools every working day. The velocity is real. I&amp;rsquo;ve shipped features in an afternoon that would have taken me most of a week, and cleared backlog items that sat untouched for a year because the effort never justified the payoff.&lt;/p></description></item><item><title>Local models in mid-2026: the engineering that closed the gap</title><link>https://coles.codes/posts/local-models-mid-2026/</link><pubDate>Fri, 12 Jun 2026 00:00:00 +1000</pubDate><guid>https://coles.codes/posts/local-models-mid-2026/</guid><description>&lt;aside class="tldr" aria-label="Summary">
 &lt;p class="tldr__label">TL;DR&lt;/p>
 &lt;p>Open weights didn&amp;rsquo;t catch the frontier in 2026, but they got close enough for most of the work I do day to day. The gap closed because sparse attention, MoE routing, latent KV compression and four-bit quantisation cut the compute and memory per token without giving up much quality, rather than through bigger models or more RAM. This walks through the engineering that closed the gap, and why a memory crunch still made the box pricier than it should have been.&lt;/p></description></item><item><title>Herding parallel agents on a remote box with herdr</title><link>https://coles.codes/posts/herding-agents-with-herdr/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/herding-agents-with-herdr/</guid><description>&lt;p>My personal coding setup is a remote Ubuntu box that I connect to via ssh. Pi and Claude Code do the work. I scope down access because Pi runs without permission prompts and I use Claude&amp;rsquo;s &lt;code>--dangerously-skip-permissions&lt;/code> flag. Neovim handles edits, while yazi gives me review and filesystem navigation with git status patched into the file tree. tmux and mosh hold it all together so nothing dies when my connection drops, and it&amp;rsquo;s been good to me. The Mac in front of me is basically a thin client unless I&amp;rsquo;m working on something UI-heavy.&lt;/p></description></item><item><title>Building lgtmaybe: a PR reviewer for any model</title><link>https://coles.codes/posts/building-lgtmaybe/</link><pubDate>Sun, 07 Jun 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/building-lgtmaybe/</guid><description>&lt;p>I built a PR reviewer called &lt;a href="https://github.com/MattJColes/lgtmaybe">lgtmaybe&lt;/a>, which is the joke I wanted in the name before I&amp;rsquo;d even started writing the code. You point it at a pull request, pick a model with one flag, and it posts inline comments plus a summary. A clean PR gets a 👍 LGTM, and everything else gets a maybe.&lt;/p>
&lt;p>The thing I&amp;rsquo;m happiest with is that you can run it on OpenAI, Anthropic, OpenRouter, Bedrock, Vertex, or a local Ollama box.&lt;/p></description></item><item><title>About</title><link>https://coles.codes/about/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/about/</guid><description>&lt;figure class="post-figure post-figure--right">
&lt;picture>
&lt;source type="image/webp" srcset="https://coles.codes/images/coffee_hu6386227120429531088.webp 320w, https://coles.codes/images/coffee_hu5970933650659788420.webp 480w" sizes="(max-width: 600px) 80vw, 15rem">
&lt;img src="https://coles.codes/images/coffee_hu13610071653516757238.jpeg" srcset="https://coles.codes/images/coffee_hu1341684920514327581.jpeg 320w, https://coles.codes/images/coffee_hu13610071653516757238.jpeg 480w" sizes="(max-width: 600px) 80vw, 15rem" width="480" height="361" data-full="/images/coffee_hu13610071653516757238.jpeg" alt="The morning coffee, my first priority!" loading="lazy" decoding="async">
&lt;/picture>&lt;figcaption>The morning coffee, my first priority!&lt;/figcaption>
&lt;/figure>&lt;p>Hey, I&amp;rsquo;m Matt - a Principal Engineer at AWS, living in Melbourne, Australia 🇦🇺. I build things and write up the bits worth keeping.&lt;/p>
&lt;p>Lately, that&amp;rsquo;s mostly:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Building tools in Python&lt;/strong> - mostly Pydantic, PydanticAI, FastAPI and AWS Strands. I like typed code, and agents do a lot of the boring work.&lt;/li>
&lt;li>&lt;strong>Playing with open-source LLMs&lt;/strong> - Qwen, GLM and friends running locally on a Framework Desktop and a DGX Spark. Lately I&amp;rsquo;ve been testing OCR and vision models for fun.&lt;/li>
&lt;li>&lt;strong>Running a homelab&lt;/strong> - Raspberry Pis, an old Synology NAS, Dell OptiPlex Micros and the bigger AI boxes, all joined by Tailscale and running lots of containers under Docker Swarm and Portainer.&lt;/li>
&lt;li>&lt;strong>Living in the terminal&lt;/strong> - Claude Code and Opus daily, Ghostty on the Mac with herdr inside it, and endless tinkering with my Linux and Claude Code configs.&lt;/li>
&lt;li>&lt;strong>Making apps&lt;/strong> - Into Flutter lately, after plenty of Swift and React Native. Backends usually built with CDK on AWS.&lt;/li>
&lt;li>&lt;strong>Talking about it&lt;/strong> - I speak at user groups and conferences like AWS re:Invent and PyCon AU, make the odd YouTube video, and used to host a stream called Devs in the Shed.&lt;/li>
&lt;/ul>
&lt;p>At work I ship software and worry about how it&amp;rsquo;ll behave under real load. My backends usually start as FastAPI modular monoliths, with services split out only when something genuinely needs to scale on its own.&lt;/p></description></item><item><title>Friends</title><link>https://coles.codes/friends/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/friends/</guid><description>&lt;p>A few blogs and people worth your time.&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://blog.kukiel.dev">Paul Kukiel&lt;/a>&lt;/strong> - a good friend of mine, and one of the best Solution Architects I know. He writes hands-on posts on AWS, Python, and AI-assisted coding.&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://melchi.me">Melchi Salins&lt;/a>&lt;/strong> - a colleague who goes deep on compute and AI infrastructure. His KV cache explainer even ships with an interactive calculator.&lt;/p>
&lt;p>&lt;em>If we know each other and you&amp;rsquo;d like a link here, get in touch.&lt;/em>&lt;/p></description></item><item><title>Projects</title><link>https://coles.codes/projects/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://coles.codes/projects/</guid><description>&lt;p>A few things I&amp;rsquo;ve built or am still tinkering with.&lt;/p>
&lt;h2 id="lgtmaybe">lgtmaybe &lt;a class="heading-anchor" href="#lgtmaybe" aria-label="Link to this section">#&lt;/a>&lt;/h2>
&lt;p>A PR reviewer that works with six model providers behind one &lt;code>--provider&lt;/code> flag. Bedrock and Vertex use GitHub OIDC, so the cloud providers don&amp;rsquo;t need static keys in your secrets. The same Python core ships as a PyPI CLI and a GitHub Action, with AI doing most of the typing. I wrote up &lt;a href="https://coles.codes/posts/building-lgtmaybe/">how I built it&lt;/a>, the full docs live at &lt;a href="https://lgtmaybe.coles.codes/">lgtmaybe.coles.codes&lt;/a>, and the source is &lt;a href="https://github.com/MattJColes/lgtmaybe">MattJColes/lgtmaybe&lt;/a>.&lt;/p></description></item><item><title>Identifiers within AWS CDK</title><link>https://coles.codes/posts/identifiers-within-cdk/</link><pubDate>Thu, 30 Sep 2021 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/identifiers-within-cdk/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Archive post - originally published 30 September 2021&lt;/strong> on &lt;a href="https://www.devsintheshed.com">devsintheshed.com&lt;/a>. The example is CDK &lt;strong>v1&lt;/strong> in JavaScript, and I&amp;rsquo;ve transcribed the code from the original article&amp;rsquo;s screenshots (the two lines of &lt;code>require&lt;/code>s and the class declaration above the visible area are the standard &lt;code>cdk init&lt;/code> template). The concept of construct IDs and unique IDs is unchanged in v2, but the imports have moved and there&amp;rsquo;s a renaming trap worth knowing about, both covered in the &lt;a href="#appendix-2026-whats-changed-in-cdk-v2">2026 appendix&lt;/a> at the end.&lt;/p></description></item><item><title>Reference and import existing assets into AWS CDK</title><link>https://coles.codes/posts/reference-and-import-existing-assets-into-aws-cdk/</link><pubDate>Thu, 30 Sep 2021 00:00:00 +0000</pubDate><guid>https://coles.codes/posts/reference-and-import-existing-assets-into-aws-cdk/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Archive post - originally published 30 September 2021&lt;/strong> on &lt;a href="https://www.devsintheshed.com">devsintheshed.com&lt;/a>. I&amp;rsquo;ve left the article exactly as it ran back then: CDK &lt;strong>v1&lt;/strong>, &lt;code>aws_cdk.core&lt;/code>, the per-service &lt;code>aws-cdk.aws-*&lt;/code> packages, the lot. It&amp;rsquo;s a snapshot of how this worked at the time. CDK has since moved to v2 and a fair bit of the code below no longer runs as written - skip to the &lt;a href="#appendix-2026-whats-changed-in-cdk-v2">2026 appendix&lt;/a> at the end for what&amp;rsquo;s different now.&lt;/p>
&lt;/blockquote>
&lt;p>Hey everyone,&lt;/p></description></item></channel></rss>