<?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: Yehor Kaliberda</title>
    <description>The latest articles on DEV Community by Yehor Kaliberda (@yehorai).</description>
    <link>https://dev.to/yehorai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963213%2Fc3c7a21d-fe6f-4315-b87e-ec6dc840a886.jpeg</url>
      <title>DEV Community: Yehor Kaliberda</title>
      <link>https://dev.to/yehorai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yehorai"/>
    <language>en</language>
    <item>
      <title>How Symbiote Annotated a Python Repository Overnight — Without Breaking Anything</title>
      <dc:creator>Yehor Kaliberda</dc:creator>
      <pubDate>Tue, 02 Jun 2026 17:30:21 +0000</pubDate>
      <link>https://dev.to/yehorai/how-symbiote-annotated-a-python-repository-overnight-without-breaking-anything-40g3</link>
      <guid>https://dev.to/yehorai/how-symbiote-annotated-a-python-repository-overnight-without-breaking-anything-40g3</guid>
      <description>&lt;p&gt;&lt;em&gt;CallMed AI — Aarhus, Denmark&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We built Symbiote to solve one specific problem: adding PEP 484 type annotations to a Python codebase without touching runtime logic, breaking existing tests, or truncating docstrings.&lt;/p&gt;

&lt;p&gt;Here's what a real run looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The target: tablib
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jazzband/tablib" rel="noopener noreferrer"&gt;tablib&lt;/a&gt; is a Python library for working with tabular data — CSV, Excel, JSON, YAML. It's maintained by jazzband, has 4,600+ stars, and is used in production by thousands of teams. It predates PEP 484 and has partial annotation coverage in its core modules.&lt;/p&gt;

&lt;p&gt;We ran Symbiote's Mirror Pass on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Symbiote does
&lt;/h2&gt;

&lt;p&gt;The pipeline has three stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Perception&lt;/strong&gt; — Symbiote parses the repository into a typed AST dependency graph using tree-sitter. Every file, every function, every import edge is mapped. The output is a &lt;code&gt;PerceptionFrame&lt;/code&gt;: a typed snapshot of what the codebase looks like right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reasoning&lt;/strong&gt; — A PlannerAgent ingests the AST graph and computes a blast radius for each proposed change. Before any file is touched, Symbiote knows which other files import it and would be affected. This is how we avoid cascading breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Action&lt;/strong&gt; — The Mirror agent rewrites each file under Bouncer-mediated file locks. Two agents cannot modify overlapping dependency chains simultaneously. Every lock event is logged to &lt;code&gt;kernel.log&lt;/code&gt; with monotonic-nanosecond timestamps. The run ends with a verdict: &lt;code&gt;COLLISION-FREE ✓&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All changes go onto a dedicated &lt;code&gt;symbiote/plan-{id}&lt;/code&gt; branch. We never push to main.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the PR looks like
&lt;/h2&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/yehorcallmedai-maker/tablib/pull/642" rel="noopener noreferrer"&gt;yehorcallmedai-maker/tablib #642&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diff adds typed signatures to public functions in &lt;code&gt;_csv.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changes: strictly additive. No logic modifications. Existing docstrings, inline comments, and doctests preserved verbatim.&lt;/p&gt;

&lt;p&gt;CI result after our push: &lt;code&gt;pre-commit.ci&lt;/code&gt; green ✅. The &lt;code&gt;ruff UP007&lt;/code&gt; lint rule (replace &lt;code&gt;Optional[str]&lt;/code&gt; with &lt;code&gt;str | None&lt;/code&gt;) was caught and fixed automatically.&lt;/p&gt;

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

&lt;p&gt;Static analysis is only as useful as annotation coverage. A codebase at 30% coverage gives mypy incomplete information — it falls back to &lt;code&gt;Any&lt;/code&gt; for unannotated parameters, which silently defeats the type checker.&lt;/p&gt;

