<?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>The Redirect Is Part of the Threat Model: Hardening MCP Client Connections</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Mon, 14 Sep 2026 15:50:20 +0000</pubDate>
      <link>https://dev.to/bengreenberg/the-redirect-is-part-of-the-threat-model-hardening-mcp-client-connections-3oa0</link>
      <guid>https://dev.to/bengreenberg/the-redirect-is-part-of-the-threat-model-hardening-mcp-client-connections-3oa0</guid>
      <description>&lt;p&gt;I was reading the release notes for the MCP Python SDK while planning this month’s AAIF Ambassador contribution, and one change stopped me: clients on 2.x now follow HTTP redirects only when they remain within the endpoint’s origin.&lt;/p&gt;

&lt;p&gt;That’s a good default.&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%2F5o5bmz3x42ftwep2zs9w.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%2F5o5bmz3x42ftwep2zs9w.png" alt="Redirect decision tree" width="800" height="1016"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A redirect can move a client from the server it was configured to trust to somewhere else. If your client carries an authenticated session, OAuth state, or tool-discovery requests along for that move, you’ve expanded the set of endpoints that can receive them.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.2.0" rel="noopener noreferrer"&gt;MCP Python SDK v2.2.0 release&lt;/a&gt;, published September 7, makes the boundary explicit. &lt;code&gt;Client("https://...")&lt;/code&gt;, &lt;code&gt;streamable_http_client&lt;/code&gt;, and &lt;code&gt;sse_client&lt;/code&gt; follow redirects only when the scheme, host, and port stay the same. An &lt;code&gt;http&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt; upgrade on the same host is allowed. A redirect anywhere else fails, and the session remains usable.&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%2Fsx3bzppg2ptzlaqvbgq3.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%2Fsx3bzppg2ptzlaqvbgq3.png" alt="What an origin-bound redirect check compares" width="799" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the configured origin as an authority boundary
&lt;/h2&gt;

&lt;p&gt;MCP clients connect to endpoints that can expose tools and prompts, then ask users and agents to act on the results. That makes the endpoint URL more than a convenience setting. It identifies where your client can establish a session and where it can send authenticated requests.&lt;/p&gt;

&lt;p&gt;Why does a redirect change that? Because HTTP redirects carry authority in a way application code can easily overlook. A client starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp.example.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the server responds with a redirect to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://other.example.net/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A general-purpose HTTP client may be happy to follow it. An MCP client needs a stricter answer: &lt;code&gt;other.example.net&lt;/code&gt; was never the configured server.&lt;/p&gt;

&lt;p&gt;The Python SDK now applies this rule to OAuth provider requests too. That closes a gap where the MCP transport might enforce an origin boundary while the authentication flow followed a different redirect policy.&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%2Frq1xfkbd689fu35g61sx.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%2Frq1xfkbd689fu35g61sx.png" alt="Redirect policy must cover every client path" width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a small rule with a direct consequence: if the server you intended to use lives at another origin, configure that URL explicitly. Don’t let a redirect decide it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirects are also deployment changes
&lt;/h2&gt;

&lt;p&gt;Same-origin redirects still have valid uses. A server may redirect &lt;code&gt;/mcp&lt;/code&gt; to &lt;code&gt;/mcp/&lt;/code&gt;, or route traffic through a path that preserves the same scheme, host, and port. The SDK release notes call out the trailing-slash case: clients no longer need an &lt;code&gt;httpx.AsyncClient&lt;/code&gt; configured with &lt;code&gt;follow_redirects&lt;/code&gt; for MCP requests.&lt;/p&gt;

&lt;p&gt;That means maintainers should treat endpoint changes as part of their security review, even when the change looks like routing cleanup.&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%2Fbaxutn9poe3zud6qxfe3.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%2Fbaxutn9poe3zud6qxfe3.png" alt="Endpoint migration decision" width="800" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ask one question before shipping an endpoint redirect: does the final URL have the same scheme, host, and port as the URL users configure?&lt;/p&gt;

&lt;p&gt;If the answer is no, publish the new endpoint and let clients opt into it. A redirect is the wrong migration mechanism when it crosses an origin boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  A review checklist for MCP client maintainers
&lt;/h2&gt;

&lt;p&gt;When you change MCP connection handling, check the following:&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%2Fdkdqevyc63kzcdwnoiw1.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%2Fdkdqevyc63kzcdwnoiw1.png" alt="MCP redirect review areas" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Endpoint normalization:&lt;/strong&gt; Confirm that adding or removing a trailing slash stays within the configured origin. Don’t rewrite a user-provided endpoint to a different host or port.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redirect behavior:&lt;/strong&gt; Enforce the origin check in every MCP transport you support. The Python SDK’s release names standard client connections, Streamable HTTP, and SSE connections. Your implementation should not leave one transport with a looser policy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OAuth flows:&lt;/strong&gt; Apply the same redirect rule to authorization, token, and metadata requests that your MCP client makes. Authentication code often uses a separate HTTP client or redirect setting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Session state:&lt;/strong&gt; When a redirect is rejected, keep the existing session state usable where your transport allows it. The Python SDK reports an &lt;code&gt;MCPError&lt;/code&gt; for disallowed redirects and keeps the session available. An SSE connection fails with &lt;code&gt;httpx2.HTTPStatusError&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authenticated tool discovery:&lt;/strong&gt; Don’t send credentials or session-bound headers to an origin that wasn’t explicitly configured. This includes requests used to discover which tools a server offers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error messages:&lt;/strong&gt; Tell the user the redirect was rejected because it left the endpoint’s origin, and show the target URL only when it is safe to expose. The useful remediation is clear: configure the intended endpoint directly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests:&lt;/strong&gt; Add cases for a same-origin path redirect, an &lt;code&gt;http&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt; upgrade on the same host, and a redirect to a different host. Also test OAuth requests separately from transport requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; gives projects such as MCP a neutral home for the protocols and open-source software that let agents work across tools and frameworks. Interoperability depends on clients agreeing on predictable behavior at boundaries like this one.&lt;/p&gt;

