DEV Community

Yehor Kaliberda
Yehor Kaliberda

Posted on

How Symbiote Annotated a Python Repository Overnight — Without Breaking Anything

CallMed AI — Aarhus, Denmark


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.

Here's what a real run looks like.

The target: tablib

tablib 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.

We ran Symbiote's Mirror Pass on it.

What Symbiote does

The pipeline has three stages:

1. Perception — 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 PerceptionFrame: a typed snapshot of what the codebase looks like right now.

2. Reasoning — 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.

3. Action — 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 kernel.log with monotonic-nanosecond timestamps. The run ends with a verdict: COLLISION-FREE ✓.

All changes go onto a dedicated symbiote/plan-{id} branch. We never push to main.

What the PR looks like

PR: yehorcallmedai-maker/tablib #642

The diff adds typed signatures to public functions in _csv.py:

# Before
def export_set(dataset, **kwargs):
    ...

# After
def export_set(dataset: Dataset, **kwargs: Any) -> str:
    ...
Enter fullscreen mode Exit fullscreen mode

Changes: strictly additive. No logic modifications. Existing docstrings, inline comments, and doctests preserved verbatim.

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

Why this matters

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

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.

Symbiote's safety model

The reason we can run this autonomously without fear:

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

The run on tablib's _csv.py completed collision-free.

What's next

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


CallMed AI 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.

callmedai.com · yehor@callmedai.com

Top comments (0)