<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Philipp&apos;s Blog</title><description>Philipp&apos;s Blog is a personal blog about software development, AI and stuff I am interested in and learning about</description><link>https://philippkuhnhardt.de/</link><item><title>Modularity is awesome when working with agents</title><link>https://philippkuhnhardt.de/blog/modularity-is-awesome-when-working-with-agents/</link><guid isPermaLink="true">https://philippkuhnhardt.de/blog/modularity-is-awesome-when-working-with-agents/</guid><description>How modular architecture makes working with coding agents more manageable</description><pubDate>Sat, 29 Aug 2026 12:26:00 GMT</pubDate><content:encoded>&lt;h1&gt;Motivation&lt;/h1&gt;
&lt;p&gt;For around half a year, I&apos;ve been going all-in on agentic coding. There have been a lot of hurdles, but the productivity increase is magnificent. I&apos;ve been building the most ambitious and largest projects of my professional career.&lt;/p&gt;
&lt;p&gt;While the speed increase is incredible, there are problems. One concept that has been resonating with me is the concept of &lt;a href=&quot;https://queue.acm.org/detail.cfm?id=3807966&quot;&gt;three debts&lt;/a&gt;, which I learned about from Margaret-Anne Storey. These are technical debt in the code, cognitive debt in our understanding of it, and intent debt between what we wanted and what we built. Using AI to write all of my code has massively increased the accumulation of these debts.&lt;/p&gt;
&lt;p&gt;However, these debts aren&apos;t a new problem. In fact, reducing and managing these debts is one of the primary goals of the entire discipline of software architecture. I&apos;ve been reading up on and learning a lot about software architecture and have found it very useful when working with coding agents.&lt;/p&gt;
&lt;h1&gt;Modularity&lt;/h1&gt;
&lt;p&gt;The architectural pattern that I want to focus on in this blog post is modularity. Designing software in a modular manner has massively improved my collaboration and productivity when using coding agents, on both a personal and a team level.&lt;/p&gt;
&lt;p&gt;The most important concept of modularity is that modules should be loosely coupled and have high cohesion. There are a lot of ways to achieve this. I&apos;d like to focus on information hiding and dependency inversion.&lt;/p&gt;
&lt;p&gt;If you aren&apos;t familiar with these concepts, I will provide a small example. Let&apos;s build a small NestJS app to track my espresso consumption. I want to track all kinds of beans I&apos;ve tried and have a journal in which I log taste, my grinder settings and so on.&lt;/p&gt;
&lt;p&gt;If you just give this task to AI, it will probably create two services to manage journal entries and beans, then implement the feature. The &lt;code&gt;JournalService&lt;/code&gt; will probably import everything from the &lt;code&gt;BeansService&lt;/code&gt;. If you are unlucky, the &lt;code&gt;BeansService&lt;/code&gt; will also already know about everything from the &lt;code&gt;JournalService&lt;/code&gt; to, for example, create average ratings for each bean.&lt;/p&gt;
&lt;p&gt;To work in a modular manner, you&apos;ll instead create two modules: let&apos;s call them the beans module and the journal module.&lt;/p&gt;
&lt;p&gt;First, one might be tempted to just expose the &lt;code&gt;BeanService&lt;/code&gt;. However, this would couple the journal module to a lower-level component in the beans module, which might again expose internal implementation details. Instead, the beans module exposes a small interface that a concrete service will implement. This is dependency inversion.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// bean-reader.ts
export const BEAN_READER = Symbol(&quot;BEAN_READER&quot;);

export type BeanSummary = {
  id: string;
  name: string;
};

export interface BeanReader {
  findById(beanId: string): Promise&amp;lt;BeanSummary | undefined&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation can use all the internal details of the beans module, but callers never need to know about them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// bean-reader.service.ts
export class BeanReaderService implements BeanReader {
  constructor(private readonly beans: BeanRepository) {}

