<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dennison Bertram</title>
    <description>The latest articles on DEV Community by Dennison Bertram (@dennisonbertram).</description>
    <link>https://dev.to/dennisonbertram</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3572813%2Facf865c6-793b-455a-91db-cfc769f1cc36.png</url>
      <title>DEV Community: Dennison Bertram</title>
      <link>https://dev.to/dennisonbertram</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dennisonbertram"/>
    <language>en</language>
    <item>
      <title>Why FreePi runs on the Pi harness (and what the new Harness Leaderboard shows)</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:21:42 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/why-freepi-runs-on-the-pi-harness-and-what-the-new-harness-leaderboard-shows-ob9</link>
      <guid>https://dev.to/dennisonbertram/why-freepi-runs-on-the-pi-harness-and-what-the-new-harness-leaderboard-shows-ob9</guid>
      <description>&lt;p&gt;The model gets all the attention. The harness around it — prompts, tools, context management, when to stop — usually decides whether a coding agent actually ships work.&lt;/p&gt;

&lt;p&gt;A new &lt;a href="https://huggingface.co/spaces/joelniklaus/harness-leaderboard" rel="noopener noreferrer"&gt;Harness Leaderboard&lt;/a&gt; from Joel Niklaus makes that measurable: same open models, same provider, same reasoning effort, same tasks. &lt;strong&gt;Only the coding-agent harness changes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What FreePi is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.freepi.ai" rel="noopener noreferrer"&gt;FreePi&lt;/a&gt; is a free AI coding agent in your terminal. Ads pay for inference. Under the hood it runs Mario Zechner's &lt;strong&gt;Pi&lt;/strong&gt; harness — the same open agent loop the leaderboard just put under a microscope.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the board measured
&lt;/h2&gt;

&lt;p&gt;Joel ran &lt;strong&gt;28,849 trials&lt;/strong&gt; across nine harnesses on GLM-5.3-Flash, DeepSeek-V4.1-Flash, and Flint, on Terminal-Bench 2.1 and DeepSWE (~$4,746 of inference). Headline score is resolve rate; cost is measured outside the harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Pi landed
&lt;/h2&gt;

&lt;p&gt;Averaged over the three models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pi&lt;/strong&gt; — &lt;strong&gt;49.4%&lt;/strong&gt; resolve at &lt;strong&gt;$0.29&lt;/strong&gt; per solved task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mini-swe-agent&lt;/strong&gt; — &lt;strong&gt;51.4%&lt;/strong&gt; at &lt;strong&gt;$0.53&lt;/strong&gt; (Pi is statistically tied, ~45% cheaper)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenCode&lt;/strong&gt; — &lt;strong&gt;$3.35&lt;/strong&gt; per solved task (&lt;strong&gt;11.7×&lt;/strong&gt; Pi)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pi sits in the top cluster and is the cheap strong option. The best harness still depends on the model, but the cost gap is hard to ignore.&lt;/p&gt;

&lt;p&gt;Source announcement: &lt;a href="https://x.com/joelniklaus/status/2118373808692437403" rel="noopener noreferrer"&gt;Joel's post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FreePi ships on Pi
&lt;/h2&gt;

&lt;p&gt;We didn't invent a new agent loop. We ship Pi with free models, a GitHub sign-in, and no card required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx free-pi-cli@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an independent board says the harness is near the top &lt;em&gt;and&lt;/em&gt; thrifty, that's the product story: best harness we know how to ship, batteries included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Harness choice moves quality and cost as much as model choice. Pi cleared that bar. FreePi puts that harness in your terminal for free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leaderboard: &lt;a href="https://huggingface.co/spaces/joelniklaus/harness-leaderboard" rel="noopener noreferrer"&gt;huggingface.co/spaces/joelniklaus/harness-leaderboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FreePi: &lt;a href="https://www.freepi.ai" rel="noopener noreferrer"&gt;www.freepi.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Credit: &lt;a href="https://x.com/pidotdev" rel="noopener noreferrer"&gt;@pidotdev&lt;/a&gt; / Mario Zechner for Pi; &lt;a href="https://x.com/joelniklaus" rel="noopener noreferrer"&gt;@joelniklaus&lt;/a&gt; for the board&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Fusion works: DeepSeek V4 Pro plans, V4 Flash writes the code</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Thu, 05 Nov 2026 16:23:16 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/how-fusion-works-deepseek-v4-pro-plans-v4-flash-writes-the-code-13mi</link>
      <guid>https://dev.to/dennisonbertram/how-fusion-works-deepseek-v4-pro-plans-v4-flash-writes-the-code-13mi</guid>
      <description>&lt;p&gt;Big models write better plans. Small models are cheaper and fast enough to type the code. I wanted both in one session without switching models by hand.&lt;/p&gt;

&lt;p&gt;That's Fusion. It's one entry in FreePi, a free AI coding agent for the terminal. This post explains how it splits a task between DeepSeek V4 Pro and DeepSeek V4 Flash, how the switch happens, and when it's worth turning on. Everything here comes from the code that ships in the CLI today.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; run &lt;code&gt;npx free-pi-cli@latest&lt;/code&gt; in a terminal, then pick Fusion in &lt;code&gt;/model&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Give this to your agent: &lt;a href="https://freepi.ai/llms.txt" rel="noopener noreferrer"&gt;freepi.ai/llms.txt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Every message you send goes through three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explore.&lt;/strong&gt; DeepSeek V4 Flash reads your code with read-only tools. It makes no edits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan.&lt;/strong&gt; DeepSeek V4 Pro gets one request. It writes a step-by-step plan into the agent's todo list. It can't edit files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build.&lt;/strong&gt; Flash takes over again. It works through the todo list, edits files and runs tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So Pro answers one request per message. Flash handles every other request, including all the follow-ups after tool calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you turn it on
&lt;/h2&gt;

&lt;p&gt;FreePi runs the pi coding agent. Type &lt;code&gt;/model&lt;/code&gt; and Fusion is the first entry in the list. Pick it, and the footer shows Fusion plus the model that answered the last request.&lt;/p&gt;

&lt;p&gt;Fusion isn't the default. A new session starts on the default model the server lists first, so you pick Fusion yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fusion is a router, not a model
&lt;/h2&gt;

&lt;p&gt;pi has a feature called virtual models. A virtual model shows up in &lt;code&gt;/model&lt;/code&gt; like any other model. Before every request, it runs a &lt;code&gt;route&lt;/code&gt; function that picks a real model to send that request to.&lt;/p&gt;

&lt;p&gt;Fusion is a virtual model. When you pick it, nothing new runs on the server. Each request still goes out as a normal request to &lt;code&gt;deepseek-v4-pro&lt;/code&gt; or &lt;code&gt;deepseek-v4-flash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The router keeps one piece of state per session branch, the current phase. Here's the core of it, trimmed, with one helper renamed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;// a new message from you: start over&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;explore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continuation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;// after the planner's one reply, it's all build&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;explore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;editedThisTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// the executor asked for a plan&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;planRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;explore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In pi, a "continuation" is any request in the agent loop that isn't a fresh message from you, like the request after a tool result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asking for a plan is a tool call
&lt;/h2&gt;

&lt;p&gt;How does Flash say "I've read enough, give me a plan"? It calls a tool named &lt;code&gt;fusion_plan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The tool takes one argument, a summary of what Flash found and what the task needs. It only exists while Fusion is selected. Its result is the planner's brief:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are now the planning model. Write a concrete
step-by-step plan (files, exact changes, tests to add
and run) and record it with the todo tool, one item
per step. Do not edit files or run commands. Another
model will carry out the plan.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router sees that &lt;code&gt;fusion_plan&lt;/code&gt; result in the transcript and sends the next request to Pro. Pro sees the whole conversation, including the files Flash already read. Then it writes the plan with the todo tool. You can see that list any time with &lt;code&gt;/todos&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails, because prompts alone didn't work
&lt;/h2&gt;

&lt;p&gt;Fusion's instructions tell Flash to explore first and call &lt;code&gt;fusion_plan&lt;/code&gt; before any edit. A comment in the code explains why that wasn't enough. In a live run, DeepSeek ignored the instruction and edited files directly.&lt;/p&gt;

&lt;p&gt;So Fusion now blocks edits in code. While Fusion is selected, the CLI checks every &lt;code&gt;edit&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before a successful &lt;code&gt;fusion_plan&lt;/code&gt; call since your last message, the edit is blocked. The model gets back "call fusion_plan first".&lt;/li&gt;
&lt;li&gt;If the planner tries to edit, it's blocked too. The planner records the plan, and the executor makes the changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few more cases are handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The planner stops early.&lt;/strong&gt; If Pro replies with text and no tool calls, the turn would end with nothing built. Fusion adds one hidden message, "The plan is recorded. Carry out the unfinished todo items now", so Flash picks up the work. This happens at most once per plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The planner request fails.&lt;/strong&gt; The retry goes to Flash in build mode instead of trying Pro again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A model is missing.&lt;/strong&gt; If either model can't be found, every request goes to the default model instead of failing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What stays with Flash
&lt;/h2&gt;

&lt;p&gt;Plain questions skip the planner. Fusion's instructions tell Flash to answer a message that needs no file changes directly, with no &lt;code&gt;fusion_plan&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Background work also goes to Flash. A compaction summary, for example, is a request outside the agent loop, and the router always sends those to the executor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The server picks the pair
&lt;/h2&gt;

&lt;p&gt;The model ids aren't hardcoded forever. On startup the CLI calls &lt;code&gt;/client-version&lt;/code&gt;, and the server can send a &lt;code&gt;fusion&lt;/code&gt; field with a planner and an executor. Today it says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"planner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deepseek-v4-pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deepseek-v4-flash"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI uses that pair only if both models are in the catalog it just loaded. Otherwise it falls back to its built-in pair. So I can swap the planner on the server without shipping a new CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use it
&lt;/h2&gt;

&lt;p&gt;Fusion fits tasks where a bad plan costs you more than a slow start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes that touch several files.&lt;/li&gt;
&lt;li&gt;A feature plus the tests for it.&lt;/li&gt;
&lt;li&gt;Refactors where the order of steps matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a one-line fix, plain DeepSeek V4 Flash is usually the better pick. Under Fusion, even a tiny edit waits for exploration and a planning request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The plan request costs more.&lt;/strong&gt; Pro is the more expensive model, so its request uses more of your daily allowance than a Flash request. The &lt;code&gt;/model&lt;/code&gt; picker shows how fast each model uses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caches don't carry over.&lt;/strong&gt; pi's docs say switching models loses the prompt cache. Each switch between Flash and Pro sends the context without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shell commands aren't blocked.&lt;/strong&gt; Exploration needs &lt;code&gt;bash&lt;/code&gt;, so Fusion leaves it alone. A shell command can still change files before the plan exists. Keep your work in git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One plan per message.&lt;/strong&gt; Pro writes the plan once. If the build goes sideways, send a new message and Fusion starts over from explore.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Fusion is a small router on top of pi. Flash explores, Pro writes one plan into the todo list, and Flash builds it. Edits stay blocked until the plan exists, and failures fall back to Flash instead of stalling. Use it for multi-file work, and stay on Flash for quick fixes.&lt;/p&gt;

&lt;p&gt;Try it free with &lt;code&gt;npx free-pi-cli@latest&lt;/code&gt;, pick Fusion in &lt;code&gt;/model&lt;/code&gt;, and read more at &lt;a href="https://www.freepi.ai" rel="noopener noreferrer"&gt;www.freepi.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>devtools</category>
      <category>llm</category>
    </item>
    <item>
      <title>Doing authorized security work in the terminal with free abliterated models</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Tue, 03 Nov 2026 15:01:37 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/doing-authorized-security-work-in-the-terminal-with-free-abliterated-models-ple</link>
      <guid>https://dev.to/dennisonbertram/doing-authorized-security-work-in-the-terminal-with-free-abliterated-models-ple</guid>
      <description>&lt;p&gt;You paste a stack trace from your own app and ask how the bug could be exploited, so you can fix it properly. The model says no.&lt;/p&gt;

&lt;p&gt;I hit this a lot. Most coding models get nervous around anything that looks like an attack, even when the system is yours.&lt;/p&gt;

