<?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: Zane Chen</title>
    <description>The latest articles on DEV Community by Zane Chen (@zeling_chen_73840b4951f53).</description>
    <link>https://dev.to/zeling_chen_73840b4951f53</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2995621%2Fe1184b7a-c502-42d2-bd15-478aa010df1a.png</url>
      <title>DEV Community: Zane Chen</title>
      <link>https://dev.to/zeling_chen_73840b4951f53</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zeling_chen_73840b4951f53"/>
    <language>en</language>
    <item>
      <title>Understand OpenClaw by Building One - 7: More Context! More Context!</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:14:04 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Layer Prompts: Context Stacking
&lt;/h2&gt;

&lt;p&gt;Getting the system prompt right is actually an non-trivial job. And a lot of pieces are not static.&lt;/p&gt;

&lt;p&gt;They're assembled from multiple layers, each adding context from different aspects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity&lt;/strong&gt; — Rarely changes (agent's core purpose)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personality&lt;/strong&gt; — Optional flavor, mostly static&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bootstrap&lt;/strong&gt; — Workspace guide, changes when you switch projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt; — Timestamp, session ID — every request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel&lt;/strong&gt; — Where the message came from — varies per request
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PromptBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SessionState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;layers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 1: Identity
&lt;/span&gt;        &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 2: Soul (optional)
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;soul_md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;## Personality&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;soul_md&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="c1"&gt;# Layer 3: Bootstrap context (workspace guide)
&lt;/span&gt;        &lt;span class="n"&gt;bootstrap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_load_bootstrap_context&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;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 4: Runtime context
&lt;/span&gt;        &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_runtime_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 5: Channel hint
&lt;/span&gt;        &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_channel_hint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F2myqdetlqzmgqnahph1w.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%2F2myqdetlqzmgqnahph1w.png" alt="Multi-layer prompts" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory: Long-Term Knowledge
&lt;/h2&gt;

&lt;p&gt;Session context is ephemeral. Memory persists. The pattern shown here uses a specialized agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pickle: @cookie Do you know &amp;lt;topic&amp;gt; about user?
cookie: Yes, &amp;lt;content&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A memory agent manages storage and retrieval. The main agent dispatches queries when it needs to remember something.&lt;/p&gt;

&lt;p&gt;Memory can be structured as something like below, but this super flexible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memories/
├── topics/
│   ├── preferences.md    # User preferences
│   └── identity.md       # User info
├── projects/
│   └── my-project.md     # Project-specific notes
└── daily-notes/
    └── 2024-01-15.md     # Daily journal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Memory System Alternatives
&lt;/h2&gt;

&lt;p&gt;The specialized agent approach keeps the main agent focused. But there are other approaches, just list a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct tools&lt;/strong&gt; - Memory tools in the main agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill-based&lt;/strong&gt; - Use CLI tools like grep&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector database&lt;/strong&gt; - Semantic search over embeddings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each has trade-offs. File-based is simple but limited. Vector databases scale but add complexity. Choose based on your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Open-Ended Problem
&lt;/h2&gt;

&lt;p&gt;Memory is where agents get hard. Retrieval relevance, storage efficiency, context integration - these are unsolved problems at scale. This implementation is a starting point. Where you take it depends on your use case.&lt;/p&gt;

&lt;p&gt;Memory is where agents become personalized. And where the hard problems live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;Previous: Agents are Running, Your are Sleeping&lt;/a&gt; | &lt;strong&gt;End of series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 6: Agents are Running, Your are Sleeping</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:14:01 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron &amp;amp; Heartbeat
&lt;/h2&gt;

&lt;p&gt;Your agent works when you talk to it. But what if it could work while you sleep?&lt;/p&gt;

&lt;p&gt;Nothing different from a cron job in engineer world, cron expressions define when a job runs. A background worker checks every minute, finds due jobs, and dispatches them.&lt;/p&gt;

&lt;p&gt;Jobs are defined in &lt;code&gt;CRON.md&lt;/code&gt; files with a schedule and prompt. The agent runs at the appointed time, does the work, and optionally posts a message back.&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;class&lt;/span&gt; &lt;span class="nc"&gt;CronDef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;  &lt;span class="c1"&gt;# Cron expression
&lt;/span&gt;    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;one_off&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CronWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_tick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cron_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discover_crons&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;due_jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_due_jobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cron_def&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;due_jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CronEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cron_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cron_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cron_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fd7ofgeycuj8djsmfgzlt.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%2Fd7ofgeycuj8djsmfgzlt.png" alt="Cron and scheduling" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron-Ops Skills
&lt;/h2&gt;

&lt;p&gt;The Cron Operation functionality is implemented using the &lt;strong&gt;SKILL system&lt;/strong&gt; rather than registering dedicated tools which avoids bloating the tool registry.&lt;/p&gt;

&lt;p&gt;Reference &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;example repo&lt;/a&gt; for example skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency Control: Don't Overload
&lt;/h2&gt;

&lt;p&gt;When multiple requests come in - from cron jobs, from users, from other agents - you need limits. Some agents are expensive to run. Some APIs have rate limits. Unbounded concurrency leads to failures.&lt;/p&gt;

&lt;p&gt;Lets use a semaphore based solution to limit concurrency. And each agent has a &lt;code&gt;max_concurrency&lt;/code&gt; setting. The semaphore ensures no more than that many instances run at once. Requests wait in line instead of crashing the system.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AgentWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SubscriberWorker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_semaphores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Semaphore&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_or_create_semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Blocks if limit reached
&lt;/span&gt;            &lt;span class="c1"&gt;# ... execute session ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Post Message Back: Agents Can Initiate
&lt;/h2&gt;

&lt;p&gt;Sometimes an agent needs to reach out proactively. Maybe it finished a long-running task. Maybe it detected something important. The &lt;code&gt;post_message&lt;/code&gt; tool lets agents initiate conversations.&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="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;AgentEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&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="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Message queued for delivery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F0g4obivkov7c7hxkltjm.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%2F0g4obivkov7c7hxkltjm.png" alt="Post message back" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how agents say "I'm done" or "Something happened" without being prompted.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;post_message&lt;/code&gt; tool is only available in Cron jobs — agents can't arbitrarily post messages outside scheduled tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  HEARTBEAT Vs CRON
&lt;/h3&gt;

&lt;p&gt;OpenClaw has two distinct scheduling mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HEARTBEAT:&lt;/strong&gt; Only one allowed, runs in the main session at a regular interval without checking time. Simple periodic execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRON:&lt;/strong&gt; Multiple allowed, runs in background respecting cron expressions. Full scheduling flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;Previous: Many of Them&lt;/a&gt; | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;Next: More Context! More Context!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 5: Many of Them</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:13:56 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many of Them
&lt;/h2&gt;

&lt;p&gt;One agent can't be an expert at everything. Neither should it try.&lt;/p&gt;

&lt;p&gt;You've built a capable agent. It can read files, search the web, run commands. But ask it to do everything and it struggles. Some tasks need specialized knowledge. Some tasks need focused context. The solution isn't a bigger agent - it's multiple smaller ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Discovery
&lt;/h3&gt;