&lt;p&gt;A configured MCP endpoint should remain the authority boundary.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>news</category>
      <category>security</category>
    </item>
    <item>
      <title>Where MCP Ends and A2A Begins: Building a Two-Agent Support Workflow Without Tool-Wrapping</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Fri, 11 Sep 2026 13:20:22 +0000</pubDate>
      <link>https://dev.to/bengreenberg/where-mcp-ends-and-a2a-begins-building-a-two-agent-support-workflow-without-tool-wrapping-3l20</link>
      <guid>https://dev.to/bengreenberg/where-mcp-ends-and-a2a-begins-building-a-two-agent-support-workflow-without-tool-wrapping-3l20</guid>
      <description>&lt;p&gt;When an agent needs help from another service, there is an architectural question to answer first: should that service be exposed as a tool, or should the agent delegate work to another agent?&lt;/p&gt;

&lt;p&gt;The distinction matters when the service on the other side is itself autonomous.&lt;/p&gt;

&lt;p&gt;A diagnostic agent, for example, may need to request missing context, investigate across several systems, maintain state across multiple exchanges, and eventually return a report. Exposing that agent as a function such as &lt;code&gt;run_diagnostics()&lt;/code&gt; can flatten those behaviors into a tool-shaped interface.&lt;/p&gt;

&lt;p&gt;MCP and A2A provide a cleaner separation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; standardizes how models and agents interact with tools, APIs, data sources, and other capabilities. &lt;a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/" rel="noopener noreferrer"&gt;A2A&lt;/a&gt; standardizes communication between independent agents that need to discover one another, exchange context, delegate work, and manage stateful tasks.&lt;/p&gt;

&lt;p&gt;For the support workflow in this tutorial, the boundary is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP is how the support agent directly uses tools and resources available within its operating environment.&lt;/li&gt;
&lt;li&gt;A2A is how the support agent delegates work to another agent that owns its own execution process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction changes the interface you build.&lt;/p&gt;

&lt;p&gt;A2A and MCP now also share a governance home. A2A became a Growth Stage project of the &lt;a href="https://aaif.io/blog/a2a-joins-aaif" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt;, joining MCP and other open agentic infrastructure projects under the Linux Foundation.&lt;/p&gt;

&lt;p&gt;Let’s build the smallest useful version of that boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The support workflow
&lt;/h2&gt;

&lt;p&gt;Consider two agents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A support agent receives a developer issue: “My deployment completed, but the API returns 401.”&lt;/li&gt;
&lt;li&gt;A diagnostic agent knows how to inspect deployment configuration and identity-provider state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The support agent has MCP tools for operations it performs directly: searching the support knowledge base, reading the ticket, and retrieving deployment metadata from systems it can access.&lt;/p&gt;

&lt;p&gt;Those tools do not have to run on the same machine as the support agent. The important distinction is that the support agent invokes them as capabilities and controls how they are composed into its workflow.&lt;/p&gt;

&lt;p&gt;The diagnostic agent is different. It owns its own process. It may inspect several systems, request additional context, perform a longer-running investigation, or produce a report after several interactions.&lt;/p&gt;

&lt;p&gt;That is where A2A fits.&lt;/p&gt;

&lt;p&gt;The handoff looks like this:&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%2Fwt1xf1a640msfysa7r42.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%2Fwt1xf1a640msfysa7r42.png" alt="A developer reports an API authentication problem to a support agent, which directly uses MCP tools and delegates the investigation to an A2A diagnostic agent." width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The useful boundary is not simply “local versus remote.” It is capability use versus agent delegation.&lt;/p&gt;

&lt;p&gt;If an agent needs to invoke a defined capability directly, MCP is usually the appropriate interface. If it needs to delegate a goal to an independently operating agent and let that agent manage its own process, A2A provides the protocol primitives for that interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the Agent Card
&lt;/h2&gt;

&lt;p&gt;Before the support agent delegates anything, it needs to know whether the diagnostic agent can handle the request.&lt;/p&gt;

&lt;p&gt;A2A uses an &lt;a href="https://a2a-protocol.org/latest/topics/agent-discovery/" rel="noopener noreferrer"&gt;Agent Card&lt;/a&gt; for this. It is a JSON metadata document describing an agent’s identity, service interfaces, supported capabilities, security requirements, and skills.&lt;/p&gt;

&lt;p&gt;For agents using well-known discovery, the card can be published at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-agent-domain/.well-known/agent-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official &lt;a href="https://github.com/a2aproject/a2a-cli" rel="noopener noreferrer"&gt;A2A CLI&lt;/a&gt; provides a direct way to inspect it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;a2a card get https://agent.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;https://agent.example.com&lt;/code&gt; with an A2A agent endpoint you operate or can access.&lt;/p&gt;

&lt;p&gt;The agent card is the client’s description of how the remote agent can be used.&lt;/p&gt;

&lt;p&gt;For this workflow, the support agent needs answers to a few practical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this agent advertise a skill related to deployment diagnostics?&lt;/li&gt;
&lt;li&gt;Does it support streaming?&lt;/li&gt;
&lt;li&gt;Which security schemes does it declare?&lt;/li&gt;
&lt;li&gt;Which A2A interfaces and endpoints does it expose?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The support agent can use that information to determine whether the agent is appropriate before sending ticket or deployment context across the boundary.&lt;/p&gt;

&lt;p&gt;This is different from a conventional tool call. With an MCP tool, the client can discover a defined tool schema and invoke that capability. With A2A, the client discovers an agent capable of accepting broader work and interacting through the A2A message and task model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep directly operated capabilities in MCP
&lt;/h2&gt;