&lt;p&gt;This post shows how I do that kind of work in the terminal with FreePi, a free AI coding agent. It covers what abliterated models are, how to pick one, and two real workflows: auditing your own repo and analyzing software you're allowed to take apart. Everything here is for authorized work only.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; run &lt;code&gt;npx free-pi-cli@latest&lt;/code&gt; in a terminal. No card, no API key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Give this to your agent: &lt;a href="https://freepi.ai/llms.txt" rel="noopener noreferrer"&gt;freepi.ai/llms.txt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The ground rules first
&lt;/h2&gt;

&lt;p&gt;This only covers work you're allowed to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Systems and code you own.&lt;/li&gt;
&lt;li&gt;Employer or client systems where you have written permission to test.&lt;/li&gt;
&lt;li&gt;Bug bounty targets, inside the program's stated scope.&lt;/li&gt;
&lt;li&gt;CTFs and lab boxes built to be attacked.&lt;/li&gt;
&lt;li&gt;Software you own or may lawfully analyze.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A different model doesn't change what you're allowed to do. Laws like the US Computer Fraud and Abuse Act still apply to you. If you're not sure you're authorized, find out before you start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "abliterated" means
&lt;/h2&gt;

&lt;p&gt;An abliterated model is an open-weight model with its refusal behavior removed. It will help with the lawful parts of security work that general models often decline, like writing a proof of concept for a bug in your own handler.&lt;/p&gt;

&lt;p&gt;In FreePi I call these abliterated models for defense. They're opt-in. You only get one if you pick it.&lt;/p&gt;

&lt;p&gt;They are not smarter. They just refuse less. You still check everything they say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You need Node.js 22.19 or newer. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx free-pi-cli@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may start as a guest. The defense models need a GitHub sign-in, though. The server refuses them for guest sessions. Type &lt;code&gt;/login&lt;/code&gt; to sign in with GitHub. If you pick a defense model as a guest, it opens the sign-in for you.&lt;/p&gt;

&lt;p&gt;Run it from the project directory you want to work in. The agent reads, searches and edits files there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a model for defense
&lt;/h2&gt;

&lt;p&gt;Inside the agent, type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The picker is built from the server's model list when the CLI starts. The abliterated models for defense sit near the top, labeled &lt;code&gt;free-pi-defense&lt;/code&gt;. Today that list is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Abliterated Large Flash&lt;/li&gt;
&lt;li&gt;Abliterated Large V2&lt;/li&gt;
&lt;li&gt;DeepSeek V4.1 Flash Abliterated&lt;/li&gt;
&lt;li&gt;Qwen 3.6 Plus Uncensored&lt;/li&gt;
&lt;li&gt;Gemma 4 Uncensored&lt;/li&gt;
&lt;li&gt;GLM 4.7 Flash Heretic&lt;/li&gt;
&lt;li&gt;Uncensored 1.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list can change without a new CLI release. Whatever you see in &lt;code&gt;/model&lt;/code&gt; is current.&lt;/p&gt;

&lt;p&gt;My habit: start on the default model. Switch to a defense model only when the general one refuses the lawful part of the job. Then switch back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow 1: audit a repo you own
&lt;/h2&gt;

&lt;p&gt;FreePi bundles a security audit skill, based on Cloudflare's open-source security-audit skill. A skill is a set of instructions the agent loads for one kind of job. You can ask for it in plain words, or force it by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/skill:freepi-security-audit security audit this codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works in two modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guidance mode&lt;/strong&gt; is for a focused question, like "is this upload handler safe?" It uses the relevant parts of the method and writes no report files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full audit mode&lt;/strong&gt; runs when you ask for a full audit, a pen test of the codebase, or a report. It works in phases: map the architecture and trust boundaries, hunt for weaknesses, try to disprove each candidate, then write the results.&lt;/p&gt;

&lt;p&gt;A full run writes these files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;architecture.md&lt;/code&gt; and &lt;code&gt;coverage-ledger.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;findings.json&lt;/code&gt;, with each record marked &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;needs_validation&lt;/code&gt; or &lt;code&gt;rejected&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REPORT.md&lt;/code&gt;, &lt;code&gt;FINDINGS-DETAIL.md&lt;/code&gt; and &lt;code&gt;NEEDS-VALIDATION.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default they go outside your repo, in &lt;code&gt;~/security-audit-skill/&amp;lt;repo-name&amp;gt;/run-&amp;lt;N&amp;gt;&lt;/code&gt;. The skill is told never to edit your source.&lt;/p&gt;

&lt;p&gt;Two things to know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It runs as one agent. The same agent that finds an issue also tries to disprove it. So you are the second reviewer. Follow each confirmed finding into the code yourself.&lt;/li&gt;
&lt;li&gt;It validates by reading code. Before it runs anything from the target, it is told to ask you and show the exact command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I find it helps to state scope at the top of the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is my own repo. I own the code and the
staging server. Review src/api for auth bugs.
Read only. Ask before running any command.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workflow 2: look inside software you may analyze
&lt;/h2&gt;

&lt;p&gt;Sometimes the question is about a binary, not source. Say a CLI tool you're licensed to use, or an Electron app you shipped years ago and lost the source for.&lt;/p&gt;

&lt;p&gt;Type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/reverse-engineer-anything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loads REA (Reverse Engineer Anything) for the current session only. It gives the agent reverse-engineering tools for native binaries, JS and Electron apps, APKs, .NET assemblies and websites.&lt;/p&gt;

&lt;p&gt;A few practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first run downloads the tools, so it can take a minute.&lt;/li&gt;
&lt;li&gt;It needs Node 22.19+, 24.11+ or 26+. If your Node is older, it tells you.&lt;/li&gt;
&lt;li&gt;Deep native analysis needs your own Hopper, Ghidra or IDA install.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then ask in plain words. "List the modules this Go binary was built from, with versions." Or "show me this Electron app's fuses and IPC handlers."&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it safe on your machine
&lt;/h2&gt;

&lt;p&gt;The agent has real tools: &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;ls&lt;/code&gt;. In a normal session FreePi has no OS sandbox. &lt;code&gt;bash&lt;/code&gt; runs on your machine.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work in a container or a throwaway VM when you test anything risky.&lt;/li&gt;
&lt;li&gt;Never point it at production. Use a local or staging copy.&lt;/li&gt;
&lt;li&gt;Keep real credentials out of that environment.&lt;/li&gt;
&lt;li&gt;Keep your work under version control.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Esc&lt;/code&gt; to interrupt the agent if it heads somewhere you didn't ask for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And reproduce every finding yourself before you report it. A model can be confidently wrong, abliterated or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Abliterated models for defense won't do your security work for you. They stop the refusals that get in the way of lawful work on your own systems. Pair one with the audit skill for code, and with &lt;code&gt;/reverse-engineer-anything&lt;/code&gt; for binaries. Set your scope, work in a sandbox you control, and check every result.&lt;/p&gt;

&lt;p&gt;Try it free with &lt;code&gt;npx free-pi-cli@latest&lt;/code&gt;, and read more at &lt;a href="https://www.freepi.ai" rel="noopener noreferrer"&gt;www.freepi.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>I Let My AI Agent Create Its Own Databases. Here's How the Passwords Stay Locked Up.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:29:55 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/i-let-my-ai-agent-create-its-own-databases-heres-how-the-passwords-stay-locked-up-b8o</link>
      <guid>https://dev.to/dennisonbertram/i-let-my-ai-agent-create-its-own-databases-heres-how-the-passwords-stay-locked-up-b8o</guid>
      <description>&lt;p&gt;There's a moment in almost every project when the app needs to remember something. My agent has written the code and shipped it, and now it needs a database. For a long time, that was where I took over. I'd click around a dashboard, copy a password, paste it into a &lt;code&gt;.env&lt;/code&gt; file, and then paste it into the chat so the agent could use it.&lt;/p&gt;

&lt;p&gt;By the end of that, the password lived in four places, and I'd chosen none of them on purpose. So I started wondering what it would look like if the agent could just ask for a database, wire it up, and never show the password to anyone, including me.&lt;/p&gt;

&lt;p&gt;This post walks through how &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt; does that. It's an open-source tool I built that turns a Linux server into a place where AI agents can deploy apps and databases on their own. By the end you'll know how to create a Postgres or Redis database with one API call, connect it to an app, keep its password out of chat logs, and remove it cleanly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words first
&lt;/h2&gt;

&lt;p&gt;agentic-hosting is one program, &lt;code&gt;ah&lt;/code&gt;, that runs on your server. Agents talk to it over an HTTP API. Each agent (or team) is a tenant with its own API key, and an app it runs is called a service.&lt;/p&gt;

&lt;p&gt;The examples below use two shell variables: &lt;code&gt;AH_URL&lt;/code&gt; is your server's address and &lt;code&gt;AH_KEY&lt;/code&gt; is the tenant's API key. The commands use &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt;, which is also how an agent drives it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asking for a database
&lt;/h2&gt;

&lt;p&gt;One request creates a database. The type is &lt;code&gt;postgres&lt;/code&gt; or &lt;code&gt;redis&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;IDEM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# reuse this on retries&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/databases"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Idempotency-Key: &lt;/span&gt;&lt;span class="nv"&gt;$IDEM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"notes-db","type":"postgres"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call doesn't come back until the database actually works. &lt;code&gt;ah&lt;/code&gt; pulls the image (&lt;code&gt;postgres:16-alpine&lt;/code&gt; or &lt;code&gt;redis:7-alpine&lt;/code&gt;), starts a container, and then checks readiness by speaking the database's own protocol, not just by seeing an open port. Once the image is on the box this takes about 30 seconds. On a fresh server the pull can take longer, so the route allows up to six minutes.&lt;/p&gt;

&lt;p&gt;That's why the &lt;code&gt;Idempotency-Key&lt;/code&gt; header matters. Agents retry. If the connection drops halfway and the agent sends the same request with the same key, &lt;code&gt;ah&lt;/code&gt; waits for the first one to finish and hands back its result, instead of making a second database. It remembers a successful result for 10 minutes.&lt;/p&gt;