&lt;p&gt;Agents are defined in &lt;code&gt;AGENT.md&lt;/code&gt; files. A loader discovers them at startup:&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;class&lt;/span&gt; &lt;span class="nc"&gt;AgentLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discover_agents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AgentDef&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Scan agents directory and return list of valid AgentDef.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;discover_definitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agents_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENT.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_parse_agent_def&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Routing: Match Tasks to Agents
&lt;/h2&gt;

&lt;p&gt;Right task to right agent? We need a routing policy to handle this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Routing Rules&lt;/strong&gt;: Find rules matching inbound source, starting from most specific rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default Fallback&lt;/strong&gt;: Fall back to global default agent if no rules match.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;  &lt;span class="c1"&gt;# Compiled regex
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_compute_tier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compute specificity tier.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&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;.*+?[]()|^$&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="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Exact match
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# Wildcard
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Specific regex
&lt;/span&gt;
&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoutingTable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_load_bindings&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;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&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;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fexqv58rglwwwnz3v9zgp.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%2Fexqv58rglwwwnz3v9zgp.png" alt="Multi-agent routing" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration in Channel Worker
&lt;/h3&gt;

&lt;p&gt;When a message arrives, the channel worker uses the routing table to find the right agent:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Use routing_table to resolve agent from bindings
&lt;/span&gt;    &lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routing_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_or_create_session_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Publish event
&lt;/span&gt;    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InboundEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Agents Want to Call Their Friends
&lt;/h2&gt;

&lt;p&gt;Let's have another tool to delegate tasks to other agents. The way it is implemented here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load subagent definition&lt;/li&gt;
&lt;li&gt;Create session&lt;/li&gt;
&lt;li&gt;Publish dispatch event&lt;/li&gt;
&lt;li&gt;Wait for result.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subagent_dispatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dispatch a task to a specialized subagent.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subagent_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agent_def&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Publish dispatch event
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;shared_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;

    &lt;span class="c1"&gt;# Wait for result
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;result_future&lt;/span&gt;
    &lt;span class="k"&gt;return&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;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Frtjpb1agtjhxu7srllc4.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%2Frtjpb1agtjhxu7srllc4.png" alt="Agent dispatch" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main agent calls &lt;code&gt;subagent_dispatch&lt;/code&gt;, which creates a new session for the subagent and waits for its response. The eventbus handles the communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative Multi-Agent Patterns
&lt;/h3&gt;

&lt;p&gt;Direct subagent dispatching is just one approach to multi-agent orchestration. Here are some other common patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared Task Lists&lt;/strong&gt;: Agents coordinate by reading from and writing to a shared task queue or database. Each agent picks up tasks as they become available, agent never talk to agent directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tmux/Screen Sessions&lt;/strong&gt;: &lt;code&gt;tmux&lt;/code&gt; allow us running multiple processes. A &lt;code&gt;tmux&lt;/code&gt; skill can be provided to agent to guide it execute multiple tasks, achieving multi-agent to some extent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;Previous: Beyond the CLI&lt;/a&gt; | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;Next: Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 4: Beyond the CLI</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:13:51 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond the CLI
&lt;/h2&gt;

&lt;p&gt;Your agent works great in the terminal. But what if you want to talk to it from Telegram? Or your phone? Or another program? Or even multiple of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event-Driven Architecture
&lt;/h2&gt;

&lt;p&gt;To make the agent more scalable, we introduce event-driven architecture before adding more feature.&lt;/p&gt;

&lt;p&gt;The pattern is pub/sub, and you already know it. An event bus sits at the center. Messages come in as events, workers process them, responses go out as events.&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="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InboundEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventSource&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&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;class&lt;/span&gt; &lt;span class="nc"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Subscribe a handler to an event class.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Publish an event to the internal queue.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_queue&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F0cx39d9o85bs9zvbyiqx.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%2F0cx39d9o85bs9zvbyiqx.png" alt="Event-driven system" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Channels &amp;amp; Agent Worker &amp;amp; Delivery Worker
&lt;/h3&gt;

&lt;p&gt;Three workers form a pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Channel Worker&lt;/strong&gt; — Receives messages from platforms (CLI, Telegram, WebSocket), publishes &lt;code&gt;InboundEvent&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Worker&lt;/strong&gt; — Subscribes to &lt;code&gt;InboundEvent&lt;/code&gt;s, runs the agent session, publishes &lt;code&gt;OutboundEvent&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery Worker&lt;/strong&gt; — Subscribes to &lt;code&gt;OutboundEvent&lt;/code&gt;s, routes responses back to the right channel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A channel is an abstraction over a messaging platform. CLI, Telegram, Discord, WebSocket. The channel publishes an &lt;code&gt;InboundEvent&lt;/code&gt; to the event bus.&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%2F1hp8yruzfqza1al16c5p.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%2F1hp8yruzfqza1al16c5p.png" alt="Channels" width="800" height="400"&gt;&lt;/a&gt;&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;class&lt;/span&gt; &lt;span class="nc"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the channel. Blocks until stop() is called.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Inbound Event to event_bus
&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Reply to incoming message.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent worker bridges events and sessions:&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;class&lt;/span&gt; &lt;span class="nc"&gt;AgentWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InboundEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dispatch_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InboundEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_def&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Publish result
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delivery worker picks up &lt;code&gt;OutboundEvent&lt;/code&gt;s and sends them back through the appropriate channel:&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;class&lt;/span&gt; &lt;span class="nc"&gt;DeliveryWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Look up which channel this session belongs to
&lt;/span&gt;        &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_channel_for_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Confirm delivery - removes persisted event file
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event Persistence: Don't Lose Messages
&lt;/h3&gt;

&lt;p&gt;What happens if the server crashes after the agent responds but before delivery? The message is lost.&lt;/p&gt;

&lt;p&gt;The fix: persist &lt;code&gt;OutboundEvent&lt;/code&gt;s to disk before dispatching, delete only after successful delivery:&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;class&lt;/span&gt; &lt;span class="nc"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_recover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Re-dispatch pending events on startup
&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_queue&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_persist_outbound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Write to disk first
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_notify_subscribers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called by DeliveryWorker after successful delivery.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Delete persisted file
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow is complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user message → channel → InboundEvent → AgentWorker → OutboundEvent → persist → DeliveryWorker → channel → user → ack → delete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  WebSocket
&lt;/h2&gt;

&lt;p&gt;Sometimes you want code to talk to your agent, not a human. WebSocket provides a programmatic interface.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WebSocketWorker&lt;/code&gt; has two roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Channel&lt;/strong&gt; — Receives messages from WebSocket clients, publishes &lt;code&gt;InboundEvent&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broadcaster&lt;/strong&gt; — Subscribes to all events, broadcasts them to every connected client
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebSocketWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Broadcaster role: subscribe to ALL events
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event_class&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;InboundEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OutboundEvent&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventbus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Channel role: receive from clients
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_run_client_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Publishes InboundEvent
&lt;/span&gt;        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Broadcaster role: send to all clients
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;await&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;send_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&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="nd"&gt;@app.websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;websocket_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;websocket_worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Faiek5yg5byrs2rdaxo9j.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%2Faiek5yg5byrs2rdaxo9j.png" alt="WebSocket" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;Previous: Pack the Conversation And Carry On&lt;/a&gt; | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;Next: Many of Them&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 3: Pack the Conversation And Carry On</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:13:46 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save the Conversation
&lt;/h2&gt;

