<?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: pratikbin</title>
    <description>The latest articles on DEV Community by pratikbin (@pratikbin).</description>
    <link>https://dev.to/pratikbin</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3888247%2F8425d2c9-6a25-4bd7-af71-669911159255.png</url>
      <title>DEV Community: pratikbin</title>
      <link>https://dev.to/pratikbin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pratikbin"/>
    <language>en</language>
    <item>
      <title>S3 as a disk for agents: geesefs vs s3fs, measured</title>
      <dc:creator>pratikbin</dc:creator>
      <pubDate>Thu, 04 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/pratikbin/s3-as-a-disk-for-agents-geesefs-vs-s3fs-measured-56a3</link>
      <guid>https://dev.to/pratikbin/s3-as-a-disk-for-agents-geesefs-vs-s3fs-measured-56a3</guid>
      <description>&lt;p&gt;Most infra today is built for a human to click through. That's ending. The thing reading your docs, hitting your API, and filling your disk next year is an agent, not a person. And an agent doesn't have a laptop. It has a Linux box and a network.&lt;/p&gt;

&lt;p&gt;So when you design for agents, two assumptions hold. They run on Linux. And the only storage layer that's actually everywhere — every cloud, every region, every cheap S3-compatible box in a closet — is S3. Not a managed filesystem, not a vendor volume. S3 won by being the lowest common denominator that the machine can reach from anywhere.&lt;/p&gt;

&lt;p&gt;Which leads to the question we kept circling. What if S3 isn't an SDK call, but a disk? Mounted into the VM, inside our VPC, so the agent writes to &lt;code&gt;/mnt/data&lt;/code&gt; and the bytes land in object storage with no code that knows S3 exists.&lt;/p&gt;

&lt;p&gt;That matters because of how agents actually write code. They call &lt;code&gt;open()&lt;/code&gt;, &lt;code&gt;pandas.read_csv("/mnt/...")&lt;/code&gt;, they shell out to &lt;code&gt;rsync&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt;. They do not reach for boto3 unless you force them. Mount the bucket and every POSIX tool speaks S3 for free. That's the point.&lt;/p&gt;

&lt;p&gt;Two projects do this seriously over FUSE: &lt;strong&gt;s3fs-fuse&lt;/strong&gt; and &lt;strong&gt;geesefs&lt;/strong&gt;. We put both on the same VM, same bucket, same workload, and measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;A 4-vCPU / 4 GB Linux VM, Ubuntu 24.04. Tigris as the S3 backend. Latest of each engine: geesefs v0.43.7, s3fs v1.97. apt still ships s3fs 1.93, so we built 1.97 from source. Same 128 MB sequential file, same 500 × 8 KiB small files, five runs, cold and warm. No on-disk cache on either side, so a remount is a true cold start.&lt;/p&gt;

&lt;p&gt;The mount commands — the part everyone gets wrong 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="c"&gt;# s3fs&lt;/span&gt;
s3fs my-bucket /mnt/s &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://t3.storage.dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; use_path_request_style &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;passwd_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/passwd-s3fs

&lt;span class="c"&gt;# geesefs  (--list-type 2 is mandatory on non-Yandex S3; forget it and listing breaks)&lt;/span&gt;
&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... &lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... &lt;span class="se"&gt;\&lt;/span&gt;
  geesefs &lt;span class="nt"&gt;--endpoint&lt;/span&gt; https://t3.storage.dev &lt;span class="nt"&gt;--list-type&lt;/span&gt; 2 my-bucket /mnt/g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount a sub-prefix with s3fs and it 404s unless that prefix already exists as a directory marker — you need &lt;code&gt;-o compat_dir&lt;/code&gt;. geesefs mounts a prefix natively. First gotcha, before a single byte moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Sequential throughput is a tie. ~45–50 MB/s on write and on cold read, both engines. That number is the wire to S3, not the filesystem. If your benchmark is &lt;code&gt;dd if=/dev/zero of=/mnt/big&lt;/code&gt;, you'll conclude the two are identical, and you'll be wrong about everything that matters.&lt;/p&gt;