&lt;p&gt;The Mirror Pass is designed to move that number. Not by guessing types, but by reading the AST, tracing the call graph, and writing annotations that are correct given what the code actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symbiote's safety model
&lt;/h2&gt;

&lt;p&gt;The reason we can run this autonomously without fear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bouncer locks&lt;/strong&gt;: every write is preceded by a granted file lock with transitive blast-radius checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additive-only&lt;/strong&gt;: if a function's existing docstring would shrink by more than 20% as a result of a rewrite, the write is aborted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch isolation&lt;/strong&gt;: worst-case rollback is &lt;code&gt;git branch -D symbiote/plan-{id}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full audit trail&lt;/strong&gt;: &lt;code&gt;kernel.log&lt;/code&gt; records every lock acquire, wait, release, and denial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The run on tablib's &lt;code&gt;_csv.py&lt;/code&gt; completed collision-free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The v2 architecture replaces full-file LLM rewrites with an AST-aware patch-merge model — changes are spliced at the &lt;code&gt;ParsedNode&lt;/code&gt; level. This eliminates context-budget pressure on large, well-documented files before we ship docstring generation at scale.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;CallMed AI&lt;/strong&gt; runs autonomous Python codebase audits and delivers them as reviewable Pull Requests. The tip-of-spear service is the Mirror Pass: PEP 484 type annotations across your entire Python repository, $1,500 flat, PR within 72 hours.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://callmedai.com" rel="noopener noreferrer"&gt;callmedai.com&lt;/a&gt; · &lt;a href="mailto:yehor@callmedai.com"&gt;yehor@callmedai.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
      <category>devtools</category>
    </item>
    <item>
      <title>We Ran Symbiote on smolagents — Here's What Changed</title>
      <dc:creator>Yehor Kaliberda</dc:creator>
      <pubDate>Mon, 01 Jun 2026 18:31:40 +0000</pubDate>
      <link>https://dev.to/yehorai/we-ran-symbiote-on-smolagents-heres-what-changed-2hcl</link>
      <guid>https://dev.to/yehorai/we-ran-symbiote-on-smolagents-heres-what-changed-2hcl</guid>
      <description>&lt;h1&gt;
  
  
  We Ran Symbiote on smolagents — Here's What Changed
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Yehor Kaliberda, CallMed AI · June 2026&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/huggingface/smolagents" rel="noopener noreferrer"&gt;smolagents&lt;/a&gt; is one of HuggingFace's most elegant libraries — a barebones agent framework where tools are defined by Python functions with type hints.&lt;br&gt;
The design is intentional: the &lt;code&gt;@tool&lt;/code&gt; decorator reads your function's type annotations and docstring to auto-generate the tool's schema for the LLM.&lt;/p&gt;

&lt;p&gt;Which makes it ironic that the &lt;em&gt;library's own core class&lt;/em&gt; — &lt;code&gt;Tool&lt;/code&gt; in &lt;code&gt;tools.py&lt;/code&gt; — has 16 methods with missing type annotations.&lt;/p&gt;

&lt;p&gt;We ran &lt;a href="https://callmedai.com" rel="noopener noreferrer"&gt;Symbiote&lt;/a&gt; on it to fix that.&lt;/p&gt;




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

&lt;p&gt;Symbiote is an autonomous codebase evolution engine.&lt;br&gt;
The pipeline runs in three stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Perception&lt;/strong&gt; — tree-sitter AST extraction produces a typed symbol graph of every class, method, and import in the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning&lt;/strong&gt; — a dependency graph (graph.json) calculates the &lt;em&gt;blast radius&lt;/em&gt; of each file before touching it, so Symbiote never modifies something that could break imports elsewhere unexpectedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; — a structured LLM rewrite (Pydantic-validated &lt;code&gt;CodeModification&lt;/code&gt;, full-file, no truncation) runs on a dedicated &lt;code&gt;symbiote/plan-*&lt;/code&gt; git branch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output is a reviewable PR. Maintainers merge only what they approve.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Found in smolagents/tools.py
&lt;/h2&gt;