&lt;p&gt;You and your agent had a great conversation. And you kill the shell. Too bad it doesn't remember any of it.&lt;/p&gt;

&lt;p&gt;The solution is simple enough, just save session metadata and messages to disk. And it can end up something like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.history/
├── index.jsonl              # Session metadata
└── sessions/
    └── {session_id}.jsonl   # Messages (one file per session)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And corresponding methods to operate on it.&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;class&lt;/span&gt; &lt;span class="nc"&gt;HistoryStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create a new conversation session.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HistoryMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Save a message to history.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HistoryMessage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get all messages for a session.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fl1s267vbqv8gvxfp3zkm.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%2Fl1s267vbqv8gvxfp3zkm.png" alt="Persistence and history" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Windows: The Hidden Limit
&lt;/h2&gt;

&lt;p&gt;LLMs have limits. Even with 200k (just became 1M at the moment I am writing this piece) token context windows, you'll hit them. All conversation messages, tool call request response, they all add up. LLM will refuse to handle them eventually.&lt;/p&gt;

&lt;p&gt;The solution is compaction, summarize old messages, keep the signal, drop the noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compaction: Pack and Carry On
&lt;/h2&gt;

&lt;p&gt;To be defensive, we apply two layer of protection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Summarize&lt;/strong&gt; — Ask LLM to condense old messages into a summary (expensive, preserves gist).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truncate&lt;/strong&gt; — Cut down oversized tool outputs first (cheap, no LLM call). This solve the edge case that the last tool call return a huge result bloat up context directly, leaving entire context in a dead state.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_and_compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SessionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SessionState&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_threshold&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;state&lt;/span&gt;

    &lt;span class="c1"&gt;# Stage 1: truncate large tool results
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_truncate_large_tool_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_threshold&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;state&lt;/span&gt;

    &lt;span class="c1"&gt;# Stage 2: summarize old messages
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_compact_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcc76uuqgcji5v4hs87oy.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%2Fcc76uuqgcji5v4hs87oy.png" alt="Compaction" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trade-off
&lt;/h3&gt;

&lt;p&gt;Same as human beings can't remember exactly what happened a year ago, the model loses nuance after compaction. You're balancing memory against token cost.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;example repo&lt;/a&gt; implements &lt;code&gt;/context&lt;/code&gt; to monitor usage and &lt;code&gt;/compact&lt;/code&gt; to manually trigger when needed.&lt;/p&gt;

&lt;p&gt;Context management is the difference between a demo and a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;Previous: Gear up Your Agent&lt;/a&gt; | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;Next: Beyond the CLI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 2: Gear up Your Agent</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:13:40 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Tools
&lt;/h2&gt;

&lt;p&gt;Tools are part of agents' code asset. But every time you want it to do something new, you have to write code, restart the server, and redeploy.&lt;/p&gt;

&lt;p&gt;How to extend its capability &amp;amp; knowledge base without changing its code?&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills - Dynamic Capabilities Loading
&lt;/h2&gt;

&lt;p&gt;Skills are lazy loaded capabilities at runtime. It isn't something Openclaw invented, but an open standard. Reference the &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview" rel="noopener noreferrer"&gt;official document&lt;/a&gt; for more info.&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%2Fmbeg5iysarc2bx68fs0a.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%2Fmbeg5iysarc2bx68fs0a.png" alt="Skills" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern is simple: a &lt;code&gt;SKILL.md&lt;/code&gt; file with YAML frontmatter for metadata loaded up front and markdown for instructions loaded when needed.&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;create_skill_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_loader&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Discover skills and get metadata
&lt;/span&gt;    &lt;span class="n"&gt;skill_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skill_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discover_skills&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Build XML description of available skills
&lt;/span&gt;    &lt;span class="n"&gt;skills_xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;skills&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;skill_metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;skills_xml&lt;/span&gt; &lt;span class="o"&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;  &amp;lt;skill name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/skill&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;skills_xml&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/skills&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Tool loads full content only when called
&lt;/span&gt;    &lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&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;Load skill. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;skills_xml&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="p"&gt;...)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;skill_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;skill_loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_skill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two Approaches to Skills
&lt;/h3&gt;

&lt;p&gt;Openclaw doesn't implement skills with a separate tool. Instead, it uses &lt;strong&gt;system prompt injection with file reading&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool Approach:&lt;/strong&gt; Dedicated &lt;code&gt;skill&lt;/code&gt; tool lists available skills and loads content. The tool schema includes skill metadata in its description. Self-contained skill discovery and loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Prompt Approach:&lt;/strong&gt; Skill metadata (id, name, description) injected into system prompt. Agent uses standard &lt;code&gt;read&lt;/code&gt; tool to read SKILL.md. No specialized skill tool needed, simpler tool registry.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Slash Commands: User Control
&lt;/h2&gt;

&lt;p&gt;Sometimes you want direct control, not a conversation. Slash commands let you manage the session itself: list skills, show session info, clear history. The implementation is fairly simple.&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;class&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommandRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse and execute a slash command. Returns None if not a command.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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="bp"&gt;None&lt;/span&gt;
        &lt;span class="c1"&gt;# Parse: /command args
&lt;/span&gt;        &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cmd_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_commands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cmd_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration in the main loop — check commands before sending to LLM:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&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;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Check for slash commands first
&lt;/span&gt;        &lt;span class="n"&gt;cmd_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&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;cmd_response&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Normal chat
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;display_agent_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Figle4x2s6xbj1hl7mx3f.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%2Figle4x2s6xbj1hl7mx3f.png" alt="Slash Commands" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Slash Commands and Session History
&lt;/h3&gt;

&lt;p&gt;Slash commands may or may not be added to the session history (message log sent to the LLM). This is a design decision — commands are user controls, not conversation content. Either approach is valid depending on your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Tools: Connect to the World
&lt;/h2&gt;

&lt;p&gt;Your agent lives in a terminal. But the information it needs lives on the web.&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%2Fsefd3hxa8ahrzqh05g18.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%2Fsefd3hxa8ahrzqh05g18.png" alt="Web tools" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two tools bridge this gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;websearch&lt;/strong&gt;: Search the web and get structured results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;webread&lt;/strong&gt;: Fetch and extract content from URLs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;websearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. **&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&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="s"&gt;   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snippet&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="k"&gt;return&lt;/span&gt; &lt;span class="sh"&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your agent can research, fact-check, and pull in live data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents That Grow
&lt;/h2&gt;

&lt;p&gt;Skill solves the problem of extending agents' capability. Web tools lift the restriction of agents limited knowledge base.&lt;/p&gt;

&lt;p&gt;If you try &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this&lt;/a&gt; out, you will find the agent is already way more capable then your expectation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;Previous: Every Agent Starts as a Loop&lt;/a&gt; | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;Next: Pack the Conversation And Carry On&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Understand OpenClaw by Building One - 1: Every Agent Starts as a Loop</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 03:12:46 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-1-every-agent-starts-as-a-loop-36ej"&gt;1. Every Agent Starts as a Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;2. Gear up Your Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-3-pack-the-conversation-and-carry-on-432a"&gt;3. Pack the Conversation And Carry On&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-4-beyond-the-cli-32p7"&gt;4. Beyond the CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-5-many-of-them-k0h"&gt;5. Many of Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-6-agents-are-running-your-are-sleeping-4ooe"&gt;6. Agents are Running, Your are Sleeping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-7-more-context-more-context-2po1"&gt;7. More Context! More Context!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code snippets and working code bases are available at &lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every Agent Starts as a Loop
&lt;/h2&gt;