&lt;p&gt;Before delegating the investigation, the support agent might use MCP tools such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search_knowledge_base("deployment completed API 401")
get_ticket_context(ticket_id)
get_deployment_metadata(deployment_id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are capabilities the support agent is operating directly. It chooses the calls, controls their sequence, and consumes the results as part of its own reasoning process.&lt;/p&gt;

&lt;p&gt;It might be tempting to expose the diagnostic agent as another tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diagnose_deployment(deployment_id, error)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can work when the interaction really is equivalent to a discrete capability invocation.&lt;/p&gt;

&lt;p&gt;The abstraction becomes less useful when the system behind it needs to behave as an agent. It may need information that was not available when the call started. It may need the caller to authorize access. It may perform work long enough to require lifecycle tracking. It may generate one or more artifacts as the investigation proceeds.&lt;/p&gt;

&lt;p&gt;Those cases can lead the tool wrapper to accumulate custom state, polling, callbacks, and continuation mechanisms.&lt;/p&gt;

&lt;p&gt;A2A already provides protocol concepts for those interactions.&lt;/p&gt;

&lt;p&gt;Use MCP to gather the information needed for a useful delegation request. Then send the goal and relevant context to the diagnostic agent through A2A.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegate the investigation
&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%2Fi0ph0aifg46jxzf1rmrm.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%2Fi0ph0aifg46jxzf1rmrm.png" alt="The support agent fetches the diagnostic agent's Agent Card, gathers local context using MCP tools, and sends an A2A delegation request that returns either a message or task." width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the Agent Card has been checked and the initial context collected, the support agent can send a message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;a2a send &lt;span class="nt"&gt;-a&lt;/span&gt; https://agent.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Investigate deployment dep_123. The deployment completed, but requests return 401. The affected API is orders."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official CLI can negotiate among the A2A interfaces advertised by the agent, including JSON-RPC, HTTP+JSON/REST, and gRPC.&lt;/p&gt;

&lt;p&gt;That means the client can interact through the A2A abstraction instead of maintaining a different application-level integration for every agent implementation.&lt;/p&gt;

&lt;p&gt;There is one important detail here: sending an A2A message does not always create a task.&lt;/p&gt;

&lt;p&gt;According to the protocol, the remote agent can return either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;Message&lt;/code&gt;, for an immediate interaction that does not require task tracking, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;Task&lt;/code&gt;, for stateful work that needs lifecycle management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a diagnostic investigation that takes time or may require additional input, a &lt;code&gt;Task&lt;/code&gt; is the more relevant model.&lt;/p&gt;

&lt;p&gt;A task gives the agents a shared unit of stateful work. It has an identifier, a lifecycle, status information, and potentially artifacts representing outputs of the work.&lt;/p&gt;

&lt;p&gt;A task can remain active while the diagnostic agent investigates. It can move into a state indicating that more input or authorization is required. It can produce artifacts such as a diagnostic report. The client can later retrieve the task or request cancellation.&lt;/p&gt;

&lt;p&gt;That is a different interaction model from invoking a function and waiting for its return value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream updates when the user is waiting
&lt;/h2&gt;

&lt;p&gt;Support workflows become difficult to reason about when a remote investigation starts and the calling application receives no information until completion.&lt;/p&gt;

&lt;p&gt;A2A supports streaming for agents that advertise the capability in their Agent Card.&lt;/p&gt;

&lt;p&gt;With the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;a2a send &lt;span class="nt"&gt;-a&lt;/span&gt; https://agent.example.com &lt;span class="nt"&gt;--stream&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Investigate deployment dep_123. The deployment completed, but requests return 401."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For task-based interactions, the A2A protocol can stream task status updates and artifact updates while the work progresses.&lt;/p&gt;

&lt;p&gt;The support agent can translate those events into useful information for the developer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The deployment details have been sent to the diagnostic agent.

The deployment is reachable. The diagnostic agent is now checking identity-provider configuration.

The diagnostic report is ready.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those messages should correspond to real information received from the remote agent. The support agent should not manufacture intermediate progress simply to make the interface appear responsive.&lt;/p&gt;

&lt;p&gt;Streaming is only one option for following work.&lt;/p&gt;

&lt;p&gt;A2A also defines task operations for retrieving task state, listing tasks, canceling active work, subscribing to task updates, and configuring push notifications. These mechanisms support workflows where the client cannot or should not keep one streaming connection open for the entire investigation.&lt;/p&gt;

&lt;p&gt;The A2A CLI exposes task-oriented commands as well, including task inspection and cancellation, which makes the lifecycle visible while developing and debugging an integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the remote agent ask for context
&lt;/h2&gt;

&lt;p&gt;The initial diagnostic request may not contain enough information to finish the investigation.&lt;/p&gt;

&lt;p&gt;That is a normal part of a stateful agent interaction.&lt;/p&gt;

&lt;p&gt;Suppose the diagnostic agent determines that it needs the deployment region before it can continue. It can return the task in an &lt;code&gt;input-required&lt;/code&gt; state and explain what information is missing.&lt;/p&gt;

&lt;p&gt;The support agent can then use an MCP tool it already controls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_deployment_metadata("dep_123")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the result contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;region = us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The support agent can send that information back while referencing the existing A2A task rather than beginning an unrelated investigation.&lt;/p&gt;

&lt;p&gt;This preserves an important boundary.&lt;/p&gt;

&lt;p&gt;The diagnostic agent does not need direct access to the support agent’s deployment metadata tool. The support agent remains responsible for its own systems and decides what context crosses the agent boundary.&lt;/p&gt;

&lt;p&gt;That can also reduce unnecessary privilege sharing. Instead of giving the diagnostic agent standing access to another system, the support agent can provide the specific piece of context needed for the current task.&lt;/p&gt;

&lt;p&gt;A2A uses task and context identifiers to support these continued interactions. A &lt;code&gt;taskId&lt;/code&gt; identifies the stateful unit of work, while a &lt;code&gt;contextId&lt;/code&gt; can group related interactions.&lt;/p&gt;

&lt;p&gt;The result is a multi-turn collaboration between agents without requiring either side to expose its internal tools, memory, or implementation to the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision rule you can use
&lt;/h2&gt;

&lt;p&gt;Before adding another integration to an agent, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this agent need to invoke a capability, or delegate a goal to another independently operating agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If it needs to invoke a capability, expose that capability through MCP.&lt;/p&gt;

&lt;p&gt;If it needs to delegate a goal, discover the other agent through its Agent Card, verify that its advertised skills and security requirements fit the request, and communicate through A2A.&lt;/p&gt;

&lt;p&gt;For this support workflow, that gives a clear split:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read ticket context&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;The support agent directly operates the ticket-system integration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search troubleshooting documentation&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;The support agent directly invokes a retrieval capability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigate a deployment through a diagnostic agent&lt;/td&gt;
&lt;td&gt;A2A&lt;/td&gt;
&lt;td&gt;The diagnostic agent owns the investigation and its execution process.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request additional diagnostic context&lt;/td&gt;
&lt;td&gt;A2A + MCP&lt;/td&gt;
&lt;td&gt;A2A carries the request between agents; the support agent can use MCP to retrieve information from its own systems.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return a diagnostic report&lt;/td&gt;
&lt;td&gt;A2A&lt;/td&gt;
&lt;td&gt;The report can be represented as an artifact produced by the delegated work.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two agents do not need to belong to different companies, frameworks, or repositories for this boundary to be useful.&lt;/p&gt;

&lt;p&gt;They can initially live in the same codebase.&lt;/p&gt;

&lt;p&gt;The protocol boundary becomes especially valuable when the diagnostic agent later moves to another framework, team, service, or organization. The support agent can continue using MCP for its tools while communicating with the diagnostic system as an agent rather than reducing it to a tool-shaped wrapper.&lt;/p&gt;

&lt;p&gt;That is the practical boundary between MCP and A2A: MCP equips an agent with capabilities. A2A gives independently operating agents a standard way to work together.&lt;/p&gt;

&lt;p&gt;The official A2A CLI is a useful place to start experimenting with that boundary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/a2aproject/a2a-cli" rel="noopener noreferrer"&gt;https://github.com/a2aproject/a2a-cli&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wiring a Reachy Mini into OpenClaw without trusting the robot</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:25:41 +0000</pubDate>
      <link>https://dev.to/bengreenberg/wiring-a-reachy-mini-into-openclaw-without-trusting-the-robot-3lgh</link>
      <guid>https://dev.to/bengreenberg/wiring-a-reachy-mini-into-openclaw-without-trusting-the-robot-3lgh</guid>
      <description>&lt;p&gt;I put a &lt;a href="https://pollen-robotics.com/reachy-mini/" rel="noopener noreferrer"&gt;Reachy Mini&lt;/a&gt; in the living room.&lt;/p&gt;

&lt;p&gt;It is a small desktop robot from Pollen Robotics, now part of Hugging Face. A head on a moving body, two antennas, a camera, a speaker, and a microphone array. Inside is a Raspberry Pi CM4 running a daemon that exposes the motors, the audio and the camera over an HTTP API on port 8000, plus an app system: you install a Python package onto the robot and the daemon runs it as the current app.&lt;/p&gt;

&lt;p&gt;Out of the box it ships with demos. It waves, it dances, it follows a face. &lt;/p&gt;

&lt;p&gt;What I wanted was a voice touchpoint for the family, and a way to interact with the family while I am traveling. I already run OpenClaw in the house. I call my instance Jeeves, which is my sarcastic but helpful British butler. It holds my calendar, the home automation devices in every room, a Jewish holiday and Sabbath scheduler, a knowledge base, and the skills that act on all of it. I talk to it through Telegram and a dashboard.&lt;/p&gt;

&lt;p&gt;So the robot is a face and a microphone in the room where my family sits, and Jeeves is everything worth saying back. Wiring the two together was my last weekend's project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious wiring, and why I did not ship it
&lt;/h2&gt;

&lt;p&gt;The obvious version is to put the agent behind the robot. Install an app on the Reachy that captures audio, sends the transcript to the OpenClaw gateway, and speaks the answer. The robot becomes a client of my agent. &lt;/p&gt;

&lt;p&gt;I got that working and then took it apart, for two reasons.&lt;/p&gt;

&lt;p&gt;The first is that the robot is not a machine I can trust. Its own daemon API has no authentication of any kind. I checked this rather than assumed it: fetch &lt;code&gt;/openapi.json&lt;/code&gt; off the robot and you get 100 endpoints and zero security schemes. Anything on the LAN can drive the motors, open the camera, or stop the running app. The security threat is low, but still not tolerable. We maintain separate guest WiFi, but even with that, I didn't like that exposure.&lt;/p&gt;

&lt;p&gt;If the agent runs on the robot, then the robot holds a gateway token. The gateway token reaches an agent with my calendar, my house and my shell. Not acceptable to me.&lt;/p&gt;

&lt;p&gt;The second reason showed up in the audit trail once it was answering questions in the room. A general question was a full agent turn: a system prompt around 30k tokens carrying tool profiles, the skills index and the workspace bootstrap, on a persistent session that had grown to 51k, with a reasoning model spending about 500 tokens of thought before its first word.&lt;/p&gt;

&lt;p&gt;Median 15 seconds to answer "what's the weather today?". &lt;/p&gt;

&lt;p&gt;At fifteen seconds nobody in the room waits for the answer. They go find a phone instead, and the robot goes back to being just a cute toy and an ornament on the shelf.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape I landed on
&lt;/h2&gt;

&lt;p&gt;One rule drives the whole design: the robot is untrusted, and everything that could leak lives behind all the security I invested in my OpenClaw setup.&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%2Fauarf1a96n4ov3py6ikg.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%2Fauarf1a96n4ov3py6ikg.png" alt="The untrusted Reachy Mini can communicate only with a scoped broker, which routes approved requests to deterministic home handlers or a lean OpenClaw agent. Appears after: " width="799" height="136"&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;Living room                          Mac mini
Reachy Mini (CM4)                    reachy-broker :8092
  reachy-mini-daemon :8000   ---&amp;gt;      one scoped bearer token
  jeeves_hub app             Tailscale 
    mic, speaker, motors               redaction at the boundary
    one token, no data                 whisper / OpenClaw gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The robot runs one app, &lt;code&gt;jeeves_hub&lt;/code&gt;. It does wake-word matching, voice activity detection, motion and expressions, and it holds exactly one bearer token scoped to the broker. It never sees a credential, a model, or a calendar entry. Audio goes up, a policy and a reply come down.&lt;/p&gt;

&lt;p&gt;The broker is a small Python HTTP server on the machine. It is the only path between the robot and my data, and it is the only thing that talks to OpenClaw.&lt;/p&gt;

&lt;p&gt;If the robot were fully compromised tomorrow, what the attacker gets the highly restricted intent allowlist and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailscale, not the LAN
&lt;/h2&gt;

&lt;p&gt;The broker binds the tailnet interface and not &lt;code&gt;0.0.0.0&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;The robot is on my tailnet, so the broker addresses it by its tailnet address too, never its LAN address. Nothing else in the house can reach the broker, and the traffic between the two is WireGuard rather than plaintext HTTP across a shared network.&lt;/p&gt;

&lt;p&gt;Tailscale also handles the remote case as well. When I "teleport" in from a hotel, that is direct WireGuard, not the vendor's WebRTC path. &lt;/p&gt;

&lt;h2&gt;
  
  
  The intent allowlist
&lt;/h2&gt;

&lt;p&gt;This is an essential part of the design.&lt;/p&gt;

&lt;p&gt;The robot cannot phrase a request. It names an intent and passes typed arguments. Free text only ever reaches one intent, &lt;code&gt;general.ask&lt;/code&gt;, which is also the least privileged one: no house access, no calendar, no files.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The intent allowlist.

This is the closed set of things a shared-room robot may ask for. An unknown
intent is refused before any data source is touched, so the blast radius of a
fully compromised robot is exactly what is listed here and nothing else.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routing a transcript to an intent is done with rules, not a model. That was deliberate. An LLM router adds a second model round trip to every turn, and it can be talked into picking a different intent by whatever is said in the room.&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;AC_MENTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:a\.?[/ ]?c\.?|air[ -]?condition(?:er|ing|ers)?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|air[ -]?cons?|cooling)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&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;The doctrine there is one line: an imperative actuates, a question never does, and a command that names no known room asks which one. "Is the ac on" is a read. "Turn on the ac" is a write. Anything ambiguous falls to the read.&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%2Fapj440ajgh9pk91zhuud.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%2Fapj440ajgh9pk91zhuud.png" alt="A deterministic router distinguishes commands, questions, ambiguous requests, and unknown intents before any home data or device is accessed." width="799" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redaction happens on the broker side of the boundary, before a word is spoken. My calendar status is coarse: busy or free, until when, and a whereabouts word from a closed vocabulary. Never event titles, locations, attendees or company names. &lt;/p&gt;

