<?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: Ben Greenberg</title>
    <description>The latest articles on DEV Community by Ben Greenberg (@bengreenberg).</description>
    <link>https://dev.to/bengreenberg</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%2F29526%2Fab3873ff-b15d-48ee-90c2-0006c40df4a1.jpg</url>
      <title>DEV Community: Ben Greenberg</title>
      <link>https://dev.to/bengreenberg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bengreenberg"/>
    <language>en</language>
    <item>
      <title>From First-Run Drop-Off to First Useful Agent Run</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Thu, 16 Jul 2026 22:16:16 +0000</pubDate>
      <link>https://dev.to/bengreenberg/from-first-run-drop-off-to-first-useful-agent-run-mde</link>
      <guid>https://dev.to/bengreenberg/from-first-run-drop-off-to-first-useful-agent-run-mde</guid>
      <description>&lt;p&gt;I keep coming back to the same onboarding question: what happens in the first 10 minutes?&lt;/p&gt;

&lt;p&gt;For agent tools, that window is brutal. A developer opens a repo, starts the agent, asks for a change, and waits to see if the tool understands the project. If the agent guesses the package manager, misses the test path, edits generated files, or asks the developer to explain the repo from scratch, trust drops fast.&lt;/p&gt;

&lt;p&gt;That isn't an agent model problem every time. A lot of it is repo readiness.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt;, hosted by the Linux Foundation, is building an open home for projects like MCP, goose, AGENTS.md, and agentgateway. That work can sound big and infrastructural, but one of the most useful entry points is small: make your repo easier for an agent to understand on the first run.&lt;/p&gt;

&lt;p&gt;AGENTS.md is the repo-side context. goose is a practical runtime path. Together, they give you a way to move from "the agent is poking around" to "the agent made a useful first pass."&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxw71wmpdfdinbwzonrg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxw71wmpdfdinbwzonrg.png" alt="A structure diagram showing AGENTS.md as repo context and goose as the runtime path that uses it for a first-run task." width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With The First Useful Run
&lt;/h2&gt;

&lt;p&gt;Don't begin by asking, "What should our agent docs say?"&lt;/p&gt;

&lt;p&gt;Ask this instead: what should a developer be able to ask an agent to do in this repo within 10 minutes?&lt;/p&gt;

&lt;p&gt;Pick one task. Not the whole system. One useful first run.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the right entry point for a small bug&lt;/li&gt;
&lt;li&gt;Add a focused test around an existing function&lt;/li&gt;
&lt;li&gt;Update a docs page with a known source file nearby&lt;/li&gt;
&lt;li&gt;Explain how a specific package or module is wired&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That first run gives your AGENTS.md a job. It isn't a policy dump. It's the context an agent needs to avoid wasting the developer's first session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put Repo Truth Where Agents Can Find It
&lt;/h2&gt;

&lt;p&gt;AGENTS.md is a simple open format for guiding coding agents, and the project site says it's already used by over 60k open-source projects: &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;https://agents.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reason it works is plain: agents need a predictable place for repo instructions. README files are written for humans. CI files are written for automation. AGENTS.md gives agents the details that usually live in maintainer heads.&lt;/p&gt;

&lt;p&gt;Your first version should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What kind of project is this?&lt;/li&gt;
&lt;li&gt;Where does source code live?&lt;/li&gt;
&lt;li&gt;Where do tests live?&lt;/li&gt;
&lt;li&gt;Which files should agents avoid editing?&lt;/li&gt;
&lt;li&gt;What style or architecture choices should agents preserve?&lt;/li&gt;
&lt;li&gt;What should the agent do before claiming a task is done?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep it short enough that someone would maintain it. Stale agent instructions are worse than missing ones because they create confident mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Instructions Like Maintainer Notes
&lt;/h2&gt;

&lt;p&gt;An AGENTS.md file doesn't need brand language. It needs maintainer notes.&lt;/p&gt;

&lt;p&gt;Say things like:&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="gh"&gt;# AGENTS.md&lt;/span&gt;

&lt;span class="gu"&gt;## Project Shape&lt;/span&gt;

This repo contains a web app and supporting packages. App code lives in &lt;span class="sb"&gt;`apps/web`&lt;/span&gt;. Shared code lives in &lt;span class="sb"&gt;`packages`&lt;/span&gt;.

&lt;span class="gu"&gt;## Working Rules&lt;/span&gt;

Prefer small changes that match nearby patterns. Do not rewrite public APIs unless the task asks for it.

&lt;span class="gu"&gt;## Tests&lt;/span&gt;

When changing behavior, add or update the closest existing test. If you can't run the test locally, say what you inspected and why the test wasn't run.

&lt;span class="gu"&gt;## Files To Avoid&lt;/span&gt;

Do not edit generated files, lockfiles, or vendored code unless the task is specifically about dependency updates.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing: fake certainty.&lt;/p&gt;

&lt;p&gt;Don't say "run the full test suite" unless that's realistic. Don't list commands you haven't checked. Don't tell the agent to use a package manager you don't use. Your agent instructions should be as true as your README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design The Goose Path
&lt;/h2&gt;

&lt;p&gt;goose is an open-source AI agent runtime under AAIF. Its project page describes it as an agent that can install, execute, edit, and test with any LLM: &lt;a href="https://aaif.io/projects/goose" rel="noopener noreferrer"&gt;https://aaif.io/projects/goose&lt;/a&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7jcxs97gifz95dtndps.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7jcxs97gifz95dtndps.png" alt="An open source, extensible AI agent that goes beyond code suggestions. Install, execute, edit, and test with any LLM." width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For onboarding, think of goose as the first-run path you can test against your repo instructions.&lt;/p&gt;

&lt;p&gt;A good first-run path has three pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clear starting task&lt;/li&gt;
&lt;li&gt;A repo-level AGENTS.md&lt;/li&gt;
&lt;li&gt;A visible stopping point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stopping point matters. If the agent changes code, how does the developer know whether it did the right thing? Maybe the agent should point to the files it changed. Maybe it should explain the test it would run. Maybe it should stop before touching a migration, generated file, or public API.&lt;/p&gt;

&lt;p&gt;That belongs in AGENTS.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make The Agent Ask Better Questions
&lt;/h2&gt;

&lt;p&gt;A useful agent doesn't need to know everything. It needs to know when to stop guessing.&lt;/p&gt;

&lt;p&gt;Add guidance for uncertainty:&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="gu"&gt;## When Unsure&lt;/span&gt;

If the requested change touches auth, billing, data deletion, or production configuration, ask before editing.

If there are multiple plausible implementations, describe the tradeoff and choose the smallest local change unless the user tells you otherwise.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does that help? Because first-run drop-off often comes from surprise. The agent edits the wrong layer, takes a broad refactor path, or treats a risky area like ordinary code.&lt;/p&gt;

&lt;p&gt;Good instructions narrow the blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat Docs As Product Surface
&lt;/h2&gt;

&lt;p&gt;Developer onboarding isn't separate from product. The docs shape what users try, where they get stuck, and whether they come back.&lt;/p&gt;

&lt;p&gt;For agent-ready repos, AGENTS.md is part of that product surface. So review it the same way you'd review a quickstart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the first task obvious?&lt;/li&gt;
&lt;li&gt;Are repo boundaries named?&lt;/li&gt;
&lt;li&gt;Are setup assumptions current?&lt;/li&gt;
&lt;li&gt;Are risky areas called out?&lt;/li&gt;
&lt;li&gt;Can a new contributor tell what "done" means?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where AAIF's open ecosystem angle becomes practical. If agent tools are going to work across projects, maintainers need shared conventions that don't depend on one vendor, one editor, or one model. AGENTS.md gives repos a portable instruction layer. goose gives developers an open way to run agent workflows against it.&lt;/p&gt;

&lt;p&gt;Small file. Real leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Use this before you point an agent at your repo:&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filo2d3ixxv4x1wxobamu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filo2d3ixxv4x1wxobamu.png" alt="An iteration loop for improving AGENTS.md by running a first task, observing wrong guesses, and editing the instructions." width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick one first-run task a new developer would value.&lt;/li&gt;
&lt;li&gt;Add or update AGENTS.md with project shape, test expectations, and files to avoid.&lt;/li&gt;
&lt;li&gt;Remove commands you haven't verified.&lt;/li&gt;
&lt;li&gt;Tell the agent how to behave around risky code paths.&lt;/li&gt;
&lt;li&gt;Run the first task through goose or your agent runtime of choice.&lt;/li&gt;
&lt;li&gt;Edit AGENTS.md based on where the agent guessed wrong.&lt;/li&gt;
&lt;li&gt;Repeat until the first run produces something you would review seriously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal isn't to make the agent perfect. The goal is to make the first session legible.&lt;/p&gt;

&lt;p&gt;A developer should be able to open the repo, start the agent, ask for one scoped task, and understand the result without becoming the repo tour guide.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>tutorial</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I built my first Robinhood Chain app as an index basket</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Sun, 12 Jul 2026 15:51:24 +0000</pubDate>
      <link>https://dev.to/arbitrum/i-built-my-first-robinhood-chain-app-as-an-index-basket-20li</link>
      <guid>https://dev.to/arbitrum/i-built-my-first-robinhood-chain-app-as-an-index-basket-20li</guid>
      <description>&lt;p&gt;I built a small index basket app on Robinhood Chain because I wanted to understand the developer path from the first contract deploy all the way to a working frontend.&lt;/p&gt;

&lt;p&gt;The app is intentionally plain: a user deposits Stock Tokens, which are blockchain tokens that represent real equity exposure, and receives an ERC-20 basket share. ERC-20 is Ethereum's standard token interface, so a compatible token exposes familiar methods like &lt;code&gt;balanceOf&lt;/code&gt;, &lt;code&gt;transfer&lt;/code&gt;, and &lt;code&gt;approve&lt;/code&gt;. The basket share is priced from live price feeds, and the user can redeem it back into the underlying Stock Tokens.&lt;/p&gt;

&lt;p&gt;That's the part that made this interesting to me. The chain is custom, but the app path is not. I still wrote Solidity, deployed with Foundry, read contract state with viem, and wrote transactions from React with wagmi.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm1c8mbe3qvudjo15x4gi.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm1c8mbe3qvudjo15x4gi.png" alt="A user connects a wallet to the React frontend, which uses wagmi and viem to call Robinhood Chain contracts that interact with Stock Tokens and Chainlink feeds." width="600" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built normal web apps, think of the chain's RPC endpoint as the API base URL. A wallet is login plus a signing key. A smart contract is backend code you deploy to the chain, except you should treat it like immutable infrastructure because you don't get to hot-patch it casually later.&lt;/p&gt;

&lt;p&gt;The demo and source are here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App: &lt;a href="https://robinhood-chain-dapp.vercel.app/" rel="noopener noreferrer"&gt;https://robinhood-chain-dapp.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/hummusonrails/robinhood-chain-dapp-example" rel="noopener noreferrer"&gt;https://github.com/hummusonrails/robinhood-chain-dapp-example&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The custom chain still feels like the EVM
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain is a custom Arbitrum Chain, which means it runs as a dedicated chain on the stack of Arbitrum, an Ethereum scaling system. It is also EVM-compatible. EVM means Ethereum Virtual Machine, the runtime that executes Solidity contracts, so the tooling surface looks like the Ethereum developer flow many tutorials already teach.&lt;/p&gt;

&lt;p&gt;An L2, or rollup, is a chain that executes transactions separately and then posts compressed proof or transaction data back to Ethereum. Robinhood Chain uses Ethereum blobs for data availability, which is a cheaper Ethereum data lane for rollups to publish the data needed to reconstruct chain state. Gas, the metered compute fee you pay to run transactions, is paid in ETH.&lt;/p&gt;

&lt;p&gt;The first deploy looks like this:&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;PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x&amp;lt;your_private_key&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RH_RPC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://rpc.testnet.chain.robinhood.com

forge create src/MyContract.sol:MyContract &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RH_RPC_URL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--private-key&lt;/span&gt; &lt;span class="nv"&gt;$PRIVATE_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--broadcast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry is the contract build, test, and deploy CLI. &lt;code&gt;forge create&lt;/code&gt; compiles the contract, sends the deployment transaction, and broadcasts it to the RPC endpoint.&lt;/p&gt;

&lt;p&gt;This is the part I appreciate as a developer. Robinhood gets its own chain configuration, infrastructure, pricing, and product controls. I still get the contract model I know how to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stock Tokens are ERC-20s with display accounting
&lt;/h2&gt;

&lt;p&gt;Stock Tokens are ERC-20s that represent real market assets. That means contracts can read balances, request approvals, and transfer them using the same functions they would use for any other ERC-20.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IERC20 nvda = IERC20(NVDA_TOKEN_ADDRESS);