&lt;p&gt;Strip away the buzzwords, and an agent is just a chat loop that sometimes executes code. The core is maybe 20 lines:&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;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_user_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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 it. No magic. The &lt;code&gt;session.chat()&lt;/code&gt; method sends messages to the LLM and returns the response. You already know this pattern.&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%2Fjad4c9shylv63dv7z4b3.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%2Fjad4c9shylv63dv7z4b3.png" alt="Chat loop" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Transform Talk into Action
&lt;/h2&gt;

&lt;p&gt;What makes an "agent" different from a "chatbot" is tools calls. The LLM decides when to use them. Your job is to define what tools exist and how to run them.&lt;/p&gt;

&lt;p&gt;The pattern is simple, define a tool schema, let the LLM decide when to call it, execute it, feed the result 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.amazonaws.com%2Fuploads%2Farticles%2F36dje81mqtisehm6iy6w.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%2F36dje81mqtisehm6iy6w.png" alt="Tools" width="800" height="400"&gt;&lt;/a&gt;&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;class&lt;/span&gt; &lt;span class="nc"&gt;BaseTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; tell the LLM what the tool does.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parameters&lt;/code&gt; schema tells the what arguments to provide.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute&lt;/code&gt; method is your implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Tool Calling Loop
&lt;/h2&gt;

&lt;p&gt;When the LLM wants to use a tool, it returns a &lt;code&gt;tool_calls&lt;/code&gt; list instead of text and emit &lt;code&gt;stop_reaonson&lt;/code&gt; as &lt;code&gt;tool_use&lt;/code&gt; at the same time. Your agent executes each tool, adds the results to the message history, and calls the LLM again. This continues until the LLM responds with text.&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;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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="n"&gt;tool_schemas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_tool_calls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start Minimal
&lt;/h2&gt;

&lt;p&gt;You don't need dozens of tools. &lt;strong&gt;Read&lt;/strong&gt;, &lt;strong&gt;Write&lt;/strong&gt;, and &lt;strong&gt;Bash&lt;/strong&gt; are enough to start. These let your agent ability to write and execute code, everything else builds on this foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Start from the beginning | &lt;a href="https://dev.to/zeling_chen_73840b4951f53/understand-openclaw-by-building-one-2-gear-up-your-agent-1gcp"&gt;Next: Gear up Your Agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/czl9707/build-your-own-openclaw" rel="noopener noreferrer"&gt;⭐ Star the repo&lt;/a&gt;&lt;/strong&gt; if you found this series helpful!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Beyond the Buzzwords: Context, Prompts, and Tools</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Fri, 13 Feb 2026 01:11:15 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/beyond-the-buzzwords-context-prompts-and-tools-2fgm</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/beyond-the-buzzwords-context-prompts-and-tools-2fgm</guid>
      <description>&lt;p&gt;Wake up in 2026, open a coding assistant, and you're jumping into a terminology soup: &lt;em&gt;Agents, Subagents, Prompts, Contexts, Memory, Modes, Permissions, Tools, Plugins, Skills, Hooks, MCP, LSP, Slash Commands, Workflows, Instructions and etc.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Companies building these tools love creating new branding for every slight variation in interaction. Instead of getting trapped in the vocabulary treadmill, look at the architecture. Every AI coding tool, no matter how fancy the marketing, deals with the same three things: &lt;strong&gt;context, tools, and prompts&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context: The Memory Budget
&lt;/h2&gt;

&lt;p&gt;Context is the agent's working memory. It's also the bottleneck. While context windows have grown, they're still finite and expensive. Every file you open, every tool output you receive, every turn in the conversation eats into your budget.&lt;/p&gt;

&lt;p&gt;The different terminologies you see imply different strategies for managing this constraint.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slash Commands&lt;/strong&gt; (&lt;code&gt;/commit&lt;/code&gt;, &lt;code&gt;/explain&lt;/code&gt;): Reusable instructions. When you catch yourself typing the same prompt over and over, slash commands are the shortcut.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Compaction&lt;/strong&gt;: The AI's long-term memory mechanism. When the chat gets bloated, the system summarizes previous turns. This keeps the conversation going, but you lose the granular details of why a specific decision was made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective Loading&lt;/strong&gt;: Load only what you need, when you need it. The idea is simple: keep the context window empty until the last possible second, then load the specific snippets required for the current line of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying problem hasn't changed since the first chatbots. Context windows are limited. The terminology keeps expanding, but we're still solving the same problem: how to give the agent what it needs without exceeding its memory budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools: The Action Layer
&lt;/h2&gt;

&lt;p&gt;A chatbot can only generate text. An agent can take action. Tools bridge the gap between thinking and doing. From an architectural perspective, everything beyond the prompt is a tool call.&lt;/p&gt;

&lt;p&gt;Tools can be categorized in many ways. Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Actuators (File System &amp;amp; Terminal)&lt;/strong&gt;: These tools let the agent actually modify your world—writing files, creating directories, running shell commands for builds and tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Navigators (LSP &amp;amp; Indexers)&lt;/strong&gt;: LLMs are great at thinking but terrible at finding where things are. They can waste tons of tokens reading file after file just to locate one function. LSP (Language Server Protocol) tools give the agent sharp insight into code syntax, letting it find the right function definition without reading everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Executioners (Sandboxes)&lt;/strong&gt;: Modern agents often use code interpreters or isolated sandboxes. If an agent isn't sure about a logic block, it can write a small script, execute it in a sandbox, and see the real output before suggesting it to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Researcher (Browsers &amp;amp; RAG)&lt;/strong&gt;: These tools let the agent step outside your local machine. A browser tool lets it read information created after the model was trained.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Bridge: MCP (Model Context Protocol)
&lt;/h3&gt;

&lt;p&gt;MCP is not a tool. It's the standardized interface that connects the agent to tools.&lt;/p&gt;

&lt;p&gt;Before MCP, each tool implementation added maintenance burden to the developer team who owned the agent. This meant rebuilding wheels for the same resources over and over.&lt;/p&gt;

&lt;p&gt;With MCP, the transport layer and tool spec are standardized. MCP server implementations become way more reusable, making the entire ecosystem plug-and-play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts: More than Just Talk
&lt;/h2&gt;

&lt;p&gt;When you talk directly to an LLM, you're giving it a user prompt. System prompts work differently. User prompts tell the agent what you want. System prompts tell the agent how to behave.&lt;/p&gt;

&lt;p&gt;There are many fancy terms for system prompts, but they're all about shaping how the AI thinks and acts. The key difference is when and how they load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base Instructions
&lt;/h3&gt;

&lt;p&gt;Base instructions load when the session starts. These set the ground rules: what tools are available, what constraints exist, what background knowledge applies. Files like &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt; typically contain these. They're the foundation that everything else builds on. They can be global or per project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills
&lt;/h3&gt;