&lt;h2&gt;
  
  
  The lean OpenClaw agent
&lt;/h2&gt;

&lt;p&gt;This is how I dropped the response time on Reachy from 15 seconds to about 3 seconds.&lt;/p&gt;

&lt;p&gt;Instead of routing general questions at &lt;code&gt;main&lt;/code&gt;, I gave the robot its own agent in &lt;code&gt;config/openclaw.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reachy family hub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lean voice agent for the living-room robot: no skills, no tools, no workspace context. The persona rides in each message."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/.openclaw/workspace-reachy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FILL_IN_YOUR_LLM_MODEL_HERE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fallbacks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"FALLBACK_MODEL_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"FALLBACK_MODEL_2"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"minimal"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"heartbeat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"every"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0m"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextInjection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thinkingDefault"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoningDefault"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line there is removing something. No skills means no skills index in the prompt. The minimal tool profile means no coding tools. &lt;code&gt;contextInjection: never&lt;/code&gt; means the workspace is not injected, so the persona files on disk are documentation for me rather than tokens I buy on every question. Thinking and reasoning off, because a living room answer is not a research task.&lt;/p&gt;

&lt;p&gt;The same question that measured 15 seconds now measures about 2.9 seconds for the model leg and 3.4 to 3.9 seconds for the whole turn including speech synthesis, at roughly 5k prompt tokens instead of 30k. About a tenth of the cost.&lt;/p&gt;