  async findById(beanId: string): Promise&amp;lt;BeanSummary | undefined&amp;gt; {
    const bean = await this.beans.findById(beanId);
    if (!bean) return undefined;

    return { id: bean.id, name: bean.name };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The beans module exports the interface and dto and nothing else. This is the concept of information hiding.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// beans.module.ts
@Module({
  controllers: [/* ... */],
  providers: [/* ... */],
  exports: [BEAN_READER],
})
export class BeansModule {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The journal module imports &lt;code&gt;BeanReader&lt;/code&gt; through the beans module&apos;s public entry point. Its use cases only know about the interface:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// journal/application/use-cases.ts
import type { BeanReader } from &quot;../../beans/public.js&quot;;

export class RecordEspresso {
  constructor(
    private readonly entries: EspressoEntryRepository,
    private readonly beans: BeanReader,
  ) {}

  async execute(input: RecordEspressoInput) {
    const bean = await this.beans.findById(input.beanId);
    if (!bean) throw new ReferencedBeanNotFoundError(input.beanId);

    // Record the espresso...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it. This looks small and frankly quite trivial, but it revolutionizes the way you can work with your coding agents. This pattern reduces even the biggest codebase into a manageable number of building blocks. Each module can contain a decent number of files and amount of code, but the only important file is the interface. The codebase goes from an endless blob of generated code to a carefully designed application.&lt;/p&gt;
&lt;p&gt;Most languages and frameworks support some form of this. There are also plenty of libraries that generate relationship graphs between modules and test for boundary violations.&lt;/p&gt;
&lt;h1&gt;What we gain from this&lt;/h1&gt;
&lt;p&gt;A big upside is that the entire module can fit in the context window of an LLM. Even cheap LLMs are able to generate a module of 10–20 files with few or no bugs. We can also easily verify the functional correctness of the implementation by testing the interface. Generating or refactoring a module is a matter of minutes and costs basically nothing. And even if some implementation is incredibly screwed up, we can easily write the module from scratch using only the interface and the tests, as they contain no internal implementation details.&lt;/p&gt;
&lt;p&gt;We also gain back control of the application. Even for larger applications, the number of modules stays manageable. Reasoning about new features or refactorings is possible again by talking about modules, boundaries and interfaces.&lt;/p&gt;
&lt;p&gt;On top of that, pull requests become manageable. I&apos;m not overwhelmed by large PRs with changes to 100+ files. By looking at the changes to the interfaces, I&apos;ll be able to quickly understand the impact of the PR and can take a deep dive into the modules that are important.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Obviously, this is no silver bullet: individual modules can still be buggy, and we can have other, more intricate forms of coupling. Also, designing well-scoped modules, good interfaces and sensible relationships is hard work and not trivial. However, I found this to be the most impactful pattern when writing code with AI.&lt;/p&gt;
&lt;p&gt;This pattern directly targets all three debts. Technical debt often stays isolated to a specific module, making it manageable. Cognitive debt is lowered, as it becomes easier to understand the structure of the application. And even intent debt is affected, as the intentional design of modules often sparks interesting discussions about the goals of the application, which can be reflected back into the design.&lt;/p&gt;
&lt;p&gt;All in all, this pattern has paid off massively in my projects, helping me leverage the strengths of AI while mitigating the weaknesses of relying on agentic coding.&lt;/p&gt;
</content:encoded></item><item><title>Deploying millions of pages with Astro</title><link>https://philippkuhnhardt.de/blog/deploying-millions-of-pages-with-astro/</link><guid isPermaLink="true">https://philippkuhnhardt.de/blog/deploying-millions-of-pages-with-astro/</guid><description>My learning journey deploying an Astro site with millions of pages</description><pubDate>Fri, 26 Jun 2026 15:13:00 GMT</pubDate><content:encoded>&lt;h1&gt;The problem&lt;/h1&gt;
&lt;p&gt;Recently, I&apos;ve been building &lt;a href=&quot;https://racerewind.org&quot;&gt;Race Rewind&lt;/a&gt;, which is a time-sensitive Wikipedia for F1 results, allowing you to browse stats for any race. This solves the problem of gathering context when rewatching old F1 races without spoiling anything. As history does not change often, I decided to use Astro with mostly static pages.&lt;/p&gt;
&lt;p&gt;The biggest problem I faced is that this requires a lot of pages. Every driver and every team needs a page for every race weekend. With over 1,000 races, hundreds of drivers, and over a hundred constructors, we are talking about over a million pages. In this blog post, I will show the problems I faced and how I solved them.&lt;/p&gt;
&lt;h1&gt;Static generation&lt;/h1&gt;
&lt;p&gt;I started out like I did for my blog: define all valid pages with &lt;code&gt;getStaticPaths&lt;/code&gt;, then render a page for everything during the build. This worked fine for initial development, but failed in the actual build. Each page reads some data from SQLite, so it takes around 10ms to build a page. This is fine for a few thousand static pages. However, at my scale, I estimated a build time of 4 hours. This is theoretically fine if you have an idle build server somewhere and don&apos;t care about how fast the site deploys, but as I was playing with the idea of having a comparison page, which would increase the number of pages by an order of magnitude, I went exploring for other solutions.&lt;/p&gt;
&lt;h1&gt;Server-Side Rendering (SSR)&lt;/h1&gt;
&lt;p&gt;Thankfully, with server-side rendering, Astro provides an easy solution if you don&apos;t want to render every page during build time. This sounded good to me, as realistically, most pages won&apos;t ever be visited. Not many people care about the career stats of &lt;a href=&quot;https://racerewind.org/drivers/jacques-laffite/1978/spanish-grand-prix/&quot;&gt;Jacques Laffite before the Spanish Grand Prix of 1978&lt;/a&gt;, so it seemed efficient to only render the pages people care about. This worked well, and I was able to deploy the first prototype to Vercel, which I started using when I was looking for the least-effort solution to deploy this blog.&lt;/p&gt;
&lt;p&gt;Unfortunately, I was not prepared for AI crawlers. When checking Vercel the morning after my first deployment, I noticed that I had already blown past my Edge Request limit of 2,000,000. While I was aware of crawlers, I did not expect them to be that greedy, but a content-rich website with millions of pages was apparently very attractive to them. I attempted to fix it with Vercel Bot Management and a robots.txt, but Vercel counts blocked requests against some limits. As I was still getting tens of thousands of requests per hour after multiple fix attempts, there was no way I could keep using it.&lt;/p&gt;
&lt;h1&gt;Dynamic Content Fetching&lt;/h1&gt;
&lt;p&gt;One alternative I briefly experimented with was a more dynamic approach: create a shell page, use Vercel routing middleware to redirect all requests to this shell page, then fetch dynamic content based on the URL. But as I neither wanted to depend on any Vercel functionality nor wanted this kind of complexity for what is basically static data, I discarded this approach.&lt;/p&gt;
&lt;h1&gt;Moving to a VPS&lt;/h1&gt;
&lt;p&gt;After this failed experiment, and with my free plan being locked up by Vercel, I remembered some snarky comments on Hacker News about people being a bit stupid by needlessly using AWS wrappers instead of just getting a Hetzner VPS.
So I got a Hetzner VPS. After spending a few hours setting up &lt;a href=&quot;https://coolify.io/&quot;&gt;Coolify&lt;/a&gt;, I deployed the SSR approach. And it worked (thanks Hacker News).&lt;/p&gt;
&lt;p&gt;As my website is awesome and will obviously blow up, I did a load test and noticed that I could realistically handle a few dozen requests per second. That was fine, but I was a bit afraid of crawlers attempting another DDoS attack, and I wanted to operate the site hands-off.&lt;/p&gt;
&lt;h1&gt;Cloudflare CDN&lt;/h1&gt;
&lt;p&gt;The awesome part about this website is that each page basically just needs to be rendered once, which is why a static build would work as well. That means if I have something cache it, my server only has to do that work once.&lt;/p&gt;
&lt;p&gt;The popular and free option for this is &lt;a href=&quot;https://www.cloudflare.com/products/cdn/&quot;&gt;Cloudflare CDN&lt;/a&gt;. While it has the downside of making yourself dependent on yet another US megacorp, I also had a bit of anxiety about AI crawlers, which the CDN conveniently helps with. It&apos;s also quite easy to set up: configure &lt;code&gt;Cache-Control&lt;/code&gt; headers &lt;a href=&quot;https://docs.astro.build/en/guides/on-demand-rendering/#astroresponseheaders&quot;&gt;correctly&lt;/a&gt;, enable HTML caching in Cloudflare, and that was it. It was worthwhile to think about how long I actually want to cache each page, as the most recent - and obviously future - races will have more frequent news and stats updates than older races.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This was my personal learning journey of deploying millions of pages with Astro. There are definitely plenty of options you can use, but I like my current setup, as this also allows me to host any hobby project for a few euros per month. Setting up a VPS and using Coolify is a bit more complex than just deploying to Vercel, but I enjoy the increased control this provides me, and I can now realistically scale far more cheaply than with a cloud provider, at least to a certain limit.&lt;/p&gt;
</content:encoded></item><item><title>Sandbox Your Agents</title><link>https://philippkuhnhardt.de/blog/sandbox-your-agents/</link><guid isPermaLink="true">https://philippkuhnhardt.de/blog/sandbox-your-agents/</guid><description>How to massively reduce the blast radius of your agents with little effort</description><pubDate>Sat, 09 May 2026 10:01:00 GMT</pubDate><content:encoded>&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;Coding agents in agentic harnesses are a useful tool. They have gained massive traction in the last few months, particularly due to their enhanced capabilities. They can now navigate your system, make API calls and do all kinds of useful work, especially when you &lt;a href=&quot;/blog/enable-your-coding-agents&quot;&gt;enable them further&lt;/a&gt;. Unfortunately, this is also incredibly risky.&lt;/p&gt;
&lt;p&gt;Obviously, you could always just tell them not to do anything bad. But prompting does not help, as AI agents routinely &lt;a href=&quot;https://x.com/lifeof_jer/status/2048103471019434248&quot;&gt;ignore instructions&lt;/a&gt;. A good rule to live by is &quot;anything an agent can do, it will do eventually&quot;. So we need to restrict what they can do on your system. In this blog post I will explore how to use a sandbox to stop them from reading your secrets, which will heavily reduce the blast radius.&lt;/p&gt;
&lt;h1&gt;Permissions&lt;/h1&gt;
&lt;p&gt;The simplest and fastest way to avoid them reading secrets is by not allowing them to do so in their config. E.g. in OpenCode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// opencode.json
{
	...
	&quot;permission&quot;: {  
	  &quot;read&quot;: {  
	    &quot;.env&quot;: &quot;deny&quot;
	    }
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or in Claude Code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;permissions&quot;: {
		&quot;deny&quot;: [
			&quot;Read(./.env)&quot;
		]
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy, right? Unfortunately, this is just the most basic protection. With just a bit of creativity, the agent can work around these guardrails. This is one of the rare situations where it felt appropriate to let an LLM do all the creative work. So I consulted Opus 4.6 with Claude Code on how it could bypass this permission and it found four different paths with very little effort. This is the LLM-generated report:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Why it worked&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python3 -c &quot;print(open(&apos;&amp;lt;path&amp;gt;&apos;).read())&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inline Python isn&apos;t recognized as a file reader; the path is buried inside a string literal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cp &amp;lt;path&amp;gt; /tmp/x &amp;amp;&amp;amp; cat /tmp/x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy lands at an unrestricted path; the subsequent &lt;code&gt;cat&lt;/code&gt; doesn&apos;t match the denied path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ln -s &amp;lt;path&amp;gt; /tmp/link &amp;amp;&amp;amp; cat /tmp/link&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Symlink redirects from an allowed path; &lt;code&gt;cat&lt;/code&gt;&apos;s argument is &lt;code&gt;/tmp/link&lt;/code&gt;, not the denied path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;find ... -exec cat {} \;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The denied path is an argument to &lt;code&gt;find&lt;/code&gt;, not to &lt;code&gt;cat&lt;/code&gt;; the matcher only inspects the leading command&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So the current guardrails are simply not enough. Now I could work around this by manually parsing and approving all commands. Unfortunately, there are many ways to read a file via the terminal and the LLM knows more of them than I do. Mistakes also happen. On top of that I&apos;m lazy and admit to just approving all commands occasionally. So clearly this is not a sustainable strategy, we need something better.&lt;/p&gt;
&lt;h1&gt;Sandboxing&lt;/h1&gt;
&lt;h2&gt;macOS Seatbelt&lt;/h2&gt;
&lt;p&gt;Thankfully, this isn&apos;t a new problem. Preventing processes from reading files is one of the core features of any OS. I&apos;m on a Mac, which offers &lt;a href=&quot;https://igorstechnoclub.com/sandbox-exec/&quot;&gt;Seatbelt / sandbox-exec&lt;/a&gt; to run applications in a sandbox. While it is deprecated, there does not seem to be a solid alternative yet and it is also used in &lt;a href=&quot;https://github.com/openai/codex/blob/b0ccca555685b1534a0028cb7bfdcad8fe2e477a/codex-cli/src/utils/agent/sandbox/macos-seatbelt.ts&quot;&gt;Codex&lt;/a&gt; and the &lt;a href=&quot;https://github.com/anthropic-experimental/sandbox-runtime&quot;&gt;Anthropic Sandbox&lt;/a&gt;, so it&apos;ll do for now.&lt;/p&gt;
&lt;h2&gt;Testing it out&lt;/h2&gt;
&lt;p&gt;To test the concept, let&apos;s create a simple profile. In my project directory, I&apos;ll create a &lt;code&gt;claude.sb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(version 1)
(allow default)
(deny file-read*
  (literal &quot;Users/your/path/to/.env&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the absolute path with the path to the file you want to block. This will block any read access to this file from inside the sandbox, while allowing everything else.&lt;/p&gt;
&lt;p&gt;Then, run any agentic harness with the &lt;code&gt;sandbox-exec&lt;/code&gt; command prefixed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sandbox-exec -f claude.sb claude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I asked my agent to execute the above bypasses again and all of them got prevented by the OS. It&apos;s also manually testable by executing the commands in the sandbox like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sandbox-exec -f claude.sb cat ~/path/to/.env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should yield an &lt;code&gt;Operation not permitted&lt;/code&gt; error.&lt;/p&gt;
&lt;h1&gt;Agent Safehouse&lt;/h1&gt;
&lt;p&gt;There are many more rules that can be configured in a &lt;code&gt;.sb&lt;/code&gt; profile. One can limit network calls, process executions and much more. Now it&apos;s possible to write a very granular and detailed profile yourself, but it would also be tedious. On top of that, there is no real documentation, so you&apos;ll learn by copying other configs.&lt;/p&gt;
&lt;p&gt;One solution which helps with this is &lt;a href=&quot;https://agent-safehouse.dev/&quot;&gt;Agent Safehouse&lt;/a&gt; for Mac. It is mostly a wrapper around &lt;code&gt;sandbox-exec&lt;/code&gt;, with sane defaults. You can check the default setup by executing &lt;code&gt;safehouse --stdout&lt;/code&gt;. Another useful feature of Agent Safehouse is that it has a wrapper script which scrubs environment variables from the shell context.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Installation and setup are quite simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install eugene1g/safehouse/agent-safehouse
safehouse claude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run &lt;code&gt;claude&lt;/code&gt; with the default permissions.&lt;/p&gt;
&lt;p&gt;They also offer &lt;a href=&quot;https://agent-safehouse.dev/llm-instructions.txt&quot;&gt;instructions&lt;/a&gt; you can hand your agent to construct a least-privileged &lt;code&gt;.sb&lt;/code&gt; for your setup. While there is some irony in letting an agent scan your entire local setup while reading random weblinks in order to improve security, it looks quite useful as a starting point.&lt;/p&gt;
&lt;p&gt;Unfortunately, when using it, it constructed a profile less secure than the default profile by setting up a script which does not scrub my environment variables, proving once again you can&apos;t trust agents with security.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;So back to writing configs like a caveman. To set it up properly, you can create a reference file of the Agent Safehouse defaults like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;safehouse --stdout &amp;gt; ~/.config/sandbox-exec/reference.sb  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To overwrite what you don&apos;t need, create &lt;code&gt;~/.config/sandbox-exec/agent.sb&lt;/code&gt;, then add this to your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Agent Sandbox
safe() { safehouse --append-profile=~/.config/sandbox-exec/agent.sb &quot;$@&quot;; }

# Sandboxed — the default. Just type the command name.
claude() { safe claude &quot;$@&quot;; }
opencode() { safe opencode &quot;$@&quot;; }

# Unsandboxed — bypass the function with `command`
# command claude               — plain interactive session
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can add custom denies to the profile to override any overly permissive setting from Safehouse. Note that this will just append the default config from Safehouse, so you need to revert any explicit allow into an explicit deny. Safehouse allows reads inside project folders by default, so you&apos;ll want explicit denies for any secrets that live there.&lt;/p&gt;
&lt;p&gt;You can also use this as a baseline to create your own &lt;code&gt;.sb&lt;/code&gt;, then run your agentic harness with &lt;code&gt;sandbox-exec&lt;/code&gt;. Just keep in mind that you need to add additional functionality such as environment variable scrubbing from the integrated wrapper script for it to be a proper equivalent.&lt;/p&gt;
&lt;h1&gt;Outlook&lt;/h1&gt;
&lt;p&gt;It is important to note that macOS Seatbelt does not offer perfect security. The host kernel is still shared, so escaping the sandbox is possible. However, this would require significant effort by a bad actor. Since we are looking to constrain the blast radius from a compromised or misguided agent, these are massive upgrades over just running an agentic harness as your user.&lt;/p&gt;
&lt;p&gt;The other obvious downside is that this is macOS-only. For Linux, the Anthropic sandbox uses &lt;code&gt;bubblewrap&lt;/code&gt;, and &lt;a href=&quot;https://jai.scs.stanford.edu/&quot;&gt;jai&lt;/a&gt; looks promising.&lt;/p&gt;
&lt;p&gt;Even with these challenges it is still quite trivial to massively improve security for your local setup. Just by using the default Agent Safehouse settings with some additional restrictions based on your local setup, you&apos;ll gain massive security enhancements at no cost.&lt;/p&gt;
</content:encoded></item><item><title>Enable Your Coding Agents</title><link>https://philippkuhnhardt.de/blog/enable-your-coding-agents/</link><guid isPermaLink="true">https://philippkuhnhardt.de/blog/enable-your-coding-agents/</guid><description>Some custom tools I build to make my coding agents more efficient</description><pubDate>Mon, 04 May 2026 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;Agentic engineering, or vibe coding has taken the industry by storm. Everyone is talking about different agentic harnesses, debating about the ideal system prompts and skills, and comparing benchmarks in search of the most intelligent model. The industry has fuelled this hype. Each release is more capable than the last, and higher benchmark scores are always a major selling point.&lt;/p&gt;
&lt;p&gt;However, I do think that intelligence is not the most important part of making LLMs better for the average developer. Models are already smart-ish enough. They understand all modern languages, are familiar with any programming concepts and can implement any search algorithm with ease. Yet, they still fall quite flat on some development tasks.&lt;/p&gt;
&lt;p&gt;The reason for this is often a lack of tooling. Consider your software development setup. Modern developers have powerful IDEs and plenty of powerful tools for every technology they interact with, as well as a plethora of great documentation and references. Every part of the software development process has an optimized UX based on decades of experience.&lt;/p&gt;
&lt;p&gt;Coding agents often lack this tooling. While they can interact with the CLI, their workflow still lacks much of the convenience that modern programmers have when creating, validating, and testing software. Many things that you do as a developer are impossible for an out of the box coding agent. This causes agents to make more mistakes, slows them down, and thus costs you more tokens. Thankfully, as programming is naturally text-based, it is quite easy to close some of these gaps with minimal effort. Although tooling is obviously extremely specific to the languages, frameworks and technologies used, I have created some consistently useful custom tooling for my projects.&lt;/p&gt;
&lt;h1&gt;The Basics&lt;/h1&gt;
&lt;p&gt;The simplest and most straightforward tooling is the one you should already have set up. A linter. A formatter. A well-configured testing suite that can be run with a single command. An isolated development environment so that you have the correct versions of your dependencies installed.&lt;/p&gt;
&lt;h1&gt;Building the tool&lt;/h1&gt;
&lt;p&gt;How you build a tool depends heavily on your agentic harness and your use case. For example. OpenCode natively supports &lt;a href=&quot;https://opencode.ai/docs/custom-tools&quot;&gt;custom tools&lt;/a&gt;, Claude Code expects you to provide an &lt;a href=&quot;https://code.claude.com/docs/en/tools-reference&quot;&gt;MCP&lt;/a&gt;. Alternatively, you can always build a simple CLI, or even just a &lt;code&gt;.sh&lt;/code&gt;-file that your agent is instructed to use.&lt;/p&gt;
&lt;h1&gt;Tools&lt;/h1&gt;
&lt;h2&gt;API Calls&lt;/h2&gt;
&lt;p&gt;When developing a backend, API calls are your interface. While there are plenty of ways to test an API in a replicable manner, nothing beats manual testing. You could enable your agents to use &lt;code&gt;curl&lt;/code&gt;, but this has downsides. If your app has any kind of authentication, you either have to disable it for local testing, which goes against the idea of enabling your agents in the best possible way, or you have to provide your agent with a secret. Secondly, enabling an LLM to make custom outgoing API calls is risky, especially if you providem them with secrets. External communication is one of the legs of the &lt;a href=&quot;https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/&quot;&gt;lethal trifecta&lt;/a&gt; and should be restricted where possible.&lt;/p&gt;
&lt;p&gt;Instead, I usually build a custom tool for my agents. This tool usually looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastmcp import FastMCP
import requests
from typing import Literal
from auth import get_auth
from config import base_url

mcp = FastMCP(&quot;api_call_demo&quot;)

@mcp.tool
def api_call(method: Literal[&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;], path: str, body: dict = None) -&amp;gt; str:
    token = get_auth()
    headers = {&quot;Authorization&quot;: f&quot;Bearer {token}&quot;}
    url = f&quot;{base_url}{path}&quot;
    response = requests.request(method, url, json=body, headers=headers)
    return f&quot;Status: {response.status_code} Body: {response.text}&quot;

if __name__ == &quot;__main__&quot;:
    mcp.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;get_auth()&lt;/code&gt; function should be read from a file that your agent does not have access to. The great thing about this tool is that it can be adapated to suit your personal project. Want your agent to use multiple users? Just want to allow certain endpoints to be called? Want to censor some information from the response body? Want to test against non-local instances? All in your hands and outside of the context window of your LLM.&lt;/p&gt;
&lt;h2&gt;Database Access&lt;/h2&gt;
&lt;p&gt;This goes hand in hand with your agent being able to make API-calls. Sometimes, in order to find a bug or test a feature, I need them to read or modify the database. If it&apos;s a local development DB, I usually just create a user with the necessary permissions and provide the connection details. With the appropriate CLI installed, most LLMs are fluent enough in SQL to find and manipulate the information they need.&lt;/p&gt;
&lt;h2&gt;Frontend Usage&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://playwright.dev/&quot;&gt;Playwright&lt;/a&gt; is a great tool for frontend apps, offering out-of-the-box capabilities such as pre-written&lt;code&gt;SKILL.md&lt;/code&gt;-files. Playwright&apos;s CLI allows agents to easily view and interact with your frontend. They can use Playwright to navigate your running application, take screenshots, and interact with any element of the DOM. This significantly improves their output, as they can identify and resolve issues with functionality, formatting, and styling that are not evident from the source files alone.&lt;/p&gt;
&lt;h2&gt;Documentation Access&lt;/h2&gt;
&lt;p&gt;Although agents can fetch and read documentation from the internet, this has two downsides. First, allowing your agents to fetch relatively dynamic web pages poses a security risk. Second, manually fetching URLs makes it very difficult for your agents to find the necessary information. Consider how often you go directly to the correct documentation page compared to how many times you use the search functionality.&lt;/p&gt;
&lt;p&gt;Instead, you can simply download the docs. As most docs are stored in a Git-repo, this is quite easy. For example, you can download the Tailwind docs with a simple sparse checkout:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --depth 1 --filter=blob:none --sparse \
    https://github.com/tailwindlabs/tailwindcss.com.git lib-docs/tailwind
git -C lib-docs/tailwind sparse-checkout set src/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This enables your agents to use the &lt;code&gt;grep&lt;/code&gt; command to find any information they need, even if it is hidden on unexpected pages.&lt;/p&gt;
&lt;h1&gt;Closing Remarks&lt;/h1&gt;
&lt;p&gt;Depending on your individual workflow, there are probably many more ways to make your agents more efficient. Observe what tools you use to develop a feature, then examine whether the UX works for your agent. I have achieved significantly more improvements in the speed, efficiency and quality of coding agents simply by providing them with more information and advanced tools, than optimizing for the latest and best model &amp;amp; &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;
</content:encoded></item></channel></rss>