&lt;p&gt;Skills load only when needed. A skill usually contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Description: a short piece of text used by the LLM to decide when to load the entire content&lt;/li&gt;
&lt;li&gt;Body: detailed instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A debugging skill won't activate until you ask to debug something. A refactoring skill stays dormant until you mention refactoring. This lazy loading matters, it saves context by not loading domain expertise until it's actually needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Commands are shorthand for user prompts. When you type &lt;code&gt;/refactor&lt;/code&gt;, you're not asking the agent to refactor in a general sense. You're triggering a pre-crafted prompt that says something like "Analyze the selected code and suggest a refactoring that improves readability while preserving functionality." The command is a shortcut for a specific prompt you'd otherwise have to type out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modes
&lt;/h3&gt;

&lt;p&gt;Modes combine system prompts with tool configurations. Plan mode might load a more analytical system prompt and restrict tools to those useful for planning—reading files, analyzing code structure, understanding dependencies. Build mode might use a more action-oriented prompt and prioritize tools for writing code, running tests, and making changes. Modes are presets that bundle together a behavior and the tools that support it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hierarchy of Prompts
&lt;/h3&gt;

&lt;p&gt;The core idea follows the same principles as software development: lazy loading resources and making them reusable.&lt;/p&gt;

&lt;p&gt;These prompts form a hierarchy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modes replace the entire configuration with a pre-baked set of prompts and tools&lt;/li&gt;
&lt;li&gt;Base instructions are the foundation—they always apply&lt;/li&gt;
&lt;li&gt;Skills layer on top, adding domain expertise when needed&lt;/li&gt;
&lt;li&gt;Commands trigger specific user prompts on top of whatever system prompts are active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every prompt terminology does the same thing: layering prompts to shape behavior. Some load early, some load late. Some replace others, some build on them. But they're all prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation: When Context Hits a Wall
&lt;/h2&gt;

&lt;p&gt;This is where the three fundamentals intersect. Because context is limited, we try to split work among multiple agents.&lt;/p&gt;

&lt;p&gt;Interestingly, even for humans, working with high-level planning and design doesn't require considering low-level details. Working with details doesn't require knowledge about the big picture beyond its own scope—as long as the spec is good enough.&lt;/p&gt;

&lt;p&gt;Think of it as the architect agent calling a worker agent as a tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The architect identifies a task that requires deep focus (e.g., "Refactor this 2,000-line module")&lt;/li&gt;
&lt;li&gt;Instead of doing it itself and bloating its own context, the architect delegates it to a subagent&lt;/li&gt;
&lt;li&gt;The subagent starts with a fresh, empty context and a specific skill prompt for refactoring&lt;/li&gt;
&lt;li&gt;Once the work is done, the subagent returns a concise summary to the architect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This recursive agency allows us to deal with bigger tasks without the main brain of the AI becoming a garbled mess of too much information.&lt;/p&gt;

&lt;p&gt;Subagent delegation is just another tool. It's a tool that happens to spawn another agent with its own context. That mental model—thinking of subagents as tools—helps when working with coding agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;The terminology will keep expanding at the speed of marketing, but the underlying mechanics of AI development stay the same.&lt;/p&gt;

&lt;p&gt;The agent vocabulary in 2026 is noisy. But by focusing on these three things—context, tools, and prompts—you move from being a consumer of buzzwords to an architect of the technology. You gain the ability to see the same reliable patterns beneath every new interface that hits the market.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>learning</category>
    </item>
    <item>
      <title>Turn Your Github Contribution Graph into Space Shooter 🚀</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Wed, 14 Jan 2026 02:45:45 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/github-space-shooter-51ho</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/github-space-shooter-51ho</guid>
      <description>&lt;p&gt;Sharing the tool I recently having so much fun building - Github Space Shooter. It does not solve any problem, but turns your boring GitHub contribution graph into a generated space shooter game gif.&lt;/p&gt;

&lt;p&gt;I just launched a &lt;a href="https://gh-space-shooter.kiyo-n-zane.com/" rel="noopener noreferrer"&gt;web wrapper&lt;/a&gt; so you can generate your own galaxy instantly without any setup and watch your year of coding turn into a space battle.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://gh-space-shooter.kiyo-n-zane.com/" rel="noopener noreferrer"&gt;web version&lt;/a&gt; for one time generation.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://github.com/czl9707/gh-space-shooter" rel="noopener noreferrer"&gt;github action&lt;/a&gt; for have the space shooter updated on a scheduled basis.&lt;/p&gt;

&lt;p&gt;And give the repo a star ⭐ if you enjoy.&lt;/p&gt;

</description>
      <category>github</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Convincing Myself to "Vibe Code"</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Sat, 27 Dec 2025 03:14:32 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/convincing-myself-to-vibe-code-2dh9</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/convincing-myself-to-vibe-code-2dh9</guid>
      <description>&lt;p&gt;Wake up in 2026, open a coding assistant, and you're jumping into a terminology soup: &lt;em&gt;Agents, Subagents, Prompts, Contexts, Memory, Modes, Permissions, Tools, Plugins, Skills, Hooks, MCP, LSP, Slash Commands, Workflows, Instructions and etc.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Companies building these tools love creating new branding for every slight variation in interaction. Instead of getting trapped in the vocabulary treadmill, look at the architecture. Every AI coding tool, no matter how fancy the marketing, deals with the same three things: &lt;strong&gt;context, tools, and prompts&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context: The Memory Budget
&lt;/h2&gt;

&lt;p&gt;Context is the agent's working memory. It's also the bottleneck. While context windows have grown, they're still finite and expensive. Every file you open, every tool output you receive, every turn in the conversation eats into your budget.&lt;/p&gt;

&lt;p&gt;The different terminologies you see imply different strategies for managing this constraint.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slash Commands&lt;/strong&gt; (&lt;code&gt;/commit&lt;/code&gt;, &lt;code&gt;/explain&lt;/code&gt;): Reusable instructions. When you catch yourself typing the same prompt over and over, slash commands are the shortcut.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Compaction&lt;/strong&gt;: The AI's long-term memory mechanism. When the chat gets bloated, the system summarizes previous turns. This keeps the conversation going, but you lose the granular details of why a specific decision was made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective Loading&lt;/strong&gt;: Load only what you need, when you need it. The idea is simple: keep the context window empty until the last possible second, then load the specific snippets required for the current line of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying problem hasn't changed since the first chatbots. Context windows are limited. The terminology keeps expanding, but we're still solving the same problem: how to give the agent what it needs without exceeding its memory budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools: The Action Layer
&lt;/h2&gt;

&lt;p&gt;A chatbot can only generate text. An agent can take action. Tools bridge the gap between thinking and doing. From an architectural perspective, everything beyond the prompt is a tool call.&lt;/p&gt;

&lt;p&gt;Tools can be categorized in many ways. Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Actuators (File System &amp;amp; Terminal)&lt;/strong&gt;: These tools let the agent actually modify your world—writing files, creating directories, running shell commands for builds and tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Navigators (LSP &amp;amp; Indexers)&lt;/strong&gt;: LLMs are great at thinking but terrible at finding where things are. They can waste tons of tokens reading file after file just to locate one function. LSP (Language Server Protocol) tools give the agent sharp insight into code syntax, letting it find the right function definition without reading everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Executioners (Sandboxes)&lt;/strong&gt;: Modern agents often use code interpreters or isolated sandboxes. If an agent isn't sure about a logic block, it can write a small script, execute it in a sandbox, and see the real output before suggesting it to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Researcher (Browsers &amp;amp; RAG)&lt;/strong&gt;: These tools let the agent step outside your local machine. A browser tool lets it read information created after the model was trained.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Bridge: MCP (Model Context Protocol)
&lt;/h3&gt;