&lt;p&gt;The persona is composed per turn by the broker and rides in the message, so every request is stateless. No &lt;code&gt;user&lt;/code&gt; field, no session header. A persistent session would re-buy the persona on every question anyway.&lt;/p&gt;

&lt;p&gt;The broker POSTs to the gateway's OpenAI-compatible endpoint and addresses the agent by name:&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;openclaw/reachy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# the agent, not a model id
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&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;body&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&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;application/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;Authorization&lt;/span&gt;&lt;span class="sh"&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;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a second agent, &lt;code&gt;openclaw/reachy-deep&lt;/code&gt;, with the same lean shape and a stronger model, reached only when somebody says "think carefully". Everything else stays on the fast path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the model out of the house
&lt;/h2&gt;

&lt;p&gt;The lean agent handles general knowledge. It does not touch the house, and that is the other half of the speed story.&lt;/p&gt;

&lt;p&gt;Before the deterministic router existed, "turn off the kitchen light" reached the model, which rediscovered the automation skill with its shell tool on every single request. 43 seconds when it worked, and a timeout when it did not.&lt;/p&gt;

&lt;p&gt;Now every device is a closed table and a deterministic intent, and a light takes one to two seconds. Same for the time, for greetings, for "what can you help with", which is spoken from a command guide generated out of the router itself so it can never advertise a phrase the robot does not understand.&lt;/p&gt;

&lt;p&gt;Zero tokens for any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it under launchd
&lt;/h2&gt;

&lt;p&gt;Both the broker and the speech service are launchd agents on the Mac.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
...

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/path/to/reachy-broker"&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; /opt/homebrew/bin/python3 &lt;span class="nt"&gt;-m&lt;/span&gt; api.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all it takes it boot it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it feels like now
&lt;/h2&gt;

&lt;p&gt;You say "hey Jeeves" and then a sentence. The hub matches the wake phrase locally, streams the audio to the broker, whisper transcribes it, the router picks an intent, and either a deterministic handler or the lean agent answers. &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%2Fk216exb6knqp2at5usl1.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%2Fk216exb6knqp2at5usl1.png" alt="The request path transcribes audio, routes house requests to deterministic handlers, and sends general questions to a stateless lean agent." width="799" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;House questions and house commands never leave the Mac. General questions go to the LLM through the gateway and never carry a fact about my family. The robot holds one token that can do exactly the things on a list I wrote.&lt;/p&gt;

&lt;p&gt;The part I did not expect to care about is that setting thatg boundary made the fun parts possible. Once the robot could only ever name an intent, I stopped worrying about what it might be talked into doing and started adding things: games, ambient motion randomly making the kids laugh. None of that needed a new trust decision, because there is only one, and it is enforced in a single file.&lt;/p&gt;

&lt;p&gt;If you are wiring a device you cannot trust into an agent that can act on your life, put a broker between them and give the device a closed list of things it may ask for. Then, once you take care of that bit, you can just get down to building it for both productivity and joy.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Vector Search Is Still the Memory Layer Agents Actually Need</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Thu, 27 Aug 2026 07:37:45 +0000</pubDate>
      <link>https://dev.to/bengreenberg/vector-search-is-still-the-memory-layer-agents-actually-need-50dn</link>
      <guid>https://dev.to/bengreenberg/vector-search-is-still-the-memory-layer-agents-actually-need-50dn</guid>
      <description>&lt;p&gt;When I was working on &lt;a href="https://pragprog.com/titles/bgvector/vector-search-with-javascript/" rel="noopener noreferrer"&gt;&lt;em&gt;Vector Search with JavaScript&lt;/em&gt;&lt;/a&gt;, vector search was a hot topic. By the time the  book was published some people had begun saying that because of LLMs and their advances, we have moved beyond vector search.&lt;/p&gt;

&lt;p&gt;This couldn't be farther from the truth. LLMs and agentic development is amazing, but it often gets things wrong. They don't fail because the model is weak always, but they fail because the right context can be sitting somewhere else and they had no idea that it existed.&lt;/p&gt;

