<?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: WonderLab</title>
    <description>The latest articles on DEV Community by WonderLab (@wonderlab).</description>
    <link>https://dev.to/wonderlab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3797373%2F25beba30-d8d4-4d2e-9ec6-170356089350.jpg</url>
      <title>DEV Community: WonderLab</title>
      <link>https://dev.to/wonderlab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wonderlab"/>
    <language>en</language>
    <item>
      <title>LLM-Driven Automated Testing Series (02): The Unified Technical Foundation — Visual Grounding / DOM Semantics / Computer Use</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Tue, 22 Sep 2026 01:21:00 +0000</pubDate>
      <link>https://dev.to/wonderlab/llm-driven-automated-testing-series-02-the-unified-technical-foundation-visual-grounding-dom-5dcb</link>
      <guid>https://dev.to/wonderlab/llm-driven-automated-testing-series-02-the-unified-technical-foundation-visual-grounding-dom-5dcb</guid>
      <description>&lt;h2&gt;
  
  
  Same problem, three answers
&lt;/h2&gt;

&lt;p&gt;"Find the submit button on this page and click it."&lt;/p&gt;

&lt;p&gt;Hand that instruction to three different open-source GUI agent projects and you'll get three entirely different implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One feeds the full screenshot to a specially-trained visual grounding model, which directly outputs a coordinate like &lt;code&gt;(842, 613)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Another parses the page's DOM or accessibility tree, finds the node with &lt;code&gt;role=button, name="Submit"&lt;/code&gt;, and calls its click handler directly&lt;/li&gt;
&lt;li&gt;A third skips any dedicated locating step entirely — a general-purpose multimodal model just looks at the screenshot and decides "click here" on its own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three paths aren't interchangeable implementation details — they're distinct architectural decisions, each with clear trade-offs and boundaries. This post builds the vocabulary the next 12 case-study posts will reuse. Without it, every post would have to re-explain "which locating approach this project uses" from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Path 1: Pixel-Based Visual Grounding Models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This path treats the GUI as an image. Given a natural-language reference ("the submit button") and a screenshot, the model directly outputs the element's coordinates or bounding box on screen.&lt;/p&gt;

&lt;p&gt;This is a dedicated sub-task in the literature called &lt;strong&gt;GUI grounding&lt;/strong&gt;: it doesn't decide "what to do next" — it only maps a natural-language reference precisely onto screen coordinates. Representative open-source models include UGround, Aria-UI, and Alibaba's self-trained GUI-Owl (used in Mobile-Agent-v3, covered in a later post).&lt;/p&gt;

&lt;p&gt;The typical architecture is two-stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User task: "Submit this form"
        ↓
Planning model (can be a general LLM): "Next step is to click the submit button"
        ↓
Visual grounding model (dedicated, e.g. UGround): "submit button" + screenshot → (842, 613)
        ↓
Execution layer: click at (842, 613)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Planning and grounding are two separate models, each specialized for one job.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: no dependency on any application-internal structured data — pure "looking," which makes it naturally cross-platform (web, mobile, desktop, even game UIs — anywhere you can take a screenshot). A key result from the UGround paper: this path beats accessibility-tree-dependent approaches (Path 2 below) by up to 20 absolute percentage points on multiple GUI grounding tasks, because real-world accessibility trees are often incomplete or unreliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every locating step requires a visual model inference pass — slower and more expensive than reading structured data directly&lt;/li&gt;
&lt;li&gt;Accuracy drops sharply for small elements and dense layouts (the ScreenSpot-Pro numbers below quantify how much)&lt;/li&gt;
&lt;li&gt;The grounding model itself needs dedicated training and maintenance — you can't get high accuracy "for free" from a general-purpose model&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Path 2: DOM / Accessibility-Tree Structured Understanding
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This path doesn't look at pixels — it looks at structure. Web pages have a DOM tree; desktop and mobile apps have an accessibility tree (the semantic view the OS exposes for screen readers and other assistive tools). Serialize that tree into text and hand it to an LLM directly: "Given this structure, which node is the submit button?"&lt;/p&gt;

&lt;p&gt;Once the target node is found, subsequent actions don't need coordinates at all — you call the node's exposed &lt;code&gt;click()&lt;/code&gt;, &lt;code&gt;setValue()&lt;/code&gt;, etc. directly, the same mechanism traditional Selenium/Playwright scripts already use. The only thing that changes is that "finding the node" moves from a hardcoded selector to LLM-based semantic matching.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Structured data is text, so token cost is far lower than images, and it's faster&lt;/li&gt;
&lt;li&gt;Locating accuracy doesn't degrade with screen resolution or element size — if the node is in the tree, semantic matching is precise regardless&lt;/li&gt;
&lt;li&gt;Can reuse an existing automation framework's execution layer (Playwright, Appium) — the change is isolated to the "locating" step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strongly dependent on the app exposing complete, reliable structured information.&lt;/strong&gt; Canvas-rendered content, embedded WebViews, and heavily custom-rendered mobile widgets often show up as an opaque black-box node in the accessibility tree with no semantic information at all — at that point this path simply fails and must fall back to a visual approach&lt;/li&gt;
&lt;li&gt;Real-world DOM/accessibility trees can be enormous (modern SPAs routinely have thousands of nodes); stuffing the whole thing into an LLM's context is expensive, requiring pruning or downsampling — this is exactly the problem work like "DOM Downsampling for LLM-Based Web Agents" sets out to solve&lt;/li&gt;
&lt;li&gt;Tree structures differ significantly across platforms (Web DOM, Android View tree, iOS UIKit tree), so one semantic-understanding pipeline doesn't transfer cleanly across platforms&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Path 3: Computer Use — Raw Coordinate Clicking
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This is the most "brute-force," yet also the most commercially deployed path of the past two years: Anthropic's Computer Use and OpenAI's Operator/CUA both fall into this category.&lt;/p&gt;

&lt;p&gt;The key difference from Path 1: &lt;strong&gt;there's no separate "grounding model" step.&lt;/strong&gt; A general-purpose multimodal model takes the screenshot directly as input and directly outputs the next action — "click at (842, 613)," "type text," "press Tab" — perception and decision-making happen in a single pass by the same model. There's no "planning model + dedicated grounding model" division of labor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Screenshot → general multimodal model (perception + decision combined) → directly outputs: click(842, 613)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Simplest architecture — no separate grounding model to maintain, no platform-specific structured data to parse&lt;/li&gt;
&lt;li&gt;Because it has zero dependency on any interface the app exposes, it has, in principle, the highest ceiling on generality — "if you can screenshot it, you can operate it" — and it's the technical basis for turning an agent from "a web automation tool" into "a general assistant that operates a computer"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Locating accuracy is the weakest of the three paths on public benchmarks — the numbers in the next section quantify this&lt;/li&gt;
&lt;li&gt;Highest latency and cost: every single action requires a full multimodal inference pass, and tasks often need many consecutive steps to complete — this is the most expensive path of the three to run&lt;/li&gt;
&lt;li&gt;No structured-data fallback: Path 2 can approach near-100% precision when the structure is reliable; Path 3 has no such ceiling guarantee — it's purely bounded by the model's visual grounding ability&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Quantifying the gap with benchmarks
&lt;/h2&gt;

&lt;p&gt;Saying "one path is better" without numbers is meaningless. A few public benchmarks let us compare the three paths on the same scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ScreenSpot family: testing "locating" in isolation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ScreenSpot&lt;/strong&gt; was the earliest GUI grounding benchmark: given a natural-language reference and a screenshot, the model must point to the target element's coordinates. Leading models already score above 90% accuracy on this benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ScreenSpot-Pro&lt;/strong&gt; is the hardened version: all screenshots come from real professional desktop software (CAD tools, IDEs, data-analysis apps), at higher resolution with smaller, denser interface elements. The same leading models drop to 37.1% (one of the highest scores reported by a 2025-generation model — most models score far lower).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark          What it measures              Leading model accuracy
─────────────────────────────────────────────────────────────────────
ScreenSpot          Locating in simple UIs         90%+
ScreenSpot-Pro       Locating in professional UIs   ~37% (best case)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That cliff-edge drop reveals a key fact: &lt;strong&gt;"locating accuracy" isn't a fixed property of a model — it collapses sharply as interface density and element size push in the wrong direction.&lt;/strong&gt; This is exactly why mobile automation (covered in five dedicated posts, 05-09) leans so heavily on dedicated grounding models — icons on a phone screen tend to be smaller and denser than buttons on desktop software.&lt;/p&gt;

&lt;h3&gt;
  
  
  OSWorld: testing end-to-end task success
&lt;/h3&gt;

&lt;p&gt;ScreenSpot only tests "can you find the element." &lt;strong&gt;OSWorld&lt;/strong&gt; tests whether a full task actually gets completed — executing a multi-step task in a real OS environment (e.g., "clean up this data in LibreOffice Calc and export it as a PDF"), which requires planning, locating, execution, and error recovery across the whole chain.&lt;/p&gt;

&lt;p&gt;The OSWorld paper reports a human baseline of &lt;strong&gt;72.36%&lt;/strong&gt; task completion, while early baseline models (e.g. GPT-4V) achieved only &lt;strong&gt;12.24%&lt;/strong&gt; — a gap of nearly 60 percentage points. The paper explicitly attributes the bottleneck to GUI grounding and operational knowledge, not task comprehension itself. That number has since closed rapidly as dedicated agent architectures (e.g. the Agent S series) improved — by 2025-2026, leading approaches reach roughly 65%-70%+, closing in on the human baseline, but that gain came from agent-architecture-level engineering — planning, reflection, retry loops — not purely from a better underlying grounding model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Human baseline    Early baseline model    2026 leading approaches
──────────────────────────────────────────────────────────────────────────────────
OSWorld task success   72.36%           12.24%                  ~65%-70%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The takeaway: &lt;strong&gt;grounding accuracy and task success rate are two different things.&lt;/strong&gt; A perfectly accurate grounding model still won't get you a high end-to-end success rate if the agent can't re-plan after a failure or verify whether its action achieved the intended effect. That's why "planning + reflection mechanisms" will be just as important a topic as "which grounding model was chosen" when we get to mobile automation projects like ARTEMIS and Mobile-Agent-v3.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three paths aren't mutually exclusive
&lt;/h2&gt;

&lt;p&gt;In practice, mature open-source projects rarely bet purely on one path. The common pattern is &lt;strong&gt;layered fallback&lt;/strong&gt;: try Path 2 first (DOM/accessibility tree — fast and precise), and fall back to Path 1 or Path 3 (visual grounding) whenever reliable structured data isn't available. AppAgent, covered in a later post (08), is a textbook example of this "parser tree + visual features, dual input" design — the two paths aren't a mutually exclusive technology choice, but two tiers of fallback ranked by reliability within a single system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concept vocabulary (reused across the series)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Referenced in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GUI Grounding&lt;/td&gt;
&lt;td&gt;Mapping a natural-language reference to on-screen coordinates/elements&lt;/td&gt;
&lt;td&gt;04-09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual grounding model&lt;/td&gt;
&lt;td&gt;A model specifically trained for GUI grounding (UGround, GUI-Owl)&lt;/td&gt;
&lt;td&gt;05, 07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility tree / DOM semantic understanding&lt;/td&gt;
&lt;td&gt;Locating elements via structured views instead of pixels&lt;/td&gt;
&lt;td&gt;04, 08, 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Computer Use&lt;/td&gt;
&lt;td&gt;A general multimodal model directly outputting coordinate actions, no separate grounding stage&lt;/td&gt;
&lt;td&gt;09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScreenSpot / ScreenSpot-Pro&lt;/td&gt;
&lt;td&gt;GUI grounding benchmarks that measure single-step locating accuracy only&lt;/td&gt;
&lt;td&gt;05-09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSWorld / AndroidWorld&lt;/td&gt;
&lt;td&gt;End-to-end benchmarks measuring full multi-step task success rate&lt;/td&gt;
&lt;td&gt;05-09&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;There are three technical paths for GUI element locating: &lt;strong&gt;dedicated visual grounding models&lt;/strong&gt; (broadly cross-platform, but slow and expensive), &lt;strong&gt;DOM/accessibility-tree structured understanding&lt;/strong&gt; (fast and precise, but dependent on the platform exposing reliable structured data), and &lt;strong&gt;Computer Use raw coordinate clicking&lt;/strong&gt; (simplest architecture, highest generality ceiling, but the lowest locating-precision ceiling)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ScreenSpot → ScreenSpot-Pro cliff&lt;/strong&gt; (90%+ dropping to ~37%) shows that locating accuracy collapses sharply with interface density — it isn't a fixed model capability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OSWorld's human-vs-model gap&lt;/strong&gt; (72.36% vs. an early baseline of 12.24%) shows that end-to-end task success depends on more than grounding precision alone — planning and error-recovery mechanisms matter just as much&lt;/li&gt;
&lt;li&gt;Mature systems typically use &lt;strong&gt;layered fallback&lt;/strong&gt; rather than betting on a single path — this vocabulary will recur throughout every case study that follows&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>autotest</category>
      <category>guiagent</category>
      <category>ai</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 189): NanoJev — A 0.6B Parallel Decision Model That Outputs Probability Distributions Instead of Generating Tokens</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Tue, 22 Sep 2026 01:19:41 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-189-nanojev-a-06b-parallel-decision-model-that-outputs-22ao</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-189-nanojev-a-06b-parallel-decision-model-that-outputs-22ao</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"A decision doesn't need to write a sentence to explain itself — it just needs a probability distribution."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;189th&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;NanoJev&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most LLM-driven decision-making approaches can't escape an awkward efficiency problem: the model autoregressively generates a chunk of text (something like "I think we should choose option B, because..."), and the final decision has to be parsed out of that text afterward. This requires token-by-token decoding — slow, costly, and the "confidence" the model expresses is usually just an adjective it happened to use, not a genuinely calibrated probability.&lt;/p&gt;

&lt;p&gt;If a task's essence is really "given a state and a set of candidates, tell me the probability of each candidate," why make the model generate a wall of text first and parse it after? NanoJev's answer: skip the generation, output the distribution directly. It's a lightweight recreation of a parallel decision model system called "Jev." Built on a Qwen3-0.6B backbone with dedicated decision heads, it takes a state and a question as input and, in a single forward pass, outputs a complete probability distribution — zero output-token decoding.&lt;/p&gt;

&lt;p&gt;954 Stars, MIT License, validated with maze navigation and Snake game benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How NanoJev structures a decision as a "state + question + candidate set" triple&lt;/li&gt;
&lt;li&gt;Three decision head types: dynamic Choice, Boolean judgment, and Ordered Score&lt;/li&gt;
&lt;li&gt;Why "zero output-token decoding" fits decision-making better than autoregressive generation&lt;/li&gt;
&lt;li&gt;The full training pipeline: build queries → organize data → train → evaluate → serve&lt;/li&gt;
&lt;li&gt;How NanoJev compares against original Jev and an untuned Qwen3-0.6B on game benchmarks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of how LLMs run inference (autoregressive generation vs. a single forward pass)&lt;/li&gt;
&lt;li&gt;Familiarity with basic probability and classification/regression concepts&lt;/li&gt;
&lt;li&gt;Optional: familiarity with reward modeling concepts in reinforcement learning&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;NanoJev's official positioning: "A 0.6B parallel decision model. States and questions in, complete probability distributions out — with zero output-token decoding."&lt;/p&gt;