&lt;p&gt;MCP is not a tool. It's the standardized interface that connects the agent to tools.&lt;/p&gt;

&lt;p&gt;Before MCP, each tool implementation added maintenance burden to the developer team who owned the agent. This meant rebuilding wheels for the same resources over and over.&lt;/p&gt;

&lt;p&gt;With MCP, the transport layer and tool spec are standardized. MCP server implementations become way more reusable, making the entire ecosystem plug-and-play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts: More than Just Talk
&lt;/h2&gt;

&lt;p&gt;When you talk directly to an LLM, you're giving it a user prompt. System prompts work differently. User prompts tell the agent what you want. System prompts tell the agent how to behave.&lt;/p&gt;

&lt;p&gt;There are many fancy terms for system prompts, but they're all about shaping how the AI thinks and acts. The key difference is when and how they load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base Instructions
&lt;/h3&gt;

&lt;p&gt;Base instructions load when the session starts. These set the ground rules: what tools are available, what constraints exist, what background knowledge applies. Files like &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt; typically contain these. They're the foundation that everything else builds on. They can be global or per project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills
&lt;/h3&gt;

&lt;p&gt;Skills load only when needed. A skill usually contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Description: a short piece of text used by the LLM to decide when to load the entire content&lt;/li&gt;
&lt;li&gt;Body: detailed instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A debugging skill won't activate until you ask to debug something. A refactoring skill stays dormant until you mention refactoring. This lazy loading matters, it saves context by not loading domain expertise until it's actually needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Commands are shorthand for user prompts. When you type &lt;code&gt;/refactor&lt;/code&gt;, you're not asking the agent to refactor in a general sense. You're triggering a pre-crafted prompt that says something like "Analyze the selected code and suggest a refactoring that improves readability while preserving functionality." The command is a shortcut for a specific prompt you'd otherwise have to type out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modes
&lt;/h3&gt;

&lt;p&gt;Modes combine system prompts with tool configurations. Plan mode might load a more analytical system prompt and restrict tools to those useful for planning—reading files, analyzing code structure, understanding dependencies. Build mode might use a more action-oriented prompt and prioritize tools for writing code, running tests, and making changes. Modes are presets that bundle together a behavior and the tools that support it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hierarchy of Prompts
&lt;/h3&gt;

&lt;p&gt;The core idea follows the same principles as software development: lazy loading resources and making them reusable.&lt;/p&gt;

&lt;p&gt;These prompts form a hierarchy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modes replace the entire configuration with a pre-baked set of prompts and tools&lt;/li&gt;
&lt;li&gt;Base instructions are the foundation—they always apply&lt;/li&gt;
&lt;li&gt;Skills layer on top, adding domain expertise when needed&lt;/li&gt;
&lt;li&gt;Commands trigger specific user prompts on top of whatever system prompts are active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every prompt terminology does the same thing: layering prompts to shape behavior. Some load early, some load late. Some replace others, some build on them. But they're all prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation: When Context Hits a Wall
&lt;/h2&gt;

&lt;p&gt;This is where the three fundamentals intersect. Because context is limited, we try to split work among multiple agents.&lt;/p&gt;

&lt;p&gt;Interestingly, even for humans, working with high-level planning and design doesn't require considering low-level details. Working with details doesn't require knowledge about the big picture beyond its own scope—as long as the spec is good enough.&lt;/p&gt;

&lt;p&gt;Think of it as the architect agent calling a worker agent as a tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The architect identifies a task that requires deep focus (e.g., "Refactor this 2,000-line module")&lt;/li&gt;
&lt;li&gt;Instead of doing it itself and bloating its own context, the architect delegates it to a subagent&lt;/li&gt;
&lt;li&gt;The subagent starts with a fresh, empty context and a specific skill prompt for refactoring&lt;/li&gt;
&lt;li&gt;Once the work is done, the subagent returns a concise summary to the architect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This recursive agency allows us to deal with bigger tasks without the main brain of the AI becoming a garbled mess of too much information.&lt;/p&gt;

&lt;p&gt;Subagent delegation is just another tool. It's a tool that happens to spawn another agent with its own context. That mental model—thinking of subagents as tools—helps when working with coding agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;The terminology will keep expanding at the speed of marketing, but the underlying mechanics of AI development stay the same.&lt;/p&gt;

&lt;p&gt;The agent vocabulary in 2026 is noisy. But by focusing on these three things—context, tools, and prompts—you move from being a consumer of buzzwords to an architect of the technology. You gain the ability to see the same reliable patterns beneath every new interface that hits the market.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
    </item>
    <item>
      <title>The CSS Odyssey: Why I Turned back to CSS After Trying Everything Else</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Thu, 05 Jun 2025 23:19:53 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/the-css-odyssey-why-i-turned-back-to-css-after-trying-everything-else-29ni</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/the-css-odyssey-why-i-turned-back-to-css-after-trying-everything-else-29ni</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; This is a story of my journey through CSS solutions. Which led to my personal opinion that simpler tools often work better than complex abstractions. CSS-in-JS, zero-runtime CSS-in-JS, and Tailwind all taught me valuable lessons, but they all added build complexity that eventually became more trouble than benefit. And I eventually returned back to CSS Modules for a better maintainability and simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with Vanilla CSS
&lt;/h2&gt;

&lt;p&gt;I started my web development journey the traditional way years back—HTML, CSS and JS in separate files. When I learned React, I kept the same approach without exploring the solutions ecosystem much. I'd create a &lt;code&gt;.css&lt;/code&gt; file, import it into my &lt;code&gt;.jsx&lt;/code&gt; file, and split stylesheets as they grew.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .container {&lt;/span&gt;
&lt;span class="c1"&gt;//     width: 100%;&lt;/span&gt;
&lt;span class="c1"&gt;//     padding: 0 var(--spacing-block);&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;others&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Encounter CSS-in-JS
&lt;/h2&gt;

&lt;p&gt;Diving into CSS-in-JS wasn't really a technical choice. My wife, as a UX designer, had good words about &lt;a href="https://m3.material.io/" rel="noopener noreferrer"&gt;Material Design&lt;/a&gt;, so I headed to &lt;a href="https://mui.com/" rel="noopener noreferrer"&gt;MUI&lt;/a&gt; without thinking too much. At that time, &lt;a href="https://emotion.sh/docs/introduction" rel="noopener noreferrer"&gt;Emotion&lt;/a&gt; was the first-class citizen there. Meanwhile, my company's UI component library chose &lt;a href="https://styled-components.com/" rel="noopener noreferrer"&gt;styled-components&lt;/a&gt; for styling. Both are widely used CSS-in-JS libraries.&lt;/p&gt;

&lt;p&gt;Most CSS-in-JS solutions promised something compelling: styling information and UI logic in one place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 var(--spacing-block)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything felt smooth. Styling information and UI logic co-located in one file. React merges HTML and JS into JSX, and now CSS joined them. Style conflicts weren't a problem anymore—the CSS-in-JS library handled styling definition order on the fly.&lt;/p&gt;