&lt;p&gt;Your docs are in one place. Tool outputs are in another. Prior decisions are in chat history, issue comments, &lt;code&gt;AGENTS.md&lt;/code&gt;, local files, and half a dozen API responses. You can paste more into the prompt, but that gets expensive and messy fast.&lt;/p&gt;

&lt;p&gt;Vector search gives agents a memory layer they can inspect, query, move, and rebuild.&lt;/p&gt;

&lt;p&gt;That still matters in an LLM-first world.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; is a good place to frame this because AAIF is about open agentic infrastructure: MCP, goose, AGENTS.md, agentgateway, and the protocols around them. If agents are going to work across tools and runtimes, memory can’t live as a hidden feature inside one hosted product. It needs to be part of the system you can reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  The prompt is the wrong database
&lt;/h3&gt;

&lt;p&gt;A prompt is a request. It’s not a storage layer.&lt;/p&gt;

&lt;p&gt;Once you treat the prompt as storage, every workflow starts to rot. You add summaries. Then summaries of summaries. Then a “context” block, and then a "context" block for the original context block.&lt;/p&gt;

&lt;p&gt;That doesn’t scale for project-specific agents.&lt;/p&gt;

&lt;p&gt;You need retrieval that can answer questions like:&lt;/p&gt;

&lt;p&gt;Which migration introduced this column?&lt;/p&gt;

&lt;p&gt;What did the tool return the last time this failed?&lt;/p&gt;

&lt;p&gt;Which internal doc explains this service boundary?&lt;/p&gt;

&lt;p&gt;What did we decide about auth in the previous session?&lt;/p&gt;

&lt;p&gt;Why does that happen? Because agents need working memory and reference memory at the same time. The model can reason over the current task, but your project context lives outside the model. Vector search gives you a way to fetch the few pieces that match the current intent instead of dragging the whole project into every turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP makes retrieval a first-class interface
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; gives AI applications a standard way to connect to external systems. MCP servers can expose tools and resources, and resources are identified by URIs in the spec.&lt;/p&gt;

&lt;p&gt;That maps cleanly to vector search.&lt;/p&gt;

&lt;p&gt;You can build an MCP server with tools like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;search_project_context(query, filters)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetch_context_chunk(uri)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;upsert_tool_result(source, content, metadata)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;list_context_sources(project_id)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The vector database doesn’t need to know about the agent. The agent doesn’t need to know about the vector database. MCP becomes the contract between them.&lt;/p&gt;

&lt;p&gt;That contract matters when you want portability. Today your agent might run in an IDE. Tomorrow it might run in a local runtime like &lt;a href="https://aaif.io/projects/goose" rel="noopener noreferrer"&gt;goose&lt;/a&gt;. The retrieval layer should move with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should go into agent memory?
&lt;/h3&gt;

&lt;p&gt;Start with the things you already look up manually.&lt;/p&gt;

&lt;p&gt;Index your docs, READMEs, runbooks, schema notes, generated API references, issue threads, and selected tool outputs. Store the raw text or clean markdown. Keep metadata with every chunk: source URI, file path, repo, commit SHA when you have it, timestamp, author if useful, and content type.&lt;/p&gt;

&lt;p&gt;Then be strict about retrieval.&lt;/p&gt;

&lt;p&gt;Don’t return anonymous chunks. Return chunks with source links.&lt;/p&gt;

&lt;p&gt;Don’t rely on similarity alone. Use metadata filters.&lt;/p&gt;

&lt;p&gt;Don’t treat old context and new context equally. Add recency where the domain changes.&lt;/p&gt;

&lt;p&gt;Don’t make the agent trust memory blindly. Give it enough source data to quote the file, open the URI, or ask for confirmation before making a risky change.&lt;/p&gt;

&lt;p&gt;Vector search is useful because it’s probabilistic. Agent memory is useful when that probability is wrapped in provenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small useful pattern
&lt;/h3&gt;

&lt;p&gt;A practical agent memory loop can stay straightforward.&lt;/p&gt;

&lt;p&gt;First, chunk source material by meaning, not by arbitrary token count. Function-level chunks work better than splitting every thousand characters in code-heavy repos. Section-level chunks work better for docs.&lt;/p&gt;

&lt;p&gt;Then embed each chunk and store it with metadata.&lt;/p&gt;

&lt;p&gt;At runtime, the agent turns the current task into a retrieval query. The MCP server searches the vector index, filters by project or source type, and returns a small set of candidates with scores and URIs. The agent fetches the best chunks, reads them, and decides what to do next.&lt;/p&gt;

&lt;p&gt;That’s enough for many workflows.&lt;/p&gt;

&lt;p&gt;You can add hybrid search when exact identifiers matter. You can add reranking when your top results are noisy. You can add write-back when tool results become useful future context. But the base shape stays the same: retrieve, inspect, act.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector search also makes memory debuggable
&lt;/h3&gt;

&lt;p&gt;When an agent gives a bad answer, you need to know whether the reasoning failed or retrieval failed.&lt;/p&gt;

&lt;p&gt;Those are different problems.&lt;/p&gt;

&lt;p&gt;If retrieval returned the wrong chunks, fix chunking, filters, metadata, or ranking. If retrieval returned the right chunks and the model ignored them, fix the prompt or tool policy. If the index is stale, fix ingestion.&lt;/p&gt;

&lt;p&gt;Without an inspectable retrieval layer, all of that collapses into “the agent was wrong.”&lt;/p&gt;

&lt;p&gt;You can log the query, returned chunk IDs, scores, metadata filters, and final sources used. You can replay the retrieval step without running the full agent. You can delete bad documents from the index. You can rebuild from source.&lt;/p&gt;

&lt;p&gt;That is what I would call operational memory.&lt;/p&gt;

&lt;p&gt;Vector search didn’t become obsolete because models got better. It became more useful because agents now have more places to look.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>vectordatabase</category>
    </item>
    <item>
      <title>What a good Agents.md should teach an agent on day one</title>
      <dc:creator>Ben Greenberg</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:38:29 +0000</pubDate>
      <link>https://dev.to/bengreenberg/what-a-good-agentsmd-should-teach-an-agent-on-day-one-3nen</link>
      <guid>https://dev.to/bengreenberg/what-a-good-agentsmd-should-teach-an-agent-on-day-one-3nen</guid>
      <description>&lt;p&gt;I hit this last week while working inside my own OpenClaw workspace: the agent had access to the right files, the right tools, and the right project context, but the useful behavior didn't come from any one magic prompt. It came from a small stack of durable instructions.&lt;/p&gt;