uint256 balance = nvda.balanceOf(user);
nvda.approve(spender, amount);
nvda.transfer(recipient, amount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrinkle is corporate actions. Stocks split. Dividends happen. The economic relationship between one token and one underlying share can change.&lt;/p&gt;

&lt;p&gt;Stock Tokens implement ERC-8056, the Scaled UI Amount extension. Raw token balances stay stable for contracts. A UI multiplier is how wallets and apps display the share-equivalent amount.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97bik6ktzeo8cc3thtrs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97bik6ktzeo8cc3thtrs.png" alt="Raw token balances stay stable for contract accounting while corporate actions update a UI multiplier used for displayed share-equivalent balances." width="600" height="112"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface IScaledUIAmount {
    function uiMultiplier() external view returns (uint256);
    function balanceOfUI(address account) external view returns (uint256);
    function totalSupplyUI() external view returns (uint256);
    function newUIMultiplier() external view returns (uint256);
    function effectiveAt() external view returns (uint256);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The display conversion is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;underlyingShares = rawBalance * uiMultiplier / 1e18;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why not just mutate balances after a split?&lt;/p&gt;

&lt;p&gt;Because contracts depend on stable accounting. If my basket contract holds a raw token balance, I don't want a display-level corporate action to unexpectedly rewrite the reserve math inside the contract. The UI can show share-equivalent amounts, and the protocol can keep using raw ERC-20 units.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basket contract does only a few things
&lt;/h2&gt;

&lt;p&gt;The sample app has two contracts and no owner.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Control surface&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BasketFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploys and records baskets&lt;/td&gt;
&lt;td&gt;Permissionless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BasketToken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Holds components, mints, redeems, prices shares&lt;/td&gt;
&lt;td&gt;No owner or upgrade path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The factory does not custody user funds. It deploys a basket and records the address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function createBasket(
    string calldata name,
    string calldata symbol,
    BasketToken.Component[] calldata components,
    uint256 maxPriceAge
) external returns (address basket) {
    basket = address(new BasketToken(name, symbol, components, maxPriceAge));
    _baskets.push(basket);
    isBasket[basket] = true;
    emit BasketCreated(basket, msg.sender, name, symbol);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each basket stores a fixed list of components. A component is a token, a price feed, and the amount of that token backing one basket share.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Component {
    address token;
    address feed;
    uint256 unitsPerShare;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A price feed is an external data source a contract or app can read. In this app, the feeds come from Chainlink, an oracle network that publishes market data onchain. An oracle is the bridge between offchain facts, like a stock price, and onchain code.&lt;/p&gt;

&lt;p&gt;On mainnet, the production chain where real assets move, the demo basket uses TSLA, NVDA, and AAPL Stock Tokens with their Chainlink feeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function _mainnetComponents()
    internal
    pure
    returns (BasketToken.Component[] memory c)
{
    c = new BasketToken.Component[](3);
    c[0] = BasketToken.Component(MAINNET_TSLA, MAINNET_TSLA_FEED, 0.4e18);
    c[1] = BasketToken.Component(MAINNET_NVDA, MAINNET_NVDA_FEED, 0.3e18);
    c[2] = BasketToken.Component(MAINNET_AAPL, MAINNET_AAPL_FEED, 0.3e18);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;TRIO&lt;/code&gt; share is backed by &lt;code&gt;0.4 TSLA&lt;/code&gt;, &lt;code&gt;0.3 NVDA&lt;/code&gt;, and &lt;code&gt;0.3 AAPL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On testnet, which is a staging chain with no real funds at risk, the demo uses faucet Stock Tokens for TSLA, AMZN, and NFLX with mock feeds. A faucet is a service that gives you test tokens so you can build without spending real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minting follows the ERC-20 approval pattern
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9pnghj64spuyu4lhuaf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9pnghj64spuyu4lhuaf.png" alt="Raw token balances stay stable for contract accounting while corporate actions update a UI multiplier used for displayed share-equivalent balances." width="528" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the frontend, minting is two steps: approve each component token, then call the basket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stockTokenAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;erc20Abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approve&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;basketAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;basketAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;basketTokenAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shares&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&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;That &lt;code&gt;writeContract&lt;/code&gt; call is from wagmi, a React library for wallet connections and contract writes. viem is the TypeScript Ethereum client underneath it for typed reads, writes, and transaction handling.&lt;/p&gt;

&lt;p&gt;Onchain, the basket pulls the required component amounts and mints shares in the same transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function mint(uint256 shares, address to) external nonReentrant {
    if (shares == 0) revert ZeroShares();

    uint256 count = _components.length;
    for (uint256 i = 0; i &amp;lt; count; i++) {
        Component memory c = _components[i];
        uint256 amount = Math.mulDiv(
            c.unitsPerShare,
            shares,
            SHARE_UNIT,
            Math.Rounding.Ceil
        );

        IERC20(c.token).safeTransferFrom(msg.sender, address(this), amount);
    }

    _mint(to, shares);
    emit Minted(msg.sender, to, shares);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rounding direction matters. Mint rounds up so a user cannot underpay the basket reserves by tiny decimal leftovers.&lt;/p&gt;

&lt;p&gt;Redeem is the mirror image. Burn first, transfer components out, and round down so the reserves cannot be overdrawn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function redeem(uint256 shares, address to) external nonReentrant {
    if (shares == 0) revert ZeroShares();
    if (to == address(0)) revert ZeroAddress();

    _burn(msg.sender, shares);

    uint256 count = _components.length;
    for (uint256 i = 0; i &amp;lt; count; i++) {
        Component memory c = _components[i];
        uint256 amount = Math.mulDiv(
            c.unitsPerShare,
            shares,
            SHARE_UNIT,
            Math.Rounding.Floor
        );

        IERC20(c.token).safeTransfer(to, amount);
    }

    emit Redeemed(msg.sender, to, shares);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redeem skips the price feed and the factory. It burns shares and returns the component tokens the contract already holds.&lt;/p&gt;

&lt;p&gt;I keep pricing and redemption apart on purpose. You use the price for the UI. Redeem returns the collateral.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local to mainnet is the path I want rehearsed
&lt;/h2&gt;

&lt;p&gt;The repo gives you the whole loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--recurse-submodules&lt;/span&gt; https://github.com/hummusonrails/robinhood-chain-dapp-example.git
&lt;span class="nb"&gt;cd &lt;/span&gt;robinhood-chain-dapp-example

pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;anvil
pnpm run deploy:local
pnpm run smoke
pnpm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;anvil&lt;/code&gt; is Foundry's local development chain. Think of it as a throwaway local server for contracts. The local deploy creates mock Stock Tokens, mock Chainlink feeds, the factory, and a demo &lt;code&gt;Tech Trio&lt;/code&gt; basket. It also writes &lt;code&gt;apps/frontend/.env.local&lt;/code&gt;, so the frontend knows which addresses to call.&lt;/p&gt;

&lt;p&gt;For Robinhood Chain testnet:&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;PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$YOUR_TESTNET_KEY&lt;/span&gt; pnpm run deploy:testnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One Foundry script handles local, testnet, and mainnet by checking the &lt;code&gt;chainid&lt;/code&gt;, which is the chain's network identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function run() external {
    vm.startBroadcast();

    BasketFactory factory = new BasketFactory();
    console2.log("FACTORY=%s", address(factory));

    BasketToken.Component[] memory components;
    if (block.chainid == 4663) {
        components = _mainnetComponents();
    } else if (block.chainid == 46630) {
        components = _testnetComponents();
    } else {
        components = _localComponents();
    }

    address basket =
        factory.createBasket("Tech Trio", "TRIO", components, MAX_PRICE_AGE);

    console2.log("DEMO_BASKET=%s", basket);
    console2.log("CHAIN_ID=%s", block.chainid);

    vm.stopBroadcast();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The testnet deployment behind the live walkthrough is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BasketFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0xC1940D5fd58ce735A44a53f910852B12250F6a14&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;BasketToken&lt;/code&gt; (&lt;code&gt;TRIO&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x7633e0920Ea46A8Ec54F61C95adECD391c01Edd4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before spending mainnet gas, I want fork tests. A fork test runs tests against a local copy of live chain state, so you can check integration assumptions without sending real transactions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm run &lt;span class="nb"&gt;test&lt;/span&gt;:contracts
pnpm run &lt;span class="nb"&gt;test&lt;/span&gt;:fork
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the shape of the fork test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function test_mintAndRedeem_withRealStockTokens() public {
    deal(TSLA, alice, 1e18);
    deal(NVDA, alice, 1e18);
    deal(AAPL, alice, 1e18);

    vm.startPrank(alice);
    IERC20Metadata(TSLA).approve(address(basket), type(uint256).max);
    IERC20Metadata(NVDA).approve(address(basket), type(uint256).max);
    IERC20Metadata(AAPL).approve(address(basket), type(uint256).max);

    basket.mint(2e18, alice);
    assertEq(basket.balanceOf(alice), 2e18);
    assertEq(IERC20Metadata(TSLA).balanceOf(address(basket)), 0.8e18);

    basket.redeem(2e18, alice);
    assertEq(basket.totalSupply(), 0);
    assertEq(IERC20Metadata(TSLA).balanceOf(alice), 1e18);
    vm.stopPrank();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the development loop I want for this kind of app: mocks for speed, testnet for wallet flow, fork tests for live integration assumptions, and mainnet only after the path is rehearsed.&lt;/p&gt;

&lt;p&gt;A block explorer, which is basically hosted request logs for a chain, then gives you a way to inspect deployed contracts and transactions. The testnet contracts are verified on Blockscout, so you can read the source and calls after deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stock Tokens change the app assumptions
&lt;/h2&gt;

&lt;p&gt;Stock Tokens still fit the ERC-20 interface, but the surrounding assumptions are different from a generic token.&lt;/p&gt;

&lt;p&gt;For user balances, don't blindly show &lt;code&gt;balanceOf&lt;/code&gt;. Use &lt;code&gt;balanceOfUI&lt;/code&gt; or apply &lt;code&gt;uiMultiplier&lt;/code&gt; so the user sees the share-equivalent amount.&lt;/p&gt;

&lt;p&gt;For prices, read the per-token Chainlink feed on mainnet. For corporate actions, track multiplier updates and pending effective times. For valuation, remember that the feed price already includes the multiplier.&lt;/p&gt;

&lt;p&gt;Stock market hours matter too. Crypto feeds may update around the clock. Stock feeds follow market sessions, so stale data checks need to reflect that.&lt;/p&gt;

&lt;p&gt;The exit path is the one I care about most. If a user wants to redeem their basket share, I don't want that flow blocked because an oracle read is stale. The contract already holds the component tokens. Redemption should return the collateral.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn the chain later; start with the app
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain runs on Arbitrum Nitro, the same underlying technology as Arbitrum One, deployed as a dedicated chain. Arbitrum One is the public shared L2. A custom Arbitrum Chain gives a team its own execution environment while keeping the Ethereum-style contract model.&lt;/p&gt;

&lt;p&gt;The mechanics under the hood are also why the fees are small. Transactions hit a sequencer, which is the service that orders transactions for the rollup, land in fast blocks, get batched, and settle back to Ethereum using blob data. The fee combines L2 execution gas with the data cost on L1, which is Ethereum itself.&lt;/p&gt;

&lt;p&gt;That's useful context, but I wouldn't start by trying to absorb the whole chain architecture.&lt;/p&gt;

&lt;p&gt;Start with the working system. Read a Stock Token balance. Approve a token. Mint a share. Redeem it. Check the price feed. Run the fork test. Look at the transaction in a block explorer.&lt;/p&gt;

&lt;p&gt;Plenty of apps can live on Robinhood Chain. This basket is a good first build because it touches the surfaces most apps using market assets will need: token reads, approvals, ERC-8056 display logic, Chainlink feeds, local mocks, testnet deployment, verified contracts, fork tests, and a Next.js frontend using wagmi and viem.&lt;/p&gt;

&lt;p&gt;What I learned from building it is where the real work sits: deciding where accounting belongs, where pricing belongs, and which assumptions deserve a test before real users and real assets touch the contract. The app stack itself is familiar.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>solidity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The MCP Release Candidate Survival Guide: Apps, Auth, Deprecations, and Tool Schemas</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:27:58 +0000</pubDate>
      <link>https://dev.to/bengreenberg/the-mcp-release-candidate-survival-guide-apps-auth-deprecations-and-tool-schemas-5da2</link>
      <guid>https://dev.to/bengreenberg/the-mcp-release-candidate-survival-guide-apps-auth-deprecations-and-tool-schemas-5da2</guid>
      <description>&lt;p&gt;The &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" rel="noopener noreferrer"&gt;MCP &lt;code&gt;2026-07-28&lt;/code&gt; release candidate&lt;/a&gt; is the largest major specification revision since MCP launched. It is also a compatibility test for everyone building clients, servers, SDKs, gateways, and developer tools around the protocol.&lt;/p&gt;

&lt;p&gt;The release candidate was locked on May 21, 2026. The final specification is scheduled for July 28, 2026. This window is the time to test real implementations and find migration pain points.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Check your transport assumptions
&lt;/h2&gt;

&lt;p&gt;The biggest change is that MCP is now stateless at the protocol layer.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feowi3elz87y74aelwnps.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feowi3elz87y74aelwnps.png" alt="Stateless topology (Source: https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/)" width="710" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your current Streamable HTTP implementation depends on &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;initialized&lt;/code&gt;, or &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, you have migration work. In the release candidate, each request carries the protocol version, client info, and capabilities in &lt;code&gt;_meta&lt;/code&gt;. The new &lt;code&gt;server/discover&lt;/code&gt; method covers cases where a client needs server capabilities up front.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;tools/call&lt;/code&gt; request over Streamable HTTP now includes headers such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changes how infrastructure can handle MCP traffic. A gateway no longer needs to inspect JSON bodies just to route or rate-limit common operations. A load balancer can send requests to any server instance because the protocol no longer assumes a sticky session.&lt;/p&gt;

&lt;p&gt;The compatibility question is simple: does your server still hide required state in the connection?&lt;/p&gt;

&lt;p&gt;If yes, move that state into an explicit application handle. For example, a tool can return a &lt;code&gt;basket_id&lt;/code&gt;, &lt;code&gt;browser_id&lt;/code&gt;, or job handle, and the model can pass it back as a normal tool argument later. That makes state visible to the model and portable across server instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Test server-to-client request flows
&lt;/h2&gt;

&lt;p&gt;Stateless MCP still needs interaction during a call. The release candidate changes how that works.&lt;/p&gt;

&lt;p&gt;Server-initiated requests can only happen while the server is processing a client request. For elicitation, roots, or sampling flows, the server returns an &lt;code&gt;InputRequiredResult&lt;/code&gt;, and the client retries the original call with &lt;code&gt;inputResponses&lt;/code&gt; and &lt;code&gt;requestState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means clients need to preserve and replay the right data. Servers need to treat the retry as a continuation, even if it lands on another instance.&lt;/p&gt;

&lt;p&gt;A good test case is a destructive tool call that asks for confirmation. The server should return an input request, the client should collect the answer, and the retry should succeed without relying on connection memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat MCP Apps as real app surfaces
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/#mcp-apps-server-rendered-user-interfaces" rel="noopener noreferrer"&gt;MCP Apps&lt;/a&gt; let servers provide interactive HTML interfaces that hosts render in sandboxed iframes.&lt;/p&gt;

&lt;p&gt;This is a big developer-experience change, but it also has security and product implications. Tools can declare UI templates ahead of time, which lets hosts prefetch, cache, and review them before anything runs. The UI still talks back through MCP’s JSON-RPC protocol, so UI-driven actions go through the same consent path as tool calls.&lt;/p&gt;

&lt;p&gt;If you maintain a host, test your iframe isolation, permission prompts, and caching behavior. If you maintain a server, check that your UI template declarations are deterministic and do not depend on hidden session state.&lt;/p&gt;

&lt;p&gt;The Apps model will reward consistent discipline: explicit templates, clear tool boundaries, and no surprise network behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Harden authorization now
&lt;/h2&gt;

&lt;p&gt;The release candidate tightens MCP authorization around OAuth 2.0 and OpenID Connect deployments.&lt;/p&gt;

&lt;p&gt;Clients now need to validate the &lt;code&gt;iss&lt;/code&gt; parameter on authorization responses under RFC 9207. Authorization servers should begin sending &lt;code&gt;iss&lt;/code&gt; now because future clients are expected to reject responses without it.&lt;/p&gt;

&lt;p&gt;Dynamic Client Registration also changes. Clients declare OpenID Connect &lt;code&gt;application_type&lt;/code&gt;, which matters for desktop and CLI clients using localhost redirect URIs. Clients also bind registered credentials to the issuing authorization server’s &lt;code&gt;issuer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The migration checklist here is direct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify &lt;code&gt;iss&lt;/code&gt; handling in clients.&lt;/li&gt;
&lt;li&gt;Confirm authorization servers send &lt;code&gt;iss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check Dynamic Client Registration metadata.&lt;/li&gt;
&lt;li&gt;Re-register when a resource moves between authorization servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially relevant for MCP because one client may connect to many servers. Mix-up risks are not theoretical in that shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Stop building new work on deprecated core features
&lt;/h2&gt;

&lt;p&gt;Roots, Sampling, and Logging are deprecated in the release candidate.&lt;/p&gt;

&lt;p&gt;They still work. The deprecation is annotation-only for this release, and the methods, types, and capability flags continue to work in every spec version published within a year of it. Removal would require a separate SEP.&lt;/p&gt;

&lt;p&gt;Still, new work should move elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Roots should move to tool parameters, resource URIs, or server configuration.&lt;/li&gt;
&lt;li&gt;Sampling should move to direct integration with model provider APIs.&lt;/li&gt;
&lt;li&gt;Logging should move to &lt;code&gt;stderr&lt;/code&gt; for stdio or OpenTelemetry for structured observability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you maintain SDK abstractions, this is the moment to add warnings without breaking users. If you maintain docs, stop teaching deprecated features as the default path.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Validate tool schemas against JSON Schema 2020-12
&lt;/h2&gt;

&lt;p&gt;Tool &lt;code&gt;inputSchema&lt;/code&gt; and &lt;code&gt;outputSchema&lt;/code&gt; now use full JSON Schema 2020-12.&lt;/p&gt;

&lt;p&gt;For input schemas, the root still has to be &lt;code&gt;type: "object"&lt;/code&gt;, but schemas can now use &lt;code&gt;oneOf&lt;/code&gt;, &lt;code&gt;anyOf&lt;/code&gt;, &lt;code&gt;allOf&lt;/code&gt;, conditionals, &lt;code&gt;$ref&lt;/code&gt;, and &lt;code&gt;$defs&lt;/code&gt;. Output schemas are unrestricted. &lt;code&gt;structuredContent&lt;/code&gt; can be any JSON value instead of only an object.&lt;/p&gt;

&lt;p&gt;That creates opportunity and risk.&lt;/p&gt;

&lt;p&gt;Servers should bound schema depth and validation time. Implementations should not auto-dereference external &lt;code&gt;$ref&lt;/code&gt; URIs. Clients that made assumptions about simple object-only schemas need tests against composed schemas.&lt;/p&gt;

&lt;p&gt;Also check error handling. The missing resource error changes from MCP’s custom &lt;code&gt;-32002&lt;/code&gt; to the JSON-RPC standard &lt;code&gt;-32602&lt;/code&gt; Invalid Params. If your client matches on the literal code, update it.&lt;/p&gt;

&lt;p&gt;As you work through the checklist, if you find any issues or major friction points bring them to the community. You can open an issue in the &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues" rel="noopener noreferrer"&gt;specification repository&lt;/a&gt;. For implementation questions, the relevant &lt;a href="https://modelcontextprotocol.io/community/working-interest-groups" rel="noopener noreferrer"&gt;Working Group&lt;/a&gt; channel in the &lt;a href="https://modelcontextprotocol.io/community/communication#discord" rel="noopener noreferrer"&gt;contributor Discord&lt;/a&gt; is the fastest path to an answer.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
      <category>news</category>
    </item>
    <item>
      <title>MCP Server or CLI: A Decision Rubric for Developer Tooling</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Wed, 01 Jul 2026 14:29:03 +0000</pubDate>
      <link>https://dev.to/bengreenberg/mcp-server-or-cli-a-decision-rubric-for-developer-tooling-2ch6</link>
      <guid>https://dev.to/bengreenberg/mcp-server-or-cli-a-decision-rubric-for-developer-tooling-2ch6</guid>
      <description>&lt;p&gt;Teams are rushing to make their internal tools available to agents. That is good. It is also where a lot of design mistakes begin.&lt;/p&gt;

&lt;p&gt;The question usually shows up like this:&lt;/p&gt;

&lt;p&gt;Should we expose this as an MCP server, or should the agent just use our CLI?&lt;/p&gt;

&lt;p&gt;That framing makes it sound like one option is more “agentic” than the other. I do not think that is the useful distinction. A CLI and an MCP server solve different problems. The better question is: where does this capability naturally live, and what contract does the agent need in order to use it well?&lt;/p&gt;

&lt;p&gt;MCP, now hosted by the &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; under the Linux Foundation, gives the open agentic AI ecosystem a shared protocol for connecting agents to tools, data, and applications. That shared protocol matters because teams should not have to rebuild the same integration patterns for every agent runtime. But MCP is not a reason to wrap every executable in a server. Sometimes a CLI is exactly the right interface. Sometimes an MCP server is. Often, the answer is both, with different responsibilities.&lt;/p&gt;

&lt;p&gt;Here is the rubric I use.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3oiqpbqmdo9utn0add6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3oiqpbqmdo9utn0add6.png" alt="A decision flow showing when to choose a CLI, an MCP server, both, or documentation only." width="200" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the workflow
&lt;/h2&gt;

&lt;p&gt;A CLI is strongest when the workflow already belongs to a human developer.&lt;/p&gt;

&lt;p&gt;If the task is repo-local, terminal-native, and already part of how developers build, test, debug, or ship software, start with the CLI. Developers know how to inspect it. CI can run it. Logs are usually visible. Failures can be reproduced outside the agent. The same interface works for humans, scripts, and automation.&lt;/p&gt;

&lt;p&gt;Good CLI-shaped examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running a project-specific code generator&lt;/li&gt;
&lt;li&gt;Applying a migration in a local development environment&lt;/li&gt;
&lt;li&gt;Linting, formatting, testing, or packaging&lt;/li&gt;
&lt;li&gt;Inspecting repo state&lt;/li&gt;
&lt;li&gt;Scaffolding files inside a checked-out project&lt;/li&gt;
&lt;li&gt;Running one-off diagnostics where stdout and exit codes are enough&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An MCP server is strongest when the workflow does not naturally belong in a terminal session, or when the agent needs a structured, discoverable capability instead of a command string.&lt;/p&gt;

&lt;p&gt;Good MCP-shaped examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading from or writing to a SaaS API&lt;/li&gt;
&lt;li&gt;Searching a private knowledge base&lt;/li&gt;
&lt;li&gt;Fetching typed records from an internal system&lt;/li&gt;
&lt;li&gt;Performing actions that need scoped authorization&lt;/li&gt;
&lt;li&gt;Exposing capabilities across multiple agent clients&lt;/li&gt;
&lt;li&gt;Providing context as resources, not just command output&lt;/li&gt;
&lt;li&gt;Giving the agent a constrained set of tool calls instead of broad shell access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trap is assuming that “agent can run command” and “agent has a good tool interface” are the same thing. They are not.&lt;/p&gt;

&lt;p&gt;A CLI gives an agent a way to execute. MCP gives an agent a way to understand what capabilities exist, what inputs they accept, and what kind of result comes back.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8zupnsmjjyaqy67nr7d.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8zupnsmjjyaqy67nr7d.png" alt="A comparison of the responsibilities that belong to CLI surfaces versus MCP tool surfaces." width="600" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-question rubric
&lt;/h2&gt;

&lt;p&gt;When deciding between a CLI, an MCP server, or both, I like to ask five questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Is this primarily an interactive human workflow?
&lt;/h3&gt;

&lt;p&gt;If yes, prefer a CLI.&lt;/p&gt;

&lt;p&gt;Developers still need tools that work when no agent is involved. If a human would reasonably run the tool while sitting inside a repo, reading logs, adjusting flags, and retrying, a CLI is usually the right primary interface.&lt;/p&gt;

&lt;p&gt;That does not mean agents cannot use it. Agents are quite good at driving existing developer workflows when the commands are documented and the outputs are predictable. This is where &lt;a href="https://aaif.io/projects/agents-md/" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; fits naturally: document which commands are safe, how to run tests, what directories are off-limits, and what failure modes are expected.&lt;/p&gt;

&lt;p&gt;The CLI should be boring in the best way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear subcommands&lt;/li&gt;
&lt;li&gt;Stable flags&lt;/li&gt;
&lt;li&gt;Machine-readable output where useful&lt;/li&gt;
&lt;li&gt;Non-zero exit codes on failure&lt;/li&gt;
&lt;li&gt;Dry-run modes for risky actions&lt;/li&gt;
&lt;li&gt;Good help text&lt;/li&gt;
&lt;li&gt;No hidden interactive prompts in automation paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the tool needs a human to make judgment calls mid-run, keep that interaction in the CLI. Do not hide it behind an MCP tool and pretend the workflow became autonomous.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Does the agent need to discover the capability?
&lt;/h3&gt;

&lt;p&gt;If yes, lean toward MCP.&lt;/p&gt;

&lt;p&gt;One of the real advantages of MCP is that tools can be described to the agent as tools. The agent does not need to infer everything from a README, shell history, or tribal knowledge. It can see available tool names, descriptions, schemas, and expected inputs.&lt;/p&gt;

&lt;p&gt;That matters when the capability is meant to be reused across agents or across teams.&lt;/p&gt;

&lt;p&gt;A CLI can be documented well, but discovery is still indirect. The agent needs to know the command exists, know where it is installed, know how to call it, and know how to interpret the output. MCP makes the capability part of the agent’s tool surface.&lt;/p&gt;

&lt;p&gt;Use MCP when the agent should be able to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What tools are available to me?&lt;/li&gt;
&lt;li&gt;What arguments does this action require?&lt;/li&gt;
&lt;li&gt;What resources can I inspect?&lt;/li&gt;
&lt;li&gt;What shape will the result have?&lt;/li&gt;
&lt;li&gt;What actions are allowed in this environment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful for APIs and internal systems where a raw CLI would either expose too much or force the agent to learn a human-oriented interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Where is the auth boundary?
&lt;/h3&gt;

&lt;p&gt;If the action crosses an authorization boundary, consider MCP carefully.&lt;/p&gt;

&lt;p&gt;A CLI often inherits the developer’s local environment: shell credentials, config files, tokens, SSH agents, cloud profiles. That can be fine for local workflows. It can also be too broad for agent access.&lt;/p&gt;

&lt;p&gt;MCP gives teams a cleaner place to define permission boundaries. The server can expose only the operations the agent should have. It can scope credentials server-side. It can validate inputs before touching the underlying system. It can log tool calls in a way that is easier to review than arbitrary shell execution.&lt;/p&gt;

&lt;p&gt;This does not make MCP magically safe. A poorly designed MCP server can still be dangerous. But the server boundary gives you a place to enforce policy.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should the agent inherit the user’s full shell environment?&lt;/li&gt;
&lt;li&gt;Should this action use delegated or scoped credentials?&lt;/li&gt;
&lt;li&gt;Do we need per-tool authorization?&lt;/li&gt;
&lt;li&gt;Do we need audit logs of agent actions?&lt;/li&gt;
&lt;li&gt;Do we need to prevent arbitrary command composition?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to those questions is yes, a CLI alone may be too blunt.&lt;/p&gt;

&lt;p&gt;For production-facing systems, this is also where infrastructure projects like &lt;a href="https://aaif.io/projects/agentgateway/" rel="noopener noreferrer"&gt;agentgateway&lt;/a&gt; become relevant. Once agent traffic spans MCP servers, APIs, models, and services, teams need consistent policy, routing, and observability. That is a different layer than the individual tool decision, but the design choices connect.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Is the operation stateful?
&lt;/h3&gt;

&lt;p&gt;State changes raise the bar.&lt;/p&gt;

&lt;p&gt;A read-only diagnostic command is one thing. A tool that creates tickets, deploys services, updates customer data, rotates secrets, or changes infrastructure is another.&lt;/p&gt;

&lt;p&gt;For state-changing actions, the interface should make the action hard to misuse. That can be done in a CLI, an MCP server, or both. The question is which interface gives you the better control surface.&lt;/p&gt;

&lt;p&gt;A CLI might be right when the state change belongs in a developer-controlled workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply this migration to my local database&lt;/li&gt;
&lt;li&gt;Generate this file in my branch&lt;/li&gt;
&lt;li&gt;Create a release artifact after tests pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An MCP server might be right when the state change touches an external system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an incident&lt;/li&gt;
&lt;li&gt;Update a CRM record&lt;/li&gt;
&lt;li&gt;Open a pull request with a structured payload&lt;/li&gt;
&lt;li&gt;Provision access for a user&lt;/li&gt;
&lt;li&gt;Trigger a workflow in a deployment platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For stateful MCP tools, I would avoid generic verbs like &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;execute&lt;/code&gt;, or &lt;code&gt;update&lt;/code&gt; when the action can be modeled more specifically. The tool should say what it does. The input schema should constrain what can happen. The response should include enough structured data for the agent to verify the result.&lt;/p&gt;

&lt;p&gt;For stateful CLIs, I want dry-run support, confirmation controls that can be disabled only in explicit automation modes, and output that makes it clear what changed.&lt;/p&gt;

&lt;p&gt;The shared principle is the same: the agent should not be guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What is the maintenance cost?
&lt;/h3&gt;

&lt;p&gt;Every new interface becomes a product surface.&lt;/p&gt;

&lt;p&gt;A CLI needs packaging, versioning, docs, help text, examples, and compatibility guarantees. An MCP server needs all of that plus server lifecycle, transport decisions, schema design, client compatibility, authentication, deployment, monitoring, and operational ownership.&lt;/p&gt;

&lt;p&gt;That cost may be worth it. But it should buy something real.&lt;/p&gt;

&lt;p&gt;An MCP wrapper around a CLI can be useful when it adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better tool descriptions&lt;/li&gt;
&lt;li&gt;Safer input validation&lt;/li&gt;
&lt;li&gt;Structured outputs&lt;/li&gt;
&lt;li&gt;Scoped permissions&lt;/li&gt;
&lt;li&gt;Shared access across agent clients&lt;/li&gt;
&lt;li&gt;A stable abstraction over a messy underlying command&lt;/li&gt;
&lt;li&gt;Resource access the CLI does not provide well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An MCP wrapper is probably not worth it when it only shells out to an existing command and returns the same text output the agent would have seen anyway.&lt;/p&gt;

&lt;p&gt;That does not mean “never wrap a CLI.” It means the wrapper should create leverage. If the MCP server is only a thinner, less debuggable path to the same command, keep the CLI and document it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “both” pattern
&lt;/h2&gt;

&lt;p&gt;Many teams should build both, but not as duplicates.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65tjx4yfo9fhfnfx03xh.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65tjx4yfo9fhfnfx03xh.png" alt="Humans and CI use the CLI, agents use an MCP server, and both call shared core logic while MCP adds policy and schemas." width="600" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A good pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CLI remains the human and CI interface.&lt;/li&gt;
&lt;li&gt;The MCP server exposes selected capabilities for agents.&lt;/li&gt;
&lt;li&gt;Shared core logic lives below both interfaces.&lt;/li&gt;
&lt;li&gt;The MCP server does not become a dumping ground for every CLI command.&lt;/li&gt;
&lt;li&gt;The CLI does not become an escape hatch for unsafe agent actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, imagine an internal deployment tool.&lt;/p&gt;

&lt;p&gt;The CLI might support the full developer workflow: build, validate, preview, deploy, rollback, inspect logs, and run local checks. It assumes the user is a developer with repo access and deployment permissions.&lt;/p&gt;

&lt;p&gt;The MCP server might expose narrower tools: get deployment status, list services, create a preview environment, request a rollback plan, or fetch logs for a specific service. Those tools can have tighter schemas and safer defaults. They can also return structured data that an agent can reason over without parsing terminal output.&lt;/p&gt;

&lt;p&gt;Both interfaces can call the same underlying deployment library. They do not need to expose the same surface area.&lt;/p&gt;

&lt;p&gt;That separation is healthy. Humans need power tools. Agents need constrained capabilities with clear contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical decision guide
&lt;/h2&gt;

&lt;p&gt;Use a CLI when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task is repo-local or terminal-native&lt;/li&gt;
&lt;li&gt;Humans need to run it directly&lt;/li&gt;
&lt;li&gt;CI should run the same interface&lt;/li&gt;
&lt;li&gt;Shell composition is a feature&lt;/li&gt;
&lt;li&gt;Text output and exit codes are enough&lt;/li&gt;
&lt;li&gt;The auth model is already appropriate for the local developer context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use an MCP server when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent needs discoverable tools or resources&lt;/li&gt;
&lt;li&gt;The task touches an external system&lt;/li&gt;
&lt;li&gt;Inputs and outputs should be typed&lt;/li&gt;
&lt;li&gt;Permissions need to be scoped&lt;/li&gt;
&lt;li&gt;Multiple agent clients should share the same integration&lt;/li&gt;
&lt;li&gt;The tool should hide implementation details&lt;/li&gt;
&lt;li&gt;Auditability and policy matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use both when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Humans and agents both need the capability&lt;/li&gt;
&lt;li&gt;The CLI is already valuable&lt;/li&gt;
&lt;li&gt;The MCP server can expose a safer or more structured subset&lt;/li&gt;
&lt;li&gt;Shared core logic can prevent drift&lt;/li&gt;
&lt;li&gt;The agent interface should be stable even if the CLI evolves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do neither, at least for now, when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The workflow is not understood yet&lt;/li&gt;
&lt;li&gt;The tool would expose broad credentials without guardrails&lt;/li&gt;
&lt;li&gt;The “agent use case” is just novelty&lt;/li&gt;
&lt;li&gt;A README update would solve the immediate problem&lt;/li&gt;
&lt;li&gt;The maintenance owner is unclear&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this means for the open agentic AI ecosystem
&lt;/h2&gt;

&lt;p&gt;The open agentic AI ecosystem needs standards like MCP. It also needs restraint.&lt;/p&gt;

&lt;p&gt;If every team turns every script into an MCP server, agents get a larger tool list but not necessarily better tools. Tool overload is real. Poor descriptions, loose schemas, unsafe side effects, and noisy outputs make agents worse, not better.&lt;/p&gt;

&lt;p&gt;The goal is not to make everything agent-accessible. The goal is to make the right capabilities available through the right contract.&lt;/p&gt;

&lt;p&gt;That is why this decision matters so much at this particular moment. MCP gives builders a common way to expose tools and context. AGENTS.md gives projects a common place to tell coding agents how to work inside a repo. agentgateway points toward the operational layer teams need when agent traffic becomes production traffic.&lt;/p&gt;

&lt;p&gt;These projects are stronger when we use them for the problems they actually solve.&lt;/p&gt;

&lt;p&gt;A CLI is not “less agentic” because it runs in a terminal. An MCP server is not “better architecture” because it speaks a protocol. The useful line is simpler:&lt;/p&gt;

&lt;p&gt;If the work belongs in the developer workflow, start with a CLI.&lt;/p&gt;

&lt;p&gt;If the agent needs a structured, discoverable, permissioned capability, build an MCP server.&lt;/p&gt;

&lt;p&gt;If both humans and agents need it, design both surfaces intentionally and keep the shared logic underneath.&lt;/p&gt;

&lt;p&gt;That is the rubric. Not CLI versus MCP. CLI where the workflow lives. MCP where the capability needs a contract.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>cli</category>
      <category>agents</category>
    </item>
    <item>
      <title>Turn a local skill library into agent-ready documentation</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Wed, 24 Jun 2026 11:32:16 +0000</pubDate>
      <link>https://dev.to/bengreenberg/turn-a-local-skill-library-into-agent-ready-documentation-10c8</link>
      <guid>https://dev.to/bengreenberg/turn-a-local-skill-library-into-agent-ready-documentation-10c8</guid>
      <description>&lt;p&gt;Most agent setups start with a pile of useful local skills.&lt;/p&gt;

&lt;p&gt;That pile usually makes sense to the person who built it. There is a skill for GitHub, one for writing, one for reminders, one for privacy filtering, one for making diagrams, one for checking session logs, one for working with canvases, one for creating new skills, and so on.&lt;/p&gt;

&lt;p&gt;The problem shows up later, when a future agent has to decide what to do.&lt;/p&gt;

&lt;p&gt;A local skill library answers the question, “What can this environment do?”&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;AGENTS.md&lt;/code&gt; file should answer a different question:&lt;/p&gt;

&lt;p&gt;When should an agent use each capability, what should it read first, what tools are safe to call, and what boundaries should it respect?&lt;/p&gt;

&lt;p&gt;That difference matters. A directory full of skills is an inventory. Agent-ready documentation is an operating map.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppyts074mnxtta05392q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppyts074mnxtta05392q.png" alt="A flow showing how a raw local skill library becomes an agent-ready AGENTS.md operating map" width="600" height="26"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tutorial uses an OpenClaw-style skill library as the worked example, but the same pattern applies to any local agent setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with capabilities, not files
&lt;/h2&gt;

&lt;p&gt;A common mistake is to document skills in filesystem order.&lt;/p&gt;

&lt;p&gt;That gives you something like this:&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="p"&gt;-&lt;/span&gt; github
&lt;span class="p"&gt;-&lt;/span&gt; writing-style-skill
&lt;span class="p"&gt;-&lt;/span&gt; privacy-filter
&lt;span class="p"&gt;-&lt;/span&gt; remind-me
&lt;span class="p"&gt;-&lt;/span&gt; canvas
&lt;span class="p"&gt;-&lt;/span&gt; skill-creator
&lt;span class="p"&gt;-&lt;/span&gt; session-logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That list is technically true, but it does not help an agent make decisions.&lt;/p&gt;

&lt;p&gt;A better first pass groups skills by the work they enable:&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="gu"&gt;## Capability map&lt;/span&gt;

&lt;span class="gu"&gt;### Development workflows&lt;/span&gt;

Use these when the user asks about code, repositories, issues, pull requests, debugging, or project maintenance.
&lt;span class="p"&gt;
-&lt;/span&gt; github: interact with GitHub through the &lt;span class="sb"&gt;`gh`&lt;/span&gt; CLI
&lt;span class="p"&gt;-&lt;/span&gt; gh-issues: fetch issues, select candidates, delegate fixes, and open PRs
&lt;span class="p"&gt;-&lt;/span&gt; node-inspect-debugger: debug Node.js processes
&lt;span class="p"&gt;-&lt;/span&gt; python-debugpy: debug Python code

&lt;span class="gu"&gt;### Writing and publishing&lt;/span&gt;

Use these when the user asks for drafts, essays, social posts, newsletters, or style-sensitive writing.
&lt;span class="p"&gt;
-&lt;/span&gt; writing-style-skill: draft in the user's preferred voice
&lt;span class="p"&gt;-&lt;/span&gt; blog-drafter: create blog drafts
&lt;span class="p"&gt;-&lt;/span&gt; x-posts: optimize short-form posts and threads

&lt;span class="gu"&gt;### Safety and privacy&lt;/span&gt;

Use these when content may contain personal data, secrets, or sensitive context.
&lt;span class="p"&gt;
-&lt;/span&gt; privacy-filter: redact PII from text or files
&lt;span class="p"&gt;-&lt;/span&gt; 1password: retrieve secrets through the approved CLI flow

&lt;span class="gu"&gt;### Personal automation&lt;/span&gt;

Use these when the user asks for reminders, home devices, calendar checks, or recurring background tasks.
&lt;span class="p"&gt;
-&lt;/span&gt; remind-me: create one-time reminders
&lt;span class="p"&gt;-&lt;/span&gt; taskflow: coordinate longer detached jobs
&lt;span class="p"&gt;-&lt;/span&gt; weather: check current weather and forecasts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns the library into a decision surface. The agent no longer has to infer that &lt;code&gt;writing-style-skill&lt;/code&gt; is relevant to an essay draft, or that &lt;code&gt;privacy-filter&lt;/code&gt; should be considered before sharing text externally. The map says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add triggers
&lt;/h2&gt;

&lt;p&gt;A capability map becomes much more useful when each section includes triggers.&lt;/p&gt;

&lt;p&gt;Triggers are short descriptions of the user intent that should cause the agent to load a skill.&lt;/p&gt;

&lt;p&gt;For example:&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="gu"&gt;## Writing and publishing&lt;/span&gt;

Load &lt;span class="sb"&gt;`writing-style-skill`&lt;/span&gt; when the user asks to:
&lt;span class="p"&gt;
-&lt;/span&gt; draft an article, essay, email, talk abstract, or newsletter
&lt;span class="p"&gt;-&lt;/span&gt; rewrite something in their voice
&lt;span class="p"&gt;-&lt;/span&gt; adapt technical content for developers
&lt;span class="p"&gt;-&lt;/span&gt; make a draft sound less generic

Load &lt;span class="sb"&gt;`x-posts`&lt;/span&gt; when the user asks to:
&lt;span class="p"&gt;
-&lt;/span&gt; write a post for X
&lt;span class="p"&gt;-&lt;/span&gt; turn an idea into a thread
&lt;span class="p"&gt;-&lt;/span&gt; improve a short social post for reach or clarity

Load &lt;span class="sb"&gt;`blog-drafter`&lt;/span&gt; only when the user explicitly wants a blog draft created.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line matters. Some skills only produce local context. Others take external action. &lt;code&gt;blog-drafter&lt;/code&gt; crosses that boundary because it creates a draft in a publishing system. The agent needs to know that this should not happen casually.&lt;/p&gt;

&lt;p&gt;Good triggers are concrete. Bad triggers are vague.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Use this for content.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Use this when drafting or revising long-form writing, especially when tone and structure matter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Load &lt;span class="sb"&gt;`writing-style-skill`&lt;/span&gt; before drafting blog posts, essays, conference abstracts, newsletter sections, or public-facing technical explanations.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best version tells the agent what the user might actually say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document the first file to read
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbf50wopkpcsyvil0tkbi.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbf50wopkpcsyvil0tkbi.png" alt="A sequence showing how an agent routes a user request through AGENTS.md, loads the relevant skill, and then acts or asks for confirmation" width="600" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many local skills have their own &lt;code&gt;SKILL.md&lt;/code&gt;. An agent should not guess from the skill name alone. The root &lt;code&gt;AGENTS.md&lt;/code&gt; should tell the agent which file is authoritative.&lt;/p&gt;

&lt;p&gt;Example:&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="gu"&gt;## Skill loading rule&lt;/span&gt;

When a task matches a skill, read that skill's &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt; before taking action.

Do not rely only on the skill name or description. The skill file may include safety rules, required tools, local paths, examples, or external-action limits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a local OpenClaw skill set, this is especially useful because different skills have different operational shapes.&lt;/p&gt;

&lt;p&gt;A writing skill might mostly contain tone rules and examples.&lt;/p&gt;

&lt;p&gt;A GitHub skill might specify preferred &lt;code&gt;gh&lt;/code&gt; commands and review behavior.&lt;/p&gt;

&lt;p&gt;A home automation skill might include device-specific constraints.&lt;/p&gt;

&lt;p&gt;A privacy skill might define which data must be filtered before sharing.&lt;/p&gt;

&lt;p&gt;The root file does not need to duplicate all of that. It needs to make skill loading mandatory and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate internal work from external action
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5s9u8k9s00e70t257r54.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5s9u8k9s00e70t257r54.png" alt="A decision flow separating internal work from external actions that require clear user intent or confirmation" width="224" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agent documentation should make one distinction very clear:&lt;/p&gt;

&lt;p&gt;Reading, drafting, searching, and organizing are internal actions.&lt;/p&gt;

&lt;p&gt;Sending, posting, publishing, deleting, buying, messaging, or changing real-world systems are external actions.&lt;/p&gt;

&lt;p&gt;That boundary belongs near the top of &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&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="gu"&gt;## External action boundary&lt;/span&gt;

The agent may freely:
&lt;span class="p"&gt;
-&lt;/span&gt; read local files
&lt;span class="p"&gt;-&lt;/span&gt; inspect skill documentation
&lt;span class="p"&gt;-&lt;/span&gt; draft text
&lt;span class="p"&gt;-&lt;/span&gt; run non-destructive local checks
&lt;span class="p"&gt;-&lt;/span&gt; prepare changes for review

The agent must ask before:
&lt;span class="p"&gt;
-&lt;/span&gt; sending email or messages
&lt;span class="p"&gt;-&lt;/span&gt; publishing posts
&lt;span class="p"&gt;-&lt;/span&gt; creating public drafts
&lt;span class="p"&gt;-&lt;/span&gt; deleting data
&lt;span class="p"&gt;-&lt;/span&gt; changing account settings
&lt;span class="p"&gt;-&lt;/span&gt; controlling physical devices
&lt;span class="p"&gt;-&lt;/span&gt; spending money
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For OpenClaw-style environments, this boundary keeps powerful skills usable without making them reckless. A &lt;code&gt;github&lt;/code&gt; skill that reads issues is different from one that opens a pull request. A &lt;code&gt;blog&lt;/code&gt; skill that drafts locally is different from one that creates a draft in an external service. A &lt;code&gt;govee&lt;/code&gt; or &lt;code&gt;homeconnect&lt;/code&gt; skill can affect the physical environment.&lt;/p&gt;

&lt;p&gt;Write those distinctions down.&lt;/p&gt;

&lt;p&gt;Agents are much better when they do not have to reconstruct your risk model from vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture tool boundaries inside each capability
&lt;/h2&gt;

&lt;p&gt;A capability map should not only say what a skill does. It should say what the agent should avoid doing.&lt;/p&gt;

&lt;p&gt;For example:&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="gu"&gt;## Privacy and redaction&lt;/span&gt;

Use &lt;span class="sb"&gt;`privacy-filter`&lt;/span&gt; before sharing user-provided text outside the local workspace when it may contain:
&lt;span class="p"&gt;
-&lt;/span&gt; names
&lt;span class="p"&gt;-&lt;/span&gt; addresses
&lt;span class="p"&gt;-&lt;/span&gt; phone numbers
&lt;span class="p"&gt;-&lt;/span&gt; email addresses
&lt;span class="p"&gt;-&lt;/span&gt; account identifiers
&lt;span class="p"&gt;-&lt;/span&gt; private messages
&lt;span class="p"&gt;-&lt;/span&gt; internal business context

Do not paste raw private content into public channels or external tools unless the user explicitly approves it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for GitHub:&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="gu"&gt;## GitHub work&lt;/span&gt;

Use &lt;span class="sb"&gt;`github`&lt;/span&gt; for repository, issue, pull request, and CI work.

Safe without asking:
&lt;span class="p"&gt;
-&lt;/span&gt; inspect issues and PRs
&lt;span class="p"&gt;-&lt;/span&gt; read CI logs
&lt;span class="p"&gt;-&lt;/span&gt; check branch status
&lt;span class="p"&gt;-&lt;/span&gt; prepare local patches

Ask before:
&lt;span class="p"&gt;
-&lt;/span&gt; opening a PR
&lt;span class="p"&gt;-&lt;/span&gt; commenting publicly
&lt;span class="p"&gt;-&lt;/span&gt; closing issues
&lt;span class="p"&gt;-&lt;/span&gt; pushing branches to shared remotes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part many local docs miss. They document how to use a tool, but not when to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Include sequencing rules
&lt;/h2&gt;

&lt;p&gt;Some skills should run before others.&lt;/p&gt;

&lt;p&gt;In the OpenClaw set, &lt;code&gt;writing-style-skill&lt;/code&gt; should load before drafting. &lt;code&gt;privacy-filter&lt;/code&gt; should run before sharing sensitive text externally. &lt;code&gt;skill-creator&lt;/code&gt; should load before changing skill files. &lt;code&gt;github&lt;/code&gt; should load before acting on GitHub state.&lt;/p&gt;

&lt;p&gt;That can be expressed as simple sequencing rules:&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="gu"&gt;## Sequencing rules&lt;/span&gt;

Before drafting public writing, load &lt;span class="sb"&gt;`writing-style-skill`&lt;/span&gt;.

Before creating or modifying a reusable skill, load &lt;span class="sb"&gt;`skill-creator`&lt;/span&gt;.

Before using private text in an external channel, consider &lt;span class="sb"&gt;`privacy-filter`&lt;/span&gt;.

Before taking GitHub action, load &lt;span class="sb"&gt;`github`&lt;/span&gt; and inspect the current repository state.

Before using a tool that affects the outside world, confirm the user intended that action.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These rules save agents from doing work in the wrong order.&lt;/p&gt;

&lt;p&gt;A good &lt;code&gt;AGENTS.md&lt;/code&gt; does not need to cover every possible path. It should cover the paths where ordering matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give agents examples of correct routing
&lt;/h2&gt;

&lt;p&gt;Examples are often more useful than rules.&lt;/p&gt;

&lt;p&gt;Here is a compact routing table:&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="gu"&gt;## Routing examples&lt;/span&gt;

User asks: "Draft this as a technical blog post."
Use: &lt;span class="sb"&gt;`writing-style-skill`&lt;/span&gt;
Do: draft in the user's preferred voice
Do not: publish it anywhere

User asks: "Turn this into a blog draft."
Use: &lt;span class="sb"&gt;`writing-style-skill`&lt;/span&gt;, then &lt;span class="sb"&gt;`blog-drafter`&lt;/span&gt;
Do: prepare the content first
Ask before: creating the external draft, unless the user clearly requested that exact action

User asks: "Can you fix this GitHub issue?"
Use: &lt;span class="sb"&gt;`github`&lt;/span&gt;, possibly &lt;span class="sb"&gt;`gh-issues`&lt;/span&gt;
Do: inspect the issue, read the repo, make local changes, run tests
Ask before: opening a PR if the instruction was ambiguous

User asks: "Remember this workflow as a reusable skill."
Use: &lt;span class="sb"&gt;`skill-creator`&lt;/span&gt;
Do: create or update the skill through the approved skill workflow
Do not: hand-edit skill proposal state if the environment has a dedicated tool for that

User asks: "Share this private message in a public post."
Use: &lt;span class="sb"&gt;`privacy-filter`&lt;/span&gt;
Do: redact or summarize safely
Ask before: publishing or sending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not to create a huge rules engine. The point is to give future agents enough examples to route the next request correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep local notes out of shared skills
&lt;/h2&gt;

&lt;p&gt;A reusable skill should explain general behavior. Local environment details belong somewhere else.&lt;/p&gt;

&lt;p&gt;For OpenClaw-style workspaces, that might mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;SKILL.md       -&amp;gt; reusable instructions
TOOLS.md       -&amp;gt; local device names, account aliases, hostnames, personal setup notes
AGENTS.md     -&amp;gt; workspace-level operating rules
MEMORY.md     -&amp;gt; durable user and project context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation keeps skills portable.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;weather&lt;/code&gt; skill can explain how weather lookup works. The local notes can say which city is usually relevant. A &lt;code&gt;canvas&lt;/code&gt; skill can explain how to present HTML on connected nodes. Local notes can say which node names exist in this setup.&lt;/p&gt;

&lt;p&gt;That distinction is small, but it keeps a skill library from turning into a private config dump.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make safety boundaries visible, not buried
&lt;/h2&gt;

&lt;p&gt;If a skill can send, post, delete, control, spend, or expose private data, say so in the capability map.&lt;/p&gt;

&lt;p&gt;A practical format:&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="gu"&gt;## High-risk skills&lt;/span&gt;

These skills can affect external systems or expose private data. Load their &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt; and confirm intent before using them for external action.
&lt;span class="p"&gt;
-&lt;/span&gt; blog-drafter: creates drafts in a publishing account
&lt;span class="p"&gt;-&lt;/span&gt; imsg: can send iMessage/SMS
&lt;span class="p"&gt;-&lt;/span&gt; work-slack: reads workplace Slack data
&lt;span class="p"&gt;-&lt;/span&gt; gog/work-google: access personal or work Google data
&lt;span class="p"&gt;-&lt;/span&gt; govee/homeconnect/sensibo/switchbot: control physical devices
&lt;span class="p"&gt;-&lt;/span&gt; 1password: accesses secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not mean the skills are unsafe. It means they are powerful.&lt;/p&gt;

&lt;p&gt;Agents do better with clear labels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the root AGENTS.md as an operating manual
&lt;/h2&gt;

&lt;p&gt;A useful &lt;code&gt;AGENTS.md&lt;/code&gt; should be short enough to read and strong enough to steer behavior.&lt;/p&gt;

&lt;p&gt;A good structure looks like this:&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="gh"&gt;# AGENTS.md&lt;/span&gt;

&lt;span class="gu"&gt;## Role&lt;/span&gt;

You are working inside this workspace. Read local instructions before acting. Prefer existing skills and tools over inventing new workflows.

&lt;span class="gu"&gt;## Startup&lt;/span&gt;

Read:
&lt;span class="p"&gt;
-&lt;/span&gt; SOUL.md
&lt;span class="p"&gt;-&lt;/span&gt; USER.md
&lt;span class="p"&gt;-&lt;/span&gt; recent daily memory files
&lt;span class="p"&gt;-&lt;/span&gt; MEMORY.md when in a direct private session

&lt;span class="gu"&gt;## Capability map&lt;/span&gt;

Group available skills by task:
&lt;span class="p"&gt;
-&lt;/span&gt; development workflows
&lt;span class="p"&gt;-&lt;/span&gt; writing and publishing
&lt;span class="p"&gt;-&lt;/span&gt; privacy and safety
&lt;span class="p"&gt;-&lt;/span&gt; personal automation
&lt;span class="p"&gt;-&lt;/span&gt; debugging and inspection
&lt;span class="p"&gt;-&lt;/span&gt; media and presentation
&lt;span class="p"&gt;-&lt;/span&gt; skill maintenance

&lt;span class="gu"&gt;## Skill loading&lt;/span&gt;

When a task matches a skill, read that skill's &lt;span class="sb"&gt;`SKILL.md`&lt;/span&gt; before acting.

&lt;span class="gu"&gt;## External action boundary&lt;/span&gt;

Internal work is allowed. External action requires clear user intent or confirmation.

&lt;span class="gu"&gt;## Safety rules&lt;/span&gt;

Protect private data. Prefer recoverable actions. Do not run destructive commands casually.

&lt;span class="gu"&gt;## Routing examples&lt;/span&gt;

Include examples of common user requests and the skills they should trigger.

&lt;span class="gu"&gt;## Local notes&lt;/span&gt;

Put machine-specific details in &lt;span class="sb"&gt;`TOOLS.md`&lt;/span&gt;, not in reusable skills.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough to turn a pile of local skills into a working agent interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat AGENTS.md as a map, not a museum
&lt;/h2&gt;

&lt;p&gt;The file should change as the skill library changes.&lt;/p&gt;

&lt;p&gt;When you add a new skill, add it to the capability map. When a tool gains external side effects, move it into the high-risk section. When an agent makes a routing mistake, add a small example that would have prevented it.&lt;/p&gt;

&lt;p&gt;The best &lt;code&gt;AGENTS.md&lt;/code&gt; files are not long. They are current.&lt;/p&gt;

&lt;p&gt;For developers building local agent systems, this is the real payoff: your skill library stops being something only you understand. It becomes a documented capability layer that future agents can read, reason over, and use correctly.&lt;/p&gt;

&lt;p&gt;That is what agent-ready documentation is for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>learning</category>
      <category>agents</category>
    </item>
    <item>
      <title>An AI agent that pays for its own API calls on AWS</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Fri, 29 May 2026 11:27:13 +0000</pubDate>
      <link>https://dev.to/bengreenberg/an-ai-agent-that-pays-for-its-own-api-calls-on-aws-din</link>
      <guid>https://dev.to/bengreenberg/an-ai-agent-that-pays-for-its-own-api-calls-on-aws-din</guid>
      <description>&lt;p&gt;I built a small AWS Bedrock AgentCore agent that pays for a paywalled API with real USDC on Arbitrum One. It asks for a report, gets back HTTP 402 Payment Required, settles the charge on its own, and retries. No API key, no card on file, no human clicking approve. The settlement lands on Arbitrum One mainnet, and the run prints an Arbiscan link to the transaction so you can read it yourself.&lt;/p&gt;

&lt;p&gt;There's a great walkthrough of the same idea on Base Sepolia by &lt;a href="https://william.mendozagopar.com/blog/bedrock-agentcore-x402.html" rel="noopener noreferrer"&gt;William Mendoza Gopar&lt;/a&gt;. This post expands upon his work: mainnet instead of testnet, real USDC instead of a burn-address demo, and a merchant built on CloudFront, Lambda@Edge, and API Gateway. The full source is at &lt;a href="https://github.com/hummusonrails/arbitrum-x402-aws" rel="noopener noreferrer"&gt;github.com/hummusonrails/arbitrum-x402-aws&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AgentCore gives you
&lt;/h2&gt;

&lt;p&gt;AgentCore is AWS's hosted runtime for AI agents, still in preview. The piece this project leans on is AgentCore Payments: a managed signer that holds an embedded wallet for you and produces payment authorizations on request. Your code asks the PaymentManager to handle a charge, and the private key stays inside AgentCore.&lt;/p&gt;

&lt;p&gt;You stand up four resources once: a PaymentManager with a connector to Coinbase CDP, an embedded crypto wallet as a PaymentInstrument, and a PaymentSession that carries a spend budget and an expiry. After that, paying is a single call.&lt;/p&gt;

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

&lt;p&gt;x402 puts the 402 Payment Required status code to work. A paid endpoint answers an unpaid request with 402 and a JSON body describing what it wants: the network, the token, the recipient, and the amount. The client signs an EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt; for USDC, which lets someone else submit the transfer on-chain, and resends the request with the signature attached. The server verifies and settles through a facilitator, then returns the content. No accounts to create, no keys to rotate, and charges as small as a fraction of a cent. That last property is what lets an agent use it with no human in the loop.&lt;/p&gt;

&lt;p&gt;One aside on the AWS side: this x402 support is a brand-new preview release. This week I worked with the AWS team to make sure it settles across EVM chains, Arbitrum One included, so the wallet, the facilitator, and the settlement path behave the same way on Arbitrum as on any other EVM network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Arbitrum One
&lt;/h2&gt;

&lt;p&gt;An agent that pays per API call cannot spend a dollar in transaction fees to move a fraction of a cent. The math only works if the settlement layer makes sub-cent payments cheap enough to vanish into the cost of the call itself. Arbitrum One clears that bar on price.&lt;/p&gt;

&lt;p&gt;Price is half of it. The other half is predictability. &lt;/p&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%2F6etruvxr8th4fheu0vts.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%2F6etruvxr8th4fheu0vts.png" alt="Free predictability during all pricing conditions is essential for agentic commerce"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An agent commits to a workflow and runs it; it cannot pause to renegotiate when gas spikes partway through. When fees jump for a few minutes, a person shrugs and waits, but an agent paying per call either overpays or stalls. Arbitrum's gas pricing is built to absorb those spikes. The recent Arbitrum One upgrade replaced the old single-target pricing model, and during peak activity it cut gas by around 98% compared to what that model would have charged. Fees stay low, and they stay close to where they were a minute ago. For x402 micropayments, that stability is worth as much as the headline number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the demo
&lt;/h2&gt;

&lt;p&gt;Three pieces talk to each other. The agent runs on AgentCore with its embedded CDP wallet. The merchant is an AWS stack: CloudFront in front, a Lambda@Edge function on &lt;code&gt;viewer-request&lt;/code&gt; that speaks x402, and an API Gateway HTTP API with a Lambda behind it that holds the report. Settlement runs through the Coinbase CDP facilitator, which broadcasts the USDC transfer on Arbitrum One.&lt;/p&gt;

&lt;p&gt;The round trip looks like this. The agent does a &lt;code&gt;GET /report&lt;/code&gt;. Lambda@Edge sees no payment and returns 402 with the terms. The agent asks AgentCore to produce a payment for those terms and retries. This time Lambda@Edge has a payment header, calls the facilitator to verify and settle, and on success passes the request through to API Gateway, which returns the gated JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent side
&lt;/h2&gt;

&lt;p&gt;The agent code is short because the wallet logic lives in AgentCore. It makes a normal GET. If the response is anything other than 402, it returns it. If it is a 402, it hands the whole challenge, status, headers, and body, to &lt;code&gt;generate_payment_header&lt;/code&gt;, which reads the terms, signs the authorization inside the embedded wallet, and returns the header to attach. Then it retries the GET.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_with_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;payment_instrument_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_session_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;

    &lt;span class="n"&gt;payment_required_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;proof_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_payment_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payment_instrument_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_instrument_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payment_session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payment_required_request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_required_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;network_preferences&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eip155:42161&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Arbitrum One
&lt;/span&gt;        &lt;span class="n"&gt;client_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proof_headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pass &lt;code&gt;network_preferences=["eip155:42161"]&lt;/code&gt; so the payment targets Arbitrum One rather than relying on a default. The agent never builds EIP-712 typed data and never touches the private key. From the caller's point of view it made one GET and got back a 200 with the report and an Arbiscan link.&lt;/p&gt;

&lt;h2&gt;
  
  
  The merchant side
&lt;/h2&gt;

&lt;p&gt;The payment logic sits in the Lambda@Edge function on &lt;code&gt;viewer-request&lt;/code&gt;. With no payment header it returns the 402 and the &lt;code&gt;accepts[]&lt;/code&gt; terms. With a header it decodes the payment, calls the CDP facilitator's &lt;code&gt;/verify&lt;/code&gt;, then &lt;code&gt;/settle&lt;/code&gt;, and returns the original request so CloudFront continues on to API Gateway. A verify or settle failure comes back as a fresh 402 or a 502.&lt;/p&gt;

&lt;p&gt;One detail to note: CDP's facilitator uses short-lived JWTs that are bound to the exact request URL and method and expire in two minutes. You cannot mint one ahead of time and paste it into config. The edge function signs a fresh JWT with &lt;code&gt;node:crypto&lt;/code&gt; for each verify and settle call, which is also why the CDP key material gets inlined into the edge bundle at build time (Lambda@Edge cannot read environment variables at runtime).&lt;/p&gt;

&lt;p&gt;A design choice worth flagging: this version verifies and settles in &lt;code&gt;viewer-request&lt;/code&gt;, before the origin responds. That keeps the demo to one function and one round trip. For production you want to verify in &lt;code&gt;viewer-request&lt;/code&gt; and settle in &lt;code&gt;viewer-response&lt;/code&gt;, so you only take the money after the content is delivered. &lt;/p&gt;

&lt;h2&gt;
  
  
  Funding it and running it
&lt;/h2&gt;

&lt;p&gt;Setup is one command that creates the AgentCore resources and prints a wallet address. You fund that wallet with USDC on Arbitrum One, grant the agent signing permission, and paste the printed IDs into &lt;code&gt;.env&lt;/code&gt;. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make run-agent
&lt;span class="gp"&gt;GET https://&amp;lt;merchant&amp;gt;&lt;/span&gt;/report
&lt;span class="go"&gt;  via AgentCore PaymentSession payment-session-...
  using Instrument            payment-instrument-...

Status: 200
Body:
{ ...gated report JSON... }

Arbiscan: https://arbiscan.io/tx/0x...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent requests the report, pays, and prints the JSON plus the transaction link. The whole round trip takes a few seconds, most of it the facilitator talking to the chain. The charge in the repo is 0.01 USDC per call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and the preview caveat
&lt;/h2&gt;

&lt;p&gt;Idle, this costs close to nothing. CloudFront, Lambda@Edge, API Gateway, and AgentCore are billed per use, and the demo's traffic fits inside their free tiers. Per call you pay the 0.01 USDC settlement and a rounding error of compute.&lt;/p&gt;

&lt;p&gt;AgentCore Payments is in preview, so treat it that way. Field names and SDK shapes can move between releases, and I hit a few of those while building this. Pin your versions and re-test after you upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;The same pattern extends in two directions worth trying. The agent can pay several different x402 endpoints with the same wallet and session, which makes it a buyer for any priced API it can reach. And the spend budget on the PaymentSession is the natural place to wire an alert or a hard stop, so an agent that misbehaves runs out of allowance instead of running up a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/hummusonrails/arbitrum-x402-aws" rel="noopener noreferrer"&gt;github.com/hummusonrails/arbitrum-x402-aws&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Base Sepolia walkthrough by William: &lt;a href="https://william.mendozagopar.com/blog/bedrock-agentcore-x402.html" rel="noopener noreferrer"&gt;go to his blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;x402 protocol: &lt;a href="https://www.x402.org" rel="noopener noreferrer"&gt;x402.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bedrock AgentCore: &lt;a href="https://aws.amazon.com/bedrock/agentcore" rel="noopener noreferrer"&gt;aws.amazon.com/bedrock/agentcore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;EIP-3009 (Transfer With Authorization): &lt;a href="https://eips.ethereum.org/EIPS/eip-3009" rel="noopener noreferrer"&gt;eips.ethereum.org/EIPS/eip-3009&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>web3</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I let a kosher lobster run my Shabbat automations</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Thu, 23 Apr 2026 06:22:18 +0000</pubDate>
      <link>https://dev.to/bengreenberg/i-let-a-kosher-lobster-run-my-shabbat-automations-5aln</link>
      <guid>https://dev.to/bengreenberg/i-let-a-kosher-lobster-run-my-shabbat-automations-5aln</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I run an Orthodox Jewish household. That means twenty-five hours a week, every week, the family doesn't touch electronics, doesn't cook, and doesn't adjust the thermostat without thinking about it. Multiply that by the Jewish holiday calendar, where a holiday can chain into Shabbat for two days straight, and you have a real scheduling problem. The fridge needs to be in Sabbath mode before candle lighting and back to normal at the conclusion of the day. The bedroom AC needs a pre-cooled run that ends before sunset. The living room AC needs to know whether it's a heat wave week or not. None of these can be touched once the time starts.&lt;/p&gt;

&lt;p&gt;I built a personal automation stack on top of OpenClaw that handles all of it. The lobster is in charge.&lt;/p&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%2Fddp7kgv0dalmsjffvrip.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%2Fddp7kgv0dalmsjffvrip.png" alt="The lobster always checks first"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a small wrinkle here that I think is a bit funny. Lobsters are not kosher. The mascot of the open source agent platform managing my religiously observant home is, in fact, one of the most explicitly non-kosher animals. The lobster AI has been very gracious about all of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used OpenClaw
&lt;/h2&gt;

&lt;p&gt;OpenClaw skills are just directories with a SKILL.md and whatever scripts you want the agent to call. That structure made it natural to build each appliance as its own skill and then layer scheduler scripts on top that get triggered by cron jobs not the LLM.&lt;/p&gt;

&lt;p&gt;This split matters. The schedulers don't need an LLM in the loop. They need to be deterministic, idempotent, and survive me forgetting they exist. The agent layer is for the times I message OpenClaw on Telegram and ask "is the fridge already in Sabbath mode?" and want a real answer back.&lt;/p&gt;

&lt;p&gt;Here are two of the schedulers, with the city ID swapped out so you can drop in your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shabbat scheduler
&lt;/h2&gt;

&lt;p&gt;This one runs every Friday morning. It pulls candle lighting and havdalah times from &lt;a href="https://www.hebcal.com/" rel="noopener noreferrer"&gt;Hebcal&lt;/a&gt;, then schedules &lt;code&gt;at&lt;/code&gt; jobs to flip the fridge into Sabbath mode an hour before candle lighting and back out at it concludes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pythondef&lt;/span&gt; &lt;span class="nf"&gt;get_shabbat_times&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch this week&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s candle lighting and havdalah times.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HEBCAL_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cfg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;geonameid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MY_CITY_GEONAMEID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;candles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;havdalah&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;parasha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;candles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;candles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;havdalah&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;havdalah&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;havdalah&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parashat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;parasha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;parasha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;havdalah&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parasha&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the actual scheduling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pythonsabbath_on_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candles&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;schedule_at_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sabbath_on_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;schedule_at_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;havdalah&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;off&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole shape of it. &lt;a href="https://www.hebcal.com/" rel="noopener noreferrer"&gt;Hebcal&lt;/a&gt; is the single source of truth for times, the appliance skill is the single source of truth for how to talk to the fridge, and the scheduler just glues them together with at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Holiday scheduler
&lt;/h2&gt;

&lt;p&gt;This is the one that took the most thought, because the Jewish holiday calendar is genuinely hostile to naive scheduling. Rosh Hashana is two days. Yom Tov can start as Shabbat ends and run another full day. Passover has a full holiday day with all restrictions on day one, the intermediate days in the middle with no electroni restrictions, and the holiday fully again on the last day. &lt;/p&gt;

&lt;p&gt;You can't just toggle the fridge per holiday day. You need to know when the whole continuous Sabbath-mode period actually ends.&lt;/p&gt;

&lt;p&gt;So the scheduler walks forward through consecutive holiday days and Shabbat as one block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pythondef&lt;/span&gt; &lt;span class="nf"&gt;find_end_of_period&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yomtov_dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Walk forward through consecutive Yom Tov days and Shabbat.
    Handles multi-day Yom Tov, Yom Tov flowing into Shabbat,
    and Shabbat sandwiched between Yom Tov days.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_date&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;next_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;yomtov_dates&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;next_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_day&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most useful piece, though, is the coordination between schedulers. When a holiday starts as Shabbat is ending, the Shabbat scheduler has already queued an &lt;code&gt;OFF&lt;/code&gt; job for the end of Shabbat. If the holiday scheduler runs and just queues its own &lt;code&gt;ON&lt;/code&gt; job on top, you get a brief window where the fridge exits Sabbath mode and re-enters it, which defeats the point. So the Yom Tov scheduler clears the conflicting Shabbat job before doing its own work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pythonif&lt;/span&gt; &lt;span class="n"&gt;dow&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Saturday: Yom Tov starts motzei Shabbat
&lt;/span&gt;    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Yom Tov follows Shabbat, fridge already in sabbath mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cancelling Shabbat OFF job so sabbath mode stays on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;clear_existing_jobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shabbat_scheduler.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one block is the kind of thing working through the full complexity of the calendar introduces you to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reminder
&lt;/h2&gt;

&lt;p&gt;Half an hour before candle lighting, OpenClaw sends me a checklist over Telegram. Nothing too complicated, just a quick list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pythonmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Shabbat Reminder -- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parasha&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Shabbat Shalom&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Candle lighting in 1 hour at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candles_local&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Havdalah at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;havdalah_local&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Checklist:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Lights&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Fridge (SabbathMode)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Door&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Plata / hot water&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reminder doesn't take any actions on the appliances. The schedulers already did that. It's a human-in-the-loop check that the things requiring human judgement (the door key, the lights I forgot to switch) actually got handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The orchestration layer
&lt;/h2&gt;

&lt;p&gt;Everything is wired together with launchd plists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kosher-lobster.openclaw.cron.shabbat-scheduler.plist fires every Friday morning
kosher-lobster.openclaw.cron.yomtov-scheduler.plist fires daily
kosher-lobster.openclaw.cron.shabbat-ac.plist and shabbat-sleep.plist handle the AC zones
kosher-lobster.openclaw.cron.shabbat-reminder.plist fires the Telegram message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AC schedulers are weather-aware. They pull a forecast, checks against a temperature threshold, and only generates &lt;code&gt;ON/OFF&lt;/code&gt; blocks if the forecast warrants it. The data model shows the shape:&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="nx"&gt;tsinterface&lt;/span&gt; &lt;span class="nx"&gt;ShabbatPlan&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;parasha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;havdalah&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;friday_high_f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;saturday_high_f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;threshold_f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;extreme_heat&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;temp_c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;temp_f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShabbatBlock&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;  &lt;span class="c1"&gt;// ON/OFF time blocks&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// active | skipped | cancelled&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mild Friday in spring? No AC plan generated, no electricity wasted. A heat wave week? Blocks get pre-computed and queued. The scheduler decides; I don't wake up at 5 AM to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The thing I underestimated going in was how much of OpenClaw's value comes from the boundary it draws between agent-driven work and deterministic work. I started by trying to make OpenClaw as the LLM "decide" when to flip the fridge. That was a bad idea. There's no room here for an agent reasoning incorrectly about a calendar edge case.&lt;/p&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%2Fm2k62yc28a1y7kjif0vk.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%2Fm2k62yc28a1y7kjif0vk.png" alt="The lobster welcomes Shabbat"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The right pattern was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The scheduler scripts are deterministic, and committable to git&lt;/li&gt;
&lt;li&gt;The skill &lt;code&gt;SKILL.md&lt;/code&gt; files teach the agent how to query state when I ask&lt;/li&gt;
&lt;li&gt;The agent never takes scheduled actions on its own; it answers questions and runs explicit commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenClaw's skill structure made that separation easy to maintain because each skill is just a folder. The schedulers live alongside the skills they call, share the same Python environment, and don't require any agent loop to function. If I uninstalled OpenClaw tomorrow the cron jobs would keep firing and the fridge would keep going into Sabbath mode on time. That's the right resilience property for this kind of automation.&lt;/p&gt;

&lt;p&gt;The other thing I learned is that the agent shines exactly where the schedulers can't help. &lt;/p&gt;

&lt;p&gt;"Is the AC set for this evening?" is a Friday-afternoon question I used to answer by a lot of inquiry into the weather, figuring out what we needed in each room and more. Now I message OpenClaw on Telegram and get the AC schedule shared with me in two seconds. &lt;/p&gt;

&lt;p&gt;The schedulers handle the recurring; the agent handles the one-off. Both of those are first-class in OpenClaw and that's why this works.&lt;/p&gt;

&lt;p&gt;The lobster, against all expectations, has turned out to be the most observant member of the household. He never forgets the time. He never opens the fridge without checking. He has read more of the Jewish calendar API than any of us.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
    </item>
    <item>
      <title>Quick tip: canonical URLs prevent silent SEO damage</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Fri, 03 Apr 2026 11:00:16 +0000</pubDate>
      <link>https://dev.to/bengreenberg/quick-tip-canonical-urls-prevent-silent-seo-damage-3460</link>
      <guid>https://dev.to/bengreenberg/quick-tip-canonical-urls-prevent-silent-seo-damage-3460</guid>
      <description>&lt;p&gt;Quick tip on SEO:&lt;/p&gt;

&lt;p&gt;If your site is accessible at both &lt;code&gt;https://example.com&lt;/code&gt; and &lt;code&gt;https://www.example.com&lt;/code&gt;, Google sees two different sites and splits your ranking signals between them.&lt;/p&gt;

&lt;p&gt;Fix: add a canonical tag to every page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/page"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if yours is set correctly — the free audit tool at &lt;a href="https://audit.hummusonrails.com/free" rel="noopener noreferrer"&gt;https://audit.hummusonrails.com/free&lt;/a&gt; checks canonical tags along with 4 other key issues.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>devtips</category>
    </item>
    <item>
      <title>Your backend code is a black box. It doesn't have to be.</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Wed, 01 Apr 2026 09:07:54 +0000</pubDate>
      <link>https://dev.to/arbitrum/your-backend-code-is-a-black-box-it-doesnt-have-to-be-59bd</link>
      <guid>https://dev.to/arbitrum/your-backend-code-is-a-black-box-it-doesnt-have-to-be-59bd</guid>
      <description>&lt;p&gt;Your API takes inputs and returns outputs. The logic in between? Nobody outside your team can verify it. Regulators read documentation you wrote about it. Partners call your endpoint and trust the response. Users don't even get that.&lt;/p&gt;

&lt;p&gt;This has worked for decades. But there's a growing category of backend logic where "trust us" isn't enough. Scoring algorithms, compliance checks, eligibility rules, validation pipelines. Anywhere someone needs to independently confirm that your code does what you say it does.&lt;/p&gt;

&lt;p&gt;You don't have a code problem. You have a verifiability problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for publicly auditable code
&lt;/h2&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%2Fvblmmb49iy7jrpp5e8h5.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%2Fvblmmb49iy7jrpp5e8h5.png" alt="Black box vs verifiable compute" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most backend engineers don't think about code auditability because most code doesn't need it. Your CRUD endpoints, your auth flows, your data transformations, those are internal concerns. Nobody outside your org needs to verify them.&lt;/p&gt;

&lt;p&gt;But some functions carry weight. A scoring algorithm that determines loan eligibility. A validation pipeline that checks regulatory compliance. A pricing engine that partners depend on. For these functions, the standard answer is documentation: you write a spec, maybe you share pseudocode, and everyone agrees to trust that the running code matches the paper.&lt;/p&gt;

&lt;p&gt;That's not verification. That's trust with extra steps.&lt;/p&gt;

&lt;p&gt;Publicly auditable code means anyone can call the same function with the same inputs and confirm they get the same outputs. The logic is visible. Execution is deterministic. And the code can't change silently after deployment.&lt;/p&gt;

&lt;p&gt;This isn't a new idea. Open source gives you code visibility. But open source doesn't prove that the code running in production is the same code in the repo. You need a runtime where the deployed code is the source of truth, and where every execution is independently reproducible.&lt;/p&gt;

&lt;p&gt;That runtime exists. And you don't need to learn a new language to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust in, verifiable execution out
&lt;/h2&gt;

&lt;p&gt;When most engineers hear "deploy code onchain," they picture learning Solidity, memorizing gas optimization tricks, and rewriting working logic in an unfamiliar language. That's a reasonable reason to stop listening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.arbitrum.io/stylus/gentle-introduction" rel="noopener noreferrer"&gt;Arbitrum Stylus&lt;/a&gt; skips that detour. You write Rust, compile to WASM, and deploy it to a blockchain that's optimized for low-cost computation. Your existing toolchain, your existing crates, your existing mental model. The contract runs alongside Solidity smart contracts with shared state, but you never need to touch Solidity yourself.&lt;/p&gt;

&lt;p&gt;This isn't about converting you to crypto. It's about giving you a deployment target where execution is publicly verifiable by default.&lt;/p&gt;

&lt;p&gt;Three properties matter here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every execution is independently auditable.&lt;/strong&gt; Anyone can call the contract with the same inputs and confirm they get the same outputs. No trust required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The logic is immutable.&lt;/strong&gt; Once deployed, the code can't change silently. If you push an update, that's a new deployment with its own address and its own history. No "we patched it last Tuesday but forgot to tell you."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution is deterministic.&lt;/strong&gt; Same inputs, same outputs, every time, on every node. No environment-specific drift, no floating point surprises across hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the architecture looks like
&lt;/h2&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%2Frk0oqvh90439evj9hyur.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%2Frk0oqvh90439evj9hyur.png" alt="Scoring engine architecture" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine" rel="noopener noreferrer"&gt;scoring engine&lt;/a&gt; to test this pattern. The architecture splits cleanly between what stays in your infrastructure and what moves onchain:&lt;/p&gt;

&lt;p&gt;Your backend stays exactly where it is. An Axum API server handles business rules, authentication, request routing. Normal backend work. The only difference is that when the API needs a computation verified, it calls an onchain contract instead of a local function.&lt;/p&gt;

&lt;p&gt;The onchain piece is a Stylus contract. It takes parameters, runs the calculation, and returns results. No state storage, just pure computation. Think of it as a function you deploy instead of host.&lt;/p&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%2F1w298ib5f7tb8xt2djjc.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%2F1w298ib5f7tb8xt2djjc.png" alt="Shared crate compilation" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A shared Rust crate holds the type definitions and compiles for both environments. The contract uses &lt;code&gt;no_std&lt;/code&gt; for WASM. The API server uses &lt;code&gt;std&lt;/code&gt; for native. Same types, both sides. The compiler guarantees they match. No serialization mismatches, no type drift between your API and your contract.&lt;/p&gt;

&lt;p&gt;One language across the entire stack. The chain boundary becomes a function call, not a language barrier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The performance question
&lt;/h2&gt;

&lt;p&gt;The first objection any backend engineer raises: what's the overhead?&lt;/p&gt;

&lt;p&gt;Fair. Onchain computation has historically been expensive. That's the whole reason Solidity exists. It was designed to minimize execution cost on the EVM.&lt;/p&gt;

&lt;p&gt;Stylus changes the math. WASM execution on Arbitrum is dramatically cheaper than EVM execution for compute-heavy workloads. The scoring engine used over 90% less gas than the equivalent Solidity contract doing identical work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Gas Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Offchain Rust&lt;/td&gt;
&lt;td&gt;953&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onchain Stylus (WASM)&lt;/td&gt;
&lt;td&gt;953&lt;/td&gt;
&lt;td&gt;76,048&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onchain Solidity (EVM)&lt;/td&gt;
&lt;td&gt;810&lt;/td&gt;
&lt;td&gt;1,027,635&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Storage operations cost the same either way, but iterative math, weighted calculations, and loop-heavy logic is where WASM pulls ahead.&lt;/p&gt;

&lt;p&gt;This isn't a contrived benchmark. &lt;a href="https://redstone.finance/" rel="noopener noreferrer"&gt;RedStone&lt;/a&gt;, an oracle provider, &lt;a href="https://blog.arbitrum.io/how-redstone-is-advancing-oracle-capabilities-with-stylus" rel="noopener noreferrer"&gt;published similar results&lt;/a&gt; after porting their verification logic to Stylus. Compute-intensive work is where this architecture pays for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where auditable code matters
&lt;/h2&gt;

&lt;p&gt;Not every function belongs onchain. But some backend logic sits in a category where "trust me" isn't good enough:&lt;/p&gt;

&lt;p&gt;Scoring and risk calculations, where regulators want to audit the exact computation, not a description of it. Eligibility determinations, where partners want to verify outcomes independently instead of trusting your API response. Compliance validation, where auditors want proof that the logic running today is the same logic approved last quarter. Data pipelines, where counterparties want to confirm their data passed through agreed-upon rules.&lt;/p&gt;

&lt;p&gt;If you've ever written documentation explaining how your algorithm works so someone else could trust it, that's the function that belongs onchain. Let them verify the code instead of reading your summary of the code.&lt;/p&gt;

&lt;p&gt;The scoring engine I built is one example of this pattern. But the pattern is the point, not the example. Any pure function where independent verification adds value is a candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd actually do on a Monday morning
&lt;/h2&gt;

&lt;p&gt;If you write Rust, the learning curve is smaller than you think.&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://github.com/OffchainLabs/cargo-stylus" rel="noopener noreferrer"&gt;&lt;code&gt;cargo-stylus&lt;/code&gt;&lt;/a&gt;, which handles compilation and deployment. Write your function with &lt;code&gt;#[entrypoint]&lt;/code&gt; and &lt;code&gt;#[public]&lt;/code&gt; macros. Compile to WASM. Deploy to a testnet. Call it from your existing API using an RPC endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[entrypoint]&lt;/span&gt;
&lt;span class="nd"&gt;#[storage]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ScoringEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[public]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ScoringEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Factor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Your computation logic here&lt;/span&gt;
        &lt;span class="c1"&gt;// Publicly verifiable, deterministic, immutable&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;Your database, your auth, your business logic, none of that moves. You add one function that runs somewhere verifiable instead of somewhere trusted. Everything else stays the same.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine" rel="noopener noreferrer"&gt;scoring engine repo&lt;/a&gt; has working code, setup instructions, and benchmarks comparing three execution paths: onchain Stylus, onchain Solidity, and offchain Rust. It's a concrete starting point, but the approach applies anywhere you need auditable computation.&lt;/p&gt;
&lt;h2&gt;
  
  
  You're adding a deployment target, not adopting a lifestyle
&lt;/h2&gt;

&lt;p&gt;The shift is smaller than it sounds. The same way you might deploy a function to Lambda for serverless execution or to a TEE for confidential computing, you deploy to Arbitrum for verifiable execution.&lt;/p&gt;

&lt;p&gt;Your Rust skills transfer directly. Your toolchain stays the same. The only thing that changes is who can verify your logic: everyone.&lt;/p&gt;

&lt;p&gt;If you've been dismissing onchain as irrelevant to backend work, spend thirty minutes with the &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/hummusonrails" rel="noopener noreferrer"&gt;
        hummusonrails
      &lt;/a&gt; / &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine" rel="noopener noreferrer"&gt;
        stylus-scoring-engine
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Verifiable credit/risk scoring engine: Stylus (Rust/WASM) vs Solidity on Arbitrum with three-way gas benchmark
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hummusonrails/stylus-scoring-engine/.github/banner.svg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fhummusonrails%2Fstylus-scoring-engine%2FHEAD%2F.github%2Fbanner.svg" alt="stylus-scoring-engine" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/942e017bf0672002dd32a857c95d66f28c5900ab541838c6c664442516309c8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c61742d737175617265" alt="License"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/c609e2b41de6e411f523c403fb94432207ee58321549a51ad8398529273854eb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d312e38382d6f72616e67652e7376673f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/c609e2b41de6e411f523c403fb94432207ee58321549a51ad8398529273854eb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d312e38382d6f72616e67652e7376673f7374796c653d666c61742d737175617265" alt="Rust"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/47a6086701dfe590e01cab365b8e6a5ef4fb5fbef9696ace98b09ee0d2133304/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374796c75732d2d73646b2d302e31302e302d3132414146462e7376673f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/47a6086701dfe590e01cab365b8e6a5ef4fb5fbef9696ace98b09ee0d2133304/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374796c75732d2d73646b2d302e31302e302d3132414146462e7376673f7374796c653d666c61742d737175617265" alt="Stylus SDK"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d49b7b9955764e444eb46dfd7f8e2fd481ec8fba30e9b7578e8d7a6ebb1fb480/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6178756d2d302e382d707572706c652e7376673f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/d49b7b9955764e444eb46dfd7f8e2fd481ec8fba30e9b7578e8d7a6ebb1fb480/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6178756d2d302e382d707572706c652e7376673f7374796c653d666c61742d737175617265" alt="Axum"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/25b3e6d0d42c98de74a98cbb4d149a1c09020cf6d1361993b72d7d5b8ffed363/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265" alt="PRs Welcome"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;Verifiable credit/risk scoring on Arbitrum with a three-way gas benchmark: offchain Rust vs onchain Stylus vs onchain Solidity.&lt;/strong&gt;
  &lt;br&gt;
  &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine#quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt; · &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt; · &lt;a href="https://github.com/hummusonrails/stylus-scoring-engine/issues" rel="noopener noreferrer"&gt;Report a Bug&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmarks&lt;/strong&gt; the same scoring algorithm across three execution environments with real gas numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scores&lt;/strong&gt; entities against configurable weighted rules with iterative convergence and cross-factor correlation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demonstrates&lt;/strong&gt; Stylus gas savings on compute-heavy workloads (over 90% cheaper than Solidity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shares&lt;/strong&gt; Rust types between the onchain contract and offchain API server from a single crate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stores&lt;/strong&gt; scoring rules in SQLite with full CRUD via REST endpoints&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hummusonrails/stylus-scoring-engine/.github/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fhummusonrails%2Fstylus-scoring-engine%2FHEAD%2F.github%2Fdemo.gif" alt="Deploy and benchmark demo" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; clone and install&lt;/span&gt;
git clone https://github.com/hummusonrails/stylus-scoring-engine.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; stylus-scoring-engine
pnpm install

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; start the local arbitrum devnode&lt;/span&gt;
pnpm devnode

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; in a new terminal, deploy both contracts&lt;/span&gt;
pnpm deploy:all

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; start the api server (reads .env written by deploy)&lt;/span&gt;
pnpm api

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; in a new terminal, run the three-way benchmark&lt;/span&gt;
pnpm benchmark&lt;/pre&gt;

&lt;/div&gt;

&lt;strong&gt;Prerequisites&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rustup.rs/" rel="nofollow noopener noreferrer"&gt;Rust&lt;/a&gt; 1.88+ with…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hummusonrails/stylus-scoring-engine" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The gap between "interesting in theory" and "I could actually use this" is shorter than you expect.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webassembly</category>
      <category>blockchain</category>
      <category>backend</category>
    </item>
    <item>
      <title>The readability scores your content tool is missing</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Tue, 31 Mar 2026 06:00:28 +0000</pubDate>
      <link>https://dev.to/bengreenberg/the-readability-scores-your-content-tool-is-missing-39be</link>
      <guid>https://dev.to/bengreenberg/the-readability-scores-your-content-tool-is-missing-39be</guid>
      <description>&lt;h1&gt;
  
  
  The Readability Scores Your Content Tool Is Missing
&lt;/h1&gt;

&lt;p&gt;Most readability tooling stops at a single score. That is a problem if you are building a documentation pipeline, a content linter, or any system that needs to catch unreadable text before it ships.&lt;/p&gt;

&lt;p&gt;Here are the four metrics worth tracking, what each one actually measures, and what your targets should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flesch-Kincaid Grade Level
&lt;/h2&gt;

&lt;p&gt;This score maps text to a U.S. school grade level based on two inputs: average sentence length and average word length in syllables. A score of 8 means a typical 13-year-old can read it without friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target: 6 to 9 for most technical docs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your score is above 12, sentences are too long or you are leaning on polysyllabic jargon. Users will skim past dense paragraphs instead of reading them. If your score is below 5, you are likely oversimplifying to the point where context is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flesch Reading Ease
&lt;/h2&gt;

&lt;p&gt;This uses the same inputs as FK Grade Level but outputs an inverse score on a 0 to 100 scale. Higher means easier. It weights sentence length more heavily than syllable count, so it punishes run-on sentences hard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target: 60 to 70 for technical documentation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below 50 and you are in academic or legal territory. Most readers will not finish the section. Above 75 and you may be losing precision, which matters in technical writing where exact phrasing carries meaning. The 60 to 70 range is the practical sweet spot where clarity and accuracy coexist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Readability Index (ARI)
&lt;/h2&gt;

&lt;p&gt;ARI takes a different approach. Instead of counting syllables, it counts characters per word. This makes it faster to compute and less sensitive to syllabification edge cases. It outputs a grade-level score similar to FK but often diverges on technical content, where long words are common but not necessarily difficult for the target audience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target: 7 to 10 for developer docs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Where ARI earns its place is as a cross-check. If FK says grade 8 but ARI says grade 14, you likely have a cluster of long technical terms inflating the character count. That is worth reviewing even if the content reads fine to a subject matter expert, because new users will not have that context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sentence Length Variance
&lt;/h2&gt;

&lt;p&gt;Most tools report average sentence length and stop there. Variance is the signal they miss. Text where every sentence is roughly the same length reads as monotonous and is harder to parse. Alternating short and long sentences creates rhythm, which keeps readers oriented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target: Standard deviation of 8 to 15 words across your sentence lengths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below 5 means your writing is flat. Above 20 means sentence structure is inconsistent in a way that will confuse automated parsers and human readers alike. This is especially relevant in procedural docs where scannable short sentences should anchor longer explanatory ones.&lt;/p&gt;




&lt;p&gt;I built a TextAnalytics API that returns all of these scores in a single call -- useful if you are building a linter, a CMS plugin, or just want to quality-gate your content pipeline: &lt;a href="https://rapidapi.com/ben-eI6jno4PU/api/textanalytics" rel="noopener noreferrer"&gt;https://rapidapi.com/ben-eI6jno4PU/api/textanalytics&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>writing</category>
      <category>api</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Quick tip: Flesch-Kincaid Grade Level 8 is your target</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Fri, 27 Mar 2026 11:00:05 +0000</pubDate>
      <link>https://dev.to/bengreenberg/quick-tip-flesch-kincaid-grade-level-8-is-your-target-ci7</link>
      <guid>https://dev.to/bengreenberg/quick-tip-flesch-kincaid-grade-level-8-is-your-target-ci7</guid>
      <description>&lt;p&gt;Quick tip on writing docs and content:&lt;/p&gt;

&lt;p&gt;Flesch-Kincaid Grade Level 8 = readable by a 13-year-old. That's your target for most technical documentation.&lt;/p&gt;

&lt;p&gt;Not because your readers are 13 — but because shorter sentences and common words reduce cognitive load. Even senior engineers read faster at grade 8.&lt;/p&gt;

&lt;p&gt;Most enterprise docs sit at grade 14-16. That's why people skim them.&lt;/p&gt;

&lt;p&gt;The TextAnalytics API returns FK grade level (plus 4 other readability scores) in a single call: &lt;a href="https://rapidapi.com/ben-eI6jno4PU/api/textanalytics" rel="noopener noreferrer"&gt;https://rapidapi.com/ben-eI6jno4PU/api/textanalytics&lt;/a&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>productivity</category>
      <category>devtips</category>
      <category>api</category>
    </item>
    <item>
      <title>I built a link preview API — here's what I learned about Open Graph</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Tue, 24 Mar 2026 07:01:41 +0000</pubDate>
      <link>https://dev.to/bengreenberg/i-built-a-link-preview-api-heres-what-i-learned-about-open-graph-2j99</link>
      <guid>https://dev.to/bengreenberg/i-built-a-link-preview-api-heres-what-i-learned-about-open-graph-2j99</guid>
      <description>&lt;h2&gt;
  
  
  I Built a Link Preview API — Here's What I Learned About Open Graph
&lt;/h2&gt;

&lt;p&gt;Link previews seem simple until you actually build something that generates them reliably. I spent weeks digging into how platforms parse Open Graph metadata, and I kept running into the same category of problems: missing images, wrong fallbacks, cached bad data. Here is what surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Link Previews Actually Are (And Why They Break)
&lt;/h2&gt;

&lt;p&gt;When you paste a URL into Slack or Twitter, the platform fetches that page, reads the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, and renders a card. The Open Graph protocol, originally developed by Facebook, defines the standard tags most platforms follow: &lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, &lt;code&gt;og:image&lt;/code&gt;, and &lt;code&gt;og:url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason previews break so often comes down to a few recurring patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;og:image&lt;/code&gt; tag is missing entirely&lt;/li&gt;
&lt;li&gt;The image URL is relative instead of absolute&lt;/li&gt;
&lt;li&gt;The image dimensions are wrong and the platform rejects it silently&lt;/li&gt;
&lt;li&gt;The metadata exists but the page blocks crawlers with a bad &lt;code&gt;robots.txt&lt;/code&gt; or a JavaScript render wall&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is brutal. If your content is rendered client-side and you have no SSR or prerendering, most crawlers will fetch an empty shell and your preview will be blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  The og:image Requirements Most Devs Skip
&lt;/h2&gt;

&lt;p&gt;I see this constantly. Developers add an &lt;code&gt;og:image&lt;/code&gt; tag, the preview still looks broken, and they cannot figure out why. The requirements are stricter than the documentation implies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions should be 1200x630 pixels.&lt;/strong&gt; Facebook and LinkedIn will downscale, but if you go too small (under 200x200) they ignore the image completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File size should stay under 1MB.&lt;/strong&gt; Larger images may time out during the crawl window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The URL must be absolute.&lt;/strong&gt; &lt;code&gt;/images/preview.png&lt;/code&gt; will not work. &lt;code&gt;https://yourdomain.com/images/preview.png&lt;/code&gt; will.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No SVGs.&lt;/strong&gt; Almost no platform will render an SVG as a preview image. Use PNG or JPEG.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How Twitter Cards Differ From Open Graph
&lt;/h2&gt;

&lt;p&gt;Twitter does read OG tags as a fallback, but it has its own system called Twitter Cards, and the &lt;code&gt;twitter:card&lt;/code&gt; type changes how everything renders.&lt;/p&gt;

&lt;p&gt;The two types you actually care about are &lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;summary_large_image&lt;/code&gt;. If you use &lt;code&gt;summary&lt;/code&gt;, Twitter shows a small thumbnail beside the text. If you use &lt;code&gt;summary_large_image&lt;/code&gt;, you get the full-width banner image. Most developers want the large image but forget to set the tag, so they get the small thumbnail and wonder why it looks wrong compared to LinkedIn.&lt;/p&gt;

&lt;p&gt;You also need &lt;code&gt;twitter:title&lt;/code&gt; and &lt;code&gt;twitter:description&lt;/code&gt; even if you already have the OG equivalents. Twitter will use OG as a fallback, but being explicit is more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The og:description Fallback Chain
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;og:description&lt;/code&gt; is missing, platforms do not just leave it blank. The fallback chain typically goes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;og:description&lt;/code&gt; meta tag&lt;/li&gt;
&lt;li&gt;Standard &lt;code&gt;meta name="description"&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;First paragraph of visible body text&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Knowing this means you can usually ensure something reasonable shows up even on pages that have not been fully optimized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Gotcha That Bit Me Hard
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;og:url&lt;/code&gt; does not match the canonical URL of the page, platforms will cache the preview against the wrong URL. I had a staging URL leak into production metadata once and spent an embarrassing amount of time wondering why the preview was showing old content. Always set &lt;code&gt;og:url&lt;/code&gt; explicitly to the canonical version.&lt;/p&gt;




&lt;p&gt;Parsing all of this reliably across different platforms, redirect chains, and malformed HTML is exactly the kind of work that sounds quick and turns into a week of edge cases. I use what I built in production at LinkPreview API. It handles the messy parts of parsing OG data so you do not have to: &lt;a href="https://rapidapi.com/ben-eI6jno4PU/api/linkpreview1" rel="noopener noreferrer"&gt;https://rapidapi.com/ben-eI6jno4PU/api/linkpreview1&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