&lt;p&gt;Although many complained about CSS-in-JS performance, the web projects I worked on seldom reached the scale where performance became a concern, so I took the benefits for granted.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What CSS-in-JS taught me:&lt;/strong&gt; Style encapsulation is powerful. Instead of defining global styles that can conflict anywhere, scoped styles help avoid pollution and save hours of debugging. The co-location principle also makes refactoring much easier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Server-Side Reality Check: Zero-Runtime CSS-in-JS
&lt;/h3&gt;

&lt;p&gt;The honeymoon ended when I migrated my SPA to Next.js. Next.js offered App Router and RSC (React Server Components), and I wanted to embrace their benefits.&lt;/p&gt;

&lt;p&gt;CSS-in-JS libraries manipulate stylesheets at runtime in the browser, making them fundamentally incompatible with server-side rendering. I had to awkwardly separate components—styling on the client, data fetching on the server. Working with component libraries required extra boilerplate to get things working.&lt;/p&gt;

&lt;p&gt;I wasn't alone facing this issue. Zero-runtime CSS-in-JS promised to solve it. Solutions like &lt;a href="https://linaria.dev/" rel="noopener noreferrer"&gt;Linaria&lt;/a&gt; and &lt;a href="https://github.com/mui/pigment-css" rel="noopener noreferrer"&gt;Pigment-CSS&lt;/a&gt; (MUI's Emotion successor) extract CSS at build time, generating static stylesheets while preserving the CSS-in-JS developer experience.&lt;/p&gt;

&lt;p&gt;From a usage perspective, nothing changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 var(--spacing-block)&lt;/span&gt;&lt;span class="dl"&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 &lt;em&gt;magic&lt;/em&gt; happened during transpilation. CSS content gets extracted to static files, and class names are injected back into JavaScript.&lt;/p&gt;

&lt;p&gt;This sounded promising—the webpack plugin would handle everything. After using it for a while, the main drawback was the debugging experience. The tool became essentially a black box. Error messages were cryptic and interfered with other tooling. This became worse in Next.js context (Yeah, another black box).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important lesson learned:&lt;/strong&gt; Don't make things that should be static dynamic. Don't sacrifice runtime performance for developer experience, especially when the build complexity trade-off isn't worth it.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Eventual Leave
&lt;/h3&gt;
&lt;/blockquote&gt;

&lt;p&gt;The breaking point came when integrating with the &lt;a href="https://unifiedjs.com/" rel="noopener noreferrer"&gt;unifiedjs&lt;/a&gt; ecosystem. After 8 hours of removing code piece by piece to isolate a build error—with completely non-descriptive error messages—I gave up.&lt;/p&gt;

&lt;p&gt;I can only describe the issue as "build-time interference between unified and Pigment-CSS." Maybe it was about server/client side bundling, maybe ESM/CommonJS module conflicts. I wasn't experienced enough to tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maybe It's Time to Join Tailwind
&lt;/h2&gt;

&lt;p&gt;Tailwind is likely the most popular CSS solution library. There are massive utilities and plugins built around it. Popular UI component libraries like &lt;a href="https://ui.shadcn.com/" rel="noopener noreferrer"&gt;shadcn/ui&lt;/a&gt; use Tailwind by default, making it even more popular. With so many good words, why not give it a try?&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailwind Itself Is a Language
&lt;/h3&gt;

&lt;p&gt;Tailwind is an abstraction layer upon CSS, built by experienced and smart CSS developers.&lt;/p&gt;

&lt;p&gt;It's great! I learned a lot by studying how they structure CSS code. One excellent example: when defining color token CSS variables, instead of &lt;code&gt;--my-color: rgb(0 0 0);&lt;/code&gt;, we can do &lt;code&gt;--my-color: 0 0 0;&lt;/code&gt;. Then when consuming: &lt;code&gt;color: rgb(var(--my-color) / 50%);&lt;/code&gt;, which gives us easier opacity control.&lt;/p&gt;

&lt;p&gt;However, migrating from CSS-in-JS to Tailwind brought quite a learning curve. It took time to memorize class naming conventions—&lt;code&gt;p-1&lt;/code&gt; means &lt;code&gt;padding: 4px&lt;/code&gt;, pseudo-class effects like &lt;code&gt;&amp;amp;:hover .class&lt;/code&gt; become &lt;code&gt;group-hover:&amp;lt;...&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Initially I thought Tailwind was simple—just a huge predefined stylesheet. Actually, it's more complex then I expected. It utilizes &lt;a href="https://github.com/csstools/postcss-plugins" rel="noopener noreferrer"&gt;PostCSS&lt;/a&gt; to perform tree shaking and collect arbitrary classes during build time. Extra learning was required beyond basic usage. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Config theme tokens in &lt;code&gt;tailwind.config.js&lt;/code&gt; and use them correctly.&lt;/li&gt;
&lt;li&gt;Tailwind doesn't handle style conflicts by itself. To solve this, There is another NPM package to install &lt;code&gt;tw-merge&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Dynamic styling is not native. They all have to be whitelisted in &lt;code&gt;safelist&lt;/code&gt; inside &lt;code&gt;tailwind.config.js&lt;/code&gt;, or just fall back to CSS variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What Tailwind taught me:&lt;/strong&gt; Systematic design thinking and utility-first patterns. The constraint-based approach forced better design decisions and taught me about consistent spacing systems and color schemes.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Backfire of Tailwind
&lt;/h3&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real issue was readability. Tailwind made applying styles extremely easy—too easy. I found myself writing class strings like below and it keeps growing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;group-hover:text-foreground text-foreground/75 transition-colors duration-500 col-span-1 min-w-[33%] text-right lg:text-right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These strings kept growing, containing layout, colors, hover states, responsive breakpoints—everything mashed together without structure or hierarchy, not even indentation. Reading this is like parsing a dense command line with multiple flags and options all crammed together. &lt;/p&gt;

&lt;p&gt;It felt like inline styles with extra steps—and arguably less readable than inline styles because of the abbreviated syntax. &lt;/p&gt;

&lt;p&gt;At meantime, I found my self duplicating some set of class name very often. Tailwind docs suggested creating my own utility classes, using &lt;code&gt;@apply&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.my-typography&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;group-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text-foreground&lt;/span&gt; &lt;span class="n"&gt;text-foreground&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt; &lt;span class="n"&gt;transition-colors&lt;/span&gt; &lt;span class="n"&gt;duration-500&lt;/span&gt; &lt;span class="n"&gt;col-span-1&lt;/span&gt; &lt;span class="n"&gt;min-w-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;33%&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;text-right&lt;/span&gt; &lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text-right&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;At this point, I realized I was just writing CSS with an abstraction layer on top. What was the benefit over vanilla CSS?&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Module, The Final Frontier
&lt;/h2&gt;

&lt;p&gt;Both CSS-in-JS and Tailwind increase build-time complexity, and add extra abstraction layers. It gets worse that when the solution create issues that involve more tools on top of them.&lt;/p&gt;

&lt;p&gt;I eventually returned to my old friend CSS—but not the poorly structured CSS I started with. The experiments with CSS-in-JS and Tailwind taught me valuable patterns and techniques:&lt;/p&gt;