&lt;p&gt;The root &lt;code&gt;AGENTS.md&lt;/code&gt; said what to read first. &lt;code&gt;SOUL.md&lt;/code&gt; defined the assistant's operating posture. &lt;code&gt;USER.md&lt;/code&gt; gave personal context. &lt;code&gt;TOOLS.md&lt;/code&gt; separated reusable tool behavior from local machine details. Skill docs explained when to load specialized workflows.&lt;/p&gt;

&lt;p&gt;That structure has proven useful for me time and time again.&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%2F2e2un8p08dzc7s2vp2iv.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%2F2e2un8p08dzc7s2vp2iv.png" alt="A stack showing AGENTS.md as the routing file that points agents to posture, user context, local tool notes, and deeper skill file" width="799" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AGENTS.md, now part of the &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; ecosystem hosted by the Linux Foundation, gives developers a plain Markdown place to tell coding agents how to work in a repo. The format is intentionally simple. The hard part isn't the file. The hard part is deciding what deserves to live in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the first five minutes
&lt;/h2&gt;

&lt;p&gt;A good &lt;code&gt;AGENTS.md&lt;/code&gt; should answer one question first: what should the agent do before touching code?&lt;/p&gt;

&lt;p&gt;In my workspace, the startup path is explicit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read &lt;code&gt;SOUL.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;USER.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read today's and yesterday's daily memory files&lt;/li&gt;
&lt;li&gt;In a main session, read &lt;code&gt;MEMORY.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gives the agent a boot order. It doesn't need to guess which file matters, whether memory is allowed, or whether private context belongs in a shared chat.&lt;/p&gt;

&lt;p&gt;Most repo instructions skip this. They say "follow project conventions" and then bury the conventions across a README, package scripts, CI config, old PRs, and comments. An agent can search, but search isn't the same as orientation.&lt;/p&gt;

&lt;p&gt;Give it a first route through the 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%2F9o7pzcjrf4gqqwd127oh.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%2F9o7pzcjrf4gqqwd127oh.png" alt="A boot flow for an agent: read AGENTS.md, load context, route by task type, act within boundaries, and update durable docs when needed." width="800" height="1182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate identity from operating rules
&lt;/h2&gt;

&lt;p&gt;Your repo probably doesn't need a &lt;code&gt;SOUL.md&lt;/code&gt;, but the pattern is useful. One file can define working posture, while &lt;code&gt;AGENTS.md&lt;/code&gt; defines project behavior.&lt;/p&gt;

&lt;p&gt;For a software repo, that might look 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="gu"&gt;## Working posture&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Read the existing code before proposing new abstractions.
&lt;span class="p"&gt;-&lt;/span&gt; Prefer local helpers over new dependencies.
&lt;span class="p"&gt;-&lt;/span&gt; Keep changes scoped to the user request.
&lt;span class="p"&gt;-&lt;/span&gt; Run the narrowest useful test first, then broaden if the change touches shared behavior.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are judgment rules. They belong near the top because they shape every later decision.&lt;/p&gt;

&lt;p&gt;Then put repo-specific mechanics somewhere else:&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;## Commands&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Install dependencies: &lt;span class="sb"&gt;`pnpm install`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Run unit tests: &lt;span class="sb"&gt;`pnpm test`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Run type checks: &lt;span class="sb"&gt;`pnpm typecheck`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why split them? Because commands change faster than principles. If you mix everything together, the file turns into a junk drawer. Agents will still read it, but you won't know which instruction is steering behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put boundaries where the agent will trip over them
&lt;/h2&gt;

&lt;p&gt;The best line in my workspace &lt;code&gt;AGENTS.md&lt;/code&gt; is short: &lt;code&gt;trash &amp;gt; rm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That teaches a local safety rule in three tokens. It says destructive deletion should be recoverable. It doesn't explain Unix philosophy. It doesn't lecture. It gives the agent a rule it can apply while acting.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;AGENTS.md&lt;/code&gt; should include boundaries like that:&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;## Red lines&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Don't edit generated files directly.
&lt;span class="p"&gt;-&lt;/span&gt; Don't change public API behavior without updating tests.
&lt;span class="p"&gt;-&lt;/span&gt; Don't run migrations against shared databases.
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`trash`&lt;/span&gt; instead of &lt;span class="sb"&gt;`rm`&lt;/span&gt; when deleting local files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the shape: concrete verbs, concrete objects, concrete limits.&lt;/p&gt;

&lt;p&gt;"Be careful with data" is too vague. "Don't run migrations against shared databases" gives the agent something it can obey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teach context access rules
&lt;/h2&gt;

&lt;p&gt;Agents often fail by reading too little or too much. Repo instructions can fix both.&lt;/p&gt;

&lt;p&gt;In my workspace, &lt;code&gt;MEMORY.md&lt;/code&gt; is only loaded in main sessions, not shared contexts. That's a privacy rule and a context rule at the same time. Daily notes are raw logs. Long-term memory is curated. &lt;code&gt;TOOLS.md&lt;/code&gt; is for environment-specific notes, while skills are reusable.&lt;/p&gt;

&lt;p&gt;That structure avoids a common problem: durable instructions become a dumping ground for every fact anyone might need someday.&lt;/p&gt;

&lt;p&gt;For a team repo, you can use the same split:&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;## Context files&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`README.md`&lt;/span&gt;: human setup and project overview.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`AGENTS.md`&lt;/span&gt;: agent workflow and repo norms.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`docs/architecture.md`&lt;/span&gt;: current service boundaries.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`docs/runbooks/`&lt;/span&gt;: production procedures. Read only when the task touches operations.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`.env.example`&lt;/span&gt;: allowed environment variable names. Never read real &lt;span class="sb"&gt;`.env`&lt;/span&gt; files unless asked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last sentence matters. It tells the agent where the map ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep local details out of shared skills
&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%2Foebi39ptrnil8hpc1n9g.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%2Foebi39ptrnil8hpc1n9g.png" alt="A decision tree for placing instructions in reusable skills, AGENTS.md, local tool notes, private memory, or temporary task notes." width="798" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TOOLS.md&lt;/code&gt; in my workspace makes a clean distinction: skills define how tools work, and &lt;code&gt;TOOLS.md&lt;/code&gt; stores local specifics like camera names, SSH aliases, speakers, or preferred voices.&lt;/p&gt;