&lt;p&gt;Everything an agent actually does is not close:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;workload&lt;/th&gt;
&lt;th&gt;s3fs&lt;/th&gt;
&lt;th&gt;geesefs&lt;/th&gt;
&lt;th&gt;gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;small-file write&lt;/td&gt;
&lt;td&gt;2.6–7 files/s&lt;/td&gt;
&lt;td&gt;563 files/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80–217×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;small-file warm read&lt;/td&gt;
&lt;td&gt;5–15 files/s&lt;/td&gt;
&lt;td&gt;1188 files/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;78–233×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sequential warm read&lt;/td&gt;
&lt;td&gt;61 MB/s&lt;/td&gt;
&lt;td&gt;883 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mount&lt;/td&gt;
&lt;td&gt;336 ms&lt;/td&gt;
&lt;td&gt;139 ms&lt;/td&gt;
&lt;td&gt;2×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ranges are real: we ran two s3fs versions (1.93 from apt, 1.97 from source) and the gap held in both directions. Two mechanisms explain almost all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Small-file writes.&lt;/strong&gt; s3fs does one synchronous PUT per file and waits for it. geesefs pipelines uploads in the background and returns. 500 tiny files is 500 serial round trips for s3fs; geesefs overlaps them. That's the 217×.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Directory listing.&lt;/strong&gt; geesefs fills file size and mtime straight from the LIST response it already made, so &lt;code&gt;ls -la&lt;/code&gt; stays cheap and consistent — single-digit milliseconds warm. s3fs is the variable one: depending on version and config it either reads attributes from the listing or does a getattr per file, and when it does the latter, an &lt;code&gt;ls -la&lt;/code&gt; over a few hundred files becomes a serial HEAD storm. We watched the same listing swing from sub-second on one s3fs build to ~11 seconds on another. Either way this is the operation that bites agents, because agents &lt;code&gt;stat&lt;/code&gt; everything: &lt;code&gt;os.listdir&lt;/code&gt; + &lt;code&gt;os.stat&lt;/code&gt;, &lt;code&gt;git status&lt;/code&gt;, rsync's whole model. A names-only &lt;code&gt;ls&lt;/code&gt; is cheap. The moment a tool wants metadata, s3fs is the one that might fall off a cliff — and you won't see it in a &lt;code&gt;dd&lt;/code&gt; test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The warm reads&lt;/strong&gt; — 14× sequential, 233× small — are the kernel page cache. geesefs lets Linux cache file pages; s3fs disables that by default and re-fetches from S3 on every read. Be precise about what this is: it is &lt;em&gt;not&lt;/em&gt; "geesefs uses less RAM." Its cached data lives in the page cache, which doesn't even show up in the process's RSS. geesefs spends memory to make re-reads free. s3fs spends almost none and pays the network every time. For an agent that reads the same dataset in a loop, free re-reads is the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off
&lt;/h2&gt;

&lt;p&gt;geesefs is not a free win, and pretending otherwise is how you get paged at 3 AM.&lt;/p&gt;

&lt;p&gt;Its cold directory listing is noisier — we saw a 0.3–5.7 s spread on the same &lt;code&gt;ls&lt;/code&gt;. It buffers writes aggressively, so you &lt;code&gt;sync -f&lt;/code&gt; before you tear the mount down or you lose the tail — bit us once, now it's reflex. It does not do concurrent multi-writer to one file — that's last-writer-wins with stale-read risk through each box's cache, so keep it to one writer or disjoint prefixes. And the async deletes that make it fast mean a &lt;code&gt;rm -rf&lt;/code&gt; lingers in the bucket for a while after the call returns.&lt;/p&gt;

&lt;p&gt;s3fs earns its place when you want a single synchronous writer with no surprises, or you're streaming a few large objects and never re-reading them. Boring, predictable, slow on metadata. Sometimes boring is the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If the consumer is an agent on Linux — many small files, repeated reads, metadata-heavy tools — mount with geesefs and give it page-cache headroom. The 80–230× isn't a marketing number; it's synchronous-per-file versus pipelined, and re-fetch versus page cache — measured across two s3fs versions. If you only ever stream big blobs once and want the simplest possible failure mode, s3fs is fine.&lt;/p&gt;

&lt;p&gt;S3 is the storage layer that's actually everywhere, and the machine consuming it doesn't care about your SDK. So give it a disk. Just pick the FUSE layer that matches how the thing on top does I/O — and measure the workload it'll really run, not &lt;code&gt;dd&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>s3</category>
      <category>fuse</category>
      <category>agents</category>
      <category>infra</category>
    </item>
    <item>
      <title>Migrating from Claude Code to Codex is not a search-replace</title>
      <dc:creator>pratikbin</dc:creator>
      <pubDate>Tue, 19 May 2026 15:25:37 +0000</pubDate>
      <link>https://dev.to/pratikbin/migrating-from-claude-code-to-codex-is-not-a-search-replace-90k</link>
      <guid>https://dev.to/pratikbin/migrating-from-claude-code-to-codex-is-not-a-search-replace-90k</guid>
      <description>&lt;p&gt;Migrating from Claude Code to Codex looks simple until you hit the stuff around the model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is easy.&lt;/p&gt;

&lt;p&gt;Hooks are not.&lt;br&gt;
MCP servers are not.&lt;br&gt;
Plugins are not.&lt;br&gt;
Permissions are very not.&lt;br&gt;
Sessions are where you find out whether you migrated the workflow or just copied a prompt.&lt;/p&gt;

&lt;p&gt;I turned this into a migration skill because agent setups are becoming infra. They need boring checklists, not vibes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The mistake
&lt;/h2&gt;

&lt;p&gt;The naive migration is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLAUDE.md -&amp;gt; AGENTS.md
.mcp.json -&amp;gt; config.toml
Done.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That only moves visible files.&lt;/p&gt;