&lt;p&gt;CSS Modules come with challenges, and I can't find optimal solutions for everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Style conflicts&lt;/strong&gt; is one. Although CSS class definitions are placed in reverse import order, problems still occur in Next.js when some pieces only appear in page but not layout CSS bundles. Following &lt;a href="https://getbem.com/" rel="noopener noreferrer"&gt;BEM (Block Element Modifier)&lt;/a&gt; and "Composition over Extension" patterns to separate concerns helps avoid styling collisions significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Media query has limitations&lt;/strong&gt;. &lt;code&gt;@media(min-width: var(--breakpoint-lg))&lt;/code&gt; won't work. &lt;a href="https://github.com/csstools/postcss-plugins" rel="noopener noreferrer"&gt;PostCSS&lt;/a&gt; plugins support custom media query &lt;code&gt;@custom-media --small-viewport (max-width: 30rem);&lt;/code&gt;, then &lt;code&gt;@media (--small-viewport)&lt;/code&gt;. Still duplications, but at least I can have breakpoints all defined in one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ending
&lt;/h2&gt;

&lt;p&gt;This round trip helped me form my tool selection principle: &lt;strong&gt;minimize build process interference&lt;/strong&gt;. Every abstraction promises to solve problems but adds its own complexity.&lt;/p&gt;

&lt;p&gt;The goal is never finding the "perfect" CSS solution, but choosing tools that align with your project's constraints. For me, that means keeping the build process simple and letting things do what they should do.&lt;/p&gt;

&lt;p&gt;Sometimes the best path forward takes you back to where you started, armed with everything you learned along the way.&lt;/p&gt;

</description>
      <category>react</category>
      <category>styling</category>
      <category>learning</category>
    </item>
    <item>
      <title>5 WTF Moments in Python</title>
      <dc:creator>Zane Chen</dc:creator>
      <pubDate>Mon, 05 May 2025 23:27:10 +0000</pubDate>
      <link>https://dev.to/zeling_chen_73840b4951f53/5-wtf-moments-in-python-255</link>
      <guid>https://dev.to/zeling_chen_73840b4951f53/5-wtf-moments-in-python-255</guid>
      <description>&lt;h2&gt;
  
  
  1. It Is Not the Function I Called!
&lt;/h2&gt;

&lt;p&gt;I encountered this issue when implementing the &lt;a href="https://www.notion.so/Code-like-an-Onion-The-Decorator-Pattern-1d1f2d6bb3c580f2b1d8ed8c66c8b57c?pvs=21" rel="noopener noreferrer"&gt;Decorator Pattern&lt;/a&gt; in python.&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;callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;callback1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callback2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callback3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;wrapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is invoked.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wrapped_cb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;wrapped_cb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;callback3 is invoked.
callback3 is invoked.
callback3 is invoked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surprised? This would likely break heads of developers famaliar with other languages. The rationale, however, is not complex. When defining function in Python, variables inside the closure are &lt;strong&gt;captured by reference&lt;/strong&gt;, not by value. This means the values of &lt;code&gt;callback&lt;/code&gt; in each &lt;code&gt;wrapped_callback&lt;/code&gt; are resolved during execution time, not definition time. When the interpreter looks up the value of &lt;code&gt;callback&lt;/code&gt; for each &lt;code&gt;wrapped_callback&lt;/code&gt;, &lt;code&gt;callback&lt;/code&gt; always holds the value of &lt;code&gt;callback3&lt;/code&gt;, since it ended up with the value when exiting the loop.&lt;/p&gt;

&lt;p&gt;To avoid this, simply avoid defining function inside loops.&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;callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;callback1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callback2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callback3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;wrapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is invoked.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;wrapped&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wrap_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wrapped_cb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;wrapped_cb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;callback1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;callback2&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;callback3&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a side note, be careful when using &lt;code&gt;lambda&lt;/code&gt; functions in Python as well. They're convenient but can cause the same problem if used inappropriately.&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;funcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&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;No.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;No.2
No.2
No.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Mutable Default Function Parameters Can Bite you
&lt;/h2&gt;

&lt;p&gt;If you ever used default parameter in compiled languages like C#, the compiler forced the default value to be a compile time value. Although Python is an interpreted language, it still binds the default value at the function definition time, which means the default value got created only once. If the value is mutable and gets modified, subsequent calls will use the modified value.&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;append_1_to_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;append_1_to_list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# [1]
&lt;/span&gt;&lt;span class="nf"&gt;append_1_to_list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 1]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solution? Avoid this pattern. If you really need this, make it default to &lt;code&gt;None&lt;/code&gt; and intialize the value in function body.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. “Finally” Is Literally Your Final Behavior
&lt;/h2&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;will_throw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Will you see me?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;will_throw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 'Will you see me?'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python silently eats the exception! This is more than confusing. I haven't found a satisfying explanation for why this happens. But the behavior here is that, the interpreter stores the error temporarily before the &lt;code&gt;finally&lt;/code&gt; statement executed. If a &lt;code&gt;return&lt;/code&gt; statement appears in &lt;code&gt;finally&lt;/code&gt;, the exception will be discarded.&lt;/p&gt;

&lt;p&gt;Similarly, &lt;code&gt;finally&lt;/code&gt; is remarkably greedy, it will even discarded your previous return value if any.&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;who_should_i_see&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You should see me.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Will you see me?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;who_should_i_see&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# 'Will you see me?'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The Method Is not the Method
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;the_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;the_method&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;the_method&lt;/span&gt;  &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To understand this behavior, let's examine what happens under the hood when we use &lt;code&gt;obj.the_method&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Instead of solving the puzzle directly, try answer another question, what is &lt;code&gt;self&lt;/code&gt; meant for in the method parameter? We are taught that &lt;code&gt;self&lt;/code&gt; representsthe object the method is bound to, which is why &lt;code&gt;self&lt;/code&gt; doesn't appear in the method signature when we call methods on objects. Who  fill the &lt;code&gt;self&lt;/code&gt; parameter with the object? Magic, and it happened when the method is accessed or called.&lt;/p&gt;

&lt;p&gt;This binding happens every time we access &lt;code&gt;obj.the_method&lt;/code&gt;, creating a new method object from the function definition and the bound object every time. This explains why &lt;code&gt;obj.the_method is obj.the_method&lt;/code&gt; evaluates to &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While we won't dive deeper in this post, it's worth learning about this comprehensively. Python object attributes use something called &lt;a href="https://docs.python.org/3/howto/descriptor.html#pure-python-equivalents" rel="noopener noreferrer"&gt;Discriptor&lt;/a&gt; to control access behavior, which is where the magic happens. Then &lt;a href="https://docs.python.org/3/howto/descriptor.html#pure-python-equivalents" rel="noopener noreferrer"&gt;Python official doc&lt;/a&gt; is the best place to learn more. &lt;/p&gt;

&lt;h2&gt;
  
  
  5. True Is an Integer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surprise again! Booleans are integers! Both &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;False&lt;/code&gt; and instances of both &lt;code&gt;bool&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt;. You might wonder about the relationship between &lt;code&gt;bool&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt;.&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="nf"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;span class="nf"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah, &lt;code&gt;bool&lt;/code&gt; is a subclass of &lt;code&gt;int&lt;/code&gt;. It's hard to believe that a "modern language" would have this quirk, but remember that. But remember, Python is 32-year-old man, at least older than me. Python was created without a boolean type, just like old C, and developers historically use 0 and 1 instead. For the backward compatibility, &lt;code&gt;bool&lt;/code&gt; became a subclass of &lt;code&gt;int&lt;/code&gt; when it was introduced.&lt;/p&gt;

</description>
      <category>python</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