&lt;p&gt;That maps well to engineering teams.&lt;/p&gt;

&lt;p&gt;A reusable instruction might say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;When debugging CI, inspect the failing job logs before changing code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local instruction might say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The staging dashboard is at &lt;span class="nt"&gt;&amp;lt;internal&lt;/span&gt; &lt;span class="na"&gt;URL&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;Those shouldn't live in the same place. Reusable instructions can move across projects. Local details shouldn't leak, and they age faster.&lt;/p&gt;

&lt;p&gt;This is one reason AGENTS.md fits naturally inside the AAIF project set. MCP describes how agents connect to tools. agentgateway works on routing and governing agent traffic. AGENTS.md handles repo-level behavior. You need all of those layers if agents are going to work across projects without each tool inventing its own private convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use skills for depth, not bulk
&lt;/h2&gt;

&lt;p&gt;My workspace says: "Skills provide your tools. When you need one, check its &lt;code&gt;SKILL.md&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;That's the right division of labor. &lt;code&gt;AGENTS.md&lt;/code&gt; should route the agent to deeper instructions. It shouldn't contain the full manual for every workflow.&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;&lt;span class="gu"&gt;## Release process&lt;/span&gt;

[900 lines of release rules, changelog policy, package registry notes, rollback steps, comms templates, and edge cases]
&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;&lt;span class="gu"&gt;## Release process&lt;/span&gt;

For release work, read &lt;span class="sb"&gt;`skills/release/SKILL.md`&lt;/span&gt; before making changes. Do not publish packages or create GitHub releases unless the user explicitly asks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this work? The root file stays readable, and the agent loads detail only when the task needs it.&lt;/p&gt;

&lt;p&gt;That matters more as context grows. An instruction file can hurt you if it forces every task to carry every workflow. A CSS fix doesn't need your incident response manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell the agent when to speak and when to stay quiet
&lt;/h2&gt;

&lt;p&gt;This is an important part as well that shouldn't be ignored.&lt;/p&gt;

&lt;p&gt;The workspace &lt;code&gt;AGENTS.md&lt;/code&gt; has group chat rules. It tells the assistant to respond when directly mentioned, when it can add value, or when correcting meaningful misinformation. It also tells the assistant to stay quiet when the conversation is casual or already answered.&lt;/p&gt;

&lt;p&gt;That's repo-relevant too. Agents need communication norms.&lt;/p&gt;

&lt;p&gt;For a development repo, that might be:&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;## PR comments&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Comment when a change affects behavior users can observe.
&lt;span class="p"&gt;-&lt;/span&gt; Mention test gaps plainly.
&lt;span class="p"&gt;-&lt;/span&gt; Don't restate the diff.
&lt;span class="p"&gt;-&lt;/span&gt; Don't leave speculative security claims without a concrete path or file reference.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents generate a lot of text by default. Your instructions should define what useful text looks like in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make maintenance part of the contract
&lt;/h2&gt;

&lt;p&gt;The workspace instructions have a blunt rule: no "mental notes." If something should persist, write it to a file.&lt;/p&gt;

&lt;p&gt;That belongs in more repos.&lt;/p&gt;

&lt;p&gt;Agents learn project facts during a task: a flaky test command, a generated directory that shouldn't be edited, a local setup wrinkle, a service boundary that wasn't documented. If the agent only uses that knowledge once, the next run pays the same discovery cost.&lt;/p&gt;

&lt;p&gt;Add a maintenance rule:&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;## Updating these instructions&lt;/span&gt;

When you learn a durable repo rule, update &lt;span class="sb"&gt;`AGENTS.md`&lt;/span&gt; or the relevant doc in the same PR. Keep task-specific notes out of this file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enforce the second sentence. Otherwise AGENTS.md becomes a chat transcript with headings.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical structure
&lt;/h2&gt;

&lt;p&gt;If I were starting a repo-level &lt;code&gt;AGENTS.md&lt;/code&gt; today, I'd use this shape:&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;## Start here&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Read this file before making changes.
&lt;span class="p"&gt;-&lt;/span&gt; Read &lt;span class="sb"&gt;`README.md`&lt;/span&gt; for setup.
&lt;span class="p"&gt;-&lt;/span&gt; Read the nearest package-level &lt;span class="sb"&gt;`AGENTS.md`&lt;/span&gt; if one exists.

&lt;span class="gu"&gt;## Working posture&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Preserve existing patterns unless the task calls for changing them.
&lt;span class="p"&gt;-&lt;/span&gt; Keep edits scoped.
&lt;span class="p"&gt;-&lt;/span&gt; Prefer small tests close to the changed code.

&lt;span class="gu"&gt;## Commands&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Install:
&lt;span class="p"&gt;-&lt;/span&gt; Test:
&lt;span class="p"&gt;-&lt;/span&gt; Typecheck:
&lt;span class="p"&gt;-&lt;/span&gt; Lint:

&lt;span class="gu"&gt;## Repo map&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`apps/web`&lt;/span&gt;: frontend
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`packages/api`&lt;/span&gt;: API client
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`packages/db`&lt;/span&gt;: schema and migrations

&lt;span class="gu"&gt;## Boundaries&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Don't edit generated files.
&lt;span class="p"&gt;-&lt;/span&gt; Don't run destructive database commands.
&lt;span class="p"&gt;-&lt;/span&gt; Ask before publishing, emailing, posting, or deploying.

&lt;span class="gu"&gt;## Workflow routing&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; For releases, read &lt;span class="sb"&gt;`docs/release.md`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; For security changes, read &lt;span class="sb"&gt;`docs/security.md`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; For UI changes, inspect existing components first.

&lt;span class="gu"&gt;## Maintenance&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Add durable lessons here.
&lt;span class="p"&gt;-&lt;/span&gt; Remove stale instructions when the code changes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough for day one. It creates the framework to build upon as you continue to iterate.&lt;/p&gt;

&lt;p&gt;The goal isn't to make the agent know everything. The goal is to make the first move reasonabe, the dangerous moves constrained, and the next file obvious.&lt;/p&gt;

&lt;p&gt;In an open agentic ecosystem, the shared convention doesn't need to be heavy to be useful. It needs to be predictable enough that any agent can arrive in your repo and know where to begin: &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <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>
  </channel>
</rss>