&lt;p&gt;A real Claude Code setup can include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/settings.json
.claude/commands/*.md
.claude/agents/*.md
.mcp.json
hooks inside settings
.claude-plugin/plugin.json
~/.claude/projects/* sessions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex has its own shape: &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.codex/config.toml&lt;/code&gt;, &lt;code&gt;.codex/hooks.json&lt;/code&gt;, skills, plugins, MCP config, sessions.&lt;/p&gt;

&lt;p&gt;The hard part is preserving behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventory first
&lt;/h2&gt;

&lt;p&gt;Before converting anything, print the map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Instructions: found
- Hooks: count by event
- CLI MCP servers: count by scope
- Desktop MCP servers: excluded
- Plugins: installed/local
- Commands: custom slash commands
- Subagents: project/user agents
- Permissions: allow/ask/deny rules
- Sessions: recent handoff candidates
- Secrets: names only, values not read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line matters.&lt;/p&gt;

&lt;p&gt;Never read secret values as part of migration. Read names. Ask the user to reset values in the target tool.&lt;/p&gt;

&lt;p&gt;Migration scripts that slurp env vars are how tokens land in git history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooks are policy
&lt;/h2&gt;

&lt;p&gt;Hooks usually encode the safety model.&lt;/p&gt;

&lt;p&gt;In Claude Code, hooks can run on &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;PermissionRequest&lt;/code&gt;, &lt;code&gt;SessionStart&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;, and more. Some hooks are commands. Some are HTTP. Some use MCP tools. Some are prompt or agent hooks.&lt;/p&gt;

&lt;p&gt;Codex hooks are useful, but not identical.&lt;/p&gt;

&lt;p&gt;So do not copy hook JSON and hope.&lt;/p&gt;

&lt;p&gt;Start with a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PreToolUse Bash       -&amp;gt; Codex PreToolUse Bash
PreToolUse Edit/Write -&amp;gt; Edit|Write or apply_patch
MCP tool hooks        -&amp;gt; mcp__server__tool matcher
SessionStart          -&amp;gt; startup/resume/clear mapping
Stop                  -&amp;gt; check matcher semantics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then flag anything that needs redesign: &lt;code&gt;Notification&lt;/code&gt;, &lt;code&gt;SessionEnd&lt;/code&gt;, &lt;code&gt;PreCompact&lt;/code&gt;, prompt hooks, agent hooks, HTTP hooks.&lt;/p&gt;

&lt;p&gt;Partial migration is okay. Silent migration is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP: not your whole desktop
&lt;/h2&gt;

&lt;p&gt;Claude Desktop MCP and Claude Code CLI MCP are different migration targets.&lt;/p&gt;

&lt;p&gt;For a CLI workflow, migrate CLI servers only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp list
claude mcp get &amp;lt;name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus project &lt;code&gt;.mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do not accidentally drag every desktop connector into a coding agent. Different threat model. Different context cost.&lt;/p&gt;

&lt;p&gt;A simple Codex stdio server usually becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.docs]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"@some/docs-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;env_vars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"DOCS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forward env vars when possible. Do not hardcode tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins are bundles
&lt;/h2&gt;

&lt;p&gt;A Claude plugin is not just a skill.&lt;/p&gt;

&lt;p&gt;It can include skills, commands, subagents, hooks, MCP servers, scripts, output styles, themes, monitors, even LSP config.&lt;/p&gt;

&lt;p&gt;So the migration report should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plugin: infra-tools
- skills: direct
- commands: skill or AGENTS recipe
- agents: subagent or skill
- hooks: partial, review events
- MCP: plugin or project config
- scripts: keep
- styles/themes/LSP: manual
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That forces the useful question: what did the plugin actually do?&lt;/p&gt;

&lt;p&gt;If it is instructions, make a skill.&lt;br&gt;
If it is automation, carry the scripts.&lt;br&gt;
If it is policy, test the hooks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Permissions are the dangerous part
&lt;/h2&gt;

&lt;p&gt;Do not loosen permissions silently.&lt;/p&gt;

&lt;p&gt;Map intent, not syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude deny rules     -&amp;gt; Codex filesystem/rules/protected paths
Claude allow prefixes -&amp;gt; Codex allow rules
Claude ask rules      -&amp;gt; prompt rules or on-request approval
acceptEdits           -&amp;gt; workspace-write + approval policy
bypassPermissions     -&amp;gt; do not map without explicit approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My conservative default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;approval_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"on-request"&lt;/span&gt;
&lt;span class="py"&gt;sandbox_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"workspace-write"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add known-safe rules.&lt;/p&gt;

&lt;p&gt;Not the other way around.&lt;/p&gt;

&lt;p&gt;Zero prompts is not the goal. Correct blast radius is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions need handoff
&lt;/h2&gt;

&lt;p&gt;Do not raw-copy session files.&lt;/p&gt;

&lt;p&gt;Use handoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx continues
continues list &lt;span class="nt"&gt;--source&lt;/span&gt; claude &lt;span class="nt"&gt;--json&lt;/span&gt;
continues inspect &amp;lt;session-id&amp;gt; &lt;span class="nt"&gt;--preset&lt;/span&gt; full &lt;span class="nt"&gt;--write-md&lt;/span&gt; handoff.md
continues resume &amp;lt;session-id&amp;gt; &lt;span class="nt"&gt;--in&lt;/span&gt; codex &lt;span class="nt"&gt;--preset&lt;/span&gt; standard
continues resume &amp;lt;session-id&amp;gt; &lt;span class="nt"&gt;--in&lt;/span&gt; codex &lt;span class="nt"&gt;--debug-prompt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bunx continues&lt;/code&gt; is fine if that is your runner. Verify it in your env.&lt;/p&gt;

&lt;p&gt;A good handoff says: objective, current repo state, files changed, commands run, decisions made, failures, pending tasks, next action.&lt;/p&gt;

&lt;p&gt;Long agent sessions rot. Handoff files keep the work inspectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the skill
&lt;/h2&gt;

&lt;p&gt;I packaged the whole checklist as a skill so you run it instead of remembering it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/nodeops-app/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; claude-code-to-codex &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does the inventory, builds the conversion tables, flags partial and manual migrations, and ends with a written report you can diff and roll back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Migrating agent CLIs is not moving a prompt.&lt;/p&gt;

&lt;p&gt;It is moving the operating system around the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;instructions
hooks
permissions
MCP servers
plugins
commands
subagents
sessions
team policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inventory first.&lt;br&gt;
Convert behavior, not files.&lt;br&gt;
Keep rollback.&lt;br&gt;
Verify with real tool calls.&lt;/p&gt;

&lt;p&gt;Boring on purpose.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codex</category>
      <category>claudcode</category>
      <category>cli</category>
    </item>
    <item>
      <title>Run NextDNS and Tailscale together without breaking MagicDNS</title>
      <dc:creator>pratikbin</dc:creator>
      <pubDate>Wed, 06 May 2026 09:56:09 +0000</pubDate>
      <link>https://dev.to/pratikbin/run-nextdns-and-tailscale-together-without-breaking-magicdns-4b06</link>
      <guid>https://dev.to/pratikbin/run-nextdns-and-tailscale-together-without-breaking-magicdns-4b06</guid>
      <description>&lt;p&gt;If you ssh into a tailnet host by name and your terminal answers &lt;code&gt;Unknown host&lt;/code&gt;, NextDNS is probably eating your queries before Tailscale sees them.&lt;/p&gt;

&lt;p&gt;Here's why, and a one-command fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conflict
&lt;/h2&gt;

&lt;p&gt;NextDNS ships an Apple Configuration Profile (&lt;code&gt;apple.nextdns.io&lt;/code&gt;) that installs a system-wide DNS-over-HTTPS handler. The profile is tidy, signed, and ten-second setup — but it captures &lt;strong&gt;every&lt;/strong&gt; query, including ones meant for your tailnet.&lt;/p&gt;

&lt;p&gt;Tailscale's MagicDNS lives at &lt;code&gt;100.100.100.100&lt;/code&gt;. Names like &lt;code&gt;mybox.tailnet-name.ts.net&lt;/code&gt; only resolve there. NextDNS doesn't know your tailnet, so it asks the public DNS root, gets &lt;code&gt;NXDOMAIN&lt;/code&gt;, and your shell gives up.&lt;/p&gt;

&lt;p&gt;The usual macOS escape hatch — dropping a file in &lt;code&gt;/etc/resolver/&lt;/code&gt; — looks like it works:&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 100.100.100.100"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/resolver/your-tailnet.ts.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scutil --dns&lt;/code&gt; even shows the new resolver. But Configuration Profiles outrank &lt;code&gt;/etc/resolver&lt;/code&gt;, so the file is ignored. Silent failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Replace the profile with NextDNS's CLI client. The CLI supports per-domain forwarders. Same profile, same blocklists, same dashboard — plus split DNS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Remove the Apple profile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;System Settings → General → Device Management → NextDNS → Remove&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install the CLI&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nextdns/tap/nextdns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Install with a forwarder for your tailnet&lt;/strong&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="nb"&gt;sudo &lt;/span&gt;nextdns &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-profile&lt;/span&gt; YOUR_PROFILE_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-forwarder&lt;/span&gt; &lt;span class="s1"&gt;'your-tailnet.ts.net=100.100.100.100'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-forwarder&lt;/span&gt; &lt;span class="s1"&gt;'ts.net=100.100.100.100'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-report-client-info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is &lt;code&gt;DOMAIN=SERVER&lt;/code&gt;, not the dnsmasq-style &lt;code&gt;/DOMAIN/SERVER&lt;/code&gt; — the CLI rejects slashes with a confusing "not a valid IP address" error.&lt;/p&gt;

&lt;p&gt;The second forwarder catches MagicDNS short names and any other &lt;code&gt;*.ts.net&lt;/code&gt; host. Drop it if you only want one tailnet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Verify&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig mybox.your-tailnet.ts.net    &lt;span class="c"&gt;# → 100.x.y.z (Tailscale)&lt;/span&gt;
dig example.com                   &lt;span class="c"&gt;# → public IP via NextDNS&lt;/span&gt;
ping mybox.your-tailnet.ts.net    &lt;span class="c"&gt;# works in your shell, not just dig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last check matters. &lt;code&gt;dig&lt;/code&gt; ignores the resolver chain; &lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, and your apps don't. If both pass, you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The NextDNS daemon listens on &lt;code&gt;127.0.0.1:53&lt;/code&gt; and becomes the system resolver. For each query it checks forwarders first, then falls back to the NextDNS DoH endpoint. Tailscale's &lt;code&gt;100.100.100.100&lt;/code&gt; only needs to be reachable, which it is whenever &lt;code&gt;tailscaled&lt;/code&gt; is running.&lt;/p&gt;

&lt;p&gt;NextDNS keeps filtering, analytics, and parental controls. Tailscale keeps MagicDNS. No fights.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;System-wide DNS-over-HTTPS profiles are convenient until you need split DNS. If you run a VPN with its own resolver — Tailscale, ZeroTier, WireGuard with &lt;code&gt;dns=&lt;/code&gt; — switch to a daemon that supports forwarders. Five minutes, no recurring pain.&lt;/p&gt;

</description>
      <category>tailscale</category>
      <category>nextdns</category>
      <category>macos</category>
      <category>networking</category>
    </item>
    <item>
      <title>Your product has an API. It still doesn't speak agent</title>
      <dc:creator>pratikbin</dc:creator>
      <pubDate>Tue, 21 Apr 2026 11:30:00 +0000</pubDate>
      <link>https://dev.to/pratikbin/your-product-has-an-api-it-still-doesnt-speak-agent-30a8</link>
      <guid>https://dev.to/pratikbin/your-product-has-an-api-it-still-doesnt-speak-agent-30a8</guid>
      <description>&lt;p&gt;Most developer tools are built for two audiences: humans and software. Humans get docs and dashboards. Software gets APIs, SDKs, and CLIs. Agents sit awkwardly in the middle. They can read, they can call APIs, they can follow instructions, but they still can't answer a basic question when they land on your domain: what does this product actually know how to help me do? Ready to copy prompt at the end&lt;/p&gt;

&lt;p&gt;I think that's a real gap. We just closed it on CreateOS, and more companies should do the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents still need to be told everything
&lt;/h2&gt;

&lt;p&gt;If Claude Code or OpenCode needs to deploy an app, it doesn't automatically know your platform exists. You can put instructions in &lt;code&gt;CLAUDE.md&lt;/code&gt;; we do that too. But that only helps inside a specific repo. An MCP server helps, but only after someone installs it. &lt;code&gt;llms.txt&lt;/code&gt; is useful, but it's descriptive. It tells an agent what your product is, not how to use it.&lt;/p&gt;

&lt;p&gt;So today, most agents are still operating on tribal knowledge and prompt glue. That's not a great long-term interface.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/cloudflare/agent-skills-discovery-rfc" rel="noopener noreferrer"&gt;Agent Skills Discovery RFC&lt;/a&gt; is a clean fix for this. Think &lt;code&gt;robots.txt&lt;/code&gt;, but for product capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped
&lt;/h2&gt;

&lt;p&gt;Starting today, &lt;code&gt;https://createos.nodeops.network/.well-known/agent-skills/index.json&lt;/code&gt; returns a machine-readable index of every skill our platform offers:&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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;https://schemas.agentskills.io/discovery/0.2.0/schema.json&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&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;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;"createos"&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;"archive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deploy ANYTHING to production on CreateOS cloud platform..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.well-known/agent-skills/createos.tar.gz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:740b1bf8c464aa6148a3eb9d666f2542..."&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;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;"nodeops-auth"&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;"skill-md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add NodeOps PKCE OAuth authentication to a Next.js app..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.well-known/agent-skills/nodeops-auth/SKILL.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:ca197a7ca9fbc5561a6b71c02ea3652..."&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;span class="p"&gt;]&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;An agent fetches one URL and gets the shortlist. Roughly 100 tokens per skill. No repo cloning, no scraping docs, no custom integration work just to understand what the platform can do.&lt;/p&gt;

&lt;p&gt;When the task matches something specific like "deploy my app" or "add NodeOps auth," the agent pulls only that skill, verifies the sha256 digest, and moves on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive disclosure is the part they got right
&lt;/h2&gt;

&lt;p&gt;The smartest part of the RFC is that it doesn't force you to dump everything into context up front.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;name&lt;/code&gt; + &lt;code&gt;description&lt;/code&gt; from &lt;code&gt;index.json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Startup / probing&lt;/td&gt;
&lt;td&gt;~100 tokens/skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Full &lt;code&gt;SKILL.md&lt;/code&gt; body&lt;/td&gt;
&lt;td&gt;When skill is activated&lt;/td&gt;
&lt;td&gt;&amp;lt;5k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Scripts, references, configs&lt;/td&gt;
&lt;td&gt;On demand&lt;/td&gt;
&lt;td&gt;As needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our &lt;code&gt;createos&lt;/code&gt; skill bundles deployment scripts, API references, and config templates at about 28 KB. An agent working on a simple auth task never has to touch any of that. It grabs &lt;code&gt;nodeops-auth/SKILL.md&lt;/code&gt; at about 6 KB and moves on. That's the whole point. Context is expensive. Don't waste it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we built it
&lt;/h2&gt;

&lt;p&gt;We kept the implementation boring on purpose: one build-time script and static files.&lt;/p&gt;

&lt;p&gt;No route handlers. No cold starts. No database read just to answer a well-known URL.&lt;/p&gt;

&lt;p&gt;Build pipeline (&lt;code&gt;scripts/build-agent-skills.mts&lt;/code&gt;, about 200 lines):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;git clone --depth 1&lt;/code&gt; the skills repo into &lt;code&gt;.agent-skills-src/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For each skill directory, read &lt;code&gt;SKILL.md&lt;/code&gt;, parse frontmatter (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If the skill is just a &lt;code&gt;SKILL.md&lt;/code&gt; → copy it. If it has supporting files → tar.gz it&lt;/li&gt;
&lt;li&gt;Compute &lt;code&gt;sha256&lt;/code&gt; digests, write &lt;code&gt;index.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It runs as a &lt;code&gt;prebuild&lt;/code&gt; hook before every &lt;code&gt;next build&lt;/code&gt; and &lt;code&gt;build:cloudflare&lt;/code&gt;, so the published skills stay in sync with upstream.&lt;/p&gt;

&lt;p&gt;Serving is just static files in &lt;code&gt;public/.well-known/agent-skills/&lt;/code&gt; behind Cloudflare Pages. Three header rules in &lt;code&gt;next.config.ts&lt;/code&gt; handle the RFC requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.json&lt;/code&gt; → &lt;code&gt;application/json; charset=utf-8&lt;/code&gt; + CORS + &lt;code&gt;max-age=60, s-maxage=300&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.md&lt;/code&gt; → &lt;code&gt;text/markdown; charset=utf-8&lt;/code&gt; + CORS + &lt;code&gt;max-age=300, s-maxage=3600&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.tar.gz&lt;/code&gt; → &lt;code&gt;application/gzip&lt;/code&gt; + CORS + &lt;code&gt;max-age=300, s-maxage=3600&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We keep the index TTL short so new skills show up quickly. Artifacts get a longer TTL because they're digest-pinned. If the sha256 matches, the file is still the file.&lt;/p&gt;

&lt;p&gt;Serving cost is basically zero because it's static content on a CDN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why more companies should publish this
&lt;/h2&gt;

&lt;p&gt;If you run a platform, an API, or a developer tool, you should seriously consider publishing &lt;code&gt;/.well-known/agent-skills/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, it's cheap. A &lt;code&gt;SKILL.md&lt;/code&gt;, some frontmatter, a small build step, and a few headers. We did the first version in an afternoon.&lt;/p&gt;

&lt;p&gt;Second, it's standard. You're not inventing another one-off agent integration. You're publishing something any RFC-compliant client can understand.&lt;/p&gt;

&lt;p&gt;Third, it composes well. Our &lt;code&gt;createos&lt;/code&gt; skill teaches an agent how to deploy. Our &lt;code&gt;nodeops-auth&lt;/code&gt; skill teaches it how to wire auth. Another vendor can publish observability or billing or incident-response skills the same way. The agent can mix and match without every platform building a separate integration for every other platform.&lt;/p&gt;

&lt;p&gt;And finally, it's probably where this goes anyway. Claude Code doesn't auto-probe &lt;code&gt;/.well-known/agent-skills/&lt;/code&gt; yet. OpenCode doesn't either. Fine. &lt;code&gt;robots.txt&lt;/code&gt; was useful before every crawler agreed on it too. Standards usually show up before the tooling catches up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off
&lt;/h2&gt;

&lt;p&gt;This is still pull, not push. Agents need to know your domain exists before they can ask for &lt;code&gt;/.well-known/agent-skills/index.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So no, this doesn't solve discovery all by itself. There's no master directory yet. In practice, the bridge is simple: mention the URL in your docs, your &lt;code&gt;AGENTS.md&lt;/code&gt;, your MCP server docs, or your &lt;code&gt;llms.txt&lt;/code&gt;. That's enough to make it usable today while clients catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add it to your product
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write your skills.&lt;/strong&gt; Create a directory per skill with a &lt;code&gt;SKILL.md&lt;/code&gt;. The frontmatter needs &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;. The body is instructions for the agent. That's the whole format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package and serve.&lt;/strong&gt; Solo skills ship as plain &lt;code&gt;SKILL.md&lt;/code&gt;. Skills with supporting files (scripts, references) ship as &lt;code&gt;.tar.gz&lt;/code&gt;. Compute sha256 digests. Write &lt;code&gt;index.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add headers.&lt;/strong&gt; &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;, and tiered &lt;code&gt;Cache-Control&lt;/code&gt;. Three URL patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish.&lt;/strong&gt; Put it behind your domain. &lt;code&gt;https://your-domain.com/.well-known/agent-skills/index.json&lt;/code&gt;. Done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwlbe44kwh0lxm5wlwxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwlbe44kwh0lxm5wlwxf.png" alt="RFC implementation on https://nodeops.networ"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The RFC is &lt;a href="https://github.com/cloudflare/agent-skills-discovery-rfc" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The schema is &lt;a href="https://schemas.agentskills.io/discovery/0.2.0/schema.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# this will fail&lt;/span&gt;
npx skills add https://createos.nodeops.network &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;there is active &lt;a href="https://github.com/vercel-labs/skills/issues/985#issuecomment-4304244935" rel="noopener noreferrer"&gt;issue&lt;/a&gt; on skills to add support for RFC v0.2.0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Or just hand this to your coding agent
&lt;/h2&gt;

&lt;p&gt;Don't feel like reading a build script? Fill in the four inputs at the top, paste this into Claude Code, Cursor, or Copilot inside the target repo, and let it wire everything up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Add &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Agent Skills Discovery RFC v0.2.0&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/cloudflare/agent-skills-discovery-rfc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; to this site so agents auto-discover our skills at &lt;span class="sb"&gt;`/.well-known/agent-skills/index.json`&lt;/span&gt;.

&lt;span class="gs"&gt;**Inputs**&lt;/span&gt; (fill in before pasting):
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Skills source**&lt;/span&gt;: &lt;span class="sb"&gt;`&amp;lt;git URL of repo holding skills&amp;gt;`&lt;/span&gt; on branch &lt;span class="sb"&gt;`&amp;lt;ref&amp;gt;`&lt;/span&gt;, skills live under path &lt;span class="sb"&gt;`&amp;lt;skills/skills&amp;gt;`&lt;/span&gt; (each subdir = one skill with a &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt; containing &lt;span class="sb"&gt;`name`&lt;/span&gt; + &lt;span class="sb"&gt;`description`&lt;/span&gt; frontmatter).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Host framework**&lt;/span&gt;: &lt;span class="sb"&gt;`&amp;lt;Next.js app router | Astro | SvelteKit | plain static | Express | other&amp;gt;`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Public dir**&lt;/span&gt;: &lt;span class="sb"&gt;`&amp;lt;public | static | dist | ...&amp;gt;`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Production origin**&lt;/span&gt;: &lt;span class="sb"&gt;`&amp;lt;https://example.com&amp;gt;`&lt;/span&gt;.

&lt;span class="gs"&gt;**Deliverables**&lt;/span&gt; (do all):
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Build script**&lt;/span&gt; (zero new deps; Node built-ins + system &lt;span class="sb"&gt;`git`&lt;/span&gt; + system &lt;span class="sb"&gt;`tar`&lt;/span&gt;):
&lt;span class="p"&gt;   -&lt;/span&gt; Clone the skills repo shallow (&lt;span class="sb"&gt;`git clone --depth 1 --branch &amp;lt;ref&amp;gt;`&lt;/span&gt;) into a gitignored &lt;span class="sb"&gt;`.agent-skills-src/`&lt;/span&gt;. Re-runs do &lt;span class="sb"&gt;`git fetch &amp;amp;&amp;amp; git reset --hard FETCH_HEAD`&lt;/span&gt;.
&lt;span class="p"&gt;   -&lt;/span&gt; Iterate each skill dir under the configured path. Read &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt;, parse YAML frontmatter, require &lt;span class="sb"&gt;`name`&lt;/span&gt; + &lt;span class="sb"&gt;`description`&lt;/span&gt;. Validate &lt;span class="sb"&gt;`name`&lt;/span&gt; against &lt;span class="sb"&gt;`^[a-z0-9]+(-[a-z0-9]+)*$`&lt;/span&gt;, length 1–64. Fail build on violation.
&lt;span class="p"&gt;   -&lt;/span&gt; If dir contains only &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt; → copy to &lt;span class="sb"&gt;`&amp;lt;public&amp;gt;/.well-known/agent-skills/&amp;lt;name&amp;gt;/SKILL.md`&lt;/span&gt;, &lt;span class="sb"&gt;`type: "skill-md"`&lt;/span&gt;.
&lt;span class="p"&gt;   -&lt;/span&gt; Else → &lt;span class="sb"&gt;`tar -czf &amp;lt;public&amp;gt;/.well-known/agent-skills/&amp;lt;name&amp;gt;.tar.gz --sort=name --mtime=@&amp;lt;upstream-commit-ts&amp;gt; -C &amp;lt;skill-dir&amp;gt; .`&lt;/span&gt;, &lt;span class="sb"&gt;`type: "archive"`&lt;/span&gt;. Fall back without repro flags on BSD tar.
&lt;span class="p"&gt;   -&lt;/span&gt; Compute &lt;span class="sb"&gt;`sha256:&amp;lt;hex&amp;gt;`&lt;/span&gt; over the served artifact bytes.
&lt;span class="p"&gt;   -&lt;/span&gt; Write &lt;span class="sb"&gt;`&amp;lt;public&amp;gt;/.well-known/agent-skills/index.json`&lt;/span&gt;:
     &lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;
json
     { "$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
       "skills": [{ "name": "...", "type": "skill-md|archive", "description": "...", "url": "/.well-known/agent-skills/...", "digest": "sha256:..." }] }


     &lt;span class="p"&gt;```&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; Sort skills alphabetically by &lt;span class="sb"&gt;`name`&lt;/span&gt; (stable digest of &lt;span class="sb"&gt;`index.json`&lt;/span&gt;).
&lt;span class="p"&gt;
2.&lt;/span&gt; &lt;span class="gs"&gt;**Wire as prebuild hook**&lt;/span&gt; for the host framework (e.g. npm &lt;span class="sb"&gt;`prebuild`&lt;/span&gt; script, Astro integration, CI step) so output regenerates on every deploy.
&lt;span class="p"&gt;
3.&lt;/span&gt; &lt;span class="gs"&gt;**HTTP response headers**&lt;/span&gt; (per framework's config — &lt;span class="sb"&gt;`next.config.ts`&lt;/span&gt; &lt;span class="sb"&gt;`headers()`&lt;/span&gt;, &lt;span class="sb"&gt;`astro.config`&lt;/span&gt; headers adapter, &lt;span class="sb"&gt;`_headers`&lt;/span&gt; for Pages/Netlify, nginx/express middleware, etc.):
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="sb"&gt;`/.well-known/agent-skills/index.json`&lt;/span&gt; → &lt;span class="sb"&gt;`Content-Type: application/json; charset=utf-8`&lt;/span&gt;, &lt;span class="sb"&gt;`Access-Control-Allow-Origin: *`&lt;/span&gt;, &lt;span class="sb"&gt;`Cache-Control: public, max-age=60, s-maxage=300`&lt;/span&gt;.
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="sb"&gt;`/.well-known/agent-skills/*.md`&lt;/span&gt; → &lt;span class="sb"&gt;`Content-Type: text/markdown; charset=utf-8`&lt;/span&gt;, &lt;span class="sb"&gt;`Access-Control-Allow-Origin: *`&lt;/span&gt;, &lt;span class="sb"&gt;`Cache-Control: public, max-age=300, s-maxage=3600`&lt;/span&gt;.
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="sb"&gt;`/.well-known/agent-skills/*.tar.gz`&lt;/span&gt; → &lt;span class="sb"&gt;`Content-Type: application/gzip`&lt;/span&gt;, &lt;span class="sb"&gt;`Access-Control-Allow-Origin: *`&lt;/span&gt;, &lt;span class="sb"&gt;`Cache-Control: public, max-age=300, s-maxage=3600`&lt;/span&gt;.
&lt;span class="p"&gt;
4.&lt;/span&gt; &lt;span class="gs"&gt;**Gitignore**&lt;/span&gt;: &lt;span class="sb"&gt;`.agent-skills-src/`&lt;/span&gt; and &lt;span class="sb"&gt;`&amp;lt;public&amp;gt;/.well-known/agent-skills/`&lt;/span&gt;.
&lt;span class="p"&gt;
5.&lt;/span&gt; &lt;span class="gs"&gt;**Verify locally**&lt;/span&gt;:
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="sb"&gt;`curl -sS http://localhost:&amp;lt;port&amp;gt;/.well-known/agent-skills/index.json | jq .`&lt;/span&gt; returns valid JSON with &lt;span class="sb"&gt;`$schema`&lt;/span&gt; + &lt;span class="sb"&gt;`skills[]`&lt;/span&gt;.
&lt;span class="p"&gt;   -&lt;/span&gt; Re-hash every served artifact and confirm match against &lt;span class="sb"&gt;`digest`&lt;/span&gt; in the index.
&lt;span class="p"&gt;   -&lt;/span&gt; Response headers match section 3.
&lt;span class="p"&gt;
6.&lt;/span&gt; &lt;span class="gs"&gt;**Post-deploy check**&lt;/span&gt;: same three &lt;span class="sb"&gt;`curl`&lt;/span&gt; calls against the production origin.

&lt;span class="gs"&gt;**Constraints**&lt;/span&gt;:
&lt;span class="p"&gt;-&lt;/span&gt; Do not add runtime dependencies. Hand-parse the two frontmatter fields (&lt;span class="sb"&gt;`name:`&lt;/span&gt; and &lt;span class="sb"&gt;`description:`&lt;/span&gt;) with a regex — full YAML is overkill here.
&lt;span class="p"&gt;-&lt;/span&gt; Static output only. No server route handler — agents must be able to fetch from edge/CDN with no origin cold start.
&lt;span class="p"&gt;-&lt;/span&gt; Never commit generated artifacts or the cloned skills repo.
&lt;span class="p"&gt;-&lt;/span&gt; Skip skills with missing/invalid frontmatter with a warning. Hard-fail on bad &lt;span class="sb"&gt;`name`&lt;/span&gt; format.

&lt;span class="gs"&gt;**Reference implementation**&lt;/span&gt; (adapt, don't copy-paste): https://github.com/cloudflare/agent-skills-discovery-rfc/tree/main/examples

Return a summary of: files created, files modified, verification commands run, and production URL of the new &lt;span class="sb"&gt;`index.json`&lt;/span&gt;.
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;
`

Four inputs in, working `/.well-known/agent-skills/` out. No runtime deps, static output, deploy hook wired.

Agents are already reading `llms.txt`. They're already talking to MCP servers. This fills a different gap: a standard way to publish what your product can help an agent do.

If you build developer tools, I think you should publish one.

Agents are already reading `llms.txt`. They're already talking to MCP servers. This fills a different gap: a standard way to publish what your product can help an agent do.

If you build developer tools, I think you should publish one.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