&lt;p&gt;Worth clarifying up front: this is not an image generation or editing tool. It's a &lt;strong&gt;structured decision model/reasoning framework&lt;/strong&gt; — essentially it keeps an LLM's language understanding ability while re-engineering the output side from "generate text" to "output a calibrated probability distribution," aimed at use cases like game agent decision-making and probability calibration research.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team and Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: TianyuCodings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base model&lt;/strong&gt;: Qwen3-0.6B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related resources&lt;/strong&gt;: model repo &lt;a href="https://huggingface.co/C-Tianyu/NanoJev" rel="noopener noreferrer"&gt;C-Tianyu/NanoJev&lt;/a&gt;, dataset &lt;a href="https://huggingface.co/datasets/C-Tianyu/NanoJev-Data" rel="noopener noreferrer"&gt;C-Tianyu/NanoJev-Data&lt;/a&gt; (both on HuggingFace)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;954&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;127&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👀 Watchers: &lt;strong&gt;8&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: MIT&lt;/li&gt;
&lt;li&gt;📊 Commits: 13&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional LLM decision-making approach:
  State + question → autoregressively generate text ("I think we should
  pick B, because...")
  ↓ parse the text to extract the final decision
  ↑ token-by-token decoding is slow and costly
  ↑ "confidence" is just an adjective in the text, never truly calibrated

NanoJev's approach:
  State + question + candidate set → a single forward pass →
  directly output a complete probability distribution
  ↑ zero output-token decoding, fast
  ↑ the distribution is trained and calibrated, usable directly for
    ranking / greedy selection / probability sampling
  ↑ a single forward pass can batch multiple independent states and questions
     (official example: "6 states · 18 questions · 44 candidate paths ·
     1 backbone forward")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Game agent decision-making&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maze navigation (8×8 up to 50×50 in various sizes), real-time decisions for a Snake game agent&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local safety judgment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast boolean judgments for scenarios like collision avoidance, combined with code-based planning&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Probability calibration research&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calibration experiments using CE (cross-entropy)/Brier loss and paired proper-reward learning&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scenarios needing batched decisions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate multiple candidate paths in a single forward pass, suited to applications needing high-throughput decision-making&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Simplest path: run the interactive demo locally (no model weights needed)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/TianyuCodings/NanoJev.git
&lt;span class="nb"&gt;cd &lt;/span&gt;NanoJev
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8080 &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--directory&lt;/span&gt; web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://127.0.0.1:8080/side-by-side.html&lt;/code&gt; for a three-panel comparison demo, or &lt;code&gt;arcade.html&lt;/code&gt; (dark arena demo) and &lt;code&gt;comparison.html&lt;/code&gt; (early benchmark viewer).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full run: download model weights and start the service&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-toy.txt

&lt;span class="c"&gt;# Download model checkpoint and dataset&lt;/span&gt;
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
from huggingface_hub import snapshot_download
snapshot_download(
    repo_id='C-Tianyu/NanoJev', local_dir='checkpoints/NanoJev',
    allow_patterns=['best.safetensors', 'config.json', 'tokenizer/*', 'backbone_config/*'],
)
snapshot_download(
    repo_id='C-Tianyu/NanoJev-Data', repo_type='dataset', local_dir='data/NanoJev',
)
"&lt;/span&gt;

&lt;span class="c"&gt;# Start the persistent service&lt;/span&gt;
python scripts/serve_decisions.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checkpoint-dir&lt;/span&gt; checkpoints/NanoJev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--web-root&lt;/span&gt; web &lt;span class="nt"&gt;--port&lt;/span&gt; 8765
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once started, the service loads the model once and accepts repeated batches of decision requests via &lt;code&gt;POST /api/evaluate&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Three Decision Head Types&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dynamic Choice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2–255 candidates&lt;/td&gt;
&lt;td&gt;A shared scalar head + set attention, outputs a probability for each candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Boolean&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single proposition&lt;/td&gt;
&lt;td&gt;Single-path sigmoid, outputs the probability the proposition is true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordered Score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2–10 levels&lt;/td&gt;
&lt;td&gt;Computes a probability-weighted expectation over the ordered level distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Triple-Structured Decisions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every decision is defined by a &lt;strong&gt;state&lt;/strong&gt;, a &lt;strong&gt;question&lt;/strong&gt;, and a &lt;strong&gt;candidate set&lt;/strong&gt;. A shared decision head processes the input and returns the corresponding distribution output for the candidate set based on the decision type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Five-Step Training Pipeline&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build queries
  ↓ generate states, questions, candidate descriptions, and target distributions
Organize data
  ↓ keep related maps, rules, and variants in the same data split
Train
  ↓ Qwen3-0.6B init + decision-head warmup + complete-question distribution losses
Evaluate
  ↓ measure probability quality + game controller records actual actions
Serve and visualize
  ↓ reuse a persistent model endpoint, replay full trajectories in the browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Multiple Pretrained Checkpoint Variants&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant Name&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variants/local_atomic_seed17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;50×50 maze demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variants/games_gold_seed17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Snake demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variants/games_api_seed17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full map comparison benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;variants/events_ce_seed17&lt;/code&gt; / &lt;code&gt;events_brier_seed17&lt;/code&gt; / &lt;code&gt;events_paired_seed17&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Calibrated decision experiments (different loss functions)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Performance Implications of "Zero Output-Token Decoding"
&lt;/h3&gt;

&lt;p&gt;NanoJev's core architectural decision is to abandon autoregressive generation entirely, re-engineering the LLM's output side into structured decision heads. The official documentation gives a fairly intuitive number for the performance difference this brings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"6 states · 18 questions · 44 candidate paths · 1 backbone forward"

Meaning:
  6 independent states + 18 questions + 44 candidate paths
  ↓ all folded into a single backbone forward pass
  ↓ instead of 44 separate autoregressive generations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost of autoregressive generation accumulates token by token — generating a chunk of explanatory text might require tens to hundreds of forward passes (one per token). NanoJev compresses the evaluation of 44 candidate paths into "1 backbone forward pass" — essentially swapping "expressing confidence in text" for "expressing probability directly through the model's internal numerical output," bypassing the entire autoregressive decoding cost chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Design Trade-offs Behind Choice / Boolean / Score Heads
&lt;/h3&gt;

&lt;p&gt;Why not use one generic head for every decision type? The three head types actually correspond to three fundamentally different statistical problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choice (dynamic selection):
  Variable candidate count (2-255) → needs "set attention"
  ↑ the model must understand "these candidates form a mutually
    exclusive set," not independent judgments

Boolean:
  A single proposition, binary yes/no → a single-path sigmoid is enough
  ↑ no need to be aware of other candidates

Score (ordered evaluation):
  Levels have an order relationship (1 star to 5 stars) → can't be
  treated as a plain classification problem
  ↑ using a probability-weighted expectation lets a non-integer
    expectation like "3.5 stars" emerge naturally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design reflects a simple but easily overlooked principle: &lt;strong&gt;when decisions have different statistical structures, the loss function and output head should differ too&lt;/strong&gt;. Forcing "which option," "true or false," and "what rating" into the same softmax loses the structural information specific to each question type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Training Results: Small Model vs. Fine-Tuning
&lt;/h3&gt;

&lt;p&gt;NanoJev was benchmarked against original Jev and an untuned Qwen3-0.6B across three game benchmarks, and the results are fairly compelling:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;NanoJev&lt;/th&gt;
&lt;th&gt;Jev (original)&lt;/th&gt;
&lt;th&gt;Untuned Qwen3-0.6B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50×50 maze (attempts/collisions)&lt;/td&gt;
&lt;td&gt;244 attempts / 36 collisions, goal reached&lt;/td&gt;
&lt;td&gt;2,738 attempts / 1,044 collisions, goal reached&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12×12 Snake (food eaten/alive)&lt;/td&gt;
&lt;td&gt;27 food, alive at horizon&lt;/td&gt;
&lt;td&gt;30 food, alive at horizon&lt;/td&gt;
&lt;td&gt;25 food, trapped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40-map navigation benchmark (test/OOD)&lt;/td&gt;
&lt;td&gt;95% / 90%&lt;/td&gt;
&lt;td&gt;100% / 95%&lt;/td&gt;
&lt;td&gt;35% / 15%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few points worth noting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The untuned Qwen3-0.6B lags significantly behind&lt;/strong&gt; (35%/15% vs. NanoJev's 95%/90%), showing that the purpose-trained decision heads deliver a real improvement — not just a rebranded model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NanoJev requires far fewer attempts than original Jev on the 50×50 maze&lt;/strong&gt; (244 vs. 2,738), suggesting the lightweight recreation is actually more efficient on this specific task, likely due to more focused training data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NanoJev's overall accuracy is slightly lower than original Jev&lt;/strong&gt; (e.g., 95% vs. 100% on the navigation benchmark), which is expected — recreating a system with fewer resources involves a reasonable trade-off in performance&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Positioning: A Research Recreation, Not a Commercial Product
&lt;/h3&gt;

&lt;p&gt;NanoJev explicitly positions itself as a "lightweight, reproducible" research alternative. That positioning is clear: it doesn't aim to outperform original Jev, but rather to let more researchers validate and extend the "parallel decision model" idea at a smaller training cost with a simpler deployment path. Roadmap items like expanding data scale and RLCD (contrastive distillation reinforcement learning) expansion all point toward "continuing to explore the boundaries of this architecture" rather than "polishing it into a product."&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/TianyuCodings/NanoJev" rel="noopener noreferrer"&gt;https://github.com/TianyuCodings/NanoJev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤗 &lt;strong&gt;Model&lt;/strong&gt;: &lt;a href="https://huggingface.co/C-Tianyu/NanoJev" rel="noopener noreferrer"&gt;C-Tianyu/NanoJev&lt;/a&gt; (HuggingFace)&lt;/li&gt;
&lt;li&gt;🤗 &lt;strong&gt;Dataset&lt;/strong&gt;: &lt;a href="https://huggingface.co/datasets/C-Tianyu/NanoJev-Data" rel="noopener noreferrer"&gt;C-Tianyu/NanoJev-Data&lt;/a&gt; (HuggingFace)&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/TianyuCodings/NanoJev/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/QwenLM/Qwen3" rel="noopener noreferrer"&gt;Qwen3&lt;/a&gt; — The 0.6B backbone network NanoJev is built on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Brier_score" rel="noopener noreferrer"&gt;Brier Score&lt;/a&gt; — One of the calibration scoring methods used in NanoJev's training&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero output-token decoding&lt;/strong&gt;: abandons autoregressive generation, using decision heads to output probability distributions directly, sidestepping the performance overhead of token-by-token decoding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triple-structured decisions&lt;/strong&gt;: state + question + candidate set clearly bounds every decision query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three decision heads, each fit for purpose&lt;/strong&gt;: Choice/Boolean/Score correspond to distinct statistical problem structures rather than being forced into a single softmax&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real benchmark validation&lt;/strong&gt;: significant improvement over untuned Qwen3-0.6B on maze navigation and Snake game benchmarks, with a reasonable performance trade-off versus original Jev&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research-oriented positioning&lt;/strong&gt;: explicitly a lightweight recreation rather than a commercial product, serving further exploration of the "parallel decision model" direction&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Game AI / agent decision-making researchers&lt;/strong&gt;: need a lightweight, reproducible baseline for parallel decision models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Researchers focused on LLM efficiency&lt;/strong&gt;: interested in architectural changes that skip autoregressive generation in favor of directly outputting structured distributions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probability calibration researchers&lt;/strong&gt;: want to study the practical effect of CE/Brier loss and paired reward learning in decision-making scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teaching/reproduction-oriented learning contexts&lt;/strong&gt;: want to understand a "decision head + backbone" architecture without training a large-scale system from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;NanoJev raises a question worth sitting with: if a task's essence is producing a probability distribution, why detour through generating a paragraph of text at all?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>nanojev</category>
      <category>llm</category>
      <category>jev</category>
    </item>
    <item>
      <title>LLM-Driven Automated Testing Series (01): Why Testing Is LLM's New Battlefield</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:06:25 +0000</pubDate>
      <link>https://dev.to/wonderlab/llm-driven-automated-testing-series-01-why-testing-is-llms-new-battlefield-509h</link>
      <guid>https://dev.to/wonderlab/llm-driven-automated-testing-series-01-why-testing-is-llms-new-battlefield-509h</guid>
      <description>&lt;h2&gt;
  
  
  A scenario every test engineer has lived through
&lt;/h2&gt;

&lt;p&gt;A product manager moves a button by a few pixels — barely noticeable to the eye. The next day, 40 tests fail red in CI simultaneously.&lt;/p&gt;

&lt;p&gt;After half an hour of digging, the culprit isn't broken business logic. It's a hardcoded CSS selector, &lt;code&gt;#submit-btn-v2&lt;/code&gt;, that can no longer find the element — the DOM structure changed and the id changed with it. The business code is entirely correct; the test script died first.&lt;/p&gt;

&lt;p&gt;This is one of the most common, most labor-draining, and least technically interesting failure modes in automated testing. It's also the starting point of this series: &lt;strong&gt;can LLMs turn this problem — and a few other long-standing ones in the testing world — from "brute-force human maintenance" into something that's actually engineered?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Three long-standing problems in traditional automated testing
&lt;/h2&gt;

&lt;p&gt;Before discussing what LLMs can do, let's name the problems clearly. None of these are new — the industry has debated them for over a decade without a cheap, general solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Fragile Locators
&lt;/h3&gt;

&lt;p&gt;Automated test scripts have to "find" an element on the page before they can act on it — click a button, fill a form, read some text. The common ways to locate an element are CSS selectors, XPath, element IDs, or raw coordinates.&lt;/p&gt;

&lt;p&gt;All of them share the same fatal assumption: &lt;strong&gt;the UI structure won't change after the test script is written.&lt;/strong&gt; In reality, UI structure changes constantly — redesigns, A/B tests, a front-end framework rewrite that restructures the component tree. The more specific a script's dependency on UI structure, the more fragile it becomes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Selector specificity     Robustness     Maintenance cost
────────────────────────────────────────────────────────
#submit-btn-v2           Very low       Breaks on any UI tweak
.btn.primary              Low            Breaks on class refactor
//div[3]/button[1]        Very low       Breaks on DOM hierarchy change
Coordinates (320, 480)    Very low       Breaks on resolution/layout change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The industry has a name for this: &lt;strong&gt;"selector hell."&lt;/strong&gt; The larger a test team grows, the higher the proportion of engineering time spent maintaining locators — not writing new tests, but fixing the locating logic of old ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: The Oracle Problem (how do you know if the result is right?)
&lt;/h3&gt;

&lt;p&gt;"Oracle" in testing refers to the standard used to judge whether an output is correct. A traditional unit test's oracle is explicit: &lt;code&gt;assert result == 42&lt;/code&gt;. But in many testing scenarios, the oracle itself is fuzzy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A screenshot differs from the last version by a few pixels — is that a bug,
  or just font-rendering jitter?
The returned JSON has an extra field — is that an API change,
  or dirty test-environment data?
The user flow completed, but the page "just doesn't look right" —
  hard to say exactly what's wrong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The traditional fix is to make the oracle explicit: pixel-level diffing (what threshold counts as a real change), schema validation (which fields must exist), assertion chains (explicitly checking state at each step). The shared problem with these approaches: &lt;strong&gt;the stricter the oracle, the more false positives (flagging meaningless changes as failures); the looser the oracle, the more false negatives (real bugs slip through).&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Maintenance Cost (test scripts age faster than the business code)
&lt;/h3&gt;

&lt;p&gt;This is a consequence of the first two problems, but it deserves its own callout because it's the direct reason teams eventually abandon automated testing.&lt;/p&gt;

&lt;p&gt;A real pattern: teams that iterate faster on features tend to end up with lower actual test coverage over time — not because nobody wants to write tests, but because tests go stale the moment they're written, nobody has time to fix them, and eventually they get &lt;code&gt;skip&lt;/code&gt;ped or ignored outright. The entropy of the test codebase grows faster than the team's capacity to maintain it.&lt;/p&gt;

&lt;p&gt;These three problems reinforce each other: fragile locators → tests fail red constantly → maintenance cost rises → the team has no time to maintain them → coverage gets cut → the oracle problem becomes harder to catch (because coverage itself is shrinking).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why now: testing happens to be a "clear right-or-wrong" scenario
&lt;/h2&gt;

&lt;p&gt;For the past two years, LLM Agent adoption has stalled in the same place across many domains: &lt;strong&gt;how do you know the Agent got it right?&lt;/strong&gt; Writing code, making decisions, generating content — the "correctness" of these outputs is usually continuous and subjective, requiring a whole separate evaluation layer to judge (which is exactly what the &lt;code&gt;eval-series&lt;/code&gt; on this blog is about).&lt;/p&gt;

&lt;p&gt;Testing is the exception. A testing task carries its own explicit success signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit test passes / fails             —— boolean, no middle ground
UI element found / not found         —— boolean
Assertion holds / doesn't hold       —— boolean
Coverage increased by X percentage points —— a quantifiable number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the quality of an LLM's output in a testing context can be verified using the test's own execution result, without needing an extra meta-layer of "is this LLM output reasonable." This is a structural advantage testing has over other LLM use cases — and it's why the past two years have produced a wave of open-source projects that actually work in production, not just proof-of-concept demos.&lt;/p&gt;

&lt;p&gt;That said, "clear right-or-wrong" only applies to the test's final execution result. Whether a test script should be generated, whether a locator should self-heal, whether a failure is a real bug or an environment flake — these judgments remain fuzzy. That's exactly where LLMs have room to intervene, not in the final "did the test pass" verdict itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three places where LLMs can plug in
&lt;/h2&gt;

&lt;p&gt;Coming back to the three problems from the opening: here's where LLMs can plug in for each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fragile locators   → Semantic locating ("find the submit button" instead of "find #submit-btn-v2")
                    → Self-healing locators (when the UI changes, re-find the element
                      semantically instead of the script just dying)

Oracle problem      → Semantic judgment ("is this visual change an actual bug to the user")
                    → instead of pure pixel/structural comparison

Maintenance cost    → Coverage-driven automatic test generation
                    → Automatic failure classification (real bug / environment issue / test issue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three threads map to what the rest of the series will cover: unit test generation (03), Web/mobile UI automation (04-09), self-healing locators (10), API testing (11), visual regression (12), and failure classification (13).&lt;/p&gt;

&lt;p&gt;The next post (02) will first lay out the shared technical foundation behind all of these approaches — visual grounding, DOM-based semantic understanding, and pure coordinate-clicking Computer Use. Each has its own principles and trade-offs, and this vocabulary will be reused in every case study that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this series won't do
&lt;/h2&gt;

&lt;p&gt;Setting expectations upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is not a trend-report on "AI testing."&lt;/strong&gt; Each post picks one technical point paired with a verifiable open-source project — no "reportedly," no "may become possible in the future" claims that can't be checked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This isn't about evaluating LLM output quality.&lt;/strong&gt; That's the &lt;code&gt;eval-series&lt;/code&gt;'s territory. This series is about "using LLMs to do testing," not "evaluating LLM outputs"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limitations won't be glossed over.&lt;/strong&gt; Every technical approach comes with a discussion of where it breaks down and what it costs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fragile locators, the oracle problem, and maintenance cost&lt;/strong&gt; are the three long-standing problems in automated testing, and they reinforce each other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing tasks carry an explicit success signal&lt;/strong&gt; (pass/fail) — this is testing's structural advantage over other LLM use cases&lt;/li&gt;
&lt;li&gt;LLMs plug in at semantic locating/self-healing, semantic oracle judgment, and automatic generation/classification — not at replacing the final "did the test pass" verdict itself&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>autotest</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 188): ARTEMIS — Google's Open-Source Mobile AI Automation Framework, Let AI Assistants Operate Phones Like a Human</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Mon, 21 Sep 2026 02:01:12 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-188-artemis-googles-open-source-mobile-ai-automation-3mkg</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-188-artemis-googles-open-source-mobile-ai-automation-3mkg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"A test script breaks the moment a button moves — an AI that actually understands the interface doesn't."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;188th&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;ARTEMIS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mobile UI automation testing has long lived with a fundamental tension: traditional scripts based on element IDs or coordinates are fast, but extremely brittle — a small layout tweak breaks test cases en masse, and maintenance cost grows linearly with how fast the app iterates. Vision-model-driven approaches are more robust, but slow and expensive, making them a poor fit for large-scale regression testing.&lt;/p&gt;

&lt;p&gt;ARTEMIS is Google's attempt to find a balance between these two extremes. It turns natural-language instructions — like "open Settings, find the Battery option, tell me the current level" — into reliable Android automation, while offering two execution modes: one optimized for speed on routine deterministic tasks, another optimized for accuracy and verifiability on complex, long-running ones. More notably, it plugs directly into Claude Code, Antigravity, Codex, and other AI IDEs via MCP, giving AI coding assistants the ability to operate real phones.&lt;/p&gt;

&lt;p&gt;8.1k Stars, Apache-2.0, built in Python, achieving 99%+ task completion on the AndroidWorld benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How ARTEMIS turns natural-language instructions into Android automation&lt;/li&gt;
&lt;li&gt;The architectural differences between the Flash and Pro execution modes, and when to use each&lt;/li&gt;
&lt;li&gt;Multi-modal target localization: combining the accessibility hierarchy, OCR, and vision models&lt;/li&gt;
&lt;li&gt;How it integrates with Claude Code and other AI IDEs via MCP&lt;/li&gt;
&lt;li&gt;How the Python SDK plugs into existing automated testing frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of Android automation testing concepts (ADB, Accessibility services)&lt;/li&gt;
&lt;li&gt;Familiarity with Python async programming (asyncio)&lt;/li&gt;
&lt;li&gt;Optional: basic understanding of MCP (Model Context Protocol)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;ARTEMIS's official positioning: "ARTEMIS turns natural-language instructions into reliable Android automation" — letting AI assistants and test suites operate real phones like a human would.&lt;/p&gt;

&lt;p&gt;One clarification worth making up front: this is not a security testing or fuzzing tool. It's an intelligent agent system focused on &lt;strong&gt;mobile UI automation testing and everyday task execution&lt;/strong&gt;, squarely in the AI-driven mobile testing/automation framework space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team and Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt;: Google&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache License 2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary language&lt;/strong&gt;: Python (requires Python 3.12+)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code provenance&lt;/strong&gt;: the project notes it includes source code developed by Minitap, Inc., building on the open-source mobile-use project&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;8,100+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;770+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👀 Watchers: &lt;strong&gt;71&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: Apache-2.0&lt;/li&gt;
&lt;li&gt;🏆 Benchmark: 99%+ task completion on AndroidWorld (covering 20+ apps, 100+ multi-step tasks)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional element-ID/coordinate-based automation scripts:
  UI layout shifts slightly → element lookup fails → tests break en masse
  ↑ Fast, but extremely brittle, high maintenance cost

Pure vision-model-driven approaches:
  Every step relies on a vision model to read the screen → accurate but slow
  ↑ Good robustness, but slow and expensive, hard to scale

ARTEMIS's approach:
  Natural-language instruction → multi-modal localization (element index first,
  vision/coordinates as fallback)
  ↓ Flash mode: routine deterministic tasks, 3-5 sec/step, fast reactive loop
  ↓ Pro mode: complex long-running tasks, 15-40 sec/step, plan-execute-verify workflow
  ↑ Choose the right mode for task complexity, balancing speed and reliability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobile app end-to-end test automation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Describe test steps in natural language instead of maintaining brittle element-locator scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bug reproduction and diagnosis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combined with Logcat log and screenshot capture, quickly reproduce and pinpoint issues&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exploratory stability testing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pro mode supports long-running, continuously monitored exploratory testing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CI/CD-integrated automated testing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate via the Python SDK into pytest and similar frameworks, folded into continuous integration pipelines&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI IDE-driven real device operation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let Claude Code, Antigravity, and other AI coding assistants directly operate a phone to verify functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Clone and one-command start:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/google/artemis.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;artemis

&lt;span class="c"&gt;# macOS/Linux one-command start (auto-detects and installs ADB, scrcpy, FFmpeg, etc.)&lt;/span&gt;
./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run a task directly via CLI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run artemis run &lt;span class="s2"&gt;"Open Settings, find Battery and tell me current level"&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt; flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install MCP integration for an IDE:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install for Antigravity&lt;/span&gt;
uv run artemis mcp &lt;span class="nt"&gt;--install&lt;/span&gt; antigravity

&lt;span class="c"&gt;# Install for all supported IDEs (including Codex)&lt;/span&gt;
uv run artemis mcp &lt;span class="nt"&gt;--install&lt;/span&gt; all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first task execution, ARTEMIS installs an "Artemis Accessibility Helper" service on the device — it only reads the screen layout locally and uploads no data, and can be pre-installed, checked, or uninstalled via command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Four Ways to Use It&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web visual test console (&lt;code&gt;artemis ui&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Interactive debugging and results review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP server&lt;/td&gt;
&lt;td&gt;Connecting an AI IDE to drive a real device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer CLI (&lt;code&gt;artemis run&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Automated testing or benchmark scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python SDK&lt;/td&gt;
&lt;td&gt;Integrating into existing test frameworks like pytest / CI pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Dual Execution Modes: Flash and Pro&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Flash Mode&lt;/th&gt;
&lt;th&gt;Pro Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Response speed&lt;/td&gt;
&lt;td&gt;~3-5 sec/step&lt;/td&gt;
&lt;td&gt;~15-40 sec/step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Single-model observe-think-act reactive loop&lt;/td&gt;
&lt;td&gt;Multi-agent graph (Planner+Operator+Checker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task planning&lt;/td&gt;
&lt;td&gt;No task plan&lt;/td&gt;
&lt;td&gt;Planner maintains a Markdown task plan with milestones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety mechanism&lt;/td&gt;
&lt;td&gt;No pre-execution safety net&lt;/td&gt;
&lt;td&gt;Every action XML-verified first, pixel fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;No checkpoint verification or final report&lt;/td&gt;
&lt;td&gt;Checker verifies plan checkpoints, supports a final review before exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toolset&lt;/td&gt;
&lt;td&gt;No ADB shell&lt;/td&gt;
&lt;td&gt;Full toolset: Explorer, notes, history lookback, video analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Routine deterministic UI tasks&lt;/td&gt;
&lt;td&gt;100+ step long-running workflows, continuous monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Python SDK Integration Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add &lt;span class="s2"&gt;"artemis-client @ git+https://github.com/google/artemis.git#subdirectory=packages/artemis-client"&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;artemis_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArtemisClient&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;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ArtemisClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://artemis-host:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device_serial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emulator-5554&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# optional: target specific device serial
&lt;/span&gt;        &lt;span class="n"&gt;default_profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# "flash" (fast reactive) or "pro" (deep reasoning)
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open System Settings, go to &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Battery&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, verify battery percentage is displayed, and check for any crash dialogs.&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;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;succeeded&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;Test failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;✅ Test Passed! Device: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_serial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | Trace ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_id&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;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK claims "zero runtime dependencies" — ADB, the agent, models, and image processing all stay on the device host. It supports strongly typed Pydantic structured outputs and assertions, and plugs directly into frameworks like pytest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. MCP Integration Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using Claude Desktop as an example (&lt;code&gt;claude_desktop_config.json&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"artemis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/artemis/.venv/bin/python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp_server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/artemis"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs also recommend mounting a behavior rules file (&lt;code&gt;mcp_server/rules.md&lt;/code&gt;) for AI agents, covering proactive exploration before coding, Flash/Pro routing strategy, and latency compensation logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Flash vs. Pro: A Pragmatic Speed/Reliability Trade-off
&lt;/h3&gt;

&lt;p&gt;ARTEMIS doesn't use a single mode for every task — it explicitly splits into two architecturally distinct execution paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flash mode (reactive):
  Observe screen → think about next step → execute action → loop
  ↑ Single-model loop, no planning, no safety net, no final report
  ↑ Loop count is unbounded by default (history is compressed, not truncated)
  ↑ Good for short, deterministic tasks like "open Settings and flip a switch"

Pro mode (plan-and-verify):
  Planner drafts a Markdown task plan (with milestones and verification items)
      ↓
  Operator runs a "Safety Net" check before each action
      ↓ (checks against the live UI tree first, then pixel fallback)
  Hits a blocker → opens an "execution incident," Operator handles recovery autonomously
      ↓
  Checker (read-only) verifies plan checkpoints, supports a final review before exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core insight behind this design: &lt;strong&gt;mobile automation task complexity is bimodal&lt;/strong&gt;. A large share of tasks are deterministic short tasks — "open a page and confirm a state" — where a simple reactive loop is fast and sufficient. A minority are complex workflows that need multi-step planning, fault tolerance, and long-running monitoring, where only a multi-agent plan-execute-verify structure can guarantee reliability. Using one architecture for both extremes wastes something either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fault-Tolerant Design in Multi-Modal Target Localization
&lt;/h3&gt;

&lt;p&gt;ARTEMIS's element localization mechanism reflects a "prefer precision, degrade gracefully" fault-tolerance philosophy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Localization priority:
  1. Accessibility Hierarchy — most precise, the go-to for standard Android UI
  2. OCR text recognition — handles cases where the hierarchy tree lacks information
  3. Vision model recognition — fallback, handles custom Canvas/Compose/Flutter UIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard Android widgets are usually exposed accurately by accessibility services, making that the fastest and most reliable localization method. But more and more apps render their UI with Compose, Flutter, or custom Canvas drawing, which is unfriendly to accessibility services — that's when OCR and vision models step in. This layered fallback strategy avoids the waste of "use the most expensive method for everything" while also avoiding the fragility of "just fail outright when standard widgets aren't recognized."&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared History Compression: Memory Management for Long-Running Tasks
&lt;/h3&gt;

&lt;p&gt;Flash and Pro share the same history compression mechanism, and this detail is worth noting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem: long-running tasks (especially Pro's 100+ step workflows) accumulate
a large volume of screenshots and action logs
  ↑ Stuffing them directly into context blows past the window limit
  ↑ Simple truncation loses important early information

ARTEMIS's approach:
  Old screenshots → replaced with visual summaries (keep semantics, drop pixel detail)
  Completed steps → compressed into retrievable history chunks
  ↑ The agent can retrieve past context when it needs to look back,
    instead of always carrying the full history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design lets Flash mode run stably long-term even with no loop cap, and is also the memory-management foundation that makes Pro's 100+ step workflows possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Compares to Similar Mobile Automation Approaches
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Appium (traditional scripts)&lt;/th&gt;
&lt;th&gt;Pure vision-model-driven&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ARTEMIS&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Localization method&lt;/td&gt;
&lt;td&gt;Fixed element ID/XPath&lt;/td&gt;
&lt;td&gt;Pure visual recognition&lt;/td&gt;
&lt;td&gt;Element index first + OCR + vision fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI-change tolerance&lt;/td&gt;
&lt;td&gt;❌ Brittle&lt;/td&gt;
&lt;td&gt;✅ Fairly robust&lt;/td&gt;
&lt;td&gt;✅ Fairly robust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution speed&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Flash is fast / Pro is moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural-language driven&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Partial support&lt;/td&gt;
&lt;td&gt;✅ Native support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI IDE integration (MCP)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Uncommon&lt;/td&gt;
&lt;td&gt;✅ Native support across multiple IDEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-running task plan/verify&lt;/td&gt;
&lt;td&gt;Must build yourself&lt;/td&gt;
&lt;td&gt;Uncommon&lt;/td&gt;
&lt;td&gt;✅ Native in Pro mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Varies by project&lt;/td&gt;
&lt;td&gt;✅ Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ARTEMIS's differentiation is combining "natural-language driven + multi-modal fault-tolerant localization + native AI IDE integration" into one tool, using its dual-mode design to avoid the dilemma of "either too slow with pure vision models, or too brittle with fixed scripts."&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/google/artemis" rel="noopener noreferrer"&gt;https://github.com/google/artemis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;License&lt;/strong&gt;: Apache License 2.0&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/google/artemis/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/google-research/android_world" rel="noopener noreferrer"&gt;AndroidWorld&lt;/a&gt; — Google Research's mobile agent benchmark project, and ARTEMIS's evaluation benchmark&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; — The standard protocol underlying ARTEMIS's integration with Claude Code and other AI IDEs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/minitap-ai/mobile-use" rel="noopener noreferrer"&gt;mobile-use&lt;/a&gt; — The open-source project (from Minitap, Inc.) that ARTEMIS is built on&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Natural-language-driven mobile automation&lt;/strong&gt;: replaces brittle element-locator scripts with instructions, lowering test maintenance cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual Flash/Pro mode design&lt;/strong&gt;: choose a fast reactive loop or a plan-execute-verify workflow based on task complexity, balancing speed and reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal fault-tolerant localization&lt;/strong&gt;: accessibility hierarchy first, OCR and vision models as graceful fallbacks, adapting from standard widgets to custom Canvas UIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native MCP integration&lt;/strong&gt;: gives Claude Code, Antigravity, Codex, and other AI IDEs the ability to directly operate real Android devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99%+ on AndroidWorld&lt;/strong&gt;: reliability validated on a standard mobile agent benchmark&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app testing teams&lt;/strong&gt;: want to escape brittle element-locator scripts and reduce maintenance cost from UI changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI application developers&lt;/strong&gt;: want AI coding assistants to be able to operate real Android devices for feature verification or bug reproduction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipeline maintainers&lt;/strong&gt;: need to seamlessly fold mobile automation testing into existing test frameworks and continuous integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exploratory/stability testing teams&lt;/strong&gt;: need long-running, continuously monitored mobile testing capability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;ARTEMIS doesn't pick a side between "fast but brittle" and "robust but slow" — it splits the tension apart directly with its Flash/Pro dual-mode design, which may be a pragmatic middle answer as mobile automation moves toward AI-driven approaches.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>google</category>
      <category>android</category>
      <category>autotest</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 187): TeamAI-CLI — Tencent's Team-Level AI Agent Middleware, Turning Individual AI Skills Into Shared Team Assets</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Sun, 20 Sep 2026 02:28:57 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-187-teamai-cli-tencents-team-level-ai-agent-middleware-bgk</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-187-teamai-cli-tencents-team-level-ai-agent-middleware-bgk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"An AI agent's capabilities shouldn't be trapped in one person's chat history — they should become a shared asset for the whole team."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;187th&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;TeamAI-CLI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When everyone on a team is using Claude Code, Cursor, or Codex to write code, a hidden problem starts building up: &lt;strong&gt;everyone is independently "training" their own AI&lt;/strong&gt;. One engineer spends half a day figuring out the right prompting approach to get Claude Code to correctly understand the project's architecture; another hits the exact same wall with no idea it's already been solved, and has to work through it from scratch. Skills, Rules, and CLAUDE.md configs scattered across the team never converge into anything shared or synchronized.&lt;/p&gt;

&lt;p&gt;That's exactly the problem TeamAI-CLI addresses. It's not another AI coding assistant — the project's own documentation lists Claude Code, Codex, and Cursor as tools it's &lt;em&gt;compatible with&lt;/em&gt;, not competitors. It's a &lt;strong&gt;meta-management layer&lt;/strong&gt; that sits on top of the AI tools your team already uses, responsible for syncing skills, rules, and knowledge bases across members, and for turning individual experience gained while using AI into a shared team asset.&lt;/p&gt;

&lt;p&gt;4.8k Stars, MIT License, built in TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;TeamAI-CLI's three-layer product architecture: Team Execution / Team Context / Team Improvement&lt;/li&gt;
&lt;li&gt;The push → MR → review-and-merge → SessionStart-hook-sync distribution flow&lt;/li&gt;
&lt;li&gt;The friction-signal-driven mechanism for automatically capturing experience&lt;/li&gt;
&lt;li&gt;How the codebase knowledge graph is built (tree-sitter AST + regex heuristics)&lt;/li&gt;
&lt;li&gt;Its relationship to Claude Code, Codex, and similar tools: not a replacement, but a "team layer"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Some experience using AI coding tools like Claude Code, Cursor, or Codex&lt;/li&gt;
&lt;li&gt;Familiarity with Git workflows (branches, Merge Requests)&lt;/li&gt;
&lt;li&gt;Optional: basic understanding of MCP (Model Context Protocol)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;TeamAI's official positioning is "the shared foundation for how your team works, learns, and improves with AI." The core idea, in one sentence: &lt;strong&gt;turn individual AI capabilities into shared team capabilities — across agents, machines, and team members.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Team and Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt;: Tencent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary language&lt;/strong&gt;: TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution&lt;/strong&gt;: global npm install (&lt;code&gt;teamai-cli&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;4,800+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;340+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👀 Watchers: &lt;strong&gt;17&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: MIT&lt;/li&gt;
&lt;li&gt;🔧 Open Issues: 33, Open PRs: 14 (actively maintained)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A team without TeamAI:
  One engineer built a perfect set of project rules in Claude Code  ← lives only on their machine
  Another spent three days figuring out how to get the AI to understand the architecture  ← that lesson never spreads
  Someone's CLAUDE.md and team conventions went stale long ago  ← nobody keeps them in sync
  ↑ Everyone's AI capability stays siloed — the team's collective ability never compounds

TeamAI's approach:
  A unified team resource repository (Skills/Rules/Docs/Agents/Hooks/MCP)
  ↓ teamai push (share) → MR review → merge
  ↓ SessionStart hook auto-triggers teamai pull (sync)
  Every team member's local AI tools automatically get the latest team standards
  ↑ Individual experience → team asset — AI capability can be version-controlled,
    reviewed, and distributed just like code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A tech lead standardizing AI tool conventions across the team&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write project-specific coding standards and architecture constraints as Skills/Rules once, sync them to everyone&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New members onboarding quickly into an existing AI collaboration setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After joining the team repo, local AI tools automatically pull the latest skills and context — no starting from scratch&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Team members sharing personal experience&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;teamai contribute&lt;/code&gt; to share a hard-won debugging insight with the whole team in one command&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Codebase knowledge accumulation for AI retrieval&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The knowledge graph feature lets AI automatically retrieve relevant code context and historical decisions before tackling a coding task&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visualizing team-wide AI usage&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;dashboard&lt;/code&gt; and &lt;code&gt;digest&lt;/code&gt; commands to understand team-wide AI usage trends and activity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Conversational install (let the AI set it up)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Paste this directly to Claude Code or any agent that supports Skills:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install the teamai skill: https://github.com/Tencent/teamai-cli/tree/main/skills/teamai ,
load the teamai skill, then set up TeamAI for my team from scratch.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Manual CLI install&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Admin: create the team's shared repository&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; teamai-cli
teamai init https://github.com/yourorg/yourrepo

&lt;span class="c"&gt;# Team member: join an existing team&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/my-project
teamai init https://github.com/yourorg/yourrepo

&lt;span class="c"&gt;# Or install at the user scope (applies across all projects)&lt;/span&gt;
teamai init https://github.com/yourorg/yourrepo &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. A Three-Layer Product Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Positioning&lt;/th&gt;
&lt;th&gt;Core Capabilities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team Execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Make every agent work the team's way"&lt;/td&gt;
&lt;td&gt;init/pull/push sync for Skills/Rules/Agents/Hooks/MCP/Env&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Team Context&lt;/strong&gt; (beta)&lt;/td&gt;
&lt;td&gt;"Make every agent understand the team"&lt;/td&gt;
&lt;td&gt;Knowledge recall, learnings, codebase knowledge graph, team wiki&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Team Improvement&lt;/strong&gt; (beta)&lt;/td&gt;
&lt;td&gt;"Make every execution improve the team"&lt;/td&gt;
&lt;td&gt;Friction-signal-driven experience sharing, session logs, weekly digest, usage dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. A Git-Native Distribution Flow&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;teamai push → creates a branch + MR → reviewer approves and merges
                                              ↓
        SessionStart hook auto-triggers teamai pull → synced to local AI tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports GitHub, GitLab, GitCode, CNB, TGit, and private Git servers — essentially folding AI collaboration standards into the normal code review process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Key Commands&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initialize: OAuth login, link repo, register member, inject hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;teamai pull&lt;/code&gt; / &lt;code&gt;push&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pull/push team resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show the diff between local state and the team repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai contribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share session experience to the team repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai recall &amp;lt;query&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search the team knowledge base (BM25 + graph-augmented)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai codebase --extract/--deep-enrich/--reconcile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Codebase knowledge graph operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;teamai digest&lt;/code&gt; / &lt;code&gt;dashboard&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Team usage stats and visualization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;teamai doctor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Diagnose configuration issues&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. A Broad Agent Compatibility Matrix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TeamAI-CLI explicitly claims compatibility with over a dozen AI coding tools, including Claude Code, Codex, Cursor, GitHub Copilot CLI, CodeBuddy, OpenCode, and Kiro. The documentation includes a detailed compatibility table showing which capabilities (skills/rules/docs/env/hooks/mcp) each tool supports.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Friction-Signal-Driven Experience Capture
&lt;/h3&gt;

&lt;p&gt;The most interesting design in TeamAI-CLI's Team Improvement layer is its trigger logic: instead of requiring users to proactively log their experience, it &lt;strong&gt;monitors friction signals in a session&lt;/strong&gt; to detect "something worth recording just happened."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Examples of friction signals:
  "The user interrupted the AI twice"
  "The AI retried the same failing tool call 8 times in a row"
  ↓
  The system identifies unusual friction
  ↓
  It prompts the user: "This debugging session looks like it has something
  worth sharing — want to contribute it to the team?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is smarter than "asking employees to write weekly summaries of lessons learned" — most valuable debugging insights happen in the moment while solving a problem, and details fade fast in hindsight. Using interaction friction as the trigger captures experience while it's still fresh.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Codebase Knowledge Graph: A Dual-Track Extraction Strategy
&lt;/h3&gt;

&lt;p&gt;The codebase knowledge graph in the Team Context layer uses a dual-track strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AST Track (precise, but costly)
  Uses a tree-sitter WASM parser to parse code
  ↓ precisely extracts functions, classes, and dependency relationships
  ↓ suited to deep parsing of core modules

Heuristic Track (fast, but coarse-grained)
  Scans code with regex heuristics
  ↓ quickly covers a broad range of files
  ↓ suited to fast indexing of peripheral code

Both tracks' results → written to the teamwiki/ directory → power the recall
command's BM25-based retrieval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dual-track design reflects a practical trade-off: running tree-sitter-level precise AST parsing across an entire large codebase is expensive, while pure regex matching loses semantic relationships. Combining both — precise parsing for core modules, fast coverage for the periphery — is a pragmatic middle ground.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Relates to Claude Code and Codex
&lt;/h3&gt;

&lt;p&gt;This is the point most likely to cause confusion about TeamAI-CLI, so it's worth clarifying directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wrong framing: TeamAI-CLI competes with Claude Code, as another AI coding CLI
Correct framing: TeamAI-CLI sits on top of Claude Code/Codex/Cursor,
                  acting as a team-level distribution and knowledge management layer

Analogy:
  Claude Code / Codex / Cursor  ≈ the IDE on each person's machine
  TeamAI-CLI                    ≈ the team's Git repo + code review process
                                   (but for Skills/Rules/knowledge, not code)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works by injecting hooks (like a SessionStart hook) and shared config files (CLAUDE.md, Skills directories, etc.) into already-installed AI tools. It doesn't provide reasoning capability itself — it orchestrates "who should use which rules, and when they get synced."&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Compares to Similar Team Collaboration Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Manually Maintained CLAUDE.md&lt;/th&gt;
&lt;th&gt;Cursor Rules (local)&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TeamAI-CLI&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cross-agent compatibility&lt;/td&gt;
&lt;td&gt;Depends on each tool's own implementation&lt;/td&gt;
&lt;td&gt;Cursor only&lt;/td&gt;
&lt;td&gt;✅ A dozen+ tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team sync mechanism&lt;/td&gt;
&lt;td&gt;Manual copy-paste / word of mouth&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;td&gt;✅ Git push/pull + MR review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic experience capture&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Friction-signal-driven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codebase knowledge graph&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ AST + heuristic dual-track&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage visualization&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ dashboard/digest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version control and review&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Local files, no review&lt;/td&gt;
&lt;td&gt;✅ Standard Git flow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TeamAI-CLI's differentiation is treating "AI collaboration conventions" as a first-class citizen of standard software engineering process (version control, code review) — instead of leaving everyone to maintain their own local config files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Tencent/teamai-cli" rel="noopener noreferrer"&gt;https://github.com/Tencent/teamai-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;npm package&lt;/strong&gt;: &lt;code&gt;teamai-cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/Tencent/teamai-cli/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt; — The parser framework behind TeamAI-CLI's codebase knowledge graph AST Track&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; — The resource distribution standard involved in TeamAI-CLI's compatibility matrix&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; — One of the main AI coding agents TeamAI-CLI integrates with&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Not an AI coding assistant, but a team-level middleware layer&lt;/strong&gt;: sits on top of Claude Code/Codex/Cursor, focused on syncing standards rather than generating code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A three-layer architecture that builds progressively&lt;/strong&gt;: Team Execution handles distribution, Team Context handles understanding, Team Improvement handles evolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Git-native distribution flow&lt;/strong&gt;: push → MR review → merge → hook-triggered sync — AI collaboration conventions follow standard software engineering process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friction-signal-driven experience capture&lt;/strong&gt;: doesn't rely on manual summarization; interaction anomalies automatically trigger experience capture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatible with a dozen+ agents&lt;/strong&gt;: not locked to a single AI tool ecosystem, covering mainstream choices like Claude Code, Codex, and Cursor&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Engineering team leads&lt;/strong&gt;: want to standardize AI collaboration conventions across the team instead of letting everyone figure it out independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mid-to-large engineering teams&lt;/strong&gt;: members use different AI tools and need a neutral layer to keep standards in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams that value knowledge management&lt;/strong&gt;: want to systematically capture hard-won lessons and codebase knowledge instead of losing them to staff turnover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams already using Claude Code/Cursor and similar tools&lt;/strong&gt;: don't want to switch tools, just want the ones they already use to be smarter and more consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;TeamAI-CLI isn't trying to solve "how AI writes code" — it's trying to solve "how one person's AI experience becomes everyone's experience," which may be a more interesting direction than simply stacking on stronger models.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>tencent</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>pen Source Project #186: security-audit — Cloudflare's Skill That Turns Your Coding Agent into a Six-Phase Security Auditor</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Sat, 19 Sep 2026 02:55:33 +0000</pubDate>
      <link>https://dev.to/wonderlab/pen-source-project-186-security-audit-cloudflares-skill-that-turns-your-coding-agent-into-a-5ggn</link>
      <guid>https://dev.to/wonderlab/pen-source-project-186-security-audit-cloudflares-skill-that-turns-your-coding-agent-into-a-5ggn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"A coding-agent skill for multi-phase security audits with independently verified, machine-readable findings."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is &lt;strong&gt;article #186&lt;/strong&gt; in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;security-audit&lt;/strong&gt; — Cloudflare's coding-agent skill, 13,825 Stars, MIT license.&lt;/p&gt;

&lt;p&gt;security-audit addresses a critical question: &lt;strong&gt;how do you make AI-generated security audit results trustworthy enough to hand to a security team?&lt;/strong&gt; The answer isn't "a smarter model" — it's a structured process: six audit phases, adversarial validation, and machine-readable finding records. It turns "the model says there's a problem here" into "here's a confirmed vulnerability with source evidence, a reproducible path, and a priority ranking."&lt;/p&gt;

&lt;h3&gt;
  
  
  What You'll Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The six-phase audit workflow, from reconnaissance to target-neutral reporting&lt;/li&gt;
&lt;li&gt;The adversarial validation principle (the checker is never the finder)&lt;/li&gt;
&lt;li&gt;The difference between three verdicts: &lt;code&gt;confirmed&lt;/code&gt; / &lt;code&gt;needs_validation&lt;/code&gt; / &lt;code&gt;rejected&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How the coverage ledger makes multi-run audits additive&lt;/li&gt;
&lt;li&gt;The sandbox requirement: why "can't execute target code" means &lt;code&gt;needs_validation&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Experience with Claude Code or similar coding agents and the skill mechanism&lt;/li&gt;
&lt;li&gt;Basic understanding of security audit concepts (attack surface, trust boundaries, vulnerability confirmation)&lt;/li&gt;
&lt;li&gt;Node.js fundamentals&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;This skill is the single-repo starting point of Cloudflare's vulnerability discovery harness. Cloudflare's official blog &lt;a href="https://blog.cloudflare.com/build-your-own-vulnerability-harness" rel="noopener noreferrer"&gt;Build your own vulnerability harness&lt;/a&gt; describes how that system evolved into a multi-stage, fleet-wide vulnerability discovery platform — and security-audit is the single-repo version it evolved from.&lt;/p&gt;

&lt;p&gt;It's not a "generate a security report" prompt template — it's an &lt;strong&gt;orchestration system&lt;/strong&gt;: isolated sub-agents run reconnaissance, hunting, validation, and verification, and every step produces structured records with independent validators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Author / Team
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt;: Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary language&lt;/strong&gt;: JavaScript (zero-dependency validators, written for Node.js)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Created&lt;/strong&gt;: 2026-06-18&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;13,825+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: 740+&lt;/li&gt;
&lt;li&gt;📄 License: MIT&lt;/li&gt;
&lt;li&gt;📅 Created: 2026-06-18&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install with the Skills CLI&lt;/span&gt;
npx skills add https://github.com/cloudflare/security-audit-skill &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skill&lt;/span&gt; security-audit

&lt;span class="c"&gt;# User-level installation&lt;/span&gt;
npx skills add https://github.com/cloudflare/security-audit-skill &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skill&lt;/span&gt; security-audit &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Start your coding agent pointed at the codebase you want to audit, then say:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;find security vulnerabilities in ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;do a security review, output to ~/audits/my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill activates automatically when the request matches its trigger (security audit / find vulnerabilities / pen-test, etc.).&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;guidance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Security questions, focused reviews, methodology&lt;/td&gt;
&lt;td&gt;Use only relevant parts; don't run the full workflow or write files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;full audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explicit audit/pen-test request, end-to-end review&lt;/td&gt;
&lt;td&gt;Run all six phases and write report files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Core: The Six-Phase Audit Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Reconnaissance
&lt;/h3&gt;

&lt;p&gt;Launch multiple &lt;code&gt;research&lt;/code&gt; agents in parallel, each returning structured source facts with &lt;code&gt;file:line&lt;/code&gt; references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent 1a&lt;/strong&gt;: product type, tech stack, build commands, subsystem boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 1b&lt;/strong&gt;: principals, authority, trust boundaries, control locations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 1c&lt;/strong&gt;: entry surfaces, copies, and sinks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outputs &lt;code&gt;architecture.md&lt;/code&gt; and &lt;code&gt;coverage-ledger.json&lt;/code&gt;. Reconnaissance is read-only — no external services are contacted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Coverage-Led Hunting
&lt;/h3&gt;

&lt;p&gt;Assigns ledger "coverage units" to isolated &lt;code&gt;general&lt;/code&gt; agents. Each hunter reads only its assigned source blocks, writes only to its own &lt;code&gt;scratch/&lt;/code&gt;, and returns one structured result.&lt;/p&gt;

&lt;p&gt;Key: &lt;strong&gt;coverage critics&lt;/strong&gt; find gaps — which units were missed, which assignments overlap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Independent Candidate Validation
&lt;/h3&gt;

&lt;p&gt;Every unique candidate (after deduplication) goes to a &lt;strong&gt;fresh verifier that did not hunt it&lt;/strong&gt;, tasked with trying to &lt;strong&gt;refute&lt;/strong&gt; it.&lt;/p&gt;

&lt;p&gt;The verifier's prompt says explicitly: "You did not write this candidate. Try to refute it from repository source and bounded local evidence."&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Structured Output
&lt;/h3&gt;

&lt;p&gt;Writes three verdict records to &lt;code&gt;findings.json&lt;/code&gt; and validates them with &lt;code&gt;validate-findings.cjs&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 5: Independent Record Verification
&lt;/h3&gt;

&lt;p&gt;Fresh agents verify final source claims. Material replacements receive another independent verifier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 6: Target-Neutral Reporting
&lt;/h3&gt;

&lt;p&gt;Derives &lt;code&gt;REPORT.md&lt;/code&gt;, &lt;code&gt;FINDINGS-DETAIL.md&lt;/code&gt;, and &lt;code&gt;NEEDS-VALIDATION.md&lt;/code&gt; from the verified records and coverage ledger.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Verdicts: Clear Semantics
&lt;/h2&gt;

&lt;p&gt;This is the most instructive design point — &lt;strong&gt;verdicts aren't "high/medium/low risk"; they're "evidence completeness"&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;confirmed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complete source trace with a bounded, reproducible observation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An exact unresolved fact, but &lt;strong&gt;no severity assigned&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rejected&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A disproved candidate (records why it was rejected)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key principle: &lt;strong&gt;"a source-grounded suspicion" ≠ "a confirmed vulnerability."&lt;/strong&gt; When a lead can't be validated because of sandbox constraints, it stays &lt;code&gt;needs_validation&lt;/code&gt; — never hastily marked &lt;code&gt;confirmed&lt;/code&gt;, never silently dropped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Adversarial Validation
&lt;/h3&gt;

&lt;p&gt;The agent that checks a finding is never the agent that found it. This prevents model self-confirmation — when a model validates its own finding, it tends to confirm it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Severity Requires Impact
&lt;/h3&gt;

&lt;p&gt;Severity = likelihood × impact, not deviation from a checklist. A problem that matches a checklist but has no actual impact is not a vulnerability.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Defense-in-Depth Gaps Are Not Vulnerabilities
&lt;/h3&gt;

&lt;p&gt;If Layer A already prevents the attack, the absence of Layer B is a hardening note, not a vulnerability.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multiple Runs Improve Coverage
&lt;/h3&gt;

&lt;p&gt;In Cloudflare's test runs, a single run found roughly half the vulnerabilities that repeated runs found in total. So the skill is designed for additive multi-run coverage — each run uses prior ledgers and findings to target gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Coverage Ledger: Making Audits Additive
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;coverage-ledger.json&lt;/code&gt; is the skill's core data asset.&lt;/p&gt;

&lt;p&gt;A normal security audit is one-shot — run once, produce a report, and running again means starting from zero. security-audit's ledger makes audits &lt;strong&gt;incremental&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First audit run:
  → generates coverage-ledger.json (records which units are covered, confirmed, or pending)
  → finds N vulnerabilities

Second audit run (same repo):
  → reads the prior ledger and findings
  → only re-hunts "uncovered gaps" and "changed source"
  → carries forward prior findings still backed by current source
  → does NOT treat stale or unresolved prior work as covered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each unit has a state: &lt;code&gt;planned&lt;/code&gt; → &lt;code&gt;in_progress&lt;/code&gt; → &lt;code&gt;completed&lt;/code&gt; / &lt;code&gt;deferred&lt;/code&gt;. If a unit can't be assigned because of agent-count limits, it's explicitly marked &lt;code&gt;deferred&lt;/code&gt; with a reason — never silently dropped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Machine-Readable Finding Records
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;findings.json&lt;/code&gt; follows the schema in &lt;code&gt;report-schema.json&lt;/code&gt;, paired with zero-dependency validators:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report-schema.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON schema for all three verdicts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validate-findings.cjs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero-dependency validator (used in Phases 4/5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validate-coverage-ledger.cjs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coverage ledger validator (used in Phases 1–5)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The parent runs &lt;code&gt;validate-coverage-ledger.cjs&lt;/code&gt; after creating the ledger and after each update, and &lt;code&gt;validate-findings.cjs&lt;/code&gt; in Phase 4 and after each Phase 5 replacement.&lt;/p&gt;

&lt;p&gt;This "machine-readable + independently validated" design lets audit output feed downstream tooling, rather than being a PDF only humans can read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sandbox Requirements: An Honest Boundary
&lt;/h2&gt;

&lt;p&gt;security-audit explicitly requires: &lt;strong&gt;executing target-controlled code must happen inside an OS-enforced sandbox&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The sandbox must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable external networking&lt;/li&gt;
&lt;li&gt;Use a sanitized allowlisted environment&lt;/li&gt;
&lt;li&gt;Enforce resource limits&lt;/li&gt;
&lt;li&gt;Allow writes only to assigned scratch paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these controls are unavailable, the workflow &lt;strong&gt;does not execute target code&lt;/strong&gt; and keeps the lead as &lt;code&gt;needs_validation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is an honest engineering decision: &lt;strong&gt;better to leave something unconfirmed than to run potentially malicious code without a safe boundary.&lt;/strong&gt; In AI security tooling, this explicit stance on the "validation boundary" is more trustworthy than "I can auto-run the PoC."&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/cloudflare/security-audit-skill" rel="noopener noreferrer"&gt;cloudflare/security-audit-skill&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Background blog&lt;/strong&gt;: &lt;a href="https://blog.cloudflare.com/build-your-own-vulnerability-harness" rel="noopener noreferrer"&gt;Build your own vulnerability harness&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔧 &lt;strong&gt;Skills CLI&lt;/strong&gt;: &lt;a href="https://skills.sh" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✉️ &lt;strong&gt;Contact&lt;/strong&gt;: &lt;a href="mailto:security-ai-research@cloudflare.com"&gt;security-ai-research@cloudflare.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;security-audit represents a specific judgment: &lt;strong&gt;the bottleneck in AI security auditing isn't "can the model find vulnerabilities" — it's "can the findings be trusted."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three things worth noting:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial validation is the core of credibility.&lt;/strong&gt; The problem with many AI audit tools is that discovery and validation are done by the same model — the model "finds" a vulnerability, then validates its own finding, naturally biased toward confirmation. security-audit cuts this bias at the process level with the "checker ≠ finder" structural constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict semantics = evidence completeness, not risk level.&lt;/strong&gt; &lt;code&gt;confirmed&lt;/code&gt; / &lt;code&gt;needs_validation&lt;/code&gt; / &lt;code&gt;rejected&lt;/code&gt; describe "where the evidence chain breaks," not "how severe this is." Severity (likelihood × impact) is a field inside &lt;code&gt;confirmed&lt;/code&gt;. This separation makes audit results machine-processable and trusted by security teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additive coverage solves the "audits are one-shot" problem.&lt;/strong&gt; Traditional audits go stale the moment they're done. security-audit's coverage ledger makes audits incremental: each run builds on prior coverage, only filling gaps and revalidating changes. This is closer to "continuous security" than "periodic audit."&lt;/p&gt;

&lt;p&gt;If you're doing security work with a coding agent, or want to understand how to make AI-generated security conclusions trustworthy, security-audit is the most complete open-source reference available.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Explore &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills, each validated against real enterprise workflows. No hype, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;personal site&lt;/a&gt; for more insights and interesting products.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>agents</category>
      <category>vulnerabilities</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 185): Coder — Self-Hosted Cloud Dev Environments, Now Also a Safe Workshop for AI Agents</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Fri, 18 Sep 2026 02:17:44 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-185-coder-self-hosted-cloud-dev-environments-now-also-a-1gaa</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-185-coder-self-hosted-cloud-dev-environments-now-also-a-1gaa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Give every developer a consistent, isolated, disposable dev machine — now give every AI agent the same kind of workshop."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;185th&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;Coder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;"It works on my machine" is a classic problem every team has hit — a new hire spends three days configuring an environment, a dependency won't install after an OS upgrade, or the local dev box simply doesn't have enough resources to run a large project. Cloud Development Environments (CDEs) answer this problem: move the dev environment to the cloud, define it declaratively with Terraform, and make it consistent, reproducible, and disposable on demand.&lt;/p&gt;

&lt;p&gt;But Coder has picked up a second layer of meaning in recent years. As AI coding agents (Claude Code, Codex, Cursor, and others) start executing tasks autonomously, a new question shows up: &lt;strong&gt;where does the agent actually run? Where does its API key live? Who can see what it did?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Coder's answer: run agents on the same isolated, auditable infrastructure as human developers. The agent's reasoning loop runs on the control plane, and no LLM credentials ever sit inside the workspace — this is a security boundary designed for AI agents from the start, not a bolt-on fix.&lt;/p&gt;

&lt;p&gt;14.9k Stars, AGPL-3.0, built in Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How Coder uses Terraform to define cloud development environments&lt;/li&gt;
&lt;li&gt;The WireGuard-tunnel network architecture connecting workspaces&lt;/li&gt;
&lt;li&gt;Four ways to run AI agents: Coder Agents, Agent Relay, Agents in the IDE, and Agents in workspace templates&lt;/li&gt;
&lt;li&gt;How the AI Gateway centralizes model governance, cost tracking, and auditing&lt;/li&gt;
&lt;li&gt;How Coder's positioning differs from traditional CDEs like GitHub Codespaces and Gitpod&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of Terraform (Infrastructure as Code)&lt;/li&gt;
&lt;li&gt;Familiarity with basic Docker/Kubernetes operations&lt;/li&gt;
&lt;li&gt;Optional: familiarity with AI agent tool-calling and MCP concepts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;Coder's official positioning is "Self-Hosted Cloud Development Environments and AI Agents." The GitHub description is even more direct: "Secure environments for developers and their agents."&lt;/p&gt;

&lt;p&gt;That line captures Coder's current dual identity: a traditional cloud development environment platform, and a secure runtime foundation for AI agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team and Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: coder/coder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://coder.com" rel="noopener noreferrer"&gt;coder.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: AGPL-3.0 (enterprise features are covered by a separate license, see the repo's &lt;code&gt;LICENSE.enterprise&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary languages&lt;/strong&gt;: Go (backend) + TypeScript (frontend, in the &lt;code&gt;site&lt;/code&gt; directory)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;14,900+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;1,500+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👀 Watchers: &lt;strong&gt;82&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: AGPL-3.0&lt;/li&gt;
&lt;li&gt;🌐 Website: &lt;a href="https://coder.com" rel="noopener noreferrer"&gt;coder.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional dev environment setup:
  New hire → install system deps → configure IDE → set up DB → get running
  ↑ Takes days, and "works on my machine" issues are common
  ↑ Local machine may lack the resources for large projects

Cloud development environments (Coder's approach):
  Terraform template defines the environment → developer clicks a button → consistent env in seconds
  ↑ Onboarding time drops from days to seconds
  ↑ Idle resources shut down automatically, saving cost
  ↑ Environments are fully consistent — no more "my machine" problem

The new problem in the AI agent era:
  You want Claude Code / Codex to run tasks autonomously
  → Where does it actually run? Locally? Inside the workspace?
  → Where does the API key live? Is exposing it to the workspace safe?
  → Who's tracking how many tokens the agent spent and what it did?

Coder's answer:
  The agent's reasoning loop runs on the control plane (on your own infrastructure)
  → No LLM credentials ever live inside the workspace
  → Every action is tied to a specific user identity — auditable by design
  → The AI Gateway centralizes authentication, cost, and policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standardized development environments for teams/enterprises&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace "everyone configures their own machine" — new hires get a standardized workspace in seconds&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud-based development for large projects&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offload compute-heavy work to the cloud when local hardware isn't enough; the local machine is just a lightweight client&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hosting AI agent background tasks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delegate long-running tasks to Claude Code, Codex, and other coding agents inside isolated workspaces without tying up the developer's local machine&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enterprise-grade AI model governance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scenarios that need to track team-wide token usage, which agents called which tools, and centrally manage API keys&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environments with strict compliance and audit requirements&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industries like finance and healthcare that need complete operational audit logs for their dev environments&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick local trial&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://coder.com/install.sh | sh
coder server

&lt;span class="c"&gt;# Production deployment (requires PostgreSQL 13+)&lt;/span&gt;
coder server &lt;span class="nt"&gt;--postgres-url&lt;/span&gt; &amp;lt;postgres-connection-string&amp;gt; &lt;span class="nt"&gt;--access-url&lt;/span&gt; &amp;lt;public-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Helm Chart is also available for Kubernetes deployment, suited to team-scale production use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Terraform-Defined Workspaces&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each workspace's underlying infrastructure is declared through a Terraform template, with support for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;EC2 instances and other cloud resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Pod-level workspaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Containerized workspaces, good for local/small-scale deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other cloud platforms&lt;/td&gt;
&lt;td&gt;Extendable through the Terraform Provider ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Template Builder offers a guided UI for common configurations, so you don't need to hand-write Terraform for everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. WireGuard Secure Tunnels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workspaces connect over WireGuard tunnels, keeping network traffic secure while supporting flexible topologies — direct SSH, IDE remote connections, or the web terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Automatic Shutdown of Idle Resources&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workspaces automatically shut down their underlying compute resources when unused, starting them back up only on demand — a key cost control mechanism that avoids the classic "forgot to turn off the cloud VM" problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Four Ways to Run AI Agents&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coder Agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A built-in native AI coding agent whose reasoning loop runs on the control plane; the workspace can be fully network-isolated, ideal for long-running background tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent Relay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connects a cloud-hosted agent service (like Cursor Cloud Agents) to a self-hosted workspace — reasoning happens in the cloud, the workspace executes tool calls (early preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agents in the IDE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Integrates with IDE-native agents like Cursor, Devin Desktop, and Zed, working alongside a developer's existing workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agents in workspace templates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Template admins install terminal-based agents like Claude Code or Codex directly into workspace templates via Registry modules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An example of an agent baked into a template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"claude-code"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"registry.coder.com/coder/claude-code/coder"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.2"&lt;/span&gt;
  &lt;span class="nx"&gt;agent_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;coder_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;strong&gt;5. AI Gateway (Enterprise)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Centralized management of all AI model access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified authentication&lt;/strong&gt;: no need to configure API keys separately in every workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trail&lt;/strong&gt;: logs all prompts and tool calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy enforcement&lt;/strong&gt;: governs access to upstream LLM providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Firewall&lt;/strong&gt;: process-level network and command policies that constrain what an agent can access and execute inside a workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Editor and Ecosystem Integrations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS Code extension, JetBrains Toolbox plugin&lt;/li&gt;
&lt;li&gt;Dev Containers support (&lt;code&gt;@devcontainers/cli&lt;/code&gt; + Docker)&lt;/li&gt;
&lt;li&gt;Envbuilder (an alternative for environments without Docker access)&lt;/li&gt;
&lt;li&gt;Kubernetes log streaming, GitHub Actions integration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Running the Agent Loop on the Control Plane Matters
&lt;/h3&gt;

&lt;p&gt;This is the most interesting architectural choice in Coder's AI agent design. Most AI coding tools follow this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Common architecture (agent runs inside the workspace/locally):
  Developer machine / workspace
  ├── AI agent process
  ├── LLM API key (in an env var/config file)
  └── Tool calls execute directly, locally

  Risk: if the workspace is compromised, the API key is exposed directly
  Risk: there's no unified audit point for everything the agent does
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coder Agents takes a different approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coder's architecture (agent reasoning runs on the control plane):
  Control plane (your infrastructure, not the workspace)
  ├── Agent reasoning loop
  ├── LLM API key (lives here, and only here)
  └── Decisions → dispatched to the workspace for execution

  Workspace
  ├── Only receives specific execution instructions
  ├── Never stores any credentials
  └── Execution results → reported back to the control plane

  Benefits:
  - A compromised workspace leaks no API key (it's simply not there)
  - Every tool call is tied to a user identity — auditable by design
  - Workspaces can be fully network-isolated and the agent still works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The design essentially separates "brain" from "hands and feet" — reasoning happens in a controlled central environment, execution is distributed to isolated workspaces. For enterprise scenarios that need compliance audits, this architecture gives you a single, controllable audit point.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trade-offs Across Four Agent Modes
&lt;/h3&gt;

&lt;p&gt;Coder doesn't push a single way to integrate AI agents — it offers four choices, each mapping to a different trust model and use case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ranked by trust level, highest to lowest:

1. Coder Agents (most controlled)
   Reasoning happens entirely on your infrastructure; credentials never leave the control plane
   Best for: enterprises with strict compliance needs requiring full audit trails

2. Agents in workspace templates (controlled + flexible)
   The agent itself runs inside the workspace, but the workspace is standardized and auditable
   Best for: teams that need to use specific tools like Claude Code or Codex

3. Agents in the IDE (developer autonomy)
   Follows the developer's own local IDE configuration; Coder just provides the underlying workspace
   Best for: letting developers use familiar tools while Coder handles the infrastructure

4. Agent Relay (trusting a third-party cloud service)
   Orchestration/reasoning happens on a third-party cloud, the workspace just executes
   Best for: teams that want cloud agent capabilities like Cursor Cloud, but need self-hosted execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical implication of this layered design: different teams have different trust boundaries for "what can the AI agent see, what can it do." Coder covers the entire spectrum — from "fully closed internal loop" to "bring in external cloud intelligence" — on the same underlying infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Compares to Similar Cloud Development Environments
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;GitHub Codespaces&lt;/th&gt;
&lt;th&gt;Gitpod&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Coder&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;GitHub-hosted only&lt;/td&gt;
&lt;td&gt;Mostly cloud-hosted&lt;/td&gt;
&lt;td&gt;Fully self-hosted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure definition&lt;/td&gt;
&lt;td&gt;Limited customization&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gitpod.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terraform (full IaC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data sovereignty&lt;/td&gt;
&lt;td&gt;❌ Lives on GitHub's cloud&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;✅ Fully self-controlled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native AI agent support&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;✅ Four modes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model governance (AI Gateway)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend flexibility&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Choose AWS/K8s/Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Partially open&lt;/td&gt;
&lt;td&gt;✅ AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Coder's core differentiation: &lt;strong&gt;data sovereignty from full self-hosting&lt;/strong&gt; + &lt;strong&gt;Terraform-level infrastructure flexibility&lt;/strong&gt; + &lt;strong&gt;a security boundary purpose-built for AI agents&lt;/strong&gt;. The trade-off is you have to operate the system yourself rather than getting "it just works" like Codespaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/coder/coder" rel="noopener noreferrer"&gt;https://github.com/coder/coder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://coder.com" rel="noopener noreferrer"&gt;https://coder.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://coder.com/docs" rel="noopener noreferrer"&gt;https://coder.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Discord&lt;/strong&gt;: Coder's community Discord&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/coder/coder/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; — The foundation of Coder's template system, declarative infrastructure definition&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://containers.dev" rel="noopener noreferrer"&gt;Devcontainers&lt;/a&gt; — The containerized dev environment standard Coder supports&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; — The tool-calling standard relevant to Coder's agent ecosystem&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Terraform defines everything&lt;/strong&gt;: workspace infrastructure is declared as IaC, freely choosing AWS/Kubernetes/Docker backends, fully reproducible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WireGuard secure tunnels + automatic idle shutdown&lt;/strong&gt;: network security and cost control together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four AI agent integration modes&lt;/strong&gt;: from the fully controlled Coder Agents to the flexible Agents in the IDE, covering different trust boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent reasoning runs on the control plane&lt;/strong&gt;: credentials never enter the workspace, auditable by design — a security architecture built for the AI agent era, not a feature bolted onto a traditional CDE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AI Gateway provides centralized governance&lt;/strong&gt;: unified authentication, cost tracking, and policy enforcement — necessary infrastructure for enterprise-grade AI usage&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams that need a self-hosted cloud dev environment&lt;/strong&gt;: don't want code and data on a third-party cloud, but still want Codespaces-level experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organizations running AI coding agents at scale&lt;/strong&gt;: need to centrally manage API keys, track cost, and audit agent actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprises with strict compliance and audit requirements&lt;/strong&gt;: finance, healthcare, and other industries that need complete operational logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps/platform teams&lt;/strong&gt;: want to manage dev environment infrastructure uniformly with Terraform instead of everyone configuring their own&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Coder originally solved the old problem of "cloud development environments," but the security boundary it built for AI agents — reasoning on the control plane, credentials never in the workspace — may turn out to be its real value in this era.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>code</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>DeepSeek Harness Series (10): Building a Complete dsh Plugin — From Requirements to Production</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Fri, 18 Sep 2026 02:01:13 +0000</pubDate>
      <link>https://dev.to/wonderlab/deepseek-harness-series-10-building-a-complete-dsh-plugin-from-requirements-to-production-j9c</link>
      <guid>https://dev.to/wonderlab/deepseek-harness-series-10-building-a-complete-dsh-plugin-from-requirements-to-production-j9c</guid>
      <description>&lt;h2&gt;
  
  
  From 'Knowing' to 'Doing'
&lt;/h2&gt;

&lt;p&gt;The previous nine articles covered a lot of ground: the Cordis plugin system, tool registration, the Agent loop, Session memory, System Prompt assembly, capability seams, multi-Agent collaboration, observability...&lt;/p&gt;

&lt;p&gt;Knowing what each piece does, and actually assembling them into something that runs, are two different things.&lt;/p&gt;

&lt;p&gt;This article does the second one.&lt;/p&gt;

&lt;p&gt;We'll build a complete plugin from scratch — from the requirements description to code you can drop into a Bundle. The knowledge from the previous nine articles won't be recited in a list — it will show up naturally in the code. Where something needs explanation, there's a note with a chapter reference.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;Plugin name: &lt;strong&gt;workspace-context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feature requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inject the current working directory into the system prompt (so the model 'knows where it is')&lt;/li&gt;
&lt;li&gt;Provide a &lt;code&gt;get_workspace_info&lt;/code&gt; tool (so the model can actively query workspace files)&lt;/li&gt;
&lt;li&gt;Pause before dangerous tool calls (names containing &lt;code&gt;delete&lt;/code&gt; or &lt;code&gt;rm&lt;/code&gt;) and wait for user confirmation&lt;/li&gt;
&lt;li&gt;Measure execution time for every tool call (observability)&lt;/li&gt;
&lt;li&gt;When the plugin is unloaded, all registrations clean up automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These five features map directly to the five main concerns of plugin development: &lt;strong&gt;prompt + tools + permissions + observability + lifecycle&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Plugin Skeleton
&lt;/h2&gt;

&lt;p&gt;A dsh plugin is a TypeScript module that follows the Cordis plugin protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/workspace-context/src/index.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Plugin name: unique identifier in the Cordis dependency tree&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workspace-context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Declare service dependencies.&lt;/span&gt;
&lt;span class="c1"&gt;// Cordis ensures these services are ready before calling apply.&lt;/span&gt;
&lt;span class="c1"&gt;// If a service is unavailable, the plugin suspends (no crash — just waits).&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;systemPrompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// Plugin entry point.&lt;/span&gt;
&lt;span class="c1"&gt;// ctx is the private Cordis context for this plugin.&lt;/span&gt;
&lt;span class="c1"&gt;// Everything registered through ctx (tools, prompt sections, event listeners)&lt;/span&gt;
&lt;span class="c1"&gt;// is bound to this context's lifecycle — automatically disposed when the plugin unloads.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// All registration goes here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three declarations (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;inject&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;) are the minimum structure for a dsh plugin.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;inject&lt;/code&gt; isn't just an import list — it's a contract. It tells the Cordis runtime: 'I depend on these services. If they're not ready yet, don't call me.' This is the foundation for hot-swapping (covered in article 02 on the Cordis plugin system).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Inject a System Prompt Section
&lt;/h2&gt;

&lt;p&gt;The cleanest way to make the model aware of the current working directory is through a System Prompt Section — not by repeating it in every user message (article 06: System Prompt Assembly).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inject workspace information into the system prompt&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workspace-context:cwd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Place this section after Agent instructions, before user-facing prompts&lt;/span&gt;
  &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSectionOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;context:workspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// Dynamic text: re-evaluated every time the system prompt is assembled.&lt;/span&gt;
  &lt;span class="c1"&gt;// If the working directory changes mid-session, the next request gets the fresh value.&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;  &lt;span class="c1"&gt;// No cwd available — inject nothing&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;## Workspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`Current working directory: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All relative file paths are resolved from this directory.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;text&lt;/code&gt; is a function, not a string. dsh calls it each time it assembles the system prompt — so the injected information is always current, never a stale snapshot.&lt;/p&gt;

&lt;p&gt;One more detail: we don't need to save the return value of &lt;code&gt;section()&lt;/code&gt; (which is a disposer). Because we used &lt;code&gt;ctx.systemPrompt.section()&lt;/code&gt;, Cordis tracks that this Section belongs to the current plugin's context and will clean it up automatically when the plugin unloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Register the &lt;code&gt;get_workspace_info&lt;/code&gt; Tool
&lt;/h2&gt;

&lt;p&gt;A tool lets the model actively 'ask' rather than just 'be told' (article 03: Tool System).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@deepseek-ai/dsh-tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defineTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_workspace_info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get information about the current workspace: directory listing and file sizes.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Relative path within the workspace. Defaults to workspace root.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Strict mode: additionalProperties: false prevents unexpected fields from the model&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// render converts the structured return value into model-readable text.&lt;/span&gt;
    &lt;span class="c1"&gt;// Separating 'structured data' from 'what the model sees' is a core dsh design principle.&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Directory: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;` (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes)`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;sizeStr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lines&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="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Read-only operation. Doesn't modify any state.&lt;/span&gt;
  &lt;span class="c1"&gt;// Safe to run in parallel with other tools.&lt;/span&gt;
  &lt;span class="na"&gt;isConcurrencySafe&lt;/span&gt;&lt;span class="p"&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Pull cwd from the session header; fall back to process.cwd() if absent&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&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;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;withFileTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Only call stat on files (to get size) — directories don't need it&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Directories first, then files; alphabetical within each group&lt;/span&gt;
      &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;isConcurrencySafe: () =&amp;gt; true&lt;/code&gt; is a performance signal. When dsh sees multiple tool call requests in the same Agent loop turn, it can run concurrency-safe tools in parallel instead of serializing them. For read-only tools, this should almost always be &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Permission Interception
&lt;/h2&gt;

&lt;p&gt;Dangerous operations — deletions, overwrites — should give a human a chance to intervene before they run. dsh handles this via the &lt;code&gt;tools/pre-execute&lt;/code&gt; event (article 03):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Intercept tool calls whose names contain 'delete' or 'rm'.&lt;/span&gt;
&lt;span class="c1"&gt;// Returning { kind: 'ask' } pauses execution and waits for user confirmation.&lt;/span&gt;
&lt;span class="c1"&gt;// Returning { kind: 'deny' } rejects the call immediately — the tool never runs.&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools/pre-execute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDangerous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Not dangerous: let it through, continue the execution chain&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isDangerous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Dangerous: require confirmation.&lt;/span&gt;
  &lt;span class="c1"&gt;// If no approval service is configured in the Bundle,&lt;/span&gt;
  &lt;span class="c1"&gt;// 'ask' automatically degrades to 'deny'.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`About to run: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;middleware pattern&lt;/strong&gt;: &lt;code&gt;next()&lt;/code&gt; means 'continue the chain'. Returning something else without calling &lt;code&gt;next()&lt;/code&gt; short-circuits the chain — the tool is never executed.&lt;/p&gt;

&lt;p&gt;There's also a design intent here: instead of a whitelist of safe tool names, the check is based on &lt;strong&gt;naming conventions&lt;/strong&gt;. Real projects might need more precise rules, but the shape is the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Execution Time Metering
&lt;/h2&gt;

&lt;p&gt;In production, you need to know which tools are slowest and which calls take suspiciously long (article 09: Observability):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrap every tool execution to measure elapsed time.&lt;/span&gt;
&lt;span class="c1"&gt;// tools/execute is a middleware hook — every tool call passes through it.&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools/execute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Call next() to run the actual tool implementation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// result.isError is the standardized error flag.&lt;/span&gt;
  &lt;span class="c1"&gt;// Here we log to console; in a real project, route to ctx.sessionTelemetry.&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Must return result — otherwise the tool's return value is lost&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&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;A common mistake to watch out for: forgetting &lt;code&gt;return result&lt;/code&gt;. The &lt;code&gt;tools/execute&lt;/code&gt; hook is a 'wrapping' hook — you can do work before and after, and optionally transform the result, but you must pass it back.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Session Observation
&lt;/h2&gt;

&lt;p&gt;Log a summary at the end of each Turn for later analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Listen to session/event and print a summary when each Turn ends&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turn/end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="c1"&gt;// reason.kind is why the Turn ended:&lt;/span&gt;
  &lt;span class="c1"&gt;// 'complete'    — normal completion (model decided the task is done)&lt;/span&gt;
  &lt;span class="c1"&gt;// 'error'       — something went wrong&lt;/span&gt;
  &lt;span class="c1"&gt;// 'interrupted' — user interrupted&lt;/span&gt;
  &lt;span class="c1"&gt;// 'max-turns'   — hit the max turn count limit&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Turn &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;turnNum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the minimal version. A complete production implementation would also emit a telemetry record via &lt;code&gt;ctx.sessionTelemetry.emit()&lt;/code&gt; and print a token consumption summary using &lt;code&gt;ctx.tokenMeter.measure(session)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Plugin: Putting It All Together
&lt;/h2&gt;

&lt;p&gt;All five parts combined into one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/workspace-context/src/index.ts&lt;/span&gt;
&lt;span class="c1"&gt;// workspace-context plugin: workspace awareness + tool + permission interception + observability&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@deepseek-ai/dsh-tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// ── Plugin metadata ────────────────────────────────────────────&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workspace-context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Declare service dependencies&lt;/span&gt;
&lt;span class="c1"&gt;// tools:        tool registry&lt;/span&gt;
&lt;span class="c1"&gt;// systemPrompt: system prompt section manager&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;systemPrompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// ── Plugin entry point ─────────────────────────────────────────&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 1. Inject a System Prompt Section ─────────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workspace-context:cwd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSectionOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;context:workspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;## Workspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`Current working directory: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All relative file paths are resolved from this directory.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 2. Register the get_workspace_info tool ────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defineTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_workspace_info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get information about the current workspace: directory listing and file sizes.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Relative path within the workspace. Defaults to workspace root.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Directory: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;` (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes)`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;sizeStr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lines&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="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;isConcurrencySafe&lt;/span&gt;&lt;span class="p"&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&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;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;withFileTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;directory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 3. Permission interception: require confirmation for dangerous tools ──&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools/pre-execute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDangerous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isDangerous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&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;{&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`About to run: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 4. Execution time metering ─────────────────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools/execute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 5. Session Turn summary observation ───────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turn/end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Turn &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;turnNum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── Note ───────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;// No cleanup code anywhere in this file.&lt;/span&gt;
  &lt;span class="c1"&gt;// Every registration made through ctx (tools.register, systemPrompt.section, ctx.on)&lt;/span&gt;
  &lt;span class="c1"&gt;// is tracked by Cordis and automatically disposed when the plugin unloads.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole plugin is about 90 lines including comments. Five concerns, 10-20 lines each. This is roughly the right size for a typical dsh plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding the Plugin to a Bundle
&lt;/h2&gt;

&lt;p&gt;Once the plugin is written, it needs to be wired into a Bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bundle.ts (pseudocode: Bundle configuration file)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;workspaceContext&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./packages/workspace-context/src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineBundle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="c1"&gt;// Core service providers must be listed before the plugins that depend on them.&lt;/span&gt;
  &lt;span class="c1"&gt;// workspace-context declares inject: ['tools', 'systemPrompt'],&lt;/span&gt;
  &lt;span class="c1"&gt;// so both providers must appear before it.&lt;/span&gt;
  &lt;span class="nx"&gt;coreToolsPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// provides the tools service&lt;/span&gt;
  &lt;span class="nx"&gt;systemPromptPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// provides the systemPrompt service&lt;/span&gt;

  &lt;span class="nx"&gt;workspaceContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// our plugin&lt;/span&gt;

  &lt;span class="c1"&gt;// Other business plugins...&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cordis handles service readiness ordering based on &lt;code&gt;inject&lt;/code&gt; declarations — even if the order in the Bundle is wrong, Cordis will wait for dependencies to be ready before activating the plugin, rather than crashing on startup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Recap: Which Articles This Plugin Uses
&lt;/h2&gt;

&lt;p&gt;This 90-line plugin covers most of the core mechanisms from the series:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin feature&lt;/th&gt;
&lt;th&gt;Series article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ctx.tools.register()&lt;/code&gt; — register a tool&lt;/td&gt;
&lt;td&gt;Article 03: Tool System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;defineTool&lt;/code&gt; + &lt;code&gt;isConcurrencySafe&lt;/code&gt; — concurrency flag&lt;/td&gt;
&lt;td&gt;Article 03: Tool System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tools/pre-execute&lt;/code&gt; — permission interception&lt;/td&gt;
&lt;td&gt;Article 03: Tool System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tools/execute&lt;/code&gt; — wrap execution&lt;/td&gt;
&lt;td&gt;Article 03: Tool System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ctx.systemPrompt.section()&lt;/code&gt; — inject a prompt section&lt;/td&gt;
&lt;td&gt;Article 06: System Prompt Assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;session/event&lt;/code&gt; — Turn-end observation&lt;/td&gt;
&lt;td&gt;Article 09: Observability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;export const inject&lt;/code&gt; — Cordis service dependencies&lt;/td&gt;
&lt;td&gt;Article 02: Cordis Plugin System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ctx&lt;/code&gt; lifecycle auto-dispose&lt;/td&gt;
&lt;td&gt;Article 02: Cordis Plugin System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;session.header.cwd&lt;/code&gt; — Session context&lt;/td&gt;
&lt;td&gt;Article 05: Session Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this plugin doesn't use: multi-Agent collaboration (article 08) and capability seams (article 07). Those are higher-level mechanisms, typically configured at the framework level rather than touched by individual business plugins.&lt;/p&gt;




&lt;h2&gt;
  
  
  The dsh Design Philosophy
&lt;/h2&gt;

&lt;p&gt;This is a good place to close out the series.&lt;/p&gt;

&lt;p&gt;dsh's core design idea is simple: &lt;strong&gt;everything is a plugin, everything has a boundary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don't need to fork the core to change almost anything about behavior — adding tools, modifying prompts, intercepting dangerous operations, wiring up monitoring — all of that happens through plugins. Boundaries are enforced by Cordis's &lt;code&gt;ctx&lt;/code&gt;: a plugin only affects what it registers, and when it unloads, it leaves no trace.&lt;/p&gt;

&lt;p&gt;This pattern comes from Cordis. dsh applies it to an Agent runtime.&lt;/p&gt;

&lt;p&gt;A production-grade Agent isn't just 'model + tool calls'. It needs permission controls, observability, persistence, multi-Agent coordination — it needs stable handles for all those engineering concerns. dsh's architecture is specifically designed to turn those problems into composable, swappable, testable pieces.&lt;/p&gt;

&lt;p&gt;This is still an early field. Many best practices are still forming, many edge cases don't have standard answers yet. But with this architectural foundation, you at least know where to add code and where to look when something goes wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Index
&lt;/h2&gt;

&lt;p&gt;If you're just starting, work through these in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Article 01: What is dsh and why use it&lt;/li&gt;
&lt;li&gt;Article 02: The Cordis plugin system — plugins, services, and lifecycles&lt;/li&gt;
&lt;li&gt;Article 03: The tool system — registration, execution, interception&lt;/li&gt;
&lt;li&gt;Article 04: The Agent loop — how a single conversation run works&lt;/li&gt;
&lt;li&gt;Article 05: Session memory — how state is stored and read&lt;/li&gt;
&lt;li&gt;Article 06: System prompt assembly — how prompts are constructed&lt;/li&gt;
&lt;li&gt;Article 07: Capability seams — swappable Agent capabilities&lt;/li&gt;
&lt;li&gt;Article 08: Multi-Agent collaboration — sub-Agents and task dispatch&lt;/li&gt;
&lt;li&gt;Article 09: Observability — token metering and telemetry&lt;/li&gt;
&lt;li&gt;Article 10: Complete plugin walkthrough (this article)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills validated in real-world, enterprise-grade workflows. Not demos — things that actually work in production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>dsh</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>DeepSeek Harness Series (09): Observability — How to Know What Your Agent Is Actually Doing</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Thu, 17 Sep 2026 10:04:56 +0000</pubDate>
      <link>https://dev.to/wonderlab/deepseek-harness-series-09-observability-how-to-know-what-your-agent-is-actually-doing-2h5g</link>
      <guid>https://dev.to/wonderlab/deepseek-harness-series-09-observability-how-to-know-what-your-agent-is-actually-doing-2h5g</guid>
      <description>&lt;h2&gt;
  
  
  Start with a Frustrating Scenario
&lt;/h2&gt;

&lt;p&gt;Your Agent runs for three minutes, then throws an error.&lt;/p&gt;

&lt;p&gt;You stare at the logs — nothing useful. You don't know which tools it called, where it got stuck, or where the tokens went. You certainly don't know why it failed.&lt;/p&gt;

&lt;p&gt;That's what missing observability looks like in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Observability Matters More for Agents
&lt;/h2&gt;

&lt;p&gt;With a traditional service, you can add breakpoints and read stack traces. Agents are different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents are non-deterministic.&lt;/strong&gt; The same input might produce a completely different sequence of tool calls. You can't set a breakpoint inside "the model's decision-making" — that's a black box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging requires log reconstruction.&lt;/strong&gt; The model's 'reasoning' only manifests in the text and tool calls it outputs. You need to capture all of that so you can rebuild the inference chain after the fact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token costs are opaque.&lt;/strong&gt; Across a multi-turn Agent conversation, which step is the most expensive? Is it the long tool result in turn three? The oversized system prompt? Without metering, you can't even identify where to optimize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production incidents need evidence.&lt;/strong&gt; When a user reports a bad answer, you need to reconstruct the full execution chain from that moment — which tools ran, what they returned, what the model saw.&lt;/p&gt;




&lt;h2&gt;
  
  
  Session Logs: The Most Complete Observability Data Source
&lt;/h2&gt;

&lt;p&gt;Recall from article 05: a dsh Session is an &lt;strong&gt;append-only log of typed events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That design isn't only about persistence — the log itself is the most complete observability source you have.&lt;/p&gt;

&lt;p&gt;Every Session event contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Structure of every SessionEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;event type (e.g. 'tool/call', 'turn/end', 'assistant/message')&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;monotonically increasing sequence number (starts at 0)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Unix timestamp in milliseconds&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;typed event payload (structure varies by type)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means: as long as you can read the Session log, you can reconstruct the entire execution — what happened at each step, how long it took, whether anything failed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-Time Listening: session/event
&lt;/h2&gt;

&lt;p&gt;You don't have to wait for the log to finish before analyzing it. dsh provides the &lt;code&gt;session/event&lt;/code&gt; event, which fires for every entry as it's written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Listen to all events on a Session in real time&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This callback fires for every event&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] seq=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; time=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Check for tool calls&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool/call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// event.data.name is the tool name&lt;/span&gt;
    &lt;span class="c1"&gt;// event.data.arguments is a raw JSON string (model output, not yet parsed)&lt;/span&gt;
    &lt;span class="c1"&gt;// event.data.callId is the unique ID for this invocation&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Tool: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Args: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Check for tool execution results&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool/result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Detect failure by looking for isError: true in the content blocks&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool_result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Result: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During development, this listener is invaluable — you see what the Agent is doing in real time, not just after it finishes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token Metering: ctx.tokenMeter
&lt;/h2&gt;

&lt;p&gt;Knowing 'what happened' is step one. Knowing 'how much it cost' is equally important.&lt;/p&gt;

&lt;p&gt;dsh provides &lt;code&gt;ctx.tokenMeter&lt;/code&gt; to measure the token pressure of the current Session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TokenMeasurement interface (from packages/llm/token-meter/src/types.ts)&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TokenMeasurement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// How many events this measurement consumed (for caching, avoids recomputation)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logRevision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SessionLogOffset&lt;/span&gt;

  &lt;span class="c1"&gt;// Total token pressure for the current request (input + output combined)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;totalTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;

  &lt;span class="c1"&gt;// Token count of the current surface (history visible to the model)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;surfaceTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;

  &lt;span class="c1"&gt;// Signed delta of surface tokens vs. the last successful request&lt;/span&gt;
  &lt;span class="c1"&gt;// (can be negative if context shrank)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;surfaceDeltaTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;

  &lt;span class="c1"&gt;// Surface nodes in positional order with their individual token counts&lt;/span&gt;
  &lt;span class="c1"&gt;// (lets you see how many tokens each message contributes)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;TokenSurfaceNode&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;Key concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;surfaceTokens&lt;/strong&gt;: How many tokens the model actually sees as input for this request. This drives the 'input token' portion of your API bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;totalTokens&lt;/strong&gt;: Input + output combined — the full cost of the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;surfaceDeltaTokens&lt;/strong&gt;: How much the surface grew since the last request. If this keeps increasing, your context is ballooning and you might need a compression strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Print a token summary at the end of each Turn&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only care about Turn-end events&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turn/end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="c1"&gt;// Call measure to get the current Token measurement for this Session&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;measurement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenMeter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Turn &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ended:`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Surface tokens: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surfaceTokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Total tokens:   &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Show delta — positive means context is growing&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surfaceDeltaTokens&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Delta:          &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Warn if context is growing too fast&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  ⚠ Context growing fast, consider compression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Telemetry Seam: ctx.sessionTelemetry
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;session/event&lt;/code&gt; listeners are great for development, but in production you need to ship data to external systems — Grafana, Datadog, CloudWatch, and so on.&lt;/p&gt;

&lt;p&gt;dsh provides a 'Telemetry Seam' for this: &lt;code&gt;ctx.sessionTelemetry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The word 'seam' is deliberate — it's a standardized interface that lets you plug in any telemetry backend, while the harness itself stays independent of any particular monitoring system.&lt;/p&gt;

&lt;p&gt;Structure of each telemetry record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SessionTelemetryRecord (from packages/session/session-telemetry/src)&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SessionTelemetryRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Two channels:&lt;/span&gt;
  &lt;span class="c1"&gt;//   'ledger': full mirror of Session log events, one-to-one mapping&lt;/span&gt;
  &lt;span class="c1"&gt;//   'ops':    operational signals, only emitted for special situations&lt;/span&gt;
  &lt;span class="nl"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ledger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ops&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="c1"&gt;// Timestamp in milliseconds&lt;/span&gt;
  &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;

  &lt;span class="c1"&gt;// Severity level&lt;/span&gt;
  &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="c1"&gt;// Identifying attributes (for querying and filtering)&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. session.id, event.type, event.seq&lt;/span&gt;
  &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Full payload: a deep copy of event.data&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What each channel does
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ledger&lt;/code&gt; channel&lt;/strong&gt;: A complete mirror of the Session log. Every Session event produces one corresponding ledger record. This is your source of truth for audit and replay.&lt;/p&gt;

&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;assistant/message&lt;/code&gt; (with full streaming data)&lt;/li&gt;
&lt;li&gt;Every &lt;code&gt;tool/call&lt;/code&gt; and &lt;code&gt;tool/result&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Failed &lt;code&gt;assistant/attempt&lt;/code&gt; events (things the model generated but didn't commit)&lt;/li&gt;
&lt;li&gt;All lifecycle events: &lt;code&gt;turn/start&lt;/code&gt;, &lt;code&gt;turn/end&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ops&lt;/code&gt; channel&lt;/strong&gt;: Operational signals, just two kinds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent-error&lt;/code&gt;: The Agent failed outside of a Turn (e.g. initialization error)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shutdown&lt;/code&gt;: The Agent shut down cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How severity is determined
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;error&lt;/code&gt;: tool results with &lt;code&gt;isError: true&lt;/code&gt;, &lt;code&gt;turn/end&lt;/code&gt; with an error reason, &lt;code&gt;agent-error&lt;/code&gt; ops events&lt;/li&gt;
&lt;li&gt;Everything else: &lt;code&gt;info&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mapping means you can filter &lt;code&gt;severity === 'error'&lt;/code&gt; in your monitoring system to see all failures instantly, without writing custom classification logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenTelemetry Integration
&lt;/h2&gt;

&lt;p&gt;dsh provides an official OTel Provider plugin: &lt;code&gt;dsh-session-telemetry-otel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How to wire it in (conceptual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add this plugin to your Bundle configuration (pseudocode)&lt;/span&gt;
&lt;span class="c1"&gt;// This connects ctx.sessionTelemetry to the OTel backend&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@deepseek-ai/dsh-session-telemetry-otel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Internally, the plugin will:&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Register an OTel backend implementation for ctx.sessionTelemetry&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Send each SessionTelemetryRecord via the OTel JS SDK's Logger API&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Support configurable Exporters (OTLP, Console, File, etc.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design principles worth knowing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary axiom&lt;/strong&gt;: The harness only calls &lt;code&gt;emit()&lt;/code&gt;. Batching, retries, and queuing are the OTel SDK's responsibility — the harness doesn't touch them. Both sides can evolve independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best-effort delivery&lt;/strong&gt;: Telemetry records may be duplicated or lost. Consumers should deduplicate ledger records using the &lt;code&gt;(session.id, format_version, event.seq)&lt;/code&gt; tuple, rather than assuming exactly-once delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;flush is optional&lt;/strong&gt;: You can call &lt;code&gt;flush()&lt;/code&gt; at the end of each Turn, but the OTel backend doesn't implement it by default (to avoid concurrency conflicts). If you need strong consistency, configure it yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-On: A Simple Debug Observer Plugin
&lt;/h2&gt;

&lt;p&gt;Let's combine everything above into a complete debug observability plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// debug-observer.ts — observability plugin for development use&lt;/span&gt;
&lt;span class="c1"&gt;// Usage: add to your Bundle during development;&lt;/span&gt;
&lt;span class="c1"&gt;//        in production, swap in dsh-session-telemetry-otel instead&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug-observer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Declare dependency injection for tokenMeter&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tokenMeter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ── 1. Watch tool calls ────────────────────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool/call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Tool Call] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Call ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// arguments is a raw JSON string — straight from the model, not yet parsed&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Args: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 2. Watch tool results ──────────────────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool/result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool_result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isError&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✗&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✓&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;// toolUseId links this result back to its matching tool/call event&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolUseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blocks&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="nx"&gt;toolUseId&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Tool Result] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (call: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolUseId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 3. Print token summary at each Turn end ────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turn/end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="c1"&gt;// reason.kind can be 'complete', 'error', 'interrupted', etc.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;measurement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenMeter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n[Turn &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] ended: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Surface: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surfaceTokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; tokens`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Total:   &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalTokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; tokens`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;measurement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surfaceDeltaTokens&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Delta:   &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// If the Turn ended with an error, print the full reason object&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// ── 4. Watch Session lifecycle ─────────────────────────────&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n[Session] created: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session/disposed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Session] disposed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin drops into your Bundle during development and gives you a complete trace of everything the Agent does. In production, swap it for the OTel plugin to route that same data into your monitoring stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging Tip: Reading JSONL Log Files
&lt;/h2&gt;

&lt;p&gt;dsh persists Session logs as JSONL files — one JSON object per line, each representing one &lt;code&gt;SessionEvent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some useful command-line analysis recipes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See all tool calls (extract just the tool names)&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;session.jsonl | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"type":"tool/call"'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.data.name'&lt;/span&gt;

&lt;span class="c"&gt;# See failed assistant attempts (model output that wasn't committed)&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;session.jsonl | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"type":"assistant/attempt"'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.'&lt;/span&gt;

&lt;span class="c"&gt;# Summarize token usage per turn (from assistant/message usage field)&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;session.jsonl | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"type":"assistant/message"'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.data.usage'&lt;/span&gt;

&lt;span class="c"&gt;# See how each Turn ended (complete vs. error)&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;session.jsonl | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"type":"turn/end"'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.data.reason.kind'&lt;/span&gt;

&lt;span class="c"&gt;# Find any failed tool executions&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;session.jsonl | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"type":"tool/result"'&lt;/span&gt; | jq &lt;span class="s1"&gt;'select(.data.message.content[].isError == true)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These assume you have &lt;code&gt;jq&lt;/code&gt; installed. On Windows, PowerShell's &lt;code&gt;ConvertFrom-Json&lt;/code&gt; provides equivalent functionality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four Layers of Observability
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Real-time observation (development)
  └─ session/event listener → each event printed to console immediately

Audit and replay (post-mortem)
  └─ JSONL log file → full execution chain reconstruction, analyzable with jq

Token usage analysis
  └─ ctx.tokenMeter.measure(session) → per-node token counts across the surface
     → pinpoint which messages are burning your budget

Production monitoring (systems level)
  └─ ctx.sessionTelemetry + OTel plugin → ship to Grafana / Datadog / CloudWatch
     → alerts, dashboards, and error tracking all wired up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Observability isn't a nice-to-have for Agents — it's &lt;strong&gt;the only way to debug them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;dsh is designed with this in mind: Sessions are event logs, and event logs are audit data by definition. &lt;code&gt;ctx.tokenMeter&lt;/code&gt; makes token consumption transparent. &lt;code&gt;ctx.sessionTelemetry&lt;/code&gt; provides a standardized seam so you can choose any backend.&lt;/p&gt;

&lt;p&gt;The core pattern is simple: &lt;strong&gt;session/event listeners → JSONL persistence → Telemetry Seam → OTel backend&lt;/strong&gt;. Which layer you use depends on your context, but all of them can run simultaneously without interfering with each other.&lt;/p&gt;

&lt;p&gt;Next up is the final article in the series. We'll bring everything together — tool registration, Session management, error handling, and observability — and build a complete, production-quality plugin from scratch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills validated in real-world, enterprise-grade workflows. Not demos — things that actually work in production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>dsh</category>
      <category>llm</category>
      <category>telemetry</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 184): WeKnora — Tencent's Enterprise Knowledge Framework, From RAG Q&amp;A to a Self-Evolving Wiki</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Thu, 17 Sep 2026 10:04:00 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-184-weknora-tencents-enterprise-knowledge-framework-from-1oke</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-184-weknora-tencents-enterprise-knowledge-framework-from-1oke</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"A document shouldn't just be retrievable — it should be understood, organized, and continuously maintained."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;184th&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;WeKnora&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most enterprise knowledge base products today still stop at the standard RAG pipeline: upload a document → vectorize it → similarity search → stuff results into a prompt. That pipeline handles simple questions fine, but falls apart when the task requires multi-step reasoning, synthesis across multiple documents, or something like "figure out what this document actually says and organize it into something I can browse like an encyclopedia."&lt;/p&gt;

&lt;p&gt;WeKnora is Tencent's answer to that gap. It isn't another RAG framework — it stacks three capabilities together: &lt;strong&gt;fast Q&amp;amp;A via RAG, autonomous multi-step reasoning via a ReAct agent that orchestrates tools on its own, and long-term knowledge accumulation via a Wiki mode that auto-generates an interlinked knowledge graph&lt;/strong&gt;. It's the core technology framework behind the WeChat Dialog Open Platform, meaning it's already been through a round of real enterprise deployment.&lt;/p&gt;

&lt;p&gt;25.4k Stars, MIT License, Go backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;WeKnora's three core capabilities: RAG Q&amp;amp;A, ReAct agent, and Wiki mode&lt;/li&gt;
&lt;li&gt;The modular pipeline architecture: document parsing → vectorization → retrieval → inference&lt;/li&gt;
&lt;li&gt;The design behind its cross-session long-term memory mechanism&lt;/li&gt;
&lt;li&gt;What the 29 tools in its official MCP Server can do&lt;/li&gt;
&lt;li&gt;Enterprise deployment considerations: multi-workspace RBAC, security encryption, observability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of RAG (Retrieval-Augmented Generation)&lt;/li&gt;
&lt;li&gt;Familiarity with Agent tool-calling and the MCP protocol&lt;/li&gt;
&lt;li&gt;Optional: knowledge of vector databases (pgvector/Milvus, etc.)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;WeKnora's official positioning: "an open-source, LLM-powered knowledge framework built for enterprise-grade document understanding, semantic retrieval, and autonomous reasoning."&lt;/p&gt;

&lt;p&gt;The core problem it's solving: turning scattered documents into a &lt;strong&gt;queryable, reasoning-capable, continuously evolving&lt;/strong&gt; knowledge asset. Those three adjectives map directly to its three capabilities — queryable (RAG), reasoning-capable (ReAct agent), continuously evolving (Wiki mode's self-maintaining version control).&lt;/p&gt;

&lt;h3&gt;
  
  
  Team and Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt;: Tencent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://weknora.weixin.qq.com" rel="noopener noreferrer"&gt;weknora.weixin.qq.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related product&lt;/strong&gt;: Core technology framework of the WeChat Dialog Open Platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;25,400+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;3,500+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👀 Watchers: &lt;strong&gt;118&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📦 Current version: &lt;strong&gt;0.8.0&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💻 Primary language: Go (backend)&lt;/li&gt;
&lt;li&gt;📄 License: MIT&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional enterprise knowledge base (pure RAG):
  Upload document → chunk → vectorize → similarity search → prompt stuffing → answer
  ↑ Handles "what does this document say about X" fine
  ↑ Struggles with "synthesize a trend analysis across three documents"
  ↑ The knowledge base never organizes itself — it stays a pile of raw documents forever

WeKnora (three-in-one):
  Simple query   → RAG fast Q&amp;amp;A (low latency, good enough)
  Complex task   → ReAct agent
                   ├── decides autonomously whether to retrieve
                   ├── calls MCP tools / web search / sandboxed code execution
                   └── returns a synthesized answer after multi-step reasoning
  Long-term      → Wiki mode
                   ├── agent distills raw documents into an interlinked Markdown knowledge base
                   ├── generates an interactive knowledge graph
                   └── supports manual editing, version history, and one-click rollback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enterprise internal knowledge Q&amp;amp;A&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralize employee handbooks, product docs, and technical specs; employees query directly via WeCom/Feishu&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customer service / pre-sales auto-response&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect IM channels (Slack/Telegram/WeCom), turn product documentation into an auto-answering knowledge base&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automating complex research tasks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ReAct agent calls web search + internal document retrieval + code sandbox to complete multi-step "research → analyze → produce a report" tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Continuous knowledge asset maintenance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wiki mode lets the document knowledge base self-update and interlink like an encyclopedia, instead of staying a pile of isolated PDFs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero-code Q&amp;amp;A deployment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Through the WeChat Dialog Open Platform, non-technical staff can configure a working knowledge Q&amp;amp;A system&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prerequisites: Docker, Docker Compose, Git&lt;/span&gt;

git clone https://github.com/Tencent/WeKnora.git
&lt;span class="nb"&gt;cd &lt;/span&gt;WeKnora
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Visit http://localhost after startup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Optional feature modules (Profiles):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable knowledge graph (Neo4j)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; neo4j up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Enable object storage (MinIO)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; minio up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Enable observability tracing (Langfuse)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; langfuse up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Enable all features&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; full up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If using a local Ollama model, run &lt;code&gt;ollama serve&lt;/code&gt; first. To upgrade, set &lt;code&gt;WEKNORA_VERSION&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;, then run &lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Three Core Capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Key Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAG fast Q&amp;amp;A&lt;/td&gt;
&lt;td&gt;Everyday simple queries&lt;/td&gt;
&lt;td&gt;Vector retrieval + direct generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReAct agent&lt;/td&gt;
&lt;td&gt;Complex multi-step tasks&lt;/td&gt;
&lt;td&gt;Autonomous orchestration of retrieval/tools/sandbox/search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wiki mode&lt;/td&gt;
&lt;td&gt;Long-term knowledge accumulation&lt;/td&gt;
&lt;td&gt;Auto-distillation + interlinking + version control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Cross-Session Long-Term Memory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike most Q&amp;amp;A systems that start from zero every conversation, WeKnora maintains memory across sessions along several dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Profile&lt;/strong&gt;: user identity and background&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preferences&lt;/strong&gt;: historical choice patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facts&lt;/strong&gt;: specific facts confirmed during conversation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks&lt;/strong&gt;: multi-turn tasks being tracked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interests&lt;/strong&gt;: topic areas the user cares about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Official MCP Server: 29 Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WeKnora ships an official MCP Server that exposes its own capabilities as standard MCP tools, letting Claude Code, Cursor, and other AI tools call WeKnora's knowledge base functions directly — retrieval, writing, Wiki editing, all reachable via MCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Multi-Source Data Ingestion&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source Type&lt;/th&gt;
&lt;th&gt;Specific Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise collaboration tools&lt;/td&gt;
&lt;td&gt;Feishu, Tencent IMA, DingTalk Docs, Yuque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev platforms&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General platforms&lt;/td&gt;
&lt;td&gt;Notion, RSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document formats&lt;/td&gt;
&lt;td&gt;PDF, Word, images, Excel, XMind, and 10+ other formats&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. 20+ LLM Provider Support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Covers major international providers (OpenAI, Azure OpenAI, Anthropic Claude, Gemini) and major Chinese providers (DeepSeek, Qwen, Zhipu, Hunyuan), plus LiteLLM and Ollama as unified access layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Enterprise Security and Permissions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-workspace RBAC&lt;/strong&gt;: four-tier role matrix with fine-grained permission control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt;: AES-256-GCM data encryption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport security&lt;/strong&gt;: gRPC TLS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection&lt;/strong&gt;: guards against server-side request forgery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped API keys&lt;/strong&gt;: limit the access range of any given key&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Modular Pipeline Architecture
&lt;/h3&gt;

&lt;p&gt;WeKnora's architecture emphasizes that every stage is swappable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Parsing
   ↓ parsers for PDF / Word / Excel / images / XMind and more
Vectorization
   ↓ supports multiple embedding models
Retrieval
   ↓ supports PostgreSQL(pgvector) / Elasticsearch / OpenSearch / Milvus / Weaviate / Qdrant
LLM Inference
   ↓ supports 20+ LLM providers, unified through LiteLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit of this design: enterprises can swap in whatever infrastructure they already have. Teams already using Milvus don't need to migrate their vector store; teams with a privately deployed model can plug it in directly. This is also the technical foundation of the "data sovereignty" promise — the entire pipeline can run in a closed loop entirely within a private environment, with no data ever needing to leave the corporate network.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Design Logic Behind RAG, ReAct, and Wiki
&lt;/h3&gt;

&lt;p&gt;Why not build one "universal" mode instead of splitting into three?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pure RAG mode's limitation:
  Retrieve → prompt → generate
  ↑ Low latency, low cost, but weak reasoning
  ↑ Good for "does this document mention X"-type questions

Pure Agent mode's problem:
  Every query walks the full reasoning chain
  ↑ Even simple questions trigger multi-round tool calls — high latency, high cost
  ↑ Users perceive the system as "slower"

WeKnora's layered design:
  Simple query   → straight to RAG (fast)
  Complex query  → escalates to ReAct (accurate)
  Knowledge accumulation → runs asynchronously via Wiki mode (doesn't affect real-time Q&amp;amp;A)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This "escalate complexity on demand" design is fundamentally a dynamic trade-off between latency/cost and capability — not every question needs agent-level reasoning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiki Mode: From a Pile of Documents to a Self-Maintaining Knowledge Base
&lt;/h3&gt;

&lt;p&gt;Wiki mode is WeKnora's most distinctive capability. Most RAG systems treat documents as raw material for retrieval — once retrieval is done, the documents themselves never change.&lt;/p&gt;

&lt;p&gt;WeKnora's Wiki mode flips this: the agent actively reads raw documents, distills them into structured Markdown pages, and interlinks those pages into a Wikipedia-like knowledge network. That network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has an interactive knowledge graph visualization&lt;/li&gt;
&lt;li&gt;Supports manual editing (what the agent generates isn't final — it's a draft)&lt;/li&gt;
&lt;li&gt;Has complete version history (Git-like diff comparison)&lt;/li&gt;
&lt;li&gt;Supports one-click rollback to any historical version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This solves a common RAG pain point: when raw documents are low-quality (disorganized structure, duplicated information, stale content mixed with current), what gets retrieved is also low-quality. Wiki mode essentially has the AI do a round of "document cleanup" first, so subsequent Q&amp;amp;A is built on organized knowledge rather than raw mess.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Compares to Similar Projects
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;LangChain + Custom RAG&lt;/th&gt;
&lt;th&gt;Dify&lt;/th&gt;
&lt;th&gt;RAGFlow&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;WeKnora&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Out-of-the-box&lt;/td&gt;
&lt;td&gt;❌ Requires heavy custom dev&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReAct agent&lt;/td&gt;
&lt;td&gt;Must build yourself&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-maintaining Wiki knowledge base&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-session long-term memory&lt;/td&gt;
&lt;td&gt;Must implement yourself&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official MCP Server&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ 29 tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise RBAC&lt;/td&gt;
&lt;td&gt;Must implement yourself&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;✅ Four-tier roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chinese LLM ecosystem support&lt;/td&gt;
&lt;td&gt;Must integrate yourself&lt;/td&gt;
&lt;td&gt;Average&lt;/td&gt;
&lt;td&gt;Average&lt;/td&gt;
&lt;td&gt;✅ Native coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;WeKnora's differentiation lies in its native Wiki mode and ReAct agent integration, plus native support for the Chinese LLM ecosystem (DeepSeek, Qwen, Hunyuan) and Chinese enterprise collaboration tools (Feishu, DingTalk, WeCom) — a clear advantage for deployment in Chinese enterprises.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Tencent/WeKnora" rel="noopener noreferrer"&gt;https://github.com/Tencent/WeKnora&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://weknora.weixin.qq.com" rel="noopener noreferrer"&gt;weknora.weixin.qq.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;License&lt;/strong&gt;: MIT License&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/Tencent/WeKnora/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; — The standard protocol underlying WeKnora's official MCP Server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://langfuse.com" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt; — The observability/tracing tool WeKnora integrates with&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt; — One of WeKnora's supported vector retrieval backends&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A three-in-one capability matrix&lt;/strong&gt;: RAG for simple queries, a ReAct agent for complex tasks, Wiki mode for long-term knowledge — escalating complexity on demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wiki mode is the standout feature&lt;/strong&gt;: distilling raw documents into a self-maintaining, rollback-capable, interlinked knowledge base, rather than leaving documents permanently "raw"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A modular pipeline&lt;/strong&gt;: every stage — parsing, vectorization, retrieval, inference — is swappable to fit existing enterprise infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An official MCP Server&lt;/strong&gt;: 29 tools that let Claude Code and other AI tools call WeKnora's capabilities directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native support for the Chinese ecosystem&lt;/strong&gt;: DeepSeek/Qwen/Hunyuan plus Feishu/DingTalk/WeCom — deeply localized&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise IT / knowledge management teams&lt;/strong&gt;: need to turn scattered documents into a maintainable knowledge asset, not just "something searchable"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers building for the Chinese AI ecosystem&lt;/strong&gt;: need a knowledge framework with native support for domestic LLMs and enterprise collaboration tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams building complex agent applications&lt;/strong&gt;: need ReAct-level multi-step reasoning rather than settling for plain RAG&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organizations prioritizing data sovereignty&lt;/strong&gt;: the entire pipeline can be deployed privately with no data leaving the internal network&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;WeKnora is trying to solve this: a knowledge base shouldn't just be a pile of retrievable documents — it should be a living knowledge system that organizes itself, reasons, and keeps evolving.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>tencent</category>
      <category>rag</category>
      <category>knowledgebase</category>
    </item>
    <item>
      <title>One Open Source Project a Day (No. 183): Openship — Vercel-Level Deploy Experience on Your Own Server</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:24:34 +0000</pubDate>
      <link>https://dev.to/wonderlab/one-open-source-project-a-day-no-183-openship-vercel-level-deploy-experience-on-your-own-39h9</link>
      <guid>https://dev.to/wonderlab/one-open-source-project-a-day-no-183-openship-vercel-level-deploy-experience-on-your-own-39h9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Deploy anything. Own everything."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;183rd&lt;/strong&gt; article in the "One Open Source Project a Day" series. Today's project is &lt;strong&gt;Openship&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vercel's developer experience is genuinely excellent — push code, and a few minutes later your app is live with SSL, routing, and previews handled automatically. But it has one fundamental problem: your application runs on someone else's servers, pricing is entirely up to the vendor, a traffic spike can blow up your bill, and if you want to leave you have to migrate everything out.&lt;/p&gt;

&lt;p&gt;The alternative is running Docker yourself — full control, but SSL, CI/CD, domain routing, databases — everything needs manual setup. High floor.&lt;/p&gt;

&lt;p&gt;Openship sits between the two: &lt;strong&gt;Vercel-level operating experience, but the application runs on your own server&lt;/strong&gt;. Push code, it builds a Docker image locally, transfers it over SSH, and OpenResty takes over routing and TLS. Your server, your data, your bill.&lt;/p&gt;

&lt;p&gt;12.3k Stars, Apache 2.0, completely free to self-host.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Openship's three deployment modes (desktop app / self-hosted server / Openship Cloud)&lt;/li&gt;
&lt;li&gt;The "build locally, deploy remotely" architecture and why it matters&lt;/li&gt;
&lt;li&gt;How Openship positions itself against Vercel/Netlify and Coolify/Dokploy&lt;/li&gt;
&lt;li&gt;The built-in services bundle: databases, mail server, object storage, backups&lt;/li&gt;
&lt;li&gt;Why "containers keep running after you delete a project" is a significant design decision&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of Docker&lt;/li&gt;
&lt;li&gt;Some experience deploying a web application&lt;/li&gt;
&lt;li&gt;Optional: familiarity with SSH and reverse proxies&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Is
&lt;/h3&gt;

&lt;p&gt;Openship is an &lt;strong&gt;open-source, self-hosted PaaS (Platform as a Service)&lt;/strong&gt;. It replicates the operational experience of commercial PaaS platforms (Vercel, Railway, Render) on your own infrastructure.&lt;/p&gt;

&lt;p&gt;The core promise is no vendor lock-in: Openship is built on open standards — Docker, OCI images, S3 protocol, SMTP, ACME (the Let's Encrypt certificate automation protocol). If you ever want to walk away from Openship, your containers, databases, and images are all in standard formats and can be migrated anywhere.&lt;/p&gt;

&lt;p&gt;One particularly notable design: &lt;strong&gt;when you delete an Openship project, the containers on your server keep running&lt;/strong&gt;. This is the opposite of most PaaS platforms, where deleting a project makes your deployment disappear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Author / Team
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: oblien (GitHub: &lt;a href="https://github.com/oblien" rel="noopener noreferrer"&gt;@oblien&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://openship.io" rel="noopener noreferrer"&gt;openship.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: Apache License 2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub Stars: &lt;strong&gt;12,300+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🍴 Forks: &lt;strong&gt;1,100+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: Apache 2.0 (free including commercial use)&lt;/li&gt;
&lt;li&gt;🌐 Website: &lt;a href="https://openship.io" rel="noopener noreferrer"&gt;openship.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Option A: Commercial PaaS (Vercel / Netlify / Railway)
  ✅ Push-to-deploy, excellent experience
  ✅ SSL / routing / CI fully automated
  ❌ App runs on vendor servers — no data sovereignty
  ❌ Traffic spikes = bill spikes
  ❌ Migrating away means moving everything

Option B: Roll your own Docker + Nginx + CI stack
  ✅ Full control, data stays with you
  ❌ SSL certs, reverse proxy, CI/CD, databases — all manual
  ❌ Everything needs individual maintenance

Option C: Openship
  ✅ Push-to-deploy (same experience as Vercel)
  ✅ SSL / routing / databases / email all built in
  ✅ App runs on your server
  ✅ Built on open standards — walk away anytime
  ✅ Self-hosting is completely free
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Three Ways to Run It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mode 1: Desktop App (solo developers)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Download the macOS / Windows / Linux desktop client. The control plane runs locally. It manages remote servers over SSH — no agent installation on the server, no control console exposed to the public internet.&lt;/p&gt;

&lt;p&gt;Best for: personal projects, side projects, not wanting to maintain extra infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mode 2: Self-Hosted Server (teams)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.openship.io | sh
openship   &lt;span class="c"&gt;# interactive wizard walks through setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the control plane on a server. Team members collaborate through the web dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mode 3: Openship Cloud (zero maintenance)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managed service starting at $10/month. You don't maintain the control plane, but the deployment target can still be your own servers (hybrid mode).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deployment Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Connect your repository (GitHub / GitLab / Bitbucket)

2. Push code
   ↓
3. Openship detects your stack (Node / Python / Go / …)
   ↓
4. Builds a Docker image on the local machine (not the production server)
   ↓
5. Transfers the image to the target server over SSH
   ↓
6. Container starts on the server
   ↓
7. OpenResty takes over routing + Let's Encrypt provisions SSL automatically
   ↓
App is live ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design point: &lt;strong&gt;building happens on the machine running the Openship console, not on the production server&lt;/strong&gt;. Production server resources aren't consumed by the build process, and a build failure doesn't affect anything running live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported Stacks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language / Runtime&lt;/th&gt;
&lt;th&gt;Databases&lt;/th&gt;
&lt;th&gt;Other Services&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Object storage (S3-compatible)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;Built-in mail server (SMTP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;Scheduled backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Let's Encrypt SSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Custom domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Private networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;DDoS protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Built-In Services Bundle
&lt;/h3&gt;

&lt;p&gt;This is where Openship pulls ahead of many comparable tools — no third-party service integrations required:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in mail server&lt;/strong&gt;: Full SMTP service with DKIM/SPF/DMARC support. No Mailgun or SendGrid subscription needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in databases&lt;/strong&gt;: Postgres, MySQL, MongoDB, Redis — one-click creation with automatic backups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled backups&lt;/strong&gt;: Automatic backups on a schedule, with one-click restore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSL certificates&lt;/strong&gt;: Let's Encrypt automatic provisioning and renewal, with wildcard domain support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interfaces
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desktop app (Mac/Windows)&lt;/td&gt;
&lt;td&gt;Solo developers, local control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web dashboard&lt;/td&gt;
&lt;td&gt;Team collaboration, browser access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;Scripting, DevOps pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Programmatic integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP endpoint&lt;/td&gt;
&lt;td&gt;AI agent calls (Claude Code, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  A Deeper Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Value of "Build Locally, Ship Remotely"
&lt;/h3&gt;

&lt;p&gt;Most self-hosted deployment platforms (Coolify, Dokploy, Caprover, etc.) build on the target server: pull code to the server, run the build process on the server.&lt;/p&gt;

&lt;p&gt;Openship inverts this: &lt;strong&gt;builds run on the machine hosting the Openship console&lt;/strong&gt;, and the resulting Docker image is transferred via SSH to the target server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coolify / Dokploy model:
  Code → target server (pull + build + run)
  ↑ Build process competes with live services for CPU/RAM
  ↑ Build failure can destabilize the server running production

Openship model:
  Code → console machine (build) → SSH transfer → target server (run only)
  ↑ Build and runtime are physically separated
  ↑ Target server resources are 100% for live traffic
  ↑ Build failures don't touch production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a resource-constrained VPS (say, a 2-core 4GB Hetzner box), this architectural difference is significant — the same hardware delivers more stable live service performance with Openship's model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The OpenResty Routing Layer
&lt;/h3&gt;

&lt;p&gt;Openship uses &lt;a href="https://openresty.org/" rel="noopener noreferrer"&gt;OpenResty&lt;/a&gt; (Nginx + Lua extensions) as its edge proxy, handling three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP routing&lt;/strong&gt;: mapping domains to the correct containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS termination&lt;/strong&gt;: Let's Encrypt certificate provisioning, renewal, and HTTPS handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-downtime cutover&lt;/strong&gt;: traffic only shifts from old to new container once the new one is healthy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's a notable design detail about the sequencing of TLS and routing: &lt;strong&gt;routing and TLS take over &lt;em&gt;after&lt;/em&gt; the application container is already running&lt;/strong&gt;. This means if DNS resolution or certificate provisioning encounters an issue, it surfaces in the console as "action required" rather than failing the entire deployment and rolling back — which makes root-cause debugging far easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Competitive Positioning
&lt;/h3&gt;

&lt;p&gt;Where Openship sits on the landscape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Commercial PaaS (Vercel / Railway / Render)
  ↑ Easiest experience, most expensive, no data sovereignty

Self-hosted PaaS (Coolify / Dokploy / Caprover)
  ↑ Self-controlled, free, but fewer built-ins, no desktop client

Openship
  ↑ Among self-hosted PaaS options: richest built-in services
     (mail server, backups, object storage)
  ↑ The only self-hosted PaaS with a desktop client
  ↑ The only self-hosted PaaS with an MCP endpoint
  ↑ Local-build architecture

Raw Docker + Nginx + CI DIY
  ↑ Most flexible, highest floor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Vercel&lt;/th&gt;
&lt;th&gt;Coolify&lt;/th&gt;
&lt;th&gt;Dokploy&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Openship&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data sovereignty&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push-to-deploy&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in mail server&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop client&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP endpoint&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local-build architecture&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containers survive project deletion&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free to self-host&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;40k+&lt;/td&gt;
&lt;td&gt;15k+&lt;/td&gt;
&lt;td&gt;12.3k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why "Containers Survive Project Deletion" Matters
&lt;/h3&gt;

&lt;p&gt;This design decision is a direct expression of Openship's core philosophy: &lt;strong&gt;the platform is a tool, not a chain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On Vercel, your deployment lifecycle is bound to the Vercel platform — delete a project, the deployment is gone, with no separation between them.&lt;/p&gt;

&lt;p&gt;Openship's logic: your containers run on your server. Openship manages them; it doesn't own them. Delete the project configuration in Openship, and the containers on your server keep running. You can operate them with standard Docker commands, or re-import them into Openship management later.&lt;/p&gt;

&lt;p&gt;This makes migrating or switching platforms nearly risk-free — the worst-case scenario is "back to manual management," not "service disappears."&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🌟 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/oblien/openship" rel="noopener noreferrer"&gt;https://github.com/oblien/openship&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://openship.io" rel="noopener noreferrer"&gt;https://openship.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://openship.io/docs" rel="noopener noreferrer"&gt;https://openship.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/oblien/openship/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/coollabsio/coolify" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt; — 40k Stars, the most feature-complete self-hosted PaaS alternative&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Dokploy/dokploy" rel="noopener noreferrer"&gt;Dokploy&lt;/a&gt; — 15k Stars, lightweight self-hosted deployment platform&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openresty.org/" rel="noopener noreferrer"&gt;OpenResty&lt;/a&gt; — The Nginx + Lua platform powering Openship's routing layer&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local-build architecture&lt;/strong&gt;: builds happen on the control console machine; the production server only runs containers — resources are separated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in services bundle&lt;/strong&gt;: databases, mail server, object storage, backups — no third-party service subscriptions required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop client&lt;/strong&gt;: solo developers can manage remote deployments over SSH without deploying an additional server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete = detach, not delete = destroy&lt;/strong&gt;: the platform manages your containers; it doesn't own them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP endpoint&lt;/strong&gt;: AI agents can call Openship directly to trigger deployment operations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Who This Is For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developers tired of Vercel bills&lt;/strong&gt;: want the same experience but with the app running on their own VPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home server / VPS tinkerers&lt;/strong&gt;: want a full PaaS experience instead of pure manual Docker ops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data-sovereignty-conscious teams&lt;/strong&gt;: not willing to put build artifacts and databases on a third-party platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI application developers&lt;/strong&gt;: need to let an Agent automatically trigger deployment pipelines via MCP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One-Line Verdict
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Openship unpacks a classic contradiction: you want Vercel's experience, but not Vercel's bill or vendor lock-in — it separates the two.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more useful knowledge and interesting products on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>openship</category>
      <category>selfhosted</category>
      <category>cicd</category>
    </item>
    <item>
      <title>DeepSeek Harness Series (08): Multi-Agent Collaboration — Subagents and Agent Teams</title>
      <dc:creator>WonderLab</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:23:28 +0000</pubDate>
      <link>https://dev.to/wonderlab/deepseek-harness-series-08-multi-agent-collaboration-subagents-and-agent-teams-46bn</link>
      <guid>https://dev.to/wonderlab/deepseek-harness-series-08-multi-agent-collaboration-subagents-and-agent-teams-46bn</guid>
      <description>&lt;h2&gt;
  
  
  A Problem One Tool Can't Solve
&lt;/h2&gt;

&lt;p&gt;Imagine this task: a comprehensive refactor of a large codebase — check naming conventions across every file, find all circular dependencies, clean up interface definitions. Over a thousand files.&lt;/p&gt;

&lt;p&gt;You could hand this to a single Agent and let it process files one by one. But the problems appear quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited context window&lt;/strong&gt;: by file 300, everything from the beginning has been pushed out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools have no reasoning&lt;/strong&gt;: a tool executes fixed logic — it can't adapt its strategy based on 'which subsystem does this file belong to?'&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools have no persistent conversation history&lt;/strong&gt;: a tool finishes and that's it; no state, no memory of what it saw last time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The essence of multi-Agent systems is delegating subtasks to another Agent that has its own independent conversation history. A subagent has its own Session, its own reasoning process, its own tools, and its own context window. The parent Agent only needs to say: 'process this batch of files and tell me what you find.'&lt;/p&gt;

&lt;p&gt;The difference from calling a tool is the difference between 'handing something off to another person' and 'starting a machine.'&lt;/p&gt;




&lt;h2&gt;
  
  
  dsh's Subagent Mechanism
&lt;/h2&gt;

&lt;p&gt;dsh implements Agent delegation via &lt;strong&gt;Subagents&lt;/strong&gt;. There are two ways to launch one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model-initiated&lt;/strong&gt;: when the model decides to delegate a subtask, it calls the built-in tool &lt;code&gt;subagent_spawn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code-initiated&lt;/strong&gt;: inside a tool's &lt;code&gt;execute&lt;/code&gt; function, call &lt;code&gt;ctx.subagents.start()&lt;/code&gt; directly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Launching a subagent requires a &lt;code&gt;SubagentStartRequest&lt;/code&gt;. The key fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified from SubagentStartRequest in packages/subagent/subagent/src/types.ts&lt;/span&gt;
&lt;span class="c1"&gt;// The actual type has more fields; these are the most important&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SubagentStartRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The subagent's initial prompt (ContentBlock array — supports text, images, etc.)&lt;/span&gt;
  &lt;span class="nl"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="c1"&gt;// The parent Agent object (provides working directory and lineage info)&lt;/span&gt;
  &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;

  &lt;span class="c1"&gt;// Cancellation signal (lets the parent abort the subagent)&lt;/span&gt;
  &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: restrict which tools the subagent can use&lt;/span&gt;
  &lt;span class="nx"&gt;toolFilter&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ToolRestriction&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: give the subagent a custom persona (overrides the global deployment persona)&lt;/span&gt;
  &lt;span class="nx"&gt;persona&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: structured output schema (makes the subagent return a JSON object instead of text)&lt;/span&gt;
  &lt;span class="nx"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ObjectJsonSchema&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: max delegation depth (prevents infinite recursion via nested subagents)&lt;/span&gt;
  &lt;span class="nx"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few fields deserve attention:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;toolFilter&lt;/code&gt;&lt;/strong&gt;: by default a subagent inherits all of its parent's tools, but in most cases you want to give it a restricted set. A code-analysis subagent only needs to read files — not write them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;persona&lt;/code&gt;&lt;/strong&gt;: a subagent can have its own system prompt prefix. You can give it a specialized role definition so it works within that role rather than inheriting the global persona.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;outputSchema&lt;/code&gt;&lt;/strong&gt;: instead of returning plain text, the subagent returns a structured JSON object. The parent Agent receives a type-safe &lt;code&gt;result.structured&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  One-Shot vs Continuable
&lt;/h2&gt;

&lt;p&gt;Subagents have two lifecycle modes, distinguished by whether the parent needs to talk to the subagent more than once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-shot Subagent&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Launch → subagent completes all its work → returns result → done. One round trip, suitable for independent subtasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuable Subagent&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;After launch, the subagent creates a persistent Session. The parent can keep sending it messages, and the subagent continues executing each time. Suitable for multi-turn collaborative work.&lt;/p&gt;

&lt;p&gt;The two modes compared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One-shot:
  Parent ──start()──► Child executes full task ──result──► Parent
  (subagent's lifecycle ends after completion)

Continuable:
  Parent ──startContinuable()──────► Child Session created, waits for input
  Parent ──sendMessage('batch 1')──► Child processes batch 1 ──result──► Parent
  Parent ──sendMessage('batch 2')──► Child processes batch 2 ──result──► Parent
  Parent ──sendMessage('summarize')► Child summarizes ──final result──► Parent
  (Child Session stays alive throughout, with a continuous conversation history)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage of a continuable subagent: its conversation history is unbroken. It remembers what it processed in earlier turns, and can draw on that memory when summarizing at the end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Communication Paths: Who Can Message Whom
&lt;/h2&gt;

&lt;p&gt;In a multi-Agent system, a natural question is: can Agents message each other freely?&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;no&lt;/strong&gt;. dsh enforces explicit constraints on communication paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Allowed?&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Parent → Child&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Requires the child's &lt;code&gt;parentSession&lt;/code&gt; to point to the parent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child → Parent&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Subagents can message their parent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling → Sibling&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Two children of the same parent cannot message each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grandparent → Grandchild&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Cross-generation messaging is rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       Parent
      /      \
  Child A   Child B
     |
   Grandchild

✅ Parent ──► Child A
✅ Child A ──► Parent
❌ Child A ──► Child B  (sibling messaging rejected)
❌ Parent ──► Grandchild  (cross-generation rejected)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a deliberate constraint. Allowing arbitrary messaging turns the Agent network into an untraceable message graph — when debugging, you'd have no idea where a message came from. A clear hierarchy means clear responsibility boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  toolFilter and persona
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tool Filtering
&lt;/h3&gt;

&lt;p&gt;Giving a subagent only the tools it actually needs is the most important access-control mechanism in a multi-Agent system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subagent can only use these three tools.&lt;/span&gt;
&lt;span class="c1"&gt;// Everything else the parent has (write_file, execute_shell, etc.) is invisible to it.&lt;/span&gt;
&lt;span class="nx"&gt;toolFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read_file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list_files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// You can also use deny to exclude specific tools:&lt;/span&gt;
  &lt;span class="c1"&gt;// deny: ['write_file', 'execute_shell']&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why bother? It's not only about security — it's about &lt;strong&gt;task focus&lt;/strong&gt;. A code-analysis subagent with &lt;code&gt;write_file&lt;/code&gt; in its tool list might 'helpfully' modify code it thinks is wrong. That's not the behavior you want. Give it read-only tools and it can only read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persona
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subagent uses a custom system prompt prefix.&lt;/span&gt;
&lt;span class="c1"&gt;// This overrides the global deployment persona,&lt;/span&gt;
&lt;span class="c1"&gt;// so the subagent operates within a specialized role.&lt;/span&gt;
&lt;span class="nx"&gt;persona&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a specialized test writer. Focus only on writing unit tests for the given code. Do not modify existing source files, do not suggest refactoring.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of persona: the parent Agent can be a general-purpose assistant, while the subagents it spawns are laser-focused experts. Each subagent gets a freshly defined role, unaffected by global configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structured Output (outputSchema)
&lt;/h2&gt;

&lt;p&gt;Subagents return text by default. But in automated pipelines, the parent Agent usually needs data it can process directly — not a paragraph of natural language.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;outputSchema&lt;/code&gt; solves this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Make the subagent return structured JSON — no text parsing needed (pseudocode)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subagents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Analyze this code and find all bugs.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;bugs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;major&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// result.text is the subagent's text output (if any)&lt;/span&gt;
&lt;span class="c1"&gt;// result.structured is a type-safe JSON object — use it directly, no parsing&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structured&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bugs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → [{ file: 'auth.ts', line: 42, severity: 'critical', description: '...' }, ...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps data flow clean across the multi-Agent system: the subagent's analysis becomes the parent Agent's decision input directly, with no extra parsing layer in between.&lt;/p&gt;




&lt;h2&gt;
  
  
  Six Subagent Providers
&lt;/h2&gt;

&lt;p&gt;dsh supports multiple underlying implementations for different deployment scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spawn-in-process&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a new Agent instance in the same process&lt;/td&gt;
&lt;td&gt;Local development, testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fork-in-process&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forks the current Session; subagent inherits the prefix history&lt;/td&gt;
&lt;td&gt;Subtasks that need to inherit context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dsh-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Launches an isolated runtime via the dsh SDK&lt;/td&gt;
&lt;td&gt;Subagents that need full isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Communicates with remote Agents via the ACP protocol&lt;/td&gt;
&lt;td&gt;Remote Agent clusters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calls a Codex Agent&lt;/td&gt;
&lt;td&gt;Code-specific tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calls Claude Code&lt;/td&gt;
&lt;td&gt;Code-specific tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most local development, &lt;code&gt;spawn-in-process&lt;/code&gt; is sufficient. When you need isolation — say, different subagents with different filesystem permissions — consider &lt;code&gt;dsh-sdk&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Experimental: Agent Teams
&lt;/h2&gt;

&lt;p&gt;Subagents follow a hierarchical model — parent-child relationships are explicit, communication paths are strict. But some collaboration patterns are inherently 'flat': multiple specialist Agents working on the same task in parallel, each reporting results as they finish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Teams&lt;/strong&gt; is dsh's work-in-progress multi-Agent collaboration framework, accessible via &lt;code&gt;ctx.agentTeams&lt;/code&gt; (experimental service):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;roster&lt;/strong&gt;: register team members, each with a name and role&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mailbox&lt;/strong&gt;: members send and receive messages via their mailbox&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;task board&lt;/strong&gt;: a shared task list the team can claim and submit work against&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;coordinator mode&lt;/strong&gt;: a coordinator Agent can distribute tasks and collect results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared to the Subagent hierarchy, Teams is closer to actual teamwork: no strict parent-child structure, members can communicate as peers, and tasks can be assigned dynamically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subagent model (hierarchical):       Agent Teams model (peer-to-peer):

     Orchestrator                        ┌──────────────────┐
    /     |      \                       │    task board     │
  Sub A  Sub B  Sub C                   └──────────────────┘
(strict parent-child,               Agent A ◄──► Agent B
 one-way delegation)                    │              │
                                        └────► Agent C ◄┘
                                        (peer messaging, dynamic collaboration)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Agent Teams is currently experimental. The API may change in future releases. Confirm version stability before using in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-On: Parent Agent Calling a Subagent
&lt;/h2&gt;

&lt;p&gt;Here's a complete pattern: a parent Agent defines a tool that internally launches a subagent to perform a specialized analysis task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: parent Agent tool that delegates to a subagent for code analysis&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyzeCodebaseTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analyze_codebase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Spawn a specialized subagent to analyze a given directory.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Directory to analyze&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Analysis focus (e.g., naming conventions, circular dependencies)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Launch the subagent (pseudocode — actual call is ctx.subagents.start())&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subagents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Analyze the directory &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Focus on: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Check each file and produce a structured issue list.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// pass the parent Agent to establish lineage&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// pass cancellation signal — if parent is cancelled, subagent stops too&lt;/span&gt;

      &lt;span class="c1"&gt;// Subagent can only read files, not modify code&lt;/span&gt;
      &lt;span class="na"&gt;toolFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read_file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list_files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;

      &lt;span class="c1"&gt;// Specialized persona: code review expert&lt;/span&gt;
      &lt;span class="na"&gt;persona&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a code review expert. Read code carefully, identify issues, and provide a structured summary. Do not modify any files.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// Require structured JSON output&lt;/span&gt;
      &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// result.structured is type-safe JSON — use it directly&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structured&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Analysis complete. Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; issue(s).\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core value of this pattern: the parent Agent doesn't need to know how the subagent analyzes code — it just receives structured results and makes its next decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison with Other Frameworks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Multi-Agent Model&lt;/th&gt;
&lt;th&gt;Key Difference from dsh&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Code Agent tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Also spawns subagents&lt;/td&gt;
&lt;td&gt;Simpler interface, but no &lt;code&gt;toolFilter&lt;/code&gt; / &lt;code&gt;persona&lt;/code&gt; / &lt;code&gt;outputSchema&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Graph-based workflow, fixed topology&lt;/td&gt;
&lt;td&gt;dsh Subagents are dynamic — the model decides when to delegate at runtime, not via a predefined graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AutoGen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conversational multi-Agent, Agents message each other directly&lt;/td&gt;
&lt;td&gt;dsh is hierarchical with explicit parent-child relationships; sibling Agents cannot communicate directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In short: LangGraph suits scenarios with fixed, predictable flows; dsh Subagents suit scenarios where the flow is determined dynamically by the model.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next in the Series
&lt;/h2&gt;

&lt;p&gt;The next article covers &lt;strong&gt;observability&lt;/strong&gt;: how do you know what an Agent did while it was running? What mechanisms does dsh provide for tracing tool calls, Token usage, and subagent lineage trees — and how do you hook into a logging system in production?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://primeskills.store" rel="noopener noreferrer"&gt;PrimeSkills&lt;/a&gt; — a curated marketplace of AI agents and skills validated in real-world, enterprise-grade workflows. Not demos — things that actually work in production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more on my &lt;a href="https://home.wonlab.top/en" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>dsh</category>
      <category>llm</category>
      <category>multiagent</category>
    </item>
  </channel>
</rss>