&lt;p&gt;Running the Symbiote perception cycle on the smolagents repo surfaced 16 annotation targets in &lt;code&gt;tools.py&lt;/code&gt; alone:&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;What was missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validate_after_init(cls)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cls: type&lt;/code&gt;, &lt;code&gt;-&amp;gt; type&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;*args: Any&lt;/code&gt;, &lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; None&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.__init_subclass__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; None&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.validate_arguments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-&amp;gt; None&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;*args: Any&lt;/code&gt;, &lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; Any&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.__call__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;*args: Any&lt;/code&gt;, &lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; Any&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.save&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&amp;gt; None&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.from_hub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.from_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;, docstring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.from_space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.from_gradio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gradio_tool: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tool.from_langchain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;langchain_tool: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;launch_gradio_demo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&amp;gt; None&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;load_tool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;repo_id: str&lt;/code&gt;, &lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;code&gt;-&amp;gt; "Tool"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;description: str&lt;/code&gt;, &lt;code&gt;-&amp;gt; Callable&lt;/code&gt;, inner types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ToolCollection.__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&amp;gt; None&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All of these are &lt;strong&gt;additive metadata&lt;/strong&gt; — zero behaviour change, PEP 484 compliant, PEP 8 preserved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for smolagents Specifically
&lt;/h2&gt;

&lt;p&gt;smolagents is built around the idea that type hints &lt;em&gt;are&lt;/em&gt; the API.&lt;br&gt;
The &lt;code&gt;@tool&lt;/code&gt; decorator calls &lt;code&gt;_convert_type_hints_to_json_schema&lt;/code&gt; under the hood to build the LLM tool schema.&lt;br&gt;
If the base &lt;code&gt;Tool&lt;/code&gt; class itself is inconsistently annotated, static analysis tools (mypy, pyright, basedpyright) surface false positives when subclassing it, making the developer experience worse.&lt;/p&gt;

&lt;p&gt;After the patch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mypy --strict&lt;/code&gt; on &lt;code&gt;tools.py&lt;/code&gt; goes from 16 errors to 0&lt;/li&gt;
&lt;li&gt;IDE autocomplete correctly infers return types from all class methods&lt;/li&gt;
&lt;li&gt;The class docstring example in the smolagents docs becomes type-checkable end-to-end&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;

&lt;p&gt;The changes are on our fork at &lt;code&gt;github.com/yehorcallmedai-maker/smolagents&lt;/code&gt;, branch &lt;code&gt;symbiote/plan-smolagents-tools&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The patch was generated by &lt;code&gt;symbiote_smolagents_patch.py&lt;/code&gt; — a deterministic replacement script with one targeted substitution per annotation target, making the diff clean and easy to review.&lt;/p&gt;

&lt;p&gt;Diff stats: &lt;strong&gt;+87 lines, −12 lines&lt;/strong&gt; across &lt;code&gt;tools.py&lt;/code&gt; only.&lt;br&gt;
No tests were modified. The existing test suite passes unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Symbiote's next pass will target &lt;code&gt;src/smolagents/models.py&lt;/code&gt; and &lt;code&gt;src/smolagents/agents.py&lt;/code&gt;, where similar annotation gaps exist in the model wrapper classes.&lt;/p&gt;

&lt;p&gt;If you maintain a Python library and want a Symbiote pass on your codebase — open-source or private — reach out at &lt;a href="https://callmedai.com" rel="noopener noreferrer"&gt;callmedai.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;CallMed AI builds autonomous tooling for codebase evolution and AI compliance.&lt;br&gt;
Symbiote is our open-architecture agent for Python annotation and documentation at scale.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Live PR: &lt;a href="https://github.com/huggingface/smolagents/pull/2333" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/pull/2333&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