&lt;p&gt;You get back a &lt;code&gt;201&lt;/code&gt; with something like this (trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3f9c0a…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notes-db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ah-db-3f9c0a…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"db_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"connection_string"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres://ah:…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Names start with a letter, can use letters, numbers, hyphens, and underscores, and can be up to 63 characters. For Postgres, the database and user are both called &lt;code&gt;ah&lt;/code&gt;. The password is 32 random bytes written as 64 hex characters. Nobody picks it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the password goes
&lt;/h2&gt;

&lt;p&gt;This is the part I care about most. Before anything is saved, &lt;code&gt;ah&lt;/code&gt; encrypts both the password and the full connection string with AES-256-GCM, using the server's master key. Only the encrypted versions go into &lt;code&gt;ah&lt;/code&gt;'s own database.&lt;/p&gt;

&lt;p&gt;The plain connection string shows up in exactly two places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the response to the create call&lt;/li&gt;
&lt;li&gt;a dedicated endpoint for fetching it
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/databases/&lt;/span&gt;&lt;span class="nv"&gt;$DB_ID&lt;/span&gt;&lt;span class="s2"&gt;/connection-string"&lt;/span&gt;
&lt;span class="c"&gt;# {"connection_string":"postgres://ah:…"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every call to that endpoint writes an audit line to the server log with the tenant, the database, and the ID of the API key that asked. Listing or reading the database (&lt;code&gt;GET /v1/databases&lt;/code&gt; and &lt;code&gt;GET /v1/databases/{id}&lt;/code&gt;) never includes it.&lt;/p&gt;

&lt;p&gt;Two honest notes. First, there's one master key for the whole server, and right now it encrypts everything directly: env vars, database passwords, connection strings. A design for deriving a separate key for each of those uses is in the repo but not built yet. Tenants are kept apart by the API, which scopes every lookup to the tenant's own records, not by separate keys. Second, "encrypted at rest" here means the credentials &lt;code&gt;ah&lt;/code&gt; stores. The rows inside your Postgres sit in a normal Docker volume, and traffic between your app and the database is plain TCP (&lt;code&gt;sslmode=disable&lt;/code&gt;) on a private network that only your services can join.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into an app
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ah&lt;/code&gt; doesn't add &lt;code&gt;DATABASE_URL&lt;/code&gt; to your app on its own. You set it, then restart. Those are two calls, and the connection string goes straight from one to the other without being printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CONN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/databases/&lt;/span&gt;&lt;span class="nv"&gt;$DB_ID&lt;/span&gt;&lt;span class="s2"&gt;/connection-string"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .connection_string&lt;span class="si"&gt;)&lt;/span&gt;

jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; u &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'{DATABASE_URL: $u}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC_ID&lt;/span&gt;&lt;span class="s2"&gt;/env"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; @-

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC_ID&lt;/span&gt;&lt;span class="s2"&gt;/restart"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting env merges new keys into what's there. The response is only &lt;code&gt;{"status":"updated", ...}&lt;/code&gt;, with no values in it. Environment variables are encrypted with the same master key. If you read them back, every value comes back as &lt;code&gt;********&lt;/code&gt; unless you add &lt;code&gt;?reveal=true&lt;/code&gt;, and a reveal is also written to the audit log.&lt;/p&gt;

&lt;p&gt;The restart matters more than it looks. It recreates the container, which is the only way new environment variables take effect, and it's also when the app gets connected to its database. More on that next.&lt;/p&gt;

&lt;p&gt;For Redis it's the same flow. Use &lt;code&gt;REDIS_URL&lt;/code&gt; as the name by convention, and the string looks like &lt;code&gt;redis://:&amp;lt;password&amp;gt;@ah-db-&amp;lt;id&amp;gt;:6379/0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the app finds the database
&lt;/h2&gt;

&lt;p&gt;Look at the host in the connection string: &lt;code&gt;ah-db-&amp;lt;id&amp;gt;&lt;/code&gt;, not an IP address. That's on purpose, and it's something I got wrong at first.&lt;/p&gt;

&lt;p&gt;Early versions handed out &lt;code&gt;127.0.0.1:&amp;lt;port&amp;gt;&lt;/code&gt;. That looks fine until you remember the app runs in its own container, where &lt;code&gt;127.0.0.1&lt;/code&gt; is the container itself. The app was knocking on its own door. It was reported as &lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues/164" rel="noopener noreferrer"&gt;issue #164&lt;/a&gt; and fixed in October.&lt;/p&gt;

&lt;p&gt;Now each database gets its own private Docker network, named &lt;code&gt;ah-dbnet-&amp;lt;tenant&amp;gt;-&amp;lt;db&amp;gt;&lt;/code&gt;, with no route to the internet. On that network the database answers to the name &lt;code&gt;ah-db-&amp;lt;id&amp;gt;&lt;/code&gt; on its normal port, 5432 or 6379. When a service is deployed, started, or restarted, &lt;code&gt;ah&lt;/code&gt; looks at its environment variables. If one of them mentions one of that tenant's databases, &lt;code&gt;ah&lt;/code&gt; connects the service to that database's network.&lt;/p&gt;

&lt;p&gt;Two details follow from this. Only the tenant's own databases are checked, so another tenant can't get in by copying your connection string into its app. And the name only works from inside a service container. Running &lt;code&gt;psql&lt;/code&gt; against it from the server itself won't resolve, and that's expected.&lt;/p&gt;

&lt;p&gt;If you're upgrading from an older version, &lt;code&gt;ah&lt;/code&gt; rewrites the old &lt;code&gt;127.0.0.1&lt;/code&gt; strings on startup, both on the database and in any service env that copied it. Running apps still need one restart to pick up the new value. The fix is covered by tests in the repo, and the design note still lists a check on a live gVisor host as a to-do. If something doesn't connect for you, I'd like to &lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues" rel="noopener noreferrer"&gt;hear about it&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or let your agent do it
&lt;/h2&gt;

&lt;p&gt;All of the above is one command with &lt;code&gt;ahc&lt;/code&gt;, the project's command-line client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ahc db provision notes-api postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a database named &lt;code&gt;notes-api-postgres&lt;/code&gt; (pick another with &lt;code&gt;--name&lt;/code&gt;), waits for it, sets &lt;code&gt;DATABASE_URL&lt;/code&gt; (or &lt;code&gt;REDIS_URL&lt;/code&gt; for Redis) on the service, and restarts it. It prints only the host, not the password. If you use Claude Code, the repo also ships an &lt;code&gt;/ah-db&lt;/code&gt; slash command that does the same steps and is told never to print the full connection string.&lt;/p&gt;

&lt;p&gt;In practice I mostly say "this app needs Postgres" and let the agent pick one of those.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits
&lt;/h2&gt;

&lt;p&gt;This is deliberately one simple path, not a database service with every option:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Engines&lt;/td&gt;
&lt;td&gt;Postgres 16, Redis 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Databases per tenant&lt;/td&gt;
&lt;td&gt;3 by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory per database&lt;/td&gt;
&lt;td&gt;512 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;gVisor, all Linux capabilities dropped except 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;blocked at 90% full&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Going past the quota returns a &lt;code&gt;409&lt;/code&gt;. If the disk is 90% full or more, creates fail with a &lt;code&gt;503&lt;/code&gt; and a &lt;code&gt;Retry-After&lt;/code&gt; header, so the agent knows to wait instead of retrying in a loop. There's no MySQL, no custom images, and no replicas or failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backups are a job for your agent
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ah&lt;/code&gt; keeps your data in a Docker volume named &lt;code&gt;ah-db-&amp;lt;id&amp;gt;&lt;/code&gt;, but it doesn't back that volume up. Its own backup command covers its control-plane state, not your app's rows. This is the part I hand to my agent, because it's a well-defined chore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every night, dump each Postgres database
with pg_dump inside its container, gzip it,
copy it to my backup host, and keep 7 days.
Restore last night's dump into a scratch
database once a week to prove it works.
Alert me if a dump is missing or empty.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent writes the timer, the script, and the restore check, and I review them once. The big platforms used to sell redundancy, backups, and monitoring as features. An agent can build the same pieces on your own server, and you can read exactly what it built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;Delete in the opposite order you created. Point the app away from the database first, so it isn't left with a broken URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC_ID&lt;/span&gt;&lt;span class="s2"&gt;/env/DATABASE_URL"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC_ID&lt;/span&gt;&lt;span class="s2"&gt;/restart"&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/databases/&lt;/span&gt;&lt;span class="nv"&gt;$DB_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delete returns &lt;code&gt;204&lt;/code&gt;. &lt;code&gt;ah&lt;/code&gt; stops and removes the container, then overwrites every file in the volume with zeros, so a later tenant on the same disk can't recover the data. Then it removes the volume, the record, and the private network. With the CLI it's &lt;code&gt;ahc db delete notes-db --confirm&lt;/code&gt;, and the &lt;code&gt;--confirm&lt;/code&gt; is required.&lt;/p&gt;

&lt;p&gt;If something is left behind anyway, a background cleanup loop removes &lt;code&gt;ah-db-*&lt;/code&gt; volumes that no database record owns, and database networks nothing owns once they're 15 minutes old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;An agent on agentic-hosting gets a working Postgres or Redis database with one call. The password is random, encrypted before it's stored, and only handed out through one audited endpoint, and the app reaches the database over a private network that's set up when it restarts. Pair it with a nightly dump your agent writes, and delete it cleanly when you're done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Try the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, or read the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/databases/" rel="noopener noreferrer"&gt;Postgres &amp;amp; Redis page&lt;/a&gt; on the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if you'd want built-in database backups or separate encryption keys next. I'd like to know which one you'd use first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Cover photo: storage tanks of the National Molasses Company on the Houston Ship Channel, with refineries behind. Blair Pittman, June 1973, for the EPA's Documerica project. U.S. National Archives, public domain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>selfhosted</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>My Hosting Platform Runs on Two SQLite Files. Here's How I Back Them Up and Bring Them Back.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Tue, 27 Oct 2026 14:17:48 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/my-hosting-platform-runs-on-two-sqlite-files-heres-how-i-back-them-up-and-bring-them-back-51h</link>
      <guid>https://dev.to/dennisonbertram/my-hosting-platform-runs-on-two-sqlite-files-heres-how-i-back-them-up-and-bring-them-back-51h</guid>
      <description>&lt;p&gt;When people hear that the thing running my server keeps all its state in SQLite, they usually ask the same question. Isn't that a toy database? Shouldn't a hosting platform sit on Postgres, with a replica and a team that knows how to fail it over?&lt;/p&gt;

&lt;p&gt;I went back and forth on this for a while. Where I ended up is that the database itself was never the scary part. The scary part is a question nobody likes to answer about their own setup. If this machine disappeared tonight, how long would it take to get everything back? With a couple of small files, the honest answer can be "a few minutes", and you can prove it by doing it.&lt;/p&gt;

&lt;p&gt;This post walks through how &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt; stores its state. It's an open-source tool I built that turns a Linux server into a place where AI agents can deploy apps and databases on their own. By the end you'll know where its data lives, how to take a safe backup while it's running, what else you need to copy, and how to restore everything on a fresh server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words first
&lt;/h2&gt;

&lt;p&gt;agentic-hosting is one program, &lt;code&gt;ah&lt;/code&gt;, that runs on your server. Agents talk to it over an HTTP API to deploy apps, add Postgres or Redis, and read logs. &lt;code&gt;ah&lt;/code&gt; is the control plane. It doesn't run your apps itself. It keeps track of them and tells Docker what to do.&lt;/p&gt;

&lt;p&gt;That record keeping is what this post is about. Every account, API key, app, build, environment variable, and database &lt;code&gt;ah&lt;/code&gt; knows about is a row in SQLite, a database that lives in a single file on disk. There's no database server to install or keep running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the data lives
&lt;/h2&gt;

&lt;p&gt;On a standard install, everything sits in &lt;code&gt;/var/lib/ah&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/ah/
├── ah.db            # state: tenants, keys,
│                    # services, builds, env
├── ah-metering.db   # usage samples
├── master.key       # encryption key
└── backups/         # output of `ah backup`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two database files, not one, and I'll come back to why. You'll also see &lt;code&gt;ah.db-wal&lt;/code&gt; and &lt;code&gt;ah.db-shm&lt;/code&gt; next to them while &lt;code&gt;ah&lt;/code&gt; is running. Those belong to SQLite, and they matter a lot for backups.&lt;/p&gt;

&lt;p&gt;You can look inside with the regular &lt;code&gt;sqlite3&lt;/code&gt; tool. This lists the schema changes the database has been through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sqlite3 /var/lib/ah/ah.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'SELECT name FROM schema_migrations;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ah&lt;/code&gt; ships its schema as numbered SQL files built into the binary. On startup it applies any it hasn't recorded yet, each one in its own transaction along with the row that records it. Right now that's 26 files for the state database and 2 for metering. When you upgrade, the new binary applies its new files the first time it starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SQLite holds up here
&lt;/h2&gt;

&lt;p&gt;Three settings do most of the work. &lt;code&gt;ah&lt;/code&gt; opens both files like this (wrapped to fit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqlite3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s"&gt;"?_journal_mode=WAL"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s"&gt;"&amp;amp;_busy_timeout=5000"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s"&gt;"&amp;amp;_foreign_keys=on"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s"&gt;"&amp;amp;_txlock=immediate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WAL mode.&lt;/strong&gt; By default SQLite writes changes straight into the main file, and readers wait while that happens. In WAL (write-ahead log) mode, new writes go to the &lt;code&gt;-wal&lt;/code&gt; file first and get folded into the main file later. Readers keep reading while a write is in progress. For a control plane, where an agent polling build status shouldn't stall another agent's deploy, this is the setting that matters most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A busy timeout.&lt;/strong&gt; SQLite still allows only one writer at a time. With &lt;code&gt;_busy_timeout=5000&lt;/code&gt;, a second writer waits up to 5 seconds for its turn instead of failing at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take the write lock up front.&lt;/strong&gt; This one took me a while to understand. A normal SQLite transaction starts as a reader and asks for the write lock only when it first writes. If another connection committed in between, SQLite returns an error right away and the busy timeout never kicks in. &lt;code&gt;_txlock=immediate&lt;/code&gt; makes every transaction ask for the write lock at the start, so it waits its turn like everyone else. The repo has a test for exactly this case.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ah&lt;/code&gt; also caps each file at 4 open connections, which is plenty for one server. And one server is the real limit of this design. Everything assumes one machine, and the docs list multi-node as a known gap. I'm fine with that. One machine with good backups is a setup I can understand at 2am.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two files
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ah.db&lt;/code&gt; holds the things you'd be upset to lose: accounts, keys, apps, builds, encrypted environment variables, and database passwords. &lt;code&gt;ah-metering.db&lt;/code&gt; holds usage. Every 60 seconds &lt;code&gt;ah&lt;/code&gt; records a CPU and memory sample for each running app, in two tables, &lt;code&gt;usage_events&lt;/code&gt; and &lt;code&gt;usage_daily&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The way I think about it, those are different kinds of data. Metering writes on a timer, forever, and it grows with every app you run. State changes when something actually happens. Since SQLite allows one writer per file, putting the steady stream of samples in its own file means it never queues behind, or in front of, a deploy. The split also shows up in backups. If the metering backup fails, &lt;code&gt;ah backup&lt;/code&gt; logs a warning and carries on. If the state backup fails, the whole run fails.&lt;/p&gt;

&lt;p&gt;The code keeps the two apart on purpose. Each migration file has to start with &lt;code&gt;state_&lt;/code&gt; or &lt;code&gt;metering_&lt;/code&gt;, and &lt;code&gt;ah&lt;/code&gt; refuses to start if one doesn't, so a schema change can't end up in the wrong database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking a backup while it's running
&lt;/h2&gt;

&lt;p&gt;Here's the trap with WAL mode. Recent writes can sit in &lt;code&gt;ah.db-wal&lt;/code&gt; for a while before they reach &lt;code&gt;ah.db&lt;/code&gt;. If you &lt;code&gt;cp ah.db&lt;/code&gt; somewhere, you can get a file that's missing recent changes, or one that's inconsistent.&lt;/p&gt;

&lt;p&gt;SQLite has a command for this, &lt;code&gt;VACUUM INTO&lt;/code&gt;. It reads the database through SQLite itself, WAL included, and writes a clean standalone copy to a new file. It works on a read-only connection, so the backup never takes the write lock away from the running server.&lt;/p&gt;

&lt;p&gt;That's what &lt;code&gt;ah backup&lt;/code&gt; does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; ah /usr/local/bin/ah backup &lt;span class="se"&gt;\&lt;/span&gt;
  /var/lib/ah/ah.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step by step, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;creates &lt;code&gt;backups/&lt;/code&gt; next to the database, readable only by its owner&lt;/li&gt;
&lt;li&gt;stops if that disk is 90% full or more, and warns at 80%&lt;/li&gt;
&lt;li&gt;opens &lt;code&gt;ah.db&lt;/code&gt; read-only and runs &lt;code&gt;VACUUM INTO&lt;/code&gt; a temp file, with a 5 minute timeout&lt;/li&gt;
&lt;li&gt;gzips the copy to &lt;code&gt;ah-YYYYMMDD-HHMMSS.db.gz&lt;/code&gt;, writing to a &lt;code&gt;.tmp&lt;/code&gt; file and renaming it at the end, so a half-written backup never has a real name&lt;/li&gt;
&lt;li&gt;does the same for &lt;code&gt;ah-metering.db&lt;/code&gt; if it exists&lt;/li&gt;
&lt;li&gt;keeps the 10 newest state backups and the 10 newest metering backups, and deletes older ones&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Afterwards you'll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/ah/backups/
├── ah-20261027-091700.db.gz
└── ah-metering-20261027-091700.db.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick sanity check that a backup is a real SQLite file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'gunzip -c /var/lib/ah/backups/ah-2*.db.gz'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 15
&lt;span class="c"&gt;# SQLite format 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run it every hour, a cron entry is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# /etc/cron.d/ah-backup
17 * * * * ah /usr/local/bin/ah backup /var/lib/ah/ah.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the backup doesn't include
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.db.gz&lt;/code&gt; files aren't everything you need:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Why you need it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;master.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;decrypts env vars and DB passwords&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/etc/default/ah&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bootstrap token and settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker volumes&lt;/td&gt;
&lt;td&gt;your Postgres and Redis data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local registry&lt;/td&gt;
&lt;td&gt;images you built on the box&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The master key is the big one. Secrets in &lt;code&gt;ah.db&lt;/code&gt; are encrypted with it, and &lt;code&gt;ah backup&lt;/code&gt; never writes it into a backup. Store it somewhere other than your database backups, because anyone holding both can read your secrets.&lt;/p&gt;

&lt;p&gt;One more detail. If you rotate the key with &lt;code&gt;ah rotate-key&lt;/code&gt;, older backups still need the old key. Keep each key for as long as you keep backups made with it.&lt;/p&gt;

&lt;p&gt;And your apps' actual data, the rows in the Postgres databases &lt;code&gt;ah&lt;/code&gt; created for you, lives in Docker volumes named &lt;code&gt;ah-db-*&lt;/code&gt;. That needs its own backup. The SQLite file only knows those databases exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting copies off the box
&lt;/h2&gt;

&lt;p&gt;A backup that lives on the machine it's protecting doesn't protect much. This is the kind of chore I hand to my agent. A prompt like this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every hour, after ah backup runs, rsync
/var/lib/ah/backups/ to my backup host.
Copy master.key to my secrets manager once,
and again after any key rotation. Alert me
if the newest backup is more than 2 hours
old.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent writes the timer, the copy script, and the check. I review them once. The big platforms used to give you redundancy, backups, and monitoring as a feature. Now an agent can build the same pieces on your own box in an afternoon.&lt;/p&gt;

&lt;p&gt;If an hour of possible loss is too much, the repo has an optional &lt;a href="https://litestream.io/" rel="noopener noreferrer"&gt;Litestream&lt;/a&gt; setup (&lt;code&gt;deploy/litestream.yml&lt;/code&gt; and a runbook in &lt;code&gt;docs/ops/&lt;/code&gt;) that streams both databases to S3-compatible storage as they change. &lt;code&gt;ah&lt;/code&gt; doesn't install or manage it. You run it as its own service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing it back
&lt;/h2&gt;

&lt;p&gt;There's no &lt;code&gt;ah restore&lt;/code&gt; command. A restore is a few file operations, which I like, because there's nothing extra that has to work on the worst day. On the new server, install &lt;code&gt;ah&lt;/code&gt; the normal way and copy over &lt;code&gt;/etc/default/ah&lt;/code&gt;. Put the backup files in &lt;code&gt;/var/lib/ah/backups/&lt;/code&gt; and the key in your home folder. Then, as root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl stop ah
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/lib/ah
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; pre-restore
&lt;span class="nb"&gt;mv &lt;/span&gt;ah.db&lt;span class="k"&gt;*&lt;/span&gt; ah-metering.db&lt;span class="k"&gt;*&lt;/span&gt; pre-restore/

&lt;span class="nv"&gt;TS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20261027-091700
&lt;span class="nb"&gt;gunzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; backups/ah-&lt;span class="nv"&gt;$TS&lt;/span&gt;.db.gz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ah.db
&lt;span class="nb"&gt;gunzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; backups/ah-metering-&lt;span class="nv"&gt;$TS&lt;/span&gt;.db.gz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ah-metering.db
&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 600 &lt;span class="nt"&gt;-o&lt;/span&gt; ah &lt;span class="nt"&gt;-g&lt;/span&gt; ah &lt;span class="se"&gt;\&lt;/span&gt;
  ~/master.key master.key

&lt;span class="nb"&gt;chown &lt;/span&gt;ah:ah ah.db ah-metering.db
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ah.db ah-metering.db
systemctl start ah
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving the old &lt;code&gt;-wal&lt;/code&gt; and &lt;code&gt;-shm&lt;/code&gt; files out of the way is the step people skip. If a stale WAL file sits next to the restored database, SQLite can try to replay it into the restored file. If you skip the metering file, &lt;code&gt;ah&lt;/code&gt; starts with an empty one and you lose usage history, nothing else.&lt;/p&gt;

&lt;p&gt;Then check it's up. The API wants HTTPS, so a local request has to say it came through the proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-Forwarded-Proto: https'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:8080/v1/system/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you restored with the wrong &lt;code&gt;master.key&lt;/code&gt;, you'll know right away. On startup &lt;code&gt;ah&lt;/code&gt; decrypts a key it uses to check API keys, and if that fails it refuses to start, with &lt;code&gt;wrong master key?&lt;/code&gt; in the error in &lt;code&gt;journalctl -u ah&lt;/code&gt;. I like that it fails loudly. A server that started and then quietly rejected every API key would be much worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens to the apps
&lt;/h2&gt;

&lt;p&gt;The restored database describes apps that may not be running on the new machine. &lt;code&gt;ah&lt;/code&gt; doesn't trust those rows. Every 30 seconds a loop called the reconciler compares the database with what Docker actually has. An app marked &lt;code&gt;running&lt;/code&gt; with no container gets marked &lt;code&gt;crashed&lt;/code&gt;, with the reason &lt;code&gt;container not found by reconciler&lt;/code&gt;. Nothing restarts on its own.&lt;/p&gt;

&lt;p&gt;To bring an app back, redeploy it with &lt;code&gt;ahc&lt;/code&gt;, the project's command-line client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ahc service redeploy my-api
ahc status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A redeploy starts the app again from the image it already had. If that image was built on the old server and you didn't copy the local registry, it won't be there. Start a fresh build from git instead, the same way the app was deployed the first time.&lt;/p&gt;

&lt;p&gt;Or tell your agent "the server was restored, redeploy everything that was running." It can list the services, see which ones are marked &lt;code&gt;crashed&lt;/code&gt;, and work through them one at a time.&lt;/p&gt;

&lt;p&gt;Then do the test that actually matters. Open an app that uses a secret, like a database password, and make sure it works. That's the proof that the key and the database belong together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;agentic-hosting keeps its whole control plane in two SQLite files: &lt;code&gt;ah.db&lt;/code&gt; for state and &lt;code&gt;ah-metering.db&lt;/code&gt; for usage. WAL mode keeps reads and writes out of each other's way, &lt;code&gt;ah backup&lt;/code&gt; uses &lt;code&gt;VACUUM INTO&lt;/code&gt; to take consistent copies while the server runs, and a restore is just putting the files and the master key back. Have your agent ship the copies off the box, and run the restore once on a scratch server before you need it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Try the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, or read the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/backups/" rel="noopener noreferrer"&gt;backups page&lt;/a&gt; on the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if you'd want a built-in &lt;code&gt;ah restore&lt;/code&gt; or volume backups for managed Postgres. I'd like to know which one you'd use first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Cover photo: seats and fittings from old Amtrak passenger cars piled in a warehouse at Mira Loma, California, waiting to be renovated or replaced. Charles O'Rear, May 1974, for the EPA's Documerica project. U.S. National Archives, public domain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>devops</category>
      <category>selfhosted</category>
      <category>ai</category>
    </item>
    <item>
      <title>When your coding model refuses your own AppSec work</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Mon, 26 Oct 2026 17:33:10 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/when-your-coding-model-refuses-your-own-appsec-work-139c</link>
      <guid>https://dev.to/dennisonbertram/when-your-coding-model-refuses-your-own-appsec-work-139c</guid>
      <description>&lt;p&gt;If you do application security, you have probably watched a coding model decline something routine: writing a proof of concept for a bug in your own staging app, or explaining how a flaw in your own handler would be exploited so you can fix it. That leaves the defender with less help than an attacker would have.&lt;/p&gt;

&lt;p&gt;I work on free-pi, a free terminal coding agent. We are adding an opt-in &lt;strong&gt;Defense&lt;/strong&gt; section to the &lt;code&gt;/model&lt;/code&gt; picker for signed-in GitHub users. The first model is &lt;strong&gt;Defense Flash&lt;/strong&gt;: a reasoning model with tool use and a 1M-token context, chosen for lawful security work that general models often refuse. It is billed from normal credits like any other model. We have not published tests of it, so this post is about using it responsibly, not a benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ground rules
&lt;/h2&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;systems and code you own,&lt;/li&gt;
&lt;li&gt;employer or client systems where you have written permission to test,&lt;/li&gt;
&lt;li&gt;in-scope bug bounty targets, following the program's rules,&lt;/li&gt;
&lt;li&gt;software you own or may lawfully analyze (free-pi's &lt;code&gt;/reverse-engineer-anything&lt;/code&gt; command covers native binaries, JS and Electron apps, APKs, .NET and websites).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use it on anyone else's systems. Computer misuse laws apply whichever model you pick, and a bounty program's scope list is a boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habits that help
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;State the context: who owns the system and what you are authorized to do.&lt;/li&gt;
&lt;li&gt;Aim at find-and-fix: ask for the minimal patch and a regression test.&lt;/li&gt;
&lt;li&gt;Test in isolation: a local container or disposable staging copy, never production, no real credentials. free-pi has no OS sandbox for normal sessions.&lt;/li&gt;
&lt;li&gt;Verify claims: reproduce a reported flaw before you file it, and review every generated patch.&lt;/li&gt;
&lt;li&gt;Keep the scope in the session: for bounty work, paste the program's scope text.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pairing
&lt;/h2&gt;

&lt;p&gt;free-pi bundles the audit method and checklists from the &lt;a href="https://github.com/cloudflare/security-audit-skill" rel="noopener noreferrer"&gt;security-audit skill&lt;/a&gt;, run in a single agent, so validate each finding yourself. &lt;code&gt;/reverse-engineer-anything&lt;/code&gt; covers software you may lawfully analyze. The Defense section is where to look when a general model declines the lawful part.&lt;/p&gt;

&lt;p&gt;More at &lt;a href="https://freepi.ai/blog/using-defense-mode-responsibly" rel="noopener noreferrer"&gt;freepi.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A practical CI and supply-chain hardening checklist you can run with a terminal agent</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Mon, 26 Oct 2026 17:32:14 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/a-practical-ci-and-supply-chain-hardening-checklist-you-can-run-with-a-terminal-agent-11h5</link>
      <guid>https://dev.to/dennisonbertram/a-practical-ci-and-supply-chain-hardening-checklist-you-can-run-with-a-terminal-agent-11h5</guid>
      <description>&lt;p&gt;Your CI is code that runs with your secrets and publishes what your users install. Cloudflare's open-source &lt;a href="https://github.com/cloudflare/security-audit-skill" rel="noopener noreferrer"&gt;security-audit skill&lt;/a&gt; includes a &lt;code&gt;SUPPLY-CHAIN-AND-RELEASE.md&lt;/code&gt; reference whose core rules are useful questions to ask your own pipeline, even without running a full review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A known-vulnerable or mutable dependency is not a finding by itself. Ask who can influence resolution and what the build does next.&lt;/li&gt;
&lt;li&gt;Follow integrity across every handoff, from source to the artifact a user runs.&lt;/li&gt;
&lt;li&gt;CI configuration is authorization code: which event triggered it, whose code runs, which secrets exist, what can it publish?&lt;/li&gt;
&lt;li&gt;A checksum fetched from the same untrusted place as the artifact proves nothing independent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I work on free-pi, a free terminal agent that bundles the skill's audit method and reference checklists. It runs them in a single agent, without sub-agents or an OS sandbox, so check every claim the agent makes yourself. This guide is illustrative, not a recorded run. Only review pipelines you own or have permission to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for a read-only review first
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;review my CI and release setup for supply-chain risks, using the
supply-chain reference in the security-audit skill. Do not change files yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seven common GitHub Actions checks
&lt;/h2&gt;

&lt;p&gt;These are general good practice, not a list from the skill. You can verify each one yourself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unpinned actions: pin &lt;code&gt;uses:&lt;/code&gt; to a full commit SHA.&lt;/li&gt;
&lt;li&gt;Broad token permissions: set a top-level &lt;code&gt;permissions:&lt;/code&gt; with the least needed.&lt;/li&gt;
&lt;li&gt;Untrusted input in &lt;code&gt;run:&lt;/code&gt; steps: PR titles and branch names can inject shell commands; pass them via env vars and quote.&lt;/li&gt;
&lt;li&gt;Risky triggers: &lt;code&gt;pull_request_target&lt;/code&gt; and &lt;code&gt;workflow_run&lt;/code&gt; can run with secrets while touching fork code.&lt;/li&gt;
&lt;li&gt;Secret reach: limit which jobs can read publish tokens; prefer short-lived credentials.&lt;/li&gt;
&lt;li&gt;Provenance: know which commit built each published artifact, and let users verify it.&lt;/li&gt;
&lt;li&gt;Lockfiles and install scripts: install from committed lockfiles and review packages with install scripts.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fix one thing per PR
&lt;/h2&gt;

&lt;p&gt;Ask the agent for one change at a time and review each diff yourself. CI files guard your secrets, so do not merge what you do not understand, and run the workflow on a branch first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cannot see
&lt;/h2&gt;

&lt;p&gt;Branch protection, registry permissions, hosted-runner settings and signing-service configuration live outside the repo. Check them in your hosting and registry settings.&lt;/p&gt;

&lt;p&gt;free-pi has an opt-in Defense model section in &lt;code&gt;/model&lt;/code&gt; for when a general model declines lawful work on your own pipeline. More at &lt;a href="https://freepi.ai/blog/harden-ci-and-supply-chain-with-a-terminal-agent" rel="noopener noreferrer"&gt;freepi.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>github</category>
      <category>cicd</category>
    </item>
    <item>
      <title>A structured security review of your own repo, from the terminal</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Mon, 26 Oct 2026 17:32:13 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/a-structured-security-review-of-your-own-repo-from-the-terminal-jh4</link>
      <guid>https://dev.to/dennisonbertram/a-structured-security-review-of-your-own-repo-from-the-terminal-jh4</guid>
      <description>&lt;p&gt;Most "AI security review" prompts give you a pile of plausible-sounding warnings. Cloudflare's open-source &lt;a href="https://github.com/cloudflare/security-audit-skill" rel="noopener noreferrer"&gt;security-audit skill&lt;/a&gt; (MIT) describes a more disciplined method: map the code, hunt by coverage, try to disprove every candidate, and write findings as structured records.&lt;/p&gt;

&lt;p&gt;I work on free-pi, a free terminal coding agent that bundles the skill's audit method and reference checklists. This post explains the method and how to check its output. It is a guide, not a recorded run, and the commands are illustrative. Only review code you own or have permission to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six phases in the skill's README
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reconnaissance: map architecture, trust boundaries and input surfaces into &lt;code&gt;architecture.md&lt;/code&gt; and &lt;code&gt;coverage-ledger.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Coverage-led hunting: work through units from the ledger.&lt;/li&gt;
&lt;li&gt;Candidate validation: try to disprove each candidate.&lt;/li&gt;
&lt;li&gt;Structured output: &lt;code&gt;findings.json&lt;/code&gt; with &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;needs_validation&lt;/code&gt; and &lt;code&gt;rejected&lt;/code&gt; records, checked against a JSON schema.&lt;/li&gt;
&lt;li&gt;Record verification: re-check source claims.&lt;/li&gt;
&lt;li&gt;Reporting: &lt;code&gt;REPORT.md&lt;/code&gt;, &lt;code&gt;FINDINGS-DETAIL.md&lt;/code&gt; and &lt;code&gt;NEEDS-VALIDATION.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The skill also has reference files of attack classes for web and auth protocols, client-side code, supply chain, cloud deployment, memory safety and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What free-pi does differently from the skill's design
&lt;/h2&gt;

&lt;p&gt;The skill's README expects an agent that can run parallel sub-agents, so the agent that finds a problem is never the agent that checks it, plus an OS-enforced sandbox for running target code. free-pi has neither. It runs the skill in a single agent that follows the method itself.&lt;/p&gt;

&lt;p&gt;So the independent checking in the skill's design does not happen. The same agent raises and judges each candidate, and you should validate every finding yourself. Do not ask it to build or run code from a repo you do not trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting it
&lt;/h2&gt;

&lt;p&gt;From your repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;security audit this codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A direct request asks for the full method; narrower questions get lighter guidance. The skill's default output directory is &lt;code&gt;~/security-audit-skill/&amp;lt;repo-name&amp;gt;/run-&amp;lt;N&amp;gt;&lt;/code&gt;, outside your repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;confirmed&lt;/code&gt;: follow the source trace in the code yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;needs_validation&lt;/code&gt;: each should name one unknown fact, and you often know the answer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rejected&lt;/code&gt;: skim to see what was set aside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skill's authors say a single run found roughly half of what repeated runs found, so run it again after your first round of fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;It reads code. It cannot see your cloud settings, registry permissions or branch protection. Treat every finding as a lead to verify, not a verdict.&lt;/p&gt;

&lt;p&gt;To try it: &lt;code&gt;npx free-pi-cli&lt;/code&gt;, sign in with GitHub, and ask for a security audit. More at &lt;a href="https://freepi.ai/blog/audit-your-repo-with-security-audit-skill" rel="noopener noreferrer"&gt;freepi.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Taught My Coding Agent to Run My Server With One Text File. Here's How to Write One for Yours.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Sun, 25 Oct 2026 14:14:09 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/i-taught-my-coding-agent-to-run-my-server-with-one-text-file-heres-how-to-write-one-for-yours-1p07</link>
      <guid>https://dev.to/dennisonbertram/i-taught-my-coding-agent-to-run-my-server-with-one-text-file-heres-how-to-write-one-for-yours-1p07</guid>
      <description>&lt;p&gt;For a while I had a ritual. Every time I opened a new chat with my coding agent, I pasted in the same paragraph: here's my server's address, here's how you deploy, the build field is called this and not that, don't do the other thing. If I forgot, the agent did what any smart newcomer does. It guessed. It guessed field names that sounded right and weren't. It called endpoints that should exist and don't.&lt;/p&gt;

&lt;p&gt;The agent wasn't the problem. It had never seen my server. Nobody had given it the manual.&lt;/p&gt;

&lt;p&gt;That paragraph is now a file. It sits in a folder on my laptop, the agent reads it when the conversation turns to my server, and I haven't pasted anything in months. I think this is one of the most underrated tricks in working with agents right now, and it takes about an hour to do for your own setup.&lt;/p&gt;

&lt;p&gt;This post explains how those files work, using &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt; as the example. It's an open-source tool I built that turns a Linux server into a place where AI agents can deploy apps and databases on their own. By the end you'll know how to install its agent instructions, use its six shortcut commands, and write the same kind of file for your own infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words first
&lt;/h2&gt;

&lt;p&gt;I use Claude Code, Anthropic's coding agent that runs in your terminal, so the examples here are for it. Two terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;skill&lt;/strong&gt; is a folder with a file called &lt;code&gt;SKILL.md&lt;/code&gt; in it. The file has a short header saying what the skill is for, then plain instructions. Claude reads the instructions when your request matches the header.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;slash command&lt;/strong&gt; is a shortcut you type, like &lt;code&gt;/ah-deploy&lt;/code&gt;. It's also just a markdown file. When you type the command, Claude Code drops that file's text into the conversation as instructions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skills follow an open format called &lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt;, so other agent tools can read the same files. Slash commands are a Claude Code feature.&lt;/p&gt;

&lt;p&gt;agentic-hosting itself is one program, &lt;code&gt;ah&lt;/code&gt;, running on your server. Agents talk to it over an HTTP API. &lt;code&gt;$AH_URL&lt;/code&gt; below is your server's address and &lt;code&gt;$AH_KEY&lt;/code&gt; is the agent's API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a skill file looks like
&lt;/h2&gt;

&lt;p&gt;Here's the top of the skill I ship, trimmed and wrapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agentic-hosting&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This skill should be used when&lt;/span&gt;
  &lt;span class="s"&gt;the user asks to install, set up, or operate&lt;/span&gt;
  &lt;span class="s"&gt;agentic-hosting... Trigger on "deploy a&lt;/span&gt;
  &lt;span class="s"&gt;service", "provision a database", "register&lt;/span&gt;
  &lt;span class="s"&gt;a tenant", "view logs", ...&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# agentic-hosting&lt;/span&gt;

Fetch this file from a raw GitHub URL and
follow it end to end. You do not need a GUI.
Every action is curl against &lt;span class="sb"&gt;`/v1/`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part between the &lt;code&gt;---&lt;/code&gt; lines is the header. The part I didn't expect to matter so much is &lt;code&gt;description&lt;/code&gt;. Claude Code keeps every installed skill's description in view all the time, but it only loads the full body when your request looks like a match. So the description isn't a summary for people. It's a list of the things you'd actually say when you want this skill. "Deploy a service." "View logs." If I'd written "a self-hosted PaaS control plane", Claude would rarely connect that to "put my app online."&lt;/p&gt;

&lt;p&gt;That loading rule also means a long skill is cheap until it's used. You can put a lot of detail in the body without paying for it in every conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two copies, two jobs
&lt;/h2&gt;

&lt;p&gt;agentic-hosting ships its instructions in two shapes, because people use agents in two ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One file you can hand to any agent.&lt;/strong&gt; The standalone skill lives at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;skills/agentic-hosting/SKILL.md&lt;/code&gt;&lt;/a&gt;. It's about 300 lines and covers everything: installing the server, creating an account, deploying from git, adding Postgres or Redis, reading logs, and the full API map. Any agent that can read a URL can follow it. &lt;code&gt;llms.txt&lt;/code&gt; (the link in the callout above) is the index that points to it and to the rest of the docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Claude Code bundle.&lt;/strong&gt; The repo's &lt;code&gt;.claude/&lt;/code&gt; folder has a fuller skill, four reference files it can open when it needs detail, and six slash commands. One line installs all of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://agentic.hosting/install-skill.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That puts the skill and its reference files in &lt;code&gt;~/.claude/skills/agentic-hosting/&lt;/code&gt; and the commands in &lt;code&gt;~/.claude/commands/&lt;/code&gt;. Files in your home folder work in every project on that machine. Then tell it where your server is, in your shell profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AH_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-server
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AH_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;keyid.secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only want the single file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;R&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://raw.githubusercontent.com
&lt;span class="nv"&gt;P&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dennisonbertram/agentic-hosting/main
&lt;span class="nv"&gt;D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.claude/skills/agentic-hosting
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/SKILL.md"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$R&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$P&lt;/span&gt;&lt;span class="s2"&gt;/skills/agentic-hosting/SKILL.md"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;The easiest way is to just talk. Once the skill is installed, these all work without any special syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy https://github.com/me/blog as "blog-web"
on port 3000, then show me the build output

add a postgres database to blog-web

what's the status of my services?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude matches the request to the skill, reads the instructions, and runs the &lt;code&gt;curl&lt;/code&gt; calls. You can also call the skill by name with &lt;code&gt;/agentic-hosting&lt;/code&gt;, since the folder name doubles as a command.&lt;/p&gt;

&lt;p&gt;The six slash commands are shortcuts for the jobs I do most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploy from a git URL or Docker image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk, services, databases, builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add Postgres or Redis to an app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stream app logs, or a build's logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-register&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create an account, get an API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ah-snapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Snapshot an app before a risky change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;/ah-status&lt;/code&gt; is the one I use daily. It reads disk use, lists every app with its status and crash count, flags anything not running, and checks for builds in progress. It's my morning "is anything on fire" check, and when something is, the same skill tells the agent how to handle it: what a tripped crash breaker looks like, how to reset it and redeploy, what to do when the disk is nearly full. The agent works the incident instead of handing me an error.&lt;/p&gt;

&lt;p&gt;One note on &lt;code&gt;/ah-register&lt;/code&gt;: it reads the server's setup secret over SSH, so it's for whoever runs the server, not for an agent that's just been handed a key.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside a slash command
&lt;/h2&gt;

&lt;p&gt;Here's the top of &lt;code&gt;/ah-deploy&lt;/code&gt;, trimmed and wrapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy a service to&lt;/span&gt;
  &lt;span class="s"&gt;agentic-hosting from a git URL or Docker&lt;/span&gt;
  &lt;span class="s"&gt;image.&lt;/span&gt;
&lt;span class="na"&gt;argument-hint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;git-url-or-image&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;&amp;lt;service-name&amp;gt; [port]&lt;/span&gt;
&lt;span class="na"&gt;allowed-tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bash&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are an agentic-hosting operator. Use
&lt;span class="sb"&gt;`$AH_URL`&lt;/span&gt; and &lt;span class="sb"&gt;`$AH_KEY`&lt;/span&gt; from the environment
(ask the user if not set).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below that is a numbered list: check the server is up, stop if the disk is over 90% full, create the app, start the build, stream the log, wait up to 10 minutes for it to be running, report the URL.&lt;/p&gt;

&lt;p&gt;Two lines in the header deserve attention.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;argument-hint&lt;/code&gt; is what you see while typing the command. It's documentation for you, nothing more.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;allowed-tools: Bash&lt;/code&gt; lets Claude run shell commands without asking you first, for the turn where you invoked the command. That's why &lt;code&gt;/ah-deploy&lt;/code&gt; runs without a stack of permission prompts. It's also something to decide on purpose. For a deploy command, I want it. For a command that deletes things, I'd leave it off and approve each step. Claude Code also has a &lt;code&gt;disable-model-invocation: true&lt;/code&gt; setting for commands Claude shouldn't start on its own, which is worth using for anything with side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug I found while writing this
&lt;/h2&gt;

&lt;p&gt;To write this post I checked every command file against the current Claude Code docs, and found a real problem in mine.&lt;/p&gt;

&lt;p&gt;Slash commands can pull in what you typed after the command. My command files were written when &lt;code&gt;$1&lt;/code&gt; meant the first argument. In current Claude Code, numbering starts at zero: &lt;code&gt;$0&lt;/code&gt; is the first argument and &lt;code&gt;$1&lt;/code&gt; is the second. So when you type this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ah-deploy https://github.com/me/blog blog-web 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the instructions Claude gets say to deploy &lt;code&gt;3000&lt;/code&gt; using &lt;code&gt;blog-web&lt;/code&gt;, and the git URL never appears in the instructions at all. Nothing errors. The agent just starts from a scrambled brief.&lt;/p&gt;

&lt;p&gt;Until the files are fixed, plain English (the examples above) is the reliable way to deploy. If you write your own commands, two habits avoid this. Use &lt;code&gt;$ARGUMENTS&lt;/code&gt;, which is everything you typed, and let Claude sort it out. Or name your arguments in the header (&lt;code&gt;arguments: [source, service, port]&lt;/code&gt;) and write &lt;code&gt;$source&lt;/code&gt; and &lt;code&gt;$service&lt;/code&gt; in the body, so the numbering can't shift under you.&lt;/p&gt;

&lt;p&gt;The bigger lesson is that these files are code that nothing compiles. They drift from your API, and from the agent tool, and nobody gets an error when they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write one for your own infrastructure
&lt;/h2&gt;

&lt;p&gt;This is the part I'd steal if I were you. You don't need a platform like mine. Any internal API, deploy script, or runbook works. Here's what I've learned makes these files good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Write the description in your own words.&lt;/strong&gt; List the phrases you'd actually type. Claude truncates long descriptions in its listing (about 1,500 characters), so lead with the common ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Show the exact request, and name the wrong one.&lt;/strong&gt; The most useful line in my skill is a negative: "There is no &lt;code&gt;git_url&lt;/code&gt; / &lt;code&gt;branch&lt;/code&gt; field." Agents kept inventing those names because they sound right. Writing down the plausible mistake fixed it in a way that just showing the right shape didn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Say what done looks like.&lt;/strong&gt; My skill doesn't say "wait for the deploy." It gives a loop that checks every 5 seconds for up to 10 minutes and stops on one of three things: running, failed, or the crash breaker open. Agents are great at following a loop and bad at deciding when to give up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Keep the main file short and link out.&lt;/strong&gt; Anthropic's docs suggest keeping &lt;code&gt;SKILL.md&lt;/code&gt; under 500 lines. Mine is about 400, and the full API listing, operations notes, domain setup, and server install live in four separate files the skill points to. Claude opens them only when it needs them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Put the habits in writing.&lt;/strong&gt; My database command says never print the full connection string, only host and port. That's an instruction, not a lock. The string still passes through the commands Claude runs. Write the habit down anyway. It works most of the time, and the real protection belongs on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Test it like code, against the real thing.&lt;/strong&gt; Open a fresh session, ask for the task in plain words, and compare what the agent does with what the server accepts. Every time I've done this I've found something. Earlier checks caught my skill reading the build's ID from a field called &lt;code&gt;id&lt;/code&gt; when the server returns &lt;code&gt;build_id&lt;/code&gt;, and waiting for a status string the server never sends. Both are fixed now. This round turned up the argument numbering above, plus a line in both skill files that says "redeploy" rebuilds from git. It doesn't. It restarts the app with the image it already has and the current settings.&lt;/p&gt;

&lt;p&gt;Here's a skeleton to start from. Swap in your own endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-deploys&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use when the user asks to deploy,&lt;/span&gt;
  &lt;span class="s"&gt;roll back, check deploy status, or read&lt;/span&gt;
  &lt;span class="s"&gt;logs for our apps on deploy.internal.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Deploying our apps&lt;/span&gt;

Base URL: $DEPLOY_URL. Auth header:
&lt;span class="sb"&gt;`Authorization: Bearer $DEPLOY_TOKEN`&lt;/span&gt;.

&lt;span class="gu"&gt;## Deploy&lt;/span&gt;
POST /deploys {"app":"&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;","ref":"&lt;span class="nt"&gt;&amp;lt;sha&amp;gt;&lt;/span&gt;"}
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`ref`&lt;/span&gt; must be a full commit SHA. There is
  no &lt;span class="sb"&gt;`branch`&lt;/span&gt; field.
&lt;span class="p"&gt;-&lt;/span&gt; Poll GET /deploys/&lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt; every 5s, up to
  10 min. Stop on "live" or "failed".

&lt;span class="gu"&gt;## Never&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Print tokens or connection strings.
&lt;span class="p"&gt;-&lt;/span&gt; Deploy to prod without saying so first.

&lt;span class="gu"&gt;## More detail&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; references/api.md for every endpoint.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it as &lt;code&gt;~/.claude/skills/my-deploys/SKILL.md&lt;/code&gt;, start a new session, and ask it to deploy something. Then read what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skill isn't the permission
&lt;/h2&gt;

&lt;p&gt;One last thing I'd tell anyone doing this. The skill file teaches the agent how. It shouldn't decide what the agent is allowed to do. That belongs in the API key.&lt;/p&gt;

&lt;p&gt;In agentic-hosting, the key you get when you create an account is an owner key. Extra keys you create for agents are member keys by default. A member key can deploy, add databases, and read logs, but it can't revoke keys or suspend the account. So my agents run with member keys, and the skill can be as capable as I like without any one agent being able to lock me out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A skill is the paragraph you keep pasting, saved as a file with a header that tells the agent when to read it. agentic-hosting ships one file any agent can follow, plus a Claude Code bundle with six shortcut commands, and the same recipe works for your own infrastructure: real phrases in the description, exact requests with the wrong ones named, a clear stopping point, and regular testing against the real API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Try the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, or start at the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if you've written a skill for your own infra and found a pattern that works. I'd like to collect them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Cover photo: members of an outdoor-orientation workshop learning to read maps and use compasses in Warrensburg, New York. Anne LaBastille, October 1973, for the EPA's Documerica project. U.S. National Archives, public domain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>My AI Agents Share a Server. Here's How I Keep Them Out of Each Other's Way.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Fri, 23 Oct 2026 14:13:48 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/my-ai-agents-share-a-server-heres-how-i-keep-them-out-of-each-others-way-enj</link>
      <guid>https://dev.to/dennisonbertram/my-ai-agents-share-a-server-heres-how-i-keep-them-out-of-each-others-way-enj</guid>
      <description>&lt;p&gt;The first time I had two coding agents deploying to the same server, I gave them the same password. It was the obvious thing to do. Then one agent's deploys started failing with an error about a build already running, and the build it was talking about belonged to the other agent. A few days later I wanted to stop one of them for the night and realized I couldn't do it without stopping both.&lt;/p&gt;

&lt;p&gt;That's when I started thinking of agents less like scripts and more like roommates. They can share a house. They each need their own key, their own room, and some rules about how loud they're allowed to be.&lt;/p&gt;

&lt;p&gt;This post shows how to give each AI agent its own account on a server running &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt;, an open-source tool that turns a Linux server into a place agents can deploy apps and databases on their own. By the end you'll know how to create an account per agent, hand out keys that expire, swap keys without downtime, and shut off one misbehaving agent while the rest keep working.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words first
&lt;/h2&gt;

&lt;p&gt;agentic-hosting is one program, &lt;code&gt;ah&lt;/code&gt;, that runs on your server. Agents talk to it over an HTTP API, the same way they'd call any web service.&lt;/p&gt;

&lt;p&gt;Three terms come up a lot below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;tenant&lt;/strong&gt; is an account on the server. Everything an agent creates (apps, databases, builds) belongs to one tenant.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;API key&lt;/strong&gt; is the password an agent sends with every request. A tenant can have several.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;bootstrap token&lt;/strong&gt; is a long secret you, the person running the server, set when you install it. It's what lets someone create a new tenant. Agents don't need it after that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the examples, &lt;code&gt;$AH_URL&lt;/code&gt; is your server's address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: one tenant per agent
&lt;/h2&gt;

&lt;p&gt;Creating a tenant is one request with the bootstrap token in a header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/tenants/register"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Bootstrap-Token: &lt;/span&gt;&lt;span class="nv"&gt;$AH_BOOTSTRAP_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"blog-agent",
       "email":"me+blog@example.com"}'&lt;/span&gt;
&lt;span class="c"&gt;# {"tenant_id":"3f9a...","api_key":"8c41....d07e..."}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server answers &lt;code&gt;201&lt;/code&gt; with the new tenant's ID and its first API key. That key is shown once. The server only keeps a fingerprint of it, so if you lose it, it can't be read back.&lt;/p&gt;

&lt;p&gt;Some rules worth knowing before you script this for a fleet of agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Each tenant needs its own email.&lt;/strong&gt; Two tenants can share a name but not an email. I use plus addresses (&lt;code&gt;me+blog@&lt;/code&gt;, &lt;code&gt;me+shop@&lt;/code&gt;), which the server accepts. A duplicate email gets &lt;code&gt;422&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration is slow on purpose.&lt;/strong&gt; The server allows 5 registrations per hour from one IP address and 20 per hour in total. This keeps anyone from guessing the bootstrap token by brute force. If you set up ten agents in a loop, the sixth one gets &lt;code&gt;429&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; header telling you how long to wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bootstrap token must be at least 32 characters.&lt;/strong&gt; The server won't start with a shorter one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're just trying this on a laptop, &lt;code&gt;./bin/ah --dev --open-registration&lt;/code&gt; skips the bootstrap token entirely. Anyone who can reach the port can then make an account, so keep that to your own machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: read the key
&lt;/h2&gt;

&lt;p&gt;A key looks like two long strings of letters and numbers joined by a dot: &lt;code&gt;keyID.secret&lt;/code&gt;. The part before the dot is the key's ID. It isn't secret, and it's how you refer to the key later. The part after the dot is the actual password.&lt;/p&gt;

&lt;p&gt;Agents send the whole thing on every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AH_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"8c41....d07e..."&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/tenant"&lt;/span&gt;

&lt;span class="c"&gt;# the key's ID is everything before the dot:&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AH_KEY&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: keep the first key, give the agent a second
&lt;/h2&gt;

&lt;p&gt;This is the habit that made everything else easier for me. The key that comes back from registration stays with me (I'll call it &lt;code&gt;$MY_KEY&lt;/code&gt;). The agent gets a separate key that I create for it, with a name and an expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/auth/keys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"claude-code","expires_in":2592000}'&lt;/span&gt;
&lt;span class="c"&gt;# {"id":"51be...","name":"claude-code",&lt;/span&gt;
&lt;span class="c"&gt;#  "api_key":"51be....9a2f...","prefix":"51be20c7",&lt;/span&gt;
&lt;span class="c"&gt;#  "expires_at":1795359600}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;expires_in&lt;/code&gt; is in seconds, so that's 30 days. Leave it out and the key never expires. The longest you can ask for is 10 years.&lt;/p&gt;

&lt;p&gt;Now there are two keys on the tenant and I can tell them apart. (&lt;code&gt;jq&lt;/code&gt; is a small tool for picking fields out of JSON.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/auth/keys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.[] | {id, name, last_used_at, expires_at}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list never shows secrets. &lt;code&gt;last_used_at&lt;/code&gt; is a Unix timestamp that updates at most every five minutes, which is enough to see whether a key is still in use before you remove it.&lt;/p&gt;

&lt;p&gt;A tenant can hold 20 keys at once. One catch I hit: an expired key still counts toward the 20 until someone revokes it. If your agents rotate keys often, have them clean up the expired ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: swap keys without downtime
&lt;/h2&gt;

&lt;p&gt;Rotating a key is the same three moves every time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new key for the agent.&lt;/li&gt;
&lt;li&gt;Give it to the agent and let it make a request with it.&lt;/li&gt;
&lt;li&gt;Revoke the old one, using its ID (the part before the dot).
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/auth/keys/&lt;/span&gt;&lt;span class="nv"&gt;$OLD_KEY_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# 204&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revoking takes effect right away, even though the server caches logins for speed. The very next request with the old key gets &lt;code&gt;401&lt;/code&gt; and &lt;code&gt;{"error":"invalid api key"}&lt;/code&gt;. An expired key gets the same answer.&lt;/p&gt;

&lt;p&gt;Because both keys work until you revoke the old one, the agent never has a moment where it's locked out. It's simple enough that an agent can do it on its own, a few days before its key expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: cut off one agent
&lt;/h2&gt;

&lt;p&gt;Say an agent goes sideways at 2am. It's redeploying in a loop, or it's doing something you didn't ask for. You have two levels of off switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revoke its key.&lt;/strong&gt; Use your own key on that tenant to revoke the agent's key, as in step 4. The agent is locked out immediately. Its apps and databases keep running, so whatever it already shipped stays up while you figure out what happened. Every other agent on the server is untouched, because they're on other tenants with other keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suspend the whole tenant.&lt;/strong&gt; If you want everything that agent built stopped too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MY_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/tenant"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This marks the tenant suspended, revokes every one of its keys, cancels its builds, and stops its apps and databases. Nothing is deleted. To bring it back, use the admin token (a second operator secret, set as &lt;code&gt;AH_ADMIN_TOKEN&lt;/code&gt; when you install):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Admin-Token: &lt;/span&gt;&lt;span class="nv"&gt;$AH_ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/tenants/&lt;/span&gt;&lt;span class="nv"&gt;$TENANT_ID&lt;/span&gt;&lt;span class="s2"&gt;/reactivate"&lt;/span&gt;
&lt;span class="c"&gt;# {"tenant_id":"3f9a...","api_key":"...",&lt;/span&gt;
&lt;span class="c"&gt;#  "status":"active"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$TENANT_ID&lt;/code&gt; is the &lt;code&gt;tenant_id&lt;/code&gt; from step 1. You get a fresh key. The databases come back on their own. The apps need a redeploy.&lt;/p&gt;

&lt;p&gt;One honest limit: keys on the same tenant are equals. There are no read-only keys or per-key permissions yet, so the agent's key could revoke yours. If that ever happens, the admin token can issue a new key for the tenant from its email with &lt;code&gt;POST /v1/auth/recover&lt;/code&gt;. It's one more reason I never give the admin token to an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "its own space" actually means
&lt;/h2&gt;

&lt;p&gt;Everything a tenant creates is scoped to it. Apps, databases, builds, and build images all carry the tenant's ID, and every lookup checks it. If one agent asks for another tenant's service by ID, it gets &lt;code&gt;404&lt;/code&gt; and &lt;code&gt;{"error":"service not found"}&lt;/code&gt;, as if the service doesn't exist.&lt;/p&gt;

&lt;p&gt;It goes below the API too. Each app runs in its own Docker container, and each tenant's containers sit on their own private network, so one agent's app can't open a connection to another agent's app or database. An agent can deploy public images from Docker Hub, or images it built or snapshotted itself, but never another tenant's.&lt;/p&gt;

&lt;p&gt;Each tenant also has its own budget. New tenants get 5 apps and 3 databases by default, and one build at a time. That last one is what bit me in the opening: two agents on one tenant were taking turns at a single build slot without knowing it. An agent can check where it stands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/tenant/usage"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'{services, databases, api_keys, rate_limit}'&lt;/span&gt;
&lt;span class="c"&gt;# {"services":{"used":2,"max":5},&lt;/span&gt;
&lt;span class="c"&gt;#  "databases":{"used":1,"max":3},&lt;/span&gt;
&lt;span class="c"&gt;#  "api_keys":{"used":2,"max":20},&lt;/span&gt;
&lt;span class="c"&gt;#  "rate_limit":100}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the operator, you can raise or lower a tenant's limits with &lt;code&gt;PATCH /v1/tenants/{id}/quotas&lt;/code&gt; and the admin token. Hitting a limit returns &lt;code&gt;409&lt;/code&gt; with a message like &lt;code&gt;quota exceeded: maximum 5 services reached&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing that isn't per tenant: web addresses
&lt;/h2&gt;

&lt;p&gt;If you start &lt;code&gt;ah&lt;/code&gt; with a domain (&lt;code&gt;--base-domain apps.example.com&lt;/code&gt;), each app gets a web address from its name: a service called &lt;code&gt;web&lt;/code&gt; lives at &lt;code&gt;https://web.apps.example.com&lt;/code&gt;. Those names are shared across the whole server, first come, first served. If one agent already has &lt;code&gt;web&lt;/code&gt;, the next agent that tries gets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;409 {"error":"subdomain already taken: web —
choose a different service name"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A handful of names like &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;www&lt;/code&gt;, and &lt;code&gt;dashboard&lt;/code&gt; are reserved for everyone. The fix is simple: have each agent put its project in the name, like &lt;code&gt;blog-web&lt;/code&gt; or &lt;code&gt;shop-api&lt;/code&gt;. Without a domain, apps get an address made from their random ID, so this never comes up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed limits for each agent
&lt;/h2&gt;

&lt;p&gt;Each tenant gets 100 requests per second, with bursts up to 200. All the keys on a tenant share that one budget. On top of that, the whole server has a limit of 500 per second (bursts to 1,000), so many agents together can't swamp it. I went through how these work in &lt;a href="https://dev.to/dennisonbertram/my-ai-agent-never-gets-tired-and-sometimes-its-wrong-heres-how-my-server-copes-oa6"&gt;an earlier post&lt;/a&gt;, so here's just the part that matters when several agents share a server.&lt;/p&gt;

&lt;p&gt;There are two different 429s, and they mean different things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;429 {"error":"rate limit exceeded"}
    -&amp;gt; this agent is going too fast

429 {"error":"global rate limit exceeded"}
    -&amp;gt; the whole server is busy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both come with a &lt;code&gt;Retry-After&lt;/code&gt; header in seconds. The first one is the agent's own doing, and it should slow down. The second means some other agent is busy, and backing off for a few seconds is all it can do. Every logged-in response also carries &lt;code&gt;X-RateLimit-Limit&lt;/code&gt; and &lt;code&gt;X-RateLimit-Remaining&lt;/code&gt;, so an agent can see the first kind coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries stay inside the tenant too
&lt;/h2&gt;

&lt;p&gt;Agents retry a lot, so the server supports an &lt;code&gt;Idempotency-Key&lt;/code&gt; header: a random string the agent sends with a POST so that if it sends the same request again, the server replays the first answer instead of doing the work twice. Those keys are remembered for 10 minutes and are scoped to the tenant.&lt;/p&gt;

&lt;p&gt;That scoping is one more reason to give each agent its own tenant. Two agents sharing a tenant could, in theory, pick the same key for the same URL. The second would get the first one's answer back, or a &lt;code&gt;409&lt;/code&gt; if their requests were different. Separate tenants can never collide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tell my agents
&lt;/h2&gt;

&lt;p&gt;If you run more than one agent, here's the short version I give each of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- You have your own tenant and your own key.
  Never use another agent's key.
- Put your project in every service name
  (blog-web, not web). Names are shared.
- Rotate your key before it expires: create a
  new one, switch, then revoke the old one.
- Revoke expired keys; they still use a slot.
- 429 "rate limit exceeded": you're too fast.
  429 "global rate limit exceeded": server is
  busy. Either way, wait Retry-After seconds.
- 409 is a limit, not a speed problem. Read the
  error before retrying.
- Send an Idempotency-Key on any POST you
  might retry.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;When agents share a server, the most useful thing I did was give each one its own tenant: its own key, its own apps and databases, its own budget, and its own place in line for builds. Keep the first key for yourself, give the agent a second one that expires, and you can rotate it, revoke it, or suspend the whole tenant without anyone else noticing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Try the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, or start at the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if you're running a few agents on one box and want per-key permissions. That's the next thing I'd like to build, and I want to hear how you'd use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Cover photo: a community garden on East 92nd Street in Manhattan, where volunteers were each assigned their own plot. Suzanne Szasz, August 1973, for the EPA's Documerica project. U.S. National Archives, public domain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>selfhosted</category>
      <category>security</category>
    </item>
    <item>
      <title>When Your AI Agent's Host Goes Down, What Should It Do? Here's What I Tell Mine.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Thu, 22 Oct 2026 13:41:52 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/when-your-ai-agents-host-goes-down-what-should-it-do-heres-what-i-tell-mine-5d24</link>
      <guid>https://dev.to/dennisonbertram/when-your-ai-agents-host-goes-down-what-should-it-do-heres-what-i-tell-mine-5d24</guid>
      <description>&lt;p&gt;Railway had a rough Wednesday. Builds were paused for about a day, and when they came back the status page asked people not to trigger the same deployment more than once. That's good advice for humans. An AI agent will never think of it on its own.&lt;/p&gt;

&lt;p&gt;This post covers how an agent can tell "the platform is down" from "my deploy is broken," what it should do in each case, and the exact rules I paste into my agents' instructions file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened at Railway
&lt;/h2&gt;

&lt;p&gt;I'm only repeating what Railway has published. On October 21, Railway &lt;a href="https://blog.railway.com/p/security-disclosure-wednesday-october-21st" rel="noopener noreferrer"&gt;disclosed&lt;/a&gt; that someone may have accessed encrypted environment variables (the settings where apps keep API keys and database passwords) and, for some of them, gotten the decrypted contents. Railway told every customer to rotate those secrets as a precaution, and paused all deployments while it investigated.&lt;/p&gt;

&lt;p&gt;As of this morning, the &lt;a href="https://status.railway.com" rel="noopener noreferrer"&gt;status page&lt;/a&gt; says builds are back for Pro and Enterprise, the deploy queue is nearly cleared, and Railway is still working on Hobby, then Free and Trial.&lt;/p&gt;

&lt;p&gt;I'm not writing this to pile on. Every platform has a bad day, including mine. But it's a clean example of a situation most agents have no plan for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I got
&lt;/h2&gt;

&lt;p&gt;On my &lt;a href="https://dev.to/dennisonbertram/railway-is-down-today-my-agents-taught-me-to-keep-a-second-place-to-ship-37kd"&gt;last post&lt;/a&gt;, a reader named alexcodebytes asked something I've been chewing on since. If an agent has a backup place to deploy, how does it know when to use it? Can it tell "the platform is down" from "my deploy failed for some other reason"?&lt;/p&gt;

&lt;p&gt;Left alone, mostly no. An agent sees an error and assumes it caused it. That's a reasonable guess most of the time, which is exactly why it's dangerous on the days it's wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the platform before touching code
&lt;/h2&gt;

&lt;p&gt;The fix is boring: make the agent look outside before it looks inside. Before it edits a single line to "fix" a failed deploy, it should answer three questions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the platform say it's down?&lt;/strong&gt; Check the status page. If there's an open incident about builds or the API, stop there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it failing everywhere?&lt;/strong&gt; If a deploy of the last version that worked also fails, the problem isn't the new code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the error point at my code?&lt;/strong&gt; A build log with a compile error or a failing test is yours. A timeout, a 5xx error, or "service unavailable" usually isn't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three point at the platform, it's an outage. If the error points at your code and the platform looks healthy, it's a broken deploy, and the agent should fix it like normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then stop retrying
&lt;/h2&gt;

&lt;p&gt;This is the part agents get worst. Each retry feels free to them. During an outage it just adds to the pile, which is why Railway asked people not to redeploy the same thing over and over.&lt;/p&gt;

&lt;p&gt;My rule is two tries, then stop and tell me. No loops, no "let me try one more approach."&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotate secrets where they're issued
&lt;/h2&gt;

&lt;p&gt;If the incident involves leaked secrets, changing the value in the platform isn't enough. The old key still works until the company that issued it (your database host, your AI provider, your payment processor) revokes it. So the agent should list every key the app uses and I go revoke each one at its source.&lt;/p&gt;

&lt;p&gt;I keep that step for myself. I don't want an agent revoking production keys on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fall back to a second place to ship
&lt;/h2&gt;

&lt;p&gt;Only after all of that does the agent use the backup. For me that's &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt;, an open-source program, &lt;code&gt;ah&lt;/code&gt;, that turns a Linux server I own into a place agents can deploy apps and databases over an HTTP API.&lt;/p&gt;

&lt;p&gt;Checking that the backup is up is one call, no key needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/system/health"&lt;/span&gt;
&lt;span class="c"&gt;# {"status":"ok"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the new secrets and kicking off a build from git looks like this (&lt;code&gt;$SVC&lt;/code&gt; is a service the agent already created there):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC&lt;/span&gt;&lt;span class="s2"&gt;/env"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"DATABASE_URL": "postgres://..."}'&lt;/span&gt;

&lt;span class="nv"&gt;BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; sha &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'{
  source_type: "git",
  source_url: "https://github.com/you/app",
  source_ref: $sha }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC&lt;/span&gt;&lt;span class="s2"&gt;/builds"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build call answers with a &lt;code&gt;build_id&lt;/code&gt; the agent can use to watch the build log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules I paste in
&lt;/h2&gt;

&lt;p&gt;Here's the block that lives in my agents' instructions file (&lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, whatever yours uses):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When a deploy fails:
1. Read the build log. If it shows a compile
   error or failing test, fix the code.
2. Otherwise check the platform status page
   before changing any code.
3. If there is an open incident, or the last
   good version also fails to deploy, it is
   an outage. Do not edit code.
4. Retry at most twice. Then stop and tell me.
5. If the incident mentions leaked secrets,
   list every key the app uses so I can
   revoke each one where it was issued.
6. Only then, deploy to $AH_URL. Check
   $AH_URL/v1/system/health first. Tell me
   you switched and why.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not clever. It just writes down what a careful person already does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You own the backup's uptime.&lt;/strong&gt; If your box is down at 3 AM, there's no status page. There's you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two places means two setups.&lt;/strong&gt; Secrets, domains, and databases have to exist in both, and data doesn't move itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rules can still be wrong.&lt;/strong&gt; A status page can lag, and an agent can still guess badly. This makes good calls more likely. It doesn't make them certain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;When a platform goes down, the risk isn't just downtime. It's an agent retrying into a wall and "fixing" code that was fine. Teach it to check the platform first, stop after two tries, leave secret rotation to you, and only then ship somewhere else. agentic-hosting is one way to have that somewhere else on a server you own.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Set up your own backup with the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>selfhosted</category>
      <category>agents</category>
    </item>
    <item>
      <title>Railway Is Down Today. My Agents Taught Me to Keep a Second Place to Ship.</title>
      <dc:creator>Dennison Bertram</dc:creator>
      <pubDate>Wed, 21 Oct 2026 19:14:53 +0000</pubDate>
      <link>https://dev.to/dennisonbertram/railway-is-down-today-my-agents-taught-me-to-keep-a-second-place-to-ship-37kd</link>
      <guid>https://dev.to/dennisonbertram/railway-is-down-today-my-agents-taught-me-to-keep-a-second-place-to-ship-37kd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update (Oct 22, 8:00 AM ET):&lt;/strong&gt; Railway has since said this was a &lt;a href="https://blog.railway.com/p/security-disclosure-wednesday-october-21st" rel="noopener noreferrer"&gt;security incident&lt;/a&gt;: an unauthorized party may have accessed customer environment variables, some decrypted. They recommend rotating every secret stored there. Builds are back for Pro and Enterprise; Hobby, Free and Trial are still waiting, per the &lt;a href="https://status.railway.com" rel="noopener noreferrer"&gt;status page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Railway has had builds and deploys paused since about 3:24 AM Eastern today. As I write this, a little after 3 PM, the &lt;a href="https://status.railway.com" rel="noopener noreferrer"&gt;status page&lt;/a&gt; still says the dashboard and API are affected and no new builds are going out in any region.&lt;/p&gt;

&lt;p&gt;I'm not writing this to dunk on Railway. I like Railway, and every platform has a day like this, including the one I run. But today is a good, concrete look at something I've been thinking about for months: what happens to your AI agents when the place they deploy to goes away for a day.&lt;/p&gt;

&lt;p&gt;This post covers what the outage looks like from an agent's side, why I keep a deploy target on a server I own, and the honest cost of doing that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⭐ If this is useful, a star on &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other people find it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤖 &lt;strong&gt;Give this to your agent:&lt;/strong&gt; point it at &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt&lt;/a&gt; and it can sign up and set things up from that one link.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened, from the status page
&lt;/h2&gt;

&lt;p&gt;Here's what Railway has posted so far. I'm only repeating what's on &lt;a href="https://status.railway.com" rel="noopener noreferrer"&gt;status.railway.com&lt;/a&gt;; times are converted to Eastern.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3:24 AM:&lt;/strong&gt; Railway starts investigating an issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:28 AM:&lt;/strong&gt; Issue identified. The dashboard, API and CLI are unavailable, and new builds are paused in all regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7:33 AM:&lt;/strong&gt; The API is partly back, but builds are still paused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12:29 PM onward:&lt;/strong&gt; Builds and deploys stay paused, the API and dashboard are still affected, and the support site (station.railway.com) is down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2:45 PM:&lt;/strong&gt; Latest update, with no new information. Railway says it will keep posting at least every 30 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The incident is titled "Dashboard and API unavailable, builds paused" and lists builds in US West, EU West, Southeast Asia and US East as affected. Check the status page for the current state, since it'll be out of date by the time you read this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agent does with a paused platform
&lt;/h2&gt;

&lt;p&gt;When I'm the one deploying, an outage is annoying. I read the status page, sigh, and go do something else.&lt;/p&gt;

&lt;p&gt;An agent doesn't sigh. It pushes a fix, asks for a deploy, gets an error, and tries again. Maybe it reads the error and decides its own code is broken, and starts "fixing" things that weren't wrong. If it's running overnight, you wake up to a long log of retries and nothing shipped.&lt;/p&gt;

&lt;p&gt;That's not the agent's fault. It was handed one place to deploy, and that place is closed. There's no Plan B in its instructions, because there's no Plan B in the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I keep a box of my own
&lt;/h2&gt;

&lt;p&gt;This is the reason I built &lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;agentic-hosting&lt;/a&gt;. It's open source, and it turns a Linux server you own into a place your agents can deploy apps and databases by themselves, with limits you set.&lt;/p&gt;

&lt;p&gt;It's one program, &lt;code&gt;ah&lt;/code&gt;, that takes requests over an HTTP API. So the "second place to ship" is just a URL and a key your agent already knows how to use. A deploy from a git repo looks like this (&lt;code&gt;$SVC&lt;/code&gt; is a service the agent created first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; sha &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'{
  source_type: "git",
  source_url: "https://github.com/you/app",
  source_ref: $sha }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AH_URL&lt;/span&gt;&lt;span class="s2"&gt;/v1/services/&lt;/span&gt;&lt;span class="nv"&gt;$SVC&lt;/span&gt;&lt;span class="s2"&gt;/builds"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AH_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not saying move everything off Railway today. I'm saying it's worth having somewhere your agents can still ship when your main platform can't. You can add one line to their instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If the main platform's deploys are failing and its
status page shows an incident, deploy to $AH_URL
instead and tell me you did.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it costs you
&lt;/h2&gt;

&lt;p&gt;Running your own deploy target isn't free, and I'd be lying if I said it was.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You own the uptime.&lt;/strong&gt; If your box goes down at 3 AM, there's no status page and no team working on it. There's you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You do the upkeep.&lt;/strong&gt; Updates, disk space, backups and TLS are your job. agentic-hosting handles a lot of the guardrails, but it's still a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One box is one box.&lt;/strong&gt; A single server is also a single point of failure. The point is having two places that fail for different reasons, not one place that never fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a lot of people, a managed platform is the right main choice. I think the mistake is having exactly one place your agents know how to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Railway's builds have been paused for most of today, and anyone whose agents deploy only there is waiting along with them. Outages happen everywhere. What you can control is whether your agents have a second place to go. agentic-hosting gives them one on a server you own, and the tradeoff is that keeping it up is on you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dennisonbertram/agentic-hosting" rel="noopener noreferrer"&gt;Star agentic-hosting on GitHub&lt;/a&gt; so more people find it.&lt;/li&gt;
&lt;li&gt;Give it to your agent: &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; to sign up and set up, or the &lt;a href="https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/skills/agentic-hosting/SKILL.md" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; for daily use.&lt;/li&gt;
&lt;li&gt;Try the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/docs/install/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, or start at the &lt;a href="https://dennisonbertram.github.io/agentic-hosting/" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>selfhosted</category>
      <category>railway</category>
    </item>
  </channel>
</rss>